? views (copy).module ? views-locale-api.module ? views-localization.patch Index: views.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/views.module,v retrieving revision 1.324 diff -u -p -r1.324 views.module --- views.module 8 Jan 2009 20:01:00 -0000 1.324 +++ views.module 16 Jan 2009 19:27:48 -0000 @@ -1144,4 +1144,220 @@ function views_views_exportables($op = ' function views_microtime() { list($usec, $sec) = explode(' ', microtime()); return (float)$sec + (float)$usec; -} \ No newline at end of file +} + +/** + * Process a string for translation. + * + * @param $string + * The string to be handled. + * @param $keys + * An array of keys to identify the string. Generally constructed from + * view name, display_id, and a property, e.g., 'header'. + * @param $op + * Operation. Valid values are: + * - 'get': retrieve an existing translation + * - 'save': save a source string + * - 'delete': delete a source string (and any translations). + */ +function views_localize_string($string, $keys, $op) { + $handlers = views_localization_handlers('callbacks'); + $handler = views_localization_handler(); + $callback = $handlers[$handler][$op]; + if (empty($callback)) { + return $string; + } + elseif ($handler == 'core') { + return $callback($string); + } + else { + return $callback($string, $keys, $op); + } +} + +/** + * Load an existing translation. + * + * @param $string + * The string to be translated. + * @param $keys + * An array of keys to identify the string. Generally constructed from + * view name, display_id, and a property, e.g., 'header'. + */ +function views_get_localized_string($string, $keys) { + return views_localize_string($string, $keys, 'get'); +} + +/** + * Save a string for translation. + * + * @param $string + * The string to be saved. + * @param $keys + * An array of keys to identify the string. Generally constructed from + * view name, display_id, and a property, e.g., 'header'. + */ +function views_save_localized_string($string, $keys) { + views_localize_string($string, $keys, 'save'); +} + +/** + * Delete an existing translation. + * + * @param $string + * The string to be deleted. + * @param $keys + * An array of keys to identify the string. Generally constructed from + * view name, display_id, and a property, e.g., 'header'. + */ +function views_delete_localized_string($string, $keys) { + views_localize_string($string, $keys, 'delete'); +} + +/** + * Return an array of available handlers for localization. + * + * Modules may implement hook_localization_handlers_alter() to alter the + * available handlers. + * + * @param $key + * A key to use for the returned values. Valid keys are 'title' for + * handler titles and 'callbacks' for handler callback functions. + * If null, a nested array with these two keys is returned. + * + * @return + * An array of handlers with the following keys: + * - 'title': the title of the handler, suitable for display in a form radios + * element. + * - 'callbacks': an array of function name callbacks with the following keys: + * - 'get': retrieve an existing translation + * - 'save': save a string for translation + * - 'delete': delete a source string (and any translations) + */ +function views_localization_handlers($key = NULL) { + static $handlers = array(); + + if (empty($handlers)) { + $handlers['none'] = array( + 'title' => t('None: do not pass admin strings for translation.'), + 'callbacks' => array( + // A FALSE callback will be skipped and the original string returned + // unmodified. + 'get' => FALSE, + 'save' => FALSE, + 'delete' => FALSE, + ), + ); + $handlers['core'] = array( + 'title' => t("Drupal core t() function: not recommended, as it doesn't support updates to existing strings."), + 'callback' => array( + 'get' => 't', + 'save' => 't', + // Delete not supported. + 'delete' => FALSE, + ), + ); + if (module_exists('i18nstrings')) { + $handlers['i18nstrings'] = array( + 'title' => t('String translation: Use the string translation module. Strings will be added to the Views group in the Translate interface page.', array('!path' => url('admin/build/translate/search'))), + 'callbacks' => array( + 'get' => 'views_localization_tt', + 'save' => 'views_localization_tt', + 'delete' => 'views_localization_remove_string', + ), + ); + } + else { + $handlers['core']['title'] .= ' ' . t('If you need to translate Views labels into other languages, consider installing the Internationalization package\'s Strings translation module.', array('!path' => url('http://drupal.org/project/i18n', array('absolute' => TRUE)))); + } + + // Allow other modules to add or change handlers. + drupal_alter('localization_handlers', $handlers); + } + + if ($key) { + $return = array(); + foreach ($handlers as $handler => $data) { + $return[$handler] = $data[$key]; + } + return $return; + } + return $handlers; +} + +/** + * Localization callback. Wrapper for i18nstrings tt() function. + * + * @param $string + * The string to be translated. + * @param $keys + * An array of keys to identify the string. Generally constructed from + * view name, display_id, and a property, e.g., 'header'. + * @param $update + * Boolean, whether to register an update. + */ +function views_localization_tt($string, $keys, $op) { + // Prevent an error if i18nstrings has been disabled. + if (!module_exists('i18nstrings')) { + return $string; + } + $location = implode(':', $keys); + switch ($op) { + case 'save': + tt('views:' . $location, $string, NULL, TRUE); + return; + case 'get': + return tt('views:' . $location, $string); + } +} + +/** + * Localization callback. Wrapper for i18nstrings i18nstrings_remove_string() + * function. + * + * @param $string + * The string to be translated. + * @param $keys + * An array of keys to identify the string. Generally constructed from + * view name, display_id, and a property, e.g., 'header'. + * @param $update + * Boolean, whether to register an update. + */ +function views_localization_remove_string($string, $keys) { + // Prevent an error if i18nstrings has been disabled. + if (!module_exists('i18nstrings')) { + return; + } + $location = implode(':', $keys); + return i18nstrings_remove_string('views:' . $location, $string, NULL, $update); +} + +/** + * Helper function to return the localization handler for Views. + * + * @return + * String, the localization handler to be used by Views. + * + * @todo: switch default to 'none'? Not done yet to avoid breaking existing + * translations in Views 2 6.x installs. + */ +function views_localization_handler() { + // Default to i18nstrings if it exists, otherwise to 'core'. + return variable_get('views_localization_handler', module_exists('i18nstrings') ? 'i18nstrings' : 'core'); +} + +/** + * Implementation of hook_locale(). + * + * @todo: implement i18nstrings 'refresh' op? + */ +function views_locale($op = 'groups') { + // i18nstrings uses the locale system. + if (views_localization_handler() == 'i18nstrings') { + switch ($op) { + case 'groups': + return array('views' => t('Views')); + } + } +} + Index: views_ui.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/views_ui.module,v retrieving revision 1.108 diff -u -p -r1.108 views_ui.module --- views_ui.module 3 Dec 2008 02:39:45 -0000 1.108 +++ views_ui.module 16 Jan 2009 19:27:49 -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: includes/admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/admin.inc,v retrieving revision 1.150 diff -u -p -r1.150 admin.inc --- includes/admin.inc 7 Jan 2009 21:52:00 -0000 1.150 +++ includes/admin.inc 16 Jan 2009 19:27:56 -0000 @@ -2725,6 +2725,16 @@ function views_ui_admin_tools() { '#default_value' => variable_get('views_no_javascript', FALSE), ); + if (module_exists('locale')) { + $form['views_localization_handler'] = array( + '#type' => 'radios', + '#title' => t('Localization handler'), + '#description' => t("Select a handler for translation of Views data like header, footer, and empty text."), + '#options' => views_localization_handlers('title'), + '#default_value' => views_localization_handler(), + ); + } + $regions = system_region_list(variable_get('theme_default', 'garland')); $form['views_devel_region'] = array( Index: includes/base.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/base.inc,v retrieving revision 1.2 diff -u -p -r1.2 base.inc --- includes/base.inc 6 Jun 2008 19:29:03 -0000 1.2 +++ includes/base.inc 16 Jan 2009 19:27:56 -0000 @@ -76,7 +76,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; } @@ -93,8 +93,19 @@ class views_object { $this->unpack_options($storage[$key], $value, isset($definition[$key]) ? $definition[$key] : array()); } - 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($value)) { + // If the view is normal or overridden, use admin string translation. + if (isset($this->view->type) && in_array($this->view->type, array('Normal', 'Overridden'))) { + // The $keys array is built from the view name, any localization keys + // sent in, and the name of the property being processed. + $storage[$key] = views_get_localized_string($value, array_merge(array($this->view->name), $localization_keys, array($key))); + } + // Otherwise, use t(). + else { + $storage[$key] = t($value); + } } else { $storage[$key] = $value; Index: includes/view.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/view.inc,v retrieving revision 1.146 diff -u -p -r1.146 view.inc --- includes/view.inc 8 Jan 2009 00:29:54 -0000 1.146 +++ includes/view.inc 16 Jan 2009 19:28:00 -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(); @@ -1303,6 +1304,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. } @@ -1328,6 +1332,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) { @@ -1504,6 +1510,76 @@ class view extends views_db_object { return $errors ? $errors : TRUE; } + + /** + * 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'); + } + + /** + * Process strings for localization or deletion. + */ + function process_locale_strings($op) { + if ($this->display && is_array($this->display)) { + foreach ($this->display as $display_id => $display) { + $translatable = array(); + // Special handling for display title. + if (isset($display->display_title)) { + $translatable['display_title'] = $display->display_title; + } + $this->unpack_translatable($translatable, $display_id, $display->display_options); + foreach ($translatable as $property => $string) { + switch ($op) { + case 'delete': + views_delete_localized_string($string, array($this->name, $display_id, $property)); + break; + case 'save': + views_save_localized_string($string, array($this->name, $display_id, $property)); + break; + } + } + } + } + } + + /** + * Unpack translatable properties and their values. + */ + function unpack_translatable(&$translatable, $display_id, $options, $definition = NULL) { + + 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()); + } + else if (!empty($definition[$key]['translatable']) && !empty($value)) { + // If the view is normal or overridden, use admin string translation. + // Otherwise t() will handle it. + if (isset($this->type) && in_array($this->type, array('Normal', 'Overridden'))) { + $translatable[$key] = $value; + } + } + } + } } /** Index: plugins/views_plugin_display.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/plugins/views_plugin_display.inc,v retrieving revision 1.18 diff -u -p -r1.18 views_plugin_display.inc --- plugins/views_plugin_display.inc 7 Jan 2009 23:31:13 -0000 1.18 +++ plugins/views_plugin_display.inc 16 Jan 2009 19:28:05 -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() {