diff --git a/components/date.inc b/components/date.inc index dfa09a5..36dba4b 100644 --- a/components/date.inc +++ b/components/date.inc @@ -130,6 +130,7 @@ function _webform_render_date($component, $value = NULL, $filter = TRUE) { '#theme_wrappers' => array('webform_element'), '#element_validate' => array('webform_validate_date'), '#webform_component' => $component, + '#translatable' => array('title', 'description'), ); if ($component['extra']['datepicker']) { diff --git a/components/email.inc b/components/email.inc index c573ad9..f65dd0b 100644 --- a/components/email.inc +++ b/components/email.inc @@ -118,6 +118,7 @@ function _webform_render_email($component, $value = NULL, $filter = TRUE) { '#element_validate' => array('_webform_validate_email'), '#theme_wrappers' => array('webform_element'), '#webform_component' => $component, + '#translatable' => array('title', 'description'), ); // Add an e-mail class for identifying the difference from normal textfields. diff --git a/components/fieldset.inc b/components/fieldset.inc index 25b051e..82d22d0 100644 --- a/components/fieldset.inc +++ b/components/fieldset.inc @@ -61,6 +61,7 @@ function _webform_render_fieldset($component, $value = NULL, $filter = TRUE) { '#attributes' => array('class' => array('webform-component-' . $component['type']), 'id' => 'webform-component-' . $component['form_key']), '#pre_render' => array('webform_fieldset_prerender'), '#webform_component' => $component, + '#translatable' => array('title', 'description'), ); return $element; diff --git a/components/file.inc b/components/file.inc index 652f0a9..6cdee1d 100644 --- a/components/file.inc +++ b/components/file.inc @@ -315,6 +315,7 @@ function _webform_render_file($component, $value = NULL, $filter = TRUE) { ), '#webform_component' => $component, '#theme_wrappers' => array(), + '#translatable' => array('title', 'description'), ); $element['#webform_component'] = $component; $element['#weight'] = $component['weight']; diff --git a/components/grid.inc b/components/grid.inc index 52cbb91..dd98706 100644 --- a/components/grid.inc +++ b/components/grid.inc @@ -148,6 +148,7 @@ function _webform_render_grid($component, $value = NULL, $filter = TRUE) { '#theme' => 'webform_grid', '#theme_wrappers' => array('webform_element'), '#webform_component' => $component, + '#translatable' => array('title', 'description', 'options'), ); $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE); @@ -160,6 +161,8 @@ function _webform_render_grid($component, $value = NULL, $filter = TRUE) { if ($component['extra']['qrand']) { _webform_shuffle_options($questions); } + + $element['#options'] = $options; foreach ($questions as $key => $question) { if ($question != '') { @@ -173,6 +176,8 @@ function _webform_render_grid($component, $value = NULL, $filter = TRUE) { // Webform handles validation manually. '#validated' => TRUE, '#webform_validated' => FALSE, + + '#translatable' => array('title'), ); } } @@ -207,7 +212,7 @@ function _webform_display_grid($component, $value, $format = 'html') { foreach ($questions as $key => $question) { if ($question !== '') { - $element[$question] = array( + $element[$key] = array( '#title' => $question, '#value' => isset($value[$key]) ? $value[$key] : NULL, ); @@ -377,18 +382,13 @@ function theme_webform_grid($variables) { $rows = array(); $header = array(array('data' => '', 'class' => array('webform-grid-question'))); - $first = TRUE; + // Set the header for the table. + foreach ($element['#options'] as $option) { + $header[] = array('data' => _webform_filter_xss($option), 'class' => array('checkbox', 'webform-grid-option')); + } foreach (element_children($element) as $key) { $question_element = $element[$key]; - // Set the header for the table. - if ($first) { - foreach ($question_element['#options'] as $option) { - $header[] = array('data' => _webform_filter_xss($option), 'class' => array('checkbox', 'webform-grid-option')); - } - $first = FALSE; - } - // Create a row with the question title. $row = array(array('data' => _webform_filter_xss($question_element['#title']), 'class' => array('webform-grid-question'))); diff --git a/components/hidden.inc b/components/hidden.inc index 343a21a..e71f755 100644 --- a/components/hidden.inc +++ b/components/hidden.inc @@ -60,6 +60,7 @@ function _webform_render_hidden($component, $value = NULL, $filter = TRUE) { '#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'], '#default_value' => $filter ? _webform_filter_values($component['value']) : $component['value'], '#weight' => $component['weight'], + '#translatable' => array('title'), ); if (isset($value[0])) { diff --git a/components/markup.inc b/components/markup.inc index dcb41ab..0800c54 100644 --- a/components/markup.inc +++ b/components/markup.inc @@ -65,6 +65,7 @@ function _webform_render_markup($component, $value = NULL, $filter = TRUE) { '#input_format' => $component['extra']['format'], '#theme_wrappers' => array('webform_element'), '#webform_component' => $component, + '#translatable' => array('title'), ); // TODO: Remove when #markup becomes available in D7. diff --git a/components/pagebreak.inc b/components/pagebreak.inc index d3c1d4c..6ff2bd6 100644 --- a/components/pagebreak.inc +++ b/components/pagebreak.inc @@ -69,6 +69,7 @@ function _webform_display_pagebreak($component, $value = NULL, $format = 'html') '#weight' => $component['weight'], '#format' => $format, '#webform_component' => $component, + '#translatable' => array('title'), ); return $element; } diff --git a/components/select.inc b/components/select.inc index a350c7b..224af6e 100644 --- a/components/select.inc +++ b/components/select.inc @@ -269,6 +269,7 @@ function _webform_render_select($component, $value = NULL, $filter = TRUE) { '#theme_wrappers' => array('webform_element'), '#pre_render' => array(), // Needed to disable double-wrapping of radios and checkboxes. '#webform_component' => $component, + '#translatable' => array('title', 'description', 'options'), ); // Convert the user-entered options list into an array. @@ -418,6 +419,7 @@ function _webform_display_select($component, $value, $format = 'html') { return array( '#title' => $component['name'], '#weight' => $component['weight'], + '#options' => _webform_select_options($component), '#theme' => 'webform_display_select', '#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'), '#format' => $format, @@ -482,7 +484,7 @@ function theme_webform_display_select($variables) { $component = $element['#webform_component']; // Convert submitted 'safe' values to un-edited, original form. - $options = _webform_select_options($component); + $options = $element['#options']; $items = array(); if ($component['extra']['multiple']) { diff --git a/components/textarea.inc b/components/textarea.inc index 4151137..7b184ab 100644 --- a/components/textarea.inc +++ b/components/textarea.inc @@ -110,6 +110,7 @@ function _webform_render_textarea($component, $value = NULL, $filter = TRUE) { '#resizable' => (bool) $component['extra']['resizable'], // MUST be FALSE to disable. '#theme_wrappers' => array('webform_element'), '#webform_component' => $component, + '#translatable' => array('title', 'description'), ); if ($component['extra']['disabled']) { diff --git a/components/textfield.inc b/components/textfield.inc index 8f24688..67cf159 100644 --- a/components/textfield.inc +++ b/components/textfield.inc @@ -133,6 +133,7 @@ function _webform_render_textfield($component, $value = NULL, $filter = TRUE) { '#attributes' => $component['extra']['attributes'], '#theme_wrappers' => array('webform_element'), '#webform_component' => $component, + '#translatable' => array('title', 'description'), ); if ($component['extra']['disabled']) { diff --git a/components/time.inc b/components/time.inc index 82f95d0..639bd33 100644 --- a/components/time.inc +++ b/components/time.inc @@ -93,6 +93,7 @@ function _webform_render_time($component, $value = NULL, $filter = TRUE) { '#theme' => 'webform_time', '#theme_wrappers' => array('webform_element'), '#webform_component' => $component, + '#translatable' => array('title', 'description'), ); if (drupal_strlen($component['value']) > 0) { diff --git a/includes/webform.components.inc b/includes/webform.components.inc index 2ed9f48..d4c0657 100644 --- a/includes/webform.components.inc +++ b/includes/webform.components.inc @@ -703,6 +703,9 @@ function webform_component_insert(&$component) { )) ->execute(); + // Create translation source for all the translatable poperties. + webform_component_update_translation_strings($component); + // Post-insert actions. module_invoke_all('webform_component_insert', $component); @@ -723,6 +726,9 @@ function webform_component_update($component) { $function($component); } + // Update / create translation source for all the translatable poperties. + webform_component_update_translation_strings($component); + $component['value'] = isset($component['value']) ? $component['value'] : NULL; $component['mandatory'] = isset($component['mandatory']) ? $component['mandatory'] : 0; db_update('webform_component') @@ -781,6 +787,9 @@ function webform_component_delete($node, $component) { webform_component_delete($node, $child_component); } + // Remove translation source for all the translatable poperties. + webform_component_delete_translation_strings($component); + // Post-delete actions. module_invoke_all('webform_component_delete', $component); } @@ -1023,3 +1032,96 @@ function webform_validate_unique($element, $form_state) { } } } + +/** + * Update / create translation source for all the translatable poperties. + * + * @param array $component + */ +function webform_component_update_translation_strings(&$component) { + if (module_exists('i18n_string')) { + // Fill in the the default values for the missing properties. + webform_component_defaults($component); + // Render the 'render' FAPI array for the component. + $element = webform_component_invoke($component['type'], 'render', $component, NULL, 'html'); + // Parse the renderable array to find the translatable properties and + // update / create translation source for them + $component['extra']['translated_strings'] = _webform_component_translation_parse($element, $component); + // Render the 'display' FAPI array for the component. + $element = webform_component_invoke($component['type'], 'display', $component, NULL, 'html'); + // Parse the renderable array to find the translatable properties and + // update / create translation source for them + $component['extra']['translated_strings'] = array_merge($component['extra']['translated_strings'], _webform_component_translation_parse($element, $component)); + } +} + +/** + * Remove translation source for all the translatable poperties. + * + * @param array $component + */ +function webform_component_delete_translation_strings($component) { + if (module_exists('i18n_string')) { + if (isset($component['extra']['translated_strings'])) { + foreach ($component['extra']['translated_strings'] as $name) { + i18n_string_remove($name); + } + } + } +} + +/** + * Parse a component renderable array to find the translatable properties and + * create / update or remove translation source for them. + * + * @param array $element + * The renderable array to be parsed. + * @param array $component + * The component which was rendered. + */ +function _webform_component_translation_parse($element, $component) { + $translated_properies = array(); + + if (!isset($element['#parents'])) { + $element['#parents'] = array(); + } + + if (isset($element['#translatable']) && is_array($element['#translatable'])) { + foreach ($element['#translatable'] as $key) { + if (isset($element['#' . $key]) && $element['#' . $key] != '') { + if (isset($element['#parents']) && count($element['#parents'])) { + $property = '[' . implode('][', $element['#parents']) . ']#' . $key; + } + else { + $property = '#' . $key; + } + if (is_array($element['#' . $key])) { + // If the translatable property is an array, we translate the children. + foreach ($element['#' . $key] as $elem_key => $elem_value) { + $name = implode(':', array('webform', $component['nid'], $component['cid'], $property . '-' . $elem_key)); + $translated_properies[] = $name; + webform_tt($name, $elem_value, NULL, TRUE); + } + } + else { + // If the translatable property is not an array, it can be treated as + // a string. + $name = implode(':', array('webform', $component['nid'], $component['cid'], $property)); + $translated_properies[] = $name; + webform_tt($name, $element['#' . $key], NULL, TRUE); + } + } + } + } + + // Recursevly call the function on the children, after adding the children + // name to its #parents array. + foreach (element_children($element) as $child) { + $element[$child]['#parents'] = $element['#parents']; + $element[$child]['#parents'][] = $child; + // Add the translated propertied to the list. + $translated_properies = array_merge($translated_properies, _webform_component_translation_parse($element[$child], $component)); + } + + return $translated_properies; +} diff --git a/webform.module b/webform.module index 612adb0..274342d 100644 --- a/webform.module +++ b/webform.module @@ -673,6 +673,44 @@ function webform_theme() { } /** + * Implements hook_i18n_string_info(). + */ +function webform_i18n_string_info() { + $groups['webform'] = array( + 'title' => t('Webform'), + 'description' => t('Localizable properties of webforms, like title, select options, and others.'), + 'format' => FALSE, // This group doesn't have strings with format + 'list' => FALSE, // This group cannot list all strings + //'refresh callback' => 'webform_i18n_string_refresh', + ); + return $groups; +} + +/** + * Update / create translation source for all the translatable poperties of + * all the components + */ +function webform_i18n_string_refresh() { + $components = db_select('webform_component', 'wc') + ->fields('wc') + ->execute() + ->fetchAllAssoc('cid'); + + foreach ($components as $component) { + $component = (array) $component; + $component['extra'] = unserialize($component['extra']); + + module_load_include('inc', 'webform', 'includes/webform.components'); + webform_component_update_translation_strings($component); + + $component['extra'] = serialize($component['extra']); + drupal_write_record('webform_component', $component, array('nid', 'cid')); + } + + return TRUE; +} + +/** * Implementation of hook_webform_component_info(). */ function webform_webform_component_info() { @@ -1886,6 +1924,8 @@ function _webform_client_form_add_component($node, $component, $component_value, // This component is display only. $data = empty($submission->data[$cid]['value']) ? NULL : $submission->data[$cid]['value']; if ($display_element = webform_component_invoke($component['type'], 'display', $component, $data, $format)) { + // Translate the translatable properties. + _webform_translate_component($display_element, $component); // The form_builder() function usually adds #parents and #id for us, but // because these are not marked for #input, we need to add them manually. if (!isset($display_element['#parents'])) { @@ -1903,6 +1943,8 @@ function _webform_client_form_add_component($node, $component, $component_value, // Add this user-defined field to the form (with all the values that are always available). $data = isset($submission->data[$cid]['value']) ? $submission->data[$cid]['value'] : NULL; if ($element = webform_component_invoke($component['type'], 'render', $component, $data, $filter)) { + // Translate the translatable properties. + _webform_translate_component($element, $component); $parent_fieldset[$component['form_key']] = $element; // Override the value if one already exists in the form state. @@ -1939,6 +1981,59 @@ function _webform_client_form_add_component($node, $component, $component_value, } } +/** + * Translates the component properties that are translatable. These are found + * in under 'translated_strigs' in the 'extra' array of the component, which + * is build when the component is inserted / updated, or when all webform + * strings are updated from admin/config/regional/translate/i18n_string. + * @param array $element + * The FAPI renderable array of the component instance. + * @param array $component + * The component. + */ +function _webform_translate_component(&$element, $component) { + if (isset($component['extra']['translated_strings']) && is_array($component['extra']['translated_strings'])) { + foreach ($component['extra']['translated_strings'] as $name) { + $name_list = explode(':', $name); + $current_element = &$element; + if (strpos($name_list[3], '[') !== FALSE) { + // The property is deeper in the renderable array, we must extract the + // the place where it is. + list ($children, $property) = explode(']#', $name_list[3]); + // Remove the '[' from the begining of the string. + $children = drupal_substr($children, 1); + $children_array = explode('][', $children); + foreach ($children_array as $child) { + if (isset($current_element[$child])) { + $current_element = &$current_element[$child]; + } + else { + continue; + } + } + } + else { + // Remove the '#' from the begining of the property, for consistency. + $property = drupal_substr($name_list[3], 1); + } + + if (strpos($property, '-') !== FALSE) { + // If property is array, we extract the key from the property. + list ($property, $key) = explode('-', $property); + if (isset($current_element['#' . $property][$key])) { + $current_element['#' . $property][$key] = webform_tt($name, $current_element['#' . $property][$key]); + } + } + else { + // Else we can treat the property as string. + if (isset($current_element['#' . $property])) { + $current_element['#' . $property] = webform_tt($name, $current_element['#' . $property]); + } + } + } + } +} + function webform_client_form_validate($form, &$form_state) { $node = node_load($form_state['values']['details']['nid']); $finished = $form_state['values']['details']['finished']; @@ -3372,11 +3467,15 @@ function webform_strtotime($date) { } /** - * Wrapper function for tt() if i18nstrings enabled. + * Wrapper function for i18n_string() if i18nstrings enabled. */ function webform_tt($name, $string, $langcode = NULL, $update = FALSE) { - if (function_exists('tt')) { - return tt($name, $string, $langcode, $update); + if (function_exists('i18n_string')) { + $options = array( + 'langcode' => $langcode, + 'update' => $update, + ); + return i18n_string($name, $string, $options); } else { return $string;