2004-08-11 11:26:20 +00:00
< ? php
// $Id$
2004-08-21 06:42:38 +00:00
2004-08-11 11:26:20 +00:00
/**
* @ file
2008-05-26 17:12:55 +00:00
* Administration functions for locale . module .
2004-08-11 11:26:20 +00:00
*/
2008-05-26 17:12:55 +00:00
/**
* Regular expression pattern used to localize JavaScript strings .
*/
2007-06-08 12:51:59 +00:00
define ( 'LOCALE_JS_STRING' , '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+' );
2007-06-17 17:41:40 +00:00
/**
* Translation import mode overwriting all existing translations
* if new translated version available .
*/
define ( 'LOCALE_IMPORT_OVERWRITE' , 0 );
/**
* Translation import mode keeping existing translations and only
* inserting new strings .
*/
define ( 'LOCALE_IMPORT_KEEP' , 1 );
2009-10-09 16:33:14 +00:00
/**
* URL language negotiation : use the path prefix as URL language
* indicator .
*/
define ( 'LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX' , 0 );
/**
* URL language negotiation : use the domain as URL language
* indicator .
*/
define ( 'LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN' , 1 );
2007-05-03 09:51:08 +00:00
/**
2009-10-09 16:33:14 +00:00
* @ defgroup locale - languages - negotiation Language negotiation options
2007-05-03 09:51:08 +00:00
* @ {
*/
2007-03-26 01:32:22 +00:00
2009-10-09 16:33:14 +00:00
/**
* Identify the language from the current content language .
*
* @ return
* The current content language code .
*/
function locale_language_from_content () {
global $language ;
return isset ( $language -> language ) ? $language -> language : FALSE ;
}
/**
* Identify language from the Accept - language HTTP header we got .
*
* We perform browser accept - language parsing only if page cache is disabled ,
* otherwise we would cache a user - specific preference .
*
* @ param $languages
* An array of valid language objects .
*
* @ return
* A valid language code on success , FALSE otherwise .
*/
function locale_language_from_browser ( $languages ) {
// Specified by the user via the browser's Accept Language setting
// Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
$browser_langs = array ();
if ( isset ( $_SERVER [ 'HTTP_ACCEPT_LANGUAGE' ])) {
$browser_accept = explode ( " , " , $_SERVER [ 'HTTP_ACCEPT_LANGUAGE' ]);
foreach ( $browser_accept as $langpart ) {
// The language part is either a code or a code with a quality.
// We cannot do anything with a * code, so it is skipped.
// If the quality is missing, it is assumed to be 1 according to the RFC.
if ( preg_match ( " !([a-z-]+)(;q=([0-9 \\ .]+))?! " , trim ( $langpart ), $found )) {
$browser_langs [ $found [ 1 ]] = ( isset ( $found [ 3 ]) ? ( float ) $found [ 3 ] : 1.0 );
}
}
}
// Order the codes by quality
arsort ( $browser_langs );
// Try to find the first preferred language we have
foreach ( $browser_langs as $langcode => $q ) {
if ( isset ( $languages [ $langcode ])) {
return $langcode ;
}
}
return FALSE ;
}
/**
* Identify language from the user preferences .
*
* @ param $languages
* An array of valid language objects .
*
* @ return
* A valid language code on succes , FALSE otherwise .
*/
function locale_language_from_user ( $languages ) {
// User preference (only for logged users).
global $user ;
if ( $user -> uid ) {
return $user -> language ;
}
// No language preference from the user.
return FALSE ;
}
/**
* Identify language from a request / session parameter .
*
* @ param $languages
* An array of valid language objects .
*
* @ return
* A valid language code on succes , FALSE otherwise .
*/
function locale_language_from_session ( $languages ) {
$param = variable_get ( 'locale_language_negotiation_session_param' , 'language' );
// Request parameter.
if ( isset ( $_GET [ $param ]) && isset ( $languages [ $langcode = $_GET [ $param ]])) {
return $_SESSION [ $param ] = $langcode ;
}
// Session parameter.
if ( isset ( $_SESSION [ $param ])) {
return $_SESSION [ $param ];
}
return FALSE ;
}
/**
* Identify language via URL prefix or domain .
*
* @ param $languages
* An array of valid language objects .
*
* @ return
* A valid language code on succes , FALSE otherwise .
*/
function locale_language_from_url ( $languages ) {
$language_url = FALSE ;
switch ( variable_get ( 'locale_language_negotiation_url_part' , LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX )) {
case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX :
// $_GET['q'] might not be available at this time, because
// path initialization runs after the language bootstrap phase.
list ( $language , $_GET [ 'q' ]) = language_url_split_prefix ( isset ( $_GET [ 'q' ]) ? $_GET [ 'q' ] : NULL , $languages );
if ( $language !== FALSE ) {
$language_url = $language -> language ;
}
break ;
case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN :
foreach ( $languages as $language ) {
$host = parse_url ( $language -> domain , PHP_URL_HOST );
if ( $host && ( $_SERVER [ 'HTTP_HOST' ] == $host )) {
$language_url = $language -> language ;
break ;
}
}
break ;
}
return $language_url ;
}
/**
* Return the URL language switcher block . Translation links may be provided by
* other modules .
*/
function locale_language_switcher_url ( $type , $path ) {
$languages = language_list ( 'enabled' );
$links = array ();
foreach ( $languages [ 1 ] as $language ) {
$links [ $language -> language ] = array (
'href' => $path ,
'title' => $language -> native ,
'language' => $language ,
'attributes' => array ( 'class' => array ( 'language-link' )),
);
}
return $links ;
}
/**
* Return the session language switcher block .
*/
function locale_language_switcher_session ( $type , $path ) {
drupal_add_css ( drupal_get_path ( 'module' , 'locale' ) . '/locale.css' );
$param = variable_get ( 'locale_language_negotiation_session_param' , 'language' );
$language_query = isset ( $_SESSION [ $param ]) ? $_SESSION [ $param ] : $GLOBALS [ $type ] -> language ;
$languages = language_list ( 'enabled' );
$links = array ();
$query = $_GET ;
unset ( $query [ 'q' ]);
foreach ( $languages [ 1 ] as $language ) {
$langcode = $language -> language ;
$links [ $langcode ] = array (
'href' => $path ,
'title' => $language -> native ,
'attributes' => array ( 'class' => array ( 'language-link' )),
'query' => $query ,
);
if ( $language_query != $langcode ) {
$links [ $langcode ][ 'query' ][ $param ] = $langcode ;
}
else {
$links [ $langcode ][ 'attributes' ][ 'class' ][] = ' session-active' ;
}
}
return $links ;
}
/**
* Rewrite URLs for the URL language provider .
*/
function locale_language_url_rewrite_url ( & $path , & $options ) {
// Language can be passed as an option, or we go for current URL language.
if ( ! isset ( $options [ 'language' ])) {
global $language_url ;
$options [ 'language' ] = $language_url ;
}
if ( isset ( $options [ 'language' ])) {
switch ( variable_get ( 'locale_language_negotiation_url_part' , LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX )) {
case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN :
if ( $options [ 'language' ] -> domain ) {
// Ask for an absolute URL with our modified base_url.
$options [ 'absolute' ] = TRUE ;
$options [ 'base_url' ] = $options [ 'language' ] -> domain ;
}
break ;
case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX :
if ( ! empty ( $options [ 'language' ] -> prefix )) {
$options [ 'prefix' ] = $options [ 'language' ] -> prefix . '/' ;
}
break ;
}
}
}
/**
* Rewrite URLs for the Session language provider .
*/
function locale_language_url_rewrite_session ( & $path , & $options ) {
static $query_rewrite , $query_param , $query_value ;
// The following values are not supposed to change during a single page
// request processing.
if ( ! isset ( $query_rewrite )) {
global $user ;
if ( ! $user -> uid ) {
$languages = language_list ( 'enabled' );
$languages = $languages [ 1 ];
$query_param = check_plain ( variable_get ( 'locale_language_negotiation_session_param' , 'language' ));
$query_value = isset ( $_GET [ $query_param ]) ? check_plain ( $_GET [ $query_param ]) : NULL ;
$query_rewrite = isset ( $languages [ $query_value ]) && language_negotiation_get_any ( LOCALE_LANGUAGE_NEGOTIATION_SESSION );
}
else {
$query_rewrite = FALSE ;
}
}
// If the user is anonymous, the user language provider is enabled, and the
// corresponding option has been set, we must preserve any explicit user
// language preference even with cookies disabled.
if ( $query_rewrite ) {
if ( is_string ( $options [ 'query' ])) {
$options [ 'query' ] = drupal_get_query_array ( $options [ 'query' ]);
}
if ( ! isset ( $options [ 'query' ][ $query_param ])) {
$options [ 'query' ][ $query_param ] = $query_value ;
}
}
}
2007-05-03 09:51:08 +00:00
/**
* @ } End of " locale-languages-negotiation "
*/
2008-12-09 19:53:36 +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 inport becuase some strings are irreversibly corrupted . For example ,
* a & amp ; in the translation would get encoded to & amp ; 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' )));
}
2007-05-03 09:51:08 +00:00
/**
* @ defgroup locale - api - add Language addition API .
* @ {
*/
/**
* API function to add a language .
*
* @ param $langcode
* Language code .
* @ param $name
* English name of the language
* @ param $native
* Native name of the language
* @ param $direction
* LANGUAGE_LTR or LANGUAGE_RTL
* @ param $domain
* Optional custom domain name with protocol , without
* trailing slash ( eg . http :// de . example . com ) .
* @ param $prefix
* Optional path prefix for the language . Defaults to the
* language code if omitted .
2007-05-15 15:29:49 +00:00
* @ param $enabled
* Optionally TRUE to enable the language when created or FALSE to disable .
* @ param $default
2007-07-02 14:41:37 +00:00
* Optionally set this language to be the default .
2007-05-03 09:51:08 +00:00
*/
2007-05-22 07:42:37 +00:00
function locale_add_language ( $langcode , $name = NULL , $native = NULL , $direction = LANGUAGE_LTR , $domain = '' , $prefix = '' , $enabled = TRUE , $default = FALSE ) {
2007-05-03 09:51:08 +00:00
// Default prefix on language code.
if ( empty ( $prefix )) {
2007-05-08 09:48:14 +00:00
$prefix = $langcode ;
2007-05-03 09:51:08 +00:00
}
2007-05-15 15:29:49 +00:00
// If name was not set, we add a predefined language.
if ( ! isset ( $name )) {
2009-03-17 15:26:29 +00:00
include_once DRUPAL_ROOT . '/includes/iso.inc' ;
2007-05-15 15:29:49 +00:00
$predefined = _locale_get_predefined_list ();
$name = $predefined [ $langcode ][ 0 ];
$native = isset ( $predefined [ $langcode ][ 1 ]) ? $predefined [ $langcode ][ 1 ] : $predefined [ $langcode ][ 0 ];
2007-05-17 08:34:43 +00:00
$direction = isset ( $predefined [ $langcode ][ 2 ]) ? $predefined [ $langcode ][ 2 ] : LANGUAGE_LTR ;
2007-05-15 15:29:49 +00:00
}
2009-03-19 10:48:51 +00:00
db_insert ( 'languages' )
-> fields ( array (
'language' => $langcode ,
'name' => $name ,
'native' => $native ,
'direction' => $direction ,
'domain' => $domain ,
'prefix' => $prefix ,
'enabled' => $enabled ,
))
-> execute ();
2007-05-03 09:51:08 +00:00
2007-05-15 15:29:49 +00:00
// Only set it as default if enabled.
if ( $enabled && $default ) {
2007-11-11 16:14:45 +00:00
variable_set ( 'language_default' , ( object ) array ( 'language' => $langcode , 'name' => $name , 'native' => $native , 'direction' => $direction , 'enabled' => ( int ) $enabled , 'plurals' => 0 , 'formula' => '' , 'domain' => '' , 'prefix' => $prefix , 'weight' => 0 , 'javascript' => '' ));
2007-05-03 09:51:08 +00:00
}
2007-08-20 07:54:46 +00:00
if ( $enabled ) {
// Increment enabled language count if we are adding an enabled language.
variable_set ( 'language_count' , variable_get ( 'language_count' , 1 ) + 1 );
}
2007-05-15 15:29:49 +00:00
2007-11-26 22:34:09 +00:00
// Force JavaScript translation file creation for the newly added language.
_locale_invalidate_js ( $langcode );
2007-05-03 09:51:08 +00:00
watchdog ( 'locale' , 'The %language language (%code) has been created.' , array ( '%language' => $name , '%code' => $langcode ));
2006-03-17 18:35:56 +00:00
}
2007-05-03 09:51:08 +00:00
/**
* @ } End of " locale-api-add "
*/
2006-03-17 18:35:56 +00:00
2007-05-03 09:51:08 +00:00
/**
* @ defgroup locale - api - import Translation import API .
* @ {
*/
2007-03-26 01:32:22 +00:00
2004-08-11 11:26:20 +00:00
/**
* Parses Gettext Portable Object file information and inserts into database
*
2006-09-01 05:38:40 +00:00
* @ param $file
* Drupal file object corresponding to the PO file to import
2007-06-08 12:51:59 +00:00
* @ param $langcode
2006-09-01 05:38:40 +00:00
* Language code
* @ param $mode
2007-06-17 17:41:40 +00:00
* Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
2007-05-03 09:51:08 +00:00
* @ param $group
* Text group to import PO file into ( eg . 'default' for interface translations )
2004-08-11 11:26:20 +00:00
*/
2007-06-08 12:51:59 +00:00
function _locale_import_po ( $file , $langcode , $mode , $group = NULL ) {
2009-08-05 15:58:35 +00:00
// Try to allocate enough time to parse and import the data.
drupal_set_time_limit ( 240 );
2004-08-21 21:13:29 +00:00
2007-06-08 12:51:59 +00:00
// Check if we have the language already in the database.
2009-03-19 10:48:51 +00:00
if ( ! db_query ( " SELECT COUNT(language) FROM { languages} WHERE language = :language " , array ( ':language' => $langcode )) -> fetchField ()) {
2005-05-05 22:22:46 +00:00
drupal_set_message ( t ( 'The language selected for import is not supported.' ), 'error' );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
2004-08-12 18:00:11 +00:00
2006-02-05 15:42:56 +00:00
// Get strings from file (returns on failure after a partial import, or on success)
2007-06-08 12:51:59 +00:00
$status = _locale_import_read_po ( 'db-store' , $file , $mode , $langcode , $group );
2006-06-22 12:23:39 +00:00
if ( $status === FALSE ) {
2007-06-08 12:51:59 +00:00
// Error messages are set in _locale_import_read_po().
2006-06-22 12:23:39 +00:00
return FALSE ;
}
2004-08-12 18:00:11 +00:00
2007-06-08 12:51:59 +00:00
// Get status information on import process.
2009-04-08 02:55:52 +00:00
list ( $header_done , $additions , $updates , $deletes , $skips ) = _locale_import_one_string ( 'db-report' );
2004-08-12 18:00:11 +00:00
2009-04-08 02:55:52 +00:00
if ( ! $header_done ) {
2006-08-18 12:17:00 +00:00
drupal_set_message ( t ( 'The translation file %filename appears to have a missing or malformed header.' , array ( '%filename' => $file -> filename )), 'error' );
2004-08-11 11:26:20 +00:00
}
2004-08-12 18:00:11 +00:00
2007-11-26 22:34:09 +00:00
// Clear cache and force refresh of JavaScript translations.
_locale_invalidate_js ( $langcode );
2007-10-15 19:51:06 +00:00
cache_clear_all ( 'locale:' , 'cache' , TRUE );
2004-10-06 12:18:03 +00:00
2007-06-08 12:51:59 +00:00
// Rebuild the menu, strings may have changed.
2004-10-05 20:09:47 +00:00
menu_rebuild ();
2004-10-06 12:18:03 +00:00
2007-06-17 17:41:40 +00:00
drupal_set_message ( t ( 'The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.' , array ( '%number' => $additions , '%update' => $updates , '%delete' => $deletes )));
watchdog ( 'locale' , 'Imported %file into %locale: %number new strings added, %update updated and %delete removed.' , array ( '%file' => $file -> filename , '%locale' => $langcode , '%number' => $additions , '%update' => $updates , '%delete' => $deletes ));
2008-12-09 19:53:36 +00:00
if ( $skips ) {
$skip_message = format_plural ( $skips , 'One translation string was skipped because it contains disallowed HTML.' , '@count translation strings were skipped because they contain disallowed HTML.' );
drupal_set_message ( $skip_message );
2009-12-06 23:56:47 +00:00
watchdog ( 'locale' , '@count disallowed HTML string(s) in %file' , array ( '@count' => $skips , '%file' => $file -> uri ), WATCHDOG_WARNING );
2008-12-09 19:53:36 +00:00
}
2004-08-11 11:26:20 +00:00
return TRUE ;
}
/**
* Parses Gettext Portable Object file into an array
*
2006-09-01 05:38:40 +00:00
* @ param $op
* Storage operation type : db - store or mem - store
* @ param $file
* Drupal file object corresponding to the PO file to import
* @ param $mode
2007-06-17 17:41:40 +00:00
* Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
2006-09-01 05:38:40 +00:00
* @ param $lang
* Language code
2007-05-03 09:51:08 +00:00
* @ param $group
* Text group to import PO file into ( eg . 'default' for interface translations )
2004-08-11 11:26:20 +00:00
*/
2007-11-22 23:58:34 +00:00
function _locale_import_read_po ( $op , $file , $mode = NULL , $lang = NULL , $group = 'default' ) {
2004-08-11 11:26:20 +00:00
2009-08-17 19:14:42 +00:00
$fd = fopen ( $file -> uri , " rb " ); // File will get closed by PHP on return
2004-08-11 11:26:20 +00:00
if ( ! $fd ) {
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation import failed, because the file %filename could not be read.' , $file );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
$context = " COMMENT " ; // Parser context: COMMENT, MSGID, MSGID_PLURAL, MSGSTR and MSGSTR_ARR
$current = array (); // Current entry being read
$plural = 0 ; // Current plural form
2006-02-05 15:42:56 +00:00
$lineno = 0 ; // Current line
2004-08-11 11:26:20 +00:00
2006-02-05 15:42:56 +00:00
while ( ! feof ( $fd )) {
$line = fgets ( $fd , 10 * 1024 ); // A line should not be this long
2007-11-21 18:24:37 +00:00
if ( $lineno == 0 ) {
2007-11-23 13:34:55 +00:00
// The first line might come with a UTF-8 BOM, which should be removed.
2007-11-21 18:24:37 +00:00
$line = str_replace ( " \xEF \xBB \xBF " , '' , $line );
}
2004-08-11 11:26:20 +00:00
$lineno ++ ;
2006-02-05 15:42:56 +00:00
$line = trim ( strtr ( $line , array ( " \\ \n " => " " )));
2004-08-11 11:26:20 +00:00
if ( ! strncmp ( " # " , $line , 1 )) { // A comment
if ( $context == " COMMENT " ) { // Already in comment context: add
$current [ " # " ][] = substr ( $line , 1 );
}
elseif (( $context == " MSGSTR " ) || ( $context == " MSGSTR_ARR " )) { // End current entry, start a new one
2007-11-21 22:04:37 +00:00
_locale_import_one_string ( $op , $current , $mode , $lang , $file , $group );
2004-08-11 11:26:20 +00:00
$current = array ();
$current [ " # " ][] = substr ( $line , 1 );
$context = " COMMENT " ;
}
else { // Parse error
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains an error: "msgstr" was expected but not found on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
}
elseif ( ! strncmp ( " msgid_plural " , $line , 12 )) {
if ( $context != " MSGID " ) { // Must be plural form for current entry
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
$line = trim ( substr ( $line , 12 ));
$quoted = _locale_import_parse_quoted ( $line );
2006-07-05 11:45:51 +00:00
if ( $quoted === FALSE ) {
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains a syntax error on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
2008-04-14 17:48:46 +00:00
$current [ " msgid " ] = $current [ " msgid " ] . " \0 " . $quoted ;
2004-08-12 18:00:11 +00:00
$context = " MSGID_PLURAL " ;
2004-08-11 11:26:20 +00:00
}
elseif ( ! strncmp ( " msgid " , $line , 5 )) {
if ( $context == " MSGSTR " ) { // End current entry, start a new one
2007-11-21 22:04:37 +00:00
_locale_import_one_string ( $op , $current , $mode , $lang , $file , $group );
2004-08-11 11:26:20 +00:00
$current = array ();
}
elseif ( $context == " MSGID " ) { // Already in this context? Parse error
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains an error: "msgid" is unexpected on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
$line = trim ( substr ( $line , 5 ));
$quoted = _locale_import_parse_quoted ( $line );
2006-07-05 11:45:51 +00:00
if ( $quoted === FALSE ) {
2007-12-22 23:24:26 +00:00
_locale_import_message ( 'The translation file %filename contains a syntax error on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
$current [ " msgid " ] = $quoted ;
$context = " MSGID " ;
}
2009-06-08 05:00:12 +00:00
elseif ( ! strncmp ( " msgctxt " , $line , 7 )) {
if ( $context == " MSGSTR " ) { // End current entry, start a new one
_locale_import_one_string ( $op , $current , $mode , $lang , $file , $group );
$current = array ();
}
elseif ( ! empty ( $current [ " msgctxt " ])) { // Already in this context? Parse error
2009-07-20 17:09:36 +00:00
_locale_import_message ( 'The translation file %filename contains an error: "msgctxt" is unexpected on line %line.' , $file , $lineno );
2009-06-08 05:00:12 +00:00
return FALSE ;
}
$line = trim ( substr ( $line , 7 ));
$quoted = _locale_import_parse_quoted ( $line );
if ( $quoted === FALSE ) {
_locale_import_message ( 'The translation file %filename contains a syntax error on line %line.' , $file , $lineno );
return FALSE ;
}
$current [ " msgctxt " ] = $quoted ;
$context = " MSGCTXT " ;
}
2004-08-11 11:26:20 +00:00
elseif ( ! strncmp ( " msgstr[ " , $line , 7 )) {
2009-06-08 05:00:12 +00:00
if (( $context != " MSGID " ) && ( $context != " MSGCTXT " ) && ( $context != " MSGID_PLURAL " ) && ( $context != " MSGSTR_ARR " )) { // Must come after msgid, msgxtxt, msgid_plural, or msgstr[]
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
2006-07-05 11:45:51 +00:00
if ( strpos ( $line , " ] " ) === FALSE ) {
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains a syntax error on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
$frombracket = strstr ( $line , " [ " );
$plural = substr ( $frombracket , 1 , strpos ( $frombracket , " ] " ) - 1 );
$line = trim ( strstr ( $line , " " ));
$quoted = _locale_import_parse_quoted ( $line );
2006-07-05 11:45:51 +00:00
if ( $quoted === FALSE ) {
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains a syntax error on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
$current [ " msgstr " ][ $plural ] = $quoted ;
$context = " MSGSTR_ARR " ;
}
elseif ( ! strncmp ( " msgstr " , $line , 6 )) {
2009-06-08 05:00:12 +00:00
if (( $context != " MSGID " ) && ( $context != " MSGCTXT " )) { // Should come just after a msgid or msgctxt block
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains an error: "msgstr" is unexpected on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
$line = trim ( substr ( $line , 6 ));
$quoted = _locale_import_parse_quoted ( $line );
2006-07-05 11:45:51 +00:00
if ( $quoted === FALSE ) {
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains a syntax error on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
$current [ " msgstr " ] = $quoted ;
$context = " MSGSTR " ;
}
elseif ( $line != " " ) {
$quoted = _locale_import_parse_quoted ( $line );
2006-07-05 11:45:51 +00:00
if ( $quoted === FALSE ) {
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains a syntax error on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
if (( $context == " MSGID " ) || ( $context == " MSGID_PLURAL " )) {
$current [ " msgid " ] .= $quoted ;
}
2009-06-08 05:00:12 +00:00
elseif ( $context == " MSGCTXT " ) {
$current [ " msgctxt " ] .= $quoted ;
}
2004-08-11 11:26:20 +00:00
elseif ( $context == " MSGSTR " ) {
$current [ " msgstr " ] .= $quoted ;
}
elseif ( $context == " MSGSTR_ARR " ) {
$current [ " msgstr " ][ $plural ] .= $quoted ;
}
else {
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename contains an error: there is an unexpected string on line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
}
}
// End of PO file, flush last entry
2009-06-08 05:00:12 +00:00
if ( ! empty ( $current ) && ! empty ( $current [ 'msgstr' ])) {
2007-11-21 22:04:37 +00:00
_locale_import_one_string ( $op , $current , $mode , $lang , $file , $group );
2004-08-11 11:26:20 +00:00
}
elseif ( $context != " COMMENT " ) {
2006-09-01 05:38:40 +00:00
_locale_import_message ( 'The translation file %filename ended unexpectedly at line %line.' , $file , $lineno );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
2006-02-05 15:42:56 +00:00
}
2006-09-01 05:38:40 +00:00
/**
2006-12-05 05:47:37 +00:00
* Sets an error message occurred during locale file parsing .
2006-09-01 05:38:40 +00:00
*
* @ param $message
* The message to be translated
* @ param $file
* Drupal file object corresponding to the PO file to import
* @ param $lineno
* An optional line number argument
*/
function _locale_import_message ( $message , $file , $lineno = NULL ) {
$vars = array ( '%filename' => $file -> filename );
if ( isset ( $lineno )) {
$vars [ '%line' ] = $lineno ;
}
2006-09-05 02:15:04 +00:00
$t = get_t ();
2006-09-01 05:38:40 +00:00
drupal_set_message ( $t ( $message , $vars ), 'error' );
}
2006-02-05 15:42:56 +00:00
/**
* Imports a string into the database
*
2006-09-01 05:38:40 +00:00
* @ param $op
* Operation to perform : 'db-store' , 'db-report' , 'mem-store' or 'mem-report'
* @ param $value
* Details of the string stored
* @ param $mode
2007-06-17 17:41:40 +00:00
* Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
2006-09-01 05:38:40 +00:00
* @ param $lang
* Language to store the string in
2007-04-06 14:15:43 +00:00
* @ param $file
* Object representation of file being imported , only required when op is 'db-store'
2007-05-03 09:51:08 +00:00
* @ param $group
* Text group to import PO file into ( eg . 'default' for interface translations )
2006-02-05 15:42:56 +00:00
*/
2007-05-03 09:51:08 +00:00
function _locale_import_one_string ( $op , $value = NULL , $mode = NULL , $lang = NULL , $file = NULL , $group = 'default' ) {
2009-04-08 02:55:52 +00:00
$report = & drupal_static ( __FUNCTION__ , array ( 'additions' => 0 , 'updates' => 0 , 'deletes' => 0 , 'skips' => 0 ));
$header_done = & drupal_static ( __FUNCTION__ . ':header_done' , FALSE );
$strings = & drupal_static ( __FUNCTION__ . ':strings' , array ());
2006-09-01 05:38:40 +00:00
switch ( $op ) {
// Return stored strings
case 'mem-report' :
return $strings ;
// Store string in memory (only supports single strings)
case 'mem-store' :
2009-06-08 05:00:12 +00:00
$strings [ isset ( $value [ 'msgctxt' ]) ? $value [ 'msgctxt' ] : '' ][ $value [ 'msgid' ]] = $value [ 'msgstr' ];
2006-09-01 05:38:40 +00:00
return ;
// Called at end of import to inform the user
case 'db-report' :
2009-04-08 02:55:52 +00:00
return array ( $header_done , $report [ 'additions' ], $report [ 'updates' ], $report [ 'deletes' ], $report [ 'skips' ]);
2006-09-01 05:38:40 +00:00
2007-06-17 17:41:40 +00:00
// Store the string we got in the database.
2006-09-01 05:38:40 +00:00
case 'db-store' :
2007-06-17 17:41:40 +00:00
// We got header information.
2006-09-01 05:38:40 +00:00
if ( $value [ 'msgid' ] == '' ) {
2007-06-17 17:41:40 +00:00
$header = _locale_import_parse_header ( $value [ 'msgstr' ]);
2006-09-01 05:38:40 +00:00
2007-06-17 17:41:40 +00:00
// Get the plural formula and update in database.
2009-08-17 19:14:42 +00:00
if ( isset ( $header [ " Plural-Forms " ]) && $p = _locale_import_parse_plural_forms ( $header [ " Plural-Forms " ], $file -> uri )) {
2006-09-01 05:38:40 +00:00
list ( $nplurals , $plural ) = $p ;
2009-03-19 10:48:51 +00:00
db_update ( 'languages' )
-> fields ( array (
'plurals' => $nplurals ,
'formula' => $plural ,
))
-> condition ( 'language' , $lang )
-> execute ();
2006-02-05 15:42:56 +00:00
}
2006-09-01 05:38:40 +00:00
else {
2009-03-19 10:48:51 +00:00
db_update ( 'languages' )
-> fields ( array (
'plurals' => 0 ,
'formula' => '' ,
))
-> condition ( 'language' , $lang )
-> execute ();
2006-09-01 05:38:40 +00:00
}
2009-04-08 02:55:52 +00:00
$header_done = TRUE ;
2006-09-01 05:38:40 +00:00
}
else {
2007-06-17 17:41:40 +00:00
// Some real string to import.
2007-05-08 09:48:14 +00:00
$comments = _locale_import_shorten_comments ( empty ( $value [ '#' ]) ? array () : $value [ '#' ]);
2007-06-22 08:32:28 +00:00
2006-09-01 05:38:40 +00:00
if ( strpos ( $value [ 'msgid' ], " \0 " )) {
2007-06-17 17:41:40 +00:00
// This string has plural versions.
2006-09-01 05:38:40 +00:00
$english = explode ( " \0 " , $value [ 'msgid' ], 2 );
$entries = array_keys ( $value [ 'msgstr' ]);
for ( $i = 3 ; $i <= count ( $entries ); $i ++ ) {
$english [] = $english [ 1 ];
}
$translation = array_map ( '_locale_import_append_plural' , $value [ 'msgstr' ], $entries );
$english = array_map ( '_locale_import_append_plural' , $english , $entries );
foreach ( $translation as $key => $trans ) {
if ( $key == 0 ) {
$plid = 0 ;
2006-02-05 15:42:56 +00:00
}
2009-06-08 05:00:12 +00:00
$plid = _locale_import_one_string_db ( $report , $lang , isset ( $value [ 'msgctxt' ]) ? $value [ 'msgctxt' ] : '' , $english [ $key ], $trans , $group , $comments , $mode , $plid , $key );
2006-02-05 15:42:56 +00:00
}
}
2006-09-01 05:38:40 +00:00
else {
2007-06-17 17:41:40 +00:00
// A simple string to import.
2006-09-01 05:38:40 +00:00
$english = $value [ 'msgid' ];
$translation = $value [ 'msgstr' ];
2009-06-08 05:00:12 +00:00
_locale_import_one_string_db ( $report , $lang , isset ( $value [ 'msgctxt' ]) ? $value [ 'msgctxt' ] : '' , $english , $translation , $group , $comments , $mode );
2006-02-05 15:42:56 +00:00
}
}
2006-09-01 05:38:40 +00:00
} // end of db-store operation
2004-08-11 11:26:20 +00:00
}
2007-06-17 17:41:40 +00:00
/**
* Import one string into the database .
*
* @ param $report
* Report array summarizing the number of changes done in the form :
* array ( inserts , updates , deletes ) .
* @ param $langcode
* Language code to import string into .
2009-06-08 05:00:12 +00:00
* @ param $context
* The context of this string .
2007-06-17 17:41:40 +00:00
* @ param $source
* Source string .
* @ param $translation
* Translation to language specified in $langcode .
* @ param $textgroup
* Name of textgroup to store translation in .
* @ param $location
* Location value to save with source string .
* @ param $mode
* Import mode to use , LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE .
* @ param $plid
* Optional plural ID to use .
* @ param $plural
* Optional plural value to use .
* @ return
* The string ID of the existing string modified or the new string added .
*/
2009-06-08 05:00:12 +00:00
function _locale_import_one_string_db ( & $report , $langcode , $context , $source , $translation , $textgroup , $location , $mode , $plid = 0 , $plural = 0 ) {
$lid = db_query ( " SELECT lid FROM { locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup " , array ( ':source' => $source , ':context' => $context , ':textgroup' => $textgroup )) -> fetchField ();
2007-06-17 17:41:40 +00:00
if ( ! empty ( $translation )) {
2008-12-09 19:53:36 +00:00
// Skip this string unless it passes a check for dangerous code.
2009-01-22 03:29:01 +00:00
// Text groups other than default still can contain HTML tags
// (i.e. translatable blocks).
if ( $textgroup == " default " && ! locale_string_is_safe ( $translation )) {
2008-12-09 19:53:36 +00:00
$report [ 'skips' ] ++ ;
$lid = 0 ;
}
elseif ( $lid ) {
2007-06-17 17:41:40 +00:00
// We have this source string saved already.
2009-03-19 10:48:51 +00:00
db_update ( 'locales_source' )
-> fields ( array (
'location' => $location ,
))
-> condition ( 'lid' , $lid )
-> execute ();
$exists = db_query ( " SELECT COUNT(lid) FROM { locales_target} WHERE lid = :lid AND language = :language " , array ( ':lid' => $lid , ':language' => $langcode )) -> fetchField ();
2007-06-17 17:41:40 +00:00
if ( ! $exists ) {
// No translation in this language.
2009-03-19 10:48:51 +00:00
db_insert ( 'locales_target' )
-> fields ( array (
'lid' => $lid ,
'language' => $langcode ,
'translation' => $translation ,
'plid' => $plid ,
'plural' => $plural ,
))
-> execute ();
2008-12-09 19:53:36 +00:00
$report [ 'additions' ] ++ ;
2007-06-17 17:41:40 +00:00
}
2008-10-12 04:30:09 +00:00
elseif ( $mode == LOCALE_IMPORT_OVERWRITE ) {
2007-07-02 14:41:37 +00:00
// Translation exists, only overwrite if instructed.
2009-03-19 10:48:51 +00:00
db_update ( 'locales_target' )
-> fields ( array (
'translation' => $translation ,
'plid' => $plid ,
'plural' => $plural ,
))
-> condition ( 'language' , $langcode )
-> condition ( 'lid' , $lid )
-> execute ();
2008-12-09 19:53:36 +00:00
$report [ 'updates' ] ++ ;
2007-06-17 17:41:40 +00:00
}
}
else {
// No such source string in the database yet.
2009-03-19 10:48:51 +00:00
$lid = db_insert ( 'locales_source' )
2009-06-08 05:00:12 +00:00
-> fields ( array (
'location' => $location ,
'source' => $source ,
'context' => ( string ) $context ,
'textgroup' => $textgroup ,
))
2009-03-19 10:48:51 +00:00
-> execute ();
db_insert ( 'locales_target' )
-> fields ( array (
'lid' => $lid ,
'language' => $langcode ,
'translation' => $translation ,
'plid' => $plid ,
'plural' => $plural
))
-> execute ();
2008-12-09 19:53:36 +00:00
$report [ 'additions' ] ++ ;
2007-06-17 17:41:40 +00:00
}
}
elseif ( $mode == LOCALE_IMPORT_OVERWRITE ) {
// Empty translation, remove existing if instructed.
2009-03-19 10:48:51 +00:00
db_delete ( 'locales_target' )
-> condition ( 'language' , $langcode )
-> condition ( 'lid' , $lid )
-> condition ( 'plid' , $plid )
-> condition ( 'plural' , $plural )
-> execute ();
2008-12-09 19:53:36 +00:00
$report [ 'deletes' ] ++ ;
2007-06-17 17:41:40 +00:00
}
2007-06-22 08:32:28 +00:00
2007-06-17 17:41:40 +00:00
return $lid ;
}
2004-08-11 11:26:20 +00:00
/**
* Parses a Gettext Portable Object file header
*
2007-05-08 09:46:24 +00:00
* @ param $header
* A string containing the complete header
* @ return
* An associative array of key - value pairs
2004-08-11 11:26:20 +00:00
*/
function _locale_import_parse_header ( $header ) {
2007-06-17 17:41:40 +00:00
$header_parsed = array ();
$lines = array_map ( 'trim' , explode ( " \n " , $header ));
2004-08-11 11:26:20 +00:00
foreach ( $lines as $line ) {
if ( $line ) {
list ( $tag , $contents ) = explode ( " : " , $line , 2 );
2007-06-17 17:41:40 +00:00
$header_parsed [ trim ( $tag )] = trim ( $contents );
2004-08-11 11:26:20 +00:00
}
}
2007-06-17 17:41:40 +00:00
return $header_parsed ;
2004-08-11 11:26:20 +00:00
}
/**
* Parses a Plural - Forms entry from a Gettext Portable Object file header
*
2006-09-01 05:38:40 +00:00
* @ param $pluralforms
* A string containing the Plural - Forms entry
2009-07-30 08:40:22 +00:00
* @ param $filepath
* A string containing the filepath
2006-09-01 05:38:40 +00:00
* @ return
* An array containing the number of plurals and a
* formula in PHP for computing the plural form
2004-08-11 11:26:20 +00:00
*/
2009-07-30 08:40:22 +00:00
function _locale_import_parse_plural_forms ( $pluralforms , $filepath ) {
2004-08-11 11:26:20 +00:00
// First, delete all whitespace
$pluralforms = strtr ( $pluralforms , array ( " " => " " , " \t " => " " ));
// Select the parts that define nplurals and plural
$nplurals = strstr ( $pluralforms , " nplurals= " );
if ( strpos ( $nplurals , " ; " )) {
$nplurals = substr ( $nplurals , 9 , strpos ( $nplurals , " ; " ) - 9 );
}
else {
return FALSE ;
}
$plural = strstr ( $pluralforms , " plural= " );
if ( strpos ( $plural , " ; " )) {
$plural = substr ( $plural , 7 , strpos ( $plural , " ; " ) - 7 );
}
else {
return FALSE ;
}
// Get PHP version of the plural formula
$plural = _locale_import_parse_arithmetic ( $plural );
2006-07-06 14:41:37 +00:00
if ( $plural !== FALSE ) {
2004-08-11 11:26:20 +00:00
return array ( $nplurals , $plural );
}
else {
2009-07-30 08:40:22 +00:00
drupal_set_message ( t ( 'The translation file %filepath contains an error: the plural formula could not be parsed.' , array ( '%filepath' => $filepath )), 'error' );
2004-08-11 11:26:20 +00:00
return FALSE ;
}
}
/**
* Parses and sanitizes an arithmetic formula into a PHP expression
*
* While parsing , we ensure , that the operators have the right
* precedence and associativity .
*
2007-05-08 09:46:24 +00:00
* @ param $string
* A string containing the arithmetic formula
* @ return
* The PHP version of the formula
2004-08-11 11:26:20 +00:00
*/
function _locale_import_parse_arithmetic ( $string ) {
// Operator precedence table
$prec = array ( " ( " => - 1 , " ) " => - 1 , " ? " => 1 , " : " => 1 , " || " => 3 , " && " => 4 , " == " => 5 , " != " => 5 , " < " => 6 , " > " => 6 , " <= " => 6 , " >= " => 6 , " + " => 7 , " - " => 7 , " * " => 8 , " / " => 8 , " % " => 8 );
// Right associativity
$rasc = array ( " ? " => 1 , " : " => 1 );
$tokens = _locale_import_tokenize_formula ( $string );
// Parse by converting into infix notation then back into postfix
$opstk = array ();
$elstk = array ();
foreach ( $tokens as $token ) {
$ctok = $token ;
// Numbers and the $n variable are simply pushed into $elarr
if ( is_numeric ( $token )) {
$elstk [] = $ctok ;
}
elseif ( $ctok == " n " ) {
$elstk [] = '$n' ;
}
elseif ( $ctok == " ( " ) {
$opstk [] = $ctok ;
}
elseif ( $ctok == " ) " ) {
$topop = array_pop ( $opstk );
2007-05-03 09:51:08 +00:00
while ( isset ( $topop ) && ( $topop != " ( " )) {
2004-08-11 11:26:20 +00:00
$elstk [] = $topop ;
$topop = array_pop ( $opstk );
}
}
2007-04-04 20:47:41 +00:00
elseif ( ! empty ( $prec [ $ctok ])) {
2004-08-11 11:26:20 +00:00
// If it's an operator, then pop from $oparr into $elarr until the
// precedence in $oparr is less than current, then push into $oparr
$topop = array_pop ( $opstk );
2007-05-03 09:51:08 +00:00
while ( isset ( $topop ) && ( $prec [ $topop ] >= $prec [ $ctok ]) && ! (( $prec [ $topop ] == $prec [ $ctok ]) && ! empty ( $rasc [ $topop ]) && ! empty ( $rasc [ $ctok ]))) {
2004-08-11 11:26:20 +00:00
$elstk [] = $topop ;
$topop = array_pop ( $opstk );
}
if ( $topop ) {
$opstk [] = $topop ; // Return element to top
}
$opstk [] = $ctok ; // Parentheses are not needed
}
else {
2006-07-05 11:45:51 +00:00
return FALSE ;
2004-08-11 11:26:20 +00:00
}
}
// Flush operator stack
$topop = array_pop ( $opstk );
while ( $topop != NULL ) {
$elstk [] = $topop ;
$topop = array_pop ( $opstk );
}
// Now extract formula from stack
$prevsize = count ( $elstk ) + 1 ;
while ( count ( $elstk ) < $prevsize ) {
$prevsize = count ( $elstk );
for ( $i = 2 ; $i < count ( $elstk ); $i ++ ) {
$op = $elstk [ $i ];
2007-05-03 09:51:08 +00:00
if ( ! empty ( $prec [ $op ])) {
2004-08-11 11:26:20 +00:00
$f = " " ;
if ( $op == " : " ) {
2008-04-14 17:48:46 +00:00
$f = $elstk [ $i - 2 ] . " ): " . $elstk [ $i - 1 ] . " ) " ;
2004-08-11 11:26:20 +00:00
}
elseif ( $op == " ? " ) {
2008-04-14 17:48:46 +00:00
$f = " ( " . $elstk [ $i - 2 ] . " ?( " . $elstk [ $i - 1 ];
2004-08-11 11:26:20 +00:00
}
else {
2008-04-14 17:48:46 +00:00
$f = " ( " . $elstk [ $i - 2 ] . $op . $elstk [ $i - 1 ] . " ) " ;
2004-08-11 11:26:20 +00:00
}
array_splice ( $elstk , $i - 2 , 3 , $f );
break ;
}
}
}
// If only one element is left, the number of operators is appropriate
if ( count ( $elstk ) == 1 ) {
return $elstk [ 0 ];
}
else {
return FALSE ;
}
}
/**
* Backward compatible implementation of token_get_all () for formula parsing
*
2007-05-08 09:46:24 +00:00
* @ param $string
* A string containing the arithmetic formula
* @ return
* The PHP version of the formula
2004-08-11 11:26:20 +00:00
*/
function _locale_import_tokenize_formula ( $formula ) {
$formula = str_replace ( " " , " " , $formula );
$tokens = array ();
for ( $i = 0 ; $i < strlen ( $formula ); $i ++ ) {
2006-01-15 07:14:14 +00:00
if ( is_numeric ( $formula [ $i ])) {
$num = $formula [ $i ];
2004-08-11 11:26:20 +00:00
$j = $i + 1 ;
2007-01-02 05:05:38 +00:00
while ( $j < strlen ( $formula ) && is_numeric ( $formula [ $j ])) {
2006-01-15 07:14:14 +00:00
$num .= $formula [ $j ];
2004-08-11 11:26:20 +00:00
$j ++ ;
}
$i = $j - 1 ;
$tokens [] = $num ;
}
2006-01-15 07:14:14 +00:00
elseif ( $pos = strpos ( " =<>!&| " , $formula [ $i ])) { // We won't have a space
$next = $formula [ $i + 1 ];
2004-08-11 11:26:20 +00:00
switch ( $pos ) {
case 1 :
case 2 :
case 3 :
case 4 :
if ( $next == '=' ) {
2008-04-14 17:48:46 +00:00
$tokens [] = $formula [ $i ] . '=' ;
2004-08-11 11:26:20 +00:00
$i ++ ;
}
else {
2006-01-15 07:14:14 +00:00
$tokens [] = $formula [ $i ];
2004-08-11 11:26:20 +00:00
}
break ;
case 5 :
if ( $next == '&' ) {
$tokens [] = '&&' ;
$i ++ ;
}
else {
2006-01-15 07:14:14 +00:00
$tokens [] = $formula [ $i ];
2004-08-11 11:26:20 +00:00
}
break ;
case 6 :
if ( $next == '|' ) {
$tokens [] = '||' ;
$i ++ ;
}
else {
2006-01-15 07:14:14 +00:00
$tokens [] = $formula [ $i ];
2004-08-11 11:26:20 +00:00
}
break ;
}
}
else {
2006-01-15 07:14:14 +00:00
$tokens [] = $formula [ $i ];
2004-08-11 11:26:20 +00:00
}
}
return $tokens ;
}
/**
* Modify a string to contain proper count indices
*
* This is a callback function used via array_map ()
*
2007-05-08 09:46:24 +00:00
* @ param $entry
* An array element
* @ param $key
* Index of the array element
2004-08-11 11:26:20 +00:00
*/
function _locale_import_append_plural ( $entry , $key ) {
2004-08-12 18:00:11 +00:00
// No modifications for 0, 1
2004-08-11 11:26:20 +00:00
if ( $key == 0 || $key == 1 ) {
return $entry ;
}
// First remove any possibly false indices, then add new ones
2006-08-18 12:17:00 +00:00
$entry = preg_replace ( '/(@count)\[[0-9]\]/' , '\\1' , $entry );
return preg_replace ( '/(@count)/' , " \\ 1[ $key ] " , $entry );
2004-08-11 11:26:20 +00:00
}
/**
* Generate a short , one string version of the passed comment array
*
2007-05-08 09:46:24 +00:00
* @ param $comment
* An array of strings containing a comment
* @ return
* Short one string version of the comment
2004-08-11 11:26:20 +00:00
*/
function _locale_import_shorten_comments ( $comment ) {
$comm = '' ;
2005-07-29 18:56:58 +00:00
while ( count ( $comment )) {
2008-04-14 17:48:46 +00:00
$test = $comm . substr ( array_shift ( $comment ), 1 ) . ', ' ;
2005-07-29 18:56:58 +00:00
if ( strlen ( $comm ) < 130 ) {
$comm = $test ;
}
else {
break ;
}
2004-08-11 11:26:20 +00:00
}
2009-12-09 15:35:48 +00:00
return trim ( substr ( $comm , 0 , - 2 ));
2004-08-11 11:26:20 +00:00
}
/**
* Parses a string in quotes
*
2007-05-08 09:46:24 +00:00
* @ param $string
* A string specified with enclosing quotes
* @ return
* The string parsed from inside the quotes
2004-08-11 11:26:20 +00:00
*/
function _locale_import_parse_quoted ( $string ) {
if ( substr ( $string , 0 , 1 ) != substr ( $string , - 1 , 1 )) {
return FALSE ; // Start and end quotes must be the same
}
$quote = substr ( $string , 0 , 1 );
$string = substr ( $string , 1 , - 1 );
if ( $quote == '"' ) { // Double quotes: strip slashes
return stripcslashes ( $string );
}
elseif ( $quote == " ' " ) { // Simple quote: return as-is
return $string ;
}
else {
return FALSE ; // Unrecognized quote
}
}
2007-05-03 09:51:08 +00:00
/**
* @ } End of " locale-api-import "
*/
2007-06-08 12:51:59 +00:00
/**
* 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 ) {
global $language ;
// 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.
2008-04-14 17:48:46 +00:00
preg_match_all ( '~[^\w]Drupal\s*\.\s*t\s*\(\s*(' . LOCALE_JS_STRING . ')\s*[,\)]~s' , $file , $t_matches );
2007-06-08 12:51:59 +00:00
// Match all Drupal.formatPlural() calls in another array.
2008-04-14 17:48:46 +00:00
preg_match_all ( '~[^\w]Drupal\s*\.\s*formatPlural\s*\(\s*.+?\s*,\s*(' . LOCALE_JS_STRING . ')\s*,\s*((?:(?:\'(?:\\\\\'|[^\'])*@count(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*@count(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+)\s*[,\)]~s' , $file , $plural_matches );
2007-06-08 12:51:59 +00:00
// Loop through all matches and process them.
$all_matches = array_merge ( $plural_matches [ 1 ], $t_matches [ 1 ]);
foreach ( $all_matches as $key => $string ) {
$strings = array ( $string );
// If there is also a plural version of this string, add it to the strings array.
if ( isset ( $plural_matches [ 2 ][ $key ])) {
$strings [] = $plural_matches [ 2 ][ $key ];
}
foreach ( $strings as $key => $string ) {
// Remove the quotes and string concatenations from the string.
$string = implode ( '' , preg_split ( '~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s' , substr ( $string , 1 , - 1 )));
2009-03-19 10:48:51 +00:00
$source = db_query ( " SELECT lid, location FROM { locales_source} WHERE source = :source AND textgroup = 'default' " , array ( ':source' => $string )) -> fetchObject ();
if ( $source ) {
2007-06-08 12:51:59 +00:00
// 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.
2009-03-19 10:48:51 +00:00
db_update ( 'locales_source' )
-> fields ( array (
'location' => $locations ,
))
-> condition ( 'lid' , $source -> lid )
-> execute ();
2007-06-08 12:51:59 +00:00
}
}
else {
// We don't have the source string yet, thus we insert it into the database.
2009-03-19 10:48:51 +00:00
db_insert ( 'locales_source' )
-> fields ( array (
'location' => $filepath ,
'source' => $string ,
2009-06-08 05:00:12 +00:00
'context' => '' ,
2009-03-19 10:48:51 +00:00
'textgroup' => 'default' ,
))
-> execute ();
2007-06-08 12:51:59 +00:00
}
}
}
}
2007-05-03 09:51:08 +00:00
/**
* @ defgroup locale - api - export Translation ( template ) export API .
* @ {
*/
2004-08-11 11:26:20 +00:00
/**
2007-06-23 20:45:36 +00:00
* Generates a structured array of all strings with translations in
* $language , if given . This array can be used to generate an export
* of the string in the database .
2004-08-11 11:26:20 +00:00
*
2007-03-28 14:08:23 +00:00
* @ param $language
2007-06-23 20:45:36 +00:00
* Language object to generate the output for , or NULL if generating
2007-03-28 14:08:23 +00:00
* translation template .
2007-05-03 09:51:08 +00:00
* @ param $group
* Text group to export PO file from ( eg . 'default' for interface translations )
2004-08-11 11:26:20 +00:00
*/
2007-06-23 20:45:36 +00:00
function _locale_export_get_strings ( $language = NULL , $group = 'default' ) {
2007-03-28 14:08:23 +00:00
if ( isset ( $language )) {
2009-06-08 05:00:12 +00:00
$result = db_query ( " SELECT s.lid, s.source, s.context, s.location, t.translation, t.plid, t.plural FROM { locales_source} s LEFT JOIN { locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural " , array ( ':language' => $language -> language , ':textgroup' => $group ));
2004-08-11 11:26:20 +00:00
}
else {
2009-06-08 05:00:12 +00:00
$result = db_query ( " SELECT s.lid, s.source, s.context, s.location, t.plid, t.plural FROM { locales_source} s LEFT JOIN { locales_target} t ON s.lid = t.lid WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural " , array ( ':textgroup' => $group ));
2004-08-11 11:26:20 +00:00
}
2007-06-23 20:45:36 +00:00
$strings = array ();
2009-03-19 10:48:51 +00:00
foreach ( $result as $child ) {
2007-06-23 20:45:36 +00:00
$string = array (
'comment' => $child -> location ,
'source' => $child -> source ,
2009-06-08 05:00:12 +00:00
'context' => $child -> context ,
'translation' => isset ( $child -> translation ) ? $child -> translation : '' ,
2007-06-23 20:45:36 +00:00
);
if ( $child -> plid ) {
// Has a parent lid. Since we process in the order of plids,
// we already have the parent in the array, so we can add the
// lid to the next plural version to it. This builds a linked
// list of plurals.
$string [ 'child' ] = TRUE ;
$strings [ $child -> plid ][ 'plural' ] = $child -> lid ;
2004-08-11 11:26:20 +00:00
}
2007-06-23 20:45:36 +00:00
$strings [ $child -> lid ] = $string ;
2004-08-11 11:26:20 +00:00
}
2007-06-23 20:45:36 +00:00
return $strings ;
}
2004-08-12 18:00:11 +00:00
2007-06-23 20:45:36 +00:00
/**
* Generates the PO ( T ) file contents for given strings .
2007-06-28 07:48:41 +00:00
*
2007-06-23 20:45:36 +00:00
* @ param $language
* Language object to generate the output for , or NULL if generating
* translation template .
* @ param $strings
* Array of strings to export . See _locale_export_get_strings ()
* on how it should be formatted .
* @ param $header
* The header portion to use for the output file . Defaults
* are provided for PO and POT files .
*/
function _locale_export_po_generate ( $language = NULL , $strings = array (), $header = NULL ) {
global $user ;
2007-06-28 07:48:41 +00:00
2007-06-23 20:45:36 +00:00
if ( ! isset ( $header )) {
if ( isset ( $language )) {
2008-04-14 17:48:46 +00:00
$header = '# ' . $language -> name . ' translation of ' . variable_get ( 'site_name' , 'Drupal' ) . " \n " ;
$header .= '# Generated by ' . $user -> name . ' <' . $user -> mail . " > \n " ;
2007-06-23 20:45:36 +00:00
$header .= " # \n " ;
$header .= " msgid \" \" \n " ;
$header .= " msgstr \" \" \n " ;
$header .= " \" Project-Id-Version: PROJECT VERSION \\ n \" \n " ;
2008-04-14 17:48:46 +00:00
$header .= " \" POT-Creation-Date: " . date ( " Y-m-d H:iO " ) . " \\ n \" \n " ;
$header .= " \" PO-Revision-Date: " . date ( " Y-m-d H:iO " ) . " \\ n \" \n " ;
2007-06-23 20:45:36 +00:00
$header .= " \" Last-Translator: NAME <EMAIL@ADDRESS> \\ n \" \n " ;
$header .= " \" Language-Team: LANGUAGE <EMAIL@ADDRESS> \\ n \" \n " ;
$header .= " \" MIME-Version: 1.0 \\ n \" \n " ;
$header .= " \" Content-Type: text/plain; charset=utf-8 \\ n \" \n " ;
$header .= " \" Content-Transfer-Encoding: 8bit \\ n \" \n " ;
if ( $language -> formula && $language -> plurals ) {
2008-04-14 17:48:46 +00:00
$header .= " \" Plural-Forms: nplurals= " . $language -> plurals . " ; plural= " . strtr ( $language -> formula , array ( '$' => '' )) . " ; \\ n \" \n " ;
2007-06-23 20:45:36 +00:00
}
}
else {
$header = " # LANGUAGE translation of PROJECT \n " ;
$header .= " # Copyright (c) YEAR NAME <EMAIL@ADDRESS> \n " ;
$header .= " # \n " ;
$header .= " msgid \" \" \n " ;
$header .= " msgstr \" \" \n " ;
$header .= " \" Project-Id-Version: PROJECT VERSION \\ n \" \n " ;
2008-04-14 17:48:46 +00:00
$header .= " \" POT-Creation-Date: " . date ( " Y-m-d H:iO " ) . " \\ n \" \n " ;
2007-06-23 20:45:36 +00:00
$header .= " \" PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ \\ n \" \n " ;
$header .= " \" Last-Translator: NAME <EMAIL@ADDRESS> \\ n \" \n " ;
$header .= " \" Language-Team: LANGUAGE <EMAIL@ADDRESS> \\ n \" \n " ;
$header .= " \" MIME-Version: 1.0 \\ n \" \n " ;
$header .= " \" Content-Type: text/plain; charset=utf-8 \\ n \" \n " ;
$header .= " \" Content-Transfer-Encoding: 8bit \\ n \" \n " ;
$header .= " \" Plural-Forms: nplurals=INTEGER; plural=EXPRESSION; \\ n \" \n " ;
2004-08-11 11:26:20 +00:00
}
}
2007-06-28 07:48:41 +00:00
2008-04-14 17:48:46 +00:00
$output = $header . " \n " ;
2007-06-28 07:48:41 +00:00
2007-06-23 20:45:36 +00:00
foreach ( $strings as $lid => $string ) {
// Only process non-children, children are output below their parent.
if ( ! isset ( $string [ 'child' ])) {
if ( $string [ 'comment' ]) {
2008-04-14 17:48:46 +00:00
$output .= '#: ' . $string [ 'comment' ] . " \n " ;
2004-08-11 11:26:20 +00:00
}
2008-04-14 17:48:46 +00:00
$output .= 'msgid ' . _locale_export_string ( $string [ 'source' ]);
2009-06-08 05:00:12 +00:00
if ( ! empty ( $string [ 'context' ])) {
$output .= 'msgctxt ' . _locale_export_string ( $string [ 'context' ]);
}
2007-06-23 20:45:36 +00:00
if ( ! empty ( $string [ 'plural' ])) {
$plural = $string [ 'plural' ];
2008-04-14 17:48:46 +00:00
$output .= 'msgid_plural ' . _locale_export_string ( $strings [ $plural ][ 'source' ]);
2007-03-28 14:08:23 +00:00
if ( isset ( $language )) {
2007-06-23 20:45:36 +00:00
$translation = $string [ 'translation' ];
for ( $i = 0 ; $i < $language -> plurals ; $i ++ ) {
2008-04-14 17:48:46 +00:00
$output .= 'msgstr[' . $i . '] ' . _locale_export_string ( $translation );
2006-09-05 11:40:44 +00:00
if ( $plural ) {
2007-06-23 20:45:36 +00:00
$translation = _locale_export_remove_plural ( $strings [ $plural ][ 'translation' ]);
$plural = isset ( $strings [ $plural ][ 'plural' ]) ? $strings [ $plural ][ 'plural' ] : 0 ;
2006-09-05 11:40:44 +00:00
}
else {
$translation = '' ;
}
2004-08-11 11:26:20 +00:00
}
}
else {
2008-04-14 17:48:46 +00:00
$output .= 'msgstr[0] ""' . " \n " ;
$output .= 'msgstr[1] ""' . " \n " ;
2004-08-11 11:26:20 +00:00
}
}
else {
2008-04-14 17:48:46 +00:00
$output .= 'msgstr ' . _locale_export_string ( $string [ 'translation' ]);
2004-08-11 11:26:20 +00:00
}
2007-06-23 20:45:36 +00:00
$output .= " \n " ;
2004-08-11 11:26:20 +00:00
}
}
2007-06-23 20:45:36 +00:00
return $output ;
}
/**
* Write a generated PO or POT file to the output .
*
* @ param $language
* Language object to generate the output for , or NULL if generating
* translation template .
* @ param $output
* The PO ( T ) file to output as a string . See _locale_export_generate_po ()
* on how it can be generated .
*/
function _locale_export_po ( $language = NULL , $output = NULL ) {
// Log the export event.
if ( isset ( $language )) {
2008-04-14 17:48:46 +00:00
$filename = $language -> language . '.po' ;
2007-06-23 20:45:36 +00:00
watchdog ( 'locale' , 'Exported %locale translation file: %filename.' , array ( '%locale' => $language -> name , '%filename' => $filename ));
}
else {
$filename = 'drupal.pot' ;
watchdog ( 'locale' , 'Exported translation file: %filename.' , array ( '%filename' => $filename ));
}
// Download the file fo the client.
header ( " Content-Disposition: attachment; filename= $filename " );
header ( " Content-Type: text/plain; charset=utf-8 " );
print $output ;
2004-08-11 11:26:20 +00:00
die ();
}
/**
* Print out a string on multiple lines
*/
2007-06-23 20:45:36 +00:00
function _locale_export_string ( $str ) {
2004-08-11 11:26:20 +00:00
$stri = addcslashes ( $str , " \0 .. \37 \\ \" " );
$parts = array ();
// Cut text into several lines
while ( $stri != " " ) {
$i = strpos ( $stri , " \\ n " );
if ( $i === FALSE ) {
$curstr = $stri ;
$stri = " " ;
}
else {
$curstr = substr ( $stri , 0 , $i + 2 );
$stri = substr ( $stri , $i + 2 );
}
$curparts = explode ( " \n " , _locale_export_wrap ( $curstr , 70 ));
$parts = array_merge ( $parts , $curparts );
}
2007-03-28 14:08:23 +00:00
// Multiline string
2004-08-11 11:26:20 +00:00
if ( count ( $parts ) > 1 ) {
2008-04-14 17:48:46 +00:00
return " \" \" \n \" " . implode ( " \" \n \" " , $parts ) . " \" \n " ;
2004-08-11 11:26:20 +00:00
}
2007-03-28 14:08:23 +00:00
// Single line string
elseif ( count ( $parts ) == 1 ) {
2004-08-11 11:26:20 +00:00
return " \" $parts[0] \" \n " ;
}
2007-03-28 14:08:23 +00:00
// No translation
else {
return " \" \" \n " ;
}
2004-08-11 11:26:20 +00:00
}
/**
* Custom word wrapping for Portable Object ( Template ) files .
*/
function _locale_export_wrap ( $str , $len ) {
2006-01-13 14:38:38 +00:00
$words = explode ( ' ' , $str );
2009-09-29 15:13:57 +00:00
$return = array ();
2004-08-11 11:26:20 +00:00
$cur = " " ;
$nstr = 1 ;
while ( count ( $words )) {
$word = array_shift ( $words );
if ( $nstr ) {
$cur = $word ;
$nstr = 0 ;
}
elseif ( strlen ( " $cur $word " ) > $len ) {
2009-09-29 15:13:57 +00:00
$return [] = $cur . " " ;
2004-08-11 11:26:20 +00:00
$cur = $word ;
}
else {
$cur = " $cur $word " ;
}
}
2009-09-29 15:13:57 +00:00
$return [] = $cur ;
2004-08-11 11:26:20 +00:00
2009-09-29 15:13:57 +00:00
return implode ( " \n " , $return );
2004-08-11 11:26:20 +00:00
}
/**
* Removes plural index information from a string
*/
function _locale_export_remove_plural ( $entry ) {
2006-08-18 12:17:00 +00:00
return preg_replace ( '/(@count)\[[0-9]\]/' , '\\1' , $entry );
2004-08-11 11:26:20 +00:00
}
/**
2007-05-03 09:51:08 +00:00
* @ } End of " locale-api-export "
2004-08-11 11:26:20 +00:00
*/
/**
2007-05-03 09:51:08 +00:00
* @ defgroup locale - api - seek String search functions .
* @ {
2004-08-11 11:26:20 +00:00
*/
/**
* Perform a string search and display results in a table
*/
2007-05-03 09:51:08 +00:00
function _locale_translate_seek () {
2007-03-26 01:32:22 +00:00
$output = '' ;
2005-03-31 21:18:08 +00:00
// We have at least one criterion to match
2009-02-05 00:32:47 +00:00
if ( ! ( $query = _locale_translate_seek_query ())) {
$query = array (
'translation' => 'all' ,
'group' => 'all' ,
'language' => 'all' ,
'string' => '' ,
);
}
2004-08-11 11:26:20 +00:00
2009-03-19 10:48:51 +00:00
$sql_query = db_select ( 'locales_source' , 's' );
$sql_query -> leftJoin ( 'locales_target' , 't' , 't.lid = s.lid' );
2009-06-08 05:00:12 +00:00
$sql_query -> fields ( 's' , array ( 'source' , 'location' , 'context' , 'lid' , 'textgroup' ));
2009-03-19 10:48:51 +00:00
$sql_query -> fields ( 't' , array ( 'translation' , 'language' ));
2004-08-11 11:26:20 +00:00
2009-03-19 10:48:51 +00:00
// Compute LIKE section.
2009-02-05 00:32:47 +00:00
switch ( $query [ 'translation' ]) {
case 'translated' :
2009-03-19 10:48:51 +00:00
$sql_query -> condition ( 't.translation' , '%' . $query [ 'string' ] . '%' , 'LIKE' );
$sql_query -> orderBy ( 't.translation' , 'DESC' );
2009-02-05 00:32:47 +00:00
break ;
case 'untranslated' :
2009-03-19 10:48:51 +00:00
$sql_query -> condition ( db_and ()
-> condition ( 's.source' , '%' . $query [ 'string' ] . '%' , 'LIKE' )
-> isNull ( 't.translation' )
);
$sql_query -> orderBy ( 's.source' );
2009-02-05 00:32:47 +00:00
break ;
case 'all' :
default :
2009-03-19 10:48:51 +00:00
$condition = db_or ()
-> condition ( 's.source' , '%' . $query [ 'string' ] . '%' , 'LIKE' );
if ( $query [ 'language' ] != 'en' ) {
// Only search in translations if the language is not forced to English.
$condition -> condition ( 't.translation' , '%' . $query [ 'string' ] . '%' , 'LIKE' );
}
$sql_query -> condition ( $condition );
2009-02-05 00:32:47 +00:00
break ;
}
2009-03-19 10:48:51 +00:00
$limit_language = NULL ;
if ( $query [ 'language' ] != 'en' && $query [ 'language' ] != 'all' ) {
$sql_query -> condition ( 'language' , $query [ 'language' ]);
$limit_language = $query [ 'language' ];
2009-02-05 00:32:47 +00:00
}
2004-08-11 11:26:20 +00:00
2009-03-19 10:48:51 +00:00
// Add a condition on the text group.
if ( ! empty ( $query [ 'group' ]) && $query [ 'group' ] != 'all' ) {
$sql_query -> condition ( 's.textgroup' , $query [ 'group' ]);
2009-02-05 00:32:47 +00:00
}
2009-03-19 10:48:51 +00:00
$sql_query = $sql_query -> extend ( 'PagerDefault' ) -> limit ( 50 );
$locales = $sql_query -> execute ();
2009-02-05 00:32:47 +00:00
$groups = module_invoke_all ( 'locale' , 'groups' );
2009-06-08 05:00:12 +00:00
$header = array ( t ( 'Text group' ), t ( 'String' ), t ( 'Context' ), ( $limit_language ) ? t ( 'Language' ) : t ( 'Languages' ), array ( 'data' => t ( 'Operations' ), 'colspan' => '2' ));
2009-03-19 10:48:51 +00:00
$strings = array ();
foreach ( $locales as $locale ) {
if ( ! isset ( $strings [ $locale -> lid ])) {
$strings [ $locale -> lid ] = array (
'group' => $locale -> textgroup ,
'languages' => array (),
'location' => $locale -> location ,
'source' => $locale -> source ,
2009-06-08 05:00:12 +00:00
'context' => $locale -> context ,
2009-03-19 10:48:51 +00:00
);
}
if ( isset ( $locale -> language )) {
$strings [ $locale -> lid ][ 'languages' ][ $locale -> language ] = $locale -> translation ;
}
2009-02-05 00:32:47 +00:00
}
2009-03-19 10:48:51 +00:00
2009-02-05 00:32:47 +00:00
$rows = array ();
2009-03-19 10:48:51 +00:00
foreach ( $strings as $lid => $string ) {
2009-02-05 00:32:47 +00:00
$rows [] = array (
2009-03-19 10:48:51 +00:00
$groups [ $string [ 'group' ]],
array ( 'data' => check_plain ( truncate_utf8 ( $string [ 'source' ], 150 , FALSE , TRUE )) . '<br /><small>' . $string [ 'location' ] . '</small>' ),
2009-06-08 05:00:12 +00:00
$string [ 'context' ],
2009-03-19 10:48:51 +00:00
array ( 'data' => _locale_translate_language_list ( $string [ 'languages' ], $limit_language ), 'align' => 'center' ),
2009-08-22 14:34:23 +00:00
array ( 'data' => l ( t ( 'edit' ), " admin/config/regional/translate/edit/ $lid " , array ( 'query' => drupal_get_destination ())), 'class' => array ( 'nowrap' )),
array ( 'data' => l ( t ( 'delete' ), " admin/config/regional/translate/delete/ $lid " , array ( 'query' => drupal_get_destination ())), 'class' => array ( 'nowrap' )),
2009-02-05 00:32:47 +00:00
);
}
2009-12-02 14:56:32 +00:00
$output .= theme ( 'table' , array ( 'header' => $header , 'rows' => $rows , 'empty' => t ( 'No strings available.' )));
$output .= theme ( 'pager' , array ( 'tags' => NULL ));
2004-08-11 11:26:20 +00:00
return $output ;
}
2007-05-03 09:51:08 +00:00
/**
* Build array out of search criteria specified in request variables
*/
function _locale_translate_seek_query () {
2009-04-08 02:55:52 +00:00
$query = & drupal_static ( __FUNCTION__ );
2007-05-03 09:51:08 +00:00
if ( ! isset ( $query )) {
$query = array ();
$fields = array ( 'string' , 'language' , 'translation' , 'group' );
foreach ( $fields as $field ) {
2009-02-05 00:32:47 +00:00
if ( isset ( $_SESSION [ 'locale_translation_filter' ][ $field ])) {
$query [ $field ] = $_SESSION [ 'locale_translation_filter' ][ $field ];
2007-05-03 09:51:08 +00:00
}
}
}
return $query ;
}
2007-11-26 22:34:09 +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
* 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 ();
unset ( $languages [ 'en' ]);
foreach ( $languages as $lcode => $data ) {
2008-04-14 17:48:46 +00:00
$parsed [ 'refresh:' . $lcode ] = 'waiting' ;
2007-11-26 22:34:09 +00:00
}
}
else {
// Invalidate single language.
2008-04-14 17:48:46 +00:00
$parsed [ 'refresh:' . $langcode ] = 'waiting' ;
2007-11-26 22:34:09 +00:00
}
variable_set ( 'javascript_parsed' , $parsed );
return $parsed ;
}
2007-06-08 12:51:59 +00:00
/**
* ( Re - ) Creates the JavaScript translation file for a language .
*
* @ param $language
* The language , the translation file should be ( re ) created for .
*/
function _locale_rebuild_js ( $langcode = NULL ) {
if ( ! isset ( $langcode )) {
global $language ;
}
else {
// Get information about the locale.
$languages = language_list ();
$language = $languages [ $langcode ];
}
// Construct the array for JavaScript translations.
// We sort on plural so that we have all plural forms before singular forms.
2009-03-19 10:48:51 +00:00
$result = db_query ( " SELECT s.lid, s.source, t.plid, t.plural, t.translation FROM { locales_source} s LEFT JOIN { locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup ORDER BY t.plural DESC " , array ( ':language' => $language -> language , ':textgroup' => 'default' ));
2007-06-08 12:51:59 +00:00
$translations = $plurals = array ();
2009-03-19 10:48:51 +00:00
foreach ( $result as $data ) {
2007-06-08 12:51:59 +00:00
// Only add this to the translations array when there is actually a translation.
if ( ! empty ( $data -> translation )) {
if ( $data -> plural ) {
// When the translation is a plural form, first add it to another array and
// wait for the singular (parent) translation.
if ( ! isset ( $plurals [ $data -> plid ])) {
$plurals [ $data -> plid ] = array ( $data -> plural => $data -> translation );
}
else {
$plurals [ $data -> plid ] += array ( $data -> plural => $data -> translation );
}
}
elseif ( isset ( $plurals [ $data -> lid ])) {
// There are plural translations for this translation, so get them from
// the plurals array and add them to the final translations array.
$translations [ $data -> source ] = array ( $data -> plural => $data -> translation ) + $plurals [ $data -> lid ];
unset ( $plurals [ $data -> lid ]);
}
else {
// There are no plural forms for this translation, so just add it to
// the translations array.
$translations [ $data -> source ] = $data -> translation ;
}
}
}
2007-11-26 22:34:09 +00:00
// Construct the JavaScript file, if there are translations.
2010-01-04 04:53:33 +00:00
$data_hash = NULL ;
2007-11-26 22:34:09 +00:00
$data = $status = '' ;
2007-06-08 12:51:59 +00:00
if ( ! empty ( $translations )) {
2007-11-26 22:34:09 +00:00
2007-06-08 12:51:59 +00:00
$data = " Drupal.locale = { " ;
if ( ! empty ( $language -> formula )) {
2009-04-27 20:19:38 +00:00
$data .= " 'pluralFormula': function ( \$ n) { return Number( { $language -> formula } ); }, " ;
2007-06-08 12:51:59 +00:00
}
2009-09-21 07:56:09 +00:00
$data .= " 'strings': " . drupal_json_encode ( $translations ) . " }; " ;
2007-06-08 12:51:59 +00:00
$data_hash = md5 ( $data );
2007-11-26 22:34:09 +00:00
}
2007-06-08 12:51:59 +00:00
2007-11-26 22:34:09 +00:00
// Construct the filepath where JS translation files are stored.
// There is (on purpose) no front end to edit that variable.
2009-08-17 19:14:42 +00:00
$dir = 'public://' . variable_get ( 'locale_js_directory' , 'languages' );
2007-06-08 12:51:59 +00:00
2007-11-26 22:34:09 +00:00
// Delete old file, if we have no translations anymore, or a different file to be saved.
2010-01-04 04:53:33 +00:00
$changed_hash = $language -> javascript != $data_hash ;
if ( ! empty ( $language -> javascript ) && ( ! $data || $changed_hash )) {
2009-08-17 19:14:42 +00:00
file_unmanaged_delete ( $dir . '/' . $language -> language . '_' . $language -> javascript . '.js' );
2007-11-26 22:34:09 +00:00
$language -> javascript = '' ;
$status = 'deleted' ;
}
2007-06-08 12:51:59 +00:00
2010-01-04 04:53:33 +00:00
// Only create a new file if the content has changed or the original file got
// lost.
$dest = $dir . '/' . $language -> language . '_' . $data_hash . '.js' ;
if ( $data && ( $changed_hash || ! file_exists ( $dest ))) {
2007-11-26 22:34:09 +00:00
// Ensure that the directory exists and is writable, if possible.
2009-08-17 19:14:42 +00:00
file_prepare_directory ( $dir , FILE_CREATE_DIRECTORY );
2007-06-08 12:51:59 +00:00
2007-11-26 22:34:09 +00:00
// Save the file.
2008-10-09 00:02:29 +00:00
if ( file_unmanaged_save_data ( $data , $dest )) {
2007-11-26 22:34:09 +00:00
$language -> javascript = $data_hash ;
2010-01-04 04:53:33 +00:00
// 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' ;
}
2007-11-26 22:34:09 +00:00
}
else {
$language -> javascript = '' ;
$status = 'error' ;
}
}
2007-06-08 12:51:59 +00:00
2010-01-04 04:53:33 +00:00
// 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 ) {
2009-03-19 10:48:51 +00:00
db_update ( 'languages' )
2009-03-25 13:38:09 +00:00
-> fields ( array (
2009-03-19 10:48:51 +00:00
'javascript' => $language -> javascript ,
))
-> condition ( 'language' , $language -> language )
-> execute ();
2007-11-26 22:34:09 +00:00
// Update the default language variable if the default language has been altered.
// This is necessary to keep the variable consistent with the database
// version of the language and to prevent checking against an outdated hash.
$default_langcode = language_default ( 'language' );
if ( $default_langcode == $language -> language ) {
2009-03-19 10:48:51 +00:00
$default = db_query ( " SELECT * FROM { languages} WHERE language = :language " , array ( ':language' => $default_langcode )) -> fetchObject ();
2007-11-26 22:34:09 +00:00
variable_set ( 'language_default' , $default );
2007-06-08 12:51:59 +00:00
}
}
2007-11-26 22:34:09 +00:00
// Log the operation and return success flag.
switch ( $status ) {
case 'updated' :
watchdog ( 'locale' , 'Updated JavaScript translation file for the language %language.' , array ( '%language' => t ( $language -> name )));
return TRUE ;
2010-01-04 04:53:33 +00:00
case 'rebuilt' :
watchdog ( 'locale' , 'JavaScript translation file %file.js was lost.' , array ( '%file' => $language -> javascript ), WATCHDOG_WARNING );
// Proceed to the 'created' case as the JavaScript translation file has
// been created again.
2007-11-26 22:34:09 +00:00
case 'created' :
watchdog ( 'locale' , 'Created JavaScript translation file for the language %language.' , array ( '%language' => t ( $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' => t ( $language -> name )));
return TRUE ;
case 'error' :
watchdog ( 'locale' , 'An error occurred during creation of the JavaScript translation file for the language %language.' , array ( '%language' => t ( $language -> name )), WATCHDOG_ERROR );
return FALSE ;
default :
// No operation needed.
return TRUE ;
2007-06-08 12:51:59 +00:00
}
}
2007-05-03 09:51:08 +00:00
/**
* List languages in search result table
*/
2007-09-01 12:50:47 +00:00
function _locale_translate_language_list ( $translation , $limit_language ) {
2009-02-05 00:32:47 +00:00
// Add CSS.
2008-10-26 18:06:39 +00:00
drupal_add_css ( drupal_get_path ( 'module' , 'locale' ) . '/locale.css' , array ( 'preprocess' => FALSE ));
2007-05-03 09:51:08 +00:00
$languages = language_list ();
unset ( $languages [ 'en' ]);
$output = '' ;
foreach ( $languages as $langcode => $language ) {
2007-09-01 12:50:47 +00:00
if ( ! $limit_language || $limit_language == $langcode ) {
2008-04-14 17:48:46 +00:00
$output .= ( ! empty ( $translation [ $langcode ])) ? $langcode . ' ' : " <em class= \" locale-untranslated \" > $langcode </em> " ;
2007-09-01 12:50:47 +00:00
}
2007-05-03 09:51:08 +00:00
}
return $output ;
}
/**
* @ } End of " locale-api-seek "
*/
/**
* @ defgroup locale - api - predefined List of predefined languages
* @ {
*/
2004-08-11 11:26:20 +00:00
/**
* Prepares the language code list for a select form item with only the unsupported ones
*/
2007-03-26 01:32:22 +00:00
function _locale_prepare_predefined_list () {
2009-03-17 15:26:29 +00:00
include_once DRUPAL_ROOT . '/includes/iso.inc' ;
2007-03-26 01:32:22 +00:00
$languages = language_list ();
$predefined = _locale_get_predefined_list ();
foreach ( $predefined as $key => $value ) {
if ( isset ( $languages [ $key ])) {
unset ( $predefined [ $key ]);
2004-08-11 11:26:20 +00:00
continue ;
}
2007-03-26 01:32:22 +00:00
// Include native name in output, if possible
2007-08-04 13:08:17 +00:00
if ( count ( $value ) > 1 ) {
2004-08-11 11:26:20 +00:00
$tname = t ( $value [ 0 ]);
2007-03-26 01:32:22 +00:00
$predefined [ $key ] = ( $tname == $value [ 1 ]) ? $tname : " $tname ( $value[1] ) " ;
2004-08-11 11:26:20 +00:00
}
else {
2007-03-26 01:32:22 +00:00
$predefined [ $key ] = t ( $value [ 0 ]);
2004-08-11 11:26:20 +00:00
}
}
2007-03-26 01:32:22 +00:00
asort ( $predefined );
return $predefined ;
2004-08-11 11:26:20 +00:00
}
2007-05-03 09:51:08 +00:00
/**
* @ } End of " locale-api-languages-predefined "
*/
2007-05-15 15:29:49 +00:00
/**
* @ defgroup locale - autoimport Automatic interface translation import
* @ {
*/
/**
2007-05-22 07:42:37 +00:00
* Prepare a batch to import translations for all enabled
* modules in a given language .
2007-05-15 15:29:49 +00:00
*
* @ param $langcode
* Language code to import translations for .
2007-05-22 07:42:37 +00:00
* @ param $finished
* Optional finished callback for the batch .
2007-11-19 13:56:14 +00:00
* @ param $skip
* Array of component names to skip . Used in the installer for the
* second pass import , when most components are already imported .
2007-05-15 15:29:49 +00:00
* @ return
* A batch structure or FALSE if no files found .
*/
2007-11-19 13:56:14 +00:00
function locale_batch_by_language ( $langcode , $finished = NULL , $skip = array ()) {
2007-05-15 15:29:49 +00:00
// Collect all files to import for all enabled modules and themes.
$files = array ();
2007-11-19 13:56:14 +00:00
$components = array ();
2009-01-06 13:20:17 +00:00
$query = db_select ( 'system' , 's' );
$query -> fields ( 's' , array ( 'name' , 'filename' ));
$query -> condition ( 's.status' , 1 );
2007-11-19 13:56:14 +00:00
if ( count ( $skip )) {
2009-01-06 13:20:17 +00:00
$query -> condition ( 'name' , $skip , 'NOT IN' );
2007-11-19 13:56:14 +00:00
}
2009-01-06 13:20:17 +00:00
$result = $query -> execute ();
foreach ( $result as $component ) {
2007-05-15 15:29:49 +00:00
// Collect all files for all components, names as $langcode.po or
// with names ending with $langcode.po. This allows for filenames
// like node-module.de.po to let translators use small files and
// be able to import in smaller chunks.
2009-02-18 15:07:27 +00:00
$files = array_merge ( $files , file_scan_directory ( dirname ( $component -> filename ) . '/translations' , '/(^|\.)' . $langcode . '\.po$/' , array ( 'recurse' => FALSE )));
2007-11-19 13:56:14 +00:00
$components [] = $component -> name ;
2007-05-15 15:29:49 +00:00
}
2007-11-19 13:56:14 +00:00
return _locale_batch_build ( $files , $finished , $components );
2007-05-15 15:29:49 +00:00
}
2007-10-15 15:18:39 +00:00
/**
* Prepare a batch to run when installing modules or enabling themes .
* This batch will import translations for the newly added components
* in all the languages already set up on the site .
*
* @ param $components
* An array of component ( theme and / or module ) names to import
* translations for .
* @ param $finished
* Optional finished callback for the batch .
*/
function locale_batch_by_component ( $components , $finished = '_locale_batch_system_finished' ) {
$files = array ();
$languages = language_list ( 'enabled' );
unset ( $languages [ 1 ][ 'en' ]);
if ( count ( $languages [ 1 ])) {
$language_list = join ( '|' , array_keys ( $languages [ 1 ]));
// Collect all files to import for all $components.
$result = db_query ( " SELECT name, filename FROM { system} WHERE status = 1 " );
2009-03-19 10:48:51 +00:00
foreach ( $result as $component ) {
2007-10-15 15:18:39 +00:00
if ( in_array ( $component -> name , $components )) {
// Collect all files for this component in all enabled languages, named
// as $langcode.po or with names ending with $langcode.po. This allows
// for filenames like node-module.de.po to let translators use small
// files and be able to import in smaller chunks.
2009-02-18 15:07:27 +00:00
$files = array_merge ( $files , file_scan_directory ( dirname ( $component -> filename ) . '/translations' , '/(^|\.)(' . $language_list . ')\.po$/' , array ( 'recurse' => FALSE )));
2007-10-15 15:18:39 +00:00
}
}
return _locale_batch_build ( $files , $finished );
}
return FALSE ;
}
2007-05-15 15:29:49 +00:00
/**
* Build a locale batch from an array of files .
*
2007-05-21 10:56:05 +00:00
* @ param $files
* Array of files to import
2007-05-15 15:29:49 +00:00
* @ param $finished
2007-05-22 07:42:37 +00:00
* Optional finished callback for the batch .
2007-11-19 13:56:14 +00:00
* @ param $components
* Optional list of component names the batch covers . Used in the installer .
2007-05-15 15:29:49 +00:00
* @ return
* A batch structure
*/
2007-11-19 13:56:14 +00:00
function _locale_batch_build ( $files , $finished = NULL , $components = array ()) {
2007-05-15 15:29:49 +00:00
$t = get_t ();
2007-05-21 10:56:05 +00:00
if ( count ( $files )) {
$operations = array ();
2007-09-04 21:10:45 +00:00
foreach ( $files as $file ) {
2007-05-21 10:56:05 +00:00
// We call _locale_batch_import for every batch operation.
2009-08-17 19:14:42 +00:00
$operations [] = array ( '_locale_batch_import' , array ( $file -> uri ));
2009-02-22 17:55:30 +00:00
}
$batch = array (
'operations' => $operations ,
'title' => $t ( 'Importing interface translations' ),
'init_message' => $t ( 'Starting import' ),
'error_message' => $t ( 'Error importing interface translations' ),
'file' => 'includes/locale.inc' ,
// This is not a batch API construct, but data passed along to the
// installer, so we know what did we import already.
'#components' => $components ,
);
if ( isset ( $finished )) {
$batch [ 'finished' ] = $finished ;
}
2007-05-15 15:29:49 +00:00
return $batch ;
}
return FALSE ;
}
/**
2007-10-15 15:18:39 +00:00
* Perform interface translation import as a batch step .
*
* @ param $filepath
* Path to a file to import .
* @ param $results
* Contains a list of files imported .
*/
function _locale_batch_import ( $filepath , & $context ) {
// The filename is either {langcode}.po or {prefix}.{langcode}.po, so
// we can extract the language code to use for the import from the end.
if ( preg_match ( '!(/|\.)([^\./]+)\.po$!' , $filepath , $langcode )) {
2009-08-17 19:14:42 +00:00
$file = ( object ) array ( 'filename' => basename ( $filepath ), 'uri' => $filepath );
2007-10-15 15:18:39 +00:00
_locale_import_read_po ( 'db-store' , $file , LOCALE_IMPORT_KEEP , $langcode [ 2 ]);
$context [ 'results' ][] = $filepath ;
}
}
/**
* Finished callback of system page locale import batch .
* Inform the user of translation files imported .
2007-05-15 15:29:49 +00:00
*/
2007-10-15 15:18:39 +00:00
function _locale_batch_system_finished ( $success , $results ) {
if ( $success ) {
drupal_set_message ( format_plural ( count ( $results ), 'One translation file imported for the newly installed modules.' , '@count translation files imported for the newly installed modules.' ));
}
2007-05-15 15:29:49 +00:00
}
2007-05-21 10:56:05 +00:00
/**
2007-10-15 15:18:39 +00:00
* Finished callback of language addition locale import batch .
* Inform the user of translation files imported .
2007-05-21 10:56:05 +00:00
*/
2007-10-15 15:18:39 +00:00
function _locale_batch_language_finished ( $success , $results ) {
if ( $success ) {
drupal_set_message ( format_plural ( count ( $results ), 'One translation file imported for the enabled modules.' , '@count translation files imported for the enabled modules.' ));
2007-05-21 10:56:05 +00:00
}
}
2007-10-15 15:18:39 +00:00
2007-05-15 15:29:49 +00:00
/**
* @ } End of " locale-autoimport "
*/
2009-03-17 15:26:29 +00:00
/**
* Get list of all predefined and custom countries .
*
* @ return
* An array of all country code => country name pairs .
*/
function country_get_list () {
include_once DRUPAL_ROOT . '/includes/iso.inc' ;
$countries = _country_get_predefined_list ();
// Allow other modules to modify the country list.
drupal_alter ( 'countries' , $countries );
return $countries ;
}
2009-10-13 21:34:15 +00:00
/**
* 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 ;
}