diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
a
|
b
|
|
159 | 159 | res.merge(t) |
160 | 160 | return res |
161 | 161 | |
162 | | for localepath in settings.LOCALE_PATHS: |
163 | | if os.path.isdir(localepath): |
164 | | res = _merge(localepath) |
165 | | |
166 | | for appname in settings.INSTALLED_APPS: |
| 162 | for appname in reversed(settings.INSTALLED_APPS): |
167 | 163 | app = import_module(appname) |
168 | 164 | apppath = os.path.join(os.path.dirname(app.__file__), 'locale') |
169 | 165 | |
… |
… |
|
173 | 169 | if projectpath and os.path.isdir(projectpath): |
174 | 170 | res = _merge(projectpath) |
175 | 171 | |
| 172 | for localepath in reversed(settings.LOCALE_PATHS): |
| 173 | if os.path.isdir(localepath): |
| 174 | res = _merge(localepath) |
| 175 | |
176 | 176 | if res is None: |
177 | 177 | if fallback is not None: |
178 | 178 | res = fallback |
diff --git a/docs/howto/i18n.txt b/docs/howto/i18n.txt
a
|
b
|
|
4 | 4 | Using internationalization in your own projects |
5 | 5 | =============================================== |
6 | 6 | |
7 | | At runtime, Django looks for translations by following this algorithm: |
| 7 | At runtime, Django builds a in-memory unified catalog of literals-translations. |
| 8 | To achieve this it looks for translations by following this algorithm regarding |
| 9 | the order in which it examines the different file paths to load the compiled |
| 10 | :term:`message files <message file>` (``.mo``) and the precedence of multiple |
| 11 | translations for the same literal: |
8 | 12 | |
9 | | * First, it looks for a ``locale`` directory in the directory containing |
10 | | your settings file. |
11 | | * Second, it looks for a ``locale`` directory in the project directory. |
12 | | * Third, it looks for a ``locale`` directory in each of the installed apps. |
13 | | It does this in the reverse order of INSTALLED_APPS |
14 | | * Finally, it checks the Django-provided base translation in |
15 | | ``django/conf/locale``. |
| 13 | * The directories listed in :setting:`LOCALE_PATHS` have the highest |
| 14 | precedence, with the ones appearing first having higher precedence than |
| 15 | the ones appearing later. |
| 16 | * Then, it looks for and uses if it exists a ``locale`` directory in each |
| 17 | of the installed apps listed in :setting:`INSTALLED_APPS`. |
| 18 | The ones appearing first have higher precedence than the ones appearing |
| 19 | later. |
| 20 | * Then, it looks for a ``locale`` directory in the project directory, or |
| 21 | more accurately, in the directory containing your settings file. |
| 22 | * Finally, the Django-provided base translation in ``django/conf/locale`` |
| 23 | is used as a fallback. |
16 | 24 | |
17 | 25 | In all cases the name of the directory containing the translation is expected to |
18 | 26 | be named using :term:`locale name` notation. E.g. ``de``, ``pt_BR``, ``es_AR``, |
… |
… |
|
37 | 45 | * ``$APPPATH/locale/<language>/LC_MESSAGES/django.(po|mo)`` |
38 | 46 | * ``$PROJECTPATH/locale/<language>/LC_MESSAGES/django.(po|mo)`` |
39 | 47 | * All paths listed in ``LOCALE_PATHS`` in your settings file are |
40 | | searched in that order for ``<language>/LC_MESSAGES/django.(po|mo)`` |
| 48 | searched for ``<language>/LC_MESSAGES/django.(po|mo)`` |
41 | 49 | * ``$PYTHONPATH/django/conf/locale/<language>/LC_MESSAGES/django.(po|mo)`` |
42 | 50 | |
43 | 51 | To create message files, you use the :djadmin:`django-admin.py makemessages <makemessages>` |
… |
… |
|
50 | 58 | to make the compiler process all the directories in your :setting:`LOCALE_PATHS` |
51 | 59 | setting. |
52 | 60 | |
53 | | Application message files are a bit complicated to discover -- they need the |
54 | | :class:`~django.middleware.locale.LocaleMiddleware`. If you don't use the |
55 | | middleware, only the Django message files and project message files will be |
56 | | installed and available at runtime. |
57 | | |
58 | 61 | Finally, you should give some thought to the structure of your translation |
59 | 62 | files. If your applications need to be delivered to other users and will |
60 | 63 | 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
|
b
|
|
171 | 171 | How Django discovers translations |
172 | 172 | --------------------------------- |
173 | 173 | |
174 | | As described in :ref:`using-translations-in-your-own-projects`, |
175 | | at runtime, Django looks for translations by following this algorithm: |
| 174 | As described in :ref:`using-translations-in-your-own-projects`, Django looks for |
| 175 | translations by following this algorithm regarding the order in which it |
| 176 | examines the different file paths to load the compiled :term:`message files |
| 177 | <message file>` (``.mo``) and the precedence of multiple translations for the |
| 178 | same literal: |
176 | 179 | |
177 | | * First, it looks for a ``locale`` directory in the directory containing |
178 | | your settings file. |
179 | | * Second, it looks for a ``locale`` directory in the project directory. |
180 | | * Third, it looks for a ``locale`` directory in each of the installed apps. |
181 | | It does this in the reverse order of INSTALLED_APPS |
182 | | * Finally, it checks the Django-provided base translation in |
183 | | ``django/conf/locale``. |
| 180 | * The directories listed in :setting:`LOCALE_PATHS` have the highest |
| 181 | precedence, with the ones appearing first having higher precedence than |
| 182 | the ones appearing later. |
| 183 | * Then, it looks for and uses if it exists a ``locale`` directory in each |
| 184 | of the installed apps listed in :setting:`INSTALLED_APPS`. |
| 185 | The ones appearing first have higher precedence than the ones appearing |
| 186 | later. |
| 187 | * Then, it looks for a ``locale`` directory in the project directory, or |
| 188 | more accurately, in the directory containing your settings file. |
| 189 | * Finally, the Django-provided base translation in ``django/conf/locale`` |
| 190 | is used as a fallback. |
184 | 191 | |
185 | 192 | In all cases the name of the directory containing the translation is expected to |
186 | 193 | be named using :term:`locale name` notation. E.g. ``de``, ``pt_BR``, ``es_AR``, |
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
|
b
|
|
8 | 8 | "Project-Id-Version: django tests\n" |
9 | 9 | "Report-Msgid-Bugs-To: \n" |
10 | 10 | "POT-Creation-Date: 2010-02-14 17:33+0100\n" |
11 | | "PO-Revision-Date: 2011-01-16 17:14+0100\n" |
| 11 | "PO-Revision-Date: 2011-01-21 21:37-0300\n" |
12 | 12 | "Last-Translator: Jannis Leidel <jannis@leidel.info>\n" |
13 | 13 | "Language-Team: de <de@li.org>\n" |
14 | 14 | "MIME-Version: 1.0\n" |
… |
… |
|
18 | 18 | |
19 | 19 | #: models.py:3 |
20 | 20 | msgid "Time" |
21 | | msgstr "Time (LOCALE_PATHS)" |
| 21 | msgstr "Zeit (LOCALE_PATHS)" |
22 | 22 | |
23 | 23 | #: models.py:5 |
| 24 | msgid "Date/time" |
| 25 | msgstr "Datum/Zeit (LOCALE_PATHS)" |
| 26 | |
| 27 | #: models.py:7 |
24 | 28 | msgctxt "month name" |
25 | 29 | msgid "May" |
26 | 30 | msgstr "Mai" |
27 | 31 | |
28 | | #: models.py:7 |
| 32 | #: models.py:9 |
29 | 33 | msgctxt "verb" |
30 | 34 | msgid "May" |
31 | 35 | msgstr "Kann" |
32 | 36 | |
33 | | #: models.py:9 |
| 37 | #: models.py:11 |
34 | 38 | msgctxt "search" |
35 | 39 | msgid "%d result" |
36 | 40 | msgid_plural "%d results" |
37 | 41 | msgstr[0] "%d Resultat" |
38 | 42 | msgstr[1] "%d Resultate" |
39 | | |
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
|
b
|
|
9 | 9 | "Project-Id-Version: PACKAGE VERSION\n" |
10 | 10 | "Report-Msgid-Bugs-To: \n" |
11 | 11 | "POT-Creation-Date: 2010-02-14 17:33+0100\n" |
12 | | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
| 12 | "PO-Revision-Date: 2011-01-21 21:37-0300\n" |
13 | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
14 | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" |
15 | 15 | "MIME-Version: 1.0\n" |
… |
… |
|
18 | 18 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" |
19 | 19 | |
20 | 20 | #: models.py:3 |
| 21 | msgid "Time" |
| 22 | msgstr "Zeit (APP)" |
| 23 | |
| 24 | #: models.py:5 |
21 | 25 | msgid "Date/time" |
22 | 26 | msgstr "Datum/Zeit (APP)" |
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
a
|
b
|
|
660 | 660 | |
661 | 661 | def assertUgettext(self, msgid, msgstr): |
662 | 662 | result = ugettext(msgid) |
663 | | self.assert_(msgstr in result, ("The string '%s' isn't in the " |
| 663 | self.assertTrue(msgstr in result, ("The string '%s' isn't in the " |
664 | 664 | "translation of '%s'; the actual result is '%s'." % (msgstr, msgid, result))) |
665 | 665 | |
666 | 666 | class AppResolutionOrderI18NTests(ResolutionOrderI18NTests): |
667 | 667 | |
668 | 668 | def setUp(self): |
669 | 669 | self.old_installed_apps = settings.INSTALLED_APPS |
670 | | settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.i18n.resolution'] |
| 670 | settings.INSTALLED_APPS = ['regressiontests.i18n.resolution'] + list(settings.INSTALLED_APPS) |
671 | 671 | super(AppResolutionOrderI18NTests, self).setUp() |
672 | 672 | |
673 | 673 | def tearDown(self): |
… |
… |
|
691 | 691 | def test_locale_paths_translation(self): |
692 | 692 | self.assertUgettext('Time', 'LOCALE_PATHS') |
693 | 693 | |
| 694 | def test_locale_paths_override_app_translation(self): |
| 695 | old_installed_apps = settings.INSTALLED_APPS |
| 696 | settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.i18n.resolution'] |
| 697 | try: |
| 698 | self.assertUgettext('Time', 'LOCALE_PATHS') |
| 699 | finally: |
| 700 | settings.INSTALLED_APPS = old_installed_apps |
| 701 | |
| 702 | def test_locale_paths_override_project_translation(self): |
| 703 | old_settings_module = settings.SETTINGS_MODULE |
| 704 | settings.SETTINGS_MODULE = 'regressiontests' |
| 705 | try: |
| 706 | self.assertUgettext('Date/time', 'LOCALE_PATHS') |
| 707 | finally: |
| 708 | settings.SETTINGS_MODULE = old_settings_module |
| 709 | |
694 | 710 | class ProjectResolutionOrderI18NTests(ResolutionOrderI18NTests): |
695 | 711 | |
696 | 712 | def setUp(self): |
… |
… |
|
708 | 724 | def test_project_override_app_translation(self): |
709 | 725 | old_installed_apps = settings.INSTALLED_APPS |
710 | 726 | settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['regressiontests.i18n.resolution'] |
711 | | self.assertUgettext('Date/time', 'PROJECT') |
712 | | settings.INSTALLED_APPS = old_installed_apps |
713 | | |
714 | | def test_project_override_locale_paths_translation(self): |
715 | | old_locale_paths = settings.LOCALE_PATHS |
716 | | settings.LOCALE_PATHS += (os.path.join(os.path.dirname(os.path.abspath(__file__)), 'other', 'locale'),) |
717 | | self.assertUgettext('Date/time', 'PROJECT') |
718 | | settings.LOCALE_PATHS = old_locale_paths |
| 727 | try: |
| 728 | self.assertUgettext('Date/time', 'PROJECT') |
| 729 | finally: |
| 730 | settings.INSTALLED_APPS = old_installed_apps |
719 | 731 | |
720 | 732 | class DjangoFallbackResolutionOrderI18NTests(ResolutionOrderI18NTests): |
721 | 733 | |
722 | 734 | def test_django_fallback(self): |
723 | | self.assertUgettext('Date/time', 'Datum/Zeit') |
| 735 | self.assertEqual(ugettext('Date/time'), 'Datum/Zeit') |
724 | 736 | |
725 | 737 | |
726 | 738 | 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
|
b
|
|
9 | 9 | "Project-Id-Version: PACKAGE VERSION\n" |
10 | 10 | "Report-Msgid-Bugs-To: \n" |
11 | 11 | "POT-Creation-Date: 2010-02-14 17:33+0100\n" |
12 | | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
| 12 | "PO-Revision-Date: 2011-01-21 21:37-0300\n" |
13 | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
14 | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" |
15 | 15 | "MIME-Version: 1.0\n" |
… |
… |
|
18 | 18 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" |
19 | 19 | |
20 | 20 | #: models.py:3 |
| 21 | msgid "Time" |
| 22 | msgstr "Zeit (PROJECT)" |
| 23 | |
| 24 | #: models.py:5 |
21 | 25 | msgid "Date/time" |
22 | 26 | msgstr "Datum/Zeit (PROJECT)" |