diff --git a/include/i18n/README.md b/include/i18n/README.md
index 5cc60a15e3e727cd07548d44ad6ee55ed5a8c216..2bf307b304c5349a3781857a1b8741224e893c5a 100644
--- a/include/i18n/README.md
+++ b/include/i18n/README.md
@@ -22,10 +22,5 @@ Comments are meant to serve as a consistent hint to the context of the text.
 
 Starting a new translation
 --------------------------
-Copy the entire en_US folder and all its contents and subfolders to a new
-language code, such as de_DE. This is recommended as opposed to copying the
-contents of another translation, because the en_US folder is the most
-up-to-date in terms of content. osTicket is developed with en_US as the
-primary language. Therefore new features will be implemented against the
-en_US data first. If you are beginning a new translation, start with the
-most current texts.
+We are using Crowdin to manage our translations. Visit our translation page
+at http://i18n.crowdin.com/
diff --git a/setup/doc/i18n.md b/setup/doc/i18n.md
new file mode 100644
index 0000000000000000000000000000000000000000..11cf18a5971eeb2638f13eb3f17ce44d1a4c1eda
--- /dev/null
+++ b/setup/doc/i18n.md
@@ -0,0 +1,103 @@
+Internationalization
+====================
+
+System Languages
+----------------
+At various parts of the system, there are several possibilities for the
+language selection of the "local" language:
+
+  1. User (client or agent) preference
+  2. Ticket thread (for system activity notes)
+  3. System (for logs and administrative messages)
+
+The system is flexible enough to support these different cases and provides
+a few wrapper functions to connect your string to the appropriate language.
+Bear in mind when writing new code that strings may need to be translated
+into more than one language. For instance, if a string is to be displayed as
+an error in a web page as well as appear in an email to the administrator,
+it may need to be translated differently for both.
+
+Consider a Spanish-speaking user visiting a German-based help desk. After
+attempting to log in several times, they receive an error banner with a
+particular message. A message is also sent to the site administrator warning
+about possible brute force attack. These messages will need to consider
+different audiences when being localized. The site administrator should
+receive a warning email in German; whereas the user should see a Spanish
+error message with details about what to do next.
+
+Creating localized strings
+--------------------------
+Creating localized strings in osTicket is similar to creating localized
+strings in any gettext-based project. osTicket has opted to use a pure-php
+version of gettext in order to avoid possible pitfalls surrounding usage of
+gettext with PHP including
+
+  * MO file caching requiring HTTP server restart
+  * `gettext` missing from the PHP installation
+  * Requirement of locale pre-configuration on the server
+
+### Adding new strings
+
+Use a few function calls to get your text localized:
+
+  * `__('string')` localize the string to the current user preference
+  * `_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
+    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
+    alternatives to a specific locale.
+
+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
+for a class variable. In such a case, a hint can be used to tell the POT
+scanner to translate the string for use elsewhere. As an example, one might
+set something like this up:
+
+```php
+    class A {
+        static $name = /* trans */ 'Localized string';
+    }
+
+    print __(A::$name);
+```
+
+In this case the localized version of the class variable is translated when
+it is used — not when it is defined.
+
+### Adding context to your strings
+
+Your text may be ambiguous or unclear when it is viewed outside the context
+of the code in which it appears. The system allows adding of comments
+similar to the stock gettext tools. Any comments written directly beside
+(behind or in front of) a localized string will be captured with the string
+in the translation template. For instance
+
+```php
+    print __('Localized' /* These comments are exported */);
+```
+
+### Building POT file for translations
+
+Use the command line to compile the POT file to standard out
+
+    php setup/cli/manage.php i18n make-pot > message.pot
+
+### Building language packs
+
+In an effort for the php version of gettext to offer similar performance to
+the extension counterpart, a variant of the MO file is used which is a PHP
+serialized array written to a file. The original MO file functions basically
+like a text array. In stead of searching through the MO file for each string
+to be translated, the original and translated texts are placed into a hash
+array for quick access and the hash array is serialized for the language
+pack. At runtime, the hash array is recreated from the export and the
+strings are quickly accessed from the PHP hash array.
+
+A MO file can be manually compiled using a command-line interface
+
+    php include/class.translation.php message.mo > message.mo.php