2
// $Id: i18nstrings.module,v 1.8.2.31 2009/01/19 19:05:12 jareyero Exp $
6
* Internationalization (i18n) package - translatable strings.
8
* Object oriented string translation using locale and textgroups.
11
* - Textgroup. Group the string belongs to, defined by locale hook.
12
* - Location. Unique id of the string for this textgroup.
13
* - Name. Unique absolute id of the string: textgroup + location.
14
* - Context. Object with textgroup, type, objectid, property.
15
* - Default language may be English or not. It will be the language set as default.
16
* Source strings will be stored in default language.
18
* @ TO DO: Handle default language changes.
20
* @author Jose A. Reyero, 2007
24
* Translated string is current.
26
define('I18NSTRINGS_STATUS_CURRENT', 0);
29
* Translated string needs updating as the source has been edited.
31
define('I18NSTRINGS_STATUS_UPDATE', 1);
34
* Implementation of hook_help().
36
function i18nstrings_help($path, $arg) {
38
case 'admin/help#i18nstrings':
39
$output = '<p>' . t('This module adds support for other modules to translate user defined strings. Depending on which modules you have enabled that use this feature you may see different text groups to translate.') .'<p>';
40
$output .= '<p>' . t('This works differently to Drupal standard localization system: The strings will be translated from the default language (which may not be English), so changing the default language may cause all these translations to be broken.') . '</p>';
42
$output .= '<li>'. t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/build/translate'))) .'</li>';
43
$output .= '<li>'. t('If you are missing strings to translate, use the <a href="@refresh-strings">refresh strings</a> page.', array('@refresh-strings' => url('admin/build/translate/refresh'))) .'</li>';
45
$output .= '<p>'. t('Read more on the <em>Internationalization handbook</em>: <a href="http://drupal.org/node/313293">Translating user defined strings</a>.') .'</p>';
48
case 'admin/build/translate/refresh':
49
$output = '<p>'. t('On this page you can refresh and update values for user defined strings.') .'</p>';
51
$output .= '<li>'. t('Use the refresh option when you are missing strings to translate for a given text group. All the strings will be re-created keeping existing translations.') .'</li>';
52
$output .= '<li>'. t('Use the update option when some of the strings had been previously translated with the localization system, but the translations are not showing up for the configurable strings.') .'</li>';
54
$output .= '<p>'. t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/build/translate'))) .'</p>';
57
case 'admin/settings/language':
58
$output = '<p>'. t('<strong>Warning</strong>: Changing the default language may have unwanted effects on string translations. Read more about <a href="@i18nstrings-help">String translation</a>', array('@i18nstrings-help' => url('admin/help/i18nstrings'))) .'</p>';
64
* Implementation of hook_menu().
66
function i18nstrings_menu() {
67
$items['admin/build/translate/refresh'] = array(
70
'type' => MENU_LOCAL_TASK,
71
'page callback' => 'i18nstrings_admin_refresh_page',
72
'file' => 'i18nstrings.admin.inc',
73
'access arguments' => array('translate interface'),
75
// AJAX callback path for strings.
76
$items['i18nstrings/save'] = array(
77
'title' => 'Save string',
78
'page callback' => 'i18nstrings_save_string',
79
'access arguments' => array('use on-page translation'),
80
'type' => MENU_CALLBACK,
86
* Implementation of hook_form_alter();
88
* Add English language in some string forms when it is not the default.
90
function i18nstrings_form_alter(&$form, $form_state, $form_id) {
93
case 'locale_translate_export_po_form':
94
case 'locale_translate_import_form':
95
$names = locale_language_list('name', TRUE);
96
if (language_default('language') != 'en' && array_key_exists('en', $names)) {
97
if (isset($form['export'])) {
98
$form['export']['langcode']['#options']['en'] = $names['en'];
101
$form['import']['langcode']['#options'][t('Already added languages')]['en'] = $names['en'];
106
case 'locale_translate_edit_form':
107
$form['#submit'][] = 'i18nstrings_translate_edit_form_submit';
110
case 'l10n_client_form':
111
$form['#action'] = url('i18nstrings/save');
117
* Process string editing form submissions.
119
* Mark translations as current.
121
function i18nstrings_translate_edit_form_submit($form, &$form_state) {
122
$lid = $form_state['values']['lid'];
123
foreach ($form_state['values']['translations'] as $key => $value) {
124
if (!empty($value)) {
125
// An update has been made, so we assume the translation is now current.
126
db_query("UPDATE {locales_target} SET status = %d WHERE lid = %d AND language = '%s'", I18NSTRINGS_STATUS_CURRENT, $lid, $key);
132
* Translate configurable string.
135
* Textgroup and location glued with ':'.
137
* String in default language. Default language may or may not be English.
139
* Optional language code if different from current request language.
141
* Whether to force update/create for the string.
143
function tt($name, $string, $langcode = NULL, $update = FALSE) {
146
$langcode = $langcode ? $langcode : $language->language;
149
i18nstrings_update_string($name, $string);
151
// If language is default, just return
152
if (language_default('language') == $langcode) {
156
return i18nstrings_tt($name, $string, $langcode, FALSE);
161
* Get configurable string,
163
* The difference with tt() is that it doesn't use a default string, it will be retrieved too.
165
* This is used for source texts that we don't have stored anywhere else. I.e. for the content
166
* types help text (i18ncontent module) there's no way we can override the default (configurable) help text
167
* so what we do is to make it blank in the configuration (so node module doesn't display it)
168
* and then we provide that help text for *all* languages, out from the locales tables.
170
* As the original language string will be stored in locales too so it should be only used when updating.
172
function i18nstrings_ts($name, $string = '', $langcode = NULL, $update = FALSE) {
175
$langcode = $langcode ? $langcode : $language->language;
179
i18nstrings_update_string($name, $string);
181
// if language is default look in sources table
182
if (language_default('language') != $langcode) {
183
$translation = i18nstrings_get_string($name, $langcode);
186
if ($source = i18nstrings_get_source($name)) {
187
$translation = $source->source;
197
* Debug util. Marks the translated strings.
199
function ttd($name, $string, $langcode = NULL, $update = FALSE) {
200
$context = i18nstrings_context($name, $string);
201
$context = implode('/', (array)$context);
202
return tt($name, $string, $langcode, $update) .'[T:'. $string .'('. $context .')]';
206
* Get translation for user defined string.
208
* @todo Add support for latest l10n client.
211
* Textgroup and location glued with ':'
213
* String in default language
215
* Language code to get translation for
217
* Force update (refresh or create), to be used when updating source strings
219
function i18nstrings_tt($name, $string, $langcode, $update = FALSE) {
222
$context = i18nstrings_context($name, $string);
225
i18nstrings_update_string($context, $string);
228
// Search for existing translation (result will be cached in this function call)
229
$translation = i18nstrings_get_string($context, $langcode);
231
if ($translation === FALSE) {
232
// If the source string is missing, create it anyway.
233
// If $update it should already been created so skip this step.
235
$source = i18nstrings_get_source($context, $string);
236
if (!$source || empty($source->context)) {
237
i18nstrings_add_string($name, $string);
243
// If current language add to l10n client list for later on page translation.
244
// If language were the default one we are not supossed to reach here.
245
if ($language->language == $langcode && function_exists('l10_client_add_string_to_page')) {
246
l10_client_add_string_to_page($string, $translation, $source->textgroup);
248
return ($translation === TRUE) ? $string : $translation;
254
function to($context, &$object, $properties = array(), $langcode = NULL, $update = FALSE) {
257
$langcode = $langcode ? $langcode : $language->language;
259
// If language is default, just return.
260
if (language_default('language') == $langcode && !$update) {
264
i18nstrings_to($context, $object, $properties, $langcode, $update);
269
* Translate object properties.
271
function i18nstrings_to($context, &$object, $properties = array(), $langcode = NULL, $update = FALSE, $create = TRUE) {
272
$context = i18nstrings_context($context);
273
// @ TODO Object prefetch
274
foreach ($properties as $property) {
275
$context->property = $property;
276
$context->location = i18nstrings_location($context);
277
if (!empty($object->$property)) {
278
$object->$property = i18nstrings_tt($context, $object->$property, $langcode, $update, $create);
284
* Update / create / remove string
289
* New value of string for update/create. May be empty for removing.
291
function i18nstrings_update_string($context, $string) {
292
$context = i18nstrings_context($context, $string);
295
$status = i18nstrings_add_string($context, $string);
298
$status = i18nstrings_remove_string($context);
301
'%location' => i18nstrings_location($context),
302
'%textgroup' => $context->textgroup,
303
'%string' => $string,
307
drupal_set_message(t('Updated string %location for textgroup %textgroup: %string', $params));
311
drupal_set_message(t('Created string %location for text group %textgroup: %string', $params));
318
* Update string translation.
320
function i18nstrings_update_translation($context, $langcode, $translation) {
321
if ($source = i18nstrings_get_source($context, $translation)) {
322
db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES(%d, '%s', '%s')", $source->lid, $langcode, $translation);
327
* Add source string to the locale tables for translation.
329
* It will also add data into i18n_strings table for faster retrieval and indexing of groups of strings.
330
* Some string context doesn't have a numeric oid (I.e. content types), it will be set to zero.
332
* This function checks for already existing string without context for this textgroup and updates it accordingly.
333
* It is intended for backwards compatibility, using already created strings.
336
* Textgroup and location glued with ':'
338
* Source string (string in default language)
343
function i18nstrings_add_string($name, $string) {
344
$context = i18nstrings_context($name, $string);
345
$location = i18nstrings_location($context);
347
// Check if we have a source string.
348
$source = i18nstrings_get_source($context, $string);
353
if ($source->source != $string) {
354
// String has changed
355
db_query("UPDATE {locales_source} SET source = '%s', location = '%s' WHERE lid = %d", $string, $location, $source->lid);
356
db_query("UPDATE {locales_target} SET status = %d WHERE lid = %d", I18NSTRINGS_STATUS_UPDATE, $source->lid);
357
$status = SAVED_UPDATED;
359
elseif ($source->location != $location) {
360
// It's not changed but it didn't have location set
361
db_query("UPDATE {locales_source} SET location = '%s' WHERE lid = %d", $location, $source->lid);
362
$status = SAVED_UPDATED;
365
db_query("UPDATE {i18n_strings} SET type = '%s', objectid = %d, property = '%s' WHERE lid = %d", $context->type, (int)$context->objectid, $context->property, $source->lid);
366
if (!db_affected_rows()) {
367
// The @db_query will prevent errors being thrown in case the rows already exist.
368
// The problem with db_affected_rows() is that MySQL returns 0 if the update didn't change the value
369
@db_query("INSERT INTO {i18n_strings} (lid, type, objectid, property) VALUES(%d, '%s', %d, '%s')", $source->lid, $context->type, (int)$context->objectid, $context->property);
373
db_query("INSERT INTO {locales_source} (location, source, textgroup, version) VALUES ('%s', '%s', '%s', '%s')", $location, $string, $context->textgroup, 1);
374
// Mysql just gets last id for latest query ?
375
$source->lid = db_last_insert_id('locales_source', 'lid');
376
// Update metadata, there seems to be a race condition sometimes so skip errors, #277711
377
@db_query("INSERT INTO {i18n_strings} (lid, type, objectid, property) VALUES(%d, '%s', %d, '%s')", $source->lid, $context->type, (int)$context->objectid, $context->property);
378
// Clear locale cache so this string can be added in a later request.
379
cache_clear_all('locale:'. $context->textgroup .':', 'cache', TRUE);
388
* Get source string provided a string context.
390
* This will search first with the full context parameters and, if not found,
391
* it will search again only with textgroup and source string.
394
* Context string or object.
396
* Context object if it exists.
398
function i18nstrings_get_source($context, $string = NULL) {
399
$context = i18nstrings_context($context, $string);
401
// Check if we have the string for this location.
402
list($where, $args) = i18nstrings_context_query($context);
403
if ($source = db_fetch_object(db_query("SELECT s.*, i.type, i.objectid, i.property FROM {locales_source} s LEFT JOIN {i18n_strings} i ON s.lid = i.lid WHERE ". implode(' AND ', $where), $args))) {
404
$source->context = $context;
407
// Search for the same string for this textgroup without object data.
408
if ($string && $source = db_fetch_object(db_query("SELECT s.*, i.type, i.objectid, i.property FROM {locales_source} s LEFT JOIN {i18n_strings} i ON s.lid = i.lid WHERE s.textgroup = '%s' AND s.source = '%s' AND i.lid IS NULL", $context->textgroup, $string))) {
409
$source->context = NULL;
415
* Get string for a language.
418
* Context string or object.
420
* Language code to retrieve string for.
423
* - Translation if found.
424
* - TRUE if not found and cached.
425
* - FALSE if not even cached.
428
function i18nstrings_get_string($context, $langcode) {
429
$context = i18nstrings_context($context);
431
if ($translation = i18nstrings_cache($context, $langcode)) {
435
// Search translation and add it to the cache.
436
list($where, $args) = i18nstrings_context_query($context);
437
$where[] = "t.language = '%s'";
439
$text = db_fetch_object(db_query("SELECT s.*, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE ". implode(' AND ', $where), $args));
441
if ($text && $text->translation) {
442
i18nstrings_cache($context, $langcode, NULL, $text->translation);
443
return $text->translation;
446
i18nstrings_cache($context, $langcode, NULL, TRUE);
447
return $text ? NULL : FALSE ;
453
* Remove string for a given context.
455
function i18nstrings_remove_string($context, $string = NULL) {
456
$context = i18nstrings_context($context, $string);
457
if ($source = i18nstrings_get_source($context, $string)) {
458
db_query("DELETE FROM {locales_target} WHERE lid = %d", $source->lid);
459
db_query("DELETE FROM {i18n_strings} WHERE lid = %d", $source->lid);
460
db_query("DELETE FROM {locales_source} WHERE lid = %d", $source->lid);
461
cache_clear_all('locale:'. $context->textgroup .':', 'cache', TRUE);
462
return SAVED_DELETED;
467
* Update context for strings.
469
* As some string locations depend on configurable values, the field needs sometimes to be updated
470
* without losing existing translations. I.e:
471
* - profile fields indexed by field name.
472
* - content types indexted by low level content type name.
475
* 'profile:field:oldfield:*' -> 'profile:field:newfield:*'
477
function i18nstrings_update_context($oldname, $newname) {
478
// Get context replacing '*' with empty string.
479
$oldcontext = i18nstrings_context(str_replace('*', '', $oldname));
480
$newcontext = i18nstrings_context(str_replace('*', '', $newname));
482
// Get location with placeholders.
483
$location = i18nstrings_location(str_replace('*', '%', $oldname));
484
foreach (array('textgroup', 'type', 'objectid', 'property') as $field) {
485
if ((!empty($oldcontext->$field) || !empty($newcontext->$field)) && $oldcontext->$field != $newcontext->$field) {
486
$replace[$field] = $newcontext->$field;
489
// Query and replace.
490
$result = db_query("SELECT s.*, i.type, i.objectid, i.property FROM {locales_source} s LEFT JOIN {i18n_strings} i ON s.lid = i.lid WHERE s.textgroup = '%s' AND s.location LIKE '%s'", $oldcontext->textgroup, $location);
491
while ($source = db_fetch_object($result)) {
492
// Make sure we have string and context.
493
$context = i18nstrings_context($oldcontext->textgroup .':'. $source->location);
494
foreach ($replace as $field => $value) {
495
$context->$field = $value;
497
// Update source string.
498
db_query("UPDATE {locales_source} SET textgroup = '%s', location = '%s' WHERE lid = %d", $context->textgroup, i18nstrings_location($context), $source->lid);
499
// Update object data.
500
db_query("UPDATE {i18n_strings} SET type = '%s', objectid = '%s', property = '%s' WHERE lid = %d", $context->type, $context->objectid, $context->property, $source->lid);
502
drupal_set_message(t('Updating string names from %oldname to %newname.', array('%oldname' => $oldname, '%newname' => $newname)));
506
* Provides interface translation services.
508
* This function is called from tt() to translate a string if needed.
513
* A string to look up translation for. If omitted, all the
514
* cached strings will be returned in all languages already
517
* Language code to use for the lookup.
519
function i18nstrings_textgroup($textgroup, $string = NULL, $langcode = NULL) {
523
// Return all cached strings if no string was specified.
524
if (!isset($string)) {
525
return isset($locale_t[$textgroup]) ? $locale_t[$textgroup] : array();
528
$langcode = isset($langcode) ? $langcode : $language->language;
530
// Store database cached translations in a static variable.
531
if (!isset($locale_t[$langcode])) {
532
$locale_t[$langcode] = array();
533
// Disabling the usage of string caching allows a module to watch for
534
// the exact list of strings used on a page. From a performance
535
// perspective that is a really bad idea, so we have no user
536
// interface for this. Be careful when turning this option off!
537
if (variable_get('locale_cache_strings', 1) == 1) {
538
if ($cache = cache_get('locale:'. $textgroup .':'. $langcode, 'cache')) {
539
$locale_t[$textgroup][$langcode] = $cache->data;
542
// Refresh database stored cache of translations for given language.
543
// We only store short strings used in current version, to improve
544
// performance and consume less memory.
545
$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 = '%s' AND s.version = '%s' AND LENGTH(s.source) < 75", $langcode, $textgroup, VERSION);
546
while ($data = db_fetch_object($result)) {
547
$locale_t[$textgroup][$langcode][$data->source] = (empty($data->translation) ? TRUE : $data->translation);
549
cache_set('locale:'. $textgroup .':'. $langcode, $locale_t[$textgroup][$langcode]);
554
// If we have the translation cached, skip checking the database
555
if (!isset($locale_t[$textgroup][$langcode][$string])) {
557
// We do not have this translation cached, so get it from the DB.
558
$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 = '%s'", $langcode, $string, $textgroup));
560
// We have the source string at least.
561
// Cache translation string or TRUE if no translation exists.
562
$locale_t[$textgroup][$langcode][$string] = (empty($translation->translation) ? TRUE : $translation->translation);
564
if ($translation->version != VERSION) {
565
// This is the first use of this string under current Drupal version. Save version
566
// and clear cache, to include the string into caching next time. Saved version is
567
// also a string-history information for later pruning of the tables.
568
db_query_range("UPDATE {locales_source} SET version = '%s' WHERE lid = %d", VERSION, $translation->lid, 0, 1);
569
cache_clear_all('locale:'. $textgroup .':', 'cache', TRUE);
573
// We don't have the source string, cache this as untranslated.
574
db_query("INSERT INTO {locales_source} (location, source, textgroup, version) VALUES ('%s', '%s', '%s', '%s')", request_uri(), $string, $textgroup, VERSION);
575
$locale_t[$langcode][$string] = TRUE;
576
// Clear locale cache so this string can be added in a later request.
577
cache_clear_all('locale:'. $textgroup .':', 'cache', TRUE);
581
return ($locale_t[$textgroup][$langcode][$string] === TRUE ? $string : $locale_t[$textgroup][$langcode][$string]);
585
* Convert context string in a context object.
588
* 'taxonomy:term:1:name'
590
* will become a $context object where
591
* $context->textgroup = 'taxonomy';
592
* $context->type = 'term';
593
* $context->objectid = 1;
594
* $context->property = 'name';
597
* 'taxonomy:title' -> (taxonomy, title, 0, 0)
598
* 'nodetype:type:[type]:name'
599
* 'nodetype:type:[type]:description'
601
* 'profile:field:[fid]:title'
604
* Context string or object.
606
* For some textgroups and objects that don't have ids we use the string itself as index.
608
* Context object with textgroup, type, objectid, property and location names.
610
function i18nstrings_context($context, $string = NULL) {
611
// Context may be already an object.
612
if (is_object($context)) {
616
// We add empty fields at the end before splitting.
617
list($textgroup, $type, $objectid, $property) = split(':', $context .':::');
618
$context = (object)array(
619
'textgroup' => $textgroup,
621
'objectid' => $objectid ? $objectid : 0,
622
'property' => $property ? $property : 0,
624
$context->location = i18nstrings_location($context);
625
if (!$context->objectid && !$context->property && $string) {
626
$context->source = $string;
633
* Get query conditions for this context.
635
function i18nstrings_context_query($context, $alias = 's') {
636
$where = array("$alias.textgroup = '%s'", "$alias.location = '%s'");
637
$args = array($context->textgroup, $context->location);
638
if (!empty($context->source)) {
639
$where[] = "s.source = '%s'";
640
$args[] = $context->source;
642
return array($where, $args);
646
* Get location string from context.
648
* Returns the location for the locale table for a string context.
650
function i18nstrings_location($context) {
651
if (is_string($context)) {
652
$context = i18nstrings_context($context);
654
$location[] = $context->type;
655
if ($context->objectid) {
656
$location[] = $context->objectid;
657
if ($context->property) {
658
$location[] = $context->property;
661
return implode(':', $location);
665
* Prefetch a number of object strings.
667
function i18nstrings_prefetch($context, $langcode = NULL, $join = array(), $conditions = array()) {
670
$langcode = $langcode ? $langcode : $language->language;
671
// Add language condition.
672
$conditions['t.language'] = $langcode;
673
// Get context conditions.
674
$context = (array)i18nstrings_context($context);
675
foreach ($context as $key => $value) {
677
if ($key == 'textgroup') {
678
$conditions['s.textgroup'] = $value;
681
$conditions['i.'. $key] = $value;
685
// Prepare where clause
686
$where = $params = array();
687
foreach ($conditions as $key => $value) {
688
if (is_array($value)) {
689
$where[] = $key .' IN ('. db_placeholders($value, is_int($value[0]) ? 'int' : 'string') .')';
690
$params = array_merge($params, $value);
693
$where[] = $key .' = '. is_int($value) ? '%d' : "'%s'";
697
$sql = "SELECT s.textgroup, s.source, i.type, i.objectid, i.property, t.translation FROM {locales_source} s";
698
$sql .=" INNER JOIN {i18n_strings} i ON s.lid = i.lid INNER JOIN {locales_target} t ON s.lid = t.lid ";
699
$sql .= implode(' ', $join) .' '. implode(' AND ', $where);
700
$result = db_query($sql, $params);
702
// Fetch all rows and store in cache.
703
while ($t = db_fetch_object($result)) {
704
i18nstrings_cache($t, $langcode, $t->source, $t->translation);
710
* Retrieves and stores translations in page (static variable) cache.
712
function i18nstrings_cache($context, $langcode, $string = NULL, $translation = NULL) {
715
$context = i18nstrings_context($context, $string);
717
if (!$context->objectid && $context->source) {
718
// This is a type indexed by string.
719
$context->objectid = $context->source;
721
// At this point context must have at least textgroup and type.
723
if ($context->property) {
724
$strings[$langcode][$context->textgroup][$context->type][$context->objectid][$context->property] = $translation;
726
elseif ($context->objectid) {
727
$strings[$langcode][$context->textgroup][$context->type][$context->objectid] = $translation;
730
$strings[$langcode][$context->textgroup][$context->type] = $translation;
734
// Search up the tree for the object or a default.
735
$search = &$strings[$langcode];
737
$list = array('textgroup', 'type', 'objectid', 'property');
738
while (($field = array_shift($list)) && !empty($context->$field)) {
739
if (isset($search[$context->$field])) {
740
$search = &$search[$context->$field];
741
if (isset($search['#default'])) {
742
$default = $search['#default'];
746
// We dont have cached this tree so we return the default.
750
// Returns the part of the array we got to.
757
* Callback for menu title translation.
759
function i18nstrings_title_callback($name, $string, $callback = NULL) {
760
$string = tt($name, $string);
762
$string = $callback($string);
768
* Refresh all user defined strings for a given text group
771
* Text group to refresh
773
* Optional, delete existing (but not refresed, strings and translations)
775
function i18nstrings_refresh_group($group, $delete = FALSE) {
776
// Delete data from i18n_strings so it is recreated
777
db_query("DELETE FROM {i18n_strings} WHERE lid IN (SELECT lid FROM {locales_source} WHERE textgroup = '%s')", $group);
779
module_invoke_all('locale', 'refresh', $group);
781
// Now delete all source strings that were not refreshed
783
$result = db_query("SELECT s.* FROM {locales_source} s LEFT JOIN {i18n_strings} i ON s.lid = i.lid WHERE s.textgroup = '%s' AND i.lid IS NULL", $group);
784
while ($source = db_fetch_object($result)) {
785
db_query("DELETE FROM {locales_target} WHERE lid = %d", $source->lid);
786
db_query("DELETE FROM {locales_source} WHERE lid = %d", $source->lid);
790
cache_clear_all('locale:'. $group .':', 'cache', TRUE);
793
/*** l10n client related functions ***/
796
* Menu callback. Saves a string translation coming as POST data.
798
function i18nstrings_save_string() {
799
global $user, $language;
801
if (user_access('use on-page translation')) {
802
$textgroup = !empty($_POST['textgroup']) ? $_POST['textgroup'] : 'default';
803
// Default textgroup will be handled by l10n_client module
804
if ($textgroup == 'default') {
805
l10n_client_save_string();
807
elseif (isset($_POST['source']) && isset($_POST['target']) && !empty($_POST['form_token']) && drupal_valid_token($_POST['form_token'], 'l10n_client_form')) {
808
i18nstrings_save_translation($language->language, $_POST['source'], $_POST['target'], $textgroup);
814
* Import translation for a given textgroup
816
* This will update multiple strings if there are duplicated ones
819
* Language code to import string into.
822
* @param $translation
823
* Translation to language specified in $langcode.
825
* Optional plural ID to use.
827
* Optional plural value to use.
829
* The number of strings updated
831
function i18nstrings_save_translation($langcode, $source, $translation, $textgroup) {
832
include_once 'includes/locale.inc';
834
if (locale_string_is_safe($translation)) {
835
$result = db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $source, $textgroup);
837
while ($source = db_fetch_object($result)) {
838
$exists = (bool) db_result(db_query("SELECT lid FROM {locales_target} WHERE lid = %d AND language = '%s'", $source->lid, $langcode));
840
// No translation in this language.
841
db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '%s')", $source->lid, $langcode, $translation);
844
// Translation exists, overwrite
845
db_query("UPDATE {locales_target} SET translation = '%s' WHERE language = '%s' AND lid = %d", $translation, $langcode, $source->lid);
b'\\ No newline at end of file'