Code

Ticket #4030: 4030_language_info_v2.diff

File 4030_language_info_v2.diff, 20.8 KB (added by akaihola, 6 years ago)

removed fallback for missing language_info.py, improved make-language-info.py

Line 
1Index: django/conf/language_info.py
2===================================================================
3--- django/conf/language_info.py        (revision 0)
4+++ django/conf/language_info.py        (revision 0)
5@@ -0,0 +1,195 @@
6+# This file was generated with django/bin/make-language-info.py and it is not
7+# recommended to edit it by hand.  For new translations, compile the new .po
8+# files and edit the LANGUAGES setting, and finally re-run
9+# django/bin/make-language-info.py to generate this file.
10+
11+language_info = {
12+ 'ar': {'bidi': True,
13+        'language_code': 'ar',
14+        'name': 'Arabic',
15+        'name_local': u'Arabic'},
16+ 'bg': {'bidi': False,
17+        'language_code': 'bg',
18+        'name': 'Bulgarian',
19+        'name_local': u'\u0431\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438'},
20+ 'bn': {'bidi': False,
21+        'language_code': 'bn',
22+        'name': 'Bengali',
23+        'name_local': u'\u09ac\u09be\u0982\u09b2\u09be'},
24+ 'ca': {'bidi': False,
25+        'language_code': 'ca',
26+        'name': 'Catalan',
27+        'name_local': u'Catal\xe0'},
28+ 'cs': {'bidi': False,
29+        'language_code': 'cs',
30+        'name': 'Czech',
31+        'name_local': u'\u010desky'},
32+ 'cy': {'bidi': False,
33+        'language_code': 'cy',
34+        'name': 'Welsh',
35+        'name_local': u'Cymraeg'},
36+ 'da': {'bidi': False,
37+        'language_code': 'da',
38+        'name': 'Danish',
39+        'name_local': u'Dansk'},
40+ 'de': {'bidi': False,
41+        'language_code': 'de',
42+        'name': 'German',
43+        'name_local': u'Deutsch'},
44+ 'el': {'bidi': False,
45+        'language_code': 'el',
46+        'name': 'Greek',
47+        'name_local': u'\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac'},
48+ 'en': {'bidi': False,
49+        'language_code': 'en',
50+        'name': 'English',
51+        'name_local': u'English'},
52+ 'es': {'bidi': False,
53+        'language_code': 'es',
54+        'name': 'Spanish',
55+        'name_local': u'Espa\xf1ol'},
56+ 'es-ar': {'bidi': False,
57+           'language_code': 'es-ar',
58+           'name': 'Argentinean Spanish',
59+           'name_local': u'Espa\xf1ol Argentino'},
60+ 'eu': {'bidi': False,
61+        'language_code': 'eu',
62+        'name': 'Basque',
63+        'name_local': u'Basque'},
64+ 'fa': {'bidi': True,
65+        'language_code': 'fa',
66+        'name': 'Persian',
67+        'name_local': u'Persian'},
68+ 'fi': {'bidi': False,
69+        'language_code': 'fi',
70+        'name': 'Finnish',
71+        'name_local': u'suomi'},
72+ 'fr': {'bidi': False,
73+        'language_code': 'fr',
74+        'name': 'French',
75+        'name_local': u'Fran\xe7ais'},
76+ 'ga': {'bidi': False,
77+        'language_code': 'ga',
78+        'name': 'Irish',
79+        'name_local': u'Irish'},
80+ 'gl': {'bidi': False,
81+        'language_code': 'gl',
82+        'name': 'Galician',
83+        'name_local': u'galego'},
84+ 'he': {'bidi': True,
85+        'language_code': 'he',
86+        'name': 'Hebrew',
87+        'name_local': u'\u05e2\u05d1\u05e8\u05d9\u05ea - Hebrew'},
88+ 'hr': {'bidi': False,
89+        'language_code': 'hr',
90+        'name': 'Croatian',
91+        'name_local': u'Hrvatski'},
92+ 'hu': {'bidi': False,
93+        'language_code': 'hu',
94+        'name': 'Hungarian',
95+        'name_local': u'Magyar'},
96+ 'is': {'bidi': False,
97+        'language_code': 'is',
98+        'name': 'Icelandic',
99+        'name_local': u'\xcdslenska'},
100+ 'it': {'bidi': False,
101+        'language_code': 'it',
102+        'name': 'Italian',
103+        'name_local': u'Italiano'},
104+ 'ja': {'bidi': False,
105+        'language_code': 'ja',
106+        'name': 'Japanese',
107+        'name_local': u'\u65e5\u672c\u8a9e'},
108+ 'ka': {'bidi': False,
109+        'language_code': 'ka',
110+        'name': 'Georgian',
111+        'name_local': u'\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8'},
112+ 'km': {'bidi': False,
113+        'language_code': 'km',
114+        'name': 'Khmer',
115+        'name_local': u'Khmer'},
116+ 'kn': {'bidi': False,
117+        'language_code': 'kn',
118+        'name': 'Kannada',
119+        'name_local': u'Kannada'},
120+ 'ko': {'bidi': False,
121+        'language_code': 'ko',
122+        'name': 'Korean',
123+        'name_local': u'\ud55c\uad6d\uc5b4'},
124+ 'lv': {'bidi': False,
125+        'language_code': 'lv',
126+        'name': 'Latvian',
127+        'name_local': u'Latvie\u0161u'},
128+ 'mk': {'bidi': False,
129+        'language_code': 'mk',
130+        'name': 'Macedonian',
131+        'name_local': u'\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438'},
132+ 'nl': {'bidi': False,
133+        'language_code': 'nl',
134+        'name': 'Dutch',
135+        'name_local': u'Nederlands'},
136+ 'no': {'bidi': False,
137+        'language_code': 'no',
138+        'name': 'Norwegian',
139+        'name_local': u'Norsk'},
140+ 'pl': {'bidi': False,
141+        'language_code': 'pl',
142+        'name': 'Polish',
143+        'name_local': u'Polski'},
144+ 'pt': {'bidi': False,
145+        'language_code': 'pt',
146+        'name': 'Portugese',
147+        'name_local': u'Portugese'},
148+ 'pt-br': {'bidi': False,
149+           'language_code': 'pt-br',
150+           'name': 'Brazilian Portuguese',
151+           'name_local': u'Portugu\xeas Brasileiro'},
152+ 'ro': {'bidi': False,
153+        'language_code': 'ro',
154+        'name': 'Romanian',
155+        'name_local': u'Romana'},
156+ 'ru': {'bidi': False,
157+        'language_code': 'ru',
158+        'name': 'Russian',
159+        'name_local': u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439'},
160+ 'sk': {'bidi': False,
161+        'language_code': 'sk',
162+        'name': 'Slovak',
163+        'name_local': u'Slovensky'},
164+ 'sl': {'bidi': False,
165+        'language_code': 'sl',
166+        'name': 'Slovenian',
167+        'name_local': u'Slovenski'},
168+ 'sr': {'bidi': False,
169+        'language_code': 'sr',
170+        'name': 'Serbian',
171+        'name_local': u'Srpski'},
172+ 'sv': {'bidi': False,
173+        'language_code': 'sv',
174+        'name': 'Swedish',
175+        'name_local': u'Svenska'},
176+ 'ta': {'bidi': False,
177+        'language_code': 'ta',
178+        'name': 'Tamil',
179+        'name_local': u'\u0ba4\u0bae\u0bbf\u0bb4\u0bcd'},
180+ 'te': {'bidi': False,
181+        'language_code': 'te',
182+        'name': 'Telugu',
183+        'name_local': u'Telugu'},
184+ 'tr': {'bidi': False,
185+        'language_code': 'tr',
186+        'name': 'Turkish',
187+        'name_local': u'T\xfcrk\xe7e'},
188+ 'uk': {'bidi': False,
189+        'language_code': 'uk',
190+        'name': 'Ukrainian',
191+        'name_local': u'Ukrainian'},
192+ 'zh-cn': {'bidi': False,
193+           'language_code': 'zh-cn',
194+           'name': 'Simplified Chinese',
195+           'name_local': u'\u7b80\u4f53\u4e2d\u6587'},
196+ 'zh-tw': {'bidi': False,
197+           'language_code': 'zh-tw',
198+           'name': 'Traditional Chinese',
199+           'name_local': u'\u7e41\u9ad4\u4e2d\u6587'}
200+}
201
202Property changes on: django/conf/language_info.py
203___________________________________________________________________
204Name: svn:eol-style
205   + native
206
207Index: django/templatetags/i18n.py
208===================================================================
209--- django/templatetags/i18n.py (revision 7438)
210+++ django/templatetags/i18n.py (working copy)
211@@ -17,6 +17,34 @@
212         context[self.variable] = [(k, translation.ugettext(v)) for k, v in settings.LANGUAGES]
213         return ''
214 
215+class GetLanguageInfoNode(Node):
216+    def __init__(self, lang_code, variable):
217+        self.lang_code = Variable(lang_code)
218+        self.variable = variable
219+
220+    def render(self, context):
221+        lang_code = self.lang_code.resolve(context)
222+        context[self.variable] = translation.get_language_info(lang_code)
223+        return ''
224+
225+class GetLanguageInfoListNode(Node):
226+    def __init__(self, languages, variable):
227+        self.languages = Variable(languages)
228+        self.variable = variable
229+
230+    def get_language_info(self, language):
231+        # ``language`` is either a language code string or a sequence with the
232+        # language code as its first item
233+        if len(language[0]) > 1:
234+            return translation.get_language_info(language[0])
235+        else:
236+            return translation.get_language_info(str(language))
237+
238+    def render(self, context):
239+        languages = self.languages.resolve(context)
240+        context[self.variable] = [self.get_language_info(l) for l in languages]
241+        return ''
242+
243 class GetCurrentLanguageNode(Node):
244     def __init__(self, variable):
245         self.variable = variable
246@@ -107,6 +135,55 @@
247         raise TemplateSyntaxError, "'get_available_languages' requires 'as variable' (got %r)" % args
248     return GetAvailableLanguagesNode(args[2])
249 
250+def do_get_language_info(parser, token):
251+    """
252+    This will store the language information dictionary for the given language
253+    code in a context variable.
254+
255+    Usage::
256+
257+        {% get_language_info for language_code as l %}
258+        {{ l.language_code }}
259+        {{ l.name }}
260+        {{ l.name_local }}
261+        {{ l.bidi|yesno:"bi-directional,uni-directional" }}
262+    """
263+    args = token.contents.split()
264+    if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
265+        raise TemplateSyntaxError, "'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:])
266+    return GetLanguageInfoNode(args[2], args[4])
267+
268+def do_get_language_info_list(parser, token):
269+    """
270+    This will store a list of language information dictionaries for the given
271+    language codes in a context variable.  The language codes can be specified
272+    ither as a list of strings or a settings.LANGUAGES style tuple (or any
273+    sequence of sequences whose first items are language codes).
274+
275+    Usage::
276+
277+        {% get_language_info_list for LANGUAGES as langs %}
278+        {% for l in langs %}
279+          {{ l.language_code }}
280+          {{ l.name }}
281+          {{ l.name_local }}
282+          {{ l.bidi|yesno:"bi-directional,uni-directional" }}
283+        {% endfor %}
284+    """
285+    args = token.contents.split()
286+    if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
287+        raise TemplateSyntaxError, "'%s' requires 'for sequence as variable' (got %r)" % (args[0], args[1:])
288+    return GetLanguageInfoListNode(args[2], args[4])
289+
290+def language_name(lang_code):
291+    return translation.get_language_info(lang_code)['name']
292+
293+def language_name_local(lang_code):
294+    return translation.get_language_info(lang_code)['name_local']
295+
296+def language_bidi(lang_code):
297+    return translation.get_language_info(lang_code)['bidi']
298+
299 def do_get_current_language(parser, token):
300     """
301     This will store the current language in the context.
302@@ -253,7 +330,13 @@
303             counter)
304 
305 register.tag('get_available_languages', do_get_available_languages)
306+register.tag('get_language_info', do_get_language_info)
307+register.tag('get_language_info_list', do_get_language_info_list)
308 register.tag('get_current_language', do_get_current_language)
309 register.tag('get_current_language_bidi', do_get_current_language_bidi)
310 register.tag('trans', do_translate)
311 register.tag('blocktrans', do_block_translate)
312+
313+register.filter(language_name)
314+register.filter(language_name_local)
315+register.filter(language_bidi)
316Index: django/utils/translation/__init__.py
317===================================================================
318--- django/utils/translation/__init__.py        (revision 7438)
319+++ django/utils/translation/__init__.py        (working copy)
320@@ -3,6 +3,10 @@
321 """
322 from django.utils.functional import lazy
323 from django.utils.encoding import force_unicode
324+try:
325+    from django.conf.language_info import language_info
326+except ImportError:
327+    raise ImportError("Module django.conf.language_info not found. Run django/bin/make-language-info.py to generate it.")
328 
329 __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext',
330         'ngettext_lazy', 'string_concat', 'activate', 'deactivate',
331@@ -109,3 +113,9 @@
332     """
333     return u''.join([force_unicode(s) for s in strings])
334 string_concat = lazy(string_concat, unicode)
335+
336+def get_language_info(lang_code):
337+    try:
338+        return language_info[lang_code]
339+    except KeyError:
340+        raise KeyError("Unknown language code %r.  If you have added a new translation, remember to add the language to the LANGUAGES setting and update the django/conf/language_info.py file by running django/bin/make-language-info.py." % lang_code)
341Index: django/bin/make-language-info.py
342===================================================================
343--- django/bin/make-language-info.py    (revision 0)
344+++ django/bin/make-language-info.py    (revision 0)
345@@ -0,0 +1,45 @@
346+#!/usr/bin/env python
347+
348+LANGUAGE_INFO_PY_FILE = 'django/conf/language_info.py'
349+
350+# Need to ensure that the i18n framework is enabled
351+from django.conf import settings
352+settings.configure(USE_I18N = True)
353+
354+TEMPLATE = """\
355+# This file was generated with django/bin/make-language-info.py and it is not
356+# recommended to edit it by hand.  For new translations, compile the new .po
357+# files and edit the LANGUAGES setting, and finally re-run
358+# django/bin/make-language-info.py to generate this file.
359+
360+language_info = {
361+ %s
362+}
363+"""
364+
365+def make_language_info(target_path):
366+    print 'Writing language information to\n%s' % target_path
367+    # Create a dummy language_info.py file to make sure we can import the
368+    # translation module.
369+    file(target_path, 'w').write(TEMPLATE % '')
370+    from django.utils import translation
371+    from pprint import pformat
372+    language_info = {}
373+    for code, name in settings.LANGUAGES:
374+        translation.activate(code)
375+        language_info[code] = {"language_code": code,
376+                               "name": name,
377+                               "name_local": translation.ugettext(name),
378+                               "bidi": code in settings.LANGUAGES_BIDI}
379+    file(target_path, 'w').write(TEMPLATE % pformat(language_info)[1:-1])
380+    # Because the timestamps of the dummy .pyc and real .py may fall on the
381+    # same second, we must enforce compilation.
382+    import py_compile
383+    py_compile.compile(target_path)
384+    print 'Done.'
385+
386+
387+if __name__ == "__main__":
388+    from os.path import join, dirname, abspath
389+    path = abspath(join(dirname(__file__), '..', '..', LANGUAGE_INFO_PY_FILE))
390+    make_language_info(path)
391
392Property changes on: django/bin/make-language-info.py
393___________________________________________________________________
394Name: svn:executable
395   + *
396Name: svn:eol-style
397   + native
398
399Index: tests/regressiontests/i18n/tests.py
400===================================================================
401--- tests/regressiontests/i18n/tests.py (revision 7438)
402+++ tests/regressiontests/i18n/tests.py (working copy)
403@@ -1,5 +1,5 @@
404 # coding: utf-8
405-import misc
406+import misc, language_info
407 
408 regressions = ur"""
409 Format string interpolation should work with *_lazy objects.
410@@ -68,5 +68,6 @@
411 
412 __test__ = {
413     'regressions': regressions,
414+    'language_info': language_info.tests,
415     'misc': misc.tests,
416 }
417Index: tests/regressiontests/templates/tests.py
418===================================================================
419--- tests/regressiontests/templates/tests.py    (revision 7438)
420+++ tests/regressiontests/templates/tests.py    (working copy)
421@@ -755,6 +755,14 @@
422             'i18n17': ('{% load i18n %}{% blocktrans with anton|escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α & β'),
423             'i18n18': ('{% load i18n %}{% blocktrans with anton|force_escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α & β'),
424 
425+            # retrieving language information
426+            'i18n19': ('{% load i18n %}{% get_language_info for "de" as l %}{{ l.language_code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}', {}, 'de: German/Deutsch bidi=False'),
427+            'i18n20': ('{% load i18n %}{% get_language_info for LANGUAGE_CODE as l %}{{ l.language_code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}', {'LANGUAGE_CODE': 'fi'}, 'fi: Finnish/suomi bidi=False'),
428+            'i18n21': ('{% load i18n %}{% get_language_info_list for langcodes as langs %}{% for l in langs %}{{ l.language_code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}; {% endfor %}', {'langcodes': ['it', 'no']}, 'it: Italian/Italiano bidi=False; no: Norwegian/Norsk bidi=False; '),
429+            'i18n22': ('{% load i18n %}{% get_language_info_list for langcodes as langs %}{% for l in langs %}{{ l.language_code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}; {% endfor %}', {'langcodes': (('sl', 'Slovenian'), ('fa', 'Persian'))}, 'sl: Slovenian/Slovenski bidi=False; fa: Persian/Persian bidi=True; '),
430+            'i18n23': ('{% load i18n %}{{ "hu"|language_name }} {{ "hu"|language_name_local }} {{ "hu"|language_bidi }}', {}, 'Hungarian Magyar False'),
431+            'i18n24': ('{% load i18n %}{{ langcode|language_name }} {{ langcode|language_name_local }} {{ langcode|language_bidi }}', {'langcode': 'nl'}, 'Dutch Nederlands False'),
432+
433             ### HANDLING OF TEMPLATE_STRING_IF_INVALID ###################################
434 
435             'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')),
436Index: docs/i18n.txt
437===================================================================
438--- docs/i18n.txt       (revision 7438)
439+++ docs/i18n.txt       (working copy)
440@@ -266,7 +266,7 @@
441 Each ``RequestContext`` has access to three translation-specific variables:
442 
443     * ``LANGUAGES`` is a list of tuples in which the first element is the
444-      language code and the second is the language name (in that language).
445+      language code and the second is the language name (in English).
446     * ``LANGUAGE_CODE`` is the current user's preferred language, as a string.
447       Example: ``en-us``. (See "How language preference is discovered", below.)
448     * ``LANGUAGE_BIDI`` is the current language's direction. If True, it's a
449@@ -640,6 +640,64 @@
450 .. _session: ../sessions/
451 .. _request object: ../request_response/#httprequest-objects
452 
453+Local names of languages
454+========================
455+
456+The ``get_language_info()`` function provides detailed information about
457+languages::
458+
459+    >>> form django.utils.translation import get_language_info
460+    >>> l = get_language_info('de')
461+    >>> print l['name'], l['name_local'], l['bidi']
462+    German Deutsch False
463+
464+The ``name`` and ``name_local`` attributes of the dictionary contain the name
465+of the language in English and the language itself, respectively.  The ``bidi``
466+attribute is True only for bi-directional languages.
467+
468+In templates, you can use special template tags or filters to retrieve the same
469+information. To get information about a single language, use the
470+``{% get_language_info %}`` tag::
471+
472+    {% get_language_info for LANGUAGE_CODE as lang %}
473+    {% get_language_info for "pl" as lang %}
474+
475+You can then access the information::
476+
477+    Language code: {{ lang.language_code }}<br />
478+    Name of language: {{ lang.name_local }}<br />
479+    Name in English: {{ lang.name }}<br />
480+    Bi-directional: {{ lang.bidi }}
481+
482+You can also use the ``{% get_language_info_list %}`` template tag to retrieve
483+information for a list of languages (e.g. active languages as specified in
484+``settings.LANGUAGES``).  See `The set_language redirect view`_ for an example
485+of how to display a language selector using ``{% get_language_info_list %}``.
486+
487+In addition to ``settings.LANGUAGES`` style nested tuples,
488+``{% get_language_info_list %}`` supports simple lists of language codes.  If
489+you do this in your view::
490+
491+  return render_to_response('mytemplate.html',
492+                            {'available_languages': ['en', 'es', 'fr']},
493+                            RequestContext(request))
494+
495+you can iterate those languages in the template::
496+
497+  {% get_language_info_list available_languages %}
498+  {% for l in available_languages %} ... {% endfor %}
499+
500+There are also simple filters available for convenience:
501+
502+    * ``{{ LANGUAGE_CODE|language_name }}``  ("German")
503+    * ``{{ LANGUAGE_CODE|language_name_local }}`` ("Deutsch")
504+    * ``{{ LANGUAGE_CODE|bidi }}`` (False)     
505+
506+The source of the language information is the ``django.conf.language_info``
507+module.  If you add a new translation, you should run the
508+``django/bin/make-language-info.py`` script to insert the information for the
509+added language.
510+
511 Using translations in your own projects
512 =======================================
513 
514@@ -732,16 +790,20 @@
515     * If that's empty -- say, if a user's browser suppresses that header --
516       then the user will be redirected to ``/`` (the site root) as a fallback.
517 
518-Here's example HTML template code::
519+Here's example HTML template code (see `Local names of languages`_ for more
520+information)::
521 
522+    {% get_language_info_list for LANGUAGES as available_languages %}
523     <form action="/i18n/setlang/" method="post">
524-    <input name="next" type="hidden" value="/next/page/" />
525-    <select name="language">
526-    {% for lang in LANGUAGES %}
527-    <option value="{{ lang.0 }}">{{ lang.1 }}</option>
528-    {% endfor %}
529-    </select>
530-    <input type="submit" value="Go" />
531+      <input name="next" type="hidden" value="/next/page/" />
532+      <select name="language">
533+        {% for lang in available_languages %}
534+        <option value="{{ lang.language_code }}" {% ifequal lang.language_code LANGUAGE_CODE %}selected{% endifequal %}>
535+          {{ lang.name_local }}
536+        </option>
537+        {% endfor %}
538+      </select>
539+      <input type="submit" value="Go" />
540     </form>
541 
542 Translations and JavaScript