Skip to content

Instantly share code, notes, and snippets.

@csytan
Created December 16, 2010 07:45
Show Gist options
  • Save csytan/743166 to your computer and use it in GitHub Desktop.
Save csytan/743166 to your computer and use it in GitHub Desktop.
db backed locale replacement for tornado on appengine
# -*- coding: utf-8 -*-
import urllib
import re
import decimal
import uuid
import datetime
from google.appengine.ext import db
try:
import json
except:
from django.utils import simplejson as json
def google_translate(text, source='en', target='fr'):
subs = {}
for arg in re.findall('(\{.+?\})', text):
sub = str(uuid.uuid4()).replace('-', '')
subs[sub] = arg
text = text.replace(arg, sub)
params = urllib.urlencode({
'v': '1.0',
'q': text,
'langpair': source + '|' + target,
'format': 'text'
})
opener = urllib.FancyURLopener()
opener.addheader('Referer', 'http://www.caterpi.com/')
request = opener.open('http://ajax.googleapis.com/ajax/services/language/translate', params)
data = json.loads(request.read())
response = data.get('responseData')
if response:
translation = response.get('translatedText')
for sub, arg in subs.items():
translation = translation.replace(sub, arg)
return translation
def convert_currency(amount, fr='USD', to='CAD', rates={}):
if fr == to:
return amount
code = fr + to
if code not in rates:
csv = urllib.urlopen(
'http://download.finance.yahoo.com/d/quotes.csv?s=' +
code + '=X&f=sl1d1t1ba&e=.csv').read()
rates[code] = decimal.Decimal(csv.split(',')[1])
return decimal.Decimal(str(amount)) * rates[code]
def word_count(text, lang, _non_word=re.compile(r'\W', re.UNICODE)):
# logosyllabic languages counted by character instead of by word
if lang in ['zh', 'zh-TW', 'zh-CN', 'ko', 'ja', 'vi']:
text = _non_word.sub('', text)
return len(text)
words = re.split(r'\s', text)
words = [_non_word.sub('', w) for w in words]
words = [w for w in words if w]
return len(words)
class Locale(object):
def __init__(self, code, currency=None):
self.code = code if code in LANGUAGES else 'en'
self.currency = currency if currency else 'USD'
# Initialize strings for date formatting
_ = self.translate
self._months = [
_("January"), _("February"), _("March"), _("April"),
_("May"), _("June"), _("July"), _("August"),
_("September"), _("October"), _("November"), _("December")]
self._weekdays = [
_("Monday"), _("Tuesday"), _("Wednesday"), _("Thursday"),
_("Friday"), _("Saturday"), _("Sunday")]
class unicode2(unicode):
"""Support for the >= Python 2.6 format method"""
def format(self, *args, **kwargs):
s = self
for key, val in kwargs.items():
s = s.replace('{' + key + '}', unicode(val))
return s
def translate(self, text, plural_text=None, count=None, key_name=None):
if count is not None and count != 1:
text = plural_text
if self.code == 'en':
return self.unicode2(text)
if not text:
return ''
t = SiteTranslation.get_translation(text, self.code, key_name=key_name)
if t: return self.unicode2(t)
return self.unicode2(text)
def format_money(self, amount, symbol=True):
amount = convert_currency(amount, to=self.currency)
prefix = CURRENCIES[self.currency] if symbol else ''
if self.currency == 'JPY':
return prefix + str(int(amount))
elif self.currency == 'RUB':
return '%.2f' % amount + ' RUB'
return prefix + '%.2f' % amount
def format_date(self, date, gmt_offset=0):
"""Adapted from tornado.locale
Formats the given date (which should be GMT).
"""
if type(date) in (int, long, float):
date = datetime.datetime.utcfromtimestamp(date)
now = datetime.datetime.utcnow()
# Round down to now. Due to click skew, things are somethings
# slightly in the future.
if date > now: date = now
local_date = date - datetime.timedelta(minutes=gmt_offset)
local_now = now - datetime.timedelta(minutes=gmt_offset)
local_yesterday = local_now - datetime.timedelta(hours=24)
difference = now - date
seconds = difference.seconds
days = difference.days
_ = self.translate
format = None
if days == 0:
if seconds < 50:
return _("Seconds ago")
if seconds < 50 * 60:
minutes = round(seconds / 60.0)
return _("1 minute ago", "{minutes} minutes ago",
minutes).format(minutes=int(minutes))
hours = round(seconds / (60.0 * 60))
return _("1 hour ago", "{hours} hours ago",
hours).format(hours=int(hours))
if days == 0:
format = _("{time}")
elif days == 1 and local_date.day == local_yesterday.day:
format = _("Yesterday")
elif days < 5:
format = _("{weekday}")
elif days < 334: # 11mo, since confusing for same month last year
format = _("{month_name} {day}")
if format is None:
format = _("{month_name} {day}, {year}")
return format.format(
month_name=self._months[local_date.month - 1],
weekday=self._weekdays[local_date.weekday()],
day=str(local_date.day),
year=str(local_date.year)
)
class SiteTranslation(db.Expando):
text = db.TextProperty()
@classmethod
def init_lang(cls, target):
assert target in LANGUAGES
keys = ['January', 'February', 'March', 'April',
'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December',
'Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday']
keys += LANGUAGES.values()
for t in cls.get_by_key_name(keys):
if not getattr(t, 'translation_' + target, None):
translation = google_translate(t.text, target=target)
t.set(translation, target)
@staticmethod
def str_to_key(s):
# key_name is limited to the first 500 bytes of the source string
return s.encode('utf-8')[:500].decode('utf-8')
@classmethod
def get_translation(cls, text, locale, key_name=None, cache={}):
if not key_name:
key_name = cls.str_to_key(text)
if not cache:
# preload translations
for t in cls.all().fetch(1000):
cache[t.key().name()] = t
if key_name in cache:
t = cache[key_name]
else:
t = cls.get_or_insert(key_name=key_name, text=text)
cache[key_name] = t
return getattr(t, 'translation_' + locale, None)
def set(self, translation, target):
assert target in LANGUAGES
setattr(self, 'translation_' + target, db.Text(translation))
self.put()
class SiteTranslationChange(db.Model):
created = db.DateTimeProperty(auto_now_add=True)
user = db.ReferenceProperty()
site_translation = db.ReferenceProperty(SiteTranslation)
lang = db.StringProperty()
translation = db.TextProperty()
SITE_LANGUAGES = ['en', 'zh', 'es', 'ja', 'ru', 'fr']
SITE_CURRENCIES = ['USD', 'CNY', 'JPY', 'EUR', 'CAD', 'GBP', 'RUB']
LANGUAGES = {
'af': 'Afrikaans',
'am': 'Amharic',
'ar': 'Arabic',
'az': 'Azerbaijani',
'be': 'Belarusian',
'bg': 'Bulgarian',
'bh': 'Bihari',
'bn': 'Bengali',
'bo': 'Tibetan',
'ca': 'Catalan',
'chr': 'Cherokee',
'cs': 'Czech',
'da': 'Danish',
'de': 'German',
'dv': 'Dhivehi',
'el': 'Greek',
'en': 'English',
'eo': 'Esperanto',
'es': 'Spanish',
'es-ES': 'Spanish (Spain)',
'es-LA': 'Spanish (Latin America)',
'et': 'Estonian',
'eu': 'Basque',
'fa': 'Persian',
'fi': 'Finnish',
'fr': 'French',
'gl': 'Galician',
'gn': 'Guarani',
'gu': 'Gujarati',
'hi': 'Hindi',
'hr': 'Croatian',
'hu': 'Hungarian',
'hy': 'Armenian',
'id': 'Indonesian',
'is': 'Icelandic',
'it': 'Italian',
'iu': 'Inuktitut',
'ga': 'Irish',
'iw': 'Hebrew',
'ja': 'Japanese',
'ka': 'Georgian',
'kk': 'Kazakh',
'km': 'Khmer',
'kn': 'Kannada',
'ko': 'Korean',
'ku': 'Kurdish',
'ky': 'Kyrgyz',
'lo': 'Laothian',
'lt': 'Lithuanian',
'lv': 'Latvian',
'mk': 'Macedonian',
'ml': 'Malayalam',
'mn': 'Mongolian',
'mr': 'Marathi',
'ms': 'Malay',
'mt': 'Maltese',
'my': 'Burmese',
'ne': 'Nepali',
'nl': 'Dutch',
'no': 'Norwegian',
'or': 'Oriya',
'pa': 'Punjabi',
'pl': 'Polish',
'ps': 'Pashto',
'pt': 'Portuguese',
'ro': 'Romanian',
'ru': 'Russian',
'sa': 'Sanskrit',
'sd': 'Sindhi',
'si': 'Sinhalese',
'sk': 'Slovak',
'sl': 'Slovenian',
'sq': 'Albanian',
'sr': 'Serbian',
'sv': 'Swedish',
'sw': 'Swahili',
'ta': 'Tamil',
'te': 'Telugu',
'tg': 'Tajik',
'th': 'Thai',
'tl': 'Filipino',
'tr': 'Turkish',
'ug': 'Uighur',
'uk': 'Ukrainian',
'ur': 'Urdu',
'uz': 'Uzbek',
'vi': 'Vietnamese',
'zh': 'Chinese',
'zh-CN': 'Chinese (simplified)',
'zh-TW': 'Chinese (traditional)'
}
LANGUAGE_CODES = LANGUAGES.keys()
NATIVE_LANGUAGES = {
'af': u'Afrikaans',
'ar': u'العربية',
'az': u'Azərbaycan dili',
'bg': u'Български',
'bn': u'বাংলা',
'bs': u'Bosanski',
'ca': u'Català',
'cs': u'Čeština',
'cy': u'Cymraeg',
'da': u'Dansk',
'de': u'Deutsch',
'el': u'Ελληνικά',
'en': u'English',
'eo': u'Esperanto',
'es': u'Español',
'et': u'Eesti',
'eu': u'Euskara',
'fa': u'فارسی',
'fi': u'Suomi',
'fo': u'Føroyskt',
'fr': u'Français',
'ga': u'Gaeilge',
'gl': u'Galego',
'he': u'עברית',
'hi': u'हिन्दी',
'hr': u'Hrvatski',
'hu': u'Magyar',
'id': u'Bahasa Indonesia',
'is': u'Íslenska',
'it': u'Italiano',
'ja': u'日本語',
'ka': u'ქართული',
'ko': u'한국어',
'la': u'lingua latina',
'lt': u'Lietuvių',
'lv': u'Latviešu',
'mk': u'Македонски',
'ml': u'മലയാളം',
'ms': u'Bahasa Melayu',
'nb': u'Norsk (bokmål)',
'ne': u'नेपाली',
'nl': u'Nederlands',
'nn': u'Norsk (nynorsk)',
'pa': u'ਪੰਜਾਬੀ',
'pl': u'Polski',
'pt': u'Português',
'ro': u'Română',
'ru': u'Русский',
'sk': u'Slovenčina',
'sl': u'Slovenščina',
'sq': u'Shqip',
'sr': u'Српски',
'sv': u'Svenska',
'sw': u'Kiswahili',
'ta': u'தமிழ்',
'te': u'తెలుగు',
'th': u'ภาษาไทย',
'tl': u'Filipino',
'tr': u'Türkçe',
'uk': u'Українська',
'vi': u'Tiếng Việt',
'zh': u'中文'
}
CURRENCIES = {
'ALL': u'Lek',
'USD': u'$',
'AFN': u'؋',
'ARS': u'$',
'AWG': u'ƒ',
'AUD': u'$',
'AZN': u'ман',
'BSD': u'$',
'BBD': u'$',
'BYR': u'p.',
'EUR': u'€',
'BZD': u'BZ$',
'BMD': u'$',
'BOB': u'$b',
'BAM': u'KM',
'BWP': u'P',
'BGN': u'лв',
'BRL': u'R$',
'GBP': u'£',
'BND': u'$',
'KHR': u'៛',
'CAD': u'$',
'KYD': u'$',
'CLP': u'$',
'CNY': u'元',
'COP': u'$',
'CRC': u'₡',
'HRK': u'kn',
'CUP': u'₱',
'EUR': u'€',
'CZK': u'Kč',
'DKK': u'kr',
'DOP': u'RD$',
'XCD': u'$',
'EGP': u'£',
'SVC': u'$',
'GBP': u'£',
'EEK': u'kr',
'EUR': u'€',
'FKP': u'£',
'FJD': u'$',
'EUR': u'€',
'GHC': u'¢',
'GIP': u'£',
'EUR': u'€',
'GTQ': u'Q',
'GGP': u'£',
'GYD': u'$',
'EUR': u'€',
'HNL': u'L',
'HKD': u'$',
'HUF': u'Ft',
'ISK': u'kr',
'INR': u'₨',
'IDR': u'Rp',
'IRR': u'﷼',
'EUR': u'€',
'IMP': u'£',
'ILS': u'₪',
'EUR': u'€',
'JMD': u'J$',
'JPY': u'¥',
'JEP': u'£',
'KZT': u'лв',
'KPW': u'₩',
'KRW': u'₩',
'KGS': u'лв',
'LAK': u'₭',
'LVL': u'Ls',
'LBP': u'£',
'LRD': u'$',
'CHF': u'CHF',
'LTL': u'Lt',
'EUR': u'€',
'MKD': u'ден',
'MYR': u'RM',
'EUR': u'€',
'MUR': u'₨',
'MXN': u'$',
'MNT': u'₮',
'MZN': u'MT',
'NAD': u'$',
'NPR': u'₨',
'ANG': u'ƒ',
'EUR': u'€',
'NZD': u'$',
'NIO': u'C$',
'NGN': u'₦',
'KPW': u'₩',
'NOK': u'kr',
'OMR': u'﷼',
'PKR': u'₨',
'PAB': u'B/.',
'PYG': u'Gs',
'PEN': u'S/.',
'PHP': u'Php',
'PLN': u'zł',
'QAR': u'﷼',
'RON': u'lei',
'RUB': u'руб',
'SHP': u'£',
'SAR': u'﷼',
'RSD': u'Дин.',
'SCR': u'₨',
'SGD': u'$',
'EUR': u'€',
'SBD': u'$',
'SOS': u'S',
'ZAR': u'R',
'KRW': u'₩',
'EUR': u'€',
'LKR': u'₨',
'SEK': u'kr',
'CHF': u'CHF',
'SRD': u'$',
'SYP': u'£',
'TWD': u'NT$',
'THB': u'฿',
'TTD': u'TT$',
'TRY': u'TL',
'TRL': u'₤',
'TVD': u'$',
'UAH': u'₴',
'GBP': u'£',
'USD': u'$',
'UYU': u'$U',
'UZS': u'лв',
'EUR': u'€',
'VEF': u'Bs',
'VND': u'₫',
'YER': u'﷼',
'ZWD': u'Z$'
}
Locale.SITE_LANGUAGES = SITE_LANGUAGES
Locale.SITE_CURRENCIES = SITE_CURRENCIES
Locale.LANGUAGES = LANGUAGES
Locale.NATIVE_LANGUAGES = NATIVE_LANGUAGES
Locale.CURRENCIES = CURRENCIES
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment