Index: modules/locale/locale.module =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v retrieving revision 1.237 diff -u -r1.237 locale.module --- modules/locale/locale.module 5 Feb 2009 00:32:46 -0000 1.237 +++ modules/locale/locale.module 9 Feb 2009 17:01:09 -0000 @@ -196,7 +196,20 @@ function locale_locale($op = 'groups') { switch ($op) { case 'groups': - return array('default' => t('Built-in interface')); + return array( + 'default' => array( + 'name' => t('Built-in interface'), + 'index' => 'source', // This group is indexed by source string + 'cache' => 'LENGTH (s.source) < 75', // Condition to fill in the cache + 'version' => VERSION, // This group's string may change with Drupal version + 'update' => TRUE, // Update sources as we are translating + ), + 'text' => array( + 'name' => t('Long texts from modules'), + 'index' => 'location', + 'cache' => FALSE, + ), + ); } } @@ -325,8 +338,9 @@ /** * Provides interface translation services. - * - * This function is called from t() to translate a string if needed. + * + * This function is string based (will use the source string for indexing) + * It is called from t() to translate a hardcoded string if needed. * * @param $string * A string to look up translation for. If omitted, all the @@ -334,10 +348,64 @@ * used on the page. * @param $langcode * Language code to use for the lookup. + * @param $textgroup + * The text group this string belongs too * @param $reset * Set to TRUE to reset the in-memory cache. */ -function locale($string = NULL, $langcode = NULL, $reset = FALSE) { +function locale_string($string = NULL, $langcode = NULL, $textgroup = 'default', $reset = FALSE) { + return locale_translate($textgroup, $string, 'source', $langcode, $string, $reset); +} + +/** + * Provides interface translation services. + * + * This function is location based (will use the string location for indexing) + * It is called from t() to translate a hardcoded string if needed. + * + * @param $textgroup + * The text group this string belongs too + * @param $location + * Source string location, will be used to find the translation + * @param $langcode + * Language code to use for the lookup. + * @param $textgroup + * The text group this string belongs too + * @param $string + * Optional source string to be used for update operations + * @param $reset + * Set to TRUE to reset the in-memory cache. + * + * @return + * Translated string if found. Passed $string if not. + */ +function locale_location($textgroup = NULL, $location = NULL, $langcode = NULL, $string = NULL, $reset = FALSE) { + return locale_translate($textgroup, $string, 'location', $langcode, $string, $reset); +} + +/** + * Provides interface translation services. + * + * This function will find translations for any of the text groups + * searching by any field (source, location) + * + * @param $textgroup + * Text group to search for + * @param $value + * Unique key for this string inside the text group + * @param $index + * Field to use as index that will be matched against $value + * @param $string + * The source string, that may be used for updating + * @param $langcode + * Language code to use for the lookup. + * @param $reset + * Set to TRUE to reset the in-memory cache. + * + * @return string + * Translated string if found, source string otherwise + */ +function locale_translate($textgroup = NULL, $value = NULL, $index = NULL, $langcode = NULL, $string = NULL, $reset = FALSE) { global $language; static $locale_t; @@ -345,66 +413,54 @@ // Reset in-memory cache. $locale_t = NULL; } - - if (!isset($string)) { - // Return all cached strings if no string was specified + // Return all cached strings or all the text group if parameters missing + if (!isset($textgroup)) { return $locale_t; } + elseif (!isset($value)) { + return isset($locale_t[$textgroup]) ? $locale_t[$textgroup] : array(); + } $langcode = isset($langcode) ? $langcode : $language->language; + // If not passed index, get the default for this text group + if (!$index) { + $index = locale_textgroups($textgroup, $index); + } + if (!$string && $index == 'source') { + $string = $value; + } // Store database cached translations in a static var. - if (!isset($locale_t[$langcode])) { - $locale_t[$langcode] = array(); - // Disabling the usage of string caching allows a module to watch for - // the exact list of strings used on a page. From a performance - // perspective that is a really bad idea, so we have no user - // interface for this. Be careful when turning this option off! - if (variable_get('locale_cache_strings', 1) == 1) { - if ($cache = cache_get('locale:' . $langcode, 'cache')) { - $locale_t[$langcode] = $cache->data; - } - else { - // Refresh database stored cache of translations for given language. - // We only store short strings used in current version, to improve - // performance and consume less memory. - $result = db_query("SELECT s.source, t.translation, t.language FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = '%s' WHERE s.textgroup = 'default' AND s.version = '%s' AND LENGTH(s.source) < 75", $langcode, VERSION); - while ($data = db_fetch_object($result)) { - $locale_t[$langcode][$data->source] = (empty($data->translation) ? TRUE : $data->translation); - } - cache_set('locale:' . $langcode, $locale_t[$langcode]); - } - } + if (!isset($locale_t[$textgroup][$index][$langcode])) { + $locale_t[$textgroup][$index][$langcode] = _locale_get_cache($textgroup, $index, $langcode); } // If we have the translation cached, skip checking the database - if (!isset($locale_t[$langcode][$string])) { - - // We do not have this translation cached, so get it from the DB. - $translation = db_fetch_object(db_query("SELECT s.lid, t.translation, s.version FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = '%s' WHERE s.source = '%s' AND s.textgroup = 'default'", $langcode, $string)); - if ($translation) { - // We have the source string at least. - // Cache translation string or TRUE if no translation exists. - $locale_t[$langcode][$string] = (empty($translation->translation) ? TRUE : $translation->translation); - - if ($translation->version != VERSION) { - // This is the first use of this string under current Drupal version. Save version + if (!isset($locale_t[$textgroup][$index][$langcode][$value])) { + // If the group has auto update mode, retrieve full translation and check some more stuff + $update = locale_textgroup($textgroup, 'update'); + $translation = locale_get_translation(array('textgroup' => $textgroup, $index => $value, 'language' => $langcode), $update); + // Cache translation string or TRUE if no translation exists. + $locale_t[$textgroup][$index][$langcode][$value] = empty($translation->translation) ? TRUE : $translation->translation; + if ($string && $update) { + // Some groups use version, some groups dosn't + $version = locale_textgroups($textgroup, 'version'); + if (!$translation && $string) { + $source = array('textgroup' => $textgroup, 'source' => $string, 'version' => $version, 'location' => request_uri()); + $source += array($index => $value); + locale_save_source($source); + } + elseif ($translation && $version && $translation->version != $version) { + // This is the first use of this string under current version. Save version // and clear cache, to include the string into caching next time. Saved version is // also a string-history information for later pruning of the tables. - db_query("UPDATE {locales_source} SET version = '%s' WHERE lid = %d", VERSION, $translation->lid); - cache_clear_all('locale:', 'cache', TRUE); + db_query("UPDATE {locales_source} SET version = '%s' WHERE lid = %d", $version, $translation->lid); + variable_set('locale_rebuild_' . $textgroup, 1); } } - else { - // We don't have the source string, cache this as untranslated. - db_query("INSERT INTO {locales_source} (location, source, textgroup, version) VALUES ('%s', '%s', 'default', '%s')", request_uri(), $string, VERSION); - $locale_t[$langcode][$string] = TRUE; - // Clear locale cache so this string can be added in a later request. - cache_clear_all('locale:', 'cache', TRUE); - } } - return ($locale_t[$langcode][$string] === TRUE ? $string : $locale_t[$langcode][$string]); + return $locale_t[$textgroup][$index][$langcode][$value] === TRUE ? $string : $locale_t[$textgroup][$index][$langcode][$value]; } /** @@ -557,6 +613,190 @@ } } +/** + * @defgroup locale-api Locale API functions for source and target strings + * @{ + */ + +/** + * Get information about text groups. + * + * This will return information for all text groups if not group specified, information for that + * group if specified, or the value of a group's property if $property is passed. + * + * @param $group + * Optional group name + * @param $property + * Optional value to query + */ +function locale_textgroup($group = NULL, $property = NULL) { + static $textgroups; + + if (!isset($textgroups)) { + $textgroups = module_invoke_all('locale', 'groups'); + } + + if ($group && $property) { + return isset($textgroups[$group][$property]) ? $textgroups[$group][$property] : NULL; + } + elseif ($group) { + return isset($textgroups[$group]) ? $textgroups[$group] : NULL; + } + elseif ($property) { + $list = array(); + foreach ($textgroups as $key => $data) { + isset($data[$property]) ? ($list[$key] = $data[$property]) : NULL; + } + return $list; + } + else { + return $textgroups; + } +} + +/** + * Get source for a string, given a key and a value + * + * @param $conditions + * Either a source lid or an array of key => value pairs + * + * @return object + * Source string + */ +function locale_get_source($conditions) { + static $_cache; + + if (is_numeric($conditions)) { + $conditions = array('lid' => $conditions); + } + + $query = db_select('locales_source', 's'); + $query->fields('s', array('lid', 'textgroup', 'source', 'location', 'version')); + foreach ($conditions as $field => $value) { + $query->condition($field, $value); + } + return $query->execute()->fetchObject(); +} + +/** + * Save / update a source string + * + * @param $string + * Object representing a source string + */ +function locale_save_source(&$string) { + // Convert always into object + $string = (object)$string; + // Invalidate cache if this text group is cacheable + if (locale_textgroup($string->textgroup, 'cache')) { + variable_set('locale_rebuild_' . $string->textgroup, 1); + } + if (!empty($string->lid)) { + return drupal_write_record('locale_source', $string, 'lid'); + } else { + return drupal_write_record('locale_source', $string); + } +} + +/** + * Get localized text for any textgroup search by location. + * + * This will search by location field instead of source string, and is used for long module texts + * + * @param $conditions + * Array of search conditions + * + * @param $getsource + * Whether to get source data only in case there's no translation + * @return Object + * The full translation object + */ +function locale_get_translation($conditions, $getsource = FALSE) { + $query = db_select('locales_source', 's'); + // If we want source data too, add fields and LEFT JOIN, otherwise no fields and INNER JOIN + if ($getsource) { + $query->fields('s', array('lid', 'version')); + $join = 'leftJoin'; + } + else { + $join = 'join'; + } + $query->$join('locales_target', 't', 's.lid = t.lid AND t.language = :langcode', array(':langcode' => $conditions['language'])); + unset($conditions['language']); + $query->fields('t', array('translation', 'plural')); + foreach ($conditions as $field => $value) { + $query->condition('s.' . $field, $value); + } + return $query->execute()->fetchObject(); +} + +/** + * Save / update a translation + * + * If the string has enough data and it doesn't have a string id, it will check and create the + * source too if not existing + * + * @param $string + * Object representing a string translation + * @return + * SAVED_NEW, SAVED_UPDATED, or FALSE on failure. + */ +function locale_save_translation(&$string) { + // If a current translation exists, just update the record, find it if not given + if (empty($string->lid)) { + if (!empty($string->textgroup)) { + $key = locale_textgroup($string->textgroup, 'key'); + if (!empty($string->$key)) { + if ($source = locale_get_source(array('textgroup' => $string->textgroup, $key => $string->$key))) { + $string->lid = $source->lid; + } + elseif (!empty($string->source)) { + // Create source if it doesn't exist and we have enough data + locale_save_source($string); + } + } + } + } + + // Check again for existing source and translation + if (!empty($string->lid)) { + // Invalidate cache if this text group is cacheable + if (!empty($string->textgroup) && locale_textgroup($string->textgroup, 'cache')) { + variable_set('locale_rebuild_' . $string->textgroup, 1); + } + $existing = locale_get_translation(array('lid' => $string->lid, 'language' => $string->language)); + if (!empty($existing)) { + return drupal_write_record('locales_target', $string, array('lid', 'language')); + } else { + return drupal_write_record('locales_target', $string); + } + } + else { + return FALSE; + } +} + +/** + * Delete a source string and all of its translations + * + * @param $lid + * String id + */ +function locale_delete_source($lid) { + +} + +/** + * Delete a translation + * + * @param $lid + * String id + */ +/** + * @} End of "locale-api" + */ + // --------------------------------------------------------------------------------- // Language switcher block @@ -601,6 +841,121 @@ } /** + * Translate custom strings with context information + * + * @param $textgroup + * The locale text group this string belongs to + * @param $key + * String id, unique inside the text group + * @param $string + * A string containing the string in the default language to translate + * @param $args + * An associative array of replacements to make after translation. + * @param $langcode + * Optional language code to translate to a language other than what is used + * to display the page. + * @return + * The translated string. + */ +function locale_tt($textgroup, $location, $string, $langcode) { + static $_tt; + + // If not loaded, loads the whole text group from cache or rebuilds it + if (!isset($_tt[$textgroup][$langcode])) { + $_tt[$textgroup][$langcode] = _locale_get_cache($textgroup, $langcode); + } + // If we have the translation cached, skip checking the database + if (!isset($_tt[$textgroup][$langcode][$location])) { + // We do not have this translation cached, so get it from the DB. + $translation = db_query("SELECT s.lid, s.source, t.translation, s.version FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location = :location AND s.textgroup = :textgroup", + array( + ':language' => $langcode, + ':textgroup' => $textgroup, + ':location' => $location, + ))->fetchObject(); + if ($translation) { + // We have the source string at least. + // Cache translation string or TRUE if no translation exists. + $_tt[$textgroup][$langcode][$location] = (empty($translation->translation) ? TRUE : $translation->translation); + + if ($translation->version != VERSION && $translation->source != $string) { + // This is the first use of this string under current Drupal version. Save version + // and clear cache, to include the string into caching next time. Saved version is + // also a string-history information for later pruning of the tables. + db_query("UPDATE {locales_source} SET version = '%s', source = '%s' WHERE lid = %d", VERSION, $string, $translation->lid); + variable_set('locale_rebuild_' . $textgroup, 1); + } + } + else { + // We don't have the source string, cache this as untranslated. + db_query("INSERT INTO {locales_source} (location, textgroup, source, version) VALUES ('%s', '%s', '%s', '%s')", $location, $textgroup, $string, VERSION); + $_tt[$textgroup][$langcode][$location] = TRUE; + // Clear locale cache so this string can be added in a later request. + variable_set('locale_rebuild_' . $textgroup, 1); + } + } + + return ($_tt[$textgroup][$langcode][$location] === TRUE ? $string : $_tt[$textgroup][$langcode][$location]); +} + +/** + * Helper function to load locale cache + * + * @param $textgroup + * Text group we want to get the cache for + * @param $langcode + * Target language to load + * @param $maxlength + * Max length of the strings to load in the cache + * @param $rebuild + * Whether to rebuild the whole cache for this text group + */ +function _locale_get_cache($textgroup, $index, $langcode, $rebuild = FALSE) { + $locale = array(); + + // See whether we need to rebuild and reset the variable if so + if (variable_get('locale_rebuild_' . $textgroup, 0)) { + $rebuild = TRUE; + variable_del('locale_rebuild_' . $textgroup); + } + + // Disabling the usage of string caching allows a module to watch for + // the exact list of strings used on a page. From a performance + // perspective that is a really bad idea, so we have no user + // interface for this. Be careful when turning this option off! + if (variable_get('locale_cache_strings', 1) == 1) { + $cache_key = 'locale:' . $textgroup . ':' . $langcode; + $cache_condition = cache_get($cache_key, 'cache'); + if (!$rebuild && $cache_condition) { + $locale = $cache->data; + } + else { + // Refresh database stored cache of translations for given language. + // We only store short strings used in current version, to improve + // performance and consume less memory. + $query = db_select('locales_source', 's'); + $query->leftJoin('locales_target', 't', 's.lid = t.lid AND t.language = :langcode', array(':langcode' => $langcode)); + $query->addField('s', $index); + $query->addField('t', 'translation'); + $query->condition('textgroup', $textgroup); + // If this text group is versioned, add such condition + if ($version = locale_textgroup($textgroup, 'version')) { + $query->condition('version', $version); + } + // Check limit for caching + if (is_string($cache_condition)) { + $query->where($cache_condition); + } + $result = $query->execute(); + foreach ($result as $data) { + $locale[$data->$index] = empty($data->translation) ? TRUE : $data->translation; + } + cache_set($cache_key, $locale); + } + } + return $locale; +} +/** * Theme locale translation filter selector. * * @ingroup themeable Index: modules/locale/locale.test =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v retrieving revision 1.16 diff -u -r1.16 locale.test --- modules/locale/locale.test 5 Feb 2009 00:32:47 -0000 1.16 +++ modules/locale/locale.test 9 Feb 2009 17:01:09 -0000 @@ -48,7 +48,7 @@ // Add string. t($name, array(), $langcode); // Reset locale cache. - locale(NULL, NULL, TRUE); + locale_string(NULL, NULL, TRUE); $this->assertText($langcode, 'Language code found'); $this->assertText($name, 'Name found'); $this->assertText($native, 'Native found'); @@ -381,3 +381,92 @@ $this->assertIdentical($anchors, array('active' => array('en'), 'inactive' => array('fr')), t('Only the current language anchor is marked as active on the language switcher block')); } } + +/** + * Tests for locale CRUD API functions. + */ +class LocaleApiTest extends DrupalWebTestCase { + function getInfo() { + return array( + 'name' => t('Locale API functions'), + 'description' => t('Tests the performance of locale CRUD APIs.'), + 'group' => t('Locale'), + ); + } + + function setUp() { + parent::setUp('locale'); + include_once DRUPAL_ROOT . '/includes/locale.inc'; + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer languages', 'translate interface', 'access administration pages')); + $this->drupalLogin($admin_user); + } + + /** + * Test locale source APIs. + */ + function testApis() { + // Insert an initial source string. + $source = new StdClass(); + $source->location = 'test'; + $source->textgroup = 'text'; + $source->source = 'test'; + $insert_result = locale_save_source($source); + $this->assertTrue($insert_result == SAVED_NEW, t('Correct value returned when new locale source string saved')); + + // Update the initial source string after changing a property. + $source->source = 'changed'; + $update_result = locale_save_source($source); + $this->assertTrue($update_result == SAVED_UPDATED, t('Correct value returned when new locale source string updated')); + + // Load the source string. + $id_loaded_source = locale_get_source($source->lid); + $this->assertTrue(isset($id_loaded_source->lid) && $id_loaded_source->lid == $source->lid, t('Source loaded by ID')); + $this->assertTrue(isset($id_loaded_source->source) && $id_loaded_source->source == 'changed', t('Source updated')); + + // Save a second source string. + $lids = array($source->lid); + unset($source->lid); + locale_save_source($source); + $lids[] = $source->lid; + // Load multiple strings. + $multiple = locale_load_source(array('lid' =>$lids)); + $this->assertTrue(count($multiple) == 2, t('Multiple locale source strings loaded by ID')); + + // Add language. + $edit = array( + 'langcode' => 'fr', + ); + $this->drupalPost('admin/settings/language/add', $edit, t('Add language')); + + // Create a locale target string. + $target = new StdClass(); + $target->lid = current($lids); + $target->translation = 'analyse'; + $target->language = 'fr'; + $insert_result = locale_save_translation($target); + $this->assertTrue($insert_result == SAVED_NEW, t('Correct value returned when new locale target string saved')); + + // Update the initial target string. + $target->translation = 'tester'; + $update_result = locale_save_translation($target); + $this->assertTrue($update_result == SAVED_UPDATED, t('Correct value returned when new locale target string updated')); + + // Load the target string. + $id_loaded_target = locale_get_translation(array('lid' => $target->lid, 'language' => $target->language)); + $this->assertTrue(isset($id_loaded_target->lid) && $id_loaded_target->lid == $target->lid, t('Target loaded by ID')); + $this->assertTrue(isset($id_loaded_target->translation) && $id_loaded_target->translation == 'tester', t('Target updated')); + + // Load a translation. + $translation = locale_get_translation(array('source' => $source->source, 'textgroup' => $source->textgroup, 'location' => $source->location, 'language' => $target->language)); + $this->assertTrue($translation == 'tester', t('Translation found with locale_get_translation()')); + $translation = locale_get_translation($source->source, $source->textgroup, $source->location, 'es'); + $this->assertTrue($translation == $source->source, t('Original string returned when missing translation requested with locale_get_translation()')); + + // Delete source and target strings. + locale_delete_strings(array('lid' =>$lids)); + $sources = locale_load_source(array('lid' =>$lids)); + $this->assertTrue(empty($sources), t('Locale source strings deleted')); + } + +} Index: includes/locale.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/locale.inc,v retrieving revision 1.202 diff -u -r1.202 locale.inc --- includes/locale.inc 5 Feb 2009 00:32:46 -0000 1.202 +++ includes/locale.inc 9 Feb 2009 17:01:07 -0000 @@ -571,7 +571,7 @@ 'options' => array('all' => t('Both translated and untranslated strings'), 'translated' => t('Only translated strings'), 'untranslated' => t('Only untranslated strings')), ); - $groups = module_invoke_all('locale', 'groups'); + $groups = locale_textgroup(NULL, 'name'); $filters['group'] = array( 'title' => t('Limit search to'), 'options' => array_merge(array('all' => t('All text groups')), $groups), @@ -855,7 +855,7 @@ */ function locale_translate_edit_form(&$form_state, $lid) { // Fetch source string, if possible. - $source = db_fetch_object(db_query('SELECT source, textgroup, location FROM {locales_source} WHERE lid = %d', $lid)); + $source = locale_get_source($lid); if (!$source) { drupal_set_message(t('String not found.'), 'error'); drupal_goto('admin/build/translate/translate'); @@ -951,19 +951,17 @@ function locale_translate_edit_form_submit($form, &$form_state) { $lid = $form_state['values']['lid']; foreach ($form_state['values']['translations'] as $key => $value) { - $translation = db_result(db_query("SELECT translation FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key)); + // Only update or insert if we have a value to use. if (!empty($value)) { - // Only update or insert if we have a value to use. - if (!empty($translation)) { - db_query("UPDATE {locales_target} SET translation = '%s' WHERE lid = %d AND language = '%s'", $value, $lid, $key); - } - else { - db_query("INSERT INTO {locales_target} (lid, translation, language) VALUES (%d, '%s', '%s')", $lid, $value, $key); - } + $translation = new StdClass(); + $translation->lid = $lid; + $translation->translation = $value; + $translation->language = $key; + locale_save_translation($translation); } - elseif (!empty($translation)) { + else { // Empty translation entered: remove existing entry from database. - db_query("DELETE FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key); + locale_delete_translations(array('lid' =>$lid, 'language' => $key)); } // Force JavaScript translation file recreation for this language. @@ -2127,7 +2125,7 @@ $result = pager_query($sql, 50, 0, NULL, $arguments); - $groups = module_invoke_all('locale', 'groups'); + $groups = locale_textgroup(NULL, 'name'); $header = array(t('Text group'), t('String'), ($limit_language) ? t('Language') : t('Languages'), array('data' => t('Operations'), 'colspan' => '2')); $arr = array(); while ($locale = db_fetch_object($result)) { @@ -2735,3 +2733,101 @@ /** * @} End of "locale-autoimport" */ + +/** + * @defgroup locale-api Locale API functions for source and target strings + * @{ + */ + +/** + * Load multiple source strings as an array + * + * To get a single source use locale_get_source() + * + * @param $conditions + * An array field-value pairs or a single lid to match. Values may be arrays, in which case + * matching will use the 'IN' operator. + */ +function locale_load_source($conditions = array()) { + $strings = array(); + $query = db_select('locales_source', 's'); + $query->fields('s', array('lid', 'textgroup', 'source', 'location', 'version')); + _locale_query_conditions($query, $conditions); + $query->execute(); + return $query->fetchAll(); +} + +/** + * Delete one or more locale source records from the database. It will also delete + * associated target records. + * + * @param $conditions + * An array field-value pairs or a single lid to match. Values may be arrays, in which case + * matching will use the 'IN' operator. + */ +function locale_delete_strings($conditions = array()) { + // Accept one or an array of ID values. + if (is_numeric($conditions)) { + $conditions = array('lid' => $conditions); + } + // Remove all associated targets before so we can use IN (SELECT ...) conditions + locale_delete_translations($conditions); + + // Build the query and execute + $query = db_delete('locales_source'); + _locale_query_conditions($query, $conditions); + + return $query->execute(); +} + +/** + * Delete one or more locale target records from the database. + * + * @param $conditions + * An array field-value pairs to match. Values may be arrays, in which case + * matching will use the 'IN' operator. + * @return + * An array of all matching records, or false on failure. + */ +function locale_delete_translations($conditions) { + // Accept one or an array of ID values. + if (is_numeric($conditions)) { + $conditions = array('lid' => $conditions); + } + // Build the query + $query = db_delete('locales_target'); + + // Check conditions they may be source or target conditions + $source_conditions = _locale_query_conditions($query, $conditions, array('lid', 'language', 'translation', 'plural')); + + // If we have source conditions, build source subquery and add it to the main query + if (!empty($source_conditions)) { + $subquery = db_select('locales_source', 's'); + $subquery->addField('s', 'lid'); + _locale_query_conditions($subquery, $source_conditions); + $query->where('lid IN (' . $subquery . ')'); + } + + return $query->execute(); +} + +/** + * Add conditions to a query (single table) optionally checking fields + * + */ +function _locale_query_conditions(&$query, $conditions, $allowed = array()) { + $excluded = array(); + foreach ($conditions as $field => $value) { + if (!$allowed || in_array($field, $allowed)) { + is_array($value) ? $query->condition($field, $value, 'IN') : $query->condition($field, $value); + } + else { + $excluded[$field] = $value; + } + } + return $excluded; +} + +/** + * @} End of "locale-api" + */ Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.865 diff -u -r1.865 common.inc --- includes/common.inc 9 Feb 2009 03:29:53 -0000 1.865 +++ includes/common.inc 9 Feb 2009 17:01:05 -0000 @@ -1085,17 +1085,14 @@ * helper function. * @see st() * @see get_t() - * + * + * After string translation the arguments if any will be replaced + * @see drupal_replace_string() + * * @param $string * A string containing the English string to translate. * @param $args - * An associative array of replacements to make after translation. Incidences - * of any key in this array are replaced with the corresponding value. Based - * on the first character of the key, the value is escaped and/or themed: - * - !variable: inserted as is - * - @variable: escape plain text to HTML (check_plain) - * - %variable: escape text and theme as a placeholder for user-submitted - * content (check_plain + theme_placeholder) + * An associative array of replacements to make after translation. * @param $langcode * Optional language code to translate to a language other than what is used * to display the page. @@ -1123,32 +1120,51 @@ } // Translate with locale module if enabled. elseif (function_exists('locale') && $langcode != 'en') { - $string = locale($string, $langcode); - } - if (empty($args)) { - return $string; + $string = locale_string($string, $langcode); } - else { - // Transform arguments before inserting them. - foreach ($args as $key => $value) { - switch ($key[0]) { - case '@': - // Escaped only. - $args[$key] = check_plain($value); - break; - - case '%': - default: - // Escaped and placeholder. - $args[$key] = theme('placeholder', $value); - break; + + return drupal_replace_string($string, $args); +} - case '!': - // Pass-through. - } - } - return strtr($string, $args); +/** + * This will translate strings using textgroup and location context + * + * When translating user defined strings we cannot rely on the source string to do the translation + * as it may change, maybe just to fix a typo and it would invalidate the translations. + * @see t() + * + * The default language for these strings will be the site default language instead of being + * always English like for t() + * + * After string translation the arguments if any will be replaced + * @see drupal_replace_string() + * + * @param $textgroup + * The locale text group this string belongs to + * @param $key + * String id, unique inside the text group + * @param $string + * A string containing the string in the default language to translate + * @param $args + * An associative array of replacements to make after translation. + * @param $langcode + * Optional language code to translate to a language other than what is used + * to display the page. + * @return + * The translated string. + */ +function tt($textgroup, $key, $string, $args = array(), $langcode = NULL) { + global $language; + + if (!isset($langcode)) { + $langcode = !empty($language->language) ? $language->language : 'en'; } + + if (function_exists('locale_tt') && $langcode != language_default('language')) { + $string = locale_tt($textgroup, $key, $string, $langcode); + } + + return drupal_replace_string($string, $args); } /** @@ -3967,7 +3983,9 @@ $object->$serial = $last_insert_id; } } - else { + // If we have a single-field primary key but got no insert ID, the + // query failed. + elseif (count($primary_keys) == 1) { $return = FALSE; } @@ -4218,3 +4236,44 @@ } variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19)); } + +/** + * Replace string with arguments + * + * @param $string + * A plain string containing to be replaced with args. + * @param $args + * An associative array of replacements to make after translation. Incidences + * of any key in this array are replaced with the corresponding value. Based + * on the first character of the key, the value is escaped and/or themed: + * - !variable: inserted as is + * - @variable: escape plain text to HTML (check_plain) + * - %variable: escape text and theme as a placeholder for user-submitted + * content (check_plain + theme_placeholder) + */ +function drupal_replace_string($string, $args) { + if (empty($args)) { + return $string; + } + else { + // Transform arguments before inserting them. + foreach ($args as $key => $value) { + switch ($key[0]) { + case '@': + // Escaped only. + $args[$key] = check_plain($value); + break; + + case '%': + default: + // Escaped and placeholder. + $args[$key] = theme('placeholder', $value); + break; + + case '!': + // Pass-through. + } + } + return strtr($string, $args); + } +} \ No newline at end of file