diff --git a/django/bin/make-messages.py b/django/bin/make-messages.py
index 92f6cbfd9a..e8c21db70f 100755
--- a/django/bin/make-messages.py
+++ b/django/bin/make-messages.py
@@ -7,6 +7,8 @@ import getopt
 
 from django.utils.translation import templateize
 
+pythonize_re = re.compile(r'\n\s*//')
+
 localedir = None
 
 if os.path.isdir(os.path.join('conf', 'locale')):
@@ -39,6 +41,9 @@ for o, v in opts:
     elif o == '-a':
         all = True
 
+if domain not in ('django', 'djangojs'):
+    print "currently make-messages.py only supports domains 'django' and 'djangojs'"
+    sys.exit(1)
 if (lang is None and not all) or domain is None:
     print "usage: make-messages.py -l <language>"
     print "   or: make-messages.py -a"
@@ -66,7 +71,28 @@ for lang in languages:
 
     for (dirpath, dirnames, filenames) in os.walk("."):
         for file in filenames:
-            if file.endswith('.py') or file.endswith('.html'):
+            if domain == 'djangojs' and file.endswith('.js'):
+                if verbose: sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
+                src = open(os.path.join(dirpath, file), "rb").read()
+                src = pythonize_re.sub('\n#', src)
+                open(os.path.join(dirpath, '%s.py' % file), "wb").write(src)
+                thefile = '%s.py' % file
+                cmd = 'xgettext %s -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy -o - "%s"' % (
+                    os.path.exists(potfile) and '--omit-header' or '', domain, os.path.join(dirpath, thefile))
+                (stdin, stdout, stderr) = os.popen3(cmd, 'b')
+                msgs = stdout.read()
+                errors = stderr.read()
+                if errors: 
+                    print "errors happened while running xgettext on %s" % file
+                    print errors
+                    sys.exit(8)
+                old = '#: '+os.path.join(dirpath, thefile)[2:]
+                new = '#: '+os.path.join(dirpath, file)[2:]
+                msgs = msgs.replace(old, new)
+                if msgs:
+                    open(potfile, 'ab').write(msgs)
+                os.unlink(os.path.join(dirpath, thefile))
+            elif domain == 'django' and (file.endswith('.py') or file.endswith('.html')):
                 thefile = file
                 if file.endswith('.html'):
                     src = open(os.path.join(dirpath, file), "rb").read()
@@ -91,22 +117,23 @@ for lang in languages:
                 if thefile != file:
                     os.unlink(os.path.join(dirpath, thefile))
 
-    (stdin, stdout, stderr) = os.popen3('msguniq %s' % potfile, 'b')
-    msgs = stdout.read()
-    errors = stderr.read()
-    if errors:
-        print "errors happened while running msguniq"
-        print errors
-        sys.exit(8)
-    open(potfile, 'w').write(msgs)
-    if os.path.exists(pofile):
-        (stdin, stdout, stderr) = os.popen3('msgmerge -q %s %s' % (pofile, potfile), 'b')
+    if os.path.exists(potfile):
+        (stdin, stdout, stderr) = os.popen3('msguniq %s' % potfile, 'b')
         msgs = stdout.read()
         errors = stderr.read()
         if errors:
-            print "errors happened while running msgmerge"
+            print "errors happened while running msguniq"
             print errors
             sys.exit(8)
-    open(pofile, 'wb').write(msgs)
-    os.unlink(potfile)
+        open(potfile, 'w').write(msgs)
+        if os.path.exists(pofile):
+            (stdin, stdout, stderr) = os.popen3('msgmerge -q %s %s' % (pofile, potfile), 'b')
+            msgs = stdout.read()
+            errors = stderr.read()
+            if errors:
+                print "errors happened while running msgmerge"
+                print errors
+                sys.exit(8)
+        open(pofile, 'wb').write(msgs)
+        os.unlink(potfile)
 
diff --git a/django/utils/text.py b/django/utils/text.py
index 6ac8352ac6..8206095f42 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -1,5 +1,7 @@
 import re
 
+from django.conf.settings import DEFAULT_CHARSET
+
 # Capitalizes the first letter of a string.
 capfirst = lambda x: x and x[0].upper() + x[1:]
 
@@ -90,3 +92,20 @@ def compress_string(s):
     zfile.write(s)
     zfile.close()
     return zbuf.getvalue()
+
+ustring_re = re.compile(u"([\u0080-\uffff])")
+def javascript_quote(s):
+
+    def fix(match):
+        return r"\u%04x" % ord(match.group(1))
+
+    if type(s) == str:
+        s = s.decode(DEFAULT_ENCODING)
+    elif type(s) != unicode:
+        raise TypeError, s
+    s = s.replace('\\', '\\\\')
+    s = s.replace('\n', '\\n')
+    s = s.replace('\t', '\\t')
+    s = s.replace("'", "\\'")
+    return str(ustring_re.sub(fix, s))
+
diff --git a/django/utils/translation.py b/django/utils/translation.py
index a8a943e391..9c36850fb9 100644
--- a/django/utils/translation.py
+++ b/django/utils/translation.py
@@ -212,6 +212,21 @@ def get_language():
     from django.conf.settings import LANGUAGE_CODE
     return LANGUAGE_CODE
 
+def catalog():
+    """
+    This function returns the current active catalog for further processing.
+    This can be used if you need to modify the catalog or want to access the
+    whole message catalog instead of just translating one string.
+    """
+    global _default, _active
+    t = _active.get(currentThread(), None)
+    if t is not None:
+        return t
+    if _default is None:
+        from django.conf import settings
+        _default = translation(settings.LANGUAGE_CODE)
+    return _default
+
 def gettext(message):
     """
     This function will be patched into the builtins module to provide the _
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 7bb2f00b2f..7b67cd2417 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -1,5 +1,12 @@
+import re
+import os
+
+import gettext as gettext_module
+
 from django.utils import httpwrappers
-from django.utils.translation import check_for_language
+from django.utils.translation import check_for_language, activate, to_locale, get_language
+from django.utils.text import javascript_quote
+from django.conf import settings
 
 def set_language(request):
     """
@@ -20,3 +27,163 @@ def set_language(request):
         else:
             response.set_cookie('django_language', lang_code)
     return response
+
+NullSource = """
+/* gettext identity library */
+
+function gettext(msgid) {
+    return msgid;
+}
+
+function ngettext(singular, plural, count) {
+    if (count == 1) {
+        return singular;
+    } else {
+        return plural;
+    }
+}
+
+function gettext_noop(msgid) {
+    return msgid;
+}
+"""
+
+LibHead = """
+/* gettext library */
+
+var catalog = new Array();
+"""
+
+LibFoot = """
+
+function gettext(msgid) {
+    var value = catalog[msgid];
+    if (typeof(value) == 'undefined') {
+        return msgid;
+    } else {
+        if (typeof(value) == 'string') {
+            return value;
+        } else {
+            return value[0];
+        }
+    }
+}
+
+function ngettext(singular, plural, count) {
+    value = catalog[singular];
+    if (typeof(value) == 'undefined') {
+        if (count == 1) {
+            return singular;
+        } else {
+            return plural;
+        }
+    } else {
+        return value[pluralidx(count)];
+    }
+}
+
+function gettext_noop(msgid) {
+    return msgid;
+}
+"""
+
+SimplePlural = """
+function pluralidx(count) {
+    if (count == 1) {
+        return 0;
+    } else {
+        return 1;
+    }
+}
+"""
+
+InterPolate = r"""
+function interpolate(fmt, obj, named) {
+    if (named) {
+        return fmt.replace(/%\(\w+\)s/, function(match){return String(obj[match.slice(2,-2)])});
+    } else {
+        return fmt.replace(/%s/, function(match){return String(obj.shift())});
+    }
+}
+"""
+
+def javascript_catalog(request, domain='djangojs', packages=None):
+    """
+    Returns the selected language catalog as a javascript library.
+
+    Receives the list of packages to check for translations in the
+    packages parameter either from an infodict or as a +-delimited
+    string from the request. Default is 'django.conf'.
+
+    Additionally you can override the gettext domain for this view,
+    but usually you don't want to do that, as JavaScript messages
+    go to the djangojs domain. But this might be needed if you
+    deliver your JavaScript source from Django templates.
+    """
+    if request.GET:
+        if request.GET.has_key('language'):
+            if check_for_language(request.GET['language']):
+                activate(request.GET['language'])
+    if packages is None:
+        packages = ['django.conf']
+    if type(packages) in (str, unicode):
+        packages = packages.split('+')
+    default_locale = to_locale(settings.LANGUAGE_CODE)
+    locale = to_locale(get_language())
+    t = {}
+    paths = []
+    for package in packages:
+        p = __import__(package, {}, {}, [''])
+        path = os.path.join(os.path.dirname(p.__file__), 'locale')
+        paths.append(path)
+        #!!! add loading of catalogs from settings.LANGUAGE_CODE and request.LANGUAGE_CODE!
+        try:
+            catalog = gettext_module.translation(domain, path, [default_locale])
+        except IOError, e:
+            catalog = None
+        if catalog is not None:
+            t.update(catalog._catalog)
+    if locale != default_locale:
+        for path in paths:
+            try:
+                catalog = gettext_module.translation(domain, path, [locale])
+            except IOError, e:
+                catalog = None
+            if catalog is not None:
+                t.update(catalog._catalog)
+    src = [LibHead]
+    plural = None
+    for l in t[''].split('\n'):
+        if l.startswith('Plural-Forms:'):
+            plural = l.split(':',1)[1].strip()
+    if plural is not None:
+        # this should actually be a compiled function of a typical plural-form:
+        # Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
+        plural = [el.strip() for el in plural.split(';') if el.strip().startswith('plural=')][0].split('=',1)[1]
+        src.append('function pluralidx(n) {\n    return %s;\n}\n' % plural)
+    else:
+        src.append(SimplePlural)
+    csrc = []
+    pdict = {}
+    for k, v in t.items():
+        if k == '':
+            continue
+        if type(k) in (str, unicode):
+            csrc.append("catalog['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(v)))
+        elif type(k) == tuple:
+            if not pdict.has_key(k[0]):
+                pdict[k[0]] = k[1]
+            else:
+                pdict[k[0]] = max(k[1], pdict[k[0]])
+            csrc.append("catalog['%s'][%d] = '%s';\n" % (javascript_quote(k[0]), k[1], javascript_quote(v)))
+        else:
+            raise TypeError, k
+    csrc.sort()
+    for k,v in pdict.items():
+        src.append("catalog['%s'] = [%s];\n" % (javascript_quote(k), ','.join(["''"]*(v+1))))
+    src.extend(csrc)
+    src.append(LibFoot)
+    src.append(InterPolate)
+    src = ''.join(src)
+    return httpwrappers.HttpResponse(src, 'text/javascript')
+