diff --git a/django/bin/make-language-info.py b/django/bin/make-language-info.py
new file mode 100644
index 0000000..707f181
--- /dev/null
+++ b/django/bin/make-language-info.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+LANGUAGE_INFO_PY_FILE = 'django/conf/language_info.py'
+
+# Need to ensure that the i18n framework is enabled
+from django.conf import settings
+settings.configure(USE_I18N = True)
+
+TEMPLATE = """\
+# This file was generated with django/bin/make-language-info.py and it is not
+# recommended to edit it by hand.  For new translations, compile the new .po
+# files and edit the LANGUAGES setting, and finally re-run
+# django/bin/make-language-info.py to generate this file.
+
+language_info = {
+ %s
+}
+"""
+
+def make_language_info(target_path):
+    print 'Writing language information to\n%s' % target_path
+    # Create a dummy language_info.py file to make sure we can import the
+    # translation module.
+    file(target_path, 'w').write(TEMPLATE % '')
+    from django.utils import translation
+    from pprint import pformat
+    language_info = {}
+    for code, name in settings.LANGUAGES:
+        translation.activate(code)
+        language_info[code] = {"language_code": code,
+                               "name": name,
+                               "name_local": translation.ugettext(name),
+                               "bidi": code in settings.LANGUAGES_BIDI}
+    file(target_path, 'w').write(TEMPLATE % pformat(language_info)[1:-1])
+    # Because the timestamps of the dummy .pyc and real .py may fall on the
+    # same second, we must enforce compilation.
+    import py_compile
+    py_compile.compile(target_path)
+    print 'Done.'
+
+
+if __name__ == "__main__":
+    from os.path import join, dirname, abspath
+    path = abspath(join(dirname(__file__), '..', '..', LANGUAGE_INFO_PY_FILE))
+    make_language_info(path)
diff --git a/django/conf/language_info.py b/django/conf/language_info.py
new file mode 100644
index 0000000..794b4ee
--- /dev/null
+++ b/django/conf/language_info.py
@@ -0,0 +1,211 @@
+# This file was generated with django/bin/make-language-info.py and it is not
+# recommended to edit it by hand.  For new translations, compile the new .po
+# files and edit the LANGUAGES setting, and finally re-run
+# django/bin/make-language-info.py to generate this file.
+
+language_info = {
+ 'ar': {'bidi': True,
+        'language_code': 'ar',
+        'name': 'Arabic',
+        'name_local': u'\u0627\u0644\u0639\u0631\u0628\u064a\u0651\u0629'},
+ 'bg': {'bidi': False,
+        'language_code': 'bg',
+        'name': 'Bulgarian',
+        'name_local': u'\u0431\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438'},
+ 'bn': {'bidi': False,
+        'language_code': 'bn',
+        'name': 'Bengali',
+        'name_local': u'\u09ac\u09be\u0982\u09b2\u09be'},
+ 'ca': {'bidi': False,
+        'language_code': 'ca',
+        'name': 'Catalan',
+        'name_local': u'catal\xe0'},
+ 'cs': {'bidi': False,
+        'language_code': 'cs',
+        'name': 'Czech',
+        'name_local': u'\u010desky'},
+ 'cy': {'bidi': False,
+        'language_code': 'cy',
+        'name': 'Welsh',
+        'name_local': u'Cymraeg'},
+ 'da': {'bidi': False,
+        'language_code': 'da',
+        'name': 'Danish',
+        'name_local': u'Dansk'},
+ 'de': {'bidi': False,
+        'language_code': 'de',
+        'name': 'German',
+        'name_local': u'Deutsch'},
+ 'el': {'bidi': False,
+        'language_code': 'el',
+        'name': 'Greek',
+        'name_local': u'\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac'},
+ 'en': {'bidi': False,
+        'language_code': 'en',
+        'name': 'English',
+        'name_local': u'English'},
+ 'es': {'bidi': False,
+        'language_code': 'es',
+        'name': 'Spanish',
+        'name_local': u'espa\xf1ol'},
+ 'es-ar': {'bidi': False,
+           'language_code': 'es-ar',
+           'name': 'Argentinean Spanish',
+           'name_local': u'espa\xf1ol de Argentina'},
+ 'et': {'bidi': False,
+        'language_code': 'et',
+        'name': 'Estonian',
+        'name_local': u'eesti'},
+ 'eu': {'bidi': False,
+        'language_code': 'eu',
+        'name': 'Basque',
+        'name_local': u'Basque'},
+ 'fa': {'bidi': True,
+        'language_code': 'fa',
+        'name': 'Persian',
+        'name_local': u'\u0641\u0627\u0631\u0633\u06cc'},
+ 'fi': {'bidi': False,
+        'language_code': 'fi',
+        'name': 'Finnish',
+        'name_local': u'suomi'},
+ 'fr': {'bidi': False,
+        'language_code': 'fr',
+        'name': 'French',
+        'name_local': u'Fran\xe7ais'},
+ 'ga': {'bidi': False,
+        'language_code': 'ga',
+        'name': 'Irish',
+        'name_local': u'Gaeilge'},
+ 'gl': {'bidi': False,
+        'language_code': 'gl',
+        'name': 'Galician',
+        'name_local': u'galego'},
+ 'he': {'bidi': True,
+        'language_code': 'he',
+        'name': 'Hebrew',
+        'name_local': u'\u05e2\u05d1\u05e8\u05d9\u05ea'},
+ 'hi': {'bidi': False,
+        'language_code': 'hi',
+        'name': 'Hindi',
+        'name_local': u'Hindi'},
+ 'hr': {'bidi': False,
+        'language_code': 'hr',
+        'name': 'Croatian',
+        'name_local': u'Hrvatski'},
+ 'hu': {'bidi': False,
+        'language_code': 'hu',
+        'name': 'Hungarian',
+        'name_local': u'Magyar'},
+ 'is': {'bidi': False,
+        'language_code': 'is',
+        'name': 'Icelandic',
+        'name_local': u'\xcdslenska'},
+ 'it': {'bidi': False,
+        'language_code': 'it',
+        'name': 'Italian',
+        'name_local': u'italiano'},
+ 'ja': {'bidi': False,
+        'language_code': 'ja',
+        'name': 'Japanese',
+        'name_local': u'\u65e5\u672c\u8a9e'},
+ 'ka': {'bidi': False,
+        'language_code': 'ka',
+        'name': 'Georgian',
+        'name_local': u'\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8'},
+ 'km': {'bidi': False,
+        'language_code': 'km',
+        'name': 'Khmer',
+        'name_local': u'Khmer'},
+ 'kn': {'bidi': False,
+        'language_code': 'kn',
+        'name': 'Kannada',
+        'name_local': u'Kannada'},
+ 'ko': {'bidi': False,
+        'language_code': 'ko',
+        'name': 'Korean',
+        'name_local': u'\ud55c\uad6d\uc5b4'},
+ 'lt': {'bidi': False,
+        'language_code': 'lt',
+        'name': 'Lithuanian',
+        'name_local': u'Lithuanian'},
+ 'lv': {'bidi': False,
+        'language_code': 'lv',
+        'name': 'Latvian',
+        'name_local': u'Latvie\u0161u'},
+ 'mk': {'bidi': False,
+        'language_code': 'mk',
+        'name': 'Macedonian',
+        'name_local': u'\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438'},
+ 'nl': {'bidi': False,
+        'language_code': 'nl',
+        'name': 'Dutch',
+        'name_local': u'Nederlands'},
+ 'no': {'bidi': False,
+        'language_code': 'no',
+        'name': 'Norwegian',
+        'name_local': u'Norsk'},
+ 'pl': {'bidi': False,
+        'language_code': 'pl',
+        'name': 'Polish',
+        'name_local': u'polski'},
+ 'pt': {'bidi': False,
+        'language_code': 'pt',
+        'name': 'Portuguese',
+        'name_local': u'Portuguese'},
+ 'pt-br': {'bidi': False,
+           'language_code': 'pt-br',
+           'name': 'Brazilian Portuguese',
+           'name_local': u'Portugu\xeas Brasileiro'},
+ 'ro': {'bidi': False,
+        'language_code': 'ro',
+        'name': 'Romanian',
+        'name_local': u'Romana'},
+ 'ru': {'bidi': False,
+        'language_code': 'ru',
+        'name': 'Russian',
+        'name_local': u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439'},
+ 'sk': {'bidi': False,
+        'language_code': 'sk',
+        'name': 'Slovak',
+        'name_local': u'slovensk\xfd'},
+ 'sl': {'bidi': False,
+        'language_code': 'sl',
+        'name': 'Slovenian',
+        'name_local': u'Sloven\u0161\u010dina'},
+ 'sr': {'bidi': False,
+        'language_code': 'sr',
+        'name': 'Serbian',
+        'name_local': u'\u0421\u0440\u043f\u0441\u043a\u0438'},
+ 'sv': {'bidi': False,
+        'language_code': 'sv',
+        'name': 'Swedish',
+        'name_local': u'Svenska'},
+ 'ta': {'bidi': False,
+        'language_code': 'ta',
+        'name': 'Tamil',
+        'name_local': u'\u0ba4\u0bae\u0bbf\u0bb4\u0bcd'},
+ 'te': {'bidi': False,
+        'language_code': 'te',
+        'name': 'Telugu',
+        'name_local': u'\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41'},
+ 'th': {'bidi': False,
+        'language_code': 'th',
+        'name': 'Thai',
+        'name_local': u'Thai'},
+ 'tr': {'bidi': False,
+        'language_code': 'tr',
+        'name': 'Turkish',
+        'name_local': u'T\xfcrk\xe7e'},
+ 'uk': {'bidi': False,
+        'language_code': 'uk',
+        'name': 'Ukrainian',
+        'name_local': u'\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430'},
+ 'zh-cn': {'bidi': False,
+           'language_code': 'zh-cn',
+           'name': 'Simplified Chinese',
+           'name_local': u'\u7b80\u4f53\u4e2d\u6587'},
+ 'zh-tw': {'bidi': False,
+           'language_code': 'zh-tw',
+           'name': 'Traditional Chinese',
+           'name_local': u'\u7e41\u9ad4\u4e2d\u6587'}
+}
diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
index 0467433..b656b9d 100644
--- a/django/templatetags/i18n.py
+++ b/django/templatetags/i18n.py
@@ -17,6 +17,34 @@ class GetAvailableLanguagesNode(Node):
         context[self.variable] = [(k, translation.ugettext(v)) for k, v in settings.LANGUAGES]
         return ''
 
+class GetLanguageInfoNode(Node):
+    def __init__(self, lang_code, variable):
+        self.lang_code = Variable(lang_code)
+        self.variable = variable
+
+    def render(self, context):
+        lang_code = self.lang_code.resolve(context)
+        context[self.variable] = translation.get_language_info(lang_code)
+        return ''
+
+class GetLanguageInfoListNode(Node):
+    def __init__(self, languages, variable):
+        self.languages = Variable(languages)
+        self.variable = variable
+
+    def get_language_info(self, language):
+        # ``language`` is either a language code string or a sequence with the
+        # language code as its first item
+        if len(language[0]) > 1:
+            return translation.get_language_info(language[0])
+        else:
+            return translation.get_language_info(str(language))
+
+    def render(self, context):
+        languages = self.languages.resolve(context)
+        context[self.variable] = [self.get_language_info(l) for l in languages]
+        return ''
+
 class GetCurrentLanguageNode(Node):
     def __init__(self, variable):
         self.variable = variable
@@ -107,6 +135,55 @@ def do_get_available_languages(parser, token):
         raise TemplateSyntaxError, "'get_available_languages' requires 'as variable' (got %r)" % args
     return GetAvailableLanguagesNode(args[2])
 
+def do_get_language_info(parser, token):
+    """
+    This will store the language information dictionary for the given language
+    code in a context variable.
+
+    Usage::
+
+        {% get_language_info for language_code as l %}
+        {{ l.language_code }}
+        {{ l.name }}
+        {{ l.name_local }}
+        {{ l.bidi|yesno:"bi-directional,uni-directional" }}
+    """
+    args = token.contents.split()
+    if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
+        raise TemplateSyntaxError, "'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:])
+    return GetLanguageInfoNode(args[2], args[4])
+
+def do_get_language_info_list(parser, token):
+    """
+    This will store a list of language information dictionaries for the given
+    language codes in a context variable.  The language codes can be specified
+    ither as a list of strings or a settings.LANGUAGES style tuple (or any
+    sequence of sequences whose first items are language codes).
+
+    Usage::
+
+        {% get_language_info_list for LANGUAGES as langs %}
+        {% for l in langs %}
+          {{ l.language_code }}
+          {{ l.name }}
+          {{ l.name_local }}
+          {{ l.bidi|yesno:"bi-directional,uni-directional" }}
+        {% endfor %}
+    """
+    args = token.contents.split()
+    if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
+        raise TemplateSyntaxError, "'%s' requires 'for sequence as variable' (got %r)" % (args[0], args[1:])
+    return GetLanguageInfoListNode(args[2], args[4])
+
+def language_name(lang_code):
+    return translation.get_language_info(lang_code)['name']
+
+def language_name_local(lang_code):
+    return translation.get_language_info(lang_code)['name_local']
+
+def language_bidi(lang_code):
+    return translation.get_language_info(lang_code)['bidi']
+
 def do_get_current_language(parser, token):
     """
     This will store the current language in the context.
@@ -253,7 +330,13 @@ def do_block_translate(parser, token):
             counter)
 
 register.tag('get_available_languages', do_get_available_languages)
+register.tag('get_language_info', do_get_language_info)
+register.tag('get_language_info_list', do_get_language_info_list)
 register.tag('get_current_language', do_get_current_language)
 register.tag('get_current_language_bidi', do_get_current_language_bidi)
 register.tag('trans', do_translate)
 register.tag('blocktrans', do_block_translate)
+
+register.filter(language_name)
+register.filter(language_name_local)
+register.filter(language_bidi)
diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py
index c0a0df9..a942fe2 100644
--- a/django/utils/translation/__init__.py
+++ b/django/utils/translation/__init__.py
@@ -3,6 +3,10 @@ Internationalization support.
 """
 from django.utils.functional import lazy
 from django.utils.encoding import force_unicode
+try:
+    from django.conf.language_info import language_info
+except ImportError:
+    raise ImportError("Module django.conf.language_info not found. Run django/bin/make-language-info.py to generate it.")
 
 __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
         'ngettext_lazy', 'string_concat', 'activate', 'deactivate',
@@ -109,3 +113,9 @@ def string_concat(*strings):
     """
     return u''.join([force_unicode(s) for s in strings])
 string_concat = lazy(string_concat, unicode)
+
+def get_language_info(lang_code):
+    try:
+        return language_info[lang_code]
+    except KeyError:
+        raise KeyError("Unknown language code %r.  If you have added a new translation, remember to add the language to the LANGUAGES setting and update the django/conf/language_info.py file by running django/bin/make-language-info.py." % lang_code)
diff --git a/docs/topics/i18n.txt b/docs/topics/i18n.txt
index 140adce..24b12fb 100644
--- a/docs/topics/i18n.txt
+++ b/docs/topics/i18n.txt
@@ -726,6 +726,64 @@ in ``request.LANGUAGE_CODE``.
 
 .. _translations-in-your-own-projects:
 
+Local names of languages
+========================
+
+The ``get_language_info()`` function provides detailed information about
+languages::
+
+    >>> form django.utils.translation import get_language_info
+    >>> l = get_language_info('de')
+    >>> print l['name'], l['name_local'], l['bidi']
+    German Deutsch False
+
+The ``name`` and ``name_local`` attributes of the dictionary contain the name
+of the language in English and the language itself, respectively.  The ``bidi``
+attribute is True only for bi-directional languages.
+
+In templates, you can use special template tags or filters to retrieve the same
+information. To get information about a single language, use the
+``{% get_language_info %}`` tag::
+
+    {% get_language_info for LANGUAGE_CODE as lang %}
+    {% get_language_info for "pl" as lang %}
+
+You can then access the information::
+
+    Language code: {{ lang.language_code }}<br />
+    Name of language: {{ lang.name_local }}<br />
+    Name in English: {{ lang.name }}<br />
+    Bi-directional: {{ lang.bidi }}
+
+You can also use the ``{% get_language_info_list %}`` template tag to retrieve
+information for a list of languages (e.g. active languages as specified in
+``settings.LANGUAGES``).  See `The set_language redirect view`_ for an example
+of how to display a language selector using ``{% get_language_info_list %}``.
+
+In addition to ``settings.LANGUAGES`` style nested tuples,
+``{% get_language_info_list %}`` supports simple lists of language codes.  If
+you do this in your view::
+
+  return render_to_response('mytemplate.html',
+                            {'available_languages': ['en', 'es', 'fr']},
+                            RequestContext(request))
+
+you can iterate those languages in the template::
+
+  {% get_language_info_list available_languages %}
+  {% for l in available_languages %} ... {% endfor %}
+
+There are also simple filters available for convenience:
+
+    * ``{{ LANGUAGE_CODE|language_name }}``  ("German")
+    * ``{{ LANGUAGE_CODE|language_name_local }}`` ("Deutsch")
+    * ``{{ LANGUAGE_CODE|bidi }}`` (False)
+
+The source of the language information is the ``django.conf.language_info``
+module.  If you add a new translation, you should run the
+``django/bin/make-language-info.py`` script to insert the information for the
+added language.
+
 Using translations in your own projects
 =======================================
 
@@ -817,18 +875,22 @@ algorithm:
     * If that's empty -- say, if a user's browser suppresses that header --
       then the user will be redirected to ``/`` (the site root) as a fallback.
 
-Here's example HTML template code:
+Here's example HTML template code (see `Local names of languages`_ for more
+information)::
 
 .. code-block:: html+django
 
+    {% get_language_info_list for LANGUAGES as available_languages %}
     <form action="/i18n/setlang/" method="post">
-    <input name="next" type="hidden" value="/next/page/" />
-    <select name="language">
-    {% for lang in LANGUAGES %}
-    <option value="{{ lang.0 }}">{{ lang.1 }}</option>
-    {% endfor %}
-    </select>
-    <input type="submit" value="Go" />
+      <input name="next" type="hidden" value="/next/page/" />
+      <select name="language">
+        {% for lang in available_languages %}
+        <option value="{{ lang.language_code }}" {% ifequal lang.language_code LANGUAGE_CODE %}selected{% endifequal %}>
+          {{ lang.name_local }}
+        </option>
+        {% endfor %}
+      </select>
+      <input type="submit" value="Go" />
     </form>
 
 Translations and JavaScript
diff --git a/tests/regressiontests/i18n/language_info.py b/tests/regressiontests/i18n/language_info.py
new file mode 100644
index 0000000..b5a1a3e
--- /dev/null
+++ b/tests/regressiontests/i18n/language_info.py
@@ -0,0 +1,18 @@
+tests = """
+>>> from django.utils.translation import get_language_info
+>>> from pprint import pprint
+>>> pprint(get_language_info('de'))
+{'bidi': False,
+ 'language_code': 'de',
+ 'name': 'German',
+ 'name_local': u'Deutsch'}
+"""
+tests = """
+>>> from django.utils.translation import get_language_info
+>>> from pprint import pprint
+>>> pprint(get_language_info('de'))
+{'bidi': False,
+ 'language_code': 'de',
+ 'name': 'German',
+ 'name_local': u'Deutsch'}
+"""
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
index 94e792c..81cfc1d 100644
--- a/tests/regressiontests/i18n/tests.py
+++ b/tests/regressiontests/i18n/tests.py
@@ -1,5 +1,5 @@
 # coding: utf-8
-import misc
+import misc, language_info
 
 regressions = ur"""
 Format string interpolation should work with *_lazy objects.
@@ -68,5 +68,6 @@ Password
 
 __test__ = {
     'regressions': regressions,
+    'language_info': language_info.tests,
     'misc': misc.tests,
 }
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index d740dbf..a60b83d 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -869,6 +869,14 @@ class Templates(unittest.TestCase):
             'i18n21': ('{% load i18n %}{% blocktrans %}{{ andrew }}{% endblocktrans %}', {'andrew': mark_safe('a & b')}, u'a & b'),
             'i18n22': ('{% load i18n %}{% trans andrew %}', {'andrew': mark_safe('a & b')}, u'a & b'),
 
+            # retrieving language information
+            'i18n23': ('{% load i18n %}{% get_language_info for "de" as l %}{{ l.language_code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}', {}, 'de: German/Deutsch bidi=False'),
+            'i18n24': ('{% load i18n %}{% get_language_info for LANGUAGE_CODE as l %}{{ l.language_code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}', {'LANGUAGE_CODE': 'fi'}, 'fi: Finnish/suomi bidi=False'),
+            'i18n25': ('{% load i18n %}{% get_language_info_list for langcodes as langs %}{% for l in langs %}{{ l.language_code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}; {% endfor %}', {'langcodes': ['it', 'no']}, u'it: Italian/italiano bidi=False; no: Norwegian/Norsk bidi=False; '),
+            'i18n26': ('{% load i18n %}{% get_language_info_list for langcodes as langs %}{% for l in langs %}{{ l.language_code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}; {% endfor %}', {'langcodes': (('sl', 'Slovenian'), ('fa', 'Persian'))}, u'sl: Slovenian/Sloven\u0161\u010dina bidi=False; fa: Persian/\u0641\u0627\u0631\u0633\u06cc bidi=True; '),
+            'i18n27': ('{% load i18n %}{{ "hu"|language_name }} {{ "hu"|language_name_local }} {{ "hu"|language_bidi }}', {}, u'Hungarian Magyar False'),
+            'i18n28': ('{% load i18n %}{{ langcode|language_name }} {{ langcode|language_name_local }} {{ langcode|language_bidi }}', {'langcode': 'nl'}, u'Dutch Nederlands False'),
+
             ### HANDLING OF TEMPLATE_STRING_IF_INVALID ###################################
 
             'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')),
