diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py
--- a/django/utils/translation/__init__.py
+++ b/django/utils/translation/__init__.py
@@ -1,8 +1,11 @@
 """
 Internationalization support.
 """
+import os.path
+
 from django.utils.encoding import force_unicode
-from django.utils.functional import lazy, curry
+from django.utils.functional import lazy
+from django.utils.importlib import import_module
 
 
 __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
@@ -33,10 +36,25 @@
     performance effect, as access to the function goes the normal path,
     instead of using __getattr__.
     """
+    cnt = 0
+
     def __getattr__(self, real_name):
         from django.conf import settings
         if settings.USE_I18N:
             from django.utils.translation import trans_real as trans
+
+            if not self.cnt:
+                self.cnt += 1
+                if settings.SETTINGS_MODULE is not None:
+                    import warnings
+                    parts = settings.SETTINGS_MODULE.split('.')
+                    project = import_module(parts[0])
+                    if os.path.isdir(os.path.join(os.path.dirname(project.__file__), 'locale')):
+                        warnings.warn(
+                            "Translations in the project directory aren't supported anymore. Use LOCALE_PATHS settings instead.",
+                            PendingDeprecationWarning
+                        )
+
         else:
             from django.utils.translation import trans_null as trans
         setattr(self, real_name, getattr(trans, real_name))
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -125,12 +125,12 @@
 
         global _translations
 
-        loc = to_locale(lang)
-
         res = _translations.get(lang, None)
         if res is not None:
             return res
 
+        loc = to_locale(lang)
+
         def _translation(path):
             try:
                 t = gettext_module.translation('django', path, [loc], DjangoTranslation)
@@ -159,11 +159,7 @@
                     res.merge(t)
             return res
 
-        for localepath in settings.LOCALE_PATHS:
-            if os.path.isdir(localepath):
-                res = _merge(localepath)
-
-        for appname in settings.INSTALLED_APPS:
+        for appname in reversed(settings.INSTALLED_APPS):
             app = import_module(appname)
             apppath = os.path.join(os.path.dirname(app.__file__), 'locale')
 
@@ -173,6 +169,10 @@
         if projectpath and os.path.isdir(projectpath):
             res = _merge(projectpath)
 
+        for localepath in reversed(settings.LOCALE_PATHS):
+            if os.path.isdir(localepath):
+                res = _merge(localepath)
+
         if res is None:
             if fallback is not None:
                 res = fallback
diff --git a/django/views/i18n.py b/django/views/i18n.py
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -196,11 +196,15 @@
     paths = []
     en_selected = locale.startswith('en')
     en_catalog_missing = True
-    # first load all english languages files for defaults
+    # paths of requested packages
     for package in packages:
         p = importlib.import_module(package)
         path = os.path.join(os.path.dirname(p.__file__), 'locale')
         paths.append(path)
+    # add the filesystem paths listed in the LOCALE_PATHS setting
+    paths.extend(list(reversed(settings.LOCALE_PATHS)))
+    # first load all english languages files for defaults
+    for path in paths:
         try:
             catalog = gettext_module.translation(domain, path, ['en'])
             t.update(catalog._catalog)
@@ -278,4 +282,3 @@
     src.append(LibFormatFoot)
     src = ''.join(src)
     return http.HttpResponse(src, 'text/javascript')
-
diff --git a/docs/howto/i18n.txt b/docs/howto/i18n.txt
--- a/docs/howto/i18n.txt
+++ b/docs/howto/i18n.txt
@@ -4,15 +4,36 @@
 Using internationalization in your own projects
 ===============================================
 
-At runtime, Django looks for translations by following this algorithm:
+At runtime, Django builds a in-memory unified catalog of literals-translations.
+To achieve this it looks for translations by following this algorithm regarding
+the order in which it examines the different file paths to load the compiled
+:term:`message files <message file>` (``.mo``) and the precedence of multiple
+translations for the same literal:
 
-    * First, it looks for a ``locale`` directory in the directory containing
-      your settings file.
-    * Second, it looks for a ``locale`` directory in the project directory.
-    * Third, it looks for a ``locale`` directory in each of the installed apps.
-      It does this in the reverse order of INSTALLED_APPS
-    * Finally, it checks the Django-provided base translation in
-      ``django/conf/locale``.
+    1. The directories listed in :setting:`LOCALE_PATHS` have the highest
+       precedence, with the ones appearing first having higher precedence than
+       the ones appearing later.
+    2. Then, it looks for and uses if it exists a ``locale`` directory in each
+       of the installed apps listed in :setting:`INSTALLED_APPS`.
+       The ones appearing first have higher precedence than the ones appearing
+       later.
+    3. Then, it looks for a ``locale`` directory in the project directory, or
+       more accurately, in the directory containing your settings file.
+    4. Finally, the Django-provided base translation in ``django/conf/locale``
+       is used as a fallback.
+
+.. deprecated:: 1.3
+    Lookup in the ``locale`` subdirectory of the directory containing your
+    settings file (item 3 above) is deprecated since the 1.3 release and will be
+    removed in 1.5. You can use the :setting:`LOCALE_PATHS` setting instead, by
+    including the full filesystem path of the same location.
+
+.. seealso::
+
+    The translations for literals included in JavaScript assets are looked up
+    following not a quite equivalent but a similar algorithm. See the
+    :ref:`javascript_catalog view documentation <javascript_catalog-view>` for
+    more details.
 
 In all cases the name of the directory containing the translation is expected to
 be named using :term:`locale name` notation. E.g. ``de``, ``pt_BR``, ``es_AR``,
@@ -37,7 +58,7 @@
     * ``$APPPATH/locale/<language>/LC_MESSAGES/django.(po|mo)``
     * ``$PROJECTPATH/locale/<language>/LC_MESSAGES/django.(po|mo)``
     * All paths listed in ``LOCALE_PATHS`` in your settings file are
-      searched in that order for ``<language>/LC_MESSAGES/django.(po|mo)``
+      searched for ``<language>/LC_MESSAGES/django.(po|mo)``
     * ``$PYTHONPATH/django/conf/locale/<language>/LC_MESSAGES/django.(po|mo)``
 
 To create message files, you use the :djadmin:`django-admin.py makemessages <makemessages>`
@@ -50,11 +71,6 @@
 to make the compiler process all the directories in your :setting:`LOCALE_PATHS`
 setting.
 
-Application message files are a bit complicated to discover -- they need the
-:class:`~django.middleware.locale.LocaleMiddleware`. If you don't use the
-middleware, only the Django message files and project message files will be
-installed and available at runtime.
-
 Finally, you should give some thought to the structure of your translation
 files. If your applications need to be delivered to other users and will
 be used in other projects, you might want to use app-specific translations.
diff --git a/docs/topics/i18n/deployment.txt b/docs/topics/i18n/deployment.txt
--- a/docs/topics/i18n/deployment.txt
+++ b/docs/topics/i18n/deployment.txt
@@ -171,16 +171,36 @@
 How Django discovers translations
 ---------------------------------
 
-As described in :ref:`using-translations-in-your-own-projects`,
-at runtime, Django looks for translations by following this algorithm:
+As described in :ref:`using-translations-in-your-own-projects`, Django looks for
+translations by following this algorithm regarding the order in which it
+examines the different file paths to load the compiled :term:`message files
+<message file>` (``.mo``) and the precedence of multiple translations for the
+same literal:
 
-    * First, it looks for a ``locale`` directory in the directory containing
-      your settings file.
-    * Second, it looks for a ``locale`` directory in the project directory.
-    * Third, it looks for a ``locale`` directory in each of the installed apps.
-      It does this in the reverse order of INSTALLED_APPS
-    * Finally, it checks the Django-provided base translation in
-      ``django/conf/locale``.
+    1. The directories listed in :setting:`LOCALE_PATHS` have the highest
+       precedence, with the ones appearing first having higher precedence than
+       the ones appearing later.
+    2. Then, it looks for and uses if it exists a ``locale`` directory in each
+       of the installed apps listed in :setting:`INSTALLED_APPS`.
+       The ones appearing first have higher precedence than the ones appearing
+       later.
+    3. Then, it looks for a ``locale`` directory in the project directory, or
+       more accurately, in the directory containing your settings file.
+    4. Finally, the Django-provided base translation in ``django/conf/locale``
+       is used as a fallback.
+
+.. deprecated:: 1.3
+    Lookup in the ``locale`` subdirectory of the directory containing your
+    settings file (item 3 above) is deprecated since the 1.3 release and will be
+    removed in 1.5. You can use the :setting:`LOCALE_PATHS` setting instead, by
+    including the full filesystem path of the same location.
+
+.. seealso::
+
+    The translations for literals included in JavaScript assets are looked up
+    following not a quite equivalent but a similar algorithm. See the
+    :ref:`javascript_catalog view documentation <javascript_catalog-view>` for
+    more details.
 
 In all cases the name of the directory containing the translation is expected to
 be named using :term:`locale name` notation. E.g. ``de``, ``pt_BR``, ``es_AR``,
diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt
--- a/docs/topics/i18n/internationalization.txt
+++ b/docs/topics/i18n/internationalization.txt
@@ -647,6 +647,8 @@
 translations into JavaScript, so you can call ``gettext``, etc., from within
 JavaScript.
 
+.. _javascript_catalog-view:
+
 The ``javascript_catalog`` view
 -------------------------------
 
@@ -657,8 +659,9 @@
 The main solution to these problems is the :meth:`django.views.i18n.javascript_catalog`
 view, which sends out a JavaScript code library with functions that mimic the
 ``gettext`` interface, plus an array of translation strings. Those translation
-strings are taken from the application, project or Django core, according to what
-you specify in either the info_dict or the URL.
+strings are taken from the application, paths listed in :setting:`LOCALE_PATHS`
+or Django core, according to what you specify in either the info_dict or the
+URL.
 
 You hook it up like this::
 
@@ -691,6 +694,16 @@
 catalog file. As a security measure, these values can only be either
 ``django.conf`` or any package from the :setting:`INSTALLED_APPS` setting.
 
+The JavaScript translations found in the paths listed in the
+:setting:`LOCALE_PATHS` setting are also always included (provided they have a
+``locale`` subdirectory).
+
+To keep consistency with the translations lookup order algorithm used for Python
+and templates, the directories listed in :setting:`LOCALE_PATHS` have the
+highest precedence with the ones appearing first having higher precedence than
+the ones appearing later. But Django core and :setting:`INSTALLED_APPS`
+translations should be explicitly listed in the *packages* parameter.
+
 Using the JavaScript translation catalog
 ----------------------------------------
 
diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo
index 6db1cb1a8ccf488dc7336d0fd707f6693a873a26..241de05ddb04199df4a515d720dc9ea263a06ca7
GIT binary patch
literal 668
zc${sK!EVz)5QewGAZjEIZ~@W7a6oE>HnAO~R&G<Zgj5t9H426UmueeNoQ>?=Xm?#v
zgm??YE5MlxZ@^n{;mSiWb|Ok8M*95ESpTfYJ3sGupBTjrVuMJCTf_&pj4#AG@s;q1
zZ^UilJMn<{LEI#M5vOa6{U*I%pVMOOF6or?8tHq|d!!#*%X2=HuCc4pTB*0v>#Moy
ze)dc_>^NB>OY29em79SQC3-_~!7K_hnR$;gFlej7vA>yR=V;Cv)5z#KP8>hZ_!~4<
zY868^7b@4_&^qgnaiNXlLz~NtKdo}hQyqgpI!Sropm5Z|>p5bGf-vAg#KR{LZpYp3
zW2yr(@-d!Ed&L*>AmmX9Q5bi(dC(<aBCO-75y}?AX%oW>p_H_cpv<s<y?Ij}6iwNd
zYO43CN9C2sk*6q1nj2v+!-GP$waNGW;qh>A`BPy#@cVQ@sOU2n3!1~>JJ(qhLaJSu
z%!IM%dau(XzH{lT$C;wx12xf^RCx?LXVUqjqB5f3N7|G&2DNBP+lzK#nf5jnJnTWZ
t<<~2!%FbJq4mOjM{$VmW9UZ36Uv07fy&DQyZ)x0YNw|M=WH0Fk*dL85s@MPk

diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
--- a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
+++ b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@
 "Project-Id-Version: django tests\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2010-02-14 17:33+0100\n"
-"PO-Revision-Date: 2011-01-16 17:14+0100\n"
+"PO-Revision-Date: 2011-01-21 21:37-0300\n"
 "Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
 "Language-Team: de <de@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -18,22 +18,25 @@
 
 #: models.py:3
 msgid "Time"
-msgstr "Time (LOCALE_PATHS)"
+msgstr "Zeit (LOCALE_PATHS)"
 
 #: models.py:5
+msgid "Date/time"
+msgstr "Datum/Zeit (LOCALE_PATHS)"
+
+#: models.py:7
 msgctxt "month name"
 msgid "May"
 msgstr "Mai"
 
-#: models.py:7
+#: models.py:9
 msgctxt "verb"
 msgid "May"
 msgstr "Kann"
 
-#: models.py:9
+#: models.py:11
 msgctxt "search"
 msgid "%d result"
 msgid_plural "%d results"
 msgstr[0] "%d Resultat"
 msgstr[1] "%d Resultate"
-
diff --git a/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.mo b/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.mo
index 1c37ac5c87e9392a742ea3db6149b902c9667bd9..4d4f202be2e13a07a07dcb894e04f35e03092c91
GIT binary patch
literal 492
zc$`&`!A=`75Qg2B9=fNh=N_gv5E`=HAb|`)EXf8%?1ojc;m}L#Chpd-u~)W(;6eIc
zeF7xjgxPXPJM!n5k$#VV`{~opZ$|Ns_)HuU?}?nf#&_ZaA&DL0j`;S5v6k{j;t$mk
z<!8#Y*ju)ZZYCSapn5^C6|1_fboN#)15Q?Wqny{)2q@FxReG7hO*WfPuBXvVwblhJ
ze63e_v8g>)RzOr<S2%P^20Db@SOH1QV~i8zhro|SzrRm?OhTNgd+q<zaf~@891<@2
zM;P}>mrEb8a?<!l2I~Zj7kLiTw8-EzE7D1Ro{mPdY(76Dsi`-zR#+*y7LcdY%SC7T
zG|$f)ePCUE78R2s+dhgP#L>{2pbXvd*pi8$9zw5eq&6q8{3V@NVX&yic)ZnhUVbWv
znOWMEHno7`A38*3vvIP)v2|-Nz_fo`KS)mCwH)jk_%Z;#7j@sgS@*sw9bh*t%RTl_
F`2u-se*XXf

diff --git a/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po b/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po
--- a/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po
+++ b/tests/regressiontests/i18n/resolution/locale/de/LC_MESSAGES/django.po
@@ -9,7 +9,7 @@
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2010-02-14 17:33+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2011-01-21 21:37-0300\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -18,5 +18,9 @@
 "Plural-Forms: nplurals=2; plural=(n != 1)\n"
 
 #: models.py:3
+msgid "Time"
+msgstr "Zeit (APP)"
+
+#: models.py:5
 msgid "Date/time"
 msgstr "Datum/Zeit (APP)"
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
--- a/tests/regressiontests/i18n/tests.py
+++ b/tests/regressiontests/i18n/tests.py
@@ -660,14 +660,14 @@
 
     def assertUgettext(self, msgid, msgstr):
         result = ugettext(msgid)
-        self.assert_(msgstr in result, ("The string '%s' isn't in the "
+        self.assertTrue(msgstr in result, ("The string '%s' isn't in the "
             "translation of '%s'; the actual result is '%s'." % (msgstr, msgid, result)))
 
 class AppResolutionOrderI18NTests(ResolutionOrderI18NTests):
 
     def setUp(self):
         self.old_installed_apps = settings.INSTALLED_APPS
-        settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.i18n.resolution']
+        settings.INSTALLED_APPS = ['regressiontests.i18n.resolution'] + list(settings.INSTALLED_APPS)
         super(AppResolutionOrderI18NTests, self).setUp()
 
     def tearDown(self):
@@ -691,6 +691,22 @@
     def test_locale_paths_translation(self):
         self.assertUgettext('Time', 'LOCALE_PATHS')
 
+    def test_locale_paths_override_app_translation(self):
+        old_installed_apps = settings.INSTALLED_APPS
+        settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.i18n.resolution']
+        try:
+            self.assertUgettext('Time', 'LOCALE_PATHS')
+        finally:
+            settings.INSTALLED_APPS = old_installed_apps
+
+    def test_locale_paths_override_project_translation(self):
+        old_settings_module = settings.SETTINGS_MODULE
+        settings.SETTINGS_MODULE = 'regressiontests'
+        try:
+            self.assertUgettext('Date/time', 'LOCALE_PATHS')
+        finally:
+            settings.SETTINGS_MODULE = old_settings_module
+
 class ProjectResolutionOrderI18NTests(ResolutionOrderI18NTests):
 
     def setUp(self):
@@ -708,19 +724,15 @@
     def test_project_override_app_translation(self):
         old_installed_apps = settings.INSTALLED_APPS
         settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.i18n.resolution']
-        self.assertUgettext('Date/time', 'PROJECT')
-        settings.INSTALLED_APPS = old_installed_apps
-
-    def test_project_override_locale_paths_translation(self):
-        old_locale_paths = settings.LOCALE_PATHS
-        settings.LOCALE_PATHS += (os.path.join(os.path.dirname(os.path.abspath(__file__)), 'other', 'locale'),)
-        self.assertUgettext('Date/time', 'PROJECT')
-        settings.LOCALE_PATHS = old_locale_paths
+        try:
+            self.assertUgettext('Date/time', 'PROJECT')
+        finally:
+            settings.INSTALLED_APPS = old_installed_apps
 
 class DjangoFallbackResolutionOrderI18NTests(ResolutionOrderI18NTests):
 
     def test_django_fallback(self):
-        self.assertUgettext('Date/time', 'Datum/Zeit')
+        self.assertEqual(ugettext('Date/time'), 'Datum/Zeit')
 
 
 class TestModels(TestCase):
diff --git a/tests/regressiontests/locale/de/LC_MESSAGES/django.mo b/tests/regressiontests/locale/de/LC_MESSAGES/django.mo
index 2ee860a80b5e75cd9f5fe727c02f74936918dc9c..81616ca7bdabe07ebff028d2480f6cada79b65f3
GIT binary patch
literal 500
zc${63!EO^V5QYtu1AE|rIC2<nt<W^<O{G?DQ*}u;RbV$+Z8kk{0h5@mVPmgsrz#J^
zd+`L^cn@ajA%c-V&y4hY{M)}?9Q<GuPl#8<3GtN3*+YCHo)MBbAifbFA2HTZeo6eK
z`Z?v_lxeZYY#;qhHjzOMg5D}t_gm@gSG5i}+u)6IURxueOvmrj>kMwPYB^iXqe^ww
z1uT4{H~4nfc&x2}s9e-Ic1i|1g#B0nNz7x66XYkrPsMQfhWeO<SgAYh|Iu-bIVKzu
zE{3NV4@s9xAFy`P_*Mq%1WZ?X4)e6g;36y1S$>sHCRMgvUXs)_JJ~3#mD~!*)A{wP
zw|tT3SFJv_uDOhgS&{7@#gF4?Y)w#x?)cu3iJ<PoplhWz=diw&&Z{t5)l)p%>v}I=
zl*7!d?M9nMz}aUVqO#pN+2Yi?truXrzpWo7=kQRD4h_5>0Y8fR@7`?(ACwMoSXPU7
L*|<Jp|FwSrmUDr3

diff --git a/tests/regressiontests/locale/de/LC_MESSAGES/django.po b/tests/regressiontests/locale/de/LC_MESSAGES/django.po
--- a/tests/regressiontests/locale/de/LC_MESSAGES/django.po
+++ b/tests/regressiontests/locale/de/LC_MESSAGES/django.po
@@ -9,7 +9,7 @@
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2010-02-14 17:33+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2011-01-21 21:37-0300\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -18,5 +18,9 @@
 "Plural-Forms: nplurals=2; plural=(n != 1)\n"
 
 #: models.py:3
+msgid "Time"
+msgstr "Zeit (PROJECT)"
+
+#: models.py:5
 msgid "Date/time"
 msgstr "Datum/Zeit (PROJECT)"
diff --git a/tests/regressiontests/views/tests/i18n.py b/tests/regressiontests/views/tests/i18n.py
--- a/tests/regressiontests/views/tests/i18n.py
+++ b/tests/regressiontests/views/tests/i18n.py
@@ -1,5 +1,6 @@
 # -*- coding:utf-8 -*-
 import gettext
+import os
 
 from django.conf import settings
 from django.test import TestCase
@@ -150,3 +151,11 @@
         response = self.client.get('/views/jsi18n_multi_packages2/')
         self.assertContains(response, javascript_quote('este texto de app3 debe ser traducido'))
         deactivate()
+
+    def testI18NWithLocalePaths(self):
+        settings.LANGUAGE_CODE = 'es-ar'
+        self.old_locale_paths = settings.LOCALE_PATHS
+        settings.LOCALE_PATHS += (os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'app3', 'locale'),)
+        response = self.client.get('/views/jsi18n/')
+        self.assertContains(response, javascript_quote('este texto de app3 debe ser traducido'))
+        settings.LOCALE_PATHS = self.old_locale_paths
