diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 31e4426d012afebc9a5e48d9f524863e9bb832f6..1004e40e156f3393a163995bb91488a083b8b44f 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -1215,7 +1215,9 @@ class SelectionField extends FormField {
                 'id'=>3,
                 'label'=>__('Prompt'), 'required'=>false, 'default'=>'',
                 'hint'=>__('Leading text shown before a value is selected'),
-                'configuration'=>array('size'=>40, 'length'=>40),
+                'configuration'=>array('size'=>40, 'length'=>40,
+                    'translatable'=>$this->getTranslateTag('prompt'),
+                ),
             )),
             'default' => new SelectionField(array(
                 'id'=>4, 'label'=>__('Default'), 'required'=>false, 'default'=>'',
diff --git a/include/class.forms.php b/include/class.forms.php
index 75d3bfc83914e8689edb856a48c16816164b7f84..1d7f95031193175f8a0c363b6e50739f34489375 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -931,7 +931,9 @@ class ChoiceField extends FormField {
             'prompt' => new TextboxField(array(
                 'id'=>2, 'label'=>__('Prompt'), 'required'=>false, 'default'=>'',
                 'hint'=>__('Leading text shown before a value is selected'),
-                'configuration'=>array('size'=>40, 'length'=>40),
+                'configuration'=>array('size'=>40, 'length'=>40,
+                    'translatable'=>$this->getTranslateTag('prompt'),
+                ),
             )),
             'multiselect' => new BooleanField(array(
                 'id'=>1, 'label'=>'Multiselect', 'required'=>false, 'default'=>false,
@@ -1835,7 +1837,10 @@ class ChoicesWidget extends Widget {
         // Determine the value for the default (the one listed if nothing is
         // selected)
         $choices = $this->field->getChoices(true);
-        $prompt = $config['prompt'] ?: __('Select');
+        $prompt = ($config['prompt'])
+            ? $this->field->getLocal('prompt', $config['prompt'])
+            : __('Select'
+            /* Used as a default prompt for a custom drop-down list */);
 
         $have_def = false;
         // We don't consider the 'default' when rendering in 'search' mode
diff --git a/include/class.list.php b/include/class.list.php
index f5f9c4bd2551f5605dc4228da9b827e6c62ac010..8a6e4667558eb2733dfdd8cc3e9e50145bab08e9 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -315,6 +315,15 @@ class DynamicList extends VerySimpleModel implements CustomList {
         return JsonDataParser::parse($this->_config->get('configuration'));
     }
 
+    function getTranslateTag($subtag) {
+        return _H(sprintf('list.%s.%s', $subtag, $this->id));
+    }
+    function getLocal($subtag) {
+        $tag = $this->getTranslateTag($subtag);
+        $T = CustomDataTranslation::translate($tag);
+        return $T != $tag ? $T : $this->get($subtag);
+    }
+
     function update($vars, &$errors) {
 
         $required = array();
@@ -520,7 +529,7 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem {
     }
 
     function getValue() {
-        return $this->get('value');
+        return $this->getLocal('value');
     }
 
     function getAbbrev() {
@@ -587,6 +596,15 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem {
         }
     }
 
+    function getTranslateTag($subtag) {
+        return _H(sprintf('listitem.%s.%s', $subtag, $this->id));
+    }
+    function getLocal($subtag) {
+        $tag = $this->getTranslateTag($subtag);
+        $T = CustomDataTranslation::translate($tag);
+        return $T != $tag ? $T : $this->get($subtag);
+    }
+
     function toString() {
         return $this->get('value');
     }
@@ -1010,6 +1028,15 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
         return $this->_properties;
     }
 
+    function getTranslateTag($subtag) {
+        return _H(sprintf('status.%s.%s', $subtag, $this->id));
+    }
+    function getLocal($subtag) {
+        $tag = $this->getTranslateTag($subtag);
+        $T = CustomDataTranslation::translate($tag);
+        return $T != $tag ? $T : $this->get($subtag);
+    }
+
     function getConfiguration() {
 
         if (!$this->_settings) {
diff --git a/include/class.sla.php b/include/class.sla.php
index 070743a2bb6f0c8e22c3664be229c8817a401852..93e39307175c053d345b31b0c597124268b1a029 100644
--- a/include/class.sla.php
+++ b/include/class.sla.php
@@ -94,6 +94,20 @@ class SLA {
         return ($this->ht['enable_priority_escalation']);
     }
 
+    function getTranslateTag($subtag) {
+        return _H(sprintf('sla.%s.%s', $subtag, $this->id));
+    }
+    function getLocal($subtag) {
+        $tag = $this->getTranslateTag($subtag);
+        $T = CustomDataTranslation::translate($tag);
+        return $T != $tag ? $T : $this->ht[$subtag];
+    }
+    static function getLocalById($id, $subtag, $default) {
+        $tag = _H(sprintf('sla.%s.%s', $subtag, $id));
+        $T = CustomDataTranslation::translate($tag);
+        return $T != $tag ? $T : $default;
+    }
+
     function update($vars,&$errors) {
 
         if(!SLA::save($this->getId(),$vars,$errors))
@@ -139,7 +153,7 @@ class SLA {
             while($row=db_fetch_array($res))
                 $slas[$row['id']] = sprintf(__('%s (%d hours - %s)'
                         /* Tokens are <name> (<#> hours - <Active|Disabled>) */),
-                        $row['name'],
+                        self::getLocalById($row['id'], 'name', $row['name']),
                         $row['grace_period'],
                         $row['isactive']?__('Active'):__('Disabled'));
         }
diff --git a/include/class.topic.php b/include/class.topic.php
index 9139f9e6d0926efe6d27bc2dc9145ef02f17b151..395e482ad52722cfa886ef346d0d9e7444009bcd 100644
--- a/include/class.topic.php
+++ b/include/class.topic.php
@@ -215,6 +215,15 @@ class Topic {
             array('Ticket', 'isTicketNumberUnique'));
     }
 
+    function getTranslateTag($subtag) {
+        return _H(sprintf('topic.%s.%s', $subtag, $this->id));
+    }
+    function getLocal($subtag) {
+        $tag = $this->getTranslateTag($subtag);
+        $T = CustomDataTranslation::translate($tag);
+        return $T != $tag ? $T : $this->ht[$subtag];
+    }
+
     function setSortOrder($i) {
         if ($i != $this->ht['sort']) {
             $sql = 'UPDATE '.TOPIC_TABLE.' SET `sort`='.db_input($i)
@@ -254,11 +263,12 @@ class Topic {
         return self::save(0, $vars, $errors);
     }
 
-    static function getHelpTopics($publicOnly=false, $disabled=false) {
+    static function getHelpTopics($publicOnly=false, $disabled=false, $localize=true) {
         global $cfg;
         static $topics, $names = array();
 
-        if (!$names) {
+        // If localization is specifically requested, then rebuild the list.
+        if (!$names || $localize) {
             $sql = 'SELECT topic_id, topic_pid, ispublic, isactive, topic FROM '.TOPIC_TABLE
                 . ' ORDER BY `sort`';
             $res = db_query($sql);
@@ -269,13 +279,23 @@ class Topic {
                 $topics[$id] = array('pid'=>$pid, 'public'=>$pub,
                     'disabled'=>!$act, 'topic'=>$topic);
 
+            $localize_this = function($id, $default) use ($localize) {
+                if (!$localize)
+                    return $default;
+
+                $tag = _H("topic.name.{$id}");
+                $T = CustomDataTranslation::translate($tag);
+                return $T != $tag ? $T : $default;
+            };
+
             // Resolve parent names
             foreach ($topics as $id=>$info) {
-                $name = $info['topic'];
+                $name = $localize_this($id, $info['topic']);
                 $loop = array($id=>true);
                 $parent = false;
-                while ($info['pid'] && ($info = $topics[$info['pid']])) {
-                    $name = sprintf('%s / %s', $info['topic'], $name);
+                while (($pid = $info['pid']) && ($info = $topics[$info['pid']])) {
+                    $name = sprintf('%s / %s', $localize_this($pid, $info['topic']),
+                        $name);
                     if ($parent && $parent['disabled'])
                         // Cascade disabled flag
                         $topics[$id]['disabled'] = true;
@@ -301,6 +321,11 @@ class Topic {
             $requested_names[$id] = $n;
         }
 
+        // XXX: If localization requested and the current locale is not the
+        // primary, the list may need to be sorted. Caching is ok here,
+        // because the locale is not going to be changed within a single
+        // request.
+
         return $requested_names;
     }
 
@@ -308,8 +333,8 @@ class Topic {
         return self::getHelpTopics(true);
     }
 
-    function getAllHelpTopics() {
-        return self::getHelpTopics(false, true);
+    function getAllHelpTopics($localize=false) {
+        return self::getHelpTopics(false, true, $localize);
     }
 
     function getIdByName($name, $pid=0) {
@@ -412,10 +437,19 @@ class Topic {
 
     static function updateSortOrder() {
         // Fetch (un)sorted names
-        if (!($names = static::getHelpTopics(false, true)))
+        if (!($names = static::getHelpTopics(false, true, false)))
             return;
 
-        uasort($names, function($a, $b) { return strcmp($a, $b); });
+        if (function_exists('collator_create')) {
+            $coll = Collator::create($cfg->getPrimaryLanguage());
+            // UASORT is necessary to preserve the keys
+            uasort($names, function($a, $b) use ($coll) {
+                return $coll->compare($a, $b); });
+        }
+        else {
+            // Really only works on English names
+            asort($names);
+        }
 
         $update = array_keys($names);
         foreach ($update as $idx=>&$id) {
diff --git a/include/staff/dynamic-list.inc.php b/include/staff/dynamic-list.inc.php
index 0b4ed80996b418ff19d6bf020dc7bc190e08ef37..3b5d9319779ab9b8302917b8217b989b5252150d 100644
--- a/include/staff/dynamic-list.inc.php
+++ b/include/staff/dynamic-list.inc.php
@@ -6,6 +6,8 @@ if ($list) {
     $action = 'update';
     $submit_text = __('Save Changes');
     $info = $list->getInfo();
+    $trans['name'] = $list->getTranslateTag('name');
+    $trans['plural'] = $list->getTranslateTag('plural');
     $newcount=2;
 } else {
     $title = __('Add New Custom List');
@@ -55,9 +57,10 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info)
                     echo $list->getName();
                 else {
                     echo sprintf('<input size="50" type="text" name="name"
+                            data-translate-tag="%s"
                             value="%s"/> <span
                             class="error">*<br/>%s</span>',
-                            $info['name'], $errors['name']);
+                            $trans['name'], $info['name'], $errors['name']);
                 }
                 ?>
             </td>
@@ -70,8 +73,9 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info)
                         echo $list->getPluralName();
                     else
                         echo sprintf('<input size="50" type="text"
+                                data-translate-tag="%s"
                                 name="name_plural" value="%s"/>',
-                                $info['name_plural']);
+                                $trans['plural'], $info['name_plural']);
                 ?>
             </td>
         </tr>
@@ -131,6 +135,7 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info)
         <tr>
             <td><i class="icon-sort"></i></td>
             <td><input type="text" size="32" name="prop-label-<?php echo $id; ?>"
+                data-translate-tag="<?php echo $f->getTranslateTag('label'); ?>"
                 value="<?php echo Format::htmlchars($f->get('label')); ?>"/>
                 <font class="error"><?php
                     if ($ferrors['label']) echo '<br/>'; echo $ferrors['label']; ?>
@@ -251,6 +256,7 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info)
                 <input type="hidden" name="sort-<?php echo $id; ?>"
                 value="<?php echo $i->getSortOrder(); ?>"/></td>
             <td><input type="text" size="40" name="value-<?php echo $id; ?>"
+                data-translate-tag="<?php echo $i->getTranslateTag('value'); ?>"
                 value="<?php echo $i->getValue(); ?>"/>
                 <?php if ($list->hasProperties()) { ?>
                    <a class="action-button field-config"
diff --git a/include/staff/helptopic.inc.php b/include/staff/helptopic.inc.php
index bd254cd2db7e1b7a1bc5d06ac42706cb5b6b3375..73db1e02235e16af3f67678d704d41a98c03908d 100644
--- a/include/staff/helptopic.inc.php
+++ b/include/staff/helptopic.inc.php
@@ -9,6 +9,7 @@ if($topic && $_REQUEST['a']!='add') {
     $info=$topic->getInfo();
     $info['id']=$topic->getId();
     $info['pid']=$topic->getPid();
+    $trans['name'] = $topic->getTranslateTag('name');
     $qstr.='&id='.$topic->getId();
 } else {
     $title=__('Add New Help Topic');
@@ -43,7 +44,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                <?php echo __('Topic');?>:
             </td>
             <td>
-                <input type="text" size="30" name="topic" value="<?php echo $info['topic']; ?>">
+                <input type="text" size="30" name="topic" value="<?php echo $info['topic']; ?>"
+                data-translate-tag="<?php echo $trans['name']; ?>"/>
                 &nbsp;<span class="error">*&nbsp;<?php echo $errors['topic']; ?></span> <i class="help-tip icon-question-sign" href="#topic"></i>
             </td>
         </tr>
diff --git a/include/staff/helptopics.inc.php b/include/staff/helptopics.inc.php
index d124a67a26f8118829b7758e7310dc42c01b2630..a161409e18943a850617525ffc0385c8b0789fdc 100644
--- a/include/staff/helptopics.inc.php
+++ b/include/staff/helptopics.inc.php
@@ -8,7 +8,7 @@ $sql='SELECT topic.* '
     .' LEFT JOIN '.DEPT_TABLE.' dept ON (dept.dept_id=topic.dept_id) '
     .' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (pri.priority_id=topic.priority_id) ';
 $sql.=' WHERE 1';
-$order_by = ($cfg->getTopicSortMode() == 'm' ? '`sort`' : '`topic_id`');
+$order_by = '`sort`';
 
 $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
 //Ok..lets roll...create the actual query
@@ -27,9 +27,6 @@ while ($row = db_fetch_array($res))
 foreach ($topics as &$t)
     $t['name'] = Topic::getTopicName($t['topic_id']);
 
-if ($cfg->getTopicSortMode() == 'a')
-    usort($topics, function($a, $b) { return strcmp($a['name'], $b['name']); });
-
 ?>
 <div class="pull-left" style="width:700px;padding-top:5px;">
  <h2><?php echo __('Help Topics');?></h2>
diff --git a/include/staff/slaplan.inc.php b/include/staff/slaplan.inc.php
index 2e72720b8f2cd8030b70e039a321177196eb282a..91dba3a2b735c40e43cf6c2b8f155176283d0326 100644
--- a/include/staff/slaplan.inc.php
+++ b/include/staff/slaplan.inc.php
@@ -8,6 +8,7 @@ if($sla && $_REQUEST['a']!='add'){
     $submit_text=__('Save Changes');
     $info=$sla->getInfo();
     $info['id']=$sla->getId();
+    $trans['name'] = $sla->getTranslateTag('name');
     $qstr.='&id='.$sla->getId();
 }else {
     $title=__('Add New SLA Plan' /* SLA is abbreviation for Service Level Agreement */);
@@ -41,7 +42,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
               <?php echo __('Name');?>:
             </td>
             <td>
-                <input type="text" size="30" name="name" value="<?php echo $info['name']; ?>">
+                <input type="text" size="30" name="name" value="<?php echo $info['name']; ?>"
+                data-translate-tag="<?php echo $trans['name']; ?>"/>
                 &nbsp;<span class="error">*&nbsp;<?php echo $errors['name']; ?></span>&nbsp;<i class="help-tip icon-question-sign" href="#name"></i>
             </td>
         </tr>
diff --git a/include/staff/templates/dynamic-field-config.tmpl.php b/include/staff/templates/dynamic-field-config.tmpl.php
index aba9966d27f34a22c22937e8bbf1a8c30c79cdc0..8051e31ef4fa2ee4834d5447d3d448d44441bded 100644
--- a/include/staff/templates/dynamic-field-config.tmpl.php
+++ b/include/staff/templates/dynamic-field-config.tmpl.php
@@ -12,7 +12,7 @@
                 ?>" <?php if (!$f->isVisible()) echo 'style="display:none;"'; ?>>
             <div class="field-label <?php if ($f->get('required')) echo 'required'; ?>">
             <label for="<?php echo $f->getWidget()->name; ?>">
-                <?php echo Format::htmlchars($f->get('label')); ?>:
+                <?php echo Format::htmlchars($f->getLocal('label')); ?>:
       <?php if ($f->get('required')) { ?>
                 <span class="error">*</span>
       <?php } ?>
diff --git a/include/staff/templates/dynamic-form.tmpl.php b/include/staff/templates/dynamic-form.tmpl.php
index 7f8cd82b88148122c8545c458121c83b27cef56b..6d0e15121a93b5d77bfee69335397e345a4da70a 100644
--- a/include/staff/templates/dynamic-form.tmpl.php
+++ b/include/staff/templates/dynamic-form.tmpl.php
@@ -79,7 +79,7 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?>
             }
             if ($field->get('hint') && !$field->isBlockLevel()) { ?>
                 <br /><em style="color:gray;display:inline-block"><?php
-                    echo Format::htmlchars($field->get('hint')); ?></em>
+                    echo Format::htmlchars($field->getLocal('hint')); ?></em>
             <?php
             }
             foreach ($field->errors() as $e) { ?>
diff --git a/include/staff/templates/list-item-properties.tmpl.php b/include/staff/templates/list-item-properties.tmpl.php
index dcfc34b92d5bb7f51497cd227a13a2f609a79bc6..df6487fd814857d2ffac204829a59110b4a57b03 100644
--- a/include/staff/templates/list-item-properties.tmpl.php
+++ b/include/staff/templates/list-item-properties.tmpl.php
@@ -19,7 +19,7 @@
             <div class="field-label">
             <label for="<?php echo $f->getWidget()->name; ?>"
                 style="vertical-align:top;padding-top:0.2em">
-                <?php echo Format::htmlchars($f->get('label')); ?>:</label>
+                <?php echo Format::htmlchars($f->getLocal('label')); ?>:</label>
                 <?php
                 if (!$internal && $f->isEditable() && $f->get('hint')) { ?>
                     <br /><em style="color:gray;display:inline-block"><?php
diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php
index d70af212e8d692ada02827e3423978bb4d28ed3d..4956c74287c2ad2df7986f6f8f7958388a0b0cff 100644
--- a/include/staff/ticket-open.inc.php
+++ b/include/staff/ticket-open.inc.php
@@ -143,7 +143,7 @@ if ($_POST)
                             }
                           });">
                     <?php
-                    if ($topics=Topic::getHelpTopics()) {
+                    if ($topics=Topic::getHelpTopics(false, false, true)) {
                         if (count($topics) == 1)
                             $selected = 'selected="selected"';
                         else { ?>