diff --git a/setup.py b/setup.py index b448832f3..ec66031f7 100755 --- a/setup.py +++ b/setup.py @@ -33,7 +33,6 @@ install_requires = [ "Unidecode>=0.04.14,<2.0", "Willow>=1.1,<1.2", "requests>=2.11.1,<3.0", - "l18n", ] # Testing dependencies diff --git a/wagtail/admin/decorators.py b/wagtail/admin/decorators.py index 870547748..555762ac5 100644 --- a/wagtail/admin/decorators.py +++ b/wagtail/admin/decorators.py @@ -1,4 +1,3 @@ -import l18n from django.contrib.auth.views import redirect_to_login as auth_redirect_to_login from django.core.exceptions import PermissionDenied from django.urls import reverse @@ -7,6 +6,7 @@ from django.utils.translation import activate as activate_lang from django.utils.translation import ugettext as _ from wagtail.admin import messages +from wagtail.utils import l18n def reject_request(request): diff --git a/wagtail/users/forms.py b/wagtail/users/forms.py index b42d9f381..653ba2a29 100644 --- a/wagtail/users/forms.py +++ b/wagtail/users/forms.py @@ -2,7 +2,6 @@ import warnings from itertools import groupby from operator import itemgetter -import l18n from django import forms from django.conf import settings from django.contrib.auth import get_user_model @@ -22,6 +21,7 @@ from wagtail.core.models import ( PAGE_PERMISSION_TYPE_CHOICES, PAGE_PERMISSION_TYPES, GroupPagePermission, Page, UserPagePermissionsProxy) from wagtail.users.models import UserProfile +from wagtail.utils import l18n User = get_user_model() diff --git a/wagtail/utils/l18n/CHANGES.rst b/wagtail/utils/l18n/CHANGES.rst new file mode 100644 index 000000000..a7c3fd58d --- /dev/null +++ b/wagtail/utils/l18n/CHANGES.rst @@ -0,0 +1,76 @@ +l18n - changes +============== + + +v2016.6.4 (07-09-2016) +---------------------- + +- Chinese (zh) translation overrides by Charlotte Blanc +- copy/deepcopy support for l18n lazy strings and dictionaries +- fix charset bug on python 2 + + +v2016.6.3 (30-08-2016) +---------------------- + +- items are now sorted in maps iterators +- subsets support + + +v2016.6.2 (23-08-2016) +---------------------- + +- fix requirement 'six' + + +v2016.6.1 (19-08-2016) +---------------------- + +- pytz 2016.6.1 +- fix encoding issues + + +v2016.6.0 (18-07-2016) +---------------------- + +- pytz 2016.6 +- remove strict pin against pytz version (#2) + + +v2016.4.0 (05-05-2016) +---------------------- + +- pytz 2016.4 +- Czech translation overrides by Jan Čermák + + +v2015.7.0 (26-11-2015) +---------------------- + +- pytz 2015.7 +- drops support for python 2.6 + + +v2015.6.0 (15-10-2015) +---------------------- + +- pytz 2015.6 +- German translations by Philipp Steinhardt +- Fixes locale files that were not included in some distribution targets (#1) +- Translates all pytz.all_timezones rather than only pytz.common_timezones + + +v2015.2.0 (22-04-2015) +---------------------- + +- updates pytz version to 2015.2 (no changes in translations) + + +v2014.10.1 (17-03-2015) +----------------------- + +- Birth +- Exposes ``tz_cities``, ``tz_fullnames`` and ``territories`` + dictionary-like objects +- Compatible with python 2.6+ and 3.3+ +- English and French translations diff --git a/wagtail/utils/l18n/CONTRIBUTORS.rst b/wagtail/utils/l18n/CONTRIBUTORS.rst new file mode 100644 index 000000000..d70cb7ca5 --- /dev/null +++ b/wagtail/utils/l18n/CONTRIBUTORS.rst @@ -0,0 +1,18 @@ +l18n contributors list +====================== + + +Code +---- + +Thomas Khyn, thomas@ksytek.com + + +Languages +--------- + +en: Thomas Khyn, thomas[at]ksytek.com +fr: Thomas Khyn, thomas[at]ksytek.com +de: Philipp Steinhardt, steinhardt[at]myvision.de +cs: Jan Čermák, sairon[at]sairon.cz +zh: Charlotte Blanc, charblanc17[at]gmail.com diff --git a/wagtail/utils/l18n/LICENSE.txt b/wagtail/utils/l18n/LICENSE.txt new file mode 100644 index 000000000..2cfd08704 --- /dev/null +++ b/wagtail/utils/l18n/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2016 Thomas Khyn and contributors (see CONTRIBUTORS.rst) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/wagtail/utils/l18n/README.rst b/wagtail/utils/l18n/README.rst new file mode 100644 index 000000000..f79a4920d --- /dev/null +++ b/wagtail/utils/l18n/README.rst @@ -0,0 +1,210 @@ +l18n +==== + +|copyright| 2014-2016 Thomas Khyn + +Locale internationalization package. Translations for places, timezones ... + +Tested with the latest minor versions of Python 2 and 3. + +Supported languages: English, French, German, Czech, Chinese +(`want to add yours?`_) + + +What is l18n? +------------- + +As you may have noticed, ``l18n`` is a contraction of ``i18n`` and ``l10n``, +namely 'internationalisation' and 'localization'. It basically provides +lazy translations for names used for localization purposes (e.g. places and +timezones). + +I started writing ``l18n`` when I was looking for translations for the pytz_ +library. Indeed, on a multi-lingual site where users can select the timezone +they are in, it's much better if they can select in their language, as in some +cases, the differences with the english name can be significant, hence the +place to look for it when it's sorted in alphabetical order. + +And as I am lazy, I thought of a way to - almost - automatically fetch the +translations from the CLDR_ (Unicode's Common Locale Data Repository) database. + +Integrating function to link timezone to country codes, there was no reason not +to try and provide translations also for the latter. In the near future, I - +or contributors - may also add currencies or measurement units fetched from +the CLDR database ... + + +How does it work? +----------------- + +To use ``l18n``, you first need to install it. It works well with ``pip``:: + + pip install l18n + +Then, in your code:: + + >>> import l18n + +``l18n`` exposes several read-only dictionary-like objects: + +l18n.tz_cities + + is a mapping between all the timezones listed in ``pytz.all_timezones`` + and human-friendly **lazy** versions of the translated name of the city + in the current language (see `Selecting the language`_ below). For example, + if the language is English:: + + >>> l18n.tz_cities['Pacific/Easter'] + L18NLazyString + >>> str(l18n.tz_cities['Pacific/Easter']) + 'Easter Island' + + In French, it would give:: + + >>> str(l18n.tz_cities['Pacific/Easter']) + 'Île de Pâques' + +l18n.tz_fullnames + + is a mapping between all the timezones listed in ``pytz.all_timezones`` + and **lazy** versions of the timezones' full names in the current language. + For example:: + + >>> str(l18n.tz_fullnames['Pacific/Easter']) + 'Pacific/Easter Island' # or 'Pacifique/Île de Pâques' in French + + It is interesting to note that for 3-components timezone names where the + local state or territory appears in the city name, ``l18n`` cleverly strips + this information so that it is not repeated:: + + >>> str(l18n.tz_fullnames['America/North_Dakota/New_Salem']) + 'America/North Dakota/New Salem' + + indeed:: + + >>> str(l18n.tz_cities['America/North_Dakota/New_salem']) + 'New Salem, North Dakota' + +l18n.territories + + is a mapping between the territory codes as defined in the CLDR_ and their + localized names, lazily defined. For example:: + + >>> str(l18n.territories['CZ']) + 'Czech Republic' # or 'République Tchèque' in French + + +.. note:: + + The values are translated each time they are evaluated, there is no caching. + This means that the same L18NLazyString / L18NLazyStringsList instance can + be used and produce 2 different outputs if you change the language between + the evaluations. + + +.. note:: + + The values in the above mentionned dictionaries can be overriden by your + own translations. The dictionaries are not read-only and values can be + added or removed at your convenience. + + +Lazy mappings special features (v.2016.6.3 onwards) +--------------------------------------------------- + +The fore-mentioned ``tz_cities``, ``tz_fullnames`` and ``territories`` are not +simple dictionaries and provide additional features. + +Sorting +....... + +When iterating over an ``L18NMap``, the items, keys or values are *yielded* in +alphabetical order **in the currently selected language**. For performance, the +results are cached by language, so the sort is only performed once per language. +Note that the values are still lazy objects that are evaluated only when +rendered into a string. + +Subsets +....... + +It is possible to generate a new ``L18NMap`` from an existing one by using the +``subset`` method and passing an iterable of ``keys`` that need to be kept in +the new mapping. Any cached sort is also used to generate the new cache, so +that there is nothing to re-calculate in the new subset. + +For example, one can generate a map of translations for +``pytz.common_timezones``:: + + >>> common_cities = l18n.tz_cities.subset(pytz.common_timezones.keys()) + + +Selecting the language +---------------------- + +By default, when importing ``l18n``, the current default locale is used (via +``locale.getdefaultlocale()``). If it is not the one you want or if you need to +change it, it is rather easy:: + + >>> l18n.set_language('en') + >>> str(l18n.tz_cities['Pacific/Easter']) + 'Easter Island' + >>> l18n.set_language('fr') + >>> str(l18n.tz_cities['Pacific/Easter']) + 'Île de Pâques' + +And in case you want to disable translation and use raw default strings:: + + >>> l18n.set_language(None) + + +Utilities +--------- + +``l18n`` also exposes a few functions that may be helpful in some cases: + +``l18n.utils.get_country_tzs(country_code)`` + + returns a list of locations for the given country code, sorted in + alphabetical order in the currently selected language + +``l18n.utils.get_country_code_from_tz(timezone)`` + + returns the country code from a given (untranslated) timezone + + +Versionning +----------- + +``l18n``'s main version number matches ``pytz``'s version number. ``l18n`` +2014.10.X will be fully compatible with ``pytz`` 2014.10 whatever the value of +X. Indeed, the primary aim is to keep ``l18n``'s translation files consistent +with ``pytz``'s timezone names. + +Before ``l18n`` 2016.6, the ``pytz`` version was pinned against the ``l18n`` +version. Now, ``l18n`` YEAR.MONTH can now be used with any subsequent ``pytz`` +version. However, note that there may be missing translations if the 2 versions +are too different from each other. In that case, open an issue_ to request a +new version of ``l18n`` to be published. + + +.. _`want to add yours?`: + +Want to add a language? +----------------------- + +Great idea !! Have a look at CONTRIBUTE.rst_. + + +Roadmap +------- + +- Add supported languages +- Add currencies and other stuff + + +.. |copyright| unicode:: 0xA9 + +.. _pytz: https://pypi.python.org/pypi/pytz/ +.. _CLDR: http://cldr.unicode.org/ +.. _CONTRIBUTE.rst: https://bitbucket.org/tkhyn/l18n/src/tip/CONTRIBUTE.rst +.. _issue: https://bitbucket.org/tkhyn/l18n/issues/new diff --git a/wagtail/utils/l18n/__init__.py b/wagtail/utils/l18n/__init__.py new file mode 100644 index 000000000..a9a76e2a9 --- /dev/null +++ b/wagtail/utils/l18n/__init__.py @@ -0,0 +1,4 @@ +from .version import __version__, __version_info__ + +from .maps import tz_cities, tz_fullnames, territories +from .translation import set_language diff --git a/wagtail/utils/l18n/__maps.py b/wagtail/utils/l18n/__maps.py new file mode 100644 index 000000000..d0c8f7e6b --- /dev/null +++ b/wagtail/utils/l18n/__maps.py @@ -0,0 +1,920 @@ +# -*- coding: utf-8 -*- + +# AUTOMATICALLY GENERATED FILE, DO NOT EDIT + +tz_locations = { + 'Brazil': u'Brazil', + 'Canada': u'Canada', + 'Asia': u'Asia', + 'America/Indiana/Tell_City': u'Tell City', + 'America/North_Dakota/New_Salem': u'New Salem', + 'US': u'United States', + 'Indiana': u'Indiana', + 'Argentina': u'Argentina', + 'Europe': u'Europe', + 'Australia': u'Australia', + 'America/Indiana/Vevay': u'Vevay', + 'Arctic': u'Arctic', + 'America/Indiana/Marengo': u'Marengo', + 'Atlantic': u'Atlantic', + 'Chile': u'Chile', + 'America/Indiana/Vincennes': u'Vincennes', + 'America': u'America', + 'America/North_Dakota/Center': u'Center', + 'Kentucky': u'Kentucky', + 'Etc': u'Etc', + 'North_Dakota': u'North Dakota', + 'America/Indiana/Knox': u'Knox', + 'America/Kentucky/Monticello': u'Monticello', + 'America/North_Dakota/Beulah': u'Beulah', + 'America/Indiana/Petersburg': u'Petersburg', + 'Mexico': u'Mexico', + 'Africa': u'Africa', + 'Pacific': u'Pacific', + 'Antarctica': u'Antarctica', + 'Indian': u'Indian', + 'America/Indiana/Winamac': u'Winamac', +} + +tz_cities = { + 'Atlantic/Canary': u'Canary', + 'Australia/Melbourne': u'Melbourne', + 'Etc/GMT+9': u'GMT+9', + 'Etc/GMT+8': u'GMT+8', + 'Europe/Lisbon': u'Lisbon', + 'Etc/GMT+3': u'GMT+3', + 'Etc/GMT+2': u'GMT+2', + 'Etc/GMT+1': u'GMT+1', + 'Etc/GMT+0': u'GMT+0', + 'America/Nipigon': u'Nipigon', + 'Etc/GMT+6': u'GMT+6', + 'Etc/GMT+5': u'GMT+5', + 'America/Miquelon': u'Miquelon', + 'Pacific/Wallis': u'Wallis', + 'America/Fort_Nelson': u'Fort Nelson', + 'Antarctica/Davis': u'Davis', + 'America/Coral_Harbour': u'Atikokan', + 'Asia/Dhaka': u'Dhaka', + 'America/St_Lucia': u'St. Lucia', + 'Canada/Newfoundland': u'Newfoundland', + 'Asia/Kashgar': u'Kashgar', + 'America/Phoenix': u'Phoenix', + 'Europe/Kaliningrad': u'Kaliningrad', + 'Etc/GMT+7': u'GMT+7', + 'America/Mazatlan': u'Mazatlan', + 'Arctic/Longyearbyen': u'Longyearbyen', + 'Europe/Guernsey': u'Guernsey', + 'GB': u'GB', + 'Europe/Paris': u'Paris', + 'Europe/Stockholm': u'Stockholm', + 'Pacific/Fiji': u'Fiji', + 'Pacific/Apia': u'Apia', + 'Etc/GMT+4': u'GMT+4', + 'Pacific/Pago_Pago': u'Pago Pago', + 'Asia/Rangoon': u'Rangoon', + 'America/Mexico_City': u'Mexico City', + 'America/Ensenada': u'Ensenada', + 'America/Puerto_Rico': u'Puerto Rico', + 'Indian/Mauritius': u'Mauritius', + 'Europe/Berlin': u'Berlin', + 'Europe/Zurich': u'Zurich', + 'Africa/Casablanca': u'Casablanca', + 'Antarctica/Macquarie': u'Macquarie Island', + 'Asia/Krasnoyarsk': u'Krasnoyarsk', + 'Australia/Canberra': u'Canberra', + 'Atlantic/Bermuda': u'Bermuda', + 'Asia/Ujung_Pandang': u'Ujung Pandang', + 'America/Araguaina': u'Araguaina', + 'Asia/Tehran': u'Tehran', + 'Asia/Saigon': u'Ho Chi Minh', + 'Asia/Baku': u'Baku', + 'America/St_Barthelemy': u'St. Barthelemy', + 'America/Porto_Acre': u'Porto Acre', + 'America/Santarem': u'Santarem', + 'America/Argentina/Cordoba': u'Cordoba', + 'Libya': u'Libya', + 'America/Danmarkshavn': u'Danmarkshavn', + 'America/Scoresbysund': u'Ittoqqortoormiit', + 'America/Eirunepe': u'Eirunepe', + 'America/Caracas': u'Caracas', + 'Asia/Baghdad': u'Baghdad', + 'Africa/Monrovia': u'Monrovia', + 'Atlantic/Stanley': u'Stanley', + 'America/St_Vincent': u'St. Vincent', + 'Portugal': u'Portugal', + 'Europe/Tiraspol': u'Tiraspol', + 'America/Vancouver': u'Vancouver', + 'Asia/Ho_Chi_Minh': u'Ho Chi Minh', + 'Europe/Busingen': u'Busingen', + 'Etc/GMT0': u'GMT0', + 'Asia/Thimphu': u'Thimphu', + 'Africa/Ouagadougou': u'Ouagadougou', + 'America/Belize': u'Belize', + 'America/Port_of_Spain': u'Port of Spain', + 'Greenwich': u'Greenwich', + 'Asia/Tashkent': u'Tashkent', + 'Asia/Tokyo': u'Tokyo', + 'Pacific/Kiritimati': u'Kiritimati', + 'Australia/Sydney': u'Sydney', + 'Europe/Riga': u'Riga', + 'Asia/Dili': u'Dili', + 'Africa/Mbabane': u'Mbabane', + 'Asia/Oral': u'Oral', + 'Asia/Aden': u'Aden', + 'Europe/Isle_of_Man': u'Isle of Man', + 'Europe/Istanbul': u'Istanbul', + 'Asia/Magadan': u'Magadan', + 'Australia/Lindeman': u'Lindeman', + 'US/Michigan': u'Michigan', + 'Pacific/Galapagos': u'Galapagos', + 'America/Bogota': u'Bogota', + 'Africa/Asmara': u'Asmara', + 'America/Chicago': u'Chicago', + 'Pacific/Kwajalein': u'Kwajalein', + 'Australia/Broken_Hill': u'Broken Hill', + 'America/Cuiaba': u'Cuiaba', + 'Indian/Christmas': u'Christmas', + 'Asia/Jayapura': u'Jayapura', + 'Europe/Brussels': u'Brussels', + 'Asia/Chongqing': u'Chongqing', + 'Australia/NSW': u'NSW', + 'Canada/Pacific': u'Pacific', + 'America/Noronha': u'Noronha', + 'Europe/Podgorica': u'Podgorica', + 'Africa/Algiers': u'Algiers', + 'Africa/Harare': u'Harare', + 'Africa/Ndjamena': u'Ndjamena', + 'America/Costa_Rica': u'Costa Rica', + 'Europe/Ljubljana': u'Ljubljana', + 'Indian/Mayotte': u'Mayotte', + 'Asia/Phnom_Penh': u'Phnom Penh', + 'America/Managua': u'Managua', + 'America/Pangnirtung': u'Pangnirtung', + 'Etc/GMT+12': u'GMT+12', + 'America/Tijuana': u'Tijuana', + 'Pacific/Fakaofo': u'Fakaofo', + 'America/Adak': u'Adak', + 'America/Antigua': u'Antigua', + 'America/Indiana/Indianapolis': u'Indianapolis', + 'America/Argentina/La_Rioja': u'La Rioja', + 'Pacific/Tahiti': u'Tahiti', + 'Asia/Brunei': u'Brunei', + 'Europe/Zagreb': u'Zagreb', + 'America/Asuncion': u'Asuncion', + 'Europe/Vienna': u'Vienna', + 'Mexico/General': u'General', + 'Australia/Hobart': u'Hobart', + 'America/Juneau': u'Juneau', + 'America/Inuvik': u'Inuvik', + 'America/Ojinaga': u'Ojinaga', + 'Europe/Astrakhan': u'Astrakhan', + 'America/Montreal': u'Montreal', + 'Asia/Seoul': u'Seoul', + 'Brazil/East': u'East', + 'Indian/Comoro': u'Comoro', + 'Antarctica/Rothera': u'Rothera', + 'Hongkong': u'Hongkong', + 'Europe/Tallinn': u'Tallinn', + 'Indian/Mahe': u'Mahe', + 'Asia/Calcutta': u'Kolkata', + 'Australia/South': u'South', + 'America/Argentina/Jujuy': u'Jujuy', + 'Asia/Bishkek': u'Bishkek', + 'America/Creston': u'Creston', + 'America/Martinique': u'Martinique', + 'Asia/Singapore': u'Singapore', + 'PRC': u'PRC', + 'Africa/Nairobi': u'Nairobi', + 'Asia/Thimbu': u'Thimbu', + 'America/Maceio': u'Maceio', + 'Africa/Cairo': u'Cairo', + 'Europe/Moscow': u'Moscow', + 'Antarctica/Palmer': u'Palmer', + 'Asia/Ulaanbaatar': u'Ulaanbaatar', + 'America/Rainy_River': u'Rainy River', + 'Africa/Kampala': u'Kampala', + 'Asia/Colombo': u'Colombo', + 'Australia/Adelaide': u'Adelaide', + 'America/Cambridge_Bay': u'Cambridge Bay', + 'Africa/Luanda': u'Luanda', + 'Pacific/Chatham': u'Chatham', + 'America/Indiana/Winamac': u'Winamac, Indiana', + 'Asia/Dacca': u'Dacca', + 'America/Cordoba': u'Cordoba', + 'Asia/Tbilisi': u'Tbilisi', + 'Europe/Gibraltar': u'Gibraltar', + 'Asia/Karachi': u'Karachi', + 'Asia/Harbin': u'Harbin', + 'Australia/Lord_Howe': u'Lord Howe', + 'Etc/GMT-9': u'GMT-9', + 'Etc/GMT-8': u'GMT-8', + 'America/Bahia_Banderas': u'Bahia Banderas', + 'Etc/GMT-1': u'GMT-1', + 'Etc/GMT-0': u'GMT-0', + 'Etc/GMT-3': u'GMT-3', + 'Etc/GMT-2': u'GMT-2', + 'Etc/GMT-5': u'GMT-5', + 'Etc/GMT-4': u'GMT-4', + 'Etc/GMT-7': u'GMT-7', + 'Europe/Nicosia': u'Nicosia', + 'America/Boa_Vista': u'Boa Vista', + 'Eire': u'Eire', + 'America/Lima': u'Lima', + 'Indian/Reunion': u'Reunion', + 'Canada/Mountain': u'Mountain', + 'Asia/Srednekolymsk': u'Srednekolymsk', + 'CET': u'CET', + 'Europe/Belfast': u'Belfast', + 'America/Blanc-Sablon': u'Blanc-Sablon', + 'Australia/West': u'West', + 'Antarctica/Syowa': u'Syowa', + 'America/Jamaica': u'Jamaica', + 'Europe/Kiev': u'Kiev', + 'Europe/Budapest': u'Budapest', + 'Canada/Eastern': u'Eastern', + 'Pacific/Midway': u'Midway', + 'America/Goose_Bay': u'Goose Bay', + 'Australia/Victoria': u'Victoria', + 'Asia/Amman': u'Amman', + 'Asia/Sakhalin': u'Sakhalin', + 'Africa/Windhoek': u'Windhoek', + 'US/Pacific': u'Pacific', + 'America/Sitka': u'Sitka', + 'Asia/Katmandu': u'Kathmandu', + 'America/Guyana': u'Guyana', + 'Pacific/Pohnpei': u'Pohnpei', + 'America/Sao_Paulo': u'Sao Paulo', + 'Turkey': u'Turkey', + 'Australia/Yancowinna': u'Yancowinna', + 'America/Lower_Princes': u'Lower Prince’s Quarter', + 'Australia/Perth': u'Perth', + 'Africa/Djibouti': u'Djibouti', + 'Asia/Jakarta': u'Jakarta', + 'Asia/Pyongyang': u'Pyongyang', + 'EST5EDT': u'EST5EDT', + 'Africa/Johannesburg': u'Johannesburg', + 'Canada/East-Saskatchewan': u'East-Saskatchewan', + 'Asia/Tel_Aviv': u'Tel Aviv', + 'Antarctica/Troll': u'Troll', + 'Asia/Istanbul': u'Istanbul', + 'Asia/Irkutsk': u'Irkutsk', + 'Iran': u'Iran', + 'Australia/ACT': u'ACT', + 'Africa/Niamey': u'Niamey', + 'Australia/North': u'North', + 'America/Belem': u'Belem', + 'America/Indiana/Marengo': u'Marengo, Indiana', + 'Africa/Nouakchott': u'Nouakchott', + 'Asia/Ashkhabad': u'Ashkhabad', + 'Europe/Vilnius': u'Vilnius', + 'America/Cayenne': u'Cayenne', + 'Africa/Mogadishu': u'Mogadishu', + 'America/Kentucky/Monticello': u'Monticello, Kentucky', + 'America/Rio_Branco': u'Rio Branco', + 'America/Cancun': u'Cancun', + 'America/Havana': u'Havana', + 'Chile/EasterIsland': u'EasterIsland', + 'Pacific/Guam': u'Guam', + 'Pacific/Kosrae': u'Kosrae', + 'Universal': u'Universal', + 'US/Arizona': u'Arizona', + 'Atlantic/Azores': u'Azores', + 'Australia/Eucla': u'Eucla', + 'Asia/Shanghai': u'Shanghai', + 'US/Alaska': u'Alaska', + 'America/Godthab': u'Nuuk', + 'Asia/Beirut': u'Beirut', + 'Africa/Maputo': u'Maputo', + 'HST': u'HST', + 'Asia/Bahrain': u'Bahrain', + 'Asia/Ashgabat': u'Ashgabat', + 'Asia/Riyadh': u'Riyadh', + 'Atlantic/Faeroe': u'Faroe', + 'America/Montevideo': u'Montevideo', + 'America/Anguilla': u'Anguilla', + 'Asia/Damascus': u'Damascus', + 'America/North_Dakota/Center': u'Center, North Dakota', + 'UCT': u'UCT', + 'America/Indiana/Vevay': u'Vevay, Indiana', + 'Atlantic/St_Helena': u'St. Helena', + 'Iceland': u'Iceland', + 'Europe/Vatican': u'Vatican', + 'America/Indiana/Vincennes': u'Vincennes, Indiana', + 'Asia/Almaty': u'Almaty', + 'Australia/Queensland': u'Queensland', + 'America/Santo_Domingo': u'Santo Domingo', + 'Africa/Brazzaville': u'Brazzaville', + 'America/Nome': u'Nome', + 'Asia/Taipei': u'Taipei', + 'America/Yakutat': u'Yakutat', + 'W-SU': u'W-SU', + 'America/Argentina/Mendoza': u'Mendoza', + 'Zulu': u'Zulu', + 'Egypt': u'Egypt', + 'America/Tortola': u'Tortola', + 'Etc/UTC': u'UTC', + 'Brazil/Acre': u'Acre', + 'Africa/Asmera': u'Asmara', + 'Antarctica/Mawson': u'Mawson', + 'America/Buenos_Aires': u'Buenos Aires', + 'Asia/Kolkata': u'Kolkata', + 'Africa/Maseru': u'Maseru', + 'America/Atikokan': u'Atikokan', + 'America/Louisville': u'Louisville', + 'Pacific/Yap': u'Yap', + 'America/Santa_Isabel': u'Santa Isabel', + 'Asia/Kuching': u'Kuching', + 'Africa/Libreville': u'Libreville', + 'Africa/Freetown': u'Freetown', + 'Africa/Bissau': u'Bissau', + 'Europe/Samara': u'Samara', + 'Europe/Amsterdam': u'Amsterdam', + 'Europe/Tirane': u'Tirane', + 'Pacific/Saipan': u'Saipan', + 'Africa/Abidjan': u'Abidjan', + 'Europe/Zaporozhye': u'Zaporozhye', + 'America/Atka': u'Atka', + 'America/El_Salvador': u'El Salvador', + 'Europe/Madrid': u'Madrid', + 'Africa/Juba': u'Juba', + 'America/Santiago': u'Santiago', + 'America/Argentina/Buenos_Aires': u'Buenos Aires', + 'America/Argentina/San_Luis': u'San Luis', + 'Europe/Skopje': u'Skopje', + 'WET': u'WET', + 'America/Aruba': u'Aruba', + 'Navajo': u'Navajo', + 'America/Indianapolis': u'Indianapolis', + 'America/Regina': u'Regina', + 'Pacific/Truk': u'Chuuk', + 'Pacific/Chuuk': u'Chuuk', + 'Asia/Khandyga': u'Khandyga', + 'Pacific/Funafuti': u'Funafuti', + 'Canada/Atlantic': u'Atlantic', + 'America/Merida': u'Merida', + 'America/Guatemala': u'Guatemala', + 'Africa/Sao_Tome': u'Sao Tome', + 'Asia/Tomsk': u'Tomsk', + 'Asia/Makassar': u'Makassar', + 'Africa/Bujumbura': u'Bujumbura', + 'Europe/Chisinau': u'Chisinau', + 'Europe/Warsaw': u'Warsaw', + 'Asia/Yekaterinburg': u'Yekaterinburg', + 'US/Hawaii': u'Hawaii', + 'MET': u'MET', + 'Antarctica/Casey': u'Casey', + 'Pacific/Enderbury': u'Enderbury', + 'America/Thule': u'Thule', + 'America/St_Johns': u'St. John’s', + 'America/Moncton': u'Moncton', + 'Europe/Helsinki': u'Helsinki', + 'Atlantic/Cape_Verde': u'Cape Verde', + 'America/Tegucigalpa': u'Tegucigalpa', + 'Indian/Cocos': u'Cocos', + 'America/Boise': u'Boise', + 'America/Guadeloupe': u'Guadeloupe', + 'America/Nassau': u'Nassau', + 'Europe/Prague': u'Prague', + 'America/Halifax': u'Halifax', + 'Asia/Hovd': u'Hovd', + 'Canada/Saskatchewan': u'Saskatchewan', + 'America/Manaus': u'Manaus', + 'America/Rankin_Inlet': u'Rankin Inlet', + 'Etc/UCT': u'UCT', + 'GB-Eire': u'GB-Eire', + 'Etc/Greenwich': u'Greenwich', + 'Atlantic/Jan_Mayen': u'Jan Mayen', + 'America/North_Dakota/Beulah': u'Beulah, North Dakota', + 'America/Chihuahua': u'Chihuahua', + 'America/Iqaluit': u'Iqaluit', + 'America/Argentina/Rio_Gallegos': u'Rio Gallegos', + 'Pacific/Gambier': u'Gambier', + 'Europe/Volgograd': u'Volgograd', + 'Africa/Bamako': u'Bamako', + 'Asia/Novokuznetsk': u'Novokuznetsk', + 'Europe/Uzhgorod': u'Uzhgorod', + 'Africa/Banjul': u'Banjul', + 'Asia/Aqtau': u'Aqtau', + 'Pacific/Palau': u'Palau', + 'Africa/Malabo': u'Malabo', + 'Europe/Minsk': u'Minsk', + 'PST8PDT': u'PST8PDT', + 'America/Argentina/ComodRivadavia': u'ComodRivadavia', + 'Atlantic/Madeira': u'Madeira', + 'Pacific/Noumea': u'Noumea', + 'GMT0': u'GMT0', + 'Africa/Kinshasa': u'Kinshasa', + 'Europe/Malta': u'Malta', + 'US/Samoa': u'Samoa', + 'America/Argentina/Ushuaia': u'Ushuaia', + 'Asia/Chungking': u'Chungking', + 'Asia/Bangkok': u'Bangkok', + 'Pacific/Niue': u'Niue', + 'America/Catamarca': u'Catamarca', + 'America/Recife': u'Recife', + 'MST': u'MST', + 'Asia/Yerevan': u'Yerevan', + 'America/La_Paz': u'La Paz', + 'Poland': u'Poland', + 'Asia/Urumqi': u'Urumqi', + 'Africa/Lusaka': u'Lusaka', + 'US/Indiana-Starke': u'Indiana-Starke', + 'Pacific/Guadalcanal': u'Guadalcanal', + 'America/Yellowknife': u'Yellowknife', + 'Australia/LHI': u'LHI', + 'Asia/Vientiane': u'Vientiane', + 'Asia/Kuwait': u'Kuwait', + 'Africa/Conakry': u'Conakry', + 'Asia/Dubai': u'Dubai', + 'America/Argentina/Tucuman': u'Tucuman', + 'Asia/Chita': u'Chita', + 'Europe/Oslo': u'Oslo', + 'Australia/Currie': u'Currie', + 'America/St_Kitts': u'St. Kitts', + 'America/Panama': u'Panama', + 'America/Hermosillo': u'Hermosillo', + 'Asia/Hebron': u'Hebron', + 'America/Guayaquil': u'Guayaquil', + 'Asia/Kuala_Lumpur': u'Kuala Lumpur', + 'Europe/London': u'London', + 'America/Menominee': u'Menominee', + 'Asia/Kamchatka': u'Kamchatka', + 'Europe/Ulyanovsk': u'Ulyanovsk', + 'Asia/Vladivostok': u'Vladivostok', + 'America/Matamoros': u'Matamoros', + 'Brazil/DeNoronha': u'DeNoronha', + 'Asia/Qatar': u'Qatar', + 'Israel': u'Israel', + 'NZ-CHAT': u'NZ-CHAT', + 'Asia/Dushanbe': u'Dushanbe', + 'Asia/Yakutsk': u'Yakutsk', + 'Asia/Omsk': u'Omsk', + 'Africa/Bangui': u'Bangui', + 'UTC': u'UTC', + 'America/Paramaribo': u'Paramaribo', + 'Etc/GMT-11': u'GMT-11', + 'Etc/GMT-10': u'GMT-10', + 'Etc/GMT-13': u'GMT-13', + 'Etc/GMT-12': u'GMT-12', + 'Etc/GMT-14': u'GMT-14', + 'Pacific/Marquesas': u'Marquesas', + 'US/Central': u'Central', + 'Europe/Bratislava': u'Bratislava', + 'Asia/Anadyr': u'Anadyr', + 'America/New_York': u'New York', + 'Pacific/Norfolk': u'Norfolk', + 'CST6CDT': u'CST6CDT', + 'Pacific/Rarotonga': u'Rarotonga', + 'America/Dominica': u'Dominica', + 'Africa/Porto-Novo': u'Porto-Novo', + 'Asia/Samarkand': u'Samarkand', + 'America/Kentucky/Louisville': u'Louisville', + 'America/Toronto': u'Toronto', + 'America/Bahia': u'Bahia', + 'Indian/Maldives': u'Maldives', + 'Africa/Accra': u'Accra', + 'Antarctica/South_Pole': u'South Pole', + 'Asia/Muscat': u'Muscat', + 'America/Virgin': u'Virgin', + 'America/Edmonton': u'Edmonton', + 'Pacific/Wake': u'Wake', + 'America/Indiana/Tell_City': u'Tell City, Indiana', + 'Australia/Darwin': u'Darwin', + 'America/Whitehorse': u'Whitehorse', + 'America/Swift_Current': u'Swift Current', + 'Europe/Copenhagen': u'Copenhagen', + 'America/Fort_Wayne': u'Fort Wayne', + 'America/Montserrat': u'Montserrat', + 'America/Mendoza': u'Mendoza', + 'US/Mountain': u'Mountain', + 'Europe/Simferopol': u'Simferopol', + 'Africa/Blantyre': u'Blantyre', + 'America/Detroit': u'Detroit', + 'America/Shiprock': u'Shiprock', + 'America/Grenada': u'Grenada', + 'Atlantic/Faroe': u'Faroe', + 'America/Indiana/Petersburg': u'Petersburg, Indiana', + 'Asia/Kathmandu': u'Kathmandu', + 'Asia/Pontianak': u'Pontianak', + 'Jamaica': u'Jamaica', + 'Europe/Athens': u'Athens', + 'America/Port-au-Prince': u'Port-au-Prince', + 'America/Cayman': u'Cayman', + 'Etc/Universal': u'Universal', + 'Africa/Dar_es_Salaam': u'Dar es Salaam', + 'America/Curacao': u'Curacao', + 'Indian/Kerguelen': u'Kerguelen', + 'Africa/Khartoum': u'Khartoum', + 'Asia/Manila': u'Manila', + 'Africa/Lome': u'Lome', + 'America/Jujuy': u'Jujuy', + 'Africa/Douala': u'Douala', + 'EET': u'EET', + 'America/Argentina/San_Juan': u'San Juan', + 'America/North_Dakota/New_Salem': u'New Salem, North Dakota', + 'America/Kralendijk': u'Kralendijk', + 'Pacific/Port_Moresby': u'Port Moresby', + 'Europe/Jersey': u'Jersey', + 'Asia/Macao': u'Macao', + 'Europe/Andorra': u'Andorra', + 'ROK': u'ROK', + 'Cuba': u'Cuba', + 'Europe/Luxembourg': u'Luxembourg', + 'Pacific/Honolulu': u'Honolulu', + 'ROC': u'ROC', + 'America/St_Thomas': u'St. Thomas', + 'Pacific/Majuro': u'Majuro', + 'Asia/Hong_Kong': u'Hong Kong', + 'Asia/Macau': u'Macau', + 'MST7MDT': u'MST7MDT', + 'Europe/Belgrade': u'Belgrade', + 'Asia/Choibalsan': u'Choibalsan', + 'US/Eastern': u'Eastern', + 'Europe/Mariehamn': u'Mariehamn', + 'Antarctica/McMurdo': u'McMurdo', + 'America/Thunder_Bay': u'Thunder Bay', + 'America/Los_Angeles': u'Los Angeles', + 'Asia/Kabul': u'Kabul', + 'Indian/Antananarivo': u'Antananarivo', + 'Europe/Sarajevo': u'Sarajevo', + 'Atlantic/Reykjavik': u'Reykjavik', + 'Asia/Nicosia': u'Nicosia', + 'Etc/GMT+11': u'GMT+11', + 'Etc/GMT+10': u'GMT+10', + 'Pacific/Ponape': u'Pohnpei', + 'Japan': u'Japan', + 'Asia/Ulan_Bator': u'Ulan Bator', + 'Asia/Barnaul': u'Barnaul', + 'Kwajalein': u'Kwajalein', + 'Pacific/Tongatapu': u'Tongatapu', + 'America/Marigot': u'Marigot', + 'Pacific/Pitcairn': u'Pitcairn', + 'Pacific/Easter': u'Easter Island', + 'US/East-Indiana': u'East-Indiana', + 'Atlantic/South_Georgia': u'South Georgia', + 'Africa/El_Aaiun': u'El Aaiun', + 'US/Pacific-New': u'Pacific-New', + 'Chile/Continental': u'Continental', + 'Europe/Kirov': u'Kirov', + 'America/Campo_Grande': u'Campo Grande', + 'America/Dawson_Creek': u'Dawson Creek', + 'America/Rosario': u'Rosario', + 'Antarctica/Vostok': u'Vostok', + 'US/Aleutian': u'Aleutian', + 'Europe/Bucharest': u'Bucharest', + 'America/Porto_Velho': u'Porto Velho', + 'Europe/Monaco': u'Monaco', + 'NZ': u'NZ', + 'Africa/Ceuta': u'Ceuta', + 'Europe/Rome': u'Rome', + 'America/Winnipeg': u'Winnipeg', + 'America/Knox_IN': u'Knox IN', + 'Asia/Aqtobe': u'Aqtobe', + 'Africa/Dakar': u'Dakar', + 'America/Fortaleza': u'Fortaleza', + 'Pacific/Samoa': u'Samoa', + 'Pacific/Tarawa': u'Tarawa', + 'America/Dawson': u'Dawson', + 'Africa/Addis_Ababa': u'Addis Ababa', + 'Pacific/Efate': u'Efate', + 'Pacific/Johnston': u'Johnston', + 'GMT': u'GMT', + 'Australia/Brisbane': u'Brisbane', + 'Canada/Yukon': u'Yukon', + 'Asia/Qyzylorda': u'Qyzylorda', + 'Europe/San_Marino': u'San Marino', + 'Asia/Jerusalem': u'Jerusalem', + 'America/Barbados': u'Barbados', + 'Pacific/Auckland': u'Auckland', + 'America/Metlakatla': u'Metlakatla', + 'Etc/Zulu': u'Zulu', + 'America/Denver': u'Denver', + 'Indian/Chagos': u'Chagos', + 'America/Glace_Bay': u'Glace Bay', + 'Pacific/Bougainville': u'Bougainville Island', + 'Africa/Gaborone': u'Gaborone', + 'Africa/Tunis': u'Tunis', + 'Australia/Tasmania': u'Tasmania', + 'Asia/Ust-Nera': u'Ust-Nera', + 'Europe/Vaduz': u'Vaduz', + 'Africa/Lubumbashi': u'Lubumbashi', + 'America/Resolute': u'Resolute', + 'Etc/GMT': u'GMT', + 'Asia/Gaza': u'Gaza', + 'Europe/Dublin': u'Dublin', + 'GMT+0': u'GMT+0', + 'Antarctica/DumontDUrville': u'Dumont d’Urville', + 'America/Argentina/Catamarca': u'Catamarca', + 'America/Indiana/Knox': u'Knox, Indiana', + 'Etc/GMT-6': u'GMT-6', + 'Africa/Timbuktu': u'Timbuktu', + 'Asia/Novosibirsk': u'Novosibirsk', + 'EST': u'EST', + 'Mexico/BajaNorte': u'BajaNorte', + 'Africa/Kigali': u'Kigali', + 'Brazil/West': u'West', + 'America/Grand_Turk': u'Grand Turk', + 'Mexico/BajaSur': u'BajaSur', + 'America/Argentina/Salta': u'Salta', + 'Canada/Central': u'Central', + 'Africa/Lagos': u'Lagos', + 'GMT-0': u'GMT-0', + 'Europe/Sofia': u'Sofia', + 'Singapore': u'Singapore', + 'Africa/Tripoli': u'Tripoli', + 'America/Anchorage': u'Anchorage', + 'America/Monterrey': u'Monterrey', + 'Pacific/Nauru': u'Nauru', +} + +territories = { + 'BD': u'Bangladesh', + 'BE': u'Belgium', + 'BF': u'Burkina Faso', + 'BG': u'Bulgaria', + 'VE': u'Venezuela', + 'BA': u'Bosnia & Herzegovina', + 'BB': u'Barbados', + 'WF': u'Wallis & Futuna', + 'BL': u'St. Barthélemy', + 'BM': u'Bermuda', + 'BN': u'Brunei', + 'BO': u'Bolivia', + 'BH': u'Bahrain', + 'BI': u'Burundi', + 'BJ': u'Benin', + 'BT': u'Bhutan', + '011': u'Western Africa', + 'BV': u'Bouvet Island', + 'BW': u'Botswana', + '014': u'Eastern Africa', + '015': u'Northern Africa', + 'BR': u'Brazil', + '017': u'Middle Africa', + '018': u'Southern Africa', + '019': u'Americas', + 'BY': u'Belarus', + 'BZ': u'Belize', + 'LV': u'Latvia', + 'RW': u'Rwanda', + 'RS': u'Serbia', + 'TL': u'Timor-Leste', + 'RE': u'Réunion', + 'TM': u'Turkmenistan', + 'OM': u'Oman', + 'TJ': u'Tajikistan', + 'RO': u'Romania', + 'TK': u'Tokelau', + 'GW': u'Guinea-Bissau', + 'GU': u'Guam', + 'GT': u'Guatemala', + 'GS': u'South Georgia & South Sandwich Islands', + 'GR': u'Greece', + 'GQ': u'Equatorial Guinea', + 'GP': u'Guadeloupe', + 'JP': u'Japan', + 'KI': u'Kiribati', + 'GY': u'Guyana', + 'GG': u'Guernsey', + 'GF': u'French Guiana', + 'GE': u'Georgia', + 'GD': u'Grenada', + '021': u'Northern America', + 'GB': u'United Kingdom', + 'GA': u'Gabon', + 'SV': u'El Salvador', + 'GN': u'Guinea', + 'GM': u'Gambia', + 'GL': u'Greenland', + '029': u'Caribbean', + 'GI': u'Gibraltar', + 'GH': u'Ghana', + 'JE': u'Jersey', + 'TN': u'Tunisia', + 'JM': u'Jamaica', + '013': u'Central America', + 'WS': u'Samoa', + 'TA': u'Tristan da Cunha', + '419': u'Latin America', + 'BQ': u'Caribbean Netherlands', + 'HR': u'Croatia', + 'BS': u'Bahamas', + 'HT': u'Haiti', + 'HU': u'Hungary', + 'HK': u'Hong Kong SAR China', + '039': u'Southern Europe', + 'HN': u'Honduras', + 'FI': u'Finland', + '142': u'Asia', + '030': u'Eastern Asia', + '034': u'Southern Asia', + '035': u'Southeast Asia', + 'PR': u'Puerto Rico', + 'PS': u'Palestinian Territories', + 'FK': u'Falkland Islands', + 'IO': u'British Indian Ocean Territory', + 'PW': u'Palau', + 'PT': u'Portugal', + 'SJ': u'Svalbard & Jan Mayen', + 'MD': u'Moldova', + 'IQ': u'Iraq', + 'PA': u'Panama', + 'PF': u'French Polynesia', + 'PG': u'Papua New Guinea', + 'PE': u'Peru', + 'PK': u'Pakistan', + 'PH': u'Philippines', + 'FO': u'Faroe Islands', + 'PN': u'Pitcairn Islands', + 'PL': u'Poland', + 'PM': u'St. Pierre & Miquelon', + 'ZM': u'Zambia', + 'EH': u'Western Sahara', + 'RU': u'Russia', + 'EE': u'Estonia', + 'EG': u'Egypt', + 'EA': u'Ceuta & Melilla', + 'ZA': u'South Africa', + 'EC': u'Ecuador', + 'IT': u'Italy', + 'VN': u'Vietnam', + 'ZZ': u'Unknown Region', + 'SB': u'Solomon Islands', + 'EU': u'European Union', + 'ET': u'Ethiopia', + 'SO': u'Somalia', + 'ZW': u'Zimbabwe', + 'SA': u'Saudi Arabia', + 'ES': u'Spain', + 'ER': u'Eritrea', + 'ME': u'Montenegro', + 'AQ': u'Antarctica', + 'MG': u'Madagascar', + 'MF': u'St. Martin', + 'MA': u'Morocco', + 'MC': u'Monaco', + 'UZ': u'Uzbekistan', + 'MM': u'Myanmar (Burma)', + 'ML': u'Mali', + 'MO': u'Macau SAR China', + 'MN': u'Mongolia', + 'HM': u'Heard & McDonald Islands', + 'AS': u'American Samoa', + 'MK': u'Macedonia', + 'MU': u'Mauritius', + 'MT': u'Malta', + 'MW': u'Malawi', + 'MV': u'Maldives', + 'MQ': u'Martinique', + 'MP': u'Northern Mariana Islands', + 'MS': u'Montserrat', + 'MR': u'Mauritania', + 'IM': u'Isle of Man', + 'UG': u'Uganda', + 'TZ': u'Tanzania', + 'MY': u'Malaysia', + 'MX': u'Mexico', + 'IL': u'Israel', + 'IC': u'Canary Islands', + 'FR': u'France', + 'AW': u'Aruba', + 'DO': u'Dominican Republic', + 'SH': u'St. Helena', + 'VC': u'St. Vincent & Grenadines', + '054': u'Melanesia', + 'DJ': u'Djibouti', + 'FJ': u'Fiji', + '057': u'Micronesian Region', + 'FM': u'Micronesia', + '053': u'Australasia', + 'NI': u'Nicaragua', + 'NL': u'Netherlands', + 'NO': u'Norway', + 'NA': u'Namibia', + 'VU': u'Vanuatu', + 'NC': u'New Caledonia', + 'NE': u'Niger', + 'NF': u'Norfolk Island', + 'NG': u'Nigeria', + 'NZ': u'New Zealand', + 'NP': u'Nepal', + 'NR': u'Nauru', + 'NU': u'Niue', + '061': u'Polynesia', + 'XK': u'Kosovo', + 'CI': u'Côte d’Ivoire', + 'CH': u'Switzerland', + 'CO': u'Colombia', + 'CN': u'China', + 'CM': u'Cameroon', + 'CL': u'Chile', + 'CC': u'Cocos (Keeling) Islands', + 'CA': u'Canada', + 'CG': u'Congo - Brazzaville', + 'CF': u'Central African Republic', + 'CD': u'Congo - Kinshasa', + 'CZ': u'Czech Republic', + 'CY': u'Cyprus', + 'CX': u'Christmas Island', + 'CR': u'Costa Rica', + 'PY': u'Paraguay', + 'CP': u'Clipperton Island', + 'CW': u'Curaçao', + 'CV': u'Cape Verde', + 'CU': u'Cuba', + 'SZ': u'Swaziland', + 'SY': u'Syria', + 'SX': u'Sint Maarten', + 'KG': u'Kyrgyzstan', + 'KE': u'Kenya', + 'SS': u'South Sudan', + 'SR': u'Suriname', + '143': u'Central Asia', + 'KH': u'Cambodia', + 'KN': u'St. Kitts & Nevis', + 'KM': u'Comoros', + 'ST': u'São Tomé & Príncipe', + 'SK': u'Slovakia', + 'KR': u'South Korea', + 'SI': u'Slovenia', + 'KP': u'North Korea', + 'KW': u'Kuwait', + 'SN': u'Senegal', + 'SM': u'San Marino', + 'SL': u'Sierra Leone', + 'SC': u'Seychelles', + 'KZ': u'Kazakhstan', + 'KY': u'Cayman Islands', + 'SG': u'Singapore', + 'SE': u'Sweden', + 'SD': u'Sudan', + '151': u'Eastern Europe', + '150': u'Europe', + 'DM': u'Dominica', + '155': u'Western Europe', + '154': u'Northern Europe', + 'VG': u'British Virgin Islands', + 'DG': u'Diego Garcia', + 'DE': u'Germany', + 'YE': u'Yemen', + 'MH': u'Marshall Islands', + 'DZ': u'Algeria', + 'US': u'United States', + 'UY': u'Uruguay', + 'YT': u'Mayotte', + 'UM': u'U.S. Outlying Islands', + 'LB': u'Lebanon', + 'LC': u'St. Lucia', + 'LA': u'Laos', + 'TV': u'Tuvalu', + 'TW': u'Taiwan', + 'TT': u'Trinidad & Tobago', + 'TR': u'Turkey', + 'LK': u'Sri Lanka', + 'LI': u'Liechtenstein', + 'CK': u'Cook Islands', + 'TO': u'Tonga', + 'LT': u'Lithuania', + 'LU': u'Luxembourg', + 'LR': u'Liberia', + 'LS': u'Lesotho', + 'TH': u'Thailand', + 'TF': u'French Southern Territories', + 'TG': u'Togo', + 'TD': u'Chad', + 'TC': u'Turks & Caicos Islands', + 'LY': u'Libya', + 'VA': u'Vatican City', + 'AC': u'Ascension Island', + '145': u'Western Asia', + 'AE': u'United Arab Emirates', + 'AD': u'Andorra', + 'AG': u'Antigua & Barbuda', + 'AF': u'Afghanistan', + 'AI': u'Anguilla', + 'VI': u'U.S. Virgin Islands', + 'IS': u'Iceland', + 'IR': u'Iran', + 'AM': u'Armenia', + 'AL': u'Albania', + 'AO': u'Angola', + '003': u'North America', + '002': u'Africa', + '001': u'World', + 'AR': u'Argentina', + 'AU': u'Australia', + 'AT': u'Austria', + '005': u'South America', + 'IN': u'India', + 'AX': u'Åland Islands', + '009': u'Oceania', + 'AZ': u'Azerbaijan', + 'IE': u'Ireland', + 'ID': u'Indonesia', + 'JO': u'Jordan', + 'UA': u'Ukraine', + 'QA': u'Qatar', + 'DK': u'Denmark', + 'MZ': u'Mozambique', + 'QO': u'Outlying Oceania', +} \ No newline at end of file diff --git a/wagtail/utils/l18n/locale/cs/LC_MESSAGES/l18n.mo b/wagtail/utils/l18n/locale/cs/LC_MESSAGES/l18n.mo new file mode 100644 index 000000000..36be8eb90 Binary files /dev/null and b/wagtail/utils/l18n/locale/cs/LC_MESSAGES/l18n.mo differ diff --git a/wagtail/utils/l18n/locale/de/LC_MESSAGES/l18n.mo b/wagtail/utils/l18n/locale/de/LC_MESSAGES/l18n.mo new file mode 100644 index 000000000..8783914b0 Binary files /dev/null and b/wagtail/utils/l18n/locale/de/LC_MESSAGES/l18n.mo differ diff --git a/wagtail/utils/l18n/locale/en/LC_MESSAGES/l18n.mo b/wagtail/utils/l18n/locale/en/LC_MESSAGES/l18n.mo new file mode 100644 index 000000000..c1d876570 Binary files /dev/null and b/wagtail/utils/l18n/locale/en/LC_MESSAGES/l18n.mo differ diff --git a/wagtail/utils/l18n/locale/fr/LC_MESSAGES/l18n.mo b/wagtail/utils/l18n/locale/fr/LC_MESSAGES/l18n.mo new file mode 100644 index 000000000..9f908272c Binary files /dev/null and b/wagtail/utils/l18n/locale/fr/LC_MESSAGES/l18n.mo differ diff --git a/wagtail/utils/l18n/locale/zh/LC_MESSAGES/l18n.mo b/wagtail/utils/l18n/locale/zh/LC_MESSAGES/l18n.mo new file mode 100644 index 000000000..f0dc12fc3 Binary files /dev/null and b/wagtail/utils/l18n/locale/zh/LC_MESSAGES/l18n.mo differ diff --git a/wagtail/utils/l18n/maps.py b/wagtail/utils/l18n/maps.py new file mode 100644 index 000000000..ec9dc0344 --- /dev/null +++ b/wagtail/utils/l18n/maps.py @@ -0,0 +1,26 @@ +import six + +from .translation import L18NMap, L18NListMap + +try: + from . import __maps + tz_cities = L18NMap(__maps.tz_cities) + territories = L18NMap(__maps.territories) + + # tz_fullnames requires a main dictionary and an auxiliary translations + # dictionary (for components) + + _main_dict = dict(__maps.tz_cities) + _aux_dict = {} + for k, v in six.iteritems(__maps.tz_locations): + if k in _main_dict: + _main_dict[k] = v + else: + _aux_dict[k] = v + + tz_fullnames = L18NListMap('/', _aux_dict, _main_dict) + +except ImportError: + tz_cities = {} + tz_fullnames = {} + territories = {} diff --git a/wagtail/utils/l18n/translation.py b/wagtail/utils/l18n/translation.py new file mode 100644 index 000000000..6f565b01a --- /dev/null +++ b/wagtail/utils/l18n/translation.py @@ -0,0 +1,275 @@ +import os +import gettext +import bisect +from locale import getdefaultlocale +from collections import MutableMapping +from copy import copy, deepcopy + +import six + + +class Trans(object): + + def __init__(self): + self.registry = {} + self.current = None + self.set(getdefaultlocale()[0]) + + def __getitem__(self, language): + if language: + try: + return self.registry[language] + except KeyError: + self.registry[language] = gettext.translation( + 'l18n', + os.path.join(os.path.dirname(__file__), 'locale'), + languages=[language], + fallback=True + ) + return self.registry[language] + else: + return None + + def set(self, language): + self.current = self[language] + + def gettext(self, s): + try: + return self.current.gettext(s) + except AttributeError: + return s + + if six.PY2: + def ugettext(self, s): + try: + return self.current.ugettext(s) + except AttributeError: + return s + + +_trans = Trans() + + +def set_language(language=None): + _trans.set(language) + + +if six.PY2: + def translate(s, utf8=True, trans=_trans): + if trans: + if utf8: + return trans.ugettext(s) + return trans.gettext(s) + else: + return s +else: + def translate(s, utf8=True, trans=_trans): + if trans: + t = trans.gettext(s) + if utf8: + return t + return t.encode() + else: + return s + + +class L18NLazyObject(object): + + def _value(self, utf8=True): + raise NotImplementedError + + def __str__(self): + return self._value(utf8=six.PY3) + + def __bytes__(self): + return self._value(utf8=False) + + def __unicode__(self): + return self._value(utf8=True) + + +class L18NLazyString(L18NLazyObject): + + def __init__(self, s): + self._str = s + + def __copy__(self): + return self.__class__(self._str) + + def __deepcopy__(self, memo): + result = self.__copy__() + memo[id(self)] = result + return result + + def _value(self, utf8=True): + return translate(self._str, utf8) + + def __repr__(self): + return 'L18NLazyString <%s>' % repr(self._str) + + def __getattr__(self, name): + # fallback to call the value's attribute in case it's not found in + # L18NLazyString + return getattr(self._value(), name) + + +class L18NLazyStringsList(L18NLazyObject): + + def __init__(self, sep='/', *s): + # we assume that the separator and the strings have the same encoding + # (text_type) + self._sep = sep + self._strings = s + + def __copy__(self): + return self.__class__(self._sep, *self._strings) + + def __deepcopy__(self, memo): + result = self.__copy__() + memo[id(self)] = result + return result + + def _value(self, utf8=True): + sep = self._sep + if utf8 and isinstance(sep, six.binary_type): + sep = sep.decode(encoding='utf-8') + elif not utf8 and isinstance(sep, six.text_type): + sep = sep.encode(encoding='utf-8') + return sep.join([translate(s, utf8) + for s in self._strings]) + + def __repr__(self): + return 'L18NLazyStringsList <%s>' % self._sep.join([ + repr(s) for s in self._strings + ]) + + def __getattr__(self, name): + # fallback to call the value's attribute in case it's not found in + # L18NLazyStringsList + return getattr(self._value(), name) + + +class L18NBaseMap(MutableMapping): + """ + Generic dictionary that returns lazy string or lazy string lists + """ + + def __init__(self, *args, **kwargs): + self.store = dict(*args, **kwargs) + self.sorted = {} + + def __copy__(self): + result = self.__class__() + result.store = self.store + result.sorted = self.sorted + return result + + def __deepcopy__(self, memo): + result = self.__class__() + memo[id(self)] = result + result.store = deepcopy(self.store, memo) + result.sorted = deepcopy(self.sorted, memo) + return result + + def __getitem__(self, key): + raise NotImplementedError + + def __setitem__(self, key, value): + self.store[key] = value + for locale, (keys, values) in six.iteritems(self.sorted): + tr = translate(value, trans=_trans[locale]) + i = bisect.bisect_left(values, tr) + keys.insert(i, key) + values.insert(i, tr) + + def __delitem__(self, key): + del self.store[key] + for keys, values in self.sorted.values(): + i = keys.index(key) + del keys[i] + del values[i] + + def __iter__(self): + loc = _trans.current._info['language'] if _trans.current else None + try: + return iter(self.sorted[loc][0]) + except KeyError: + keys = [] + values = [] + # we can't use iteritems here, as we need to call __getitem__ + # via self[key] + for key in iter(self.store): + value = six.text_type(self[key]) + i = bisect.bisect_left(values, value) + keys.insert(i, key) + values.insert(i, value) + self.sorted[loc] = (keys, values) + return iter(keys) + + def __len__(self): + return len(self.store) + + def subset(self, keys): + """ + Generates a subset of the current map (e.g. to retrieve only tzs in + common_timezones from the tz_cities or tz_fullnames maps) + """ + sub = self.__class__() + + self_keys = set(self.store.keys()) + subset_keys = self_keys.intersection(keys) + removed_keys = self_keys.difference(subset_keys) + + sub.store = {k: self.store[k] for k in subset_keys} + for loc, sorted_items in six.iteritems(self.sorted): + loc_keys = copy(self.sorted[loc][0]) + loc_values = copy(self.sorted[loc][1]) + for k in removed_keys: + i = loc_keys.index(k) + del loc_keys[i] + del loc_values[i] + sub.sorted[loc] = (loc_keys, loc_values) + return sub + + +class L18NMap(L18NBaseMap): + + def __getitem__(self, key): + return L18NLazyString(self.store[key]) + + +class L18NListMap(L18NBaseMap): + + def __init__(self, sep='/', aux=None, *args, **kwargs): + self._sep = sep + self._aux = aux + super(L18NListMap, self).__init__(*args, **kwargs) + + def __copy__(self): + result = super(L18NListMap, self).__copy__() + result._sep = self._sep + result._aux = self._aux + return result + + def __deepcopy__(self, memo): + result = super(L18NListMap, self).__deepcopy__(memo) + result._sep = self._sep + result._aux = None if self._aux is None else deepcopy(self._aux, memo) + return result + + def __getitem__(self, key): + strs = key.split(self._sep) + strs[-1] = key + lst = [] + for s in strs: + try: + lst.append(self.store[s]) + except KeyError: + lst.append(self._aux[s]) + return L18NLazyStringsList(self._sep, *lst) + + def subset(self, keys): + sub = super(L18NListMap, self).subset(keys) + sub._sep = self._sep + sub._aux = deepcopy(self._aux) + return sub diff --git a/wagtail/utils/l18n/utils.py b/wagtail/utils/l18n/utils.py new file mode 100644 index 000000000..8a2a50015 --- /dev/null +++ b/wagtail/utils/l18n/utils.py @@ -0,0 +1,32 @@ +import locale + +from pytz import country_timezones +import six + +from .maps import tz_cities + + +def get_country_timezones(country_code): + """ + Retrieves the timezones for a given country, sorted in alphabetical order + """ + + tz_list = [] + + if country_code in country_timezones: + tzs = country_timezones[country_code] + tz_list = [(t, tz_cities[t]) for t in tzs] + tz_list.sort(lambda x, y: locale.strcoll(x[1], y[1])) + + return tz_list + + +def get_country_code_from_tz(tz): + """ + Retrieves the country matching a given timezone + """ + + for c, t in six.iteritems(country_timezones): + if tz in t: + return c + return None diff --git a/wagtail/utils/l18n/version.py b/wagtail/utils/l18n/version.py new file mode 100644 index 000000000..e2ea0ed23 --- /dev/null +++ b/wagtail/utils/l18n/version.py @@ -0,0 +1,40 @@ +__version_info__ = (2016, 6, 4, 'final', 0) + + +def get_version(version=__version_info__): + + dev_st = {'alpha': 'a', 'beta': 'b', 'rc': 'c', 'final': ''} + + assert len(version) == 5 + assert version[3] in dev_st.keys() + + n = 2 + (version[2] != 0) + version_str = '.'.join([str(v) for v in version[:n]]) + + if version[3] == 'final': + return version_str + + if version[3:] == ('alpha', 0): + return '%s.dev%s' % (version_str, get_hg_chgset()) + else: + return ''.join((version_str, dev_st[version[3]], str(version[4]))) + + +def get_hg_chgset(): + import subprocess + + try: + # python 3 + DEVNULL = subprocess.DEVNULL + except AttributeError: + import os + DEVNULL = open(os.devnull, 'wb') + + try: + return subprocess.check_output(['hg', 'id', '-i'], + stderr=DEVNULL).strip() + except: + return '?' + + +__version__ = get_version()