Index: views_ui.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/views_ui.module,v retrieving revision 1.109 diff -u -p -r1.109 views_ui.module --- views_ui.module 30 Jan 2009 00:56:01 -0000 1.109 +++ views_ui.module 5 Jul 2009 16:55:52 -0000 @@ -233,6 +233,10 @@ function views_ui_cache_load($name) { // Check to see if someone else is already editing this view. global $user; $view->locked = db_fetch_object(db_query("SELECT s.uid, v.updated FROM {views_object_cache} v INNER JOIN {sessions} s ON v.sid = s.sid WHERE s.sid != '%s' and v.name = '%s' and v.obj = 'view' ORDER BY v.updated ASC", session_id(), $view->name)); + // Set a flag to indicate that this view is being edited. + // This flag will be used e.g. to determine whether strings + // should be localized. + $view->editing = TRUE; } } Index: help/views.help.ini =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/help/views.help.ini,v retrieving revision 1.17 diff -u -p -r1.17 views.help.ini --- help/views.help.ini 22 Apr 2009 07:03:35 -0000 1.17 +++ help/views.help.ini 5 Jul 2009 16:55:52 -0000 @@ -159,6 +159,9 @@ parent = analyze-theme [overrides] title = What are overrides? +[localization] +title = Localizing views data like header and footer text + [embed] title = Embedding a view into other parts of your site Index: includes/admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/admin.inc,v retrieving revision 1.154.2.6 diff -u -p -r1.154.2.6 admin.inc --- includes/admin.inc 10 Jun 2009 22:02:26 -0000 1.154.2.6 +++ includes/admin.inc 5 Jul 2009 16:55:53 -0000 @@ -2741,6 +2741,14 @@ function views_ui_admin_tools() { '#default_value' => variable_get('views_no_javascript', FALSE), ); + $form['views_localization_plugin'] = array( + '#type' => 'radios', + '#title' => t('Translation method'), + '#options' => views_fetch_plugin_names('localization', NULL, array(), TRUE), + '#default_value' => variable_get('views_localization_plugin', 'core'), + '#description' => t('Select a translation method to use for Views data like header, footer, and empty text.'), + ); + $regions = system_region_list(variable_get('theme_default', 'garland')); $regions['watchdog'] = t('Watchdog'); Index: includes/base.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/base.inc,v retrieving revision 1.2.2.1 diff -u -p -r1.2.2.1 base.inc --- includes/base.inc 25 Jun 2009 22:15:36 -0000 1.2.2.1 +++ includes/base.inc 5 Jul 2009 16:55:53 -0000 @@ -19,7 +19,8 @@ class views_object { * @code * 'option_name' => array( * - 'default' => default value, - * - 'translatable' => TRUE/FALSE (wrap in t() on export if true), + * - 'translatable' => TRUE/FALSE (use a localization plugin on display and + * wrap in t() on export if true), * - 'contains' => array of items this contains, with its own defaults, etc. * If contains is set, the default will be ignored and assumed to * be array() @@ -28,8 +29,6 @@ class views_object { * @endcode * Each option may have any of the following functions: * - export_option_OPTIONNAME -- Special export handling if necessary. - * - translate_option_OPTIONNAME -- Special handling for translating data - * within the option, if necessary. */ function option_definition() { return array(); } @@ -76,7 +75,7 @@ class views_object { * Unpack options over our existing defaults, drilling down into arrays * so that defaults don't get totally blown away. */ - function unpack_options(&$storage, $options, $definition = NULL) { + function unpack_options(&$storage, $options, $definition = NULL, $localization_keys = array()) { if (!is_array($options)) { return; } @@ -85,17 +84,35 @@ class views_object { $definition = $this->option_definition(); } + // Ensure we have a localization plugin. + $this->view->init_localization(); foreach ($options as $key => $value) { + if (is_array($value)) { if (!isset($storage[$key]) || !is_array($storage[$key])) { $storage[$key] = array(); } $this->unpack_options($storage[$key], $value, isset($definition[$key]['contains']) ? $definition[$key]['contains'] : array()); + $this->unpack_options($storage[$key], $value, isset($definition[$key]['contains']) ? $definition[$key]['contains'] : array(), array_merge($localization_keys, array($key))); } - else if (!empty($definition[$key]['translatable']) && !empty($value)) { - $storage[$key] = t($value); - } + // Don't localize strings during editing. When editing, we need to work with + // the original data, not the translated version. + else if (!$this->view->editing && (!empty($definition[$key]['translatable']) || !empty($definition['contains'][$key]['translatable'])) && !empty($value)) { + // Allow other modules to make changes to the string before it's + // sent for translation. + // Look for a propertyname_format property. + $translation_data = $this->view->invoke_translation_process($value, isset($options[$key . '_format']) ? $options[$key . '_format'] : NULL, 'pre'); + if ($this->view->is_translatable()) { + // The $keys array is built from the view name, any localization keys + // sent in, and the name of the property being processed. + $storage[$key] = $this->view->localization_plugin->translate($value, array_merge(array($this->view->name), $localization_keys, array($key))); + } + // Otherwise, this is a code-based string, so we can use t(). + else { + $storage[$key] = t($value); + } + $this->view->invoke_translation_process($value, $translation_data, 'post'); else { $storage[$key] = $value; } Index: includes/plugins.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/plugins.inc,v retrieving revision 1.152.2.3 diff -u -p -r1.152.2.3 plugins.inc --- includes/plugins.inc 2 Jun 2009 20:22:24 -0000 1.152.2.3 +++ includes/plugins.inc 5 Jul 2009 16:55:53 -0000 @@ -11,7 +11,7 @@ */ function views_views_plugins() { $path = drupal_get_path('module', 'views') . '/js'; - return array( + $plugins = array( 'module' => 'views', // This just tells our themes are elsewhere. 'display' => array( 'parent' => array( @@ -267,7 +267,31 @@ function views_views_plugins() { 'help topic' => 'cache-time', ), ), + 'localization' => array( + 'parent' => array( + 'no ui' => TRUE, + 'handler' => 'views_plugin_localization', + 'parent' => '', + ), + 'none' => array( + 'title' => t('None'), + 'help' => t('Do not pass admin strings for translation.'), + 'handler' => 'views_plugin_localization_none', + 'help topic' => 'localization-none', + ), + 'core' => array( + 'title' => t('Core'), + 'help' => t("Use Drupal core t() function. Not recommended, as it doesn't support updates to existing strings."), + 'handler' => 'views_plugin_localization_core', + 'help topic' => 'localization-core', + ), + ), ); + // Add a help message pointing to the i18views module if it is not present. + if (!module_exists('i18nviews')) { + $plugins['localization']['core']['help'] .= ' ' . t('If you need to translate Views labels into other languages, consider installing the Internationalization package\'s Views translation module.', array('!path' => url('http://drupal.org/project/i18n', array('absolute' => TRUE)))); + } + return $plugins; } /** Index: includes/view.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/view.inc,v retrieving revision 1.151.2.12 diff -u -p -r1.151.2.12 view.inc --- includes/view.inc 1 Jul 2009 15:36:26 -0000 1.151.2.12 +++ includes/view.inc 5 Jul 2009 16:55:53 -0000 @@ -23,6 +23,7 @@ class view extends views_db_object { // State variables var $built = FALSE; var $executed = FALSE; + var $editing = FALSE; var $args = array(); var $build_info = array(); @@ -62,6 +63,9 @@ class view extends views_db_object { } $this->query = new stdClass(); + + // Initialize localization. + $this->init_localization(); } /** @@ -1319,6 +1323,9 @@ class view extends views_db_object { $this->_save_rows($key); } + // Save data for translation. + $this->save_locale_strings(); + cache_clear_all('views_urls', 'cache_views'); cache_clear_all(); // clear the page cache as well. } @@ -1344,6 +1351,8 @@ class view extends views_db_object { return; } + $this->delete_locale_strings(); + db_query("DELETE FROM {views_view} WHERE vid = %d", $this->vid); // Delete from all of our subtables as well. foreach ($this->db_objects() as $key) { @@ -1399,6 +1408,15 @@ class view extends views_db_object { } } + // Give the localization system a chance to export translatables to code. + if ($this->init_localization()) { + $this->export_locale_strings('export'); + $translatables = $this->localization_plugin->export_render($indent); + if (!empty($translatables)) { + $output .= $translatables; + } + } + return $output; } @@ -1521,6 +1539,137 @@ class view extends views_db_object { return $errors ? $errors : TRUE; } + + /** + * Find and initialize the localizer plugin. + */ + function init_localization() { + if (isset($this->localization_plugin) && is_object($this->localization_plugin)) { + return TRUE; + } + + $this->localization_plugin = views_get_plugin('localization', variable_get('views_localization_plugin', 'core')); + + if (empty($this->localization_plugin)) { + return FALSE; + } + + $this->localization_plugin->init($this); + + return TRUE; + } + + /** + * Determine whether a view supports admin string translation. + */ + function is_translatable() { + // If the view is normal or overridden, use admin string translation. + // A newly created view won't have a type. Accept this. + return (!isset($this->type) || in_array($this->type, array(t('Normal'), t('Overridden')))) ? TRUE : FALSE; + } + + /** + * Send strings for localization. + */ + function save_locale_strings() { + $this->process_locale_strings('save'); + } + + /** + * Delete localized strings. + */ + function delete_locale_strings() { + $this->process_locale_strings('delete'); + } + + /** + * Export localized strings. + */ + function export_locale_strings() { + $this->process_locale_strings('export'); + } + + /** + * Process strings for localization, deletion or export to code. + */ + function process_locale_strings($op) { + // Ensure this view supports translation, we have a display, and we + // have a localization plugin. + if (($this->is_translatable() || $op == 'export') && $this->init_display() && $this->init_localization()) { + foreach ($this->display as $display_id => $display) { + $translatable = array(); + // Special handling for display title. + if (isset($display->display_title)) { + $translatable[] = array($display->display_title, array('display_title')); + } + $this->unpack_translatable($translatable, $display_id, $display->display_options); + foreach ($translatable as $data) { + list($string, $keys) = $data; + switch ($op) { + case 'save': + $this->localization_plugin->save($string, array_merge(array($this->name, $display_id), $keys)); + break; + case 'delete': + $this->localization_plugin->delete($string, array_merge(array($this->name, $display_id), $keys)); + break; + case 'export': + $this->localization_plugin->export($string, array_merge(array($this->name, $display_id), $keys)); + break; + } + } + } + } + } + + /** + * Unpack translatable properties and their values. + */ + function unpack_translatable(&$translatable, $display_id, $options, $definition = NULL, $keys = array()) { + + if (!is_array($options)) { + return; + } + + // Ensure we have displays with handlers. + $this->init_display(); + + if (!isset($definition)) { + $definition = $this->display[$display_id]->handler->option_definition(); + } + + foreach ($options as $key => $value) { + if (is_array($value)) { + $this->unpack_translatable($translatable, $display_id, $value, isset($definition[$key]) ? $definition[$key] : array(), array_merge($keys, array($key))); + } + else if (!empty($definition[$key]['translatable']) && !empty($value)) { + // Allow other modules to make changes to the string before it's + // sent for translation. + // Look for a propertyname_format property. + $this->invoke_translation_process($value, isset($options[$key . '_format']) ? $options[$key . '_format'] : NULL, 'pre'); + $translatable[] = array($value, array_merge($keys, array($key))); + } + } + } + + /** + * Invoke hook_translation_pre_process() or hook_translation_post_process(). + * + * Like node_invoke_nodeapi(), this function is needed to enable both passing + * by reference and fetching return values. + */ + function invoke_translation_process(&$value, $arg, $op) { + $return = array(); + $hook = 'translation_' . $op . '_process'; + foreach (module_implements($hook) as $module) { + $function = $module . '_' . $hook; + $result = $function($value, $arg); + if (isset($result)) { + $return[$module] = $result; + } + } + return $return; + } + } /** Index: plugins/views_plugin_display.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/plugins/views_plugin_display.inc,v retrieving revision 1.20.2.6 diff -u -p -r1.20.2.6 views_plugin_display.inc --- plugins/views_plugin_display.inc 26 Jun 2009 00:23:37 -0000 1.20.2.6 +++ plugins/views_plugin_display.inc 5 Jul 2009 16:55:53 -0000 @@ -40,7 +40,9 @@ class views_plugin_display extends views unset($options['defaults']); } - $this->unpack_options($this->options, $options); + // Last argument is an array of keys to be used in identifying + // strings for translation. + $this->unpack_options($this->options, $options, NULL, array($display->id)); } function destroy() {