From f243ab7eaadb7bf191c81afbcce5f360479c38df Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Wed, 30 Jan 2013 20:45:16 +0100 Subject: [PATCH 01/23] add an explicit cache timeout value in the CacheRosettaStorage, to mitigate #59 --- rosetta/storage.py | 2 +- rosetta/templatetags/rosetta.py | 52 ++++++++++++++++++--------------- rosetta/views.py | 1 - testproject/urls.py | 3 ++ 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/rosetta/storage.py b/rosetta/storage.py index 75e6dd0..7c2d77d 100644 --- a/rosetta/storage.py +++ b/rosetta/storage.py @@ -86,7 +86,7 @@ class CacheRosettaStorage(BaseRosettaStorage): def set(self, key, val): #print ('set', self._key_prefix + key) - cache.set(self._key_prefix + key, val) + cache.set(self._key_prefix + key, val, 86400) def has(self, key): #print ('has', self._key_prefix + key) diff --git a/rosetta/templatetags/rosetta.py b/rosetta/templatetags/rosetta.py index bb28352..4eeec2a 100644 --- a/rosetta/templatetags/rosetta.py +++ b/rosetta/templatetags/rosetta.py @@ -3,44 +3,48 @@ from django.utils.safestring import mark_safe from django.utils.html import escape import re from django.template import Node -from django.utils.encoding import smart_str, smart_unicode -from django.template.defaultfilters import stringfilter +from django.utils.encoding import smart_unicode + register = template.Library() rx = re.compile(r'(%(\([^\s\)]*\))?[sd])') + def format_message(message): - return mark_safe(rx.sub('\\1', escape(message).replace(r'\n','
\n'))) -format_message=register.filter(format_message) + return mark_safe(rx.sub('\\1', escape(message).replace(r'\n', '
\n'))) +format_message = register.filter(format_message) def lines_count(message): - return 1 + sum([len(line)/50 for line in message.split('\n')]) -lines_count=register.filter(lines_count) + return 1 + sum([len(line) / 50 for line in message.split('\n')]) +lines_count = register.filter(lines_count) -def mult(a,b): - return int(a)*int(b) -mult=register.filter(mult) -def minus(a,b): +def mult(a, b): + return int(a) * int(b) +mult = register.filter(mult) + + +def minus(a, b): try: return int(a) - int(b) except: return 0 -minus=register.filter(minus) +minus = register.filter(minus) -def gt(a,b): + +def gt(a, b): try: return int(a) > int(b) except: return False -gt=register.filter(gt) +gt = register.filter(gt) def do_incr(parser, token): args = token.split_contents() if len(args) < 2: - raise TemplateSyntaxError("'incr' tag requires at least one argument") + raise SyntaxError("'incr' tag requires at least one argument") name = args[1] if not hasattr(parser, '_namedIncrNodes'): parser._namedIncrNodes = {} @@ -51,26 +55,28 @@ do_incr = register.tag('increment', do_incr) class IncrNode(template.Node): - def __init__(self, init_val=0): - self.val = init_val + def __init__(self, init_val=0): + self.val = init_val + + def render(self, context): + self.val += 1 + return smart_unicode(self.val) + - def render(self, context): - self.val += 1 - return smart_unicode(self.val) - - def is_fuzzy(message): return message and hasattr(message, 'flags') and 'fuzzy' in message.flags is_fuzzy = register.filter(is_fuzzy) + class RosettaCsrfTokenPlaceholder(Node): def render(self, context): return mark_safe(u"") + def rosetta_csrf_token(parser, token): try: from django.template.defaulttags import csrf_token - return csrf_token(parser,token) + return csrf_token(parser, token) except ImportError: return RosettaCsrfTokenPlaceholder() -rosetta_csrf_token=register.tag(rosetta_csrf_token) +rosetta_csrf_token = register.tag(rosetta_csrf_token) diff --git a/rosetta/views.py b/rosetta/views.py index 0fc2ab5..cfbb818 100644 --- a/rosetta/views.py +++ b/rosetta/views.py @@ -15,7 +15,6 @@ from rosetta.signals import entry_changed, post_save from rosetta.storage import get_storage import re import rosetta -import datetime import unicodedata import hashlib import os diff --git a/testproject/urls.py b/testproject/urls.py index 0948f3b..5bdc133 100644 --- a/testproject/urls.py +++ b/testproject/urls.py @@ -1,4 +1,5 @@ from django.conf.urls.defaults import patterns, include, url +from django.contrib.staticfiles.urls import staticfiles_urlpatterns # Uncomment the next two lines to enable the admin: from django.contrib import admin @@ -16,3 +17,5 @@ urlpatterns = patterns('', url(r'^admin/', include(admin.site.urls)), url(r'^rosetta/', include('rosetta.urls')) ) + +urlpatterns += staticfiles_urlpatterns() From 5cbd6e6ca2a4cff8985751b65c950adeea487d1a Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Sun, 10 Feb 2013 11:08:27 +0100 Subject: [PATCH 02/23] upgraded bundled polib to v1.0.3 --- CHANGES | 3 + rosetta/polib.py | 265 +++++++++++++++++++++++++++++------------------ 2 files changed, 168 insertions(+), 100 deletions(-) diff --git a/CHANGES b/CHANGES index 7ab3b2b..08ecf49 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +Version 0.7.0 +------------- +* Upgraded bundled polib to version 1.0.3 - http://pypi.python.org/pypi/polib/1.0.3 * Support timezones on the last modified PO header. Thanks @jmoiron (Issue #43) * Actually move to the next block when submitting a lot of translations (Issue #13) * Add msgctxt to the entry hash to differentiate entries with context. Thanks @metalpriest (Issue #39) diff --git a/rosetta/polib.py b/rosetta/polib.py index b151b03..a40fcec 100644 --- a/rosetta/polib.py +++ b/rosetta/polib.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -* coding: utf-8 -*- # # License: MIT (see LICENSE file provided) # vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: @@ -12,10 +12,10 @@ modify entries, comments or metadata, etc. or create new po files from scratch. :func:`~polib.mofile` convenience functions. """ -__author__ = 'David Jean Louis ' -__version__ = '0.7.0' -__all__ = ['pofile', 'POFile', 'POEntry', 'mofile', 'MOFile', 'MOEntry', - 'detect_encoding', 'escape', 'unescape', 'detect_encoding',] +__author__ = 'David Jean Louis ' +__version__ = '1.0.3' +__all__ = ['pofile', 'POFile', 'POEntry', 'mofile', 'MOFile', 'MOEntry', + 'default_encoding', 'escape', 'unescape', 'detect_encoding', ] import array import codecs @@ -25,11 +25,13 @@ import struct import sys import textwrap + # the default encoding to use when encoding cannot be detected default_encoding = 'utf-8' # python 2/3 compatibility helpers {{{ + if sys.version_info[:2] < (3, 0): PY3 = False text_type = unicode @@ -49,10 +51,10 @@ else: def u(s): return s - # }}} # _pofile_or_mofile {{{ + def _pofile_or_mofile(f, type, **kwargs): """ Internal function used by :func:`polib.pofile` and :func:`polib.mofile` to @@ -68,15 +70,16 @@ def _pofile_or_mofile(f, type, **kwargs): parser = kls( f, encoding=enc, - check_for_duplicates=kwargs.get('check_for_duplicates', False) + check_for_duplicates=kwargs.get('check_for_duplicates', False), + klass=kwargs.get('klass') ) instance = parser.parse() instance.wrapwidth = kwargs.get('wrapwidth', 78) return instance - # }}} # function pofile() {{{ + def pofile(pofile, **kwargs): """ Convenience function that parses the po or pot file ``pofile`` and returns @@ -98,12 +101,17 @@ def pofile(pofile, **kwargs): ``check_for_duplicates`` whether to check for duplicate entries when adding entries to the file (optional, default: ``False``). + + ``klass`` + class which is used to instantiate the return value (optional, + default: ``None``, the return value with be a :class:`~polib.POFile` + instance). """ return _pofile_or_mofile(pofile, 'pofile', **kwargs) - # }}} # function mofile() {{{ + def mofile(mofile, **kwargs): """ Convenience function that parses the mo file ``mofile`` and returns a @@ -126,12 +134,17 @@ def mofile(mofile, **kwargs): ``check_for_duplicates`` whether to check for duplicate entries when adding entries to the file (optional, default: ``False``). + + ``klass`` + class which is used to instantiate the return value (optional, + default: ``None``, the return value with be a :class:`~polib.POFile` + instance). """ return _pofile_or_mofile(mofile, 'mofile', **kwargs) - # }}} # function detect_encoding() {{{ + def detect_encoding(file, binary_mode=False): """ Try to detect the encoding used by the ``file``. The ``file`` argument can @@ -159,7 +172,12 @@ def detect_encoding(file, binary_mode=False): return False return True - if not os.path.exists(file): + try: + is_file = os.path.exists(file) + except (ValueError, UnicodeEncodeError): + is_file = False + + if not is_file: match = rxt.search(file) if match: enc = match.group(1).strip() @@ -185,10 +203,10 @@ def detect_encoding(file, binary_mode=False): return enc f.close() return default_encoding - # }}} # function escape() {{{ + def escape(st): """ Escapes the characters ``\\\\``, ``\\t``, ``\\n``, ``\\r`` and ``"`` in @@ -199,10 +217,10 @@ def escape(st): .replace('\r', r'\r')\ .replace('\n', r'\n')\ .replace('\"', r'\"') - # }}} # function unescape() {{{ + def unescape(st): """ Unescapes the characters ``\\\\``, ``\\t``, ``\\n``, ``\\r`` and ``"`` in @@ -218,12 +236,12 @@ def unescape(st): return '\r' if m == '\\': return '\\' - return m # handles escaped double quote + return m # handles escaped double quote return re.sub(r'\\(\\|n|t|r|")', unescape_repl, st) - # }}} # class _BaseFile {{{ + class _BaseFile(list): """ Common base class for the :class:`~polib.POFile` and :class:`~polib.MOFile` @@ -301,15 +319,17 @@ class _BaseFile(list): Overriden ``list`` method to implement the membership test (in and not in). The method considers that an entry is in the file if it finds an entry - that has the same msgid (the test is **case sensitive**). + that has the same msgid (the test is **case sensitive**) and the same + msgctxt (or none for both entries). Argument: ``entry`` an instance of :class:`~polib._BaseEntry`. """ - return self.find(entry.msgid, by='msgid') is not None - + return self.find(entry.msgid, by='msgid', msgctxt=entry.msgctxt) \ + is not None + def __eq__(self, other): return str(self) == str(other) @@ -420,7 +440,7 @@ class _BaseFile(list): entries = [e for e in self if not e.obsolete] for e in entries: if getattr(e, by) == st: - if msgctxt and e.msgctxt != msgctxt: + if msgctxt is not False and e.msgctxt != msgctxt: continue return e return None @@ -428,7 +448,7 @@ class _BaseFile(list): def ordered_metadata(self): """ Convenience method that returns an ordered version of the metadata - dictionnary. The return value is list of tuples (metadata name, + dictionary. The return value is list of tuples (metadata name, metadata_value). """ # copy the dict first @@ -464,6 +484,7 @@ class _BaseFile(list): """ offsets = [] entries = self.translated_entries() + # the keys are sorted in the .mo file def cmp(_self, other): # msgfmt compares entries with msgctxt if it exists @@ -500,11 +521,11 @@ class _BaseFile(list): msgid += self._encode(e.msgid) msgstr = self._encode(e.msgstr) offsets.append((len(ids), len(msgid), len(strs), len(msgstr))) - ids += msgid + b('\0') + ids += msgid + b('\0') strs += msgstr + b('\0') # The header is 7 32-bit unsigned integers. - keystart = 7*4+16*entries_len + keystart = 7 * 4 + 16 * entries_len # and the values start after the keys valuestart = keystart + len(ids) koffsets = [] @@ -512,8 +533,8 @@ class _BaseFile(list): # The string table first has the list of keys, then the list of values. # Each entry has first the size of the string, then the file offset. for o1, l1, o2, l2 in offsets: - koffsets += [l1, o1+keystart] - voffsets += [l2, o2+valuestart] + koffsets += [l1, o1 + keystart] + voffsets += [l2, o2 + valuestart] offsets = koffsets + voffsets # check endianness for magic number if struct.pack('@h', 1) == struct.pack(' 1: # python 3.2 or superior output += array.array("i", offsets).tobytes() @@ -547,10 +574,10 @@ class _BaseFile(list): if isinstance(mixed, text_type): mixed = mixed.encode(self.encoding) return mixed - # }}} # class POFile {{{ + class POFile(_BaseFile): """ Po (or Pot) file reader/writer. @@ -606,7 +633,7 @@ class POFile(_BaseFile): """ Convenience method that returns the list of untranslated entries. """ - return [e for e in self if not e.translated() and not e.obsolete \ + return [e for e in self if not e.translated() and not e.obsolete and not 'fuzzy' in e.flags] def fuzzy_entries(self): @@ -637,32 +664,36 @@ class POFile(_BaseFile): ``refpot`` object POFile, the reference catalog. """ + # Store entries in dict/set for faster access + self_entries = dict((entry.msgid, entry) for entry in self) + refpot_msgids = set(entry.msgid for entry in refpot) + # Merge entries that are in the refpot for entry in refpot: - e = self.find(entry.msgid, include_obsolete_entries=True) + e = self_entries.get(entry.msgid) if e is None: e = POEntry() self.append(e) e.merge(entry) # ok, now we must "obsolete" entries that are not in the refpot anymore for entry in self: - if refpot.find(entry.msgid) is None: + if entry.msgid not in refpot_msgids: entry.obsolete = True - # }}} # class MOFile {{{ + class MOFile(_BaseFile): """ Mo file reader/writer. This class inherits the :class:`~polib._BaseFile` class and, by extension, the python ``list`` type. """ - BIG_ENDIAN = 0xde120495 + BIG_ENDIAN = 0xde120495 LITTLE_ENDIAN = 0x950412de def __init__(self, *args, **kwargs): """ - Constructor, accepts all keywords arguments accepted by + Constructor, accepts all keywords arguments accepted by :class:`~polib._BaseFile` class. """ _BaseFile.__init__(self, *args, **kwargs) @@ -720,10 +751,10 @@ class MOFile(_BaseFile): Convenience method to keep the same interface with POFile instances. """ return [] - # }}} # class _BaseEntry {{{ + class _BaseEntry(object): """ Base class for :class:`~polib.POEntry` and :class:`~polib.MOEntry` classes. @@ -775,12 +806,14 @@ class _BaseEntry(object): ret = [] # write the msgctxt if any if self.msgctxt is not None: - ret += self._str_field("msgctxt", delflag, "", self.msgctxt, wrapwidth) + ret += self._str_field("msgctxt", delflag, "", self.msgctxt, + wrapwidth) # write the msgid ret += self._str_field("msgid", delflag, "", self.msgid, wrapwidth) # write the msgid_plural if any if self.msgid_plural: - ret += self._str_field("msgid_plural", delflag, "", self.msgid_plural, wrapwidth) + ret += self._str_field("msgid_plural", delflag, "", + self.msgid_plural, wrapwidth) if self.msgstr_plural: # write the msgstr_plural if any msgstrs = self.msgstr_plural @@ -789,10 +822,12 @@ class _BaseEntry(object): for index in keys: msgstr = msgstrs[index] plural_index = '[%s]' % index - ret += self._str_field("msgstr", delflag, plural_index, msgstr, wrapwidth) + ret += self._str_field("msgstr", delflag, plural_index, msgstr, + wrapwidth) else: # otherwise write the msgstr - ret += self._str_field("msgstr", delflag, "", self.msgstr, wrapwidth) + ret += self._str_field("msgstr", delflag, "", self.msgstr, + wrapwidth) ret.append('') ret = u('\n').join(ret) return ret @@ -806,20 +841,21 @@ class _BaseEntry(object): Returns the string representation of the entry. """ return unicode(self).encode(self.encoding) - + def __eq__(self, other): return str(self) == str(other) - def _str_field(self, fieldname, delflag, plural_index, field, wrapwidth=78): + def _str_field(self, fieldname, delflag, plural_index, field, + wrapwidth=78): lines = field.splitlines(True) if len(lines) > 1: - lines = [''] + lines # start with initial empty line + lines = [''] + lines # start with initial empty line else: escaped_field = escape(field) specialchars_count = 0 for c in ['\\', '\n', '\r', '\t', '"']: specialchars_count += field.count(c) - # comparison must take into account fieldname length + one space + # comparison must take into account fieldname length + one space # + 2 quotes (eg. msgid "") flength = len(fieldname) + 3 if plural_index: @@ -829,7 +865,7 @@ class _BaseEntry(object): # Wrap the line but take field name into account lines = [''] + [unescape(item) for item in wrap( escaped_field, - wrapwidth - 2, # 2 for quotes "" + wrapwidth - 2, # 2 for quotes "" drop_whitespace=False, break_long_words=False )] @@ -845,10 +881,10 @@ class _BaseEntry(object): #import pdb; pdb.set_trace() ret.append('%s"%s"' % (delflag, escape(mstr))) return ret - # }}} # class POEntry {{{ + class POEntry(_BaseEntry): """ Represents a po file entry. @@ -923,9 +959,9 @@ class POEntry(_BaseEntry): filelist.append(fpath) filestr = ' '.join(filelist) if wrapwidth > 0 and len(filestr) + 3 > wrapwidth: - # textwrap split words that contain hyphen, this is not - # what we want for filenames, so the dirty hack is to - # temporally replace hyphens with a char that a file cannot + # textwrap split words that contain hyphen, this is not + # what we want for filenames, so the dirty hack is to + # temporally replace hyphens with a char that a file cannot # contain, like "*" ret += [l.replace('*', '-') for l in wrap( filestr.replace('-', '*'), @@ -942,7 +978,8 @@ class POEntry(_BaseEntry): ret.append('#, %s' % ', '.join(self.flags)) # previous context and previous msgid/msgid_plural - fields = ['previous_msgctxt', 'previous_msgid', 'previous_msgid_plural'] + fields = ['previous_msgctxt', 'previous_msgid', + 'previous_msgid_plural'] for f in fields: val = getattr(self, f) if val: @@ -988,8 +1025,10 @@ class POEntry(_BaseEntry): else: return -1 # Finally: Compare message ID - if self.msgid > other.msgid: return 1 - elif self.msgid < other.msgid: return -1 + if self.msgid > other.msgid: + return 1 + elif self.msgid < other.msgid: + return -1 return 0 def __gt__(self, other): @@ -1050,19 +1089,19 @@ class POEntry(_BaseEntry): self.msgstr_plural[pos] except KeyError: self.msgstr_plural[pos] = '' - # }}} # class MOEntry {{{ + class MOEntry(_BaseEntry): """ Represents a mo file entry. """ pass - # }}} # class _POFileParser {{{ + class _POFileParser(object): """ A finite state machine to parse efficiently and correctly po @@ -1096,7 +1135,10 @@ class _POFileParser(object): else: self.fhandle = pofile.splitlines() - self.instance = POFile( + klass = kwargs.get('klass') + if klass is None: + klass = POFile + self.instance = klass( pofile=pofile, encoding=enc, check_for_duplicates=kwargs.get('check_for_duplicates', False) @@ -1139,7 +1181,7 @@ class _POFileParser(object): self.add('PP', all, 'PP') self.add('CT', ['ST', 'HE', 'GC', 'OC', 'FL', 'TC', 'PC', 'PM', 'PP', 'MS', 'MX'], 'CT') - self.add('MI', ['ST', 'HE', 'GC', 'OC', 'FL', 'CT', 'TC', 'PC', + self.add('MI', ['ST', 'HE', 'GC', 'OC', 'FL', 'CT', 'TC', 'PC', 'PM', 'PP', 'MS', 'MX'], 'MI') self.add('MP', ['TC', 'GC', 'PC', 'PM', 'PP', 'MI'], 'MP') self.add('MS', ['MI', 'MP', 'TC'], 'MS') @@ -1174,6 +1216,9 @@ class _POFileParser(object): tokens = line.split(None, 2) nb_tokens = len(tokens) + if tokens[0] == '#~|': + continue + if tokens[0] == '#~' and nb_tokens > 1: line = line[3:].strip() tokens = tokens[1:] @@ -1186,41 +1231,56 @@ class _POFileParser(object): # msgid, msgid_plural, msgctxt & msgstr. if tokens[0] in keywords and nb_tokens > 1: line = line[len(tokens[0]):].lstrip() + if re.search(r'([^\\]|^)"', line[1:-1]): + raise IOError('Syntax error in po file %s (line %s): ' + 'unescaped double quote found' % + (self.instance.fpath, i)) self.current_token = line self.process(keywords[tokens[0]], i) continue self.current_token = line - if tokens[0] == '#:' and nb_tokens > 1: + if tokens[0] == '#:': + if nb_tokens <= 1: + continue # we are on a occurrences line self.process('OC', i) elif line[:1] == '"': # we are on a continuation line + if re.search(r'([^\\]|^)"', line[1:-1]): + raise IOError('Syntax error in po file %s (line %s): ' + 'unescaped double quote found' % + (self.instance.fpath, i)) self.process('MC', i) elif line[:7] == 'msgstr[': # we are on a msgstr plural self.process('MX', i) - elif tokens[0] == '#,' and nb_tokens > 1: + elif tokens[0] == '#,': + if nb_tokens <= 1: + continue # we are on a flags line self.process('FL', i) - elif tokens[0] == '#': - if line == '#': line += ' ' + elif tokens[0] == '#' or tokens[0].startswith('##'): + if line == '#': + line += ' ' # we are on a translator comment line self.process('TC', i) - elif tokens[0] == '#.' and nb_tokens > 1: + elif tokens[0] == '#.': + if nb_tokens <= 1: + continue # we are on a generated comment line self.process('GC', i) elif tokens[0] == '#|': - if nb_tokens < 2: - self.process('??', i) - continue + if nb_tokens <= 1: + raise IOError('Syntax error in po file %s (line %s)' % + (self.instance.fpath, i)) # Remove the marker and any whitespace right after that. line = line[2:].lstrip() @@ -1233,12 +1293,16 @@ class _POFileParser(object): if nb_tokens == 2: # Invalid continuation line. - self.process('??', i) + raise IOError('Syntax error in po file %s (line %s): ' + 'invalid continuation line' % + (self.instance.fpath, i)) # we are on a "previous translation" comment line, if tokens[1] not in prev_keywords: # Unknown keyword in previous translation comment. - self.process('??', i) + raise IOError('Syntax error in po file %s (line %s): ' + 'unknown keyword %s' % + (self.instance.fpath, i, tokens[1])) # Remove the keyword and any whitespace # between it and the starting quote. @@ -1247,27 +1311,28 @@ class _POFileParser(object): self.process(prev_keywords[tokens[1]], i) else: - self.process('??', i) + raise IOError('Syntax error in po file %s (line %s)' % + (self.instance.fpath, i)) if self.current_entry: # since entries are added when another entry is found, we must add # the last entry here (only if there are lines) self.instance.append(self.current_entry) - # before returning the instance, check if there's metadata and if + # before returning the instance, check if there's metadata and if # so extract it in a dict - firstentry = self.instance[0] - if firstentry.msgid == '': # metadata found + metadataentry = self.instance.find('') + if metadataentry: # metadata found # remove the entry - firstentry = self.instance.pop(0) - self.instance.metadata_is_fuzzy = firstentry.flags + self.instance.remove(metadataentry) + self.instance.metadata_is_fuzzy = metadataentry.flags key = None - for msg in firstentry.msgstr.splitlines(): + for msg in metadataentry.msgstr.splitlines(): try: key, val = msg.split(':', 1) self.instance.metadata[key] = val.strip() - except: + except (ValueError, KeyError): if key is not None: - self.instance.metadata[key] += '\n'+ msg.strip() + self.instance.metadata[key] += '\n' + msg.strip() # close opened file if not isinstance(self.fhandle, list): # must be file self.fhandle.close() @@ -1310,7 +1375,7 @@ class _POFileParser(object): if action(): self.current_state = state except Exception: - raise IOError('Syntax error in po file: %s (line %s)' % (self.instance.fpath, linenum)) + raise IOError('Syntax error in po file (line %s)' % linenum) # state handlers @@ -1328,7 +1393,10 @@ class _POFileParser(object): self.current_entry = POEntry() if self.current_entry.tcomment != '': self.current_entry.tcomment += '\n' - self.current_entry.tcomment += self.current_token[2:] + tcomment = self.current_token.lstrip('#') + if tcomment.startswith(' '): + tcomment = tcomment[1:] + self.current_entry.tcomment += tcomment return True def handle_gc(self): @@ -1352,10 +1420,10 @@ class _POFileParser(object): try: fil, line = occurrence.split(':') if not line.isdigit(): - fil = fil + line + fil = fil + line line = '' self.current_entry.occurrences.append((fil, line)) - except: + except (ValueError, AttributeError): self.current_entry.occurrences.append((occurrence, '')) return True @@ -1432,38 +1500,30 @@ class _POFileParser(object): """Handle a msgid or msgstr continuation line.""" token = unescape(self.current_token[1:-1]) if self.current_state == 'CT': - typ = 'msgctxt' self.current_entry.msgctxt += token elif self.current_state == 'MI': - typ = 'msgid' self.current_entry.msgid += token elif self.current_state == 'MP': - typ = 'msgid_plural' self.current_entry.msgid_plural += token elif self.current_state == 'MS': - typ = 'msgstr' self.current_entry.msgstr += token elif self.current_state == 'MX': - typ = 'msgstr[%s]' % self.msgstr_index self.current_entry.msgstr_plural[self.msgstr_index] += token elif self.current_state == 'PP': - typ = 'previous_msgid_plural' token = token[3:] self.current_entry.previous_msgid_plural += token elif self.current_state == 'PM': - typ = 'previous_msgid' token = token[3:] self.current_entry.previous_msgid += token elif self.current_state == 'PC': - typ = 'previous_msgctxt' token = token[3:] self.current_entry.previous_msgctxt += token # don't change the current state return False - # }}} # class _MOFileParser {{{ + class _MOFileParser(object): """ A class to parse binary mo files. @@ -1487,7 +1547,11 @@ class _MOFileParser(object): file (optional, default: ``False``). """ self.fhandle = open(mofile, 'rb') - self.instance = MOFile( + + klass = kwargs.get('klass') + if klass is None: + klass = MOFile + self.instance = klass( fpath=mofile, encoding=kwargs.get('encoding', default_encoding), check_for_duplicates=kwargs.get('check_for_duplicates', False) @@ -1529,7 +1593,7 @@ class _MOFileParser(object): self.fhandle.seek(msgstrs_index[i][1]) msgstr = self.fhandle.read(msgstrs_index[i][0]) - if i == 0: # metadata + if i == 0: # metadata raw_metadata, metadata = msgstr.split(b('\n')), {} for line in raw_metadata: tokens = line.split(b(':'), 1) @@ -1548,7 +1612,8 @@ class _MOFileParser(object): entry = self._build_entry( msgid=msgid_tokens[0], msgid_plural=msgid_tokens[1], - msgstr_plural=dict((k,v) for k,v in enumerate(msgstr.split(b('\0')))) + msgstr_plural=dict((k, v) for k, v in + enumerate(msgstr.split(b('\0')))) ) else: entry = self._build_entry(msgid=msgid, msgstr=msgstr) @@ -1564,7 +1629,7 @@ class _MOFileParser(object): if len(msgctxt_msgid) > 1: kwargs = { 'msgctxt': msgctxt_msgid[0].decode(encoding), - 'msgid' : msgctxt_msgid[1].decode(encoding), + 'msgid': msgctxt_msgid[1].decode(encoding), } else: kwargs = {'msgid': msgid.decode(encoding)} @@ -1588,17 +1653,17 @@ class _MOFileParser(object): if len(tup) == 1: return tup[0] return tup - # }}} # class TextWrapper {{{ + class TextWrapper(textwrap.TextWrapper): """ Subclass of textwrap.TextWrapper that backport the drop_whitespace option. """ def __init__(self, *args, **kwargs): - drop_whitespace = kwargs.pop('drop_whitespace', True) + drop_whitespace = kwargs.pop('drop_whitespace', True) textwrap.TextWrapper.__init__(self, *args, **kwargs) self.drop_whitespace = drop_whitespace @@ -1662,7 +1727,7 @@ class TextWrapper(textwrap.TextWrapper): self._handle_long_word(chunks, cur_line, cur_len, width) # If the last chunk on this line is all whitespace, drop it. - if self.drop_whitespace and cur_line and cur_line[-1].strip() == '': + if self.drop_whitespace and cur_line and not cur_line[-1].strip(): del cur_line[-1] # Convert current line back to a string and store it in list @@ -1671,10 +1736,10 @@ class TextWrapper(textwrap.TextWrapper): lines.append(indent + ''.join(cur_line)) return lines - # }}} # function wrap() {{{ + def wrap(text, width=70, **kwargs): """ Wrap a single paragraph of text, returning a list of wrapped lines. @@ -1683,4 +1748,4 @@ def wrap(text, width=70, **kwargs): return TextWrapper(width=width, **kwargs).wrap(text) return textwrap.wrap(text, width=width, **kwargs) -#}}} +# }}} From eeea8a6b1905dc82348e72d1fd5891635e58a131 Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Fri, 15 Feb 2013 15:15:01 +0100 Subject: [PATCH 03/23] dutch translation --- CHANGES | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 08ecf49..c51c6e2 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Version 0.7.0 * Support timezones on the last modified PO header. Thanks @jmoiron (Issue #43) * Actually move to the next block when submitting a lot of translations (Issue #13) * Add msgctxt to the entry hash to differentiate entries with context. Thanks @metalpriest (Issue #39) +* Added Dutch translation. Thanks, @leonderijke Version 0.6.8 ------------- @@ -19,7 +20,7 @@ Version 0.6.7 Version 0.6.6 ------------- * Django 1.4 support (Issue #30, #33) -* Better handling of translation callbacks on Bing's translation API and support of composite locales (Issue #26) +* Better handling of translation callbacks on Bing's translation API and support of composite locales (Issue #26) Version 0.6.5 ------------- @@ -29,7 +30,7 @@ Version 0.6.5 Version 0.6.4 ------------- -* Added ROSETTA_REQUIRES_AUTH option to grant access to non authenticated users (False by default) +* Added ROSETTA_REQUIRES_AUTH option to grant access to non authenticated users (False by default) Version 0.6.3 ------------- From e487ac7af54ee02ed307b2c9566ebdb8a1984d6d Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Fri, 15 Feb 2013 15:19:01 +0100 Subject: [PATCH 04/23] whoops, wrong project :) --- CHANGES | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGES b/CHANGES index c51c6e2..dc69d67 100644 --- a/CHANGES +++ b/CHANGES @@ -4,7 +4,6 @@ Version 0.7.0 * Support timezones on the last modified PO header. Thanks @jmoiron (Issue #43) * Actually move to the next block when submitting a lot of translations (Issue #13) * Add msgctxt to the entry hash to differentiate entries with context. Thanks @metalpriest (Issue #39) -* Added Dutch translation. Thanks, @leonderijke Version 0.6.8 ------------- From ba972b5b6ade85d21734a1a456f1dbac6099787f Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Thu, 21 Feb 2013 16:46:16 +0100 Subject: [PATCH 05/23] Possible fix for issues #63 and #64 --- CHANGES | 1 + rosetta/poutil.py | 61 ++++++++++++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/CHANGES b/CHANGES index dc69d67..fb7195f 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Version 0.7.0 * Support timezones on the last modified PO header. Thanks @jmoiron (Issue #43) * Actually move to the next block when submitting a lot of translations (Issue #13) * Add msgctxt to the entry hash to differentiate entries with context. Thanks @metalpriest (Issue #39) +* Better discovery of locale files on Django 1.4+ Thanks @tijs (Issues #63, #64) Version 0.6.8 ------------- diff --git a/rosetta/poutil.py b/rosetta/poutil.py index 5e72a0b..2a63989 100644 --- a/rosetta/poutil.py +++ b/rosetta/poutil.py @@ -46,7 +46,10 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False) project = __import__(parts[0], {}, {}, []) abs_project_path = os.path.normpath(os.path.abspath(os.path.dirname(project.__file__))) if project_apps: - paths.append(os.path.abspath(os.path.join(os.path.dirname(project.__file__), 'locale'))) + if os.path.exists(os.path.abspath(os.path.join(os.path.dirname(project.__file__), 'locale'))): + paths.append(os.path.abspath(os.path.join(os.path.dirname(project.__file__), 'locale'))) + if os.path.exists(os.path.abspath(os.path.join(os.path.dirname(project.__file__), '..', 'locale'))): + paths.append(os.path.abspath(os.path.join(os.path.dirname(project.__file__), '..', 'locale'))) # django/locale if django_apps: @@ -75,7 +78,6 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False) app = __import__(appname, {}, {}, []) apppath = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(app.__file__), 'locale'))) - # django apps if 'contrib' in apppath and 'django' in apppath and not django_apps: @@ -84,52 +86,57 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False) # third party external if not third_party_apps and abs_project_path not in apppath: continue - + # local apps if not project_apps and abs_project_path in apppath: continue - - + if os.path.isdir(apppath): paths.append(apppath) - - - - + ret = set() langs = (lang,) if u'-' in lang: - _l,_c = map(lambda x:x.lower(),lang.split(u'-')) - langs += (u'%s_%s' %(_l, _c), u'%s_%s' %(_l, _c.upper()), ) + _l, _c = map(lambda x: x.lower(), lang.split(u'-')) + langs += (u'%s_%s' % (_l, _c), u'%s_%s' % (_l, _c.upper()), ) elif u'_' in lang: - _l,_c = map(lambda x:x.lower(),lang.split(u'_')) - langs += (u'%s-%s' %(_l, _c), u'%s-%s' %(_l, _c.upper()), ) - + _l, _c = map(lambda x: x.lower(), lang.split(u'_')) + langs += (u'%s-%s' % (_l, _c), u'%s-%s' % (_l, _c.upper()), ) + paths = map(os.path.normpath, paths) + paths = list(set(paths)) for path in paths: for lang_ in langs: dirname = os.path.join(path, lang_, 'LC_MESSAGES') - for fn in ('django.po','djangojs.po',): + for fn in ('django.po', 'djangojs.po',): filename = os.path.join(dirname, fn) if os.path.isfile(filename): ret.add(os.path.abspath(filename)) return list(ret) -def pagination_range(first,last,current): + +def pagination_range(first, last, current): r = [] - + r.append(first) - if first + 1 < last: r.append(first+1) - - if current -2 > first and current -2 < last: r.append(current-2) - if current -1 > first and current -1 < last: r.append(current-1) - if current > first and current < last: r.append(current) - if current + 1 < last and current+1 > first: r.append(current+1) - if current + 2 < last and current+2 > first: r.append(current+2) - - if last-1 > first: r.append(last-1) + if first + 1 < last: + r.append(first + 1) + + if current - 2 > first and current - 2 < last: + r.append(current - 2) + if current - 1 > first and current - 1 < last: + r.append(current - 1) + if current > first and current < last: + r.append(current) + if current + 1 < last and current + 1 > first: + r.append(current + 1) + if current + 2 < last and current + 2 > first: + r.append(current + 2) + + if last - 1 > first: + r.append(last - 1) r.append(last) - + r = list(set(r)) r.sort() prev = 10000 From 7e61bdbdac79dc0bd5526b552e2465cb941e82ed Mon Sep 17 00:00:00 2001 From: Mike Rooney Date: Mon, 25 Feb 2013 15:06:54 -0500 Subject: [PATCH 06/23] fix broken i18n documentation link in README.md --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 304ccf9..b7207ad 100644 --- a/README.rst +++ b/README.rst @@ -92,7 +92,7 @@ Usage Generate a batch of files to translate -------------------------------------- -See `Django's documentation on Internationalization `_ to setup your project to use i18n and create the ``gettext`` catalog files. +See `Django's documentation on Internationalization `_ to setup your project to use i18n and create the ``gettext`` catalog files. Translate away! --------------- From 2577016b587a39fda10bc795953cd20c65471ff4 Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Mon, 25 Feb 2013 21:20:38 +0100 Subject: [PATCH 07/23] This actually seems more appropriate --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b7207ad..925797b 100644 --- a/README.rst +++ b/README.rst @@ -92,7 +92,7 @@ Usage Generate a batch of files to translate -------------------------------------- -See `Django's documentation on Internationalization `_ to setup your project to use i18n and create the ``gettext`` catalog files. +See `Django's documentation on Internationalization `_ to setup your project to use i18n and create the ``gettext`` catalog files. Translate away! --------------- From 08569f23cefd3a57fe6b11897fe08b91231d3ae0 Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Wed, 27 Feb 2013 18:24:53 +0100 Subject: [PATCH 08/23] wip python 3 support --- rosetta/storage.py | 3 +- rosetta/templatetags/rosetta.py | 4 +- rosetta/tests/__init__.py | 123 ++++++++++++++++---------------- rosetta/tests/urls.py | 7 +- rosetta/urls.py | 6 +- rosetta/views.py | 25 +++---- runtests_multi_venv.sh | 8 +++ testproject/urls.py | 6 +- 8 files changed, 103 insertions(+), 79 deletions(-) diff --git a/rosetta/storage.py b/rosetta/storage.py index 7c2d77d..2faf605 100644 --- a/rosetta/storage.py +++ b/rosetta/storage.py @@ -4,6 +4,7 @@ from django.utils import importlib from django.core.exceptions import ImproperlyConfigured import hashlib import time +import six class BaseRosettaStorage(object): @@ -62,7 +63,7 @@ class CacheRosettaStorage(BaseRosettaStorage): if 'rosetta_cache_storage_key_prefix' in self.request.session: self._key_prefix = self.request.session['rosetta_cache_storage_key_prefix'] else: - self._key_prefix = hashlib.new('sha1', str(time.time())).hexdigest() + self._key_prefix = hashlib.new('sha1', six.text_type(time.time()).encode('utf8')).hexdigest() self.request.session['rosetta_cache_storage_key_prefix'] = self._key_prefix if self.request.session['rosetta_cache_storage_key_prefix'] != self._key_prefix: diff --git a/rosetta/templatetags/rosetta.py b/rosetta/templatetags/rosetta.py index 4eeec2a..3cb7f09 100644 --- a/rosetta/templatetags/rosetta.py +++ b/rosetta/templatetags/rosetta.py @@ -3,7 +3,7 @@ from django.utils.safestring import mark_safe from django.utils.html import escape import re from django.template import Node -from django.utils.encoding import smart_unicode +import six register = template.Library() @@ -60,7 +60,7 @@ class IncrNode(template.Node): def render(self, context): self.val += 1 - return smart_unicode(self.val) + return six.text_type(self.val) def is_fuzzy(message): diff --git a/rosetta/tests/__init__.py b/rosetta/tests/__init__.py index 21e0e1a..656d1cb 100644 --- a/rosetta/tests/__init__.py +++ b/rosetta/tests/__init__.py @@ -9,6 +9,7 @@ from rosetta.conf import settings as rosetta_settings from rosetta.signals import entry_changed, post_save import os import shutil +import six import django @@ -59,14 +60,14 @@ class RosettaTestCase(TestCase): def test_1_ListLoading(self): r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') r = self.client.get(reverse('rosetta-pick-file')) - self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in r.content) + self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in str(r.content)) def test_2_PickFile(self): r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0,), kwargs=dict()) + '?rosetta') r = self.client.get(reverse('rosetta-home')) - self.assertTrue('dummy language' in r.content) + self.assertTrue('dummy language' in str(r.content)) def test_3_DownloadZIP(self): r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') @@ -86,10 +87,10 @@ class RosettaTestCase(TestCase): r = self.client.get(reverse('rosetta-home') + '?filter=untranslated') r = self.client.get(reverse('rosetta-home')) # make sure both strings are untranslated - self.assertTrue('dummy language' in r.content) - self.assertTrue('String 1' in r.content) - self.assertTrue('String 2' in r.content) - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content) + self.assertTrue('dummy language' in str(r.content)) + self.assertTrue('String 1' in str(r.content)) + self.assertTrue('String 2' in str(r.content)) + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) # post a translation r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next')) @@ -100,17 +101,17 @@ class RosettaTestCase(TestCase): r = self.client.get(reverse('rosetta-home')) # the translated string no longer is up for translation - self.assertTrue('String 1' in r.content) - self.assertTrue('String 2' not in r.content) + self.assertTrue('String 1' in str(r.content)) + self.assertTrue('String 2' not in str(r.content)) # display only translated strings r = self.client.get(reverse('rosetta-home') + '?filter=translated') r = self.client.get(reverse('rosetta-home')) # The tranlsation was persisted - self.assertTrue('String 1' not in r.content) - self.assertTrue('String 2' in r.content) - self.assertTrue('Hello, world' in r.content) + self.assertTrue('String 1' not in str(r.content)) + self.assertTrue('String 2' in str(r.content)) + self.assertTrue('Hello, world' in str(r.content)) def test_5_TestIssue67(self): # testcase for issue 67: http://code.google.com/p/django-rosetta/issues/detail?id=67 @@ -131,10 +132,10 @@ class RosettaTestCase(TestCase): r = self.client.get(reverse('rosetta-home')) # make sure all strings are untranslated - self.assertTrue('dummy language' in r.content) - self.assertTrue('String 1' in r.content) - self.assertTrue('String 2' in r.content) - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content) + self.assertTrue('dummy language' in str(r.content)) + self.assertTrue('String 1' in str(r.content)) + self.assertTrue('String 2' in str(r.content)) + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) # post a translation r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next')) @@ -154,21 +155,21 @@ class RosettaTestCase(TestCase): r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') r = self.client.get(reverse('rosetta-pick-file')) - self.assertTrue('rosetta/locale/xx/LC_MESSAGES/django.po' not in r.content) + self.assertTrue('rosetta/locale/xx/LC_MESSAGES/django.po' not in str(r.content)) rosetta_settings.EXCLUDED_APPLICATIONS = () r = self.client.get(reverse('rosetta-pick-file') + '?rosetta') - self.assertTrue('rosetta/locale/xx/LC_MESSAGES/django.po' in r.content) + self.assertTrue('rosetta/locale/xx/LC_MESSAGES/django.po' in str(r.content)) def test_7_selfInApplist(self): self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') r = self.client.get(reverse('rosetta-pick-file')) - self.assertTrue('rosetta/locale/xx/LC_MESSAGES/django.po' in r.content) + self.assertTrue('rosetta/locale/xx/LC_MESSAGES/django.po' in str(r.content)) self.client.get(reverse('rosetta-pick-file') + '?filter=project') r = self.client.get(reverse('rosetta-pick-file')) - self.assertTrue('rosetta/locale/xx/LC_MESSAGES/django.po' not in r.content) + self.assertTrue('rosetta/locale/xx/LC_MESSAGES/django.po' not in str(r.content)) def test_8_hideObsoletes(self): r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') @@ -178,12 +179,12 @@ class RosettaTestCase(TestCase): # not in listing for p in range(1, 5): r = self.client.get(reverse('rosetta-home') + '?page=%d' % p) - self.assertTrue('dummy language' in r.content) - self.assertTrue('Les deux' not in r.content) + self.assertTrue('dummy language' in str(r.content)) + self.assertTrue('Les deux' not in str(r.content)) r = self.client.get(reverse('rosetta-home') + '?query=Les%20Deux') - self.assertTrue('dummy language' in r.content) - self.assertTrue('Les deux' not in r.content) + self.assertTrue('dummy language' in str(r.content)) + self.assertTrue('Les deux' not in str(r.content)) def test_9_concurrency(self): shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file) @@ -200,9 +201,9 @@ class RosettaTestCase(TestCase): r2 = self.client2.get(reverse('rosetta-home') + '?filter=untranslated') r2 = self.client2.get(reverse('rosetta-home')) - self.assertTrue('String 1' in r.content) + self.assertTrue('String 1' in str(r.content)) self.assertTrue('String 1' in r2.content) - self.assertTrue('m_08e4e11e2243d764fc45a5a4fba5d0f2' in r.content) + self.assertTrue('m_08e4e11e2243d764fc45a5a4fba5d0f2' in str(r.content)) r = self.client.post(reverse('rosetta-home'), dict(m_08e4e11e2243d764fc45a5a4fba5d0f2='Hello, world', _next='_next'), follow=True) r2 = self.client2.get(reverse('rosetta-home')) @@ -216,8 +217,8 @@ class RosettaTestCase(TestCase): r2 = self.client2.get(reverse('rosetta-home') + '?filter=untranslated') r2 = self.client2.get(reverse('rosetta-home')) - self.assertTrue('String 2' in r2.content and 'm_e48f149a8b2e8baa81b816c0edf93890' in r2.content) - self.assertTrue('String 2' in r.content and 'm_e48f149a8b2e8baa81b816c0edf93890' in r.content) + self.assertTrue('String 2' in str(r2.content) and 'm_e48f149a8b2e8baa81b816c0edf93890' in str(r2.content)) + self.assertTrue('String 2' in str(r.content) and 'm_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) # client 2 posts! r2 = self.client2.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world, from client two!', _next='_next'), follow=True) @@ -227,7 +228,7 @@ class RosettaTestCase(TestCase): # uh-oh here comes client 1 r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world, from client one!', _next='_next'), follow=True) # An error message is displayed - self.assertTrue('save-conflict' in r.content) + self.assertTrue('save-conflict' in str(r.content)) # client 2 won pofile_content = open(self.dest_file, 'r').read() @@ -235,34 +236,34 @@ class RosettaTestCase(TestCase): # Both clients show all strings, error messages are gone r = self.client.get(reverse('rosetta-home') + '?filter=translated') - self.assertTrue('save-conflict' not in r.content) + self.assertTrue('save-conflict' not in str(r.content)) r2 = self.client2.get(reverse('rosetta-home') + '?filter=translated') self.assertTrue('save-conflict' not in r2.content) r = self.client.get(reverse('rosetta-home')) - self.assertTrue('save-conflict' not in r.content) + self.assertTrue('save-conflict' not in str(r.content)) r2 = self.client2.get(reverse('rosetta-home')) self.assertTrue('save-conflict' not in r2.content) # Both have client's two version - self.assertTrue('Hello, world, from client two!' in r.content) + self.assertTrue('Hello, world, from client two!' in str(r.content)) self.assertTrue('Hello, world, from client two!' in r2.content) self.assertTrue('save-conflict' not in r2.content) - self.assertTrue('save-conflict' not in r.content) + self.assertTrue('save-conflict' not in str(r.content)) def test_10_issue_79_num_entries(self): shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue79.template')), self.dest_file) self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') r = self.client.get(reverse('rosetta-pick-file')) - self.assertTrue('1' in r.content) - self.assertTrue('%s%%' % str(floatformat(0.0, 2)) in r.content) - self.assertTrue('1' in r.content) + self.assertTrue('1' in str(r.content)) + self.assertTrue('%s%%' % str(floatformat(0.0, 2)) in str(r.content)) + self.assertTrue('1' in str(r.content)) def test_11_issue_80_tab_indexes(self): self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0,), kwargs=dict())) r = self.client.get(reverse('rosetta-home')) - self.assertTrue('tabindex="3"' in r.content) + self.assertTrue('tabindex="3"' in str(r.content)) def test_12_issue_82_staff_user(self): settings.ROSETTA_REQUIRES_AUTH = True @@ -286,35 +287,35 @@ class RosettaTestCase(TestCase): settings.LANGUAGES = (('fr', 'French'), ('xx', 'Dummy Language'),) self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') r = self.client.get(reverse('rosetta-pick-file')) - self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in r.content) - self.assertTrue(('contrib') not in r.content) + self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in str(r.content)) + self.assertTrue(('contrib') not in str(r.content)) self.client.get(reverse('rosetta-pick-file') + '?filter=django') r = self.client.get(reverse('rosetta-pick-file')) - self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') not in r.content) + self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') not in str(r.content)) if self.django_version_major >= 1 and self.django_version_minor >= 3: - self.assertTrue(('contrib') in r.content) + self.assertTrue(('contrib') in str(r.content)) self.client.get(reverse('rosetta-pick-file') + '?filter=all') r = self.client.get(reverse('rosetta-pick-file')) - self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in r.content) + self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') in str(r.content)) if self.django_version_major >= 1 and self.django_version_minor >= 3: - self.assertTrue(('contrib') in r.content) + self.assertTrue(('contrib') in str(r.content)) self.client.get(reverse('rosetta-pick-file') + '?filter=project') r = self.client.get(reverse('rosetta-pick-file')) - self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') not in r.content) + self.assertTrue(os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') not in str(r.content)) if self.django_version_major >= 1 and self.django_version_minor >= 3: - self.assertTrue(('contrib') not in r.content) + self.assertTrue(('contrib') not in str(r.content)) def test_14_issue_99_context_and_comments(self): self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict())) r = self.client.get(reverse('rosetta-home')) - self.assertTrue('This is a text of the base template' in r.content) - self.assertTrue('Context hint' in r.content) + self.assertTrue('This is a text of the base template' in str(r.content)) + self.assertTrue('Context hint' in str(r.content)) def test_14_issue_87_entry_changed_signal(self): # copy the template file @@ -329,7 +330,7 @@ class RosettaTestCase(TestCase): self.test_old_msgstr = kwargs.get('old_msgstr') self.test_new_msgstr = sender.msgstr self.test_msg_id = sender.msgid - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content) + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) # post a translation r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next')) @@ -350,7 +351,7 @@ class RosettaTestCase(TestCase): def test_receiver(sender, **kwargs): self.test_sig_lang = kwargs.get('language_code') - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content) + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) # post a translation r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next')) @@ -369,7 +370,7 @@ class RosettaTestCase(TestCase): def test_receiver(sender, **kwargs): self.test_16_has_request = 'request' in kwargs - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content) + self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in str(r.content)) # post a translation r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next')) @@ -385,7 +386,7 @@ class RosettaTestCase(TestCase): r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0, ), kwargs=dict())) r = self.client.get(reverse('rosetta-home')) - self.assertTrue('m_bb9d8fe6159187b9ea494c1b313d23d4' in r.content) + self.assertTrue('m_bb9d8fe6159187b9ea494c1b313d23d4' in str(r.content)) # post a translation, it should have properly wrapped lines r = self.client.post(reverse('rosetta-home'), dict(m_bb9d8fe6159187b9ea494c1b313d23d4='Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.', _next='_next')) @@ -397,7 +398,7 @@ class RosettaTestCase(TestCase): self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0, ), kwargs=dict())) r = self.client.get(reverse('rosetta-home')) - self.assertTrue('m_bb9d8fe6159187b9ea494c1b313d23d4' in r.content) + self.assertTrue('m_bb9d8fe6159187b9ea494c1b313d23d4' in str(r.content)) rosetta_settings.POFILE_WRAP_WIDTH = 0 r = self.client.post(reverse('rosetta-home'), dict(m_bb9d8fe6159187b9ea494c1b313d23d4='Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.', _next='_next')) pofile_content = open(self.dest_file, 'r').read() @@ -409,9 +410,9 @@ class RosettaTestCase(TestCase): self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0, ), kwargs=dict())) r = self.client.get(reverse('rosetta-home')) - self.assertTrue('m_ff7060c1a9aae9c42af4d54ac8551f67_1' in r.content) - self.assertTrue('m_ff7060c1a9aae9c42af4d54ac8551f67_0' in r.content) - self.assertTrue('m_09f7e02f1290be211da707a266f153b3' in r.content) + self.assertTrue('m_ff7060c1a9aae9c42af4d54ac8551f67_1' in str(r.content)) + self.assertTrue('m_ff7060c1a9aae9c42af4d54ac8551f67_0' in str(r.content)) + self.assertTrue('m_09f7e02f1290be211da707a266f153b3' in str(r.content)) # post a translation, it should have properly wrapped lines r = self.client.post(reverse('rosetta-home'), dict( @@ -438,7 +439,7 @@ class RosettaTestCase(TestCase): self.client.get(reverse('rosetta-language-selection', args=('xx', 0, ), kwargs=dict())) r = self.client.get(reverse('rosetta-home')) self.assertFalse(len(str(self.client.cookies.get('sessionid'))) > 4096) - self.assertTrue('m_9efd113f7919952523f06e0d88da9c54' in r.content) + self.assertTrue('m_9efd113f7919952523f06e0d88da9c54' in str(r.content)) r = self.client.post(reverse('rosetta-home'), dict( m_9efd113f7919952523f06e0d88da9c54='Testing cookie length', _next='_next' @@ -448,8 +449,8 @@ class RosettaTestCase(TestCase): self.client.get(reverse('rosetta-home') + '?filter=translated') r = self.client.get(reverse('rosetta-home')) - self.assertTrue('Testing cookie length' in r.content) - self.assertTrue('m_9f6c442c6d579707440ba9dada0fb373' in r.content) + self.assertTrue('Testing cookie length' in str(r.content)) + self.assertTrue('m_9f6c442c6d579707440ba9dada0fb373' in str(r.content)) # Two, the cookie backend rosetta_settings.STORAGE_CLASS = 'rosetta.storage.SessionRosettaStorage' @@ -461,10 +462,10 @@ class RosettaTestCase(TestCase): r = self.client.get(reverse('rosetta-home')) self.assertTrue(len(str(self.client.cookies.get('sessionid'))) > 4096) # boom: be a good browser, truncate the cookie - self.client.cookies['sessionid'] = unicode(self.client.cookies.get('sessionid'))[:4096] + self.client.cookies['sessionid'] = six.text_type(self.client.cookies.get('sessionid'))[:4096] r = self.client.get(reverse('rosetta-home')) - self.assertFalse('m_9efd113f7919952523f06e0d88da9c54' in r.content) + self.assertFalse('m_9efd113f7919952523f06e0d88da9c54' in str(r.content)) def test_20_concurrency_of_cache_backend(self): rosetta_settings.STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage' @@ -485,6 +486,6 @@ class RosettaTestCase(TestCase): r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict())) r = self.client.get(reverse('rosetta-home')) # We have distinct hashes, even though the msgid and msgstr are identical - self.assertTrue('m_4765f7de94996d3de5975fa797c3451f' in r.content) - self.assertTrue('m_08e4e11e2243d764fc45a5a4fba5d0f2' in r.content) + self.assertTrue('m_4765f7de94996d3de5975fa797c3451f' in str(r.content)) + self.assertTrue('m_08e4e11e2243d764fc45a5a4fba5d0f2' in str(r.content)) diff --git a/rosetta/tests/urls.py b/rosetta/tests/urls.py index 0f9c199..f5cfe8b 100644 --- a/rosetta/tests/urls.py +++ b/rosetta/tests/urls.py @@ -1,4 +1,9 @@ -from django.conf.urls.defaults import * +try: + from django.conf.urls import patterns, include, url +except ImportError: + from django.conf.urls.defaults import patterns, include, url + + urlpatterns = patterns('', url(r'^rosetta/',include('rosetta.urls')), url(r'^admin/$','rosetta.tests.views.dummy', name='dummy-login') diff --git a/rosetta/urls.py b/rosetta/urls.py index b701b69..4c493fc 100644 --- a/rosetta/urls.py +++ b/rosetta/urls.py @@ -1,4 +1,8 @@ -from django.conf.urls.defaults import url, patterns +try: + from django.conf.urls import patterns, url +except ImportError: + from django.conf.urls.defaults import patterns, url + urlpatterns = patterns('rosetta.views', url(r'^$', 'home', name='rosetta-home'), diff --git a/rosetta/views.py b/rosetta/views.py index cfbb818..8203353 100644 --- a/rosetta/views.py +++ b/rosetta/views.py @@ -5,7 +5,7 @@ from django.core.urlresolvers import reverse from django.http import Http404, HttpResponseRedirect, HttpResponse from django.shortcuts import render_to_response from django.template import RequestContext -from django.utils.encoding import smart_unicode, iri_to_uri +from django.utils.encoding import iri_to_uri from django.utils.translation import ugettext_lazy as _ from django.views.decorators.cache import never_cache from rosetta.conf import settings as rosetta_settings @@ -18,6 +18,7 @@ import rosetta import unicodedata import hashlib import os +import six def home(request): @@ -57,9 +58,9 @@ def home(request): rosetta_i18n_pofile = pofile(rosetta_i18n_fn, wrapwidth=rosetta_settings.POFILE_WRAP_WIDTH) for entry in rosetta_i18n_pofile: entry.md5hash = hashlib.md5( - entry.msgid.encode("utf8") + - entry.msgstr.encode("utf8") + - (entry.msgctxt and entry.msgctxt.encode("utf8") or "") + (six.text_type(entry.msgid) + + six.text_type(entry.msgstr) + + six.text_type(entry.msgctxt and entry.msgctxt.encode("utf8") or "")).encode('utf8') ).hexdigest() else: @@ -86,7 +87,7 @@ def home(request): # polib parses .po files into unicode strings, but # doesn't bother to convert plural indexes to int, # so we need unicode here. - plural_id = unicode(rx_plural.match(key).groups()[1]) + plural_id = six.text_type(rx_plural.match(key).groups()[1]) elif rx.match(key): md5hash = str(rx.match(key).groups()[0]) @@ -183,7 +184,7 @@ def home(request): if 'query' in request.REQUEST and request.REQUEST.get('query', '').strip(): query = request.REQUEST.get('query').strip() rx = re.compile(re.escape(query), re.IGNORECASE) - paginator = Paginator([e for e in rosetta_i18n_pofile if not e.obsolete and rx.search(smart_unicode(e.msgstr) + smart_unicode(e.msgid) + u''.join([o[0] for o in e.occurrences]))], rosetta_settings.MESSAGES_PER_PAGE) + paginator = Paginator([e for e in rosetta_i18n_pofile if not e.obsolete and rx.search(six.text_type(e.msgstr) + six.text_type(e.msgid) + u''.join([o[0] for o in e.occurrences]))], rosetta_settings.MESSAGES_PER_PAGE) else: if rosetta_i18n_filter == 'untranslated': paginator = Paginator(rosetta_i18n_pofile.untranslated_entries(), rosetta_settings.MESSAGES_PER_PAGE) @@ -271,7 +272,7 @@ def download_file(request): mo_fn = str(po_fn.replace('.po', '.mo')) # not so smart, huh zipdata = StringIO() zipf = zipfile.ZipFile(zipdata, mode="w") - zipf.writestr(po_fn, unicode(rosetta_i18n_pofile).encode("utf8")) + zipf.writestr(po_fn, six.text_type(rosetta_i18n_pofile).encode("utf8")) zipf.writestr(mo_fn, rosetta_i18n_pofile.to_binary()) zipf.close() zipdata.seek(0) @@ -351,14 +352,14 @@ def lang_sel(request, langid, idx): file_ = find_pos(langid, project_apps=project_apps, django_apps=django_apps, third_party_apps=third_party_apps)[int(idx)] storage.set('rosetta_i18n_lang_code', langid) - storage.set('rosetta_i18n_lang_name', unicode([l[1] for l in settings.LANGUAGES if l[0] == langid][0])) + storage.set('rosetta_i18n_lang_name', six.text_type([l[1] for l in settings.LANGUAGES if l[0] == langid][0])) storage.set('rosetta_i18n_fn', file_) po = pofile(file_) for entry in po: - entry.md5hash = hashlib.md5( - entry.msgid.encode("utf8") + - entry.msgstr.encode("utf8") + - (entry.msgctxt and entry.msgctxt.encode("utf8") or "") + entry.md5hash = hashlib.new('md5', + (six.text_type(entry.msgid) + + six.text_type(entry.msgstr) + + six.text_type(entry.msgctxt and entry.msgctxt.encode("utf8") or "")).encode('utf8') ).hexdigest() storage.set('rosetta_i18n_pofile', po) diff --git a/runtests_multi_venv.sh b/runtests_multi_venv.sh index 36cc9db..e8801f2 100644 --- a/runtests_multi_venv.sh +++ b/runtests_multi_venv.sh @@ -21,3 +21,11 @@ python manage.py test rosetta cd .. deactivate +. venv_15_p3/bin/activate +cd testproject +python manage.py --version +python --version +python manage.py test rosetta +cd .. +deactivate + diff --git a/testproject/urls.py b/testproject/urls.py index 5bdc133..485c9e5 100644 --- a/testproject/urls.py +++ b/testproject/urls.py @@ -1,4 +1,8 @@ -from django.conf.urls.defaults import patterns, include, url +try: + from django.conf.urls import patterns, include, url +except ImportError: + from django.conf.urls.defaults import patterns, include, url + from django.contrib.staticfiles.urls import staticfiles_urlpatterns # Uncomment the next two lines to enable the admin: From 16d1a49515b3df0dffcdcfc43486bf3b82bccf62 Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Wed, 27 Feb 2013 21:01:00 +0100 Subject: [PATCH 09/23] all tests pass --- rosetta/tests/__init__.py | 28 ++++++++++++++-------------- rosetta/views.py | 7 +++---- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/rosetta/tests/__init__.py b/rosetta/tests/__init__.py index 656d1cb..157fa26 100644 --- a/rosetta/tests/__init__.py +++ b/rosetta/tests/__init__.py @@ -120,8 +120,8 @@ class RosettaTestCase(TestCase): f_ = open(self.dest_file, 'rb') content = f_.read() f_.close() - self.assertTrue(u'Hello, world' not in content) - self.assertTrue(u'|| n%100>=20) ? 1 : 2)' in content) + self.assertTrue('Hello, world' not in six.text_type(content)) + self.assertTrue('|| n%100>=20) ? 1 : 2)' in six.text_type(content)) del(content) # Load the template file @@ -144,9 +144,9 @@ class RosettaTestCase(TestCase): f_ = open(self.dest_file, 'rb') content = f_.read() f_.close() - self.assertTrue(u'Hello, world' in content) - self.assertTrue(u'|| n%100>=20) ? 1 : 2)' in content) - self.assertTrue(u'or n%100>=20) ? 1 : 2)' not in content) + self.assertTrue('Hello, world' in str(content)) + self.assertTrue('|| n%100>=20) ? 1 : 2)' in str(content)) + self.assertTrue('or n%100>=20) ? 1 : 2)' not in str(content)) del(content) def test_6_ExcludedApps(self): @@ -202,15 +202,15 @@ class RosettaTestCase(TestCase): r2 = self.client2.get(reverse('rosetta-home')) self.assertTrue('String 1' in str(r.content)) - self.assertTrue('String 1' in r2.content) + self.assertTrue('String 1' in str(r2.content)) self.assertTrue('m_08e4e11e2243d764fc45a5a4fba5d0f2' in str(r.content)) r = self.client.post(reverse('rosetta-home'), dict(m_08e4e11e2243d764fc45a5a4fba5d0f2='Hello, world', _next='_next'), follow=True) r2 = self.client2.get(reverse('rosetta-home')) # Client 2 reloads the home, forces a reload of the catalog, # the untranslated string1 is now translated - self.assertTrue('String 1' not in r2.content) - self.assertTrue('String 2' in r2.content) + self.assertTrue('String 1' not in str(r2.content)) + self.assertTrue('String 2' in str(r2.content)) r = self.client.get(reverse('rosetta-home') + '?filter=untranslated') r = self.client.get(reverse('rosetta-home')) @@ -223,7 +223,7 @@ class RosettaTestCase(TestCase): # client 2 posts! r2 = self.client2.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world, from client two!', _next='_next'), follow=True) - self.assertTrue('save-conflict' not in r2.content) + self.assertTrue('save-conflict' not in str(r2.content)) # uh-oh here comes client 1 r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world, from client one!', _next='_next'), follow=True) @@ -238,16 +238,16 @@ class RosettaTestCase(TestCase): r = self.client.get(reverse('rosetta-home') + '?filter=translated') self.assertTrue('save-conflict' not in str(r.content)) r2 = self.client2.get(reverse('rosetta-home') + '?filter=translated') - self.assertTrue('save-conflict' not in r2.content) + self.assertTrue('save-conflict' not in str(r2.content)) r = self.client.get(reverse('rosetta-home')) self.assertTrue('save-conflict' not in str(r.content)) r2 = self.client2.get(reverse('rosetta-home')) - self.assertTrue('save-conflict' not in r2.content) + self.assertTrue('save-conflict' not in str(r2.content)) # Both have client's two version self.assertTrue('Hello, world, from client two!' in str(r.content)) - self.assertTrue('Hello, world, from client two!' in r2.content) - self.assertTrue('save-conflict' not in r2.content) + self.assertTrue('Hello, world, from client two!' in str(r2.content)) + self.assertTrue('save-conflict' not in str(r2.content)) self.assertTrue('save-conflict' not in str(r.content)) def test_10_issue_79_num_entries(self): @@ -486,6 +486,6 @@ class RosettaTestCase(TestCase): r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict())) r = self.client.get(reverse('rosetta-home')) # We have distinct hashes, even though the msgid and msgstr are identical + #print (r.content) self.assertTrue('m_4765f7de94996d3de5975fa797c3451f' in str(r.content)) self.assertTrue('m_08e4e11e2243d764fc45a5a4fba5d0f2' in str(r.content)) - diff --git a/rosetta/views.py b/rosetta/views.py index 8203353..dba01d5 100644 --- a/rosetta/views.py +++ b/rosetta/views.py @@ -60,7 +60,7 @@ def home(request): entry.md5hash = hashlib.md5( (six.text_type(entry.msgid) + six.text_type(entry.msgstr) + - six.text_type(entry.msgctxt and entry.msgctxt.encode("utf8") or "")).encode('utf8') + six.text_type(entry.msgctxt or "")).encode('utf8') ).hexdigest() else: @@ -252,7 +252,6 @@ home = user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)(ho def download_file(request): import zipfile - from StringIO import StringIO storage = get_storage(request) # original filename rosetta_i18n_fn = storage.get('rosetta_i18n_fn', None) @@ -270,7 +269,7 @@ def download_file(request): offered_fn = rosetta_i18n_fn.split('/')[-1] po_fn = str(rosetta_i18n_fn.split('/')[-1]) mo_fn = str(po_fn.replace('.po', '.mo')) # not so smart, huh - zipdata = StringIO() + zipdata = six.BytesIO() zipf = zipfile.ZipFile(zipdata, mode="w") zipf.writestr(po_fn, six.text_type(rosetta_i18n_pofile).encode("utf8")) zipf.writestr(mo_fn, rosetta_i18n_pofile.to_binary()) @@ -359,7 +358,7 @@ def lang_sel(request, langid, idx): entry.md5hash = hashlib.new('md5', (six.text_type(entry.msgid) + six.text_type(entry.msgstr) + - six.text_type(entry.msgctxt and entry.msgctxt.encode("utf8") or "")).encode('utf8') + six.text_type(entry.msgctxt or "")).encode('utf8') ).hexdigest() storage.set('rosetta_i18n_pofile', po) From 0e65e98b97c3428640b45267329c40f76713224f Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Wed, 27 Feb 2013 21:03:52 +0100 Subject: [PATCH 10/23] changes, added dependency for six --- CHANGES | 1 + setup.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index fb7195f..90b640c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,6 @@ Version 0.7.0 ------------- +* Support for Django 1.5 and HEAD, support for Python 3. * Upgraded bundled polib to version 1.0.3 - http://pypi.python.org/pypi/polib/1.0.3 * Support timezones on the last modified PO header. Thanks @jmoiron (Issue #43) * Actually move to the next block when submitting a lot of translations (Issue #13) diff --git a/setup.py b/setup.py index 3393074..686b89f 100644 --- a/setup.py +++ b/setup.py @@ -20,5 +20,9 @@ setup( 'Framework :: Django', ], include_package_data=True, - zip_safe=False + zip_safe=False, + install_requires=[ + 'six >=1.2.0', + 'Django >= 1.3' + ] ) From 8db3fa89b4643812b3ce22815ef095caa60d0965 Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Wed, 27 Feb 2013 21:08:57 +0100 Subject: [PATCH 11/23] Python3 support --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 925797b..5ca1a5b 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ Features Requirements ************ -Rosetta requires Django 1.3 or later +Rosetta requires Django 1.3, 1.4 or 1.5. When running with Django 1.5, Python 3.x is supported. ************ Installation From 6ac75be721d94c852e9457b1ef375c9bb81e8745 Mon Sep 17 00:00:00 2001 From: Marco Bonetti Date: Thu, 28 Feb 2013 12:18:08 +0100 Subject: [PATCH 12/23] wip cleanup --- rosetta/templates/rosetta/base.html | 2 +- rosetta/templates/rosetta/js/rosetta.js | 12 ++++----- rosetta/templates/rosetta/pofile.html | 24 ++++++++--------- rosetta/tests/__init__.py | 35 +++++++++++++++++++++++++ rosetta/views.py | 35 ++++++++++++++++++------- 5 files changed, 79 insertions(+), 29 deletions(-) diff --git a/rosetta/templates/rosetta/base.html b/rosetta/templates/rosetta/base.html index e3949df..79a5085 100644 --- a/rosetta/templates/rosetta/base.html +++ b/rosetta/templates/rosetta/base.html @@ -14,7 +14,7 @@ diff --git a/rosetta/templates/rosetta/js/rosetta.js b/rosetta/templates/rosetta/js/rosetta.js index 4399177..a8ed143 100644 --- a/rosetta/templates/rosetta/js/rosetta.js +++ b/rosetta/templates/rosetta/js/rosetta.js @@ -7,16 +7,16 @@ google.setOnLoadCallback(function() { $('.hide', $(this).parent()).hide(); }); -{% if ENABLE_TRANSLATION_SUGGESTIONS and BING_APP_ID %} +{% if rosetta_settings.ENABLE_TRANSLATION_SUGGESTIONS and rosetta_settings.BING_APP_ID %} $('a.suggest').click(function(e){ e.preventDefault(); var a = $(this); var str = a.html(); var orig = $('.original .message', a.parents('tr')).html(); var trans=$('textarea',a.parent()); - var sourceLang = '{{ MESSAGES_SOURCE_LANGUAGE_CODE }}'; + var sourceLang = '{{ rosetta_settings.MESSAGES_SOURCE_LANGUAGE_CODE }}'; var destLang = '{{ rosetta_i18n_lang_code }}'; - var app_id = '{{ BING_APP_ID }}'; + var app_id = '{{ rosetta_settings.BING_APP_ID }}'; var apiUrl = "http://api.microsofttranslator.com/V2/Ajax.svc/Translate"; orig = unescape(orig).replace(//g,'\n').replace(//,'').replace(/<\/code>/g,'').replace(/>/g,'>').replace(/</g,'<'); @@ -50,7 +50,7 @@ google.setOnLoadCallback(function() { $($('.part',td).get(j)).css('top',textareaY + 'px'); }); }); - + $('.translation textarea').blur(function() { if($(this).val()) { $('.alert', $(this).parents('tr')).remove(); @@ -70,7 +70,7 @@ google.setOnLoadCallback(function() { } else { if (!(origs === null && trads === null)) { $(this).before(error); - return false; + return false; } } return true; @@ -78,5 +78,5 @@ google.setOnLoadCallback(function() { }); $('.translation textarea').eq(0).focus(); - + }); diff --git a/rosetta/templates/rosetta/pofile.html b/rosetta/templates/rosetta/pofile.html index df17d72..8482c0d 100644 --- a/rosetta/templates/rosetta/pofile.html +++ b/rosetta/templates/rosetta/pofile.html @@ -15,7 +15,7 @@ {% endblock %} -{% block pagetitle %}{{block.super}} - {{MESSAGES_SOURCE_LANGUAGE_NAME}} - {{rosetta_i18n_lang_name}} ({{ rosetta_i18n_pofile.percent_translated|floatformat:0 }}%){% endblock %} +{% block pagetitle %}{{block.super}} - {{rosetta_settings.MESSAGES_SOURCE_LANGUAGE_NAME}} - {{rosetta_i18n_lang_name}} ({{ rosetta_i18n_pofile.percent_translated|floatformat:0 }}%){% endblock %} {% block breadcumbs %}
@@ -33,15 +33,15 @@