Index: utils/translation.py
===================================================================
--- utils/translation.py	(revision 1523)
+++ utils/translation.py	(working copy)
@@ -212,6 +212,21 @@
     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 _
Index: utils/text.py
===================================================================
--- utils/text.py	(revision 1523)
+++ utils/text.py	(working copy)
@@ -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 @@
     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))
+
Index: views/i18n.py
===================================================================
--- views/i18n.py	(revision 1523)
+++ views/i18n.py	(working copy)
@@ -1,5 +1,8 @@
+import re
+
 from django.utils import httpwrappers
-from django.utils.translation import check_for_language
+from django.utils.translation import check_for_language, catalog, DjangoTranslation, activate
+from django.utils.text import javascript_quote
 
 def set_language(request):
     """
@@ -20,3 +23,130 @@
         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):
+    """
+    Returns the selected language catalog as a javascript library.
+    """
+    if request.GET:
+        if request.GET.has_key('language'):
+            activate(request.GET['language'])
+    t = catalog()
+    if isinstance(t, DjangoTranslation):
+        src = [LibHead]
+        plural = None
+        for l in t._catalog[''].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._catalog.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)
+    else:
+        src = [NullSource, InterPolate]
+    return httpwrappers.HttpResponse(src, 'text/javascript')
+
