~spreadubuntu/spreadubuntu/devel-drupal6

« back to all changes in this revision

Viewing changes to sites/all/modules/i18n/i18nstrings/i18nstrings.module

  • Committer: ruben
  • Date: 2009-06-08 09:38:49 UTC
  • Revision ID: ruben@captive-20090608093849-s1qtsyctv2vwp1x1
SpreadUbuntu moving to Drupal6. Based on ubuntu-drupal theme and adding our modules

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
// $Id: i18nstrings.module,v 1.8.2.31 2009/01/19 19:05:12 jareyero Exp $
 
3
 
 
4
/**
 
5
 * @file
 
6
 * Internationalization (i18n) package - translatable strings.
 
7
 *
 
8
 * Object oriented string translation using locale and textgroups.
 
9
 *
 
10
 * Some concepts
 
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.
 
17
 *
 
18
 * @ TO DO: Handle default language changes.
 
19
 *
 
20
 * @author Jose A. Reyero, 2007
 
21
 */
 
22
 
 
23
/**
 
24
 * Translated string is current.
 
25
 */
 
26
define('I18NSTRINGS_STATUS_CURRENT', 0);
 
27
 
 
28
/**
 
29
 * Translated string needs updating as the source has been edited.
 
30
 */
 
31
define('I18NSTRINGS_STATUS_UPDATE', 1);
 
32
 
 
33
/**
 
34
 * Implementation of hook_help().
 
35
 */
 
36
function i18nstrings_help($path, $arg) {
 
37
  switch ($path) {
 
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>';
 
41
      $output .= '<ul>';
 
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>';
 
44
      $output .= '</ul>';
 
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>';
 
46
      return $output;
 
47
 
 
48
    case 'admin/build/translate/refresh':
 
49
      $output = '<p>'. t('On this page you can refresh and update values for user defined strings.') .'</p>';
 
50
      $output .= '<ul>';
 
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>';
 
53
      $output .= '</ul>';
 
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>';
 
55
      return $output;
 
56
 
 
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>';
 
59
      return $output;
 
60
  }
 
61
}
 
62
 
 
63
/**
 
64
 * Implementation of hook_menu().
 
65
 */
 
66
function i18nstrings_menu() {
 
67
  $items['admin/build/translate/refresh'] = array(
 
68
    'title' => 'Refresh',
 
69
    'weight' => 20,
 
70
    'type' => MENU_LOCAL_TASK,
 
71
    'page callback' => 'i18nstrings_admin_refresh_page',
 
72
    'file' => 'i18nstrings.admin.inc',
 
73
    'access arguments' => array('translate interface'),
 
74
  );
 
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,
 
81
  );
 
82
  return $items;
 
83
}
 
84
 
 
85
/**
 
86
 * Implementation of hook_form_alter();
 
87
 *
 
88
 * Add English language in some string forms when it is not the default.
 
89
 */
 
90
function i18nstrings_form_alter(&$form, $form_state, $form_id) {
 
91
 
 
92
  switch ($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'];
 
99
        }
 
100
        else {
 
101
          $form['import']['langcode']['#options'][t('Already added languages')]['en'] = $names['en'];
 
102
        }
 
103
      }
 
104
      break;
 
105
 
 
106
    case 'locale_translate_edit_form':
 
107
      $form['#submit'][] = 'i18nstrings_translate_edit_form_submit';
 
108
      break;
 
109
 
 
110
    case 'l10n_client_form':
 
111
      $form['#action'] = url('i18nstrings/save');
 
112
      break;
 
113
  }
 
114
}
 
115
 
 
116
/**
 
117
 * Process string editing form submissions.
 
118
 *
 
119
 * Mark translations as current.
 
120
 */
 
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);
 
127
    }
 
128
  }
 
129
}
 
130
 
 
131
/**
 
132
 * Translate configurable string.
 
133
 *
 
134
 * @param $name
 
135
 *   Textgroup and location glued with ':'.
 
136
 * @param $string
 
137
 *   String in default language. Default language may or may not be English.
 
138
 * @param $langcode
 
139
 *   Optional language code if different from current request language.
 
140
 * @param $update
 
141
 *   Whether to force update/create for the string.
 
142
 */
 
143
function tt($name, $string, $langcode = NULL, $update = FALSE) {
 
144
  global $language;
 
145
 
 
146
  $langcode = $langcode ? $langcode : $language->language;
 
147
 
 
148
  if ($update) {
 
149
    i18nstrings_update_string($name, $string);
 
150
  }
 
151
  // If language is default, just return
 
152
  if (language_default('language') == $langcode) {
 
153
    return $string;
 
154
  }
 
155
  else {
 
156
    return i18nstrings_tt($name, $string, $langcode, FALSE);
 
157
  }
 
158
}
 
159
 
 
160
/**
 
161
 * Get configurable string,
 
162
 *
 
163
 * The difference with tt() is that it doesn't use a default string, it will be retrieved too.
 
164
 * 
 
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.
 
169
 *
 
170
 * As the original language string will be stored in locales too so it should be only used when updating.
 
171
 */
 
172
function i18nstrings_ts($name, $string = '', $langcode = NULL, $update = FALSE) {
 
173
  global $language;
 
174
 
 
175
  $langcode = $langcode ? $langcode : $language->language;
 
176
  $translation = NULL;
 
177
 
 
178
  if ($update) {
 
179
    i18nstrings_update_string($name, $string);
 
180
  }
 
181
  // if language is default look in sources table
 
182
  if (language_default('language') != $langcode) {
 
183
    $translation = i18nstrings_get_string($name, $langcode);
 
184
  }
 
185
  if (!$translation) {
 
186
    if ($source = i18nstrings_get_source($name)) {
 
187
      $translation = $source->source;
 
188
    }
 
189
    else {
 
190
      $translation = '';
 
191
    }
 
192
  }
 
193
  return $translation;
 
194
}
 
195
 
 
196
/**
 
197
 * Debug util. Marks the translated strings.
 
198
 */
 
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 .')]';
 
203
}
 
204
 
 
205
/**
 
206
 * Get translation for user defined string.
 
207
 *
 
208
 * @todo Add support for latest l10n client.
 
209
 *
 
210
 * @param $name
 
211
 *   Textgroup and location glued with ':'
 
212
 * @param $string
 
213
 *   String in default language
 
214
 * @param $langcode
 
215
 *   Language code to get translation for
 
216
 * @param $update
 
217
 *   Force update (refresh or create), to be used when updating source strings
 
218
 */
 
219
function i18nstrings_tt($name, $string, $langcode, $update = FALSE) {
 
220
  global $language;
 
221
 
 
222
  $context = i18nstrings_context($name, $string);
 
223
 
 
224
  if ($update) {
 
225
    i18nstrings_update_string($context, $string);
 
226
  }
 
227
 
 
228
  // Search for existing translation (result will be cached in this function call)
 
229
  $translation = i18nstrings_get_string($context, $langcode);
 
230
 
 
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.
 
234
    if (!$update) {
 
235
      $source = i18nstrings_get_source($context, $string);
 
236
      if (!$source || empty($source->context)) {
 
237
        i18nstrings_add_string($name, $string);
 
238
      }
 
239
    }
 
240
    $translation = TRUE;
 
241
  }
 
242
 
 
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);
 
247
  }
 
248
  return ($translation === TRUE) ? $string : $translation;
 
249
}
 
250
 
 
251
/**
 
252
 * Translate object.
 
253
 */
 
254
function to($context, &$object, $properties = array(), $langcode = NULL, $update = FALSE) {
 
255
  global $language;
 
256
 
 
257
  $langcode = $langcode ? $langcode : $language->language;
 
258
 
 
259
  // If language is default, just return.
 
260
  if (language_default('language') == $langcode && !$update) {
 
261
    return $object;
 
262
  }
 
263
  else {
 
264
    i18nstrings_to($context, $object, $properties, $langcode, $update);
 
265
  }
 
266
}
 
267
 
 
268
/**
 
269
 * Translate object properties.
 
270
 */
 
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);
 
279
    }
 
280
  }
 
281
}
 
282
 
 
283
/**
 
284
 * Update / create / remove string
 
285
 *
 
286
 * @param $context
 
287
 *   String context.
 
288
 * @pram $string
 
289
 *   New value of string for update/create. May be empty for removing.
 
290
 */
 
291
function i18nstrings_update_string($context, $string) {
 
292
  $context = i18nstrings_context($context, $string);
 
293
  
 
294
  if ($string) {
 
295
    $status = i18nstrings_add_string($context, $string);
 
296
  }
 
297
  else {
 
298
    $status = i18nstrings_remove_string($context);
 
299
  }
 
300
  $params = array(
 
301
    '%location' => i18nstrings_location($context),
 
302
    '%textgroup' => $context->textgroup,
 
303
    '%string' => $string,
 
304
  );
 
305
  switch ($status) {
 
306
    case SAVED_UPDATED:
 
307
      drupal_set_message(t('Updated string %location for textgroup %textgroup: %string', $params));
 
308
      break;
 
309
 
 
310
    case SAVED_NEW:
 
311
      drupal_set_message(t('Created string %location for text group %textgroup: %string', $params));
 
312
      break;
 
313
  }
 
314
  return $status;
 
315
}
 
316
 
 
317
/**
 
318
 * Update string translation.
 
319
 */
 
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);
 
323
  }
 
324
}
 
325
 
 
326
/**
 
327
 * Add source string to the locale tables for translation.
 
328
 *
 
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.
 
331
 * 
 
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.
 
334
 *
 
335
 * @param $name
 
336
 *   Textgroup and location glued with ':'
 
337
 * @param $string
 
338
 *   Source string (string in default language)
 
339
 * 
 
340
 * @return
 
341
 *   Update status.
 
342
 */
 
343
function i18nstrings_add_string($name, $string) {
 
344
  $context = i18nstrings_context($name, $string);
 
345
  $location = i18nstrings_location($context);
 
346
 
 
347
  // Check if we have a source string.
 
348
  $source = i18nstrings_get_source($context, $string);
 
349
 
 
350
  $status = -1;
 
351
 
 
352
  if ($source) {
 
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;
 
358
    }
 
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;
 
363
    }
 
364
    // Update metadata.
 
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);
 
370
    }
 
371
  }
 
372
  else {
 
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);
 
380
    // Create string.
 
381
    $status = SAVED_NEW;
 
382
  }
 
383
 
 
384
  return $status;
 
385
}
 
386
 
 
387
/**
 
388
 * Get source string provided a string context.
 
389
 *
 
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.
 
392
 *
 
393
 * @param $context
 
394
 *   Context string or object.
 
395
 * @return
 
396
 *   Context object if it exists.
 
397
 */
 
398
function i18nstrings_get_source($context, $string = NULL) {
 
399
  $context = i18nstrings_context($context, $string);
 
400
 
 
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;
 
405
    return $source;
 
406
  }
 
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;
 
410
    return $source;
 
411
  }
 
412
}
 
413
 
 
414
/**
 
415
 * Get string for a language.
 
416
 *
 
417
 * @param $context
 
418
 *   Context string or object.
 
419
 * @param $langcode
 
420
 *   Language code to retrieve string for.
 
421
 *
 
422
 * @return
 
423
 *   - Translation if found.
 
424
 *   - TRUE if not found and cached.
 
425
 *   - FALSE if not even cached.
 
426
 *
 
427
 */
 
428
function i18nstrings_get_string($context, $langcode) {
 
429
  $context = i18nstrings_context($context);
 
430
  
 
431
  if ($translation = i18nstrings_cache($context, $langcode)) {
 
432
    return $translation;
 
433
  }
 
434
  else {
 
435
    // Search translation and add it to the cache.
 
436
    list($where, $args) = i18nstrings_context_query($context);
 
437
    $where[] = "t.language = '%s'";
 
438
    $args[] = $langcode;
 
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));
 
440
 
 
441
    if ($text && $text->translation) {
 
442
      i18nstrings_cache($context, $langcode, NULL, $text->translation);
 
443
      return $text->translation;
 
444
    }
 
445
    else {
 
446
      i18nstrings_cache($context, $langcode, NULL, TRUE);
 
447
      return $text ? NULL : FALSE ;
 
448
    }
 
449
  }
 
450
}
 
451
 
 
452
/**
 
453
 * Remove string for a given context.
 
454
 */
 
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;
 
463
  }
 
464
}
 
465
 
 
466
/**
 
467
 * Update context for strings.
 
468
 *
 
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.
 
473
 *
 
474
 * Example:
 
475
 *  'profile:field:oldfield:*' -> 'profile:field:newfield:*'
 
476
 */
 
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));
 
481
 
 
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;
 
487
    }
 
488
  }
 
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;
 
496
    }
 
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);
 
501
  }
 
502
  drupal_set_message(t('Updating string names from %oldname to %newname.', array('%oldname' => $oldname, '%newname' => $newname)));
 
503
}
 
504
 
 
505
/**
 
506
 * Provides interface translation services.
 
507
 *
 
508
 * This function is called from tt() to translate a string if needed.
 
509
 *
 
510
 * @param $textgroup
 
511
 *
 
512
 * @param $string
 
513
 *   A string to look up translation for. If omitted, all the
 
514
 *   cached strings will be returned in all languages already
 
515
 *   used on the page.
 
516
 * @param $langcode
 
517
 *   Language code to use for the lookup.
 
518
 */
 
519
function i18nstrings_textgroup($textgroup, $string = NULL, $langcode = NULL) {
 
520
  global $language;
 
521
  static $locale_t;
 
522
 
 
523
  // Return all cached strings if no string was specified.
 
524
  if (!isset($string)) {
 
525
    return isset($locale_t[$textgroup]) ? $locale_t[$textgroup] : array();
 
526
  }
 
527
 
 
528
  $langcode = isset($langcode) ? $langcode : $language->language;
 
529
 
 
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;
 
540
      }
 
541
      else {
 
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);
 
548
        }
 
549
        cache_set('locale:'. $textgroup .':'. $langcode, $locale_t[$textgroup][$langcode]);
 
550
      }
 
551
    }
 
552
  }
 
553
 
 
554
  // If we have the translation cached, skip checking the database
 
555
  if (!isset($locale_t[$textgroup][$langcode][$string])) {
 
556
 
 
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));
 
559
    if ($translation) {
 
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);
 
563
 
 
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);
 
570
      }
 
571
    }
 
572
    else {
 
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);
 
578
    }
 
579
  }
 
580
 
 
581
  return ($locale_t[$textgroup][$langcode][$string] === TRUE ? $string : $locale_t[$textgroup][$langcode][$string]);
 
582
}
 
583
 
 
584
/**
 
585
 * Convert context string in a context object.
 
586
 *
 
587
 * I.e.
 
588
 *   'taxonomy:term:1:name'
 
589
 *
 
590
 * will become a $context object where
 
591
 *   $context->textgroup = 'taxonomy';
 
592
 *   $context->type = 'term';
 
593
 *   $context->objectid = 1;
 
594
 *   $context->property = 'name';
 
595
 *
 
596
 * Examples:
 
597
 *  'taxonomy:title' -> (taxonomy, title, 0, 0)
 
598
 *  'nodetype:type:[type]:name'
 
599
 *  'nodetype:type:[type]:description'
 
600
 *  'profile:category'
 
601
 *  'profile:field:[fid]:title'
 
602
 *
 
603
 * @param $context
 
604
 *   Context string or object.
 
605
 * @param $string
 
606
 *   For some textgroups and objects that don't have ids we use the string itself as index.
 
607
 * @return
 
608
 *   Context object with textgroup, type, objectid, property and location names.
 
609
 */
 
610
function i18nstrings_context($context, $string = NULL) {
 
611
  // Context may be already an object.
 
612
  if (is_object($context)) {
 
613
    return $context;
 
614
  }
 
615
  else {
 
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,
 
620
      'type' => $type,
 
621
      'objectid' => $objectid ? $objectid : 0,
 
622
      'property' => $property ? $property : 0,
 
623
    );
 
624
    $context->location = i18nstrings_location($context);
 
625
    if (!$context->objectid && !$context->property && $string) {
 
626
      $context->source = $string;
 
627
    }
 
628
    return $context;
 
629
  }
 
630
}
 
631
 
 
632
/**
 
633
 * Get query conditions for this context.
 
634
 */
 
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;
 
641
  }
 
642
  return array($where, $args);
 
643
}
 
644
 
 
645
/**
 
646
 * Get location string from context.
 
647
 *
 
648
 * Returns the location for the locale table for a string context.
 
649
 */
 
650
function i18nstrings_location($context) {
 
651
  if (is_string($context)) {
 
652
    $context = i18nstrings_context($context);
 
653
  }
 
654
  $location[] = $context->type;
 
655
  if ($context->objectid) {
 
656
    $location[] = $context->objectid;
 
657
    if ($context->property) {
 
658
      $location[] = $context->property;
 
659
    }
 
660
  }
 
661
  return implode(':', $location);
 
662
}
 
663
 
 
664
/**
 
665
 * Prefetch a number of object strings.
 
666
 */
 
667
function i18nstrings_prefetch($context, $langcode = NULL, $join = array(), $conditions = array()) {
 
668
  global $language;
 
669
 
 
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) {
 
676
    if ($value) {
 
677
      if ($key == 'textgroup') {
 
678
        $conditions['s.textgroup'] = $value;
 
679
      }
 
680
      else {
 
681
        $conditions['i.'. $key] = $value;
 
682
      }
 
683
    }
 
684
  }
 
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);
 
691
    }
 
692
    else {
 
693
      $where[] = $key .' = '. is_int($value) ? '%d' : "'%s'";
 
694
      $params[] = $value;
 
695
    }
 
696
  }
 
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);
 
701
 
 
702
  // Fetch all rows and store in cache.
 
703
  while ($t = db_fetch_object($result)) {
 
704
    i18nstrings_cache($t, $langcode, $t->source, $t->translation);
 
705
  }
 
706
 
 
707
}
 
708
 
 
709
/**
 
710
 * Retrieves and stores translations in page (static variable) cache.
 
711
 */
 
712
function i18nstrings_cache($context, $langcode, $string = NULL, $translation = NULL) {
 
713
  static $strings;
 
714
 
 
715
  $context = i18nstrings_context($context, $string);
 
716
 
 
717
  if (!$context->objectid && $context->source) {
 
718
    // This is a type indexed by string.
 
719
    $context->objectid = $context->source;
 
720
  }
 
721
  // At this point context must have at least textgroup and type.
 
722
  if ($translation) {
 
723
    if ($context->property) {
 
724
      $strings[$langcode][$context->textgroup][$context->type][$context->objectid][$context->property] = $translation;
 
725
    }
 
726
    elseif ($context->objectid) {
 
727
      $strings[$langcode][$context->textgroup][$context->type][$context->objectid] = $translation;
 
728
    }
 
729
    else {
 
730
      $strings[$langcode][$context->textgroup][$context->type] = $translation;
 
731
    }
 
732
  }
 
733
  else {
 
734
    // Search up the tree for the object or a default.
 
735
    $search = &$strings[$langcode];
 
736
    $default = NULL;
 
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'];
 
743
        }
 
744
      }
 
745
      else  {
 
746
        // We dont have cached this tree so we return the default.
 
747
        return $default;
 
748
      }
 
749
    }
 
750
    // Returns the part of the array we got to.
 
751
    return $search;
 
752
  }
 
753
 
 
754
}
 
755
 
 
756
/**
 
757
 * Callback for menu title translation.
 
758
 */
 
759
function i18nstrings_title_callback($name, $string, $callback = NULL) {
 
760
  $string = tt($name, $string);
 
761
  if ($callback) {
 
762
    $string = $callback($string);
 
763
  }
 
764
  return $string;
 
765
}
 
766
 
 
767
/**
 
768
 * Refresh all user defined strings for a given text group
 
769
 * 
 
770
 * @param $group
 
771
 *   Text group to refresh
 
772
 * @param $delete
 
773
 *   Optional, delete existing (but not refresed, strings and translations) 
 
774
 */
 
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);
 
778
  
 
779
  module_invoke_all('locale', 'refresh', $group);
 
780
  
 
781
  // Now delete all source strings that were not refreshed
 
782
  if ($delete) {
 
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);    
 
787
    }
 
788
  }
 
789
  
 
790
  cache_clear_all('locale:'. $group .':', 'cache', TRUE);
 
791
}
 
792
 
 
793
/*** l10n client related functions ***/
 
794
 
 
795
/**
 
796
 * Menu callback. Saves a string translation coming as POST data.
 
797
 */
 
798
function i18nstrings_save_string() {
 
799
  global $user, $language;
 
800
  
 
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();
 
806
    }
 
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);
 
809
    }
 
810
  }
 
811
}
 
812
 
 
813
/**
 
814
 * Import translation for a given textgroup 
 
815
 * 
 
816
 * This will update multiple strings if there are duplicated ones
 
817
 * 
 
818
 * @param $langcode
 
819
 *   Language code to import string into.
 
820
 * @param $source
 
821
 *   Source string.
 
822
 * @param $translation
 
823
 *   Translation to language specified in $langcode.
 
824
 * @param $plid
 
825
 *   Optional plural ID to use.
 
826
 * @param $plural
 
827
 *   Optional plural value to use.
 
828
 * @return
 
829
 *   The number of strings updated
 
830
 */
 
831
function i18nstrings_save_translation($langcode, $source, $translation, $textgroup) {
 
832
  include_once 'includes/locale.inc';
 
833
  
 
834
  if (locale_string_is_safe($translation)) {
 
835
    $result = db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $source, $textgroup);
 
836
    $count = 0;
 
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));
 
839
      if (!$exists) {
 
840
        // No translation in this language.
 
841
        db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '%s')", $source->lid, $langcode, $translation);
 
842
      }
 
843
      else {
 
844
        // Translation exists, overwrite
 
845
        db_query("UPDATE {locales_target} SET translation = '%s' WHERE language = '%s' AND lid = %d", $translation, $langcode, $source->lid);
 
846
      }
 
847
      $count ++;
 
848
    }
 
849
    return $count;
 
850
  } else {
 
851
    return FALSE;
 
852
  }
 
853
}
 
 
b'\\ No newline at end of file'