skip pdf processing if necessary
[mediagoblin.git] / mediagoblin / tools / translate.py
index b99c4aa4ffe16c7707c7fd6c7d8aa0208c448d0e..b20e57d1ea2791e1e9773ed95d532cc41e5a8c7b 100644 (file)
@@ -1,5 +1,5 @@
 # GNU MediaGoblin -- federated, autonomous media hosting
-# Copyright (C) 2011 MediaGoblin contributors.  See AUTHORS.
+# Copyright (C) 2011, 2012 MediaGoblin contributors.  See AUTHORS.
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as published by
@@ -16,7 +16,9 @@
 
 import gettext
 import pkg_resources
-from babel.localedata import exists
+
+
+from babel import localedata
 from babel.support import LazyProxy
 
 from mediagoblin import mg_globals
@@ -25,14 +27,40 @@ from mediagoblin import mg_globals
 # Translation tools
 ###################
 
-
+AVAILABLE_LOCALES = None
 TRANSLATIONS_PATH = pkg_resources.resource_filename(
     'mediagoblin', 'i18n')
 
 
+def set_available_locales():
+    """Set available locales for which we have translations"""
+    global AVAILABLE_LOCALES
+    locales=['en', 'en_US'] # these are available without translations
+    for locale in localedata.list():
+        if gettext.find('mediagoblin', TRANSLATIONS_PATH, [locale]):
+            locales.append(locale)
+    AVAILABLE_LOCALES = locales
+
+
+class ReallyLazyProxy(LazyProxy):
+    """
+    Like LazyProxy, except that it doesn't cache the value ;)
+    """
+    @property
+    def value(self):
+        return self._func(*self._args, **self._kwargs)
+
+    def __repr__(self):
+        return "<%s for %s(%r, %r)>" % (
+            self.__class__.__name__,
+            self._func,
+            self._args,
+            self._kwargs)
+
+
 def locale_to_lower_upper(locale):
     """
-    Take a locale, regardless of style, and format it like "en-us"
+    Take a locale, regardless of style, and format it like "en_US"
     """
     if '-' in locale:
         lang, country = locale.split('-', 1)
@@ -46,7 +74,7 @@ def locale_to_lower_upper(locale):
 
 def locale_to_lower_lower(locale):
     """
-    Take a locale, regardless of style, and format it like "en_US"
+    Take a locale, regardless of style, and format it like "en_us"
     """
     if '_' in locale:
         lang, country = locale.split('_', 1)
@@ -57,42 +85,32 @@ def locale_to_lower_lower(locale):
 
 def get_locale_from_request(request):
     """
-    Figure out what target language is most appropriate based on the
-    request
+    Return most appropriate language based on prefs/request request
     """
-    request_form = request.GET or request.POST
+    request_args = (request.args, request.form)[request.method=='POST']
 
-    if request_form.has_key('lang'):
-        return locale_to_lower_upper(request_form['lang'])
+    if 'lang' in request_args:
+        # User explicitely demanded a language, normalize lower_uppercase
+        target_lang = locale_to_lower_upper(request_args['lang'])
 
-    # Your routing can explicitly specify a target language
-    matchdict = request.matchdict or {}
-
-    if matchdict.has_key('locale'):
-        target_lang = matchdict['locale']
-    elif request.session.has_key('target_lang'):
+    elif 'target_lang' in request.session:
+        # TODO: Uh, ohh, this is never ever set anywhere?
         target_lang = request.session['target_lang']
-    # Pull the first acceptable language or English
     else:
-        # WebOb recently changed how it handles determining best language.
-        # Here's a compromise commit that handles either/or...
-        if hasattr(request.accept_language, "best_matches"):
-            accept_lang_matches = request.accept_language.best_matches()
-            if accept_lang_matches:
-                target_lang = accept_lang_matches[0]
-            else:
-                target_lang = 'en'
-        else:
-            target_lang = request.accept.best_match(
-                request.accept_language, 'en')
-
-    return locale_to_lower_upper(target_lang)
+        # Pull the most acceptable language based on browser preferences
+        # This returns one of AVAILABLE_LOCALES which is aready case-normalized.
+        # Note: in our tests request.accept_languages is None, so we need
+        # to explicitely fallback to en here.
+        target_lang = request.accept_languages.best_match(AVAILABLE_LOCALES) \
+                      or "en_US"
+
+    return target_lang
 
 SETUP_GETTEXTS = {}
 
-def setup_gettext(locale):
+def get_gettext_translation(locale):
     """
-    Setup the gettext instance based on this locale
+    Return the gettext instance based on this locale
     """
     # Later on when we have plugins we may want to enable the
     # multi-translations system they have so we can handle plugin
@@ -100,21 +118,19 @@ def setup_gettext(locale):
 
     # TODO: fallback nicely on translations from pt_PT to pt if not
     # available, etc.
-    if SETUP_GETTEXTS.has_key(locale):
+    if locale in SETUP_GETTEXTS:
         this_gettext = SETUP_GETTEXTS[locale]
     else:
         this_gettext = gettext.translation(
             'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
-        if exists(locale):
+        if localedata.exists(locale):
             SETUP_GETTEXTS[locale] = this_gettext
+    return this_gettext
 
-    mg_globals.setup_globals(
-        translations=this_gettext)
 
-
-# Force en to be setup before anything else so that
-# mg_globals.translations is never None
-setup_gettext('en')
+def set_thread_locale(locale):
+    """Set the current translation for this thread"""
+    mg_globals.thread_scope.translations = get_gettext_translation(locale)
 
 
 def pass_to_ugettext(*args, **kwargs):
@@ -124,7 +140,17 @@ def pass_to_ugettext(*args, **kwargs):
     The reason we can't have a global ugettext method is because
     mg_globals gets swapped out by the application per-request.
     """
-    return mg_globals.translations.ugettext(
+    return mg_globals.thread_scope.translations.ugettext(
+        *args, **kwargs)
+
+def pass_to_ungettext(*args, **kwargs):
+    """
+    Pass a translation on to the appropriate ungettext method.
+
+    The reason we can't have a global ugettext method is because
+    mg_globals gets swapped out by the application per-request.
+    """
+    return mg_globals.thread_scope.translations.ungettext(
         *args, **kwargs)
 
 
@@ -134,9 +160,12 @@ def lazy_pass_to_ugettext(*args, **kwargs):
 
     This is useful if you have to define a translation on a module
     level but you need it to not translate until the time that it's
-    used as a string.
+    used as a string. For example, in:
+        def func(self, message=_('Hello boys and girls'))
+
+    you would want to use the lazy version for _.
     """
-    return LazyProxy(pass_to_ugettext, *args, **kwargs)
+    return ReallyLazyProxy(pass_to_ugettext, *args, **kwargs)
 
 
 def pass_to_ngettext(*args, **kwargs):
@@ -146,7 +175,7 @@ def pass_to_ngettext(*args, **kwargs):
     The reason we can't have a global ngettext method is because
     mg_globals gets swapped out by the application per-request.
     """
-    return mg_globals.translations.ngettext(
+    return mg_globals.thread_scope.translations.ngettext(
         *args, **kwargs)
 
 
@@ -158,7 +187,17 @@ def lazy_pass_to_ngettext(*args, **kwargs):
     level but you need it to not translate until the time that it's
     used as a string.
     """
-    return LazyProxy(pass_to_ngettext, *args, **kwargs)
+    return ReallyLazyProxy(pass_to_ngettext, *args, **kwargs)
+
+def lazy_pass_to_ungettext(*args, **kwargs):
+    """
+    Lazily pass to ungettext.
+
+    This is useful if you have to define a translation on a module
+    level but you need it to not translate until the time that it's
+    used as a string.
+    """
+    return ReallyLazyProxy(pass_to_ungettext, *args, **kwargs)
 
 
 def fake_ugettext_passthrough(string):