1261 lines
50 KiB
Plaintext
1261 lines
50 KiB
Plaintext
<?php
|
|
|
|
/**
|
|
* @file
|
|
* Add language handling functionality and enables the translation of the
|
|
* user interface to languages other than English.
|
|
*
|
|
* When enabled, multiple languages can be set up. The site interface
|
|
* can be displayed in different languages, as well as nodes can have languages
|
|
* assigned. The setup of languages and translations is completely web based.
|
|
* Gettext portable object files are supported.
|
|
*/
|
|
|
|
/**
|
|
* Regular expression pattern used to localize JavaScript strings.
|
|
*/
|
|
const LOCALE_JS_STRING = '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+';
|
|
|
|
/**
|
|
* Regular expression pattern used to match simple JS object literal.
|
|
*
|
|
* This pattern matches a basic JS object, but will fail on an object with
|
|
* nested objects. Used in JS file parsing for string arg processing.
|
|
*/
|
|
const LOCALE_JS_OBJECT = '\{.*?\}';
|
|
|
|
/**
|
|
* Regular expression to match an object containing a key 'context'.
|
|
*
|
|
* Pattern to match a JS object containing a 'context key' with a string value,
|
|
* which is captured. Will fail if there are nested objects.
|
|
*/
|
|
define('LOCALE_JS_OBJECT_CONTEXT', '
|
|
\{ # match object literal start
|
|
.*? # match anything, non-greedy
|
|
(?: # match a form of "context"
|
|
\'context\'
|
|
|
|
|
"context"
|
|
|
|
|
context
|
|
)
|
|
\s*:\s* # match key-value separator ":"
|
|
(' . LOCALE_JS_STRING . ') # match context string
|
|
.*? # match anything, non-greedy
|
|
\} # match end of object literal
|
|
');
|
|
|
|
/**
|
|
* Flag for locally not customized interface translation.
|
|
*
|
|
* Such translations are imported from .po files downloaded from
|
|
* localize.drupal.org for example.
|
|
*/
|
|
const LOCALE_NOT_CUSTOMIZED = 0;
|
|
|
|
/**
|
|
* Flag for locally customized interface translation.
|
|
*
|
|
* Such translations are edited from their imported originals on the user
|
|
* interface or are imported as customized.
|
|
*/
|
|
const LOCALE_CUSTOMIZED = 1;
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// Hook implementations
|
|
|
|
/**
|
|
* Implements hook_help().
|
|
*/
|
|
function locale_help($path, $arg) {
|
|
switch ($path) {
|
|
case 'admin/help#locale':
|
|
$output = '';
|
|
$output .= '<h3>' . t('About') . '</h3>';
|
|
$output .= '<p>' . t('The Locale module allows your Drupal site to be presented in languages other than the default English, and to be multilingual. The Locale module works by maintaining a database of translations, and examining text as it is about to be displayed. When a translation of the text is available in the language to be displayed, the translation is displayed rather than the original text. When a translation is unavailable, the original text is displayed, and then stored for review by a translator. For more information, see the online handbook entry for <a href="@locale">Locale module</a>.', array('@locale' => 'http://drupal.org/documentation/modules/locale/')) . '</p>';
|
|
$output .= '<h3>' . t('Uses') . '</h3>';
|
|
$output .= '<dl>';
|
|
$output .= '<dt>' . t('Translating interface text') . '</dt>';
|
|
$output .= '<dd>' . t('Translations of text in the Drupal interface may be provided by:');
|
|
$output .= '<ul>';
|
|
$output .= '<li>' . t("Translating within your site, using the Locale module's integrated <a href='@translate'>translation interface</a>.", array('@translate' => url('admin/config/regional/translate'))) . '</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 contains 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 translations page</a>.', array('@translations' => 'http://localize.drupal.org')) . '</li>';
|
|
$output .= '<li>' . 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/config/regional/translate/import'), '@export' => url('admin/config/regional/translate/export'))) . '</li>';
|
|
$output .= '</ul></dd>';
|
|
$output .= '</dl>';
|
|
return $output;
|
|
|
|
case 'admin/config/regional/language':
|
|
return '<p>' . t('Interface text can be translated. <a href="@translations">Download contributed translations</a> from Drupal.org.', array('@translations' => 'http://localize.drupal.org')) . '</p>';
|
|
|
|
case 'admin/config/regional/translate':
|
|
$output = '<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 in a specific language.', array('@export' => url('admin/config/regional/translate/export'))) . '</p>';
|
|
return $output;
|
|
|
|
case 'admin/config/regional/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/config/regional/language/add'), '@translations' => 'http://localize.drupal.org')) . '</p>';
|
|
return $output;
|
|
|
|
case 'admin/config/regional/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>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_menu().
|
|
*/
|
|
function locale_menu() {
|
|
// Translation functionality.
|
|
$items['admin/config/regional/translate'] = array(
|
|
'title' => 'User interface translation',
|
|
'description' => 'Translate the built-in user interface.',
|
|
'page callback' => 'locale_translate_seek_screen',
|
|
'access arguments' => array('translate interface'),
|
|
'file' => 'locale.pages.inc',
|
|
'weight' => -5,
|
|
);
|
|
$items['admin/config/regional/translate/translate'] = array(
|
|
'title' => 'Translate',
|
|
'weight' => -10,
|
|
'type' => MENU_DEFAULT_LOCAL_TASK,
|
|
);
|
|
$items['admin/config/regional/translate/import'] = array(
|
|
'title' => 'Import',
|
|
'page callback' => 'drupal_get_form',
|
|
'page arguments' => array('locale_translate_import_form'),
|
|
'access arguments' => array('translate interface'),
|
|
'weight' => 20,
|
|
'type' => MENU_LOCAL_TASK,
|
|
'file' => 'locale.bulk.inc',
|
|
);
|
|
$items['admin/config/regional/translate/export'] = array(
|
|
'title' => 'Export',
|
|
'page callback' => 'drupal_get_form',
|
|
'page arguments' => array('locale_translate_export_form'),
|
|
'access arguments' => array('translate interface'),
|
|
'weight' => 30,
|
|
'type' => MENU_LOCAL_TASK,
|
|
'file' => 'locale.bulk.inc',
|
|
);
|
|
$items['admin/config/regional/translate/edit/%'] = array(
|
|
'title' => 'Edit string',
|
|
'page callback' => 'drupal_get_form',
|
|
'page arguments' => array('locale_translate_edit_form', 5),
|
|
'access arguments' => array('translate interface'),
|
|
'file' => 'locale.pages.inc',
|
|
);
|
|
$items['admin/config/regional/translate/delete/%'] = array(
|
|
'title' => 'Delete string',
|
|
'page callback' => 'locale_translate_delete_page',
|
|
'page arguments' => array(5),
|
|
'access arguments' => array('translate interface'),
|
|
'file' => 'locale.pages.inc',
|
|
);
|
|
|
|
// 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.admin.inc',
|
|
);
|
|
$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'),
|
|
'file' => 'locale.admin.inc',
|
|
);
|
|
$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'),
|
|
'file' => 'locale.admin.inc',
|
|
);
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_init().
|
|
*
|
|
* Initialize date formats according to the user's current locale.
|
|
*/
|
|
function locale_init() {
|
|
global $conf, $language_interface;
|
|
|
|
// 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_interface->langcode);
|
|
|
|
// Setup appropriate date formats for this locale.
|
|
$formats = locale_get_localized_date_format($languages);
|
|
foreach ($formats as $format_type => $format) {
|
|
$conf[$format_type] = $format;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_permission().
|
|
*/
|
|
function locale_permission() {
|
|
return array(
|
|
'translate interface' => array(
|
|
'title' => t('Translate interface texts'),
|
|
'restrict access' => TRUE,
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Form builder callback to display language selection widget.
|
|
*
|
|
* @ingroup forms
|
|
* @see locale_form_alter()
|
|
*/
|
|
function locale_language_selector_form($user) {
|
|
global $language_interface;
|
|
// Get list of enabled languages only.
|
|
$languages = language_list(TRUE);
|
|
|
|
// If the user is being created, we set the user language to the page language.
|
|
$user_preferred_language = $user->uid ? user_preferred_language($user) : $language_interface;
|
|
|
|
$names = array();
|
|
foreach ($languages as $langcode => $item) {
|
|
$names[$langcode] = $item->name;
|
|
}
|
|
// Get language negotiation settings.
|
|
$mode = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_DEFAULT;
|
|
$form['locale'] = array(
|
|
'#type' => 'fieldset',
|
|
'#title' => t('Language settings'),
|
|
'#weight' => 1,
|
|
);
|
|
$form['locale']['preferred_langcode'] = array(
|
|
'#type' => (count($names) <= 5 ? 'radios' : 'select'),
|
|
'#title' => t('Language'),
|
|
'#default_value' => $user_preferred_language->langcode,
|
|
'#options' => $names,
|
|
'#description' => $mode ? t("This account's preferred language for e-mails and site presentation.") : t("This account's preferred language for e-mails."),
|
|
);
|
|
// User entities contain both a langcode property (for identifying the
|
|
// language of the entity data) and a preferred_langcode property (see above).
|
|
// Rather than provide a UI forcing the user to choose both separately,
|
|
// assume that the user profile data is in the user's preferred language. This
|
|
// element provides that synchronization. For use-cases where this
|
|
// synchronization is not desired, a module can alter or remove this element.
|
|
$form['locale']['langcode'] = array(
|
|
'#type' => 'value',
|
|
'#value_callback' => '_locale_language_selector_langcode_value',
|
|
// For the synchronization to work, this element must have a larger weight
|
|
// than the preferred_langcode element. Set a large weight here in case
|
|
// a module alters the weight of the other element.
|
|
'#weight' => 100,
|
|
);
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Sets the value of the user register and profile forms' langcode element.
|
|
*
|
|
* @see locale_language_selector_form()
|
|
*/
|
|
function _locale_language_selector_langcode_value($element, $input, &$form_state) {
|
|
$form_state['complete_form']['locale']['preferred_langcode']['#description'] .= ' ' . t("This is also assumed to be the primary language of this account's profile information.");
|
|
return $form_state['values']['preferred_langcode'];
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_alter().
|
|
*
|
|
* Adds language fields to user forms.
|
|
*/
|
|
function locale_form_alter(&$form, &$form_state, $form_id) {
|
|
// Only alter user forms if there is more than one language.
|
|
if (language_multilingual()) {
|
|
// Display language selector when either creating a user on the admin
|
|
// interface or editing a user account.
|
|
if ($form_id == 'user_register_form' || $form_id == 'user_profile_form') {
|
|
$selector = locale_language_selector_form($form['#user']);
|
|
if ($form_id == 'user_register_form') {
|
|
$selector['locale']['#access'] = user_access('administer users');
|
|
}
|
|
$form += $selector;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_BASE_FORM_ID_alter().
|
|
*/
|
|
function locale_form_node_form_alter(&$form, &$form_state) {
|
|
$form['#submit'][] = 'locale_field_node_form_submit';
|
|
}
|
|
|
|
/**
|
|
* Form submit handler for node_form().
|
|
*
|
|
* Checks if Locale is registered as a translation handler and handle possible
|
|
* node language changes.
|
|
*
|
|
* This submit handler needs to run before entity_form_submit_build_entity()
|
|
* is invoked by node_form_submit_build_node(), because it alters the values of
|
|
* attached fields. Therefore, it cannot be a hook_node_submit() implementation.
|
|
*/
|
|
function locale_field_node_form_submit($form, &$form_state) {
|
|
if (field_has_translation_handler('node', 'locale')) {
|
|
$node = (object) $form_state['values'];
|
|
$available_languages = field_content_languages();
|
|
list(, , $bundle) = entity_extract_ids('node', $node);
|
|
|
|
foreach (field_info_instances('node', $bundle) as $instance) {
|
|
$field_name = $instance['field_name'];
|
|
$field = field_info_field($field_name);
|
|
$previous_langcode = $form[$field_name]['#language'];
|
|
|
|
// Handle a possible language change: new language values are inserted,
|
|
// previous ones are deleted.
|
|
if ($field['translatable'] && $previous_langcode != $node->langcode) {
|
|
$form_state['values'][$field_name][$node->langcode] = $node->{$field_name}[$previous_langcode];
|
|
$form_state['values'][$field_name][$previous_langcode] = array();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_theme().
|
|
*/
|
|
function locale_theme() {
|
|
return array(
|
|
'locale_date_format_form' => array(
|
|
'render element' => 'form',
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_field_language_alter().
|
|
*/
|
|
function locale_field_language_alter(&$display_langcode, $context) {
|
|
// Do not apply core language fallback rules if they are disabled or if Locale
|
|
// is not registered as a translation handler.
|
|
if (variable_get('locale_field_language_fallback', TRUE) && field_has_translation_handler($context['entity_type'], 'locale')) {
|
|
locale_field_language_fallback($display_langcode, $context['entity'], $context['langcode']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies language fallback rules to the fields attached to the given entity.
|
|
*
|
|
* Core language fallback rules simply check if fields have a field translation
|
|
* for the requested language code. If so the requested language is returned,
|
|
* otherwise all the fallback candidates are inspected to see if there is a
|
|
* field translation available in another language.
|
|
* By default this is called by locale_field_language_alter(), but this
|
|
* behavior can be disabled by setting the 'locale_field_language_fallback'
|
|
* variable to FALSE.
|
|
*
|
|
* @param $field_langcodes
|
|
* A reference to an array of language codes keyed by field name.
|
|
* @param $entity
|
|
* The entity to be displayed.
|
|
* @param $langcode
|
|
* The language code $entity has to be displayed in.
|
|
*/
|
|
function locale_field_language_fallback(&$field_langcodes, $entity, $langcode) {
|
|
// Lazily init fallback candidates to avoid unnecessary calls.
|
|
$fallback_candidates = NULL;
|
|
$field_languages = array();
|
|
|
|
foreach ($field_langcodes as $field_name => $field_langcode) {
|
|
// If the requested language is defined for the current field use it,
|
|
// otherwise search for a fallback value among the fallback candidates.
|
|
if (isset($entity->{$field_name}[$langcode])) {
|
|
$field_langcodes[$field_name] = $langcode;
|
|
}
|
|
elseif (!empty($entity->{$field_name})) {
|
|
if (!isset($fallback_candidates)) {
|
|
require_once DRUPAL_ROOT . '/core/includes/language.inc';
|
|
$fallback_candidates = language_fallback_get_candidates();
|
|
}
|
|
foreach ($fallback_candidates as $fallback_langcode) {
|
|
if (isset($entity->{$field_name}[$fallback_langcode])) {
|
|
$field_langcodes[$field_name] = $fallback_langcode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_entity_info_alter().
|
|
*/
|
|
function locale_entity_info_alter(&$entity_info) {
|
|
$entity_info['node']['translation']['locale'] = TRUE;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_language_insert().
|
|
*/
|
|
function locale_language_insert($language) {
|
|
// @todo move these two cache clears out. See http://drupal.org/node/1293252
|
|
// Changing the language settings impacts the interface.
|
|
cache('page')->flush();
|
|
// Force JavaScript translation file re-creation for the new language.
|
|
_locale_invalidate_js($language->langcode);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_language_update().
|
|
*/
|
|
function locale_language_update($language) {
|
|
// @todo move these two cache clears out. See http://drupal.org/node/1293252
|
|
// Changing the language settings impacts the interface.
|
|
cache('page')->flush();
|
|
// Force JavaScript translation file re-creation for the modified language.
|
|
_locale_invalidate_js($language->langcode);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_language_delete().
|
|
*/
|
|
function locale_language_delete($language) {
|
|
// Remove translations.
|
|
db_delete('locales_target')
|
|
->condition('language', $language->langcode)
|
|
->execute();
|
|
|
|
_locale_invalidate_js($language->langcode);
|
|
|
|
// Changing the language settings impacts the interface:
|
|
cache('page')->flush();
|
|
|
|
// Clearing all locale cache from database
|
|
cache()->delete('locale:' . $language->langcode);
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// Locale core functionality
|
|
|
|
/**
|
|
* Provides interface translation services.
|
|
*
|
|
* This function is called from t() to translate a string if needed.
|
|
*
|
|
* @param $string
|
|
* A string to look up translation for. If omitted, all the
|
|
* cached strings will be returned in all languages already
|
|
* used on the page.
|
|
* @param $context
|
|
* The context of this string.
|
|
* @param $langcode
|
|
* Language code to use for the lookup.
|
|
*/
|
|
function locale($string = NULL, $context = NULL, $langcode = NULL) {
|
|
global $language_interface;
|
|
|
|
// Use the advanced drupal_static() pattern, since this is called very often.
|
|
static $drupal_static_fast;
|
|
if (!isset($drupal_static_fast)) {
|
|
$drupal_static_fast['locale'] = &drupal_static(__FUNCTION__);
|
|
}
|
|
$locale_t = &$drupal_static_fast['locale'];
|
|
|
|
|
|
if (!isset($string)) {
|
|
// Return all cached strings if no string was specified
|
|
return $locale_t;
|
|
}
|
|
|
|
$langcode = isset($langcode) ? $langcode : $language_interface->langcode;
|
|
|
|
// Store database cached translations in a static variable. Only build the
|
|
// cache after $language_interface has been set to avoid an unnecessary cache
|
|
// rebuild.
|
|
if (!isset($locale_t[$langcode]) && isset($language_interface)) {
|
|
$locale_t[$langcode] = array();
|
|
// Disabling the usage of string caching allows a module to watch for
|
|
// the exact list of strings used on a page. From a performance
|
|
// perspective that is a really bad idea, so we have no user
|
|
// interface for this. Be careful when turning this option off!
|
|
if (variable_get('locale_cache_strings', 1) == 1) {
|
|
if ($cache = cache()->get('locale:' . $langcode)) {
|
|
$locale_t[$langcode] = $cache->data;
|
|
}
|
|
elseif (lock_acquire('locale_cache_' . $langcode)) {
|
|
// Refresh database stored cache of translations for given language.
|
|
// We only store short strings used in current version, to improve
|
|
// performance and consume less memory.
|
|
$result = db_query("SELECT s.source, s.context, t.translation, t.language FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.version = :version AND LENGTH(s.source) < :length", array(':language' => $langcode, ':version' => VERSION, ':length' => variable_get('locale_cache_length', 75)));
|
|
foreach ($result as $data) {
|
|
$locale_t[$langcode][$data->context][$data->source] = (empty($data->translation) ? TRUE : $data->translation);
|
|
}
|
|
cache()->set('locale:' . $langcode, $locale_t[$langcode]);
|
|
lock_release('locale_cache_' . $langcode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we have the translation cached, skip checking the database
|
|
if (!isset($locale_t[$langcode][$context][$string])) {
|
|
|
|
// We do not have this translation cached, so get it from the DB.
|
|
$translation = db_query("SELECT s.lid, t.translation, s.version FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.source = :source AND s.context = :context", array(
|
|
':language' => $langcode,
|
|
':source' => $string,
|
|
':context' => (string) $context,
|
|
))->fetchObject();
|
|
if ($translation) {
|
|
// We have the source string at least.
|
|
// Cache translation string or TRUE if no translation exists.
|
|
$locale_t[$langcode][$context][$string] = (empty($translation->translation) ? TRUE : $translation->translation);
|
|
|
|
if ($translation->version != VERSION) {
|
|
// This is the first use of this string under current Drupal version. Save version
|
|
// and clear cache, to include the string into caching next time. Saved version is
|
|
// also a string-history information for later pruning of the tables.
|
|
db_update('locales_source')
|
|
->fields(array('version' => VERSION))
|
|
->condition('lid', $translation->lid)
|
|
->execute();
|
|
cache()->deletePrefix('locale:');
|
|
}
|
|
}
|
|
else {
|
|
// We don't have the source string, cache this as untranslated.
|
|
db_merge('locales_source')
|
|
->insertFields(array(
|
|
'location' => request_uri(),
|
|
'version' => VERSION,
|
|
))
|
|
->key(array(
|
|
'source' => $string,
|
|
'context' => (string) $context,
|
|
))
|
|
->execute();
|
|
$locale_t[$langcode][$context][$string] = TRUE;
|
|
// Clear locale cache so this string can be added in a later request.
|
|
cache()->deletePrefix('locale:');
|
|
}
|
|
}
|
|
|
|
return ($locale_t[$langcode][$context][$string] === TRUE ? $string : $locale_t[$langcode][$context][$string]);
|
|
}
|
|
|
|
/**
|
|
* Reset static variables used by locale().
|
|
*/
|
|
function locale_reset() {
|
|
drupal_static_reset('locale');
|
|
}
|
|
|
|
/**
|
|
* Returns plural form index for a specific number.
|
|
*
|
|
* The index is computed from the formula of this language.
|
|
*
|
|
* @param $count
|
|
* Number to return plural for.
|
|
* @param $langcode
|
|
* Optional language code to translate to a language other than
|
|
* what is used to display the page.
|
|
* @return
|
|
* The numeric index of the plural variant to use for this $langcode and
|
|
* $count combination or -1 if the language was not found or does not have a
|
|
* plural formula.
|
|
*/
|
|
function locale_get_plural($count, $langcode = NULL) {
|
|
global $language_interface;
|
|
|
|
// Used to locally cache the plural formulas for all languages.
|
|
$plural_formulas = &drupal_static(__FUNCTION__, array());
|
|
// Used to store precomputed plural indexes corresponding to numbers
|
|
// individually for each language.
|
|
$plural_indexes = &drupal_static(__FUNCTION__ . ':plurals', array());
|
|
|
|
$langcode = $langcode ? $langcode : $language_interface->langcode;
|
|
|
|
if (!isset($plural_indexes[$langcode][$count])) {
|
|
// Retrieve and statically cache the plural formulas for all languages.
|
|
if (empty($plural_formulas)) {
|
|
$plural_formulas = variable_get('locale_translation_plurals', array());
|
|
}
|
|
// If there is a plural formula for the language, evaluate it for the given
|
|
// $count and statically cache the result for the combination of language
|
|
// and count, since the result will always be identical.
|
|
if (!empty($plural_formulas[$langcode])) {
|
|
// $n is used inside the expression in the eval().
|
|
$n = $count;
|
|
$plural_indexes[$langcode][$count] = @eval('return intval(' . $plural_formulas[$langcode]['formula'] . ');');
|
|
}
|
|
// In case there is no plural formula for English (no imported translation
|
|
// for English), use a default formula.
|
|
elseif ($langcode == 'en') {
|
|
$plural_indexes[$langcode][$count] = (int) ($count != 1);
|
|
}
|
|
// Otherwise, return -1 (unknown).
|
|
else {
|
|
$plural_indexes[$langcode][$count] = -1;
|
|
}
|
|
}
|
|
return $plural_indexes[$langcode][$count];
|
|
}
|
|
|
|
|
|
/**
|
|
* Implements hook_modules_installed().
|
|
*/
|
|
function locale_modules_installed($modules) {
|
|
locale_system_update($modules);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_themes_enabled().
|
|
*
|
|
* @todo This is technically wrong. We must not import upon enabling, but upon
|
|
* initial installation. The theme system is missing an installation hook.
|
|
*/
|
|
function locale_themes_enabled($themes) {
|
|
locale_system_update($themes);
|
|
}
|
|
|
|
/**
|
|
* Imports translations when new modules or themes are installed.
|
|
*
|
|
* This function will either import translation for the component change
|
|
* right away, or start a batch if more files need to be imported.
|
|
*
|
|
* @param $components
|
|
* An array of component (theme and/or module) names to import
|
|
* translations for.
|
|
*
|
|
* @todo
|
|
* This currently imports all .po files available, independent of
|
|
* $components. Once we integrated with update status for project
|
|
* identification, we should revisit and only import files for the
|
|
* identified projects for the components.
|
|
*/
|
|
function locale_system_update($components) {
|
|
include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc';
|
|
if ($batch = locale_translate_batch_import_files(NULL, TRUE)) {
|
|
batch_set($batch);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_js_alter().
|
|
*
|
|
* This function checks all JavaScript files currently added via drupal_add_js()
|
|
* and invokes parsing if they have not yet been parsed for Drupal.t()
|
|
* and Drupal.formatPlural() calls. Also refreshes the JavaScript translation
|
|
* file if necessary, and adds it to the page.
|
|
*/
|
|
function locale_js_alter(&$javascript) {
|
|
global $language_interface;
|
|
|
|
$dir = 'public://' . variable_get('locale_js_directory', 'languages');
|
|
$parsed = variable_get('javascript_parsed', array());
|
|
$files = $new_files = FALSE;
|
|
|
|
foreach ($javascript as $item) {
|
|
if ($item['type'] == 'file') {
|
|
$files = TRUE;
|
|
$filepath = $item['data'];
|
|
if (!in_array($filepath, $parsed)) {
|
|
// Don't parse our own translations files.
|
|
if (substr($filepath, 0, strlen($dir)) != $dir) {
|
|
_locale_parse_js_file($filepath);
|
|
$parsed[] = $filepath;
|
|
$new_files = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there are any new source files we parsed, invalidate existing
|
|
// JavaScript translation files for all languages, adding the refresh
|
|
// flags into the existing array.
|
|
if ($new_files) {
|
|
$parsed += _locale_invalidate_js();
|
|
}
|
|
|
|
// If necessary, rebuild the translation file for the current language.
|
|
if (!empty($parsed['refresh:' . $language_interface->langcode])) {
|
|
// Don't clear the refresh flag on failure, so that another try will
|
|
// be performed later.
|
|
if (_locale_rebuild_js()) {
|
|
unset($parsed['refresh:' . $language_interface->langcode]);
|
|
}
|
|
// Store any changes after refresh was attempted.
|
|
variable_set('javascript_parsed', $parsed);
|
|
}
|
|
// If no refresh was attempted, but we have new source files, we need
|
|
// to store them too. This occurs if current page is in English.
|
|
elseif ($new_files) {
|
|
variable_set('javascript_parsed', $parsed);
|
|
}
|
|
|
|
// Add the translation JavaScript file to the page.
|
|
$locale_javascripts = variable_get('locale_translation_javascript', array());
|
|
if ($files && !empty($locale_javascripts[$language_interface->langcode])) {
|
|
// Add the translation JavaScript file to the page.
|
|
$file = $dir . '/' . $language_interface->langcode . '_' . $locale_javascripts[$language_interface->langcode] . '.js';
|
|
$javascript[$file] = drupal_js_defaults($file);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implement hook_library_info_alter().
|
|
*
|
|
* Provides the language support for the jQuery UI Date Picker.
|
|
*/
|
|
function locale_library_info_alter(&$libraries, $module) {
|
|
global $language_interface;
|
|
if ($module == 'system' && isset($libraries['system']['ui.datepicker'])) {
|
|
$datepicker = drupal_get_path('module', 'locale') . '/locale.datepicker.js';
|
|
$libraries['system']['ui.datepicker']['js'][$datepicker] = array('group' => JS_THEME);
|
|
$libraries['system']['ui.datepicker']['js'][] = array(
|
|
'data' => array(
|
|
'jqueryuidatepicker' => array(
|
|
'rtl' => $language_interface->direction == LANGUAGE_RTL,
|
|
'firstDay' => variable_get('date_first_day', 0),
|
|
),
|
|
),
|
|
'type' => 'setting',
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_FORM_ID_alter() for language_admin_overview_form().
|
|
*/
|
|
function locale_form_language_admin_overview_form_alter(&$form, &$form_state) {
|
|
$languages = $form['languages']['#languages'];
|
|
|
|
$total_strings = db_query("SELECT COUNT(*) FROM {locales_source}")->fetchField();
|
|
$stats = array_fill_keys(array_keys($languages), array());
|
|
|
|
// If we have source strings, count translations and calculate progress.
|
|
if (!empty($total_strings)) {
|
|
$translations = db_query("SELECT COUNT(*) AS translated, t.language FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid GROUP BY language");
|
|
foreach ($translations as $data) {
|
|
$stats[$data->language]['translated'] = $data->translated;
|
|
if ($data->translated > 0) {
|
|
$stats[$data->language]['ratio'] = round($data->translated / $total_strings * 100, 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
array_splice($form['languages']['#header'], -1, 0, t('Interface translation'));
|
|
|
|
foreach ($languages as $langcode => $language) {
|
|
$stats[$langcode] += array(
|
|
'translated' => 0,
|
|
'ratio' => 0,
|
|
);
|
|
if ($langcode != 'en' || locale_translate_english()) {
|
|
$form['languages'][$langcode]['locale_statistics'] = array(
|
|
'#type' => 'link',
|
|
'#title' => t('@translated/@total (@ratio%)', array(
|
|
'@translated' => $stats[$langcode]['translated'],
|
|
'@total' => $total_strings,
|
|
'@ratio' => $stats[$langcode]['ratio'],
|
|
)),
|
|
'#href' => 'admin/config/regional/translate/translate',
|
|
);
|
|
}
|
|
else {
|
|
$form['languages'][$langcode]['locale_statistics'] = array(
|
|
'#markup' => t('not applicable'),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_FORM_ID_alter() for language_admin_add_form(().
|
|
*/
|
|
function locale_form_language_admin_add_form_alter(&$form, &$form_state) {
|
|
$form['predefined_submit']['#submit'][] = 'locale_form_language_admin_add_form_alter_submit';
|
|
$form['custom_language']['submit']['#submit'][] = 'locale_form_language_admin_add_form_alter_submit';
|
|
}
|
|
|
|
/**
|
|
* Set a batch for newly added language.
|
|
*/
|
|
function locale_form_language_admin_add_form_alter_submit($form, $form_state) {
|
|
if (empty($form_state['values']['predefined_langcode']) || $form_state['values']['predefined_langcode'] == 'custom') {
|
|
$langcode = $form_state['values']['langcode'];
|
|
}
|
|
else {
|
|
$langcode = $form_state['values']['predefined_langcode'];
|
|
}
|
|
|
|
include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc';
|
|
locale_translate_add_language_set_batch($langcode);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_FORM_ID_alter() for language_admin_edit_form().
|
|
*/
|
|
function locale_form_language_admin_edit_form_alter(&$form, &$form_state) {
|
|
if ($form['langcode']['#type'] == 'value' && $form['langcode']['#value'] == 'en') {
|
|
$form['locale_translate_english'] = array(
|
|
'#title' => t('Enable interface translation to English'),
|
|
'#type' => 'checkbox',
|
|
'#default_value' => locale_translate_english(),
|
|
);
|
|
$form['#submit'][] = 'locale_form_language_admin_edit_form_alter_submit';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Submission handler to record our custom setting.
|
|
*/
|
|
function locale_form_language_admin_edit_form_alter_submit($form, $form_state) {
|
|
variable_set('locale_translate_english', $form_state['values']['locale_translate_english']);
|
|
}
|
|
|
|
/**
|
|
* Utility function to tell if locale translates to English.
|
|
*/
|
|
function locale_translate_english() {
|
|
return variable_get('locale_translate_english', FALSE);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_FORM_ID_alter() for system_file_system_settings().
|
|
*
|
|
* Add interface translation directory setting to directories configuration.
|
|
*/
|
|
function locale_form_system_file_system_settings_alter(&$form, $form_state) {
|
|
$form['locale_translate_file_directory'] = array(
|
|
'#type' => 'textfield',
|
|
'#title' => t('Interface translations directory'),
|
|
'#default_value' => variable_get('locale_translate_file_directory', conf_path() . '/files/translations'),
|
|
'#maxlength' => 255,
|
|
'#description' => t('A local file system path where interface translation files are looked for. This directory must exist.'),
|
|
'#after_build' => array('system_check_directory'),
|
|
'#weight' => 10,
|
|
);
|
|
if ($form['file_default_scheme']) {
|
|
$form['file_default_scheme']['#weight'] = 20;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements MODULE_preprocess_HOOK().
|
|
*/
|
|
function locale_preprocess_node(&$variables) {
|
|
if ($variables['langcode'] != LANGUAGE_NOT_SPECIFIED) {
|
|
global $language_interface;
|
|
|
|
$node_language = language_load($variables['langcode']);
|
|
if ($node_language->langcode != $language_interface->langcode) {
|
|
// If the node language was different from the page language, we should
|
|
// add markup to identify the language. Otherwise the page language is
|
|
// inherited.
|
|
$variables['attributes_array']['lang'] = $variables['langcode'];
|
|
if ($node_language->direction != $language_interface->direction) {
|
|
// If text direction is different form the page's text direction, add
|
|
// direction information as well.
|
|
$dir = array('ltr', 'rtl');
|
|
$variables['attributes_array']['dir'] = $dir[$node_language->direction];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that a string is safe to be added or imported as a translation.
|
|
*
|
|
* This test can be used to detect possibly bad translation strings. It should
|
|
* not have any false positives. But it is only a test, not a transformation,
|
|
* as it destroys valid HTML. We cannot reliably filter translation strings
|
|
* on import because some strings are irreversibly corrupted. For example,
|
|
* a & in the translation would get encoded to &amp; by filter_xss()
|
|
* before being put in the database, and thus would be displayed incorrectly.
|
|
*
|
|
* The allowed tag list is like filter_xss_admin(), but omitting div and img as
|
|
* not needed for translation and likely to cause layout issues (div) or a
|
|
* possible attack vector (img).
|
|
*/
|
|
function locale_string_is_safe($string) {
|
|
return decode_entities($string) == decode_entities(filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var')));
|
|
}
|
|
|
|
/**
|
|
* Parses a JavaScript file, extracts strings wrapped in Drupal.t() and
|
|
* Drupal.formatPlural() and inserts them into the database.
|
|
*/
|
|
function _locale_parse_js_file($filepath) {
|
|
// The file path might contain a query string, so make sure we only use the
|
|
// actual file.
|
|
$parsed_url = drupal_parse_url($filepath);
|
|
$filepath = $parsed_url['path'];
|
|
// Load the JavaScript file.
|
|
$file = file_get_contents($filepath);
|
|
|
|
// Match all calls to Drupal.t() in an array.
|
|
// Note: \s also matches newlines with the 's' modifier.
|
|
preg_match_all('~
|
|
[^\w]Drupal\s*\.\s*t\s* # match "Drupal.t" with whitespace
|
|
\(\s* # match "(" argument list start
|
|
(' . LOCALE_JS_STRING . ')\s* # capture string argument
|
|
(?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture str args
|
|
(?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*) # optionally capture context
|
|
?)? # close optional args
|
|
[,\)] # match ")" or "," to finish
|
|
~sx', $file, $t_matches);
|
|
|
|
// Match all Drupal.formatPlural() calls in another array.
|
|
preg_match_all('~
|
|
[^\w]Drupal\s*\.\s*formatPlural\s* # match "Drupal.formatPlural" with whitespace
|
|
\( # match "(" argument list start
|
|
\s*.+?\s*,\s* # match count argument
|
|
(' . LOCALE_JS_STRING . ')\s*,\s* # match singular string argument
|
|
( # capture plural string argument
|
|
(?: # non-capturing group to repeat string pieces
|
|
(?:
|
|
\' # match start of single-quoted string
|
|
(?:\\\\\'|[^\'])* # match any character except unescaped single-quote
|
|
@count # match "@count"
|
|
(?:\\\\\'|[^\'])* # match any character except unescaped single-quote
|
|
\' # match end of single-quoted string
|
|
|
|
|
" # match start of double-quoted string
|
|
(?:\\\\"|[^"])* # match any character except unescaped double-quote
|
|
@count # match "@count"
|
|
(?:\\\\"|[^"])* # match any character except unescaped double-quote
|
|
" # match end of double-quoted string
|
|
)
|
|
(?:\s*\+\s*)? # match "+" with possible whitespace, for str concat
|
|
)+ # match multiple because we supports concatenating strs
|
|
)\s* # end capturing of plural string argument
|
|
(?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture string args
|
|
(?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*)? # optionally capture context
|
|
)?
|
|
[,\)]
|
|
~sx', $file, $plural_matches);
|
|
|
|
$matches = array();
|
|
|
|
// Add strings from Drupal.t().
|
|
foreach ($t_matches[1] as $key => $string) {
|
|
$matches[] = array(
|
|
'string' => $string,
|
|
'context' => $t_matches[2][$key],
|
|
);
|
|
}
|
|
|
|
// Add string from Drupal.formatPlural().
|
|
foreach ($plural_matches[1] as $key => $string) {
|
|
$matches[] = array(
|
|
'string' => $string,
|
|
'context' => $plural_matches[3][$key],
|
|
);
|
|
|
|
// If there is also a plural version of this string, add it to the strings array.
|
|
if (isset($plural_matches[2][$key])) {
|
|
$matches[] = array(
|
|
'string' => $plural_matches[2][$key],
|
|
'context' => $plural_matches[3][$key],
|
|
);
|
|
}
|
|
}
|
|
|
|
// Loop through all matches and process them.
|
|
foreach ($matches as $key => $match) {
|
|
|
|
// Remove the quotes and string concatenations from the string and context.
|
|
$string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($match['string'], 1, -1)));
|
|
$context = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($match['context'], 1, -1)));
|
|
|
|
$source = db_query("SELECT lid, location FROM {locales_source} WHERE source = :source AND context = :context", array(':source' => $string, ':context' => $context))->fetchObject();
|
|
if ($source) {
|
|
// We already have this source string and now have to add the location
|
|
// to the location column, if this file is not yet present in there.
|
|
$locations = preg_split('~\s*;\s*~', $source->location);
|
|
|
|
if (!in_array($filepath, $locations)) {
|
|
$locations[] = $filepath;
|
|
$locations = implode('; ', $locations);
|
|
|
|
// Save the new locations string to the database.
|
|
db_update('locales_source')
|
|
->fields(array(
|
|
'location' => $locations,
|
|
))
|
|
->condition('lid', $source->lid)
|
|
->execute();
|
|
}
|
|
}
|
|
else {
|
|
// We don't have the source string yet, thus we insert it into the database.
|
|
db_insert('locales_source')
|
|
->fields(array(
|
|
'location' => $filepath,
|
|
'source' => $string,
|
|
'context' => $context,
|
|
))
|
|
->execute();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Force the JavaScript translation file(s) to be refreshed.
|
|
*
|
|
* This function sets a refresh flag for a specified language, or all
|
|
* languages except English, if none specified. JavaScript translation
|
|
* files are rebuilt (with locale_update_js_files()) the next time a
|
|
* request is served in that language.
|
|
*
|
|
* @param $langcode
|
|
* The language code for which the file needs to be refreshed.
|
|
*
|
|
* @return
|
|
* New content of the 'javascript_parsed' variable.
|
|
*/
|
|
function _locale_invalidate_js($langcode = NULL) {
|
|
$parsed = variable_get('javascript_parsed', array());
|
|
|
|
if (empty($langcode)) {
|
|
// Invalidate all languages.
|
|
$languages = language_list();
|
|
if (!locale_translate_english()) {
|
|
unset($languages['en']);
|
|
}
|
|
foreach ($languages as $lcode => $data) {
|
|
$parsed['refresh:' . $lcode] = 'waiting';
|
|
}
|
|
}
|
|
else {
|
|
// Invalidate single language.
|
|
$parsed['refresh:' . $langcode] = 'waiting';
|
|
}
|
|
|
|
variable_set('javascript_parsed', $parsed);
|
|
return $parsed;
|
|
}
|
|
|
|
/**
|
|
* (Re-)Creates the JavaScript translation file for a language.
|
|
*
|
|
* @param $langcode
|
|
* The language, the translation file should be (re)created for.
|
|
*/
|
|
function _locale_rebuild_js($langcode = NULL) {
|
|
if (!isset($langcode)) {
|
|
global $language_interface;
|
|
$language = $language_interface;
|
|
}
|
|
else {
|
|
// Get information about the locale.
|
|
$languages = language_list();
|
|
$language = $languages[$langcode];
|
|
}
|
|
|
|
// Construct the array for JavaScript translations.
|
|
// Only add strings with a translation to the translations array.
|
|
$result = db_query("SELECT s.lid, s.source, s.context, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%'", array(':language' => $language->langcode));
|
|
|
|
$translations = array();
|
|
foreach ($result as $data) {
|
|
$translations[$data->context][$data->source] = $data->translation;
|
|
}
|
|
|
|
// Construct the JavaScript file, if there are translations.
|
|
$data_hash = NULL;
|
|
$data = $status = '';
|
|
if (!empty($translations)) {
|
|
|
|
$data = "Drupal.locale = { ";
|
|
|
|
$locale_plurals = variable_get('locale_translation_plurals', array());
|
|
if (!empty($locale_plurals[$language->langcode])) {
|
|
$data .= "'pluralFormula': function (\$n) { return Number({$locale_plurals[$language->langcode]['formula']}); }, ";
|
|
}
|
|
|
|
$data .= "'strings': " . drupal_json_encode($translations) . " };";
|
|
$data_hash = drupal_hash_base64($data);
|
|
}
|
|
|
|
// Construct the filepath where JS translation files are stored.
|
|
// There is (on purpose) no front end to edit that variable.
|
|
$dir = 'public://' . variable_get('locale_js_directory', 'languages');
|
|
|
|
// Delete old file, if we have no translations anymore, or a different file to be saved.
|
|
$locale_javascripts = variable_get('locale_translation_javascript', array());
|
|
$changed_hash = !isset($locale_javascripts[$language->langcode]) || ($locale_javascripts[$language->langcode] != $data_hash);
|
|
if (!empty($locale_javascripts[$language->langcode]) && (!$data || $changed_hash)) {
|
|
file_unmanaged_delete($dir . '/' . $language->langcode . '_' . $locale_javascripts[$language->langcode] . '.js');
|
|
$locale_javascripts[$language->langcode] = '';
|
|
$status = 'deleted';
|
|
}
|
|
|
|
// Only create a new file if the content has changed or the original file got
|
|
// lost.
|
|
$dest = $dir . '/' . $language->langcode . '_' . $data_hash . '.js';
|
|
if ($data && ($changed_hash || !file_exists($dest))) {
|
|
// Ensure that the directory exists and is writable, if possible.
|
|
file_prepare_directory($dir, FILE_CREATE_DIRECTORY);
|
|
|
|
// Save the file.
|
|
if (file_unmanaged_save_data($data, $dest)) {
|
|
$locale_javascripts[$language->langcode] = $data_hash;
|
|
// If we deleted a previous version of the file and we replace it with a
|
|
// new one we have an update.
|
|
if ($status == 'deleted') {
|
|
$status = 'updated';
|
|
}
|
|
// If the file did not exist previously and the data has changed we have
|
|
// a fresh creation.
|
|
elseif ($changed_hash) {
|
|
$status = 'created';
|
|
}
|
|
// If the data hash is unchanged the translation was lost and has to be
|
|
// rebuilt.
|
|
else {
|
|
$status = 'rebuilt';
|
|
}
|
|
}
|
|
else {
|
|
$locale_javascripts[$language->langcode] = '';
|
|
$status = 'error';
|
|
}
|
|
}
|
|
|
|
// Save the new JavaScript hash (or an empty value if the file just got
|
|
// deleted). Act only if some operation was executed that changed the hash
|
|
// code.
|
|
if ($status && $changed_hash) {
|
|
variable_set('locale_translation_javascript', $locale_javascripts);
|
|
}
|
|
|
|
// Log the operation and return success flag.
|
|
switch ($status) {
|
|
case 'updated':
|
|
watchdog('locale', 'Updated JavaScript translation file for the language %language.', array('%language' => $language->name));
|
|
return TRUE;
|
|
case 'rebuilt':
|
|
watchdog('locale', 'JavaScript translation file %file.js was lost.', array('%file' => $locale_javascripts[$language->langcode]), WATCHDOG_WARNING);
|
|
// Proceed to the 'created' case as the JavaScript translation file has
|
|
// been created again.
|
|
case 'created':
|
|
watchdog('locale', 'Created JavaScript translation file for the language %language.', array('%language' => $language->name));
|
|
return TRUE;
|
|
case 'deleted':
|
|
watchdog('locale', 'Removed JavaScript translation file for the language %language because no translations currently exist for that language.', array('%language' => $language->name));
|
|
return TRUE;
|
|
case 'error':
|
|
watchdog('locale', 'An error occurred during creation of the JavaScript translation file for the language %language.', array('%language' => $language->name), WATCHDOG_ERROR);
|
|
return FALSE;
|
|
default:
|
|
// No operation needed.
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|