diff --git a/include/class.i18n.php b/include/class.i18n.php
index 11b6f5c805d3f5dd1a2a15a9e2a174bc43e6f56f..2a28487eceb33507a550d86ffbd33de8cea79ac9 100644
--- a/include/class.i18n.php
+++ b/include/class.i18n.php
@@ -334,16 +334,26 @@ class Internationalization {
             global $cfg;
             return __($msgid);
         }
-        function _SN($msgid, $plural, $count) {
+        function _NS($msgid, $plural, $count) {
             global $cfg;
         }
 
+        // Phrases with separate contexts
+        function _P($context, $msgid) {
+            return TextDomain::lookup()->getTranslation()
+                ->pgettext($context, $msgid);
+        }
+        function _NP($context, $singular, $plural, $n) {
+            return TextDomain::lookup()->getTranslation()
+                ->npgettext($context, $singular, $plural, $n);
+        }
+
         // Language-specific translations
         function _L($msgid, $locale) {
             return TextDomain::lookup()->getTranslation($locale)
                 ->translate($msgid);
         }
-        function _LN($msgid, $plural, $count, $locale) {
+        function _NL($msgid, $plural, $count, $locale) {
             return TextDomain::lookup()->getTranslation($locale)
                 ->ngettext($msgid);
         }
diff --git a/include/class.translation.php b/include/class.translation.php
index 736a1281082e40a189fef09fbe450054e29eeafc..f4ee44662c75cc0f61279df3a44384fb839a48f1 100644
--- a/include/class.translation.php
+++ b/include/class.translation.php
@@ -3,11 +3,12 @@
     class.gettext.php
 
     This implements a `Translation` class that is loosely based on the PHP
-    gettext pure-php module.
+    gettext pure-php module. It includes some code from the project and some
+    code which is based in part at least on the PHP gettext project.
 
     This extension to the PHP gettext extension using a specially crafted MO
-    file which is a PHP serialized hash array. The file can be built using a
-    utility method in this class.
+    file which is a PHP hash array. The file can be built using a utility
+    method in this class.
 
     Jared Hancock <jared@osticket.com>
     Copyright (c)  2006-2014 osTicket
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index 3a0f48c14c552f256d446a6dac146266c21578fc..a2473d27f0adc74d8309f18d54499ff3062468ce 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -116,7 +116,7 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting
     <select name="status">
         <option value="">&mdash; <?php echo __('Any Status');?> &mdash;</option>
         <option value="open"
-            <?php echo ($status=='open')?'selected="selected"':'';?>><?php echo __('Open');?> (<?php echo $thisclient->getNumOpenTickets(); ?>)</option>
+            <?php echo ($status=='open')?'selected="selected"':'';?>><?php echo _P('ticket-status', 'Open');?> (<?php echo $thisclient->getNumOpenTickets(); ?>)</option>
         <?php
         if($thisclient->getNumClosedTickets()) {
             ?>
diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php
index 8aa896126dcf2a96c79cfc4e470d325af8867409..444362b3521f9861cd376334862377f444f266c8 100644
--- a/include/staff/ticket-open.inc.php
+++ b/include/staff/ticket-open.inc.php
@@ -373,7 +373,7 @@ if ($_POST)
         <tr>
             <td colspan=2>
                 <textarea class="richtext ifhtml draft draft-delete"
-                    placeholder="Optional internal note (recommended on assignment)"
+                    placeholder="<?php echo __('Optional internal note (recommended on assignment)'); ?>"
                     data-draft-namespace="ticket.staff.note" name="note"
                     cols="21" rows="6" style="width:80%;"
                     ><?php echo $info['note']; ?></textarea>
@@ -382,7 +382,7 @@ if ($_POST)
     </tbody>
 </table>
 <p style="text-align:center;">
-    <input type="submit" name="submit" value="<?php echo __('Open');?>">
+    <input type="submit" name="submit" value="<?php echo _P('action-button', 'Open');?>">
     <input type="reset"  name="reset"  value="<?php echo __('Reset');?>">
     <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick="javascript:
         $('.richtext').each(function() {
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index 8a427e1e106b37e4b30853b7681c181f9efe52e1..158504928c4e9b1c35e203a9c9be077e1f0429f9 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -554,7 +554,7 @@ if ($results) {
             <label for="status"><?php echo __('Status');?>:</label>
             <select id="status" name="status">
                 <option value="">&mdash; <?php echo __('Any Status');?> &mdash;</option>
-                <option value="open"><?php echo __('Open');?></option>
+                <option value="open"><?php echo _P('ticket-status', 'Open');?></option>
                 <?php
                 if(!$cfg->showAnsweredTickets()) {?>
                 <option value="answered"><?php echo __('Answered');?></option>
diff --git a/scp/tickets.php b/scp/tickets.php
index 579664d06bf10249d5c20e8ec903a071a9973b65..e3a5693fa4c9069f6bf5c7cba4b96d0285538764 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -522,8 +522,11 @@ $stats= $thisstaff->getTicketsStats();
 
 //Navigation
 $nav->setTabActive('tickets');
+$open_name = _P('queue-name',
+    /* This is the name of the open ticket queue */
+    'Open');
 if($cfg->showAnsweredTickets()) {
-    $nav->addSubMenu(array('desc'=>__('Open').' ('.number_format($stats['open']+$stats['answered']).')',
+    $nav->addSubMenu(array('desc'=>$open_name.' ('.number_format($stats['open']+$stats['answered']).')',
                             'title'=>__('Open Tickets'),
                             'href'=>'tickets.php',
                             'iconclass'=>'Ticket'),
@@ -531,7 +534,7 @@ if($cfg->showAnsweredTickets()) {
 } else {
 
     if($stats) {
-        $nav->addSubMenu(array('desc'=>__('Open').' ('.number_format($stats['open']).')',
+        $nav->addSubMenu(array('desc'=>$open_name.' ('.number_format($stats['open']).')',
                                'title'=>__('Open Tickets'),
                                'href'=>'tickets.php',
                                'iconclass'=>'Ticket'),
diff --git a/setup/cli/modules/i18n.php b/setup/cli/modules/i18n.php
index 78183885e757872a4816da9e20ffebdaf3ece633..dfa00cc711ca7f512a01585a4d738a08b52a925e 100644
--- a/setup/cli/modules/i18n.php
+++ b/setup/cli/modules/i18n.php
@@ -259,21 +259,28 @@ class i18n_Compiler extends Module {
             }
         }
     }
-    function __read_args($tokens, $constants=1) {
+    function __read_args($tokens, $proto=false) {
         $args = array('forms'=>array());
         $arg = null;
+        $proto = $proto ?: array('forms'=>1);
 
         while (list($string,$T) = $this->__read_next_string($tokens)) {
-            if (count($args['forms']) < $constants && $string) {
+            // Add context and forms
+            if (isset($proto['context']) && !isset($args['context'])) {
+                $args['context'] = $string['form'];
+            }
+            elseif (count($args['forms']) < $proto['forms'] && $string) {
                 if (isset($string['constant']) && !$string['constant']) {
                     throw new Exception($string['form'] . ': Untranslatable string');
                 }
                 $args['forms'][] = $string['form'];
-                $args['line'] = $string['line'];
-                if (isset($string['comments']))
-                    $args['comments'] = array_merge(
-                        @$args['comments'] ?: array(), $string['comments']);
             }
+            // Add usage and comment info
+            if (!isset($args['line']) && isset($string['line']))
+                $args['line'] = $string['line'];
+            if (isset($string['comments']))
+                $args['comments'] = array_merge(
+                    @$args['comments'] ?: array(), $string['comments']);
 
             switch ($T[0]) {
             case ')':
@@ -398,6 +405,10 @@ class i18n_Compiler extends Module {
             if ($f = @$S['flags']) {
                 print "#, ".implode(', ', $f)."\n";
             }
+            if (isset($S['context'])) {
+                print "msgctxt ";
+                $this->__write_string($S['context']);
+            }
             print "msgid ";
             $this->__write_string($S['forms'][0]);
             if (count($S['forms']) == 2) {
@@ -414,7 +425,14 @@ class i18n_Compiler extends Module {
 
     function _make_pot() {
         error_reporting(E_ALL);
-        $funcs = array('__'=>1, '_S'=>1, '_N'=>2, '_SN'=>2);
+        $funcs = array(
+            '__'    => array('forms'=>1),
+            '_S'    => array('forms'=>1),
+            '_N'    => array('forms'=>2),
+            '_NS'   => array('forms'=>2),
+            '_P'    => array('context'=>1, 'forms'=>1),
+            '_NP'   => array('context'=>1, 'forms'=>2),
+        );
         $files = Test::getAllScripts();
         $strings = array();
         foreach ($files as $f) {
@@ -436,6 +454,8 @@ class i18n_Compiler extends Module {
         $primary = $forms[0];
         // Normalize the $primary string
         $primary = preg_replace(array("`\\\(['$])`", '`(?<!\\\)"`'), array("$1", '\"'), $primary);
+        if (isset($call['context']))
+            $primary = $call['context'] . "\x04" . $primary;
         if (!isset($strings[$primary])) {
             $strings[$primary] = array('forms' => $forms);
         }
@@ -447,11 +467,13 @@ class i18n_Compiler extends Module {
             $E['flags'] = array_unique(array_merge(@$E['flags'] ?: array(), $call['flags']));
         if (isset($call['comments']))
             $E['comments'] = array_merge(@$E['comments'] ?: array(), $call['comments']);
+        if (isset($call['context']))
+            $E['context'] = $call['context'];
     }
 
     function __getAllJsPhrases() {
         $strings = array();
-        $funcs = array('__'=>1);
+        $funcs = array('__'=>array('forms'=>1));
         foreach (glob_recursive(ROOT_DIR . "*.js") as $s) {
             $script = file_get_contents($s);
             $s = str_replace(ROOT_DIR, '', $s);
diff --git a/setup/doc/i18n.md b/setup/doc/i18n.md
index 11cf18a5971eeb2638f13eb3f17ce44d1a4c1eda..21048966730715a1ac8270920376b7e6023a958d 100644
--- a/setup/doc/i18n.md
+++ b/setup/doc/i18n.md
@@ -44,13 +44,18 @@ Use a few function calls to get your text localized:
   * `_N('string', 'strings', n)` localize a string with plural alternatives
     to the current user locale preference.
   * `_S('string')` localize the string to the system primary language
-  * `_SN('string', 'strings', n)` localize a string with plural alternatives
+  * `_NS('string', 'strings', n)` localize a string with plural alternatives
     to the system primary language.
   * `_L('string', locale)` localize a string to a named locale. This is
     useful in a ticket thread, for instance, which may have a language
     preference separate from both the user and the system primary language
-  * `_LN('string', 'strings', n, locale)` localize a string with plural
+  * `_NL('string', 'strings', n, locale)` localize a string with plural
     alternatives to a specific locale.
+  * `_P('context', 'string')` localize a string which may have the same
+    source text as another string but have a different context or usage. An
+    example would be `open` used as a noun, adjective, and verb.
+  * `_NP('context', 'string', 'strings', n)` localize a string with plural
+    alternatives which may have different contexts in the source language.
 
 In some cases, it is not possible to use a function to translate your
 string. For instance, if it is used a as a class constant or default value
@@ -69,6 +74,11 @@ set something like this up:
 In this case the localized version of the class variable is translated when
 it is used — not when it is defined.
 
+The `* trans *` text in comment is significant. Both the asterisks and the
+term `trans` are used to signify that the phrase should be made
+translatable. The comment must also be placed immedately before or after the
+string without any other PHP symbols in between (like the semi-colon).
+
 ### Adding context to your strings
 
 Your text may be ambiguous or unclear when it is viewed outside the context
@@ -81,6 +91,21 @@ in the translation template. For instance
     print __('Localized' /* These comments are exported */);
 ```
 
+All types of PHP comments are supported. They must be placed inside the
+parentheses of the localization call. If using single-line comments, use
+multiple lines if necessary to define the call so that your single-line
+comments can be used. For instance:
+
+```php
+    print sprintf(__(
+        // Tokens will be <a> of <b> <object(s)>
+        '%1$d of %2$d %3$s'
+        ),
+        $a, $b,
+        _N('object', 'objects', $b)
+    );
+```
+
 ### Building POT file for translations
 
 Use the command line to compile the POT file to standard out