2001-03-10 11:07:52 +00:00
<?php
2001-02-12 20:40:43 +00:00
2004-08-11 11:26:20 +00:00
/**
* @file
2012-10-05 15:42:46 +00:00
* Enables the translation of the user interface to languages other than English.
2004-08-11 11:26:20 +00:00
*
2012-10-05 15:42:46 +00:00
* When enabled, multiple languages can be set up. The site interface can be
* displayed in different languages, and nodes can have languages assigned. The
* setup of languages and translations is completely web based. Gettext portable
* object files are supported.
2004-08-11 11:26:20 +00:00
*/
2012-06-14 08:42:51 +00:00
use Drupal\locale\LocaleLookup;
2012-08-21 23:22:22 +00:00
use Drupal\locale\LocaleConfigSubscriber;
2012-10-08 18:10:13 +00:00
use Drupal\locale\SourceString;
use Drupal\locale\StringDatabaseStorage;
2012-09-07 06:26:10 +00:00
use Drupal\locale\TranslationsStream;
2012-10-08 18:10:13 +00:00
use Drupal\Core\Database\Database;
2012-06-14 08:42:51 +00:00
2012-04-11 14:47:06 +00:00
/**
* 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;
2012-10-11 21:30:02 +00:00
/**
* Translation update mode: Use local files only.
*
* When checking for available translation updates, only local files will be
* used. Any remote translation file will be ignored. Also custom modules and
* themes which have set a "server pattern" to use a remote translation server
* will be ignored.
*/
const LOCALE_TRANSLATION_USE_SOURCE_LOCAL = 'local';
/**
* Translation update mode: Use both remote and local files.
*
* When checking for available translation updates, both local and remote files
* will be checked.
*/
const LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL = 'remote_and_local';
/**
* Default location of gettext file on the translation server.
*
* @see locale_translation_default_translation_server().
*/
const LOCALE_TRANSLATION_DEFAULT_SERVER_PATTERN = 'http://ftp.drupal.org/files/translations/%core/%project/%project-%version.%language.po';
/**
2012-12-07 18:17:37 +00:00
* The number of seconds that the translations status entry should be considered.
*/
const LOCALE_TRANSLATION_STATUS_TTL = 600;
/**
* UI option for override of existing translations. Override any translation.
*/
const LOCALE_TRANSLATION_OVERWRITE_ALL = 'all';
/**
* UI option for override of existing translations. Only override non-customized
* translations.
2012-10-11 21:30:02 +00:00
*/
2012-12-07 18:17:37 +00:00
const LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED = 'non_customized';
/**
* UI option for override of existing translations. Don't override existing
* translations.
*/
const LOCALE_TRANSLATION_OVERWRITE_NONE = 'none';
/**
* Translation source is a remote file.
*/
const LOCALE_TRANSLATION_REMOTE = 'remote';
/**
* Translation source is a local file.
*/
const LOCALE_TRANSLATION_LOCAL = 'local';
/**
* Translation source is the current translation.
*/
const LOCALE_TRANSLATION_CURRENT = 'current';
/**
* Translation source is a downloaded file.
*/
const LOCALE_TRANSLATION_DOWNLOADED = 'download';
/**
* Translation source is an imported file.
*/
const LOCALE_TRANSLATION_IMPORTED = 'import';
2012-10-11 21:30:02 +00:00
2004-06-18 15:04:37 +00:00
/**
2009-12-04 16:49:48 +00:00
* Implements hook_help().
2004-06-18 15:04:37 +00:00
*/
2007-06-30 19:46:58 +00:00
function locale_help($path, $arg) {
switch ($path) {
2005-11-01 10:17:34 +00:00
case 'admin/help#locale':
2009-12-07 05:02:37 +00:00
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
2012-03-21 05:51:30 +00:00
$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>';
2009-12-07 05:02:37 +00:00
$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>';
2012-12-07 18:17:37 +00:00
$output .= '<li>' . t('<a href="@update">Automatic import</a> of translations when you add a language or enable a module or theme. These translations are obtained from the <a href="@url">Drupal translation server</a>. Although Drupal modules and themes may not be fully translated in all languages, new translations become available frequently. Interface translation updates can be downloaded and installed automatically at <a href="@config">regular intervals</a>.', array('@url' => 'http://localize.drupal.org', '@update' => url('admin/reports/translations'), '@config' => url('admin/config/regional/translate/settings'))) . '</li>';
2009-12-07 05:02:37 +00:00
$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>';
2012-12-07 18:17:37 +00:00
$output .= '<li>' . t("If an existing translations do not meet your needs, the interface translations files in Gettext Portable Object (<em>.po</em>) format may be modified, or new <em>.po</em> files may be created, using a desktop Gettext editor. The Locale module's <a href='@import'>manual 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>';
2009-12-07 05:02:37 +00:00
$output .= '</ul></dd>';
$output .= '</dl>';
2005-11-01 10:17:34 +00:00
return $output;
2011-10-14 01:00:26 +00:00
2009-08-21 14:28:52 +00:00
case 'admin/config/regional/language':
2012-12-07 18:17:37 +00:00
return '<p>' . t('Interface translations are automatically imported when a language is added, or when new modules or themes are enabled. The report, <a href="@update">Available translation updates</a>, shows the status. Interface text can be <a href="@translate">customized</a>.', array('@update' => url('admin/reports/translations'), '@translate' => url('admin/config/regional/translate'))) . '</p>';
2011-10-14 01:00:26 +00:00
2009-08-21 14:28:52 +00:00
case 'admin/config/regional/translate':
2012-12-07 18:17:37 +00:00
$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: Because translation tasks involves 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>';
2007-11-24 20:35:37 +00:00
return $output;
2011-10-14 01:00:26 +00:00
2009-08-21 14:28:52 +00:00
case 'admin/config/regional/translate/import':
2012-12-07 18:17:37 +00:00
$output = '<p>' . t('Translation files are automatically downloaded and imported when <a href="@language">languages</a> are added, or when modules or themes are enabled.', array('@language' => url('admin/config/regional/language'))). '</p>';
$output .= '<p>' . t('For situations where translations need to be manually imported, this page imports the translated strings contained in a Gettext Portable Object (.po) file, which can be downloaded from the <a href="@url">Drupal translation server</a>. Alternatively, a .po file may need to be imported after offline editing in a Gettext translation editor. Importing an individual .po file may be a lengthy process.', array('@url' => 'http://localize.drupal.org')) . '</p>';
2007-12-18 16:27:44 +00:00
return $output;
2011-10-14 01:00:26 +00:00
2009-08-21 14:28:52 +00:00
case 'admin/config/regional/translate/export':
2008-04-14 17:48:46 +00:00
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>';
2004-08-12 18:00:11 +00:00
}
2001-06-29 22:08:57 +00:00
}
2004-04-21 13:56:38 +00:00
/**
2009-12-04 16:49:48 +00:00
* Implements hook_menu().
2004-04-21 13:56:38 +00:00
*/
2007-01-24 14:48:36 +00:00
function locale_menu() {
2011-12-22 11:26:12 +00:00
// Translation functionality.
2009-08-21 14:28:52 +00:00
$items['admin/config/regional/translate'] = array(
2011-10-14 01:00:26 +00:00
'title' => 'User interface translation',
'description' => 'Translate the built-in user interface.',
Issue #1452188 by Schnitzel, droplet, Sutharsan, Bojhan, Kristen Pol, Gábor Hojtsy, ershov.andrey, perusio, nod_, rvilar, andypost: Added New UI for string translation.
2012-06-15 10:01:31 +00:00
'page callback' => 'locale_translate_page',
2007-05-03 09:51:08 +00:00
'access arguments' => array('translate interface'),
2011-08-02 00:37:34 +00:00
'file' => 'locale.pages.inc',
2010-03-30 07:17:19 +00:00
'weight' => -5,
2007-05-03 09:51:08 +00:00
);
2009-08-21 14:28:52 +00:00
$items['admin/config/regional/translate/translate'] = array(
2009-02-05 00:32:47 +00:00
'title' => 'Translate',
2011-10-14 01:00:26 +00:00
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
2007-01-24 14:48:36 +00:00
);
2009-08-21 14:28:52 +00:00
$items['admin/config/regional/translate/import'] = array(
2007-05-03 09:51:08 +00:00
'title' => 'Import',
2008-10-02 13:11:34 +00:00
'page callback' => 'drupal_get_form',
'page arguments' => array('locale_translate_import_form'),
2008-04-23 20:01:56 +00:00
'access arguments' => array('translate interface'),
2007-01-24 14:48:36 +00:00
'weight' => 20,
'type' => MENU_LOCAL_TASK,
2011-08-27 08:30:16 +00:00
'file' => 'locale.bulk.inc',
2007-01-24 14:48:36 +00:00
);
2009-08-21 14:28:52 +00:00
$items['admin/config/regional/translate/export'] = array(
2007-05-03 09:51:08 +00:00
'title' => 'Export',
2012-04-09 18:24:12 +00:00
'page callback' => 'drupal_get_form',
'page arguments' => array('locale_translate_export_form'),
2008-04-23 20:01:56 +00:00
'access arguments' => array('translate interface'),
2007-05-03 09:51:08 +00:00
'weight' => 30,
'type' => MENU_LOCAL_TASK,
2011-08-27 08:30:16 +00:00
'file' => 'locale.bulk.inc',
2007-01-24 14:48:36 +00:00
);
2012-12-07 18:17:37 +00:00
$items['admin/config/regional/translate/settings'] = array(
'title' => 'Settings',
'page callback' => 'drupal_get_form',
'page arguments' => array('locale_translate_settings'),
'access arguments' => array('translate interface'),
'weight' => 40,
'type' => MENU_LOCAL_TASK,
'file' => 'locale.pages.inc',
);
2012-10-11 21:30:02 +00:00
$items['admin/reports/translations'] = array(
'title' => 'Available translation updates',
'description' => 'Get a status report about available interface translations for your installed modules and themes.',
2012-12-07 18:17:37 +00:00
'page callback' => 'drupal_get_form',
'page arguments' => array('locale_translation_status_form'),
2012-10-11 21:30:02 +00:00
'access arguments' => array('translate interface'),
'file' => 'locale.pages.inc',
);
$items['admin/reports/translations/check'] = array(
'title' => 'Manual translation update check',
'page callback' => 'locale_translation_manual_status',
'access arguments' => array('translate interface'),
'type' => MENU_CALLBACK,
'file' => 'locale.pages.inc',
);
2004-08-12 18:00:11 +00:00
2004-06-18 15:04:37 +00:00
return $items;
2001-06-20 20:00:40 +00:00
}
2004-06-18 15:04:37 +00:00
/**
2009-12-04 16:49:48 +00:00
* Implements hook_permission().
2004-06-18 15:04:37 +00:00
*/
2009-07-05 18:00:11 +00:00
function locale_permission() {
2008-02-20 13:46:43 +00:00
return array(
2008-10-09 15:15:55 +00:00
'translate interface' => array(
2009-12-01 13:14:43 +00:00
'title' => t('Translate interface texts'),
2012-03-10 20:38:14 +00:00
'restrict access' => TRUE,
2008-10-09 15:15:55 +00:00
),
2008-02-20 13:46:43 +00:00
);
2007-05-03 09:51:08 +00:00
}
/**
2009-12-04 16:49:48 +00:00
* Implements hook_theme().
2007-05-03 09:51:08 +00:00
*/
function locale_theme() {
return array(
Issue #1452188 by Schnitzel, droplet, Sutharsan, Bojhan, Kristen Pol, Gábor Hojtsy, ershov.andrey, perusio, nod_, rvilar, andypost: Added New UI for string translation.
2012-06-15 10:01:31 +00:00
'locale_translate_edit_form_strings' => array(
'render element' => 'form',
'file' => 'locale.pages.inc',
),
2007-05-03 09:51:08 +00:00
);
}
2007-03-26 01:32:22 +00:00
2012-09-07 06:26:10 +00:00
/**
* Implements hook_stream_wrappers().
*/
function locale_stream_wrappers() {
$wrappers = array(
'translations' => array(
'name' => t('Translation files'),
'class' => 'Drupal\locale\TranslationsStream',
'description' => t('Translation files'),
2012-12-07 18:17:37 +00:00
'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
2012-09-07 06:26:10 +00:00
),
);
return $wrappers;
}
2011-11-29 02:23:49 +00:00
/**
2011-12-22 11:26:12 +00:00
* Implements hook_language_insert().
2011-11-29 02:23:49 +00:00
*/
2011-12-22 11:26:12 +00:00
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.
2012-11-28 21:36:29 +00:00
cache('page')->deleteAll();
2011-12-22 11:26:12 +00:00
// Force JavaScript translation file re-creation for the new language.
2012-01-10 15:29:08 +00:00
_locale_invalidate_js($language->langcode);
2011-11-29 02:23:49 +00:00
}
/**
2011-12-22 11:26:12 +00:00
* Implements hook_language_update().
2011-11-29 02:23:49 +00:00
*/
2011-12-22 11:26:12 +00:00
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.
2012-11-28 21:36:29 +00:00
cache('page')->deleteAll();
2011-12-22 11:26:12 +00:00
// Force JavaScript translation file re-creation for the modified language.
2012-01-10 15:29:08 +00:00
_locale_invalidate_js($language->langcode);
2011-11-29 02:23:49 +00:00
}
/**
2011-12-22 11:26:12 +00:00
* Implements hook_language_delete().
2011-11-29 02:23:49 +00:00
*/
2011-12-22 11:26:12 +00:00
function locale_language_delete($language) {
// Remove translations.
2012-10-08 18:10:13 +00:00
locale_storage()->deleteTranslations(array('language' => $language->langcode));
2011-12-22 11:26:12 +00:00
2012-06-16 15:16:07 +00:00
// Remove interface translation files.
module_load_include('inc', 'locale', 'locale.bulk');
2012-12-07 18:17:37 +00:00
locale_translate_delete_translation_files(array(), array($language->langcode));
2011-12-22 11:26:12 +00:00
// Changing the language settings impacts the interface:
2012-12-07 18:17:37 +00:00
_locale_invalidate_js($language->langcode);
2012-11-28 21:36:29 +00:00
cache('page')->deleteAll();
2011-12-22 11:26:12 +00:00
2012-12-07 18:17:37 +00:00
// Clear locale translation caches.
locale_translation_status_delete_languages(array($language->langcode));
2012-01-10 15:29:08 +00:00
cache()->delete('locale:' . $language->langcode);
2011-11-29 02:23:49 +00:00
}
2012-10-11 21:30:02 +00:00
/**
* Returns list of translatable languages.
*
* @return array
* Array of installed languages keyed by language name. English is omitted
* unless it is marked as translatable.
*/
function locale_translatable_language_list() {
$languages = language_list();
if (!locale_translate_english()) {
unset($languages['en']);
}
return $languages;
}
2004-08-12 18:00:11 +00:00
2004-08-11 11:26:20 +00:00
/**
2006-03-17 18:35:56 +00:00
* Provides interface translation services.
2004-08-11 11:26:20 +00:00
*
* This function is called from t() to translate a string if needed.
2007-06-15 19:02:47 +00:00
*
* @param $string
* A string to look up translation for. If omitted, all the
2007-06-18 16:09:39 +00:00
* cached strings will be returned in all languages already
2007-06-15 19:02:47 +00:00
* used on the page.
2009-06-08 05:00:12 +00:00
* @param $context
* The context of this string.
2007-06-15 19:02:47 +00:00
* @param $langcode
* Language code to use for the lookup.
2004-08-11 11:26:20 +00:00
*/
2010-01-10 19:06:47 +00:00
function locale($string = NULL, $context = NULL, $langcode = NULL) {
2012-08-09 20:17:01 +00:00
$language_interface = language(LANGUAGE_TYPE_INTERFACE);
2012-01-03 05:00:32 +00:00
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
2012-09-24 21:08:10 +00:00
$drupal_static_fast['locale'] = &drupal_static(__FUNCTION__, array('cache' => array(), 'exists' => NULL));
}
$locale_t = &$drupal_static_fast['locale']['cache'];
$locale_exists = &$drupal_static_fast['locale']['exists'];
// Check whether Locale module is actually installed and operational.
// The mere existence of locale() does not imply that Locale module is
// actually enabled and its database tables are installed. Since PHP code
// cannot be unloaded, this is typically the case in the environment that
// is executing a test.
if (!isset($locale_exists)) {
$locale_exists = function_exists('module_exists') && module_exists('locale');
}
if (!$locale_exists) {
return $string;
2012-01-03 05:00:32 +00:00
}
2008-02-06 19:38:28 +00:00
2007-06-15 19:02:47 +00:00
if (!isset($string)) {
2008-01-04 16:04:49 +00:00
// Return all cached strings if no string was specified
2007-06-15 19:02:47 +00:00
return $locale_t;
}
2012-02-22 13:37:04 +00:00
$langcode = isset($langcode) ? $langcode : $language_interface->langcode;
2007-05-15 20:19:47 +00:00
2012-06-14 08:42:51 +00:00
// Strings are cached by langcode, context and roles, using instances of the
// LocaleLookup class to handle string lookup and caching.
if (!isset($locale_t[$langcode][$context]) && isset($language_interface)) {
2012-10-08 18:10:13 +00:00
$locale_t[$langcode][$context] = new LocaleLookup($langcode, $context, locale_storage());
2002-01-05 17:07:48 +00:00
}
2009-06-08 05:00:12 +00:00
return ($locale_t[$langcode][$context][$string] === TRUE ? $string : $locale_t[$langcode][$context][$string]);
2004-08-11 11:26:20 +00:00
}
2003-06-02 20:54:37 +00:00
2010-01-10 19:06:47 +00:00
/**
* Reset static variables used by locale().
*/
function locale_reset() {
drupal_static_reset('locale');
}
2012-10-08 18:10:13 +00:00
/**
* Gets the locale storage controller class .
*
* @return Drupal\locale\StringStorageInterface
*/
function locale_storage() {
$storage = &drupal_static(__FUNCTION__);
if (!isset($storage)) {
$options = array('target' => 'default');
$storage = new StringDatabaseStorage(Database::getConnection($options['target']), $options);
}
return $storage;
}
2004-08-11 11:26:20 +00:00
/**
2006-03-17 18:35:56 +00:00
* Returns plural form index for a specific number.
2004-08-11 11:26:20 +00:00
*
2006-03-17 18:35:56 +00:00
* The index is computed from the formula of this language.
2007-05-30 08:08:59 +00:00
*
2007-05-29 14:37:49 +00:00
* @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.
2012-02-16 13:42:26 +00:00
* @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.
2004-08-11 11:26:20 +00:00
*/
2007-05-29 14:37:49 +00:00
function locale_get_plural($count, $langcode = NULL) {
2012-08-09 20:17:01 +00:00
$language_interface = language(LANGUAGE_TYPE_INTERFACE);
2012-02-16 13:42:26 +00:00
// 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());
2004-08-11 11:26:20 +00:00
2012-02-22 13:37:04 +00:00
$langcode = $langcode ? $langcode : $language_interface->langcode;
2007-05-30 08:08:59 +00:00
2012-02-16 13:42:26 +00:00
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());
2004-08-11 11:26:20 +00:00
}
2012-02-16 13:42:26 +00:00
// 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().
2004-08-11 11:26:20 +00:00
$n = $count;
2012-02-16 13:42:26 +00:00
$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);
2004-08-11 11:26:20 +00:00
}
2012-02-16 13:42:26 +00:00
// Otherwise, return -1 (unknown).
2004-08-11 11:26:20 +00:00
else {
2012-02-16 13:42:26 +00:00
$plural_indexes[$langcode][$count] = -1;
2003-09-23 19:28:03 +00:00
}
2002-01-12 21:50:58 +00:00
}
2012-02-16 13:42:26 +00:00
return $plural_indexes[$langcode][$count];
2003-09-23 19:28:03 +00:00
}
2002-01-12 21:50:58 +00:00
2007-03-26 01:32:22 +00:00
2007-05-21 10:56:05 +00:00
/**
2011-02-21 23:51:45 +00:00
* Implements hook_modules_installed().
*/
function locale_modules_installed($modules) {
locale_system_update($modules);
}
2012-12-07 18:17:37 +00:00
/**
* Implements hook_modules_uninstalled().
*/
function locale_modules_uninstalled($modules) {
locale_system_remove($modules);
}
2011-02-21 23:51:45 +00:00
/**
* 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);
}
2012-12-07 18:17:37 +00:00
/**
* Implements hook_themes_disabled().
*/
function locale_themes_disabled($themes) {
locale_system_remove($themes);
}
2011-02-21 23:51:45 +00:00
/**
* Imports translations when new modules or themes are installed.
2007-05-21 10:56:05 +00:00
*
2012-12-07 18:17:37 +00:00
* This function will start a batch to import translations for the added
* components.
2007-05-21 10:56:05 +00:00
*
2012-12-07 18:17:37 +00:00
* @param array $components
2007-05-21 10:56:05 +00:00
* An array of component (theme and/or module) names to import
* translations for.
*/
function locale_system_update($components) {
2012-08-30 20:50:11 +00:00
// Skip running the translation imports if in the installer,
// because it would break out of the installer flow. We have
// built-in support for translation imports in the installer.
2012-12-07 18:17:37 +00:00
if (!drupal_installation_attempted() && locale_translatable_language_list()) {
module_load_include('compare.inc', 'locale');
// Update the list of translatable projects and start the import batch.
// Only when new projects are added the update batch will be triggered. Not
// each enabled module will introduce a new project. E.g. sub modules.
$projects = array_keys(locale_translation_build_projects());
if ($components = array_intersect($components, $projects)) {
module_load_include('fetch.inc', 'locale');
// Get translation status of the projects, download and update translations.
$options = _locale_translation_default_update_options();
$batch = locale_translation_batch_update_build($components, array(), $options);
2012-08-30 20:50:11 +00:00
batch_set($batch);
}
2007-05-21 10:56:05 +00:00
}
}
2012-12-07 18:17:37 +00:00
/**
* Delete translation history of modules and themes.
*
* Only the translation history is removed, not the source strings or
* translations. This is not possible because strings are shared between
* modules and we have no record of which string is used by which module.
*
* @param array $components
* An array of component (theme and/or module) names to remove
* translation history.
*/
function locale_system_remove($components) {
if (locale_translatable_language_list()) {
module_load_include('compare.inc', 'locale');
// Only when projects are removed, the translation files and records will be
// deleted. Not each disabled module will remove a project. E.g. sub modules.
$projects = array_keys(locale_translation_get_projects());
if ($components = array_intersect($components, $projects)) {
locale_translation_file_history_delete($components);
// Remove translation files.
module_load_include('inc', 'locale', 'locale.bulk');
locale_translate_delete_translation_files($components, array());
// Remove translatable projects.
// Followup issue http://drupal.org/node/1842362 to replace the
// {locale_project} table. Then change this to a function call.
db_delete('locale_project')
->condition('name', $components)
->execute();
// Clear the translation status.
locale_translation_status_delete_projects($components);
}
}
}
2007-11-26 22:34:09 +00:00
/**
2009-12-04 16:49:48 +00:00
* Implements hook_js_alter().
2007-11-26 22:34:09 +00:00
*
* 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.
*/
2008-11-23 16:00:08 +00:00
function locale_js_alter(&$javascript) {
2012-08-09 20:17:01 +00:00
$language_interface = language(LANGUAGE_TYPE_INTERFACE);
2007-11-26 22:34:09 +00:00
2009-08-17 19:14:42 +00:00
$dir = 'public://' . variable_get('locale_js_directory', 'languages');
2012-10-13 15:58:55 +00:00
$parsed = state()->get('system.javascript_parsed') ?: array();
2007-11-26 22:34:09 +00:00
$files = $new_files = FALSE;
2008-11-10 05:23:01 +00:00
foreach ($javascript as $item) {
2012-08-30 19:24:38 +00:00
if (isset($item['type']) && $item['type'] == 'file') {
2008-11-10 05:23:01 +00:00
$files = TRUE;
$filepath = $item['data'];
if (!in_array($filepath, $parsed)) {
// Don't parse our own translations files.
if (substr($filepath, 0, strlen($dir)) != $dir) {
2010-02-22 20:53:13 +00:00
_locale_parse_js_file($filepath);
2008-11-10 05:23:01 +00:00
$parsed[] = $filepath;
$new_files = TRUE;
2007-11-26 22:34:09 +00:00
}
}
}
}
// 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) {
2010-02-22 20:53:13 +00:00
$parsed += _locale_invalidate_js();
2007-11-26 22:34:09 +00:00
}
// If necessary, rebuild the translation file for the current language.
2012-02-22 13:37:04 +00:00
if (!empty($parsed['refresh:' . $language_interface->langcode])) {
2007-11-26 22:34:09 +00:00
// Don't clear the refresh flag on failure, so that another try will
// be performed later.
2010-02-22 20:53:13 +00:00
if (_locale_rebuild_js()) {
2012-02-22 13:37:04 +00:00
unset($parsed['refresh:' . $language_interface->langcode]);
2007-11-26 22:34:09 +00:00
}
// Store any changes after refresh was attempted.
2012-10-13 15:58:55 +00:00
state()->set('system.javascript_parsed', $parsed);
2007-11-26 22:34:09 +00:00
}
// 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.
2008-10-12 04:30:09 +00:00
elseif ($new_files) {
2012-10-13 15:58:55 +00:00
state()->set('system.javascript_parsed', $parsed);
2007-11-26 22:34:09 +00:00
}
// Add the translation JavaScript file to the page.
2011-11-09 04:25:48 +00:00
$locale_javascripts = variable_get('locale_translation_javascript', array());
2012-02-22 13:37:04 +00:00
if ($files && !empty($locale_javascripts[$language_interface->langcode])) {
2008-11-23 16:00:08 +00:00
// Add the translation JavaScript file to the page.
2012-02-22 13:37:04 +00:00
$file = $dir . '/' . $language_interface->langcode . '_' . $locale_javascripts[$language_interface->langcode] . '.js';
2008-11-23 16:00:08 +00:00
$javascript[$file] = drupal_js_defaults($file);
2007-11-26 22:34:09 +00:00
}
}
2012-08-30 19:24:38 +00:00
/**
* Implements hook_library_info().
*/
function locale_library_info() {
$libraries['drupal.locale.admin'] = array(
'title' => 'Locale',
'version' => VERSION,
'js' => array(
drupal_get_path('module', 'locale') . '/locale.admin.js' => array(),
),
'dependencies' => array(
array('system', 'jquery'),
array('system', 'drupal'),
array('system', 'jquery.once'),
),
);
$libraries['drupal.locale.datepicker'] = array(
'title' => 'Locale Datepicker UI',
'version' => VERSION,
'js' => array(
drupal_get_path('module', 'locale') . '/locale.datepicker.js' => array(),
),
'dependencies' => array(
array('system', 'jquery'),
array('system', 'drupal'),
2012-09-24 13:12:09 +00:00
array('system', 'drupalSettings'),
2012-08-30 19:24:38 +00:00
),
);
return $libraries;
}
2012-06-12 12:04:44 +00:00
/**
2011-09-24 19:50:19 +00:00
* Implement hook_library_info_alter().
2010-04-29 05:15:43 +00:00
*
* Provides the language support for the jQuery UI Date Picker.
*/
2011-09-24 19:50:19 +00:00
function locale_library_info_alter(&$libraries, $module) {
2012-06-24 17:30:34 +00:00
if ($module == 'system' && isset($libraries['jquery.ui.datepicker'])) {
2012-08-09 20:17:01 +00:00
$language_interface = language(LANGUAGE_TYPE_INTERFACE);
2012-06-12 12:04:44 +00:00
// locale.datepicker.js should be added in the JS_LIBRARY group, so that
// this attach behavior will execute early. JS_LIBRARY is the default for
// hook_library_info_alter(), thus does not have to be specified explicitly.
2012-08-30 19:24:38 +00:00
$libraries['jquery.ui.datepicker']['dependencies'][] = array('locale', 'drupal.locale.datepicker');
2012-06-24 17:30:34 +00:00
$libraries['jquery.ui.datepicker']['js'][] = array(
2010-04-29 05:15:43 +00:00
'data' => array(
2012-06-12 12:04:44 +00:00
'jquery' => array(
'ui' => array(
'datepicker' => array(
'isRTL' => $language_interface->direction == LANGUAGE_RTL,
2012-11-29 07:37:55 +00:00
'firstDay' => config('system.date')->get('first_day'),
2012-06-12 12:04:44 +00:00
),
),
2010-04-29 05:15:43 +00:00
),
),
'type' => 'setting',
);
}
}
2011-10-14 01:00:26 +00:00
/**
2011-12-22 11:26:12 +00:00
* Implements hook_form_FORM_ID_alter() for language_admin_overview_form().
2011-10-14 01:00:26 +00:00
*/
2011-12-22 11:26:12 +00:00
function locale_form_language_admin_overview_form_alter(&$form, &$form_state) {
2011-10-14 01:00:26 +00:00
$languages = $form['languages']['#languages'];
2012-10-08 18:10:13 +00:00
$total_strings = locale_storage()->countStrings();
2011-10-14 01:00:26 +00:00
$stats = array_fill_keys(array_keys($languages), array());
// If we have source strings, count translations and calculate progress.
if (!empty($total_strings)) {
2012-10-08 18:10:13 +00:00
$translations = locale_storage()->countTranslations();
foreach ($translations as $langcode => $translated) {
$stats[$langcode]['translated'] = $translated;
if ($translated > 0) {
$stats[$langcode]['ratio'] = round($translated / $total_strings * 100, 2);
2011-10-14 01:00:26 +00:00
}
}
}
array_splice($form['languages']['#header'], -1, 0, t('Interface translation'));
foreach ($languages as $langcode => $language) {
$stats[$langcode] += array(
'translated' => 0,
'ratio' => 0,
);
2012-06-16 11:54:51 +00:00
if (!$language->locked && ($langcode != 'en' || locale_translate_english())) {
2011-10-14 01:00:26 +00:00
$form['languages'][$langcode]['locale_statistics'] = array(
Issue #1452188 by Schnitzel, droplet, Sutharsan, Bojhan, Kristen Pol, Gábor Hojtsy, ershov.andrey, perusio, nod_, rvilar, andypost: Added New UI for string translation.
2012-06-15 10:01:31 +00:00
'#markup' => l(
t('@translated/@total (@ratio%)', array(
'@translated' => $stats[$langcode]['translated'],
'@total' => $total_strings,
'@ratio' => $stats[$langcode]['ratio'],
)),
'admin/config/regional/translate/translate',
array('query' => array('langcode' => $langcode))
),
2011-10-14 01:00:26 +00:00
);
}
else {
$form['languages'][$langcode]['locale_statistics'] = array(
2011-10-27 03:55:02 +00:00
'#markup' => t('not applicable'),
2011-10-14 01:00:26 +00:00
);
}
}
}
2011-10-27 03:55:02 +00:00
/**
2011-12-22 11:26:12 +00:00
* 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';
}
/**
2012-10-05 15:42:46 +00:00
* Form submission handler for language_admin_add_form().
*
* Set a batch for a newly-added language.
2011-12-22 11:26:12 +00:00
*/
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'];
}
2012-12-07 18:17:37 +00:00
// Download and import translations for the newly added language.
module_load_include('fetch.inc', 'locale');
$options = _locale_translation_default_update_options();
$batch = locale_translation_batch_update_build(array(), array($langcode), $options);
batch_set($batch);
2011-12-22 11:26:12 +00:00
}
/**
* Implements hook_form_FORM_ID_alter() for language_admin_edit_form().
2011-10-27 03:55:02 +00:00
*/
2011-12-22 11:26:12 +00:00
function locale_form_language_admin_edit_form_alter(&$form, &$form_state) {
2011-10-27 03:55:02 +00:00
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(),
);
2011-12-22 11:26:12 +00:00
$form['#submit'][] = 'locale_form_language_admin_edit_form_alter_submit';
2011-10-27 03:55:02 +00:00
}
}
/**
2012-10-05 15:42:46 +00:00
* Form submission handler for language_admin_edit_form().
2011-10-27 03:55:02 +00:00
*/
2011-12-22 11:26:12 +00:00
function locale_form_language_admin_edit_form_alter_submit($form, $form_state) {
2011-10-27 03:55:02 +00:00
variable_set('locale_translate_english', $form_state['values']['locale_translate_english']);
}
/**
2012-10-05 15:42:46 +00:00
* Checks whether locale translates to English.
*
* @return bool
* Returns TRUE if content should be translated to English, FALSE otherwise.
2011-10-27 03:55:02 +00:00
*/
function locale_translate_english() {
return variable_get('locale_translate_english', FALSE);
}
2011-10-29 11:40:09 +00:00
/**
* 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,
2012-12-07 18:17:37 +00:00
'#description' => t('A local file system path where interface translation files will be stored.'),
2011-10-29 11:40:09 +00:00
'#after_build' => array('system_check_directory'),
'#weight' => 10,
);
if ($form['file_default_scheme']) {
$form['file_default_scheme']['#weight'] = 20;
}
2012-10-11 21:30:02 +00:00
$form['#submit'][] = 'locale_system_file_system_settings_submit';
}
/**
* Submit handler for the file system settings form.
*
* Clears the translation status when the Interface translations directory
* changes. Without a translations directory local po files in the directory
* should be ignored. The old translation status is no longer valid.
*/
function locale_system_file_system_settings_submit(&$form, $form_state) {
if ($form['locale_translate_file_directory']['#default_value'] != $form_state['values']['locale_translate_file_directory']) {
locale_translation_clear_status();
}
2011-10-29 11:40:09 +00:00
}
2011-11-25 11:31:30 +00:00
/**
2012-05-04 19:53:43 +00:00
* Implements hook_preprocess_HOOK() for node.tpl.php.
2011-11-25 11:31:30 +00:00
*/
function locale_preprocess_node(&$variables) {
2012-11-07 10:33:34 +00:00
if ($variables['node']->langcode != LANGUAGE_NOT_SPECIFIED) {
2012-08-09 20:17:01 +00:00
$language_interface = language(LANGUAGE_TYPE_INTERFACE);
2011-11-25 11:31:30 +00:00
2012-11-07 10:33:34 +00:00
$node_language = language_load($variables['node']->langcode);
2012-02-22 13:37:04 +00:00
if ($node_language->langcode != $language_interface->langcode) {
2011-11-25 11:31:30 +00:00
// If the node language was different from the page language, we should
// add markup to identify the language. Otherwise the page language is
// inherited.
2012-11-07 10:33:34 +00:00
$variables['attributes']['lang'] = $variables['node']->langcode;
2012-02-22 13:37:04 +00:00
if ($node_language->direction != $language_interface->direction) {
2011-11-25 11:31:30 +00:00
// If text direction is different form the page's text direction, add
// direction information as well.
$dir = array('ltr', 'rtl');
2012-08-03 15:31:18 +00:00
$variables['attributes']['dir'] = $dir[$node_language->direction];
2011-11-25 11:31:30 +00:00
}
}
}
}
2012-04-11 14:47:06 +00:00
2012-12-07 18:17:37 +00:00
/**
* Gets current translation status from the {locale_file} table.
*
* @return array
* Array of translation file objects.
*/
function locale_translation_get_file_history() {
$history = &drupal_static(__FUNCTION__, array());
if (empty($history)) {
// Get file history from the database.
$result = db_query('SELECT project, langcode, filename, version, uri, timestamp, last_checked FROM {locale_file}');
foreach ($result as $file) {
$file->type = LOCALE_TRANSLATION_CURRENT;
$history[$file->project][$file->langcode] = $file;
}
}
return $history;
}
/**
* Updates the {locale_file} table.
*
* @param object $file
* Object representing the file just imported.
*
* @return integer
* FALSE on failure. Otherwise SAVED_NEW or SAVED_UPDATED.
*
* @see drupal_write_record()
*/
function locale_translation_update_file_history($file) {
// Update or write new record.
if (db_query("SELECT project FROM {locale_file} WHERE project = :project AND langcode = :langcode", array(':project' => $file->project, ':langcode' => $file->langcode))->fetchField()) {
$update = array('project', 'langcode');
}
else {
$update = array();
}
return drupal_write_record('locale_file', $file, $update);
}
/**
* Deletes the history of downloaded translations.
*
* @param array $projects
* Project name(s) to be deleted from the file history. If both project(s) and
* language code(s) are specified the conditions will be ANDed.
* @param array $langcode
* Language code(s) to be deleted from the file history.
*/
function locale_translation_file_history_delete($projects = array(), $langcodes = array()) {
$query = db_delete('locale_file');
if (!empty($projects)) {
$query->condition('project', $projects);
}
if (!empty($langcodes)) {
$query->condition('langcode', $langcodes);
}
$query->execute();
}
/**
* Saves the status of translation sources in static cache.
*
* @param array $data
* Array of translation source data, structured by project name and langcode.
*/
function locale_translation_status_save($data) {
// Followup issue: http://drupal.org/node/1842362
// Split status storage per module/language and expire individually. This will
// improve performance for large sites.
$status = state()->get('locale.translation_status');
$status = empty($status) ? array() : $status;
// Merge the new data into the existing structured status array.
foreach ($data as $project => $languages) {
foreach ($languages as $langcode => $source) {
$status[$project][$langcode] = $source;
}
}
state()->set('locale.translation_status', $status);
state()->set('locale.translation_last_checked', REQUEST_TIME);
}
/**
* Delete language entries from the status cache.
*
* @param array $langcodes
* Language code(s) to be deleted from the cache.
*/
function locale_translation_status_delete_languages($langcodes) {
2012-12-17 21:40:36 +00:00
if ($status = state()->get('locale.translation_status')) {
foreach ($status as $project => $languages) {
foreach ($languages as $langcode => $source) {
if (in_array($langcode, $langcodes)) {
unset($status[$project][$langcode]);
}
2012-12-07 18:17:37 +00:00
}
}
2012-12-17 21:40:36 +00:00
state()->set('locale.translation_status', $status);
2012-12-07 18:17:37 +00:00
}
}
/**
* Delete project entries from the status cache.
*
* @param array $projects
* Project name(s) to be deleted from the cache.
*/
function locale_translation_status_delete_projects($projects) {
$status = state()->get('locale.translation_status');
foreach ($status as $project => $languages) {
if (in_array($project, $projects)) {
unset($status[$project]);
}
}
state()->set('locale.translation_status', $status);
}
2012-10-11 21:30:02 +00:00
/**
* Clear the translation status cache.
*/
function locale_translation_clear_status() {
2012-12-07 18:17:37 +00:00
state()->delete('locale.translation_status');
state()->delete('locale.translation_last_checked');
}
/**
* Checks whether remote translation sources are used.
*
* @return bool
* Returns TRUE if remote translations sources should be taken into account
* when checking or importing translation files, FALSE otherwise.
*/
function locale_translation_use_remote_source() {
return config('locale.settings')->get('translation.use_source') == LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL;
2012-10-11 21:30:02 +00:00
}
2012-04-11 14:47:06 +00:00
/**
* 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')));
}
2012-10-23 10:25:45 +00:00
/**
* Refresh related information after string translations have been updated.
*
* The information that will be refreshed includes:
* - JavaScript translations.
* - Locale cache.
*
* @param array $langcodes
* Language codes for updated translations.
* @param array $lids
* List of string identifiers that have been updated / created.
*/
function _locale_refresh_translations($langcodes, $lids) {
if ($lids && $langcodes) {
// Update javascript translations if any of the strings has a javascript location.
if ($strings = locale_storage()->getStrings(array('lid' => $lids, 'type' => 'javascript'))) {
array_map('_locale_invalidate_js', $langcodes);
}
}
// Clear locale cache.
2012-11-28 21:36:29 +00:00
cache()->deleteTags(array('locale' => TRUE));
2012-10-23 10:25:45 +00:00
}
2012-04-11 14:47:06 +00:00
/**
* Parses a JavaScript file, extracts strings wrapped in Drupal.t() and
* Drupal.formatPlural() and inserts them into the database.
2012-10-23 10:25:45 +00:00
*
* @param string $filepath
* File name to parse.
*
* @return array
* Array of string objects to update indexed by context and source.
2012-04-11 14:47:06 +00:00
*/
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)));
2012-10-08 18:10:13 +00:00
$source = locale_storage()->findString(array('source' => $string, 'context' => $context));
2012-10-23 10:25:45 +00:00
if (!$source) {
2012-04-11 14:47:06 +00:00
// We don't have the source string yet, thus we insert it into the database.
2012-10-23 10:25:45 +00:00
$source = locale_storage()->createString(array(
2012-10-08 18:10:13 +00:00
'source' => $string,
'context' => $context,
2012-10-23 10:25:45 +00:00
));
2012-04-11 14:47:06 +00:00
}
2012-10-23 10:25:45 +00:00
// Besides adding the location this will tag it for current version.
$source->addLocation('javascript', $filepath);
$source->save();
2012-04-11 14:47:06 +00:00
}
}
/**
* 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
2012-10-13 15:58:55 +00:00
* New content of the 'system.javascript_parsed' variable.
2012-04-11 14:47:06 +00:00
*/
function _locale_invalidate_js($langcode = NULL) {
2012-10-13 15:58:55 +00:00
$parsed = state()->get('system.javascript_parsed') ?: array();
2012-04-11 14:47:06 +00:00
if (empty($langcode)) {
// Invalidate all languages.
2012-10-11 21:30:02 +00:00
$languages = locale_translatable_language_list();
2012-04-11 14:47:06 +00:00
foreach ($languages as $lcode => $data) {
$parsed['refresh:' . $lcode] = 'waiting';
}
}
else {
// Invalidate single language.
$parsed['refresh:' . $langcode] = 'waiting';
}
2012-10-13 15:58:55 +00:00
state()->set('system.javascript_parsed', $parsed);
2012-04-11 14:47:06 +00:00
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)) {
2012-08-09 20:17:01 +00:00
$language = language(LANGUAGE_TYPE_INTERFACE);
2012-04-11 14:47:06 +00:00
}
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.
2012-10-23 10:25:45 +00:00
$conditions = array(
'type' => 'javascript',
'language' => $language->langcode,
'translated' => TRUE,
);
2012-04-11 14:47:06 +00:00
$translations = array();
2012-10-23 10:25:45 +00:00
foreach (locale_storage()->getTranslations($conditions) as $data) {
2012-04-11 14:47:06 +00:00
$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;
}
}
2012-08-21 23:22:22 +00:00
/**
* Implements hook_language_init().
*/
function locale_language_init() {
// Add locale helper to configuration subscribers.
drupal_container()->get('dispatcher')->addSubscriber(new LocaleConfigSubscriber());
}