Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 510 lines (442 sloc) 21.688 kb
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
1 #!/usr/bin/env python
2 #
3 # Copyright 2009 Facebook
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
15 # under the License.
16
17 """Translation methods for generating localized strings.
18
f6b3651 @bdarnell More autodoc fixes
bdarnell authored
19 To load a locale and generate a translated string::
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
20
21 user_locale = locale.get("es_LA")
22 print user_locale.translate("Sign out")
23
24 locale.get() returns the closest matching locale, not necessarily the
25 specific locale you requested. You can support pluralization with
f6b3651 @bdarnell More autodoc fixes
bdarnell authored
26 additional arguments to translate(), e.g.::
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
27
28 people = [...]
29 message = user_locale.translate(
30 "%(list)s is online", "%(list)s are online", len(people))
31 print message % {"list": user_locale.list(people)}
32
33 The first string is chosen if len(people) == 1, otherwise the second
34 string is chosen.
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
35
36 Applications should call one of load_translations (which uses a simple
37 CSV format) or load_gettext_translations (which uses the .mo format
38 supported by gettext and related tools). If neither method is called,
39 the locale.translate method will simply return the original string.
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
40 """
41
58a7ff1 @bdarnell Turn on __future__ division too.
bdarnell authored
42 from __future__ import absolute_import, division, with_statement
da6d821 @bdarnell Standardize future imports for all files in the package.
bdarnell authored
43
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
44 import csv
45 import datetime
46 import os
a93ccd7 @bdarnell Allow any properly-formatted locale, not just ones in LOCALE_NAMES.
bdarnell authored
47 import re
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
48
6b75758 python3k to_unicode fix in locale.load_translations
sergey aleksandrov authored
49 from tornado import escape
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
50 from tornado.log import gen_log
6b75758 python3k to_unicode fix in locale.load_translations
sergey aleksandrov authored
51
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
52 _default_locale = "en_US"
53 _translations = {}
54 _supported_locales = frozenset([_default_locale])
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
55 _use_gettext = False
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
56
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
57
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
58 def get(*locale_codes):
59 """Returns the closest match for the given locale codes.
60
61 We iterate over all given locale codes in order. If we have a tight
62 or a loose match for the code (e.g., "en" for "en_US"), we return
63 the locale. Otherwise we move to the next code in the list.
64
65 By default we return en_US if no translations are found for any of
66 the specified locales. You can change the default locale with
67 set_default_locale() below.
68 """
69 return Locale.get_closest(*locale_codes)
70
71
72 def set_default_locale(code):
73 """Sets the default locale, used in get_closest_locale().
74
75 The default locale is assumed to be the language used for all strings
76 in the system. The translations loaded from disk are mappings from
77 the default locale to the destination locale. Consequently, you don't
78 need to create a translation file for the default locale.
79 """
80 global _default_locale
81 global _supported_locales
82 _default_locale = code
83 _supported_locales = frozenset(_translations.keys() + [_default_locale])
84
85
86 def load_translations(directory):
b92e70d @bdarnell Fix a non-ascii docstring
bdarnell authored
87 u"""Loads translations from CSV files in a directory.
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
88
89 Translations are strings with optional Python-style named placeholders
90 (e.g., "My name is %(name)s") and their associated translations.
91
92 The directory should have translation files of the form LOCALE.csv,
93 e.g. es_GT.csv. The CSV files should have two or three columns: string,
94 translation, and an optional plural indicator. Plural indicators should
95 be one of "plural" or "singular". A given string can have both singular
96 and plural forms. For example "%(name)s liked this" may have a
97 different verb conjugation depending on whether %(name)s is one
98 name or a list of names. There should be two rows in the CSV file for
99 that string, one with plural indicator "singular", and one "plural".
100 For strings with no verbs that would change on translation, simply
101 use "unknown" or the empty string (or don't include the column at all).
102
5970af4 @bdarnell Document the fact that CSV files should not have spaces after commas
bdarnell authored
103 The file is read using the csv module in the default "excel" dialect.
104 In this format there should not be spaces after the commas.
105
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
106 Example translation es_LA.csv:
107
108 "I love you","Te amo"
b92e70d @bdarnell Fix a non-ascii docstring
bdarnell authored
109 "%(name)s liked this","A %(name)s les gust\u00f3 esto","plural"
110 "%(name)s liked this","A %(name)s le gust\u00f3 esto","singular"
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
111
112 """
113 global _translations
114 global _supported_locales
115 _translations = {}
116 for path in os.listdir(directory):
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
117 if not path.endswith(".csv"):
118 continue
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
119 locale, extension = path.split(".")
a93ccd7 @bdarnell Allow any properly-formatted locale, not just ones in LOCALE_NAMES.
bdarnell authored
120 if not re.match("[a-z]+(_[A-Z]+)?$", locale):
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
121 gen_log.error("Unrecognized locale %r (path: %s)", locale,
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
122 os.path.join(directory, path))
123 continue
562d960 @bdarnell Better ($LANG-independent) fix for csv translation loading on py3.
bdarnell authored
124 full_path = os.path.join(directory, path)
125 try:
126 # python 3: csv.reader requires a file open in text mode.
127 # Force utf8 to avoid dependence on $LANG environment variable.
128 f = open(full_path, "r", encoding="utf-8")
129 except TypeError:
130 # python 2: files return byte strings, which are decoded below.
131 # Once we drop python 2.5, this could use io.open instead
132 # on both 2 and 3.
133 f = open(full_path, "r")
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
134 _translations[locale] = {}
135 for i, row in enumerate(csv.reader(f)):
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
136 if not row or len(row) < 2:
137 continue
6b75758 python3k to_unicode fix in locale.load_translations
sergey aleksandrov authored
138 row = [escape.to_unicode(c).strip() for c in row]
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
139 english, translation = row[:2]
140 if len(row) > 2:
141 plural = row[2] or "unknown"
142 else:
143 plural = "unknown"
144 if plural not in ("plural", "singular", "unknown"):
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
145 gen_log.error("Unrecognized plural indicator %r in %s line %d",
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
146 plural, path, i + 1)
147 continue
148 _translations[locale].setdefault(plural, {})[english] = translation
149 f.close()
150 _supported_locales = frozenset(_translations.keys() + [_default_locale])
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
151 gen_log.debug("Supported locales: %s", sorted(_supported_locales))
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
152
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
153
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
154 def load_gettext_translations(directory, domain):
155 """Loads translations from gettext's locale tree
156
157 Locale tree is similar to system's /usr/share/locale, like:
158
159 {directory}/{lang}/LC_MESSAGES/{domain}.mo
160
161 Three steps are required to have you app translated:
162
163 1. Generate POT translation file
164 xgettext --language=Python --keyword=_:1,2 -d cyclone file1.py file2.html etc
165
166 2. Merge against existing POT file:
167 msgmerge old.po cyclone.po > new.po
168
169 3. Compile:
170 msgfmt cyclone.po -o {directory}/pt_BR/LC_MESSAGES/cyclone.mo
171 """
172 import gettext
173 global _translations
174 global _supported_locales
175 global _use_gettext
176 _translations = {}
177 for lang in os.listdir(directory):
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
178 if lang.startswith('.'):
179 continue # skip .svn, etc
180 if os.path.isfile(os.path.join(directory, lang)):
181 continue
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
182 try:
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
183 os.stat(os.path.join(directory, lang, "LC_MESSAGES", domain + ".mo"))
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
184 _translations[lang] = gettext.translation(domain, directory,
185 languages=[lang])
186 except Exception, e:
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
187 gen_log.error("Cannot load translation for '%s': %s", lang, str(e))
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
188 continue
189 _supported_locales = frozenset(_translations.keys() + [_default_locale])
190 _use_gettext = True
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
191 gen_log.debug("Supported locales: %s", sorted(_supported_locales))
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
192
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
193
99d9d74 @ohardy Fix locale.get_supported_locales(): remove cls parameter
ohardy authored
194 def get_supported_locales():
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
195 """Returns a list of all the supported locale codes."""
196 return _supported_locales
197
198
199 class Locale(object):
55d3be1 @bdarnell Run coverage check and fill in the blanks
bdarnell authored
200 """Object representing a locale.
201
202 After calling one of `load_translations` or `load_gettext_translations`,
203 call `get` or `get_closest` to get a Locale object.
204 """
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
205 @classmethod
206 def get_closest(cls, *locale_codes):
207 """Returns the closest match for the given locale code."""
208 for code in locale_codes:
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
209 if not code:
210 continue
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
211 code = code.replace("-", "_")
212 parts = code.split("_")
213 if len(parts) > 2:
214 continue
215 elif len(parts) == 2:
216 code = parts[0].lower() + "_" + parts[1].upper()
217 if code in _supported_locales:
218 return cls.get(code)
219 if parts[0].lower() in _supported_locales:
220 return cls.get(parts[0].lower())
221 return cls.get(_default_locale)
222
223 @classmethod
224 def get(cls, code):
225 """Returns the Locale for the given locale code.
226
227 If it is not supported, we raise an exception.
228 """
229 if not hasattr(cls, "_cache"):
230 cls._cache = {}
231 if code not in cls._cache:
232 assert code in _supported_locales
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
233 translations = _translations.get(code, None)
234 if translations is None:
235 locale = CSVLocale(code, {})
236 elif _use_gettext:
237 locale = GettextLocale(code, translations)
238 else:
239 locale = CSVLocale(code, translations)
240 cls._cache[code] = locale
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
241 return cls._cache[code]
242
243 def __init__(self, code, translations):
244 self.code = code
245 self.name = LOCALE_NAMES.get(code, {}).get("name", u"Unknown")
246 self.rtl = False
247 for prefix in ["fa", "ar", "he"]:
248 if self.code.startswith(prefix):
249 self.rtl = True
250 break
251 self.translations = translations
252
253 # Initialize strings for date formatting
254 _ = self.translate
255 self._months = [
256 _("January"), _("February"), _("March"), _("April"),
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
257 _("May"), _("June"), _("July"), _("August"),
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
258 _("September"), _("October"), _("November"), _("December")]
259 self._weekdays = [
260 _("Monday"), _("Tuesday"), _("Wednesday"), _("Thursday"),
261 _("Friday"), _("Saturday"), _("Sunday")]
262
263 def translate(self, message, plural_message=None, count=None):
55d3be1 @bdarnell Run coverage check and fill in the blanks
bdarnell authored
264 """Returns the translation for the given message for this locale.
265
266 If plural_message is given, you must also provide count. We return
267 plural_message when count != 1, and we return the singular form
268 for the given message when count == 1.
269 """
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
270 raise NotImplementedError()
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
271
272 def format_date(self, date, gmt_offset=0, relative=True, shorter=False,
273 full_format=False):
274 """Formats the given date (which should be GMT).
275
276 By default, we return a relative time (e.g., "2 minutes ago"). You
277 can return an absolute date string with relative=False.
278
279 You can force a full format date ("July 10, 1980") with
280 full_format=True.
7c7b011 @bdarnell Make Locale.format_date() behave sanely with dates in the future.
bdarnell authored
281
282 This method is primarily intended for dates in the past.
283 For dates in the future, we fall back to full format.
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
284 """
285 if self.code.startswith("ru"):
286 relative = False
287 if type(date) in (int, long, float):
288 date = datetime.datetime.utcfromtimestamp(date)
289 now = datetime.datetime.utcnow()
7c7b011 @bdarnell Make Locale.format_date() behave sanely with dates in the future.
bdarnell authored
290 if date > now:
291 if relative and (date - now).seconds < 60:
292 # Due to click skew, things are some things slightly
293 # in the future. Round timestamps in the immediate
294 # future down to now in relative mode.
295 date = now
296 else:
297 # Otherwise, future dates always use the full format.
298 full_format = True
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
299 local_date = date - datetime.timedelta(minutes=gmt_offset)
300 local_now = now - datetime.timedelta(minutes=gmt_offset)
301 local_yesterday = local_now - datetime.timedelta(hours=24)
302 difference = now - date
303 seconds = difference.seconds
304 days = difference.days
305
306 _ = self.translate
307 format = None
308 if not full_format:
309 if relative and days == 0:
310 if seconds < 50:
311 return _("1 second ago", "%(seconds)d seconds ago",
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
312 seconds) % {"seconds": seconds}
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
313
314 if seconds < 50 * 60:
315 minutes = round(seconds / 60.0)
316 return _("1 minute ago", "%(minutes)d minutes ago",
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
317 minutes) % {"minutes": minutes}
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
318
319 hours = round(seconds / (60.0 * 60))
320 return _("1 hour ago", "%(hours)d hours ago",
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
321 hours) % {"hours": hours}
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
322
323 if days == 0:
324 format = _("%(time)s")
325 elif days == 1 and local_date.day == local_yesterday.day and \
326 relative:
327 format = _("yesterday") if shorter else \
328 _("yesterday at %(time)s")
329 elif days < 5:
330 format = _("%(weekday)s") if shorter else \
331 _("%(weekday)s at %(time)s")
332 elif days < 334: # 11mo, since confusing for same month last year
333 format = _("%(month_name)s %(day)s") if shorter else \
334 _("%(month_name)s %(day)s at %(time)s")
335
336 if format is None:
337 format = _("%(month_name)s %(day)s, %(year)s") if shorter else \
338 _("%(month_name)s %(day)s, %(year)s at %(time)s")
339
340 tfhour_clock = self.code not in ("en", "en_US", "zh_CN")
341 if tfhour_clock:
342 str_time = "%d:%02d" % (local_date.hour, local_date.minute)
343 elif self.code == "zh_CN":
344 str_time = "%s%d:%02d" % (
345 (u'\u4e0a\u5348', u'\u4e0b\u5348')[local_date.hour >= 12],
346 local_date.hour % 12 or 12, local_date.minute)
347 else:
348 str_time = "%d:%02d %s" % (
349 local_date.hour % 12 or 12, local_date.minute,
350 ("am", "pm")[local_date.hour >= 12])
351
352 return format % {
353 "month_name": self._months[local_date.month - 1],
354 "weekday": self._weekdays[local_date.weekday()],
355 "day": str(local_date.day),
356 "year": str(local_date.year),
357 "time": str_time
358 }
359
360 def format_day(self, date, gmt_offset=0, dow=True):
361 """Formats the given date as a day of week.
362
363 Example: "Monday, January 22". You can remove the day of week with
364 dow=False.
365 """
366 local_date = date - datetime.timedelta(minutes=gmt_offset)
367 _ = self.translate
368 if dow:
369 return _("%(weekday)s, %(month_name)s %(day)s") % {
370 "month_name": self._months[local_date.month - 1],
c4f682d @finiteloop Bug in locale.py and increase poll time for autoreloader
finiteloop authored
371 "weekday": self._weekdays[local_date.weekday()],
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
372 "day": str(local_date.day),
373 }
374 else:
375 return _("%(month_name)s %(day)s") % {
376 "month_name": self._months[local_date.month - 1],
377 "day": str(local_date.day),
378 }
379
380 def list(self, parts):
381 """Returns a comma-separated list for the given list of parts.
382
383 The format is, e.g., "A, B and C", "A and B" or just "A" for lists
384 of size 1.
385 """
386 _ = self.translate
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
387 if len(parts) == 0:
388 return ""
389 if len(parts) == 1:
390 return parts[0]
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
391 comma = u' \u0648 ' if self.code.startswith("fa") else u", "
392 return _("%(commas)s and %(last)s") % {
393 "commas": comma.join(parts[:-1]),
394 "last": parts[len(parts) - 1],
395 }
396
397 def friendly_number(self, value):
398 """Returns a comma-separated number for the given integer."""
399 if self.code not in ("en", "en_US"):
400 return str(value)
401 value = str(value)
402 parts = []
403 while value:
404 parts.append(value[-3:])
405 value = value[:-3]
406 return ",".join(reversed(parts))
407
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
408
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
409 class CSVLocale(Locale):
410 """Locale implementation using tornado's CSV translation format."""
411 def translate(self, message, plural_message=None, count=None):
412 if plural_message is not None:
413 assert count is not None
414 if count != 1:
415 message = plural_message
416 message_dict = self.translations.get("plural", {})
417 else:
418 message_dict = self.translations.get("singular", {})
419 else:
420 message_dict = self.translations.get("unknown", {})
421 return message_dict.get(message, message)
422
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
423
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
424 class GettextLocale(Locale):
425 """Locale implementation using the gettext module."""
489997d @bdarnell Fix load_gettext_translations on python 3
bdarnell authored
426 def __init__(self, code, translations):
427 try:
428 # python 2
429 self.ngettext = translations.ungettext
430 self.gettext = translations.ugettext
431 except AttributeError:
432 # python 3
433 self.ngettext = translations.ngettext
434 self.gettext = translations.gettext
435 # self.gettext must exist before __init__ is called, since it
436 # calls into self.translate
437 super(GettextLocale, self).__init__(code, translations)
438
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
439 def translate(self, message, plural_message=None, count=None):
440 if plural_message is not None:
441 assert count is not None
489997d @bdarnell Fix load_gettext_translations on python 3
bdarnell authored
442 return self.ngettext(message, plural_message, count)
62bc2e4 Add support for reading translation from gettext .mo files instead of…
Ben Darnell authored
443 else:
489997d @bdarnell Fix load_gettext_translations on python 3
bdarnell authored
444 return self.gettext(message)
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
445
446 LOCALE_NAMES = {
447 "af_ZA": {"name_en": u"Afrikaans", "name": u"Afrikaans"},
3dc7eba @bdarnell Add Amharic to tornado.locale's list of languages.
bdarnell authored
448 "am_ET": {"name_en": u"Amharic", "name": u'\u12a0\u121b\u122d\u129b'},
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
449 "ar_AR": {"name_en": u"Arabic", "name": u"\u0627\u0644\u0639\u0631\u0628\u064a\u0629"},
450 "bg_BG": {"name_en": u"Bulgarian", "name": u"\u0411\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438"},
451 "bn_IN": {"name_en": u"Bengali", "name": u"\u09ac\u09be\u0982\u09b2\u09be"},
452 "bs_BA": {"name_en": u"Bosnian", "name": u"Bosanski"},
453 "ca_ES": {"name_en": u"Catalan", "name": u"Catal\xe0"},
454 "cs_CZ": {"name_en": u"Czech", "name": u"\u010ce\u0161tina"},
455 "cy_GB": {"name_en": u"Welsh", "name": u"Cymraeg"},
456 "da_DK": {"name_en": u"Danish", "name": u"Dansk"},
457 "de_DE": {"name_en": u"German", "name": u"Deutsch"},
458 "el_GR": {"name_en": u"Greek", "name": u"\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac"},
459 "en_GB": {"name_en": u"English (UK)", "name": u"English (UK)"},
460 "en_US": {"name_en": u"English (US)", "name": u"English (US)"},
461 "es_ES": {"name_en": u"Spanish (Spain)", "name": u"Espa\xf1ol (Espa\xf1a)"},
462 "es_LA": {"name_en": u"Spanish", "name": u"Espa\xf1ol"},
463 "et_EE": {"name_en": u"Estonian", "name": u"Eesti"},
464 "eu_ES": {"name_en": u"Basque", "name": u"Euskara"},
465 "fa_IR": {"name_en": u"Persian", "name": u"\u0641\u0627\u0631\u0633\u06cc"},
466 "fi_FI": {"name_en": u"Finnish", "name": u"Suomi"},
467 "fr_CA": {"name_en": u"French (Canada)", "name": u"Fran\xe7ais (Canada)"},
468 "fr_FR": {"name_en": u"French", "name": u"Fran\xe7ais"},
469 "ga_IE": {"name_en": u"Irish", "name": u"Gaeilge"},
470 "gl_ES": {"name_en": u"Galician", "name": u"Galego"},
471 "he_IL": {"name_en": u"Hebrew", "name": u"\u05e2\u05d1\u05e8\u05d9\u05ea"},
472 "hi_IN": {"name_en": u"Hindi", "name": u"\u0939\u093f\u0928\u094d\u0926\u0940"},
473 "hr_HR": {"name_en": u"Croatian", "name": u"Hrvatski"},
474 "hu_HU": {"name_en": u"Hungarian", "name": u"Magyar"},
475 "id_ID": {"name_en": u"Indonesian", "name": u"Bahasa Indonesia"},
476 "is_IS": {"name_en": u"Icelandic", "name": u"\xcdslenska"},
477 "it_IT": {"name_en": u"Italian", "name": u"Italiano"},
0dcee8d @bdarnell Fix mangled language names in tornado.locale for Chinese, Japanese, K…
bdarnell authored
478 "ja_JP": {"name_en": u"Japanese", "name": u"\u65e5\u672c\u8a9e"},
479 "ko_KR": {"name_en": u"Korean", "name": u"\ud55c\uad6d\uc5b4"},
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
480 "lt_LT": {"name_en": u"Lithuanian", "name": u"Lietuvi\u0173"},
481 "lv_LV": {"name_en": u"Latvian", "name": u"Latvie\u0161u"},
482 "mk_MK": {"name_en": u"Macedonian", "name": u"\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438"},
483 "ml_IN": {"name_en": u"Malayalam", "name": u"\u0d2e\u0d32\u0d2f\u0d3e\u0d33\u0d02"},
484 "ms_MY": {"name_en": u"Malay", "name": u"Bahasa Melayu"},
485 "nb_NO": {"name_en": u"Norwegian (bokmal)", "name": u"Norsk (bokm\xe5l)"},
486 "nl_NL": {"name_en": u"Dutch", "name": u"Nederlands"},
487 "nn_NO": {"name_en": u"Norwegian (nynorsk)", "name": u"Norsk (nynorsk)"},
488 "pa_IN": {"name_en": u"Punjabi", "name": u"\u0a2a\u0a70\u0a1c\u0a3e\u0a2c\u0a40"},
489 "pl_PL": {"name_en": u"Polish", "name": u"Polski"},
490 "pt_BR": {"name_en": u"Portuguese (Brazil)", "name": u"Portugu\xeas (Brasil)"},
491 "pt_PT": {"name_en": u"Portuguese (Portugal)", "name": u"Portugu\xeas (Portugal)"},
492 "ro_RO": {"name_en": u"Romanian", "name": u"Rom\xe2n\u0103"},
493 "ru_RU": {"name_en": u"Russian", "name": u"\u0420\u0443\u0441\u0441\u043a\u0438\u0439"},
494 "sk_SK": {"name_en": u"Slovak", "name": u"Sloven\u010dina"},
495 "sl_SI": {"name_en": u"Slovenian", "name": u"Sloven\u0161\u010dina"},
496 "sq_AL": {"name_en": u"Albanian", "name": u"Shqip"},
497 "sr_RS": {"name_en": u"Serbian", "name": u"\u0421\u0440\u043f\u0441\u043a\u0438"},
498 "sv_SE": {"name_en": u"Swedish", "name": u"Svenska"},
499 "sw_KE": {"name_en": u"Swahili", "name": u"Kiswahili"},
500 "ta_IN": {"name_en": u"Tamil", "name": u"\u0ba4\u0bae\u0bbf\u0bb4\u0bcd"},
501 "te_IN": {"name_en": u"Telugu", "name": u"\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41"},
502 "th_TH": {"name_en": u"Thai", "name": u"\u0e20\u0e32\u0e29\u0e32\u0e44\u0e17\u0e22"},
503 "tl_PH": {"name_en": u"Filipino", "name": u"Filipino"},
504 "tr_TR": {"name_en": u"Turkish", "name": u"T\xfcrk\xe7e"},
505 "uk_UA": {"name_en": u"Ukraini ", "name": u"\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430"},
506 "vi_VN": {"name_en": u"Vietnamese", "name": u"Ti\u1ebfng Vi\u1ec7t"},
0dcee8d @bdarnell Fix mangled language names in tornado.locale for Chinese, Japanese, K…
bdarnell authored
507 "zh_CN": {"name_en": u"Chinese (Simplified)", "name": u"\u4e2d\u6587(\u7b80\u4f53)"},
508 "zh_TW": {"name_en": u"Chinese (Traditional)", "name": u"\u4e2d\u6587(\u7e41\u9ad4)"},
2afa973 @finiteloop Move Tornado project to Github
finiteloop authored
509 }
Something went wrong with that request. Please try again.