04319df9581d6368070eb1e2d20bf16372e711c0
[project/i18n.git] / i18n_string / i18n_string.module
1 <?php
2
3 /**
4 * @file
5 * Internationalization (i18n) package - translatable strings.
6 *
7 * Object oriented string translation using locale and textgroups. As opposed to core locale strings,
8 * all strings handled by this module will have a unique id (name), composed by several parts
9 *
10 * A string name or string id will have the form 'textgroup:type:objectid:property'. Examples:
11 *
12 * - 'profile:field:profile_name:title', will be the title for the profile field 'profile_name'
13 * - 'taxonomy:term:tid:name', will be the name for the taxonomy term tid
14 * - 'views:view_name:display_id:footer', footer text
15 *
16 * Notes:
17 * - The object id must be an integer. This is intended for quick indexing of some properties
18 *
19 * Some concepts
20 * - Textgroup. Group the string belongs to, defined by locale hook.
21 * - Location. Unique id of the string for this textgroup.
22 * - Name. Unique absolute id of the string: textgroup + location.
23 * - Context. Object with textgroup, type, objectid, property.
24 *
25 * Default language
26 * - Default language may be English or not. It will be the language set as default.
27 * Source strings will be stored in default language.
28 * - In the traditional i18n use case you shouldn't change the default language once defined.
29 *
30 * Default language changes
31 * - You might result in the need to change the default language at a later point.
32 * - Enabling translation of default language will curcumvent previous limitations.
33 * - Check i18n_string_translate_langcode() for more details.
34 *
35 * The API other modules to translate/update/remove user defined strings consists of
36 *
37 * @see i18n_string($name, $string, $langcode)
38 * @see i18n_string_update($name, $string, $format)
39 * @see i18n_string_remove($name, $string)
40 *
41 * @author Jose A. Reyero, 2007
42 */
43
44 /**
45 * Translated string is current.
46 */
47 define('I18N_STRING_STATUS_CURRENT', 0);
48
49 /**
50 * Translated string needs updating as the source has been edited.
51 */
52 define('I18N_STRING_STATUS_UPDATE', 1);
53
54 /**
55 * Source string is obsoleted, cannot be found anymore. To be deleted.
56 */
57 define('I18N_STRING_STATUS_DELETE', 2);
58
59 /**
60 * Implements hook_help().
61 */
62 function i18n_string_help($path, $arg) {
63 switch ($path) {
64 case 'admin/help#i18n_string':
65 $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>';
66 $output .= '<p>' . t('This works differently to Drupal standard localization system: The strings will be translated from the <a href="@configure-strings">source language</a>, which defaults to the site default language (it may not be English), so changing the default language may cause all these translations to be broken.', array('@configure-strings' => url('admin/config/regional/i18n/strings'))) . '</p>';
67 $output .= '<ul>';
68 $output .= '<li>' . t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate'))) . '</li>';
69 $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>';
70 $output .= '</ul>';
71 $output .= '<p>' . t('Read more on the <em>Internationalization handbook</em>: <a href="http://drupal.org/node/313293">Translating user defined strings</a>.') . '</p>';
72 return $output;
73
74 case 'admin/config/regional/translate/i18n_string':
75 $output = '<p>' . t('On this page you can refresh and update values for user defined strings.') . '</p>';
76 $output .= '<ul>';
77 $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>';
78 $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>';
79 $output .= '</ul>';
80 $output .= '<p>' . t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate'))) . '</p>';
81 $output .= '<p>' . t('<strong>Important:</strong> To configure which text formats are safe for translation, visit the <a href="@configure-strings">configure strings</a> page before refreshing your strings.', array('@configure-strings' => url('admin/config/regional/i18n/strings'))) . '</p>';
82 return $output;
83
84 case 'admin/config/regional/language':
85 $output = '<p>' . t('<strong>Warning</strong>: Changing the default language may have unwanted effects on string translations. Check also the <a href="@configure-strings">source language</a> for translations and read more about <a href="@i18n_string-help">String translation</a>', array('@configure-strings' => url('admin/config/regional/i18n/strings'), '@i18n_string-help' => url('admin/help/i18n_string'))) . '</p>';
86 return $output;
87 case 'admin/config/regional/i18n/strings':
88 $output = '<p>' . t('When translating user defined strings that have a text format associated, translators will be able to edit the text before it is filtered which may be a security risk for some filters. An obvious example is when using the PHP filter but other filters may also be dangerous.') . '</p>';
89 $output .= '<p>' . t('As a general rule <strong>do not allow any filtered text to be translated unless the translators already have access to that text format</strong>. However if you are doing all your translations through this site\'s translation UI or the Localization client, and never importing translations for other textgroups than <i>default</i>, filter access will be checked for translators on every translation page.') . '</p>';
90 $output .= '<p>' . t('<strong>Important:</strong> After disallowing some text format, use the <a href="@refresh-strings">refresh strings</a> page so forbidden strings are deleted and not allowed anymore for translators.', array('@refresh-strings' => url('admin/config/regional/translate/i18n_string'))) . '</p>';
91 return $output;
92 case 'admin/config/filters':
93 return '<p>' . t('After updating your text formats do not forget to review the list of formats allowed for string translations on the <a href="@configure-strings">configure translatable strings</a> page.', array('@configure-strings' => url('admin/config/regional/i18n/strings'))) . '</p>';
94 }
95 }
96
97 /**
98 * Implements hook_menu().
99 */
100 function i18n_string_menu() {
101 $items['admin/config/regional/translate/i18n_string'] = array(
102 'title' => 'Strings',
103 'description' => 'Refresh user defined strings.',
104 'weight' => 20,
105 'type' => MENU_LOCAL_TASK,
106 'page callback' => 'drupal_get_form',
107 'page arguments' => array('i18n_string_admin_refresh_form'),
108 'file' => 'i18n_string.admin.inc',
109 'access arguments' => array('translate interface'),
110 );
111 $items['admin/config/regional/i18n/strings'] = array(
112 'title' => 'Strings',
113 'description' => 'Options for user defined strings.',
114 'weight' => 20,
115 'type' => MENU_LOCAL_TASK,
116 'page callback' => 'drupal_get_form',
117 'page arguments' => array('variable_edit_form', array('i18n_string_allowed_formats', 'i18n_string_source_language')),
118 'access arguments' => array('administer site configuration'),
119 );
120 // AJAX callback path for strings.
121 $items['i18n_string/save'] = array(
122 'title' => 'Save string',
123 'page callback' => 'i18n_string_l10n_client_save_string',
124 'access arguments' => array('use on-page translation'),
125 'file' => 'i18n_string.pages.inc',
126 'type' => MENU_CALLBACK,
127 );
128 return $items;
129 }
130
131 /**
132 * Implements hook_hook_info().
133 */
134 function i18n_string_hook_info() {
135 $hooks['i18n_string_info'] =
136 $hooks['i18n_string_list'] =
137 $hooks['i18n_string_refresh'] =
138 $hooks['i18n_string_objects'] = array(
139 'group' => 'i18n',
140 );
141 return $hooks;
142 }
143
144 /**
145 * Implements hook_locale().
146 *
147 * Provide the information from i18n_string groups to locale module
148 */
149 function i18n_string_locale($op = 'groups') {
150 if ($op == 'groups') {
151 $groups = array();
152 foreach (i18n_string_group_info() as $name => $info) {
153 $groups[$name] = $info['title'];
154 }
155 return $groups;
156 }
157 }
158
159 /**
160 * Implements hook_modules_enabled().
161 */
162 function i18n_string_modules_enabled($modules) {
163 module_load_include('admin.inc', 'i18n_string');
164 i18n_string_refresh_enabled_modules($modules);
165 }
166
167 /**
168 * Implements hook_modules_uninstalled().
169 */
170 function i18n_string_modules_uninstalled($modules) {
171 module_load_include('admin.inc', 'i18n_string');
172 i18n_string_refresh_uninstalled_modules($modules);
173 }
174
175 /**
176 * Implements hook_form_FORM_ID_alter()
177 */
178 function i18n_string_form_l10n_client_form_alter(&$form, &$form_state) {
179 $form['#action'] = url('i18n_string/save');
180 }
181
182 /**
183 * Implements hook_form_FORM_ID_alter()
184 */
185 function i18n_string_form_locale_translate_edit_form_alter(&$form, &$form_state) {
186 // Restrict filter permissions and handle validation and submission for i18n strings
187 $context = db_select('i18n_string', 'i18ns')
188 ->fields('i18ns')
189 ->condition('lid', $form['lid']['#value'])
190 ->execute()
191 ->fetchObject();
192 if ($context) {
193 $form['i18n_string_context'] = array('#type' => 'value', '#value' => $context);
194 // Replace validate callback
195 $form['#validate'] = array('i18n_string_translate_edit_form_validate');
196 if ($context->format) {
197 $formats = filter_formats();
198 $format = $formats[$context->format];
199 $disabled = !filter_access($format);
200 if ($disabled) {
201 drupal_set_message(t('This string uses the %name text format. You are not allowed to translate or edit texts with this format.', array('%name' => $format->name)), 'warning');
202 }
203 foreach (element_children($form['translations']) as $langcode) {
204 $form['translations'][$langcode]['#disabled'] = $disabled;
205 }
206 $form['translations']['format_help'] = array(
207 '#type' => 'markup',
208 '#markup' => '<h5>' . t('Text format: @name', array('@name' => $format->name)) . '</h5>' . theme('filter_tips', array('tips' => _filter_tips($context->format, FALSE))),
209 );
210 $form['submit']['#disabled'] = $disabled;
211 }
212 }
213 // Aditional submit callback
214 $form['#submit'][] = 'i18n_string_translate_edit_form_submit';
215 }
216
217 /**
218 * Implements hook_form_FORM_ID_alter()
219 */
220 function i18n_string_form_locale_translate_export_po_form_alter(&$form, $form_state) {
221 $names = locale_language_list('name', TRUE);
222 if (i18n_string_source_language()->language != 'en' && array_key_exists('en', $names)) {
223 $form['langcode']['#options']['en'] = $names['en'];
224 }
225 }
226
227 /**
228 * Implements hook_form_FORM_ID_alter()
229 */
230 function i18n_string_form_locale_translate_import_form_alter(&$form, $form_state) {
231 if (i18n_string_source_language()->language != 'en') {
232 $names = locale_language_list('name', TRUE);
233 if (array_key_exists('en', $names)) {
234 $form['import']['langcode']['#options'][t('Already added languages')]['en'] = $names['en'];
235 }
236 else {
237 $form['import']['langcode']['#options'][t('Languages not yet added')]['en'] = t('English');
238 }
239 }
240 $form['#submit'][] = 'i18n_string_locale_translate_import_form_submit';
241 }
242
243 /**
244 * Process string editing form validations.
245 *
246 * If it is an allowed format, skip default validation, the text will be filtered later
247 */
248 function i18n_string_translate_edit_form_validate($form, &$form_state) {
249 $context = $form_state['values']['i18n_string_context'];
250 if (empty($context->format)) {
251 // If not text format use regular validation for all strings
252 $copy_state = $form_state;
253 $copy_state['values']['textgroup'] = 'default';
254 locale_translate_edit_form_validate($form, $copy_state);
255 }
256 elseif (!i18n_string_translate_access($context)) {
257 form_set_error('translations', t('You are not allowed to translate or edit texts with this text format.'));
258 }
259 }
260
261 /**
262 * Process string editing form submissions.
263 *
264 * Mark translations as current.
265 */
266 function i18n_string_translate_edit_form_submit($form, &$form_state) {
267 $lid = $form_state['values']['lid'];
268 foreach ($form_state['values']['translations'] as $key => $value) {
269 if (!empty($value)) {
270 // An update has been made, so we assume the translation is now current.
271 db_update('locales_target')
272 ->fields(array('i18n_status' => I18N_STRING_STATUS_CURRENT))
273 ->condition('lid', $lid)
274 ->condition('language', $key)
275 ->execute();
276 }
277 }
278 }
279
280 /**
281 * Update string data after import form submit
282 */
283 function i18n_string_locale_translate_import_form_submit($form, &$form_state) {
284 if (!drupal_get_messages('error', FALSE) && i18n_string_group_info($form_state['values']['group'])) {
285 i18n_string_textgroup($form_state['values']['group'])->update_check();
286 }
287 }
288
289 /**
290 * Check if translation is required for this language code.
291 *
292 * Translation is required when default language is different from the given
293 * language, or when default language translation is explicitly enabled.
294 *
295 * No UI is provided to enable translation of default language. On the other
296 * hand, you can enable/disable translation for a specific language by adding
297 * the following to your settings.php
298 *
299 * @param $langcode
300 * Optional language code to check. It will default to current request language.
301 * @code
302 * // Enable translation of specific language. Language code is 'xx'
303 * $conf['i18n_string_translate_langcode_xx'] = TRUE;
304 * // Disable translation of specific language. Language code is 'yy'
305 * $conf['i18n_string_translate_langcode_yy'] = FALSE;
306 * @endcode
307 */
308 function i18n_string_translate_langcode($langcode = NULL) {
309 $translate = &drupal_static(__FUNCTION__);
310 $langcode = isset($langcode) ? $langcode : i18n_langcode();
311 if (!isset($translate[$langcode])) {
312 $translate[$langcode] = variable_get('i18n_string_translate_langcode_' . $langcode, i18n_string_source_language() != $langcode);
313 }
314 return $translate[$langcode];
315 }
316
317 /**
318 * Create string class from string name
319 */
320 function i18n_string_build($name, $string = NULL) {
321 list ($group, $context) = i18n_string_context($name);
322 return i18n_string_textgroup($group)->build_string($context, $string);
323 }
324
325 /**
326 * Update / create translation source for user defined strings.
327 *
328 * @param $name
329 * Array or string concatenated with ':' that contains textgroup and string context
330 * @param $string
331 * Source string in default language. Default language may or may not be English.
332 * Array of key => string to update multiple strings at once
333 * @param $options
334 * Array with additional options:
335 * - 'format', String format if the string has text format
336 * - 'messages', Whether to print out status messages
337 */
338 function i18n_string_update($name, $string, $options = array()) {
339 if (is_array($string)) {
340 return i18n_string_multiple('update', $name, $string, $options);
341 }
342 else {
343 list($textgroup, $context) = i18n_string_context($name);
344 return i18n_string_textgroup($textgroup)->context_update($context, $string, $options);
345 }
346 }
347
348 /**
349 * Update context for strings.
350 *
351 * As some string locations depend on configurable values, the field needs sometimes to be updated
352 * without losing existing translations. I.e:
353 * - profile fields indexed by field name.
354 * - content types indexted by low level content type name.
355 *
356 * Example:
357 * 'profile:field:oldfield:*' -> 'profile:field:newfield:*'
358 */
359 function i18n_string_update_context($oldname, $newname) {
360 module_load_install('i18n_string');
361 return i18n_string_install_update_context($oldname, $newname);
362 }
363
364 /**
365 * Get textgroup handler
366 */
367 function i18n_string_textgroup($textgroup) {
368 $groups = &drupal_static(__FUNCTION__);
369 if (!isset($groups[$textgroup])) {
370 $class = i18n_string_group_info($textgroup, 'class', 'i18n_string_textgroup_default');
371 $groups[$textgroup] = new $class($textgroup);
372 }
373 return $groups[$textgroup];
374 }
375
376 /**
377 * Check whether a string format is allowed for translation.
378 */
379 function i18n_string_allowed_format($format_id = NULL) {
380 if (!$format_id) {
381 return TRUE;
382 }
383 else {
384 return in_array($format_id, variable_get('i18n_string_allowed_formats', array(filter_fallback_format())), TRUE);
385 }
386 }
387
388 /**
389 * Convert string name into textgroup and string context
390 *
391 * @param $name
392 * Array or string concatenated with ':' that contains textgroup and string context
393 * @param $replace
394 * Parameter to replace the placeholder ('*') if we are dealing with multiple strings
395 * Or parameter to append to context if there's no placeholder
396 *
397 * @return array
398 * The first element will be the text group name
399 * The second one will be an array with string name elements, without textgroup
400 */
401 function i18n_string_context($name, $replace = NULL) {
402 $parts = is_array($name) ? $name : explode(':', $name);
403 if ($replace) {
404 $key = array_search('*', $parts);
405 if ($key !== FALSE) {
406 $parts[$key] = $replace;
407 }
408 elseif (count($parts) < 4) {
409 array_push($parts, $replace);
410 }
411 }
412 $textgroup = array_shift($parts);
413 $context = $parts;
414 return array($textgroup, $context);
415 }
416
417 /**
418 * Mark form element as localizable
419 */
420 function i18n_string_element_mark(&$element) {
421 $description = '<strong>'. t('This string will be localizable. You can translate it using the <a href="@translate-interface">translate interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate/translate'))) .'</strong>';
422 if (empty($element['#description'])) {
423 $element['#description'] = $description;
424 }
425 else {
426 $element['#description'] .= ' ' . $description;
427 }
428 }
429
430
431 /**
432 * Get source string object.
433 */
434 function i18n_string_get_source($name) {
435 list ($textgroup, $context) = i18n_string_context($name);
436 return i18n_string_textgroup($textgroup)->build_string($context)->get_source();
437 }
438
439 /**
440 * Get textgroup info, from hook_locale('info')
441 *
442 * @param $group
443 * Text group name.
444 * @param $default
445 * Default value to return for a property if not set.
446 */
447 function i18n_string_group_info($group = NULL, $property = NULL, $default = NULL) {
448 $info = &drupal_static(__FUNCTION__ , NULL);
449
450 if (!isset($info)) {
451 $info = module_invoke_all('i18n_string_info');
452 drupal_alter('i18n_string_info', $info);
453 }
454
455 if ($group && $property) {
456 return isset($info[$group][$property]) ? $info[$group][$property] : $default;
457 }
458 elseif ($group) {
459 return isset($info[$group]) ? $info[$group] : array();
460 }
461 else {
462 return $info;
463 }
464 }
465
466
467 /**
468 * Translate / update multiple strings
469 *
470 * @param $strings
471 * Array of name => string pairs
472 */
473 function i18n_string_multiple($operation, $name, $strings, $options = array()) {
474 $result = array();
475 // Strings may be an array of properties, we need to shift it
476 if ($operation == 'remove') {
477 $strings = array_flip($strings);
478 }
479 foreach ($strings as $key => $string) {
480 list($textgroup, $context) = i18n_string_context($name, $key);
481 array_unshift($context, $textgroup);
482 $result[$key] = call_user_func('i18n_string_' . $operation, $context, $string, $options);
483 }
484 return $result;
485 }
486
487 /**
488 * @ingroup i18napi
489 * @{
490 */
491
492 /**
493 * Get translation for user defined string.
494 *
495 * This function is intended to return translations for plain strings that have NO text format
496 *
497 * @param $name
498 * Array or string concatenated with ':' that contains textgroup and string context
499 * @param $string
500 * String in default language or array of strings to be translated
501 * @param $options
502 * An associative array of additional options, with the following keys:
503 * - 'langcode' (defaults to the current language) The language code to translate to a language other than what is used to display the page.
504 * - 'filter' Filtering callback to apply to the translated string only
505 * - 'format' Input format to apply to the translated string only
506 * - 'callback' Callback to apply to the result (both to translated or untranslated string
507 * - 'sanitize' Whether to filter the translation applying the text format if any, default is TRUE
508 * - 'sanitize default' Whether to filter the default value if no translation found, default is FALSE
509 */
510 function i18n_string_translate($name, $string, $options = array()) {
511 if (is_array($string)) {
512 return i18n_string_translate_list($name, $string, $options);
513 }
514 else {
515 $options['langcode'] = $langcode = isset($options['langcode']) ? $options['langcode'] : i18n_langcode();
516 if (i18n_string_translate_langcode($langcode)) {
517 list($textgroup, $context) = i18n_string_context($name);
518 $translation = i18n_string_textgroup($textgroup)->context_translate($context, $string, $options);
519 // Add for l10n client if available, we pass translation object that contains the format
520 i18n_string_l10n_client_add($translation, $langcode);
521 return $translation->format_translation($langcode, $options);
522 }
523 else {
524 // If we don't want to translate to this language, format and return
525 if (!empty($options['sanitize default']) && empty($options['format'])) {
526 $string = check_plain($string);
527 }
528 return i18n_string_format($string, $options);
529 }
530 }
531 }
532
533 /**
534 * Check user access to translate a specific string.
535 *
536 * If the string has a format the user is not allowed to edit, it will return FALSE
537 *
538 * @param $string_format;
539 * String object or $format_id
540 */
541 function i18n_string_translate_access($string_format, $account = NULL) {
542 $format_id = is_object($string_format) ? i18n_object_field($string_format, 'format') : $string_format;
543 return empty($format_id) || i18n_string_allowed_format($format_id) && ($format = filter_format_load($format_id)) && filter_access($format, $account) ;
544 }
545
546 /**
547 * Format the resulting translation or the default string applying callbacks
548 *
549 * @param $string
550 * Text string.
551 * @param $options
552 * Array of options for string formatting:
553 * - 'format', text format to apply to the string, defaults to none.
554 * - 'sanitize', whether to apply the text format, defaults to TRUE.
555 * - 'cache', text format parameter.
556 * - 'langcode', text format parameter, defaults to current page language.
557 */
558 function i18n_string_format($string, $options = array()) {
559 $options += array('langcode' => i18n_langcode(), 'format' => FALSE, 'sanitize' => TRUE, 'cache' => FALSE);
560 // Apply format and callback
561 if ($string) {
562 if ($options['format'] && $options['sanitize']) {
563 $string = check_markup($string, $options['format'], $options['langcode'], $options['cache']);
564 }
565 if (isset($options['callback'])) {
566 $string = call_user_func($options['callback'], $string);
567 }
568 }
569 // Finally, apply prefix and suffix
570 $options += array('prefix' => '', 'suffix' => '');
571 return $options['prefix'] . $string . $options['suffix'];
572 }
573
574 /**
575 * Get filtered translation.
576 *
577 * This function is intended to return translations for strings that have a text format
578 *
579 * @param $name
580 * Array or string concatenated with ':' that contains textgroup and string context
581 * @param $default
582 * Default string to return if not found, already filtered
583 * @param $options
584 * Array with additional options.
585 */
586 function i18n_string_text($name, $default, $options = array()) {
587 $options += array('format' => filter_fallback_format(), 'sanitize' => TRUE);
588 return i18n_string_translate($name, $default, $options);
589 }
590
591 /**
592 * Translation for plain string. In case it finds a translation it applies check_plain() to it
593 *
594 * @param $name
595 * Array or string concatenated with ':' that contains textgroup and string context
596 * @param $default
597 * Default string to return if not found
598 * @param $options
599 * Array with additional options
600 */
601 function i18n_string_plain($name, $default, $options = array()) {
602 $options += array('filter' => 'check_plain');
603 return i18n_string_translate($name, $default, $options);
604 }
605
606 /**
607 * Get source language code for translations
608 */
609 function i18n_string_source_language() {
610 return variable_get('i18n_string_source_language', language_default('language'));
611 }
612
613 /**
614 * Translation for list of options
615 *
616 * @param $options
617 * Array with additional options, some changes
618 * - 'index' => field that will be mapped to the array key (defaults to 'property')
619 */
620 function i18n_string_translate_list($name, $strings, $options = array()) {
621 $options['langcode'] = $langcode = isset($options['langcode']) ? $options['langcode'] : i18n_langcode();
622 // If language is default, just return
623 if (i18n_string_translate_langcode($langcode)) {
624 // Get textgroup context, preserve placeholder
625 list($textgroup, $context) = i18n_string_context($name, '*');
626 $translations = i18n_string_textgroup($textgroup)->multiple_translate($context, $strings, $options);
627 // Add for l10n client if available, we pass translation object that contains the format
628 foreach ($translations as $index => $translation) {
629 i18n_string_l10n_client_add($translation, $langcode);
630 $strings[$index] = $translation->format_translation($langcode, $options);
631 }
632 }
633 else {
634 // Format and return
635 foreach ($strings as $key => $string) {
636 $strings[$key] = i18n_string_format($string, $options);
637 }
638 }
639 return $strings;
640 }
641
642 /**
643 * Remove source and translations for user defined string.
644 *
645 * Though for most strings the 'name' or 'string id' uniquely identifies that string,
646 * there are some exceptions (like profile categories) for which we need to use the
647 * source string itself as a search key.
648 *
649 * @param $context
650 * String context
651 * @param $string
652 * Optional source string (string in default language).
653 * Array of string properties to remove
654 */
655 function i18n_string_remove($name, $string = NULL, $options = array()) {
656 if (is_array($string)) {
657 return i18n_string_multiple('remove', $name, $string, $options);
658 }
659 else {
660 list($textgroup, $context) = i18n_string_context($name);
661 return i18n_string_textgroup($textgroup)->context_remove($context, $string, $options);
662 }
663 }
664
665 /**
666 * @} End of "ingroup i18napi".
667 */
668
669 /*** l10n client related functions ***/
670
671 /**
672 * Add string to l10n strings if enabled and allowed for this string
673 *
674 * @param $context
675 * String object
676 */
677 function i18n_string_l10n_client_add($string, $langcode) {
678 // If current language add to l10n client list for later on page translation.
679 // If langcode translation was disabled we are not supossed to reach here.
680 if (($langcode == i18n_langcode()) && function_exists('l10_client_add_string_to_page') && i18n_string_translate_access($string)) {
681 $translation = $string->get_translation($langcode);
682 $source = !empty($string->source) ? $string->source : FALSE;
683 l10_client_add_string_to_page($source, $translation ? $translation : FALSE, $string->textgroup, $string->context);
684 }
685 }
686
687 /**
688 * Get information about object string translation
689 */
690 function i18n_string_object_info($type = NULL, $property = NULL) {
691 if ($type) {
692 if (($info = i18n_object_info($type, 'string translation'))) {
693 if ($property) {
694 return isset($info[$property]) ? $info[$property] : NULL;
695 }
696 else {
697 return $info;
698 }
699 }
700 }
701 else {
702 $list = array();
703 foreach (i18n_object_info() as $type => $info) {
704 if (!empty($info['string translation'])) {
705 $list[$type] = $info;
706 }
707 }
708 return $list;
709 }
710 }
711
712 /**
713 * Translate object properties
714 *
715 * We clone the object previously so we don't risk translated properties being saved
716 *
717 * @param $type
718 * Object type
719 * @param $object
720 * Object or array
721 */
722 function i18n_string_object_translate($type, $object, $options = array()) {
723 $langcode = isset($options['langcode']) ? $options['langcode'] : i18n_langcode();
724 if (i18n_string_translate_langcode($langcode)) {
725 // Object properties will be returned without filtering as in the original one.
726 $options += array('sanitize' => FALSE);
727 return i18n_object($type, $object)->translate($langcode, $options);
728 }
729 else {
730 return $object;
731 }
732 }
733
734 /**
735 * Remove object strings, because object is deleted
736 *
737 * @param $type
738 * Object type
739 * @param $object
740 * Object or array
741 */
742 function i18n_string_object_remove($type, $object, $options = array()) {
743 return i18n_object($type, $object)->strings_remove($options);
744 }
745
746 /**
747 * Update object properties.
748 *
749 * @param $type
750 * Object type
751 * @param $object
752 * Object or array
753 */
754 function i18n_string_object_update($type, $object, $options = array()) {
755 return i18n_object($type, $object)->strings_update($options);
756 }
757
758 /**
759 * Generic translation page for i18n_strings objects.
760 */
761 function i18n_string_object_translate_page($object_type, $object_value, $langcode = NULL) {
762 module_load_include('inc', 'i18n_string', 'i18n_string.pages');
763 return i18n_string_translate_page_object($object_type, $object_value, $langcode);
764 }
765
766 /**
767 * Preload all strings for this textroup/context.
768 *
769 * This is a performance optimization to load all needed strings with a single query.
770 *
771 * Examples of valid string name to search are:
772 * - 'taxonomy:term:*:title'
773 * This will find all titles for taxonomy terms
774 * - array('taxonomy', 'term', array(1,2), '*')
775 * This will find all properties for taxonomy terms 1 and 2
776 *
777 * @param $name
778 * Specially crafted string name, it may take '*' and array parameters for each element.
779 * @param $langcode
780 * Language code to search translations. Defaults to current language.
781 *
782 * @return array()
783 * String objects indexed by context.
784 */
785 function i18n_string_translation_search($name, $langcode = NULL) {
786 $langcode = isset($langcode) ? $langcode : i18n_langcode();
787 list ($textgroup, $context) = i18n_string_context($name);
788 return i18n_string_textgroup($textgroup)->multiple_translation_search($context, $langcode);
789 }
790
791 /**
792 * Update / create translation for a certain source.
793 *
794 * @param $name
795 * Array or string concatenated with ':' that contains textgroup and string context
796 * @param $translation
797 * Translation string for this language code
798 * @param $langcode
799 * The language code to translate to a language other than what is used to display the page.
800 * @param $source_string
801 * Optional source string, just in case it needs to be created.
802 *
803 * @return mixed
804 * Source string object if update was successful.
805 * Null if source string not found.
806 * FALSE if use doesn't have permission to edit this translation.
807 */
808 function i18n_string_translation_update($name, $translation, $langcode, $source_string = NULL) {
809 if (is_array($translation)) {
810 return i18n_string_multiple('translation_update', $name, $translation, $langcode);
811 }
812 elseif ($source = i18n_string_get_source($name)) {
813 if (i18n_string_translation_validate($source, $translation)) {
814 if ($langcode == i18n_string_source_language()->language) {
815 // It's the default language so we should update the string source as well.
816 i18n_string_update($name, $translation);
817 }
818 else {
819 list ($textgroup, $context) = i18n_string_context($name);
820 i18n_string_textgroup($textgroup)->update_translation($context, $langcode, $translation);
821 }
822 return $source;
823 }
824 else {
825 // We cannot update this string because of its input format.
826 return FALSE;
827 }
828 }
829 elseif ($source_string) {
830 // We don't have a source in the database, so we need to create it, but only if we've got the source too.
831 // Note this string won't have any format.
832 i18n_string_update($name, $source_string);
833 return i18n_string_translation_update($name, $translation, $langcode);
834 }
835 else {
836 return NULL;
837 }
838 }
839
840 /**
841 * Validate translation and check user access to input format
842 */
843 function i18n_string_translation_validate($i18nstring, $translation) {
844 if (!empty($i18nstring->format)) {
845 // If we've got a text format, just need to check user access to it.
846 return i18n_string_translate_access($i18nstring);
847 }
848 else {
849 // If not text format use standard locale validation.
850 // Note: looks like locale.inc is included by locale_init() ?!
851 return locale_string_is_safe($translation);
852 }
853 }