Add a new locale from plugin

Usually the best way to add a new language to Discourse is by creating a pull request as described in "https://meta.discourse.org/t/how-to-add-a-new-language/14970".
The following guide is for you, if you want to use a language that can't be added to core right now.
Adding a locale that extends an existing locale
Let's say you want to add a Spanish locale for Mexico (
es_MX). Discourse already ships a Spanish locale (
es), so this is quite easy.
Your plugin's directory structure should look like this:
custom-locales
├── assets
│   └── locales
│       └── es_MX.js.erb
├── config
│   └── locales
│       ├── client.es_MX.yml
│       └── server.es_MX.yml
└── plugin.rb
plugin.rb
You add the new locale by calling 
register_locale.
The first parameter is required and must be the language code.
name (the English name of the locale) and 
nativeName (the name shown in the user interface) can be omitted, if the language code exists in 
names.yml.
fallbackLocale is the language code that should be used as fall back for missing translations and pluralization rules.
# name: custom-locales
# about: An example plugin for adding new locales.
# version: 1.0
register_locale("es_MX", name: "Spanish (Mexico)", nativeName: "Español (México)", fallbackLocale: "es")
<locale>.js.erb
The content of 
assets/locales/es_MX.js.erb is quite simple -- make sure you replace the locale code within that file with the one you need.
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:es_MX) %>
Translation files in config/locales
The files 
config/locales/client.es_MX.yml and 
config/locales/server.es_MX.yml contain the translations you want to use. You don't need to provide all translations needed by Discourse. The 
fallbackLocale and English will be used in case of missing translations.
Adding a new locale
Adding a completely new locale, that shouldn't fall back to an existing locale, is a little bit more work.
Your plugin's directory structure should look like this:
custom-locales
├── assets
│   └── locales
│       └── foo.js.erb
├── config
│   └── locales
│       ├── client.foo.yml
│       └── server.foo.yml
├── lib
│   └── javascripts
│       └── locale
│           ├── message_format
│           │   └── foo.js
│           └── moment_js
│               └── foo.js
│           └── moment_js_timezones
│               └── foo.js
└── plugin.rb
plugin.rb
You add the new locale by calling 
register_locale.
The first parameter is required and must be the language code.
name (the English name of the locale) and 
nativeName (the name shown in the user interface) can be omitted, if the language code exists in 
names.yml, otherwise you should set them too.
plural describes the language's pluralization rules. Take a look at 
plurals.rb for inspiration. Also, you can omit this parameter if 
plurals.rb already contains your language code.
# name: custom-locales
# about: An example plugin for adding new locales.
# version: 1.0
register_locale(
  "foo",
  name: "Foo",
  nativeName: "Foo Bar",
  plural: {
    keys: [:one, :other],
    rule: lambda { |n| n == 1 ? :one : :other }
  }
)
<locale>.js.erb
The content of 
assets/locales/foo.js.erb is quite simple -- make sure you replace the locale code within that file with the one you need.
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:foo) %>
Translation files in config/locales
The files 
config/locales/client.foo.yml and 
config/locales/server.foo.yml contain the translations you want to use. You don't need to provide all translations needed by Discourse. The English translations will be used in case of missing translations.
FAQ
Discourse doesn't load my locale. What is wrong?
Make sure that the plugin is enabled, that you registered the locale correctly and that all required files exist and have the correct language code in the filename. Discourse doesn't load locales when it detects missing files.