i18n_string.pages.inc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. <?php
  2. /**
  3. * @file
  4. * Internationalization (i18n) package - translatable strings reusable admin UI.
  5. *
  6. * @author Jose A. Reyero, 2007
  7. */
  8. // Load locale libraries
  9. include_once DRUPAL_ROOT . '/includes/locale.inc';
  10. include_once drupal_get_path('module', 'locale') . '/locale.admin.inc';
  11. /**
  12. * Generate translate page from object
  13. */
  14. function i18n_string_translate_page_object($object_type, $object_value, $language = NULL) {
  15. // For backwards compatibility, ensure parameter is a language object
  16. $language = i18n_language_object($language);
  17. $langcode = $language ? $language->language : NULL;
  18. // Get base keys for all these strings. Object key may be multiple like for blocks (module, delta)
  19. $object = i18n_object($object_type, $object_value);
  20. $strings = $object->get_strings(array('empty' => TRUE));
  21. if (empty($langcode)) {
  22. drupal_set_title(t('Translate !name', array('!name' => i18n_object_info($object_type, 'title'))));
  23. return i18n_string_translate_page_overview($object, $strings);
  24. }
  25. else {
  26. drupal_set_title(t('Translate to !language', array('!language' => i18n_language_name($langcode))));
  27. return drupal_get_form('i18n_string_translate_page_form', $strings, $langcode);
  28. }
  29. }
  30. /**
  31. * Provide a core translation module like overview page for this object.
  32. */
  33. function i18n_string_translate_page_overview($object, $strings) {
  34. $build['i18n_overview'] = drupal_get_form('i18n_string_translate_page_overview_form', $object, $strings);
  35. return $build;
  36. }
  37. /**
  38. * Provide a core translation module like overview page for this object.
  39. */
  40. function i18n_string_translate_page_overview_form($form, &$form_state, $object, $strings) {
  41. //include_once DRUPAL_ROOT . '/includes/language.inc';
  42. // Set the default item key, assume it's the first.
  43. $item_title = reset($strings);
  44. $header = array(
  45. 'language' => t('Language'),
  46. 'title' => t('Title'),
  47. 'status' => t('Status'),
  48. 'operations' => t('Operations')
  49. );
  50. $source_language = variable_get_value('i18n_string_source_language');
  51. $rows = array();
  52. foreach (language_list() as $langcode => $language) {
  53. if ($langcode == $source_language) {
  54. $items = array(
  55. 'language' => check_plain($language->name) . ' ' . t('(source)'),
  56. 'title' => check_plain($item_title->get_string()),
  57. 'status' => t('original'),
  58. 'operations' => l(t('edit'), $object->get_edit_path()),
  59. );
  60. }
  61. else {
  62. // Try to figure out if this item has any of its properties translated.
  63. $translated = FALSE;
  64. foreach ($strings as $i18nstring) {
  65. if ($i18nstring->get_translation($langcode)) {
  66. $translated = TRUE;
  67. break;
  68. }
  69. }
  70. // Translate the item that was requested to be displayed as title.
  71. $items = array(
  72. 'language' => check_plain($language->name),
  73. 'title' => $item_title->format_translation($langcode, array('sanitize default' => TRUE)),
  74. 'status' => $translated ? t('translated') : t('not translated'),
  75. 'operations' => l(t('translate'), $object->get_translate_path($langcode), array('query' => drupal_get_destination())),
  76. );
  77. }
  78. foreach ($items as $key => $markup) {
  79. $rows[$langcode][$key] = $markup;
  80. //$form['#rows'][$langcode][$key]['#markup'] = $markup;
  81. }
  82. }
  83. // Build a form so it can be altered later, with all this information.
  84. $form['object'] = array('#type' => 'value', '#value' => $object);
  85. $form['source_language'] = array('#type' => 'value', '#value' => $source_language);
  86. $form['languages'] = array(
  87. '#header' => $header,
  88. '#rows' => $rows,
  89. '#theme' => 'table',
  90. );
  91. return $form;
  92. }
  93. /**
  94. * Form builder callback for in-place string translation.
  95. *
  96. * @param $strings
  97. * Array of strings indexed by string name (may be indexed by group key too if $groups is present)
  98. * @param $langcode
  99. * Language code to translate to.
  100. * @param $groups
  101. * Optional groups to provide some string grouping. Array with group key and title pairs.
  102. */
  103. function i18n_string_translate_page_form($form, &$form_state, $strings, $langcode, $groups = NULL) {
  104. $form = i18n_string_translate_page_form_base($form, $langcode);
  105. if ($groups) {
  106. // I we've got groups, string list is grouped by group key.
  107. $form['string_groups'] = array('#type' => 'value', '#value' => $strings);
  108. foreach ($groups as $key => $title) {
  109. $form['display'] = array(
  110. '#type' => 'vertical_tabs',
  111. );
  112. $form['strings'][$key] = array(
  113. '#group' => 'display',
  114. '#title' => $title,
  115. '#type' => 'fieldset',
  116. ) + i18n_string_translate_page_form_strings($strings[$key], $langcode);
  117. }
  118. }
  119. else {
  120. // We add a single group with key 'all', but no tabs.
  121. $form['string_groups'] = array('#type' => 'value', '#value' => array('all' => $strings));
  122. $form['strings']['all'] = i18n_string_translate_page_form_strings($strings, $langcode);
  123. }
  124. return $form;
  125. }
  126. /**
  127. * Create base form for string translation
  128. */
  129. function i18n_string_translate_page_form_base($form, $langcode, $redirect = NULL) {
  130. $form['langcode'] = array(
  131. '#type' => 'value',
  132. '#value' => $langcode,
  133. );
  134. $form['submit'] = array(
  135. '#type' => 'submit',
  136. '#value' => t('Save translation'),
  137. '#weight' => 10,
  138. );
  139. if ($redirect) {
  140. $form['#redirect'] = array(
  141. $redirect,
  142. );
  143. }
  144. // Add explicit validate and submit hooks so this can be used from inside any form.
  145. $form['#submit'] = array('i18n_string_translate_page_form_submit');
  146. return $form;
  147. }
  148. /**
  149. * Create field elements for strings
  150. */
  151. function i18n_string_translate_page_form_strings($strings, $langcode) {
  152. $formats = filter_formats();
  153. foreach ($strings as $item) {
  154. // We may have a source or not. Load it, our string may get the format from it.
  155. $source = $item->get_source();
  156. $format_id = $source ? $source->format : $item->format;
  157. $description = '';
  158. // Check permissions to translate this string, depends on format, etc..
  159. if ($message = $item->check_translate_access()) {
  160. // We'll display a disabled element with the reason it cannot be translated.
  161. $disabled = TRUE;
  162. $description = $message;
  163. }
  164. else {
  165. $disabled = FALSE;
  166. $description = '';
  167. // If we don't have a source and it can be translated, we create it.
  168. if (!$source) {
  169. // Enable messages just as a reminder these strings are not being updated properly.
  170. $status = $item->update(array('messages' => TRUE));
  171. if ($status === FALSE || $status === SAVED_DELETED) {
  172. // We don't have a source string so nothing to translate here
  173. $disabled = TRUE;
  174. }
  175. else {
  176. $source = $item->get_source();
  177. }
  178. }
  179. }
  180. $default_value = $item->format_translation($langcode, array('langcode' => $langcode, 'sanitize' => FALSE, 'debug' => FALSE));
  181. $form[$item->get_name()] = array(
  182. '#title' => $item->get_title(),
  183. '#type' => 'textarea',
  184. '#default_value' => $default_value,
  185. '#disabled' => $disabled,
  186. '#description' => $description . _i18n_string_translate_format_help($format_id),
  187. //'#i18n_string_format' => $source ? $source->format : 0,
  188. // If disabled, provide smaller textarea (that can be expanded anyway).
  189. '#rows' => $disabled ? 1 : min(ceil(str_word_count($default_value) / 12), 10),
  190. // Change the parent for disabled strings so we don't get empty values later
  191. '#parents' => array($disabled ? 'disabled_strings': 'strings', $item->get_name()),
  192. );
  193. }
  194. return $form;
  195. }
  196. /**
  197. * Form submission callback for in-place string translation.
  198. */
  199. function i18n_string_translate_page_form_submit($form, &$form_state) {
  200. $count = $success = 0;
  201. foreach ($form_state['values']['strings'] as $name => $value) {
  202. $count++;
  203. list($textgroup, $context) = i18n_string_context(explode(':', $name));
  204. $result = i18n_string_textgroup($textgroup)->update_translation($context, $form_state['values']['langcode'], $value);
  205. $success += ($result ? 1 : 0);
  206. }
  207. if ($success) {
  208. drupal_set_message(format_plural($success, 'A translation was saved successfully.', '@count translations were saved successfully.'));
  209. }
  210. if ($error = $count - $success) {
  211. drupal_set_message(format_plural($error, 'A translation could not be saved.', '@count translations could not be saved.'), 'warning');
  212. }
  213. if (isset($form['#redirect'])) {
  214. $form_state['redirect'] = $form['#redirect'];
  215. }
  216. }
  217. /**
  218. * Menu callback. Saves a string translation coming as POST data.
  219. */
  220. function i18n_string_l10n_client_save_string() {
  221. global $user, $language;
  222. if (user_access('use on-page translation')) {
  223. $textgroup = !empty($_POST['textgroup']) ? $_POST['textgroup'] : 'default';
  224. // Other textgroups will be handled by l10n_client module
  225. if (!i18n_string_group_info($textgroup)) {
  226. return l10n_client_save_string();
  227. }
  228. elseif (isset($_POST['source']) && isset($_POST['target']) && !empty($_POST['context']) && !empty($_POST['form_token']) && drupal_valid_token($_POST['form_token'], 'l10n_client_form')) {
  229. $name = $textgroup . ':' . $_POST['context'];
  230. if ($i18nstring = i18n_string_get_source($name)) {
  231. // Since this is not a real form, we double check access permissions here too.
  232. if ($error = $i18nstring->check_translate_access()) {
  233. $message = theme('l10n_client_message', array('message' => t('Not saved due to: !reason', array('!reason' => $error)), 'level' => WATCHDOG_WARNING));
  234. }
  235. else {
  236. $result = i18n_string_translation_update($name, $_POST['target'], $language->language, $_POST['source']);
  237. if ($result) {
  238. $message = theme('l10n_client_message', array('message' => t('Translation saved locally for user defined string.'), 'level' => WATCHDOG_INFO));
  239. }
  240. elseif ($result === FALSE) {
  241. $message = theme('l10n_client_message', array('message' => t('Not saved due to insufficient permissions.')));
  242. }
  243. else {
  244. $message = theme('l10n_client_message', array('message' => t('Not saved due to unknown reason.')));
  245. }
  246. }
  247. }
  248. else {
  249. $message = theme('l10n_client_message', array('message' => t('Not saved due to source string missing.')));
  250. }
  251. }
  252. else {
  253. $message = theme('l10n_client_message', array('message' => t('Not saved due to missing form values.')));
  254. }
  255. drupal_json_output($message);
  256. exit;
  257. }
  258. }
  259. /**
  260. * User interface for string editing.
  261. */
  262. function i18n_string_locale_translate_edit_form($form, &$form_state, $lid) {
  263. // Fetch source string, if possible.
  264. $source = db_query('SELECT source, context, textgroup, location FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject();
  265. if (!$source) {
  266. drupal_set_message(t('String not found.'), 'error');
  267. drupal_goto('admin/config/regional/translate/translate');
  268. }
  269. // Add original text to the top and some values for form altering.
  270. $form['original'] = array(
  271. '#type' => 'item',
  272. '#title' => t('Original text'),
  273. '#markup' => check_plain(wordwrap($source->source, 0)),
  274. );
  275. if (!empty($source->context)) {
  276. $form['context'] = array(
  277. '#type' => 'item',
  278. '#title' => t('Context'),
  279. '#markup' => check_plain($source->context),
  280. );
  281. }
  282. $form['lid'] = array(
  283. '#type' => 'value',
  284. '#value' => $lid
  285. );
  286. $form['textgroup'] = array(
  287. '#type' => 'value',
  288. '#value' => $source->textgroup,
  289. );
  290. $form['location'] = array(
  291. '#type' => 'value',
  292. '#value' => $source->location
  293. );
  294. // Include default form controls with empty values for all languages.
  295. // This ensures that the languages are always in the same order in forms.
  296. $languages = language_list();
  297. // We don't need the default language value, that value is in $source.
  298. $omit = $source->textgroup == 'default' ? 'en' : i18n_string_source_language();
  299. unset($languages[($omit)]);
  300. $form['translations'] = array('#tree' => TRUE);
  301. // Approximate the number of rows to use in the default textarea.
  302. $rows = min(ceil(str_word_count($source->source) / 12), 10);
  303. foreach ($languages as $langcode => $language) {
  304. $form['translations'][$langcode] = array(
  305. '#type' => 'textarea',
  306. '#title' => t($language->name),
  307. '#rows' => $rows,
  308. '#default_value' => '',
  309. );
  310. }
  311. // Fetch translations and fill in default values in the form.
  312. $result = db_query("SELECT DISTINCT translation, language FROM {locales_target} WHERE lid = :lid AND language <> :omit", array(':lid' => $lid, ':omit' => $omit));
  313. foreach ($result as $translation) {
  314. $form['translations'][$translation->language]['#default_value'] = $translation->translation;
  315. }
  316. $form['actions'] = array('#type' => 'actions');
  317. $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
  318. // Restrict filter permissions and handle validation and submission for i18n strings.
  319. if (i18n_string_group_info($source->textgroup)) {
  320. if ($i18nstring = i18n_string_get_by_lid($form['lid']['#value'])) {
  321. $form['i18n_string'] = array('#type' => 'value', '#value' => $i18nstring);
  322. if ($message = $i18nstring->check_translate_access()) {
  323. drupal_set_message($message);
  324. $disabled = TRUE;
  325. }
  326. // Add format help anyway, though the form may be disabled.
  327. $form['translations']['format_help']['#markup'] = _i18n_string_translate_format_help($i18nstring->format);
  328. }
  329. else {
  330. drupal_set_message(t('Source string not found.'), 'warning');
  331. $disabled = TRUE;
  332. }
  333. if (!empty($disabled)) {
  334. // Disable all form elements
  335. $form['submit']['#disabled'] = TRUE;
  336. foreach (element_children($form['translations']) as $langcode) {
  337. $form['translations'][$langcode]['#disabled'] = TRUE;
  338. }
  339. }
  340. }
  341. return $form;
  342. }
  343. /**
  344. * Process string editing form validations.
  345. *
  346. * If it is an allowed format, skip default validation, the text will be filtered later
  347. */
  348. function i18n_string_locale_translate_edit_form_validate($form, &$form_state) {
  349. if (empty($form_state['values']['i18n_string'])) {
  350. // If not i18n string use regular locale validation.
  351. $copy_state = $form_state;
  352. locale_translate_edit_form_validate($form, $copy_state);
  353. }
  354. }
  355. /**
  356. * Process string editing form submissions.
  357. *
  358. * Mark translations as current.
  359. */
  360. function i18n_string_locale_translate_edit_form_submit($form, &$form_state) {
  361. // Invoke locale submission.
  362. locale_translate_edit_form_submit($form, $form_state);
  363. $lid = $form_state['values']['lid'];
  364. foreach ($form_state['values']['translations'] as $key => $value) {
  365. if (!empty($value)) {
  366. // An update has been made, so we assume the translation is now current.
  367. db_update('locales_target')
  368. ->fields(array('i18n_status' => I18N_STRING_STATUS_CURRENT))
  369. ->condition('lid', $lid)
  370. ->condition('language', $key)
  371. ->execute();
  372. }
  373. }
  374. }
  375. /**
  376. * Help for text format.
  377. */
  378. function _i18n_string_translate_format_help($format_id) {
  379. $output = '';
  380. if ($format = filter_format_load($format_id)) {
  381. $title = t('Text format: @name', array('@name' => $format->name));
  382. $tips = theme('filter_tips', array('tips' => _filter_tips($format_id, FALSE)));
  383. }
  384. elseif ($format_id == I18N_STRING_FILTER_XSS) {
  385. $title = t('Standard XSS filter.');
  386. $allowed_html = '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>';
  387. $tips[] = t('Allowed HTML tags: @tags', array('@tags' => $allowed_html));
  388. }
  389. elseif ($format_id == I18N_STRING_FILTER_XSS_ADMIN) {
  390. $title = t('Administration XSS filter.');
  391. $tips[] = t('It will allow most HTML tags but not scripts nor styles.');
  392. }
  393. elseif ($format_id) {
  394. $title = t('Unknown filter: @name', array('@name' => $format_id));
  395. }
  396. if (!empty($title)) {
  397. $output .= '<h5>' . $title . '</h5>';
  398. }
  399. if (!empty($tips)) {
  400. $output .= is_array($tips) ? theme('item_list', array('items' => $tips)) : $tips;
  401. }
  402. return $output;
  403. }