diff --git a/includes/rules.core.inc b/includes/rules.core.inc
index 03ef537..aaf764b 100644
--- a/includes/rules.core.inc
+++ b/includes/rules.core.inc
@@ -460,6 +460,23 @@ abstract class RulesPlugin extends RulesExtendable {
   }
 
   /**
+   * Iterate over all elements nested below the current element.
+   *
+   * This helper can be used to recursively iterate over all elements of a
+   * configuration. To iterate over the children only, just regulary iterate
+   * over the object.
+   *
+   * @param $mode
+   *   (optional) The iteration mode used. See
+   *   RecursiveIteratorIterator::construct(). Defaults to SELF_FIRST.
+   *
+   * @return RecursiveIteratorIterator
+   */
+  public function elements($mode = RecursiveIteratorIterator::SELF_FIRST) {
+    return new RecursiveIteratorIterator($this, $mode);
+  }
+
+  /**
    * Do a deep clone.
    */
   public function __clone() {
@@ -822,12 +839,34 @@ abstract class RulesPlugin extends RulesExtendable {
    * Returns the argument as configured in the element settings for the
    * parameter $name described with $info.
    *
+   * @param $name
+   *   The name of the parameter for which to get the argument.
+   * @param $info
+   *   Info about the parameter.
+   * @param RulesState $state
+   *   The current evaluation state.
+   * @param $langcode
+   *   (optional) The language code used to get the argument value if the
+   *   argument value should be translated. By default (NULL) the current
+   *   interface language will be used.
+   *
+   * @return
+   *   The argument, possibly wrapped.
+   *
    * @throws RulesEvaluationException
    *   In case the argument cannot be retrieved an exception is thrown.
    */
-  protected function getArgument($name, $info, RulesState $state) {
+  protected function getArgument($name, $info, RulesState $state, $langcode = NULL) {
+    // Only apply the langcode if the parameter has been marked translatable.
+    if (empty($info['translatable'])) {
+      $langcode = LANGUAGE_NONE;
+    }
+    elseif (!isset($langcode)) {
+      $langcode = $GLOBALS['language']->language;
+    }
+
     if (!empty($this->settings[$name . ':select'])) {
-      $arg = $state->applyDataSelector($this->settings[$name . ':select']);
+      $arg = $state->applyDataSelector($this->settings[$name . ':select'], $langcode);
     }
     elseif (isset($this->settings[$name])) {
       $arg = rules_wrap_data($this->settings[$name], $info);
@@ -854,6 +893,10 @@ abstract class RulesPlugin extends RulesExtendable {
     if (!empty($this->settings[$name . ':process'])) {
       // For processing, make sure the data is unwrapped now.
       $return = rules_unwrap_data(array($arg), array($info));
+      // @todo for Drupal 8: Refactor to add the name and language code as
+      // separate parameter to process().
+      $info['#name'] = $name;
+      $info['#langcode'] = $langcode;
       return isset($return[0]) ? $this->settings[$name . ':process']->process($return[0], $info, $state, $this) : NULL;
     }
     // Support passing already sanitized values.
@@ -870,16 +913,24 @@ abstract class RulesPlugin extends RulesExtendable {
    *   If case an argument cannot be retrieved an exception is thrown.
    */
   protected function getExecutionArguments(RulesState $state) {
-    $param_info = $this->pluginParameterInfo();
-    foreach ($param_info as $name => $info) {
-      $args[$name] = $this->getArgument($name, $info, $state);
+    $parameters = $this->pluginParameterInfo();
+    // If there is language parameter, get its value first so it can be used
+    // for getting other translatable values.
+    $langcode = NULL;
+    if (isset($parameters['language'])) {
+      $lang_arg = $this->getArgument('language', $parameters['language'], $state);
+      $langcode = $lang_arg instanceof EntityMetadataWrapper ? $lang_arg->value() : $lang_arg;
+    }
+    // Now get all arguments.
+    foreach ($parameters as $name => $info) {
+      $args[$name] = $name == 'language' ? $lang_arg : $this->getArgument($name, $info, $state, $langcode);
     }
     // Append the settings and the execution state. Faces will append $this.
     $args['settings'] = $this->settings;
     $args['state'] = $state;
     // Make the wrapped variables for the arguments available in the state.
     $state->currentArguments = $args;
-    return rules_unwrap_data($args, $param_info);
+    return rules_unwrap_data($args, $parameters);
   }
 
   /**
diff --git a/includes/rules.processor.inc b/includes/rules.processor.inc
index d3a285c..468bea0 100644
--- a/includes/rules.processor.inc
+++ b/includes/rules.processor.inc
@@ -235,31 +235,37 @@ abstract class RulesDataInputEvaluator extends RulesDataProcessor {
   protected function __construct($setting, $param_info, $var_info = array(), $processor = NULL) {
     $this->setting = TRUE;
     $this->processor = $processor;
-    $this->prepare($setting, $var_info);
+    $this->prepare($setting, $var_info, $param_info);
   }
 
   /**
    * Overridden to generate evaluator $options and invoke evaluate().
    */
   public function process($value, $info, RulesState $state, RulesPlugin $element, $options = NULL) {
-    if (!isset($options)) {
-      $cache = rules_get_cache();
-      $languages = language_list();
-      $info += array(
-        'cleaning callback' => isset($cache['data info'][$info['type']]['cleaning callback']) ? $cache['data info'][$info['type']]['cleaning callback'] : FALSE,
-        'sanitize' => FALSE,
-      );
-      $options = array_filter(array(
-        'language' => isset($element->settings['language']) && isset($languages[$element->settings['language']]) ? $languages[$element->settings['language']] : NULL,
-        'callback' => $info['cleaning callback'],
-        'sanitize' => $info['sanitize'],
-      ));
-    }
+    $options = isset($options) ? $options : $this->getEvaluatorOptions($info, $state, $element);
     $value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element, $options) : $value;
     return $this->evaluate($value, $options, $state);
   }
 
   /**
+   * Generates the evaluator $options.
+   */
+  protected function getEvaluatorOptions($info, $state, $element) {
+    $cache = rules_get_cache();
+    $languages = language_list();
+    $info += array(
+      'cleaning callback' => isset($cache['data info'][$info['type']]['cleaning callback']) ? $cache['data info'][$info['type']]['cleaning callback'] : FALSE,
+      'sanitize' => FALSE,
+    );
+    $options = array_filter(array(
+      'language' => $info['#langcode'] != LANGUAGE_NONE && isset($languages[$info['#langcode']]) ? $languages[$info['#langcode']] : NULL,
+      'callback' => $info['cleaning callback'],
+      'sanitize' => $info['sanitize'],
+    ));
+    return $options;
+  }
+
+  /**
    * Overriden to prepare input evaluator processors. The setting is expected
    * to be the input value to be evaluated later on and is replaced by the
    * suiting processor.
@@ -282,7 +288,7 @@ abstract class RulesDataInputEvaluator extends RulesDataProcessor {
    */
   public static function attachForm(&$form, $settings, $param_info, $var_info, $access_check = TRUE) {
     foreach (self::evaluators($param_info, $access_check) as $name => $info) {
-      $form['help'][$name] = call_user_func(array($info['class'], 'help'), $var_info);
+      $form['help'][$name] = call_user_func(array($info['class'], 'help'), $var_info, $param_info);
       $form['help'][$name]['#weight'] = $info['weight'];
     }
   }
@@ -311,6 +317,9 @@ abstract class RulesDataInputEvaluator extends RulesDataProcessor {
    *   The text to evaluate later on.
    * @param $variables
    *   An array of info about available variables.
+   * @param $param_info
+   *   (optional) An array of information about the handled parameter value.
+   *   For backward compatibility, this parameter is not required.
    */
   abstract public function prepare($text, $variables);
 
@@ -330,6 +339,7 @@ abstract class RulesDataInputEvaluator extends RulesDataProcessor {
    *     should be sanitized.
    * @param RulesState
    *   The rules evaluation state.
+   *
    * @return
    *   The evaluated text.
    */
@@ -340,6 +350,10 @@ abstract class RulesDataInputEvaluator extends RulesDataProcessor {
    *
    * @param $variables
    *   An array of info about available variables.
+   * @param $param_info
+   *   (optional) An array of information about the handled parameter value.
+   *   For backward compatibility, this parameter is not required.
+   *
    * @return
    *   A renderable array.
    */
diff --git a/includes/rules.state.inc b/includes/rules.state.inc
index 2e716b2..ac537e4 100644
--- a/includes/rules.state.inc
+++ b/includes/rules.state.inc
@@ -269,6 +269,9 @@ class RulesState {
    *
    * @param $selector
    *   The selector string, e.g. "node:author:mail".
+   * @param $langcode
+   *   (optional) The language code used to get the argument value if the
+   *   argument value should be translated. Defaults to LANGUAGE_NONE.
    *
    * @return EntityMetadataWrapper
    *   The wrapper for the given selector.
@@ -276,7 +279,7 @@ class RulesState {
    * @throws RulesEvaluationException
    *   Throws a RulesEvaluationException in case the selector cannot be applied.
    */
-  public function applyDataSelector($selector) {
+  public function applyDataSelector($selector, $langcode = LANGUAGE_NONE) {
     $parts = explode(':', str_replace('-', '_', $selector), 2);
     $wrapper = $this->get($parts[0]);
     if (count($parts) == 1) {
@@ -288,7 +291,13 @@ class RulesState {
     try {
       foreach (explode(':', $parts[1]) as $name) {
         if ($wrapper instanceof EntityListWrapper || $wrapper instanceof EntityStructureWrapper) {
+
+          // Apply the language if given.
+          if ($langcode != LANGUAGE_NONE && $wrapper instanceof EntityStructureWrapper) {
+            $wrapper->language($langcode);
+          }
           $wrapper = $wrapper->get($name);
+          // @todo: revert wrapper language.
         }
         else {
           throw new RulesEvaluationException('Unable to apply data selector %selector. The specified variable is not a list or a structure: %wrapper.', array('%selector' => $selector, '%wrapper' => $wrapper));
diff --git a/modules/data.rules.inc b/modules/data.rules.inc
index 5880208..f97b9a0 100644
--- a/modules/data.rules.inc
+++ b/modules/data.rules.inc
@@ -222,6 +222,7 @@ function rules_action_data_set_form_alter(&$form, &$form_state, $options, RulesA
   if (!empty($options['init']) && !isset($form_state['rules_element_step'])) {
     $form['negate']['#access'] = FALSE;
     unset($form['parameter']['value']);
+    unset($form['parameter']['language']);
     $form['submit'] = array(
       '#type' => 'submit',
       '#value' => t('Continue'),
diff --git a/modules/events.inc b/modules/events.inc
index d37f9ee..a0e3e23 100644
--- a/modules/events.inc
+++ b/modules/events.inc
@@ -165,4 +165,4 @@ function rules_get_entity_view_modes($name, $var_info) {
 
 /**
  * @}
- */
\ No newline at end of file
+ */
diff --git a/modules/system.eval.inc b/modules/system.eval.inc
index 32a4a88..70a5f51 100644
--- a/modules/system.eval.inc
+++ b/modules/system.eval.inc
@@ -75,18 +75,21 @@ function rules_action_breadcrumb_set(array $titles, array $paths) {
 /**
  * Action Implementation: Send mail.
  */
-function rules_action_mail($to, $subject, $message, $from = NULL, $settings, RulesState $state, RulesPlugin $element) {
+function rules_action_mail($to, $subject, $message, $from = NULL, $langcode, $settings, RulesState $state, RulesPlugin $element) {
   $to = str_replace(array("\r", "\n"), '', $to);
   $from = !empty($from) ? str_replace(array("\r", "\n"), '', $from) : NULL;
   $params = array(
     'subject' => $subject,
     'message' => $message,
+    'langcode' => $langcode,
   );
   // Set a unique key for this mail.
   $name = isset($element->root()->name) ? $element->root()->name : 'unnamed';
   $key = 'rules_action_mail_' . $name . '_' . $element->elementId();
+  $languages = language_list();
+  $language = $langcode == LANGUAGE_NONE ? language_default() : $languages[$langcode];
 
-  $message = drupal_mail('rules', $key, $to, language_default(), $params, $from);
+  $message = drupal_mail('rules', $key, $to, $language, $params, $from);
   if ($message['result']) {
     watchdog('rules', 'Successfully sent email to %recipient', array('%recipient' => $to));
   }
@@ -112,12 +115,10 @@ function rules_action_mail_to_users_of_role($roles, $subject, $message, $from =
   $params = array(
     'subject' => $subject,
     'message' => $message,
-    'action' => $element,
-    'state' => $state,
   );
   // Set a unique key for this mail.
   $name = isset($element->root()->name) ? $element->root()->name : 'unnamed';
-  $key = 'rules_action_mail_to_users_of_role_' . $name . '_' . $element->elementId();
+  $key = 'rules_action_mail_to_users_of_role_' . $name . '_' . $element->elementId();  $languages = language_list();
 
   $message = array('result' => TRUE);
   foreach ($result as $row) {
diff --git a/modules/system.rules.inc b/modules/system.rules.inc
index f9cf195..fcd4f80 100644
--- a/modules/system.rules.inc
+++ b/modules/system.rules.inc
@@ -90,7 +90,6 @@ function _rules_system_watchdog_log_entry_info() {
   );
 }
 
-
 /**
  * Implements hook_rules_action_info() on behalf of the system module.
  */
@@ -104,6 +103,7 @@ function rules_system_action_info() {
           'type' => 'text',
           'label' => t('Message'),
           'sanitize' => TRUE,
+          'translatable' => TRUE,
         ),
         'type' => array(
           'type' => 'token',
@@ -161,6 +161,7 @@ function rules_system_action_info() {
           'type' => 'list<text>',
           'label' => t('Titles'),
           'description' => t('A list of titles for the breadcrumb links.'),
+          'translatable' => TRUE,
         ),
         'paths' => array(
           'type' => 'list<text>',
@@ -184,11 +185,13 @@ function rules_system_action_info() {
           'type' => 'text',
           'label' => t('Subject'),
           'description' => t("The mail's subject."),
+          'translatable' => TRUE,
         ),
         'message' => array(
           'type' => 'text',
           'label' => t('Message'),
           'description' => t("The mail's message body."),
+          'translatable' => TRUE,
         ),
         'from' => array(
           'type' => 'text',
@@ -196,6 +199,15 @@ function rules_system_action_info() {
           'description' => t("The mail's from address. Leave it empty to use the site-wide configured address."),
           'optional' => TRUE,
         ),
+        'language' => array(
+          'type' => 'token',
+          'label' => t('Language'),
+          'description' => t('If specified, the language used for getting the mail message and subject.'),
+          'options list' => 'entity_metadata_language_list',
+          'optional' => TRUE,
+          'default value' => LANGUAGE_NONE,
+          'default mode' => 'selector',
+        ),
       ),
       'base' => 'rules_action_mail',
       'access callback' => 'rules_system_integration_access',
diff --git a/rules.api.php b/rules.api.php
index 807b9db..1490ab0 100644
--- a/rules.api.php
+++ b/rules.api.php
@@ -92,10 +92,20 @@
  *     FALSE.
  *   - restriction: (optional) Restrict how the argument for this parameter may
  *     be provided. Supported values are 'selector' and 'input'.
+ *   - default mode: (optional) Customize the default mode for providing the
+ *     argument value for a parameter. The default depends on the required data
+ *     type.
  *   - sanitize: (optional) Allows parameters of type 'text' to demand an
  *     already sanitized argument. If enabled, any user specified value won't be
  *     sanitized itself, but replacements applied by input evaluators are as
  *     well as values retrieved from selected data sources.
+ *   - translatable: (optional) If set to TRUE, the provided argument value
+ *     of the parameter is translatable via i18n String translation. This is
+ *     applicable for textual parameters only, i.e. parameters of type 'text',
+ *     'token', 'list<text>' and 'list<token>'. Defaults to FALSE.
+ *   - ui class: (optional) Allows overriding the UI class, which is used to
+ *     generate the configuration UI of a parameter. Defaults to the UI class of
+ *     the specified data type.
  *   - cleaning callback: (optional) A callback that input evaluators may use
  *     to clean inserted replacements; e.g. this is used by the token evaluator.
  *   - wrapped: (optional) Set this to TRUE in case the data should be passed
@@ -756,6 +766,7 @@ function hook_rules_config_execute($config) {
  *   An array of rules configurations with the configuration names as keys.
  *
  * @see hook_default_rules_configuration_alter()
+ * @see hook_rules_config_defaults_rebuild()
  */
 function hook_default_rules_configuration() {
   $rule = rules_reaction_rule();
@@ -789,6 +800,37 @@ function hook_default_rules_configuration_alter(&$configs) {
 }
 
 /**
+ * Act after rebuilding default configurations.
+ *
+ * This hook is invoked by the entity module after default rules configurations
+ * have been rebuilt; i.e. defaults have been saved to the database.
+ *
+ * @param $rules_configs
+ *   The array of default rules configurations which have been inserted or
+ *   updated, keyed by name.
+ * @param $originals
+ *   An array of original rules configurations keyed by name; i.e. the rules
+ *   configurations before the current defaults have been applied. For inserted
+ *   rules configurations no original is available.
+ *
+ * @see hook_default_rules_configuration()
+ * @see entity_defaults_rebuild()
+ */
+function hook_rules_config_defaults_rebuild($rules_configs, $originals) {
+  // Once all defaults have been rebuilt, update all i18n strings at once. That
+  // way we build the rules cache once the rebuild is complete and avoid
+  // rebuilding caches for each updated rule.
+  foreach ($rules_configs as $name => $rule_config) {
+    if (empty($originals[$name])) {
+      rules_i18n_rules_config_insert($rule_config);
+    }
+    else {
+      rules_i18n_rules_config_update($rule_config, $originals[$name]);
+    }
+  }
+}
+
+/**
  * Alter rules components before execution.
  *
  * This hooks allows altering rules components before they are cached for later
@@ -899,5 +941,31 @@ function hook_rules_element_upgrade_alter($element, $target) {
 }
 
 /**
+ * Allows modules to alter or to extend the provided Rules UI.
+ *
+ * Use this hook over the regular hook_menu_alter() as the Rules UI is re-used
+ * and embedded by modules. See rules_ui().
+ *
+ * @param $items
+ *   The menu items to alter.
+ * @param $base_path
+ *   The base path of the Rules UI.
+ * @param $base_count
+ *   The count of the directories contained in the base path.
+ */
+function hook_rules_ui_menu_alter(&$items, $base_path, $base_count) {
+  $items[$base_path . '/manage/%rules_config/schedule'] = array(
+    'title callback' => 'rules_get_title',
+    'title arguments' => array('Schedule !plugin "!label"', $base_count + 1),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('rules_scheduler_schedule_form', $base_count + 1, $base_path),
+    'access callback' => 'rules_config_access',
+    'access arguments' => array('update', $base_count + 1),
+    'file' => 'rules_scheduler.admin.inc',
+    'file path' => drupal_get_path('module', 'rules_scheduler'),
+  );
+}
+
+/**
  * @}
  */
diff --git a/rules.module b/rules.module
index b99ce96..818a9d9 100644
--- a/rules.module
+++ b/rules.module
@@ -15,6 +15,8 @@ function rules_init() {
 /**
  * Returns an instance of the rules UI controller, which eases re-using the Rules UI.
  *
+ * See the rules_admin.module for example usage.
+ *
  * @return RulesUIController
  */
 function rules_ui() {
@@ -578,6 +580,7 @@ function rules_entity_info() {
         'name' => 'name',
         'label' => 'label',
       ),
+      'module' => 'rules',
       'static cache' => TRUE,
       'bundles' => array(),
       'configuration' => TRUE,
@@ -968,6 +971,10 @@ function rules_theme() {
       'render element' => 'element',
       'file' => 'ui/ui.theme.inc',
     ),
+    'rules_settings_help' => array(
+      'variables' => array('text' => '', 'heading' => ''),
+      'file' => 'ui/ui.theme.inc',
+    ),
   );
 }
 
diff --git a/rules_i18n/rules_i18n.i18n.inc b/rules_i18n/rules_i18n.i18n.inc
new file mode 100644
index 0000000..003540d
--- /dev/null
+++ b/rules_i18n/rules_i18n.i18n.inc
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Internationalization integration based upon the entity API i18n stuff.
+ */
+
+/**
+ * Rules i18n integration controller.
+ */
+class RulesI18nStringController extends EntityDefaultI18nStringController {
+
+  /**
+   * Overriden to customize i18n object info.
+   *
+   * @see EntityDefaultI18nStringController::hook_object_info()
+   */
+  public function hook_object_info() {
+    $info = parent::hook_object_info();
+    $info['rules_config']['class'] = 'RulesI18nStringObjectWrapper';
+    return $info;
+  }
+
+  /**
+   * Overriden to customize the used menu wildcard.
+   */
+  protected function menuWildcard() {
+    return '%rules_config';
+  }
+
+  /**
+   * Provide the menu base path. We can provide only one though.
+   */
+  protected function menuBasePath() {
+    return 'admin/config/workflow/rules/reaction';
+  }
+}
+
+/**
+ * Custom I18n String object wrapper, which register custom properties per config.
+ */
+class RulesI18nStringObjectWrapper extends i18n_string_object_wrapper {
+
+  /**
+   * Get translatable properties
+   */
+  protected function build_properties() {
+    $strings = parent::build_properties();
+    $properties = array();
+
+    // Also add in the configuration label, as the i18n String UI requires
+    // a String to be available always.
+    $properties['label'] = array(
+      'title' => t('Configuration name'),
+      'string' => $this->object->label,
+    );
+
+    $this->buildElementProperties($this->object, $properties);
+
+    // Add in translations for all elements.
+    foreach ($this->object->elements() as $element) {
+      $this->buildElementProperties($element, $properties);
+    }
+    $strings[$this->get_textgroup()]['rules_config'][$this->object->name] = $properties;
+    return $strings;
+  }
+
+  /**
+   * Adds in translatable properties of the given element.
+   */
+  protected function buildElementProperties($element, &$properties) {
+
+    foreach ($element->pluginParameterInfo() as $name => $info) {
+      // Add in all directly provided input variables.
+      if (!empty($info['translatable']) && isset($element->settings[$name])) {
+        // If its an array of textual values, translate each value on its own.
+        if (is_array($element->settings[$name])) {
+          foreach ($element->settings[$name] as $i => $value) {
+            $properties[$element->elementId() . ':' . $name . ':' . $i] = array(
+              'title' => t('@plugin "@label" (id @id), @parameter, Value @delta', array('@plugin' => drupal_ucfirst($element->plugin()), '@label' => $element->label(), '@id' => $element->elementId(), '@parameter' => $info['label'], '@delta' => $i + 1)),
+              'string' => $value,
+            );
+          }
+        }
+        else {
+          $properties[$element->elementId() . ':' . $name] = array(
+            'title' => t('@plugin "@label" (id @id), @parameter', array('@plugin' => drupal_ucfirst($element->plugin()), '@label' => $element->label(), '@id' => $element->elementId(), '@parameter' => $info['label'])),
+            'string' => $element->settings[$name],
+          );
+        }
+      }
+    }
+  }
+}
diff --git a/rules_i18n/rules_i18n.info b/rules_i18n/rules_i18n.info
new file mode 100644
index 0000000..5522c74
--- /dev/null
+++ b/rules_i18n/rules_i18n.info
@@ -0,0 +1,9 @@
+name = Rules translation
+description = Allows translating rules.
+dependencies[] = rules
+dependencies[] = i18n_string
+package = Multilingual - Internationalization
+core = 7.x
+files[] = rules_i18n.i18n.inc
+files[] = rules_i18n.rules.inc
+files[] = rules_i18n.test
\ No newline at end of file
diff --git a/rules_i18n/rules_i18n.module b/rules_i18n/rules_i18n.module
new file mode 100644
index 0000000..2013871
--- /dev/null
+++ b/rules_i18n/rules_i18n.module
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Rules i18n integration.
+ */
+
+
+/**
+ * Implements hook_menu().
+ */
+function rules_i18n_rules_ui_menu_alter(&$items, $base_path, $base_count) {
+
+  $items[$base_path . '/manage/%rules_config/edit'] = array(
+    'title' => 'Edit',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -100,
+  );
+
+  // For reaction-rules i18n generates the menu items, for the others we provide
+  // further i18n menu items for all other base paths.
+
+  if ($base_path != 'admin/config/workflow/rules/reaction') {
+
+    $items[$base_path . '/manage/%rules_config/translate'] = array(
+      'title' => 'Translate',
+      'page callback' => 'i18n_page_translate_localize',
+      'page arguments' => array('rules_config', $base_count + 1),
+      'access callback' => 'i18n_object_translate_access',
+      'access arguments' => array('rules_config', $base_count + 1),
+      'type' => MENU_LOCAL_TASK,
+      'file' => 'i18n.pages.inc',
+      'file path' => drupal_get_path('module', 'i18n'),
+      'weight' => 10,
+    );
+
+    $items[$base_path . '/manage/%rules_config/translate/%i18n_language'] = array(
+      'title' => 'Translate',
+      'page callback' => 'i18n_page_translate_localize',
+      'page arguments' => array('rules_config', $base_count + 1, $base_count + 3),
+      'access callback' => 'i18n_object_translate_access',
+      'access arguments' => array('rules_config', $base_count),
+      'type' => MENU_CALLBACK,
+      'file' => 'i18n.pages.inc',
+      'file path' => drupal_get_path('module', 'i18n'),
+      'weight' => 10,
+    );
+  }
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function rules_i18n_entity_info_alter(&$info) {
+  // Enable i18n support via the entity API.
+  $info['rules_config']['i18n controller class'] = 'RulesI18nStringController';
+}
+
+/**
+ * Implements hook_rules_config_insert().
+ */
+function rules_i18n_rules_config_insert($rules_config) {
+  // Do nothing when rebuilding defaults to avoid multiple cache rebuilds.
+  // @see rules_i18n_rules_config_defaults_rebuild()
+  if (!empty($rules_config->is_rebuild)) {
+    return;
+  }
+
+  i18n_string_object_update('rules_config', $rules_config);
+}
+
+/**
+ * Implements hook_rules_config_update().
+ */
+function rules_i18n_rules_config_update($rules_config, $original = NULL) {
+  // Do nothing when rebuilding defaults to avoid multiple cache rebuilds.
+  // @see rules_i18n_rules_config_defaults_rebuild()
+  if (!empty($rules_config->is_rebuild)) {
+    return;
+  }
+  $original = $original ? $original : $rules_config->original;
+
+  // Account for name changes.
+  if ($original->name != $rules_config->name) {
+    i18n_string_update_context("rules:rules_config:{$original->name}:*", "rules:rules_config:{$rules_config->name}:*");
+  }
+
+  // We need to remove the strings of any disappeared properties, i.e. strings
+  // from translatable parameters of deleted actions.
+
+  // i18n_object() uses a static cache per config, so bypass it to wrap the
+  // original entity.
+  $old_i18n_object = new RulesI18nStringObjectWrapper('rules_config', $original);
+  $old_strings = $old_i18n_object->get_strings(array('empty' => TRUE));
+
+  // Note: For the strings to have updated values, the updated entity needs to
+  // be handled last due to i18n's cache.
+  $strings = i18n_object('rules_config', $rules_config)->get_strings(array('empty' => TRUE));
+
+  foreach (array_diff_key($old_strings, $strings) as $name => $string) {
+    $string->remove(array('empty' => TRUE));
+  }
+  // Now update the remaining strings.
+  foreach ($strings as $string) {
+    $string->update(array('empty' => TRUE, 'update' => TRUE));
+  }
+}
+
+/**
+ * Implements hook_rules_config_delete().
+ */
+function rules_i18n_rules_config_delete($rules_config) {
+  i18n_string_object_remove('rules_config', $rules_config);
+}
+
+/**
+ * Implements hook_rules_config_defaults_rebuild().
+ */
+function rules_i18n_rules_config_defaults_rebuild($rules_configs, $originals) {
+  // Once all defaults have been rebuilt, update all i18n strings at once. That
+  // way we build the rules cache once the rebuild is complete and avoid
+  // rebuilding caches for each updated rule.
+  foreach ($rules_configs as $name => $rule_config) {
+    if (empty($originals[$name])) {
+      rules_i18n_rules_config_insert($rule_config);
+    }
+    else {
+      rules_i18n_rules_config_update($rule_config, $originals[$name]);
+    }
+  }
+}
diff --git a/rules_i18n/rules_i18n.rules.inc b/rules_i18n/rules_i18n.rules.inc
new file mode 100644
index 0000000..30c7c54
--- /dev/null
+++ b/rules_i18n/rules_i18n.rules.inc
@@ -0,0 +1,184 @@
+<?php
+
+/**
+ * @file
+ * Internationalization rules integration.
+ */
+
+/**
+ * Implements hook_rules_action_info().
+ */
+function rules_i18n_rules_action_info() {
+  $items['rules_i18n_t'] = array(
+    'label' => t('Translate a text'),
+    'group' => t('Translation'),
+    'parameter' => array(
+      'text' => array(
+        'type' => 'text',
+        'label' => t('Text'),
+        'description' => t('The text to translate.'),
+        'translatable' => TRUE,
+      ),
+      'language' => array(
+        'type' => 'token',
+        'label' => t('Language'),
+        'description' => t('The language to translate the text into.'),
+        'options list' => 'entity_metadata_language_list',
+      ),
+    ),
+    'provides' => array(
+      'text' => array('type' => 'text', 'label' => t('The translated text')),
+    ),
+    'base' => 'rules_i18n_action_t',
+    'access callback' => 'rules_i18n_rules_integration_access',
+  );
+  $items['rules_i18n_select'] = array(
+    'label' => t('Select a translated value'),
+    'group' => t('Translation'),
+    'parameter' => array(
+      'data' => array(
+        'type' => '*',
+        'label' => t('Data'),
+        'description' => t('Select a translated value, e.g. a translatable field. If the selected data is not translatable, the language neutral value will be selected.'),
+        'translatable' => TRUE,
+        'restrict' => 'select',
+      ),
+      'language' => array(
+        'type' => 'token',
+        'label' => t('Language'),
+        'description' => t('The language to translate the value into.'),
+        'options list' => 'entity_metadata_language_list',
+      ),
+    ),
+    'provides' => array(
+      'data_translated' => array('type' => '*', 'label' => t('The translated value')),
+    ),
+    'base' => 'rules_i18n_action_select',
+    'access callback' => 'rules_i18n_rules_integration_access',
+  );
+  return $items;
+}
+
+/**
+ * Access callback for the rules i18n integration.
+ */
+function rules_i18n_rules_integration_access() {
+  return user_access('translate interface');
+}
+
+/**
+ * Action callback: Translate a text.
+ */
+function rules_i18n_action_t($text) {
+  // Nothing to do, as our input evaluator has already translated it.
+  // @see RulesI18nStringEvaluator
+  return array('text' => $text);
+}
+
+/**
+ * Action callback: Select a translated value.
+ */
+function rules_i18n_action_select($data) {
+  // Nothing to do, as Rules applies the language to the data selector for us.
+  return array('data_translated' => $data);
+}
+
+/**
+ * Action "Select a translated value" info_alter callback.
+ */
+function rules_i18n_action_select_info_alter(&$element_info, $element) {
+  $element->settings += array('data:select' => NULL);
+  if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) {
+    $info = $wrapper->info();
+    // Pass through the data type of the selected data.
+    $element_info['provides']['data_translated']['type'] = $wrapper->type();
+  }
+}
+
+/**
+ * Implements hook_rules_evaluator_info().
+ */
+function rules_i18n_rules_evaluator_info() {
+  return array(
+    'i18n' => array(
+      'class' => 'RulesI18nStringEvaluator',
+      'type' => array('text', 'list<text>', 'token', 'list<token>'),
+      'weight' => -10,
+     ),
+  );
+}
+
+/**
+ * A class implementing a rules input evaluator processing tokens.
+ */
+class RulesI18nStringEvaluator extends RulesDataInputEvaluator {
+
+  public static function access() {
+    return user_access('translate interface');
+  }
+
+  public function prepare($text, $var_info, $param_info = NULL) {
+    if (!empty($param_info['translatable'])) {
+      $this->setting = TRUE;
+    }
+    else {
+      // Else, skip this evaluator.
+      $this->setting = NULL;
+    }
+  }
+
+  /**
+   * Prepare the i18n-context string.
+   *
+   * We have to use process() here instead of evaluate() because we need more
+   * context than evaluate() provides.
+   */
+  public function process($value, $info, RulesState $state, RulesPlugin $element, $options = NULL) {
+    $options = isset($options) ? $options : $this->getEvaluatorOptions($info, $state, $element);
+    $value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element, $options) : $value;
+    if (isset($element->root()->name)) {
+      $config_name = $element->root()->name;
+      $id = $element->elementId();
+      $name = $info['#name'];
+      $options['i18n context'] = "rules:rules_config:$config_name:$id:$name";
+      return $this->evaluate($value, $options, $state);
+    }
+    return $this->value;
+  }
+
+  /**
+   * Translate the value.
+   *
+   * If the element provides a language parameter, we are using this target
+   * language provided via $options['language'].
+   */
+  public function evaluate($value, $options, RulesState $state) {
+    $langcode = isset($options['language']) ? $options['language']->language : NULL;
+    if (is_array($value)) {
+      foreach ($value as $key => $text) {
+        $value[$key] = entity_i18n_string($options['i18n context'] . ':' . $key, $text, $langcode);
+      }
+    }
+    else {
+      $value = entity_i18n_string($options['i18n context'], $value, $langcode);
+    }
+    return $value;
+  }
+
+  public static function help($var_info, $param_info = array()) {
+    if (!empty($param_info['translatable'])) {
+      if ($param_info['custom translation language']) {
+        $text = t('Translations can be provided at the %translate tab. The argument value is translated to the configured language.', array('%translate' => t('Translate')));
+      }
+      else {
+        $text = t('Translations can be provided at the %translate tab. The argument value is translated to the current interface language.', array('%translate' => t('Translate')));
+      }
+      $render = array(
+        '#theme' => 'rules_settings_help',
+        '#text' => $text,
+        '#heading' => t('Translation'),
+      );
+      return $render;
+    }
+  }
+}
diff --git a/rules_i18n/rules_i18n.test b/rules_i18n/rules_i18n.test
new file mode 100644
index 0000000..2e1326c
--- /dev/null
+++ b/rules_i18n/rules_i18n.test
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @file
+ * Rules i18n tests.
+ */
+
+/**
+ * Test the i18n integration.
+ */
+class RulesI18nTestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Rules I18n',
+      'description' => 'Tests translating Rules configs.',
+      'group' => 'Rules',
+      'dependencies' => array('i18n_string'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('rules_i18n');
+    $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages'));
+    $this->drupalLogin($this->admin_user);
+    $this->addLanguage('de');
+  }
+
+  /**
+   * Copied from i18n module (class Drupali18nTestCase).
+   *
+   * We cannot extend from Drupali18nTestCase as else the test-bot would die.
+   */
+  public function addLanguage($language_code) {
+    // Check to make sure that language has not already been installed.
+    $this->drupalGet('admin/config/regional/language');
+
+    if (strpos($this->drupalGetContent(), 'enabled[' . $language_code . ']') === FALSE) {
+      // Doesn't have language installed so add it.
+      $edit = array();
+      $edit['langcode'] = $language_code;
+      $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+
+      // Make sure we are not using a stale list.
+      drupal_static_reset('language_list');
+      $languages = language_list('language');
+      $this->assertTrue(array_key_exists($language_code, $languages), t('Language was installed successfully.'));
+
+      if (array_key_exists($language_code, $languages)) {
+        $this->assertRaw(t('The language %language has been created and can now be used. More information is available on the <a href="@locale-help">help screen</a>.', array('%language' => $languages[$language_code]->name, '@locale-help' => url('admin/help/locale'))), t('Language has been created.'));
+      }
+    }
+    elseif ($this->xpath('//input[@type="checkbox" and @name=:name and @checked="checked"]', array(':name' => 'enabled[' . $language_code . ']'))) {
+      // It's installed and enabled. No need to do anything.
+      $this->assertTrue(true, 'Language [' . $language_code . '] already installed and enabled.');
+    }
+    else {
+      // It's installed but not enabled. Enable it.
+      $this->assertTrue(true, 'Language [' . $language_code . '] already installed.');
+      $this->drupalPost(NULL, array('enabled[' . $language_code . ']' => TRUE), t('Save configuration'));
+      $this->assertRaw(t('Configuration saved.'), t('Language successfully enabled.'));
+    }
+  }
+
+  /**
+   * Tests translating rules configs.
+   */
+  function testRulesConfigTranslation() {
+    // Create a rule and translate it.
+    $rule = rule();
+    $rule->label = 'label-en';
+    $rule->action('drupal_message', array('message' => 'English message for [site:current-user].'));
+    $rule->save();
+
+    $actions = $rule->actions();
+    $id = $actions[0]->elementId();
+
+    // Add a translation.
+    i18n_string_textgroup('rules')->update_translation("rules_config:{$rule->name}:label", 'de', 'label-de');
+    i18n_string_textgroup('rules')->update_translation("rules_config:{$rule->name}:$id:message", 'de', 'German message für [site:current-user].');
+
+    // Execute the Rule and make sure the translated message has been output.
+    // To do so, set the global language to German.
+    $languages = language_list();
+    $GLOBALS['language'] = $languages['de'];
+
+    // Clear messages and execute the rule.
+    i18n_string_textgroup('rules')->cache_reset();
+    drupal_get_messages();
+    $rule->execute();
+
+    $messages = drupal_get_messages();
+    $this->assertEqual($messages['status'][0], 'German message für ' . $GLOBALS['user']->name . '.', 'Translated message has been output.');
+
+    // Test re-naming the rule.
+    $rule->name = 'rules_i18n_name_2';
+    $rule->save();
+    $translation = entity_i18n_string("rules:rules_config:{$rule->name}:label", $rule->label, 'de');
+    $this->assertEqual($translation, 'label-de', 'Translation survives a name change.');
+
+    // Test updating and make sure the translation stays.
+    $rule->label = 'Label new';
+    $rule->save();
+    $translation = entity_i18n_string("rules:rules_config:{$rule->name}:label", $rule->label, 'de');
+    $this->assertEqual($translation, 'label-de', 'Translation survives an update.');
+
+    // Test deleting the action and make sure the string is deleted too.
+    $actions[0]->delete();
+    $rule->save();
+    $translation = entity_i18n_string("rules_config:{$rule->name}:$id:message", 'English message for [site:current-user].', 'de');
+    $this->assertEqual($translation, 'English message for [site:current-user].', 'Translation of deleted action has been deleted.');
+
+    // Now delete the whole config and make sure all translations are deleted.
+    $rule->delete();
+    $translation = entity_i18n_string("rules_config:{$rule->name}:label", 'label-en', 'de');
+    $this->assertEqual($translation, 'label-en', 'Translation of deleted config has been deleted.');
+  }
+}
diff --git a/rules_scheduler/rules_scheduler.module b/rules_scheduler/rules_scheduler.module
index 3fa1baf..baab87c 100644
--- a/rules_scheduler/rules_scheduler.module
+++ b/rules_scheduler/rules_scheduler.module
@@ -68,6 +68,24 @@ function rules_scheduler_run_task(array $task) {
 }
 
 /**
+ * Implements hook_rules_ui_menu_alter().
+ *
+ * Adds a menu item for the 'schedule' operation.
+ */
+function rules_scheduler_rules_ui_menu_alter(&$items, $base_path, $base_count) {
+  $items[$base_path . '/manage/%rules_config/schedule'] = array(
+    'title callback' => 'rules_get_title',
+    'title arguments' => array('Schedule !plugin "!label"', $base_count + 1),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('rules_scheduler_schedule_form', $base_count + 1, $base_path),
+    'access callback' => 'rules_config_access',
+    'access arguments' => array('update', $base_count + 1),
+    'file' => 'rules_scheduler.admin.inc',
+    'file path' => drupal_get_path('module', 'rules_scheduler'),
+  );
+}
+
+/**
  * Implements hook_menu().
  */
 function rules_scheduler_menu() {
diff --git a/tests/rules.test b/tests/rules.test
index e932479..f8d4e44 100644
--- a/tests/rules.test
+++ b/tests/rules.test
@@ -70,6 +70,7 @@ class RulesTestCase extends DrupalWebTestCase {
     $it = new RecursiveIteratorIterator($rule->conditions());
     $this->assertEqual(iterator_count($it), 6, 'Iterated over all conditions');
     $this->assertEqual(iterator_count($rule->actions()), 1, 'Iterated over all actions');
+    $this->assertEqual(iterator_count($rule->elements()), 10, 'Iterated over all rule elements.');
 
     // Test getting dependencies and the integrity check.
     $rule->integrityCheck();
diff --git a/ui/ui.controller.inc b/ui/ui.controller.inc
index 80aecfa..a0abce1 100644
--- a/ui/ui.controller.inc
+++ b/ui/ui.controller.inc
@@ -139,6 +139,8 @@ class RulesUIController {
       'file' => 'ui/ui.forms.inc',
       'file path' => drupal_get_path('module', 'rules'),
     );
+    drupal_alter('rules_ui_menu', $items, $base_path, $base_count);
+
     if (module_exists('rules_scheduler')) {
       $items[$base_path . '/manage/%rules_config/schedule'] = array(
         'title callback' => 'rules_get_title',
@@ -272,7 +274,14 @@ class RulesUIController {
     ));
 
     // Add operations depending on the options and the exportable status.
-    $row[] = $config->hasStatus(ENTITY_FIXED) ? '' : l(t('edit'), RulesPluginUI::path($name));
+    if (!$config->hasStatus(ENTITY_FIXED)) {
+      $row[] =  l(t('edit'), RulesPluginUI::path($name));
+      $row[] =  l(t('translate'), RulesPluginUI::path($name, 'translate'));
+    }
+    else {
+      $row[] = '';
+      $row[] = '';
+    }
 
     if (!$options['hide status op']) {
       // Add either an enable or disable link.
diff --git a/ui/ui.core.inc b/ui/ui.core.inc
index bc69ac4..3977dbf 100644
--- a/ui/ui.core.inc
+++ b/ui/ui.core.inc
@@ -282,7 +282,7 @@ class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface {
    * Actually generates the parameter form for the given data type.
    */
   protected function getParameterForm($name, $info, $settings, &$mode) {
-    $class = $this->getDataTypeClass($info['type']);
+    $class = $this->getDataTypeClass($info['type'], $info);
     $supports_input_mode = in_array('RulesDataDirectInputFormInterface', class_implements($class));
 
     // Init the mode.
@@ -297,10 +297,20 @@ class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface {
         $mode = $info['restriction'];
       }
       else {
-        $mode = call_user_func(array($class, 'getDefaultMode'));
+        // Allow the parameter to define the 'default mode' and fallback to the
+        // data type default.
+        $mode = !empty($info['default mode']) ? $info['default mode'] : call_user_func(array($class, 'getDefaultMode'));
       }
     }
 
+    // For translatable parameters, pre-populate an internal translation source
+    // key so data type forms or input evaluators (i18n) may produce suiting
+    // help.
+    if (drupal_multilingual() && !empty($info['translatable'])) {
+      $parameter = $this->element->pluginParameterInfo();
+      $info['custom translation language'] = !empty($parameter['language']);
+    }
+
     // Add the parameter form.
     if ($mode == 'input' && $supports_input_mode) {
       $form['settings'] = call_user_func(array($class, 'inputForm'), $name, $info, $settings, $this->element);
@@ -583,9 +593,14 @@ class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface {
    * Returns the name of class for the given data type.
    *
    * @param $data_type
-   *  The name of the data typ
+   *   The name of the data typ
+   * @param $parameter_info
+   *   (optional) An array of info about the to be configured parameter.
    */
-  public function getDataTypeClass($data_type) {
+  public function getDataTypeClass($data_type, $parameter_info = array()) {
+    if (!empty($parameter_info['ui class'])) {
+      return $parameter_info['ui class'];
+    }
     $cache = rules_get_cache();
     $data_info = $cache['data_info'];
     return (is_string($data_type) && isset($data_info[$data_type]['ui class'])) ? $data_info[$data_type]['ui class'] : 'RulesDataUI';
@@ -621,7 +636,7 @@ class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface {
       }
       elseif (isset($this->element->settings[$name]) && (!isset($parameter['default value']) || $parameter['default value'] != $this->element->settings[$name])) {
         $method = empty($parameter['options list']) ? 'render' : 'renderOptionsLabel';
-        $class = $this->getDataTypeClass($parameter['type']);
+        $class = $this->getDataTypeClass($parameter['type'], $parameter);
         // We cannot use method_exists() here as it would trigger a PHP bug,
         // @see http://drupal.org/node/1258284
         $element = call_user_func(array($class, $method), $this->element->settings[$name], $name, $parameter, $this->element);
@@ -887,7 +902,7 @@ class RulesContainerPluginUI extends RulesPluginUI {
     $form['elements']['#attributes']['class'][] = 'rules-container-plugin';
 
     // Recurse over all element childrens or use the provided iterator.
-    $iterator = isset($iterator) ? $iterator : new RecursiveIteratorIterator($this->element, RecursiveIteratorIterator::SELF_FIRST);
+    $iterator = isset($iterator) ? $iterator : $this->element->elements();
     $root_depth = $this->element->depth();
     foreach ($iterator as $key => $child) {
       $id = $child->elementId();
diff --git a/ui/ui.data.inc b/ui/ui.data.inc
index 3891219..bb0fec6 100755
--- a/ui/ui.data.inc
+++ b/ui/ui.data.inc
@@ -68,6 +68,20 @@ class RulesDataUI {
       '#description' => t("The data selector helps you drill down into the data available to Rules. <em>To make entity fields appear in the data selector, you may have to use the condition 'entity has field' (or 'content is of type').</em> More useful tips about data selection is available in <a href='@url'>the online documentation</a>.",
         array('@url' => rules_external_help('data-selection'))),
     );
+
+    if (!empty($info['translatable'])) {
+      if ($info['custom translation language']) {
+        $text = t('If a multilingual data source (i.e. a translatable field) is given, the argument is translated to the configured language.');
+      }
+      else {
+        $text = t('If a multilingual data source (i.e. a translatable field) is given, the argument is translated to the current interface language.');
+      }
+      $form['translation'] = array(
+        '#theme' => 'rules_settings_help',
+        '#text' => $text,
+        '#heading' => t('Translation'),
+      );
+    }
     $form['help'] = array(
       '#theme' => 'rules_data_selector_help',
       '#variables' => $element->availableVariables(),
@@ -167,9 +181,11 @@ class RulesDataUITextToken extends RulesDataUIText {
 
   public static function inputForm($name, $info, $settings, RulesPlugin $element) {
     $form = parent::inputForm($name, $info, $settings, $element);
-    $form[$name]['#element_validate'][] = 'rules_ui_element_token_validate';
-    $form[$name]['#description'] = t('May only contain lowercase letters, numbers, and underscores and has to start with a letter.');
-    $form[$name]['#rows'] = 1;
+    if ($form[$name]['#type'] == 'textarea') {
+      $form[$name]['#element_validate'][] = 'rules_ui_element_token_validate';
+      $form[$name]['#description'] = t('May only contain lowercase letters, numbers, and underscores and has to start with a letter.');
+      $form[$name]['#rows'] = 1;
+    }
     return $form;
   }
 }
diff --git a/ui/ui.theme.inc b/ui/ui.theme.inc
index db58c1c..42bf6e8 100755
--- a/ui/ui.theme.inc
+++ b/ui/ui.theme.inc
@@ -275,3 +275,13 @@ function theme_rules_autocomplete($variables) {
 
   return $output;
 }
+
+/**
+ * General theme function for displaying settings related help.
+ * @ingroup themeable
+ */
+function theme_rules_settings_help($variables) {
+  $text = $variables['text'];
+  $heading = $variables['heading'];
+  return "<p class=\"rules-settings-help\"><strong>$heading:</strong> $text</p>";
+}
