Merge branch 'develop' into pr/44

Conflicts:
	README.rst
	rosetta/conf/settings.py
	rosetta/poutil.py
This commit is contained in:
Marco Bonetti 2014-01-15 17:07:38 +01:00
commit 043ace4ebb
43 changed files with 2105 additions and 1395 deletions

6
.gitignore vendored
View file

@ -8,3 +8,9 @@ build
rosetta/locale/xx/LC_MESSAGES/*.mo
/.settings
/.project
testproject/coverage.xml
testproject/htmlcov/
testproject/rosetta.db
testproject/src/
testproject/.coverage
.venv_*

38
CHANGES
View file

@ -1,5 +1,39 @@
Version 0.7.3
-------------
* Fix for test settings leaking onto global settings: LANGUAGES was overridden and not set back (Issue #81 - Thanks @zsoldosp)
* Test against Django 1.6.1
* Missing context variable in catalog list (Issue #87 - Thanks @kunitoki)
* Added support for Yandex translation API (Issue #89 - Thanks @BlackWizard) See supported languages and limitations here: https://github.com/mbi/django-rosetta/pull/89
* Added support for the Azure translation API, replacing the BING API. (Issue #86, thanks @davidkuchar and @maikelwever)
* Removed support for the signed_cookies SESSION_ENGINE + SessionRosettaStorage in Django 1.6, because serialization of POFiles would fail
* Simplified the group membership test (Issue #90 - Thanks @dotsbb)
* Better serving of admin static files. (Issue #61, thanks @tback)
* Dropped Django 1.3 support
Version 0.7.2
-------------
* Fix for when settings imports unicode_literals for some reason (Issue #67)
* Fixed mess with app_id between pages (Issue #68, thanks @tsouvarev)
* Added Farsi translation. Thanks, @amiraliakbari
* Improved the permission system, allowing for more advanced permission mechanisms. Thanks, @codeinthehole and @tangentlabs
* Fixed the ordering of apps in the language selection screen. (Issue #73, thanks @tsouvarev, @kanu and everyone else involved in tracking this one down)
* Support for complex locale names. (Issue #71, Thanks @strycore)
* Configurable cache name (Issue #75, Thanks @Karmak23)
Version 0.7.1
-------------
* Fix: value missing in context
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)
* 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)
* List apps in alphabetical order
Version 0.6.8
-------------
@ -15,7 +49,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
-------------
@ -25,7 +59,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
-------------

View file

@ -1,6 +1,12 @@
include MANIFEST.in
include LICENSE
exclude *.pyc
exclude *.sh
recursive-include rosetta/locale *
recursive-include rosetta/tests *
recursive-include rosetta/utils *
recursive-include rosetta/templates *
prune testproject
prune rosetta/tests/__pycache__
prune rosetta/utils/__pycache__
prune rosetta/utils/microsofttranslator/__pycache__

View file

@ -20,7 +20,7 @@ Features
Requirements
************
Rosetta requires Django 1.3 or later (it should work with Django 1.1 and 1.2, but it is not supported.)
Rosetta requires Django 1.4 or newer. When running with Django 1.5, Python 3.x is supported.
************
Installation
@ -54,13 +54,16 @@ Rosetta can be configured via the following parameters, to be defined in your pr
* ``ROSETTA_MESSAGES_PER_PAGE``: Number of messages to display per page. Defaults to ``10``.
* ``ROSETTA_ENABLE_TRANSLATION_SUGGESTIONS``: Enable AJAX translation suggestions. Defaults to ``False``.
* ``BING_APP_ID``: Translation suggestions used to come from the Google Translation API service, but free service has been discontinued, and the next best thing is Microsoft `Bing's Translation API <http://msdn.microsoft.com/en-us/library/ff512404.aspx>`_. To use this service you must first `obtain an AppID key <https://ssl.bing.com/webmaster/Developers/AppIds/>`_, then specify the key here. Defaults to ``None``.
* ``YANDEX_TRANSLATE_KEY``: Translation suggestions from Yandex `Yandex.Translate API <http://api.yandex.com/translate/>`_. To use this service you must first `obtain an AppID key <https://translate.yandex.com/apikeys>`_, then specify the key here. Defaults to ``None``.
* ``AZURE_CLIENT_ID`` and ``AZURE_CLIENT_SECRET``: Translation suggestions using the Microsoft Azure API. To use this service, you must first `register for the service <https://datamarket.azure.com/dataset/5BA839F1-12CE-4CCE-BF57-A49D98D29A44>`_, then specify the 'Customer ID' and 'Primary Account Key' respectively, which you can find on your `account information page on Azure Marketplace <https://datamarket.azure.com/account?lang=en>`_.
* ``ROSETTA_MESSAGES_SOURCE_LANGUAGE_CODE`` and ``ROSETTA_MESSAGES_SOURCE_LANGUAGE_NAME``: Change these if the source language in your PO files isn't English. Default to ``'en'`` and ``'English'`` respectively.
* ``ROSETTA_WSGI_AUTO_RELOAD`` and ``ROSETTA_UWSGI_AUTO_RELOAD``: When running WSGI daemon mode, using ``mod_wsgi`` 2.0c5 or later, this setting controls whether the contents of the gettext catalog files should be automatically reloaded by the WSGI processes each time they are modified. For performance reasons, this setting should be disabled in production environments. Default to ``False``.
* ``ROSETTA_EXCLUDED_APPLICATIONS``: Exclude applications defined in this list from being translated. Defaults to ``()``.
* ``ROSETTA_REQUIRES_AUTH``: Require authentication for all Rosetta views. Defaults to ``True``.
* ``ROSETTA_POFILE_WRAP_WIDTH``: Sets the line-length of the edited PO file. Set this to ``0`` to mimic ``makemessage``'s ``--no-wrap`` option. Defaults to ``78``.
* ``ROSETTA_STORAGE_CLASS``: See the note below on Storages. Defaults to ``rosetta.storage.CacheRosettaStorage``
* ``ROSETTA_ACCESS_CONTROL_FUNCTION``: An alternative function that determines if a given user can access the translation views. This function receives a ``user`` as its argument, and returns a boolean specifying whether the passed user is allowed to use Rosetta or not.
* ``ROSETTA_CACHE_NAME``: When using ``rosetta.storage.CacheRosettaStorage``, you can store the rosetta data in a specific cache. This is particularly useful when your ``default`` cache is a ``django.core.cache.backends.dummy.DummyCache`` (which happens on pre-production environments). If unset, it will default to ``rosetta`` if a cache with this name exists, or ``default`` if not.
* ``ROSETTA_POFILENAMES``: Defines which po filenames are exposed in the web interface. Defaults to ``('django.po', 'djangojs.po')``
********
@ -71,10 +74,12 @@ To prevent re-reading and parsing the PO file catalogs over and over again, Rose
Django 1.4 has introduced a signed cookie session backend, which stores the whole content of the session in an encrypted cookie. Unfortunately this doesn't work with large PO files, as the limit of 4096 chars that can be stored in a cookie are easily exceeded.
In this case the Cache-based backend should be used (by setting ``ROSETTA_STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'``). Please make sure that a proper CACHES backend is configured in your Django settings.
In this case the Cache-based backend should be used (by setting ``ROSETTA_STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'``). Please make sure that a proper ``CACHES`` backend is configured in your Django settings if your Django app is being served in a multi-process environment, or the different server processes, serving subsequent requests, won't find the storage data left by previous requests.
Alternatively you can switch back to using the Session based storage by setting ``ROSETTA_STORAGE_CLASS = 'rosetta.storage.SessionRosettaStorage`` in your settings. This is perfectly safe on Django 1.3. On Django 1.4 or higher make sure you have DON'T use the `signed_cookies <https://docs.djangoproject.com/en/dev/topics/http/sessions/#using-cookie-based-sessions>`_ ``SESSION_BACKEND`` with this Rosetta storage backend or funky things might happen.
**TL;DR**: if you run Django with gunincorn, mod-wsgi or other multi-process environment, the Django-default ``CACHES`` ``LocMemCache`` backend won't suffice: use memcache instead, or you will run into issues.
********
Security
********
@ -93,7 +98,7 @@ Usage
Generate a batch of files to translate
--------------------------------------
See `Django's documentation on Internationalization <http://www.djangoproject.com/documentation/i18n/>`_ to setup your project to use i18n and create the ``gettext`` catalog files.
See `Django's documentation on Internationalization <https://docs.djangoproject.com/en/1.5/topics/i18n/translation/>`_ to setup your project to use i18n and create the ``gettext`` catalog files.
Translate away!
---------------
@ -129,5 +134,5 @@ By default Rosetta hides its own catalog files in the file selection interface (
Acknowledgments
***************
* Rosetta uses the excellent `polib <http://code.google.com/p/polib/>`_ library to parse and handle gettext files.
* Rosetta uses the excellent `polib <https://bitbucket.org/izi/polib>`_ library to parse and handle gettext files.

View file

@ -1,4 +1,4 @@
VERSION = (0, 6, 8)
VERSION = (0, 7, 3)
def get_version(svn=False, limit=3):

31
rosetta/access.py Normal file
View file

@ -0,0 +1,31 @@
from django.conf import settings
from django.utils import importlib
def can_translate(user):
return get_access_control_function()(user)
def get_access_control_function():
"""
Return a predicate for determining if a user can access the Rosetta views
"""
fn_path = getattr(settings, 'ROSETTA_ACCESS_CONTROL_FUNCTION', None)
if fn_path is None:
return is_superuser_staff_or_in_translators_group
# Dynamically load a permissions function
perm_module, perm_func = fn_path.rsplit('.', 1)
perm_module = importlib.import_module(perm_module)
return getattr(perm_module, perm_func)
# Default access control test
def is_superuser_staff_or_in_translators_group(user):
if not getattr(settings, 'ROSETTA_REQUIRES_AUTH', True):
return True
if not user.is_authenticated():
return False
elif user.is_superuser and user.is_staff:
return True
else:
return user.groups.filter(name='translators').exists()

View file

@ -6,8 +6,14 @@ MESSAGES_PER_PAGE = getattr(settings, 'ROSETTA_MESSAGES_PER_PAGE', 10)
# Enable Google translation suggestions
ENABLE_TRANSLATION_SUGGESTIONS = getattr(settings, 'ROSETTA_ENABLE_TRANSLATION_SUGGESTIONS', False)
# Can be obtained for free here: https://translate.yandex.com/apikeys
YANDEX_TRANSLATE_KEY = getattr(settings, 'YANDEX_TRANSLATE_KEY', None)
# Can be obtained for free here: https://ssl.bing.com/webmaster/Developers/AppIds/
BING_APP_ID = getattr(settings, 'BING_APP_ID', None)
AZURE_CLIENT_ID = getattr(settings, 'AZURE_CLIENT_ID', None)
AZURE_CLIENT_SECRET = getattr(settings, 'AZURE_CLIENT_SECRET', None)
# Displays this language beside the original MSGID in the admin
MAIN_LANGUAGE = getattr(settings, 'ROSETTA_MAIN_LANGUAGE', None)
@ -16,6 +22,9 @@ MAIN_LANGUAGE = getattr(settings, 'ROSETTA_MAIN_LANGUAGE', None)
MESSAGES_SOURCE_LANGUAGE_CODE = getattr(settings, 'ROSETTA_MESSAGES_SOURCE_LANGUAGE_CODE', 'en')
MESSAGES_SOURCE_LANGUAGE_NAME = getattr(settings, 'ROSETTA_MESSAGES_SOURCE_LANGUAGE_NAME', 'English')
ACCESS_CONTROL_FUNCTION = getattr(
settings, 'ROSETTA_ACCESS_CONTROL_FUNCTION', None)
"""
When running WSGI daemon mode, using mod_wsgi 2.0c5 or later, this setting
@ -52,5 +61,14 @@ POFILE_WRAP_WIDTH = getattr(settings, 'ROSETTA_POFILE_WRAP_WIDTH', 78)
# Storage class to handle temporary data storage
STORAGE_CLASS = getattr(settings, 'ROSETTA_STORAGE_CLASS', 'rosetta.storage.CacheRosettaStorage')
# Allow overriding of the default filenames, you mostly won't need to change this
POFILENAMES = getattr(settings, 'ROSETTA_POFILENAMES', ('django.po', 'djangojs.po'))
ROSETTA_CACHE_NAME = getattr(settings, 'ROSETTA_CACHE_NAME', 'default'
if settings.CACHES.get('rosetta', None) is None
else 'rosetta')
# Require users to be authenticated (and Superusers or in group "translators").
# Set this to False at your own risk
ROSETTA_REQUIRES_AUTH = getattr(settings, 'ROSETTA_REQUIRES_AUTH', True)

Binary file not shown.

View file

@ -0,0 +1,206 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-04-01 18:36+0430\n"
"PO-Revision-Date: 2013-04-01 19:03+0430\n"
"Last-Translator: <admin@arsh.co>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=1; plural=0\n"
"X-Translated-Using: django-rosetta 0.7.1\n"
#: templates/rosetta/languages.html:5 templates/rosetta/languages.html.py:8
msgid "Language selection"
msgstr "انتخاب زبان"
#: templates/rosetta/languages.html:8 templates/rosetta/pofile.html:22
msgid "Home"
msgstr "خانه"
#: templates/rosetta/languages.html:9
msgid ""
"Couldn't load the specified language file. This usually happens when using "
"the Encrypted Cookies Session Storage backend on Django 1.4 or "
"higher.<br/>Setting ROSETTA_STORAGE_CLASS = "
"'rosetta.storage.CacheRosettaStorage' in your settings file should fix this."
msgstr ""
"بارگزاری کاتالوگ زبان مورد نظر ممکن نبود. این مشکل معمولا به خاطر استفاده از"
" Encrypted Cookies Session Storage backend در جنگو ۱.۴ یا بالاتر رخ "
"می‌دهد.<br/>با افزودن خط زیر به فایل تنظیمات پروژه معمولا این مشکل را حل "
"می‌کند:<br/>ROSETTA_STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'"
#: templates/rosetta/languages.html:15
msgid "Filter"
msgstr "فیلتر"
#: templates/rosetta/languages.html:16
msgid "Project"
msgstr "پروژه"
#: templates/rosetta/languages.html:17
msgid "Third party"
msgstr "شخص ثالث"
#: templates/rosetta/languages.html:19 templates/rosetta/pofile.html:39
msgid "All"
msgstr "همه"
#: templates/rosetta/languages.html:32
msgid "Application"
msgstr "کارکرد"
#: templates/rosetta/languages.html:33
msgid "Progress"
msgstr "پیشرفت"
#: templates/rosetta/languages.html:34
msgid "Messages"
msgstr "پیام‌ها"
#: templates/rosetta/languages.html:35
msgid "Translated"
msgstr "ترجمه شده"
#: templates/rosetta/languages.html:36 templates/rosetta/pofile.html:71
msgid "Fuzzy"
msgstr "نیاز به بازبینی"
#: templates/rosetta/languages.html:37
msgid "Obsolete"
msgstr "کهنه"
#: templates/rosetta/languages.html:38
msgid "File"
msgstr "پرونده"
#: templates/rosetta/languages.html:61
msgid "Nothing to translate!"
msgstr "موردی برای ترجمه وجود ندارد!"
#: templates/rosetta/languages.html:62
msgid ""
"You haven't specified any languages in your settings file, or haven't yet "
"generated a batch of translation catalogs."
msgstr ""
"شما زبانی را در فایل تنظیمات تعیین نکردید، یا هنوز کاتالوگ‌های ترجمه را "
"ایجاد نکردید."
#: templates/rosetta/languages.html:63
#, python-format
msgid ""
"Please refer to <a href=\"%(i18n_doc_link)s\">Django's I18N "
"documentation</a> for a guide on how to set up internationalization for your"
" project."
msgstr ""
"لطفا به <a href=\"%(i18n_doc_link)s\">مستندات جهانی‌سازی جنگو "
"documentation</a> برای راهنما در مورد روش راه‌اندازی امکانات جهانی‌سازی برای"
" پروژه‌تان مراجعه کنید."
#: templates/rosetta/pofile.html:9
msgid "Pick another file"
msgstr "انتخاب یک فایل دیگر"
#: templates/rosetta/pofile.html:10
msgid "Download this catalog"
msgstr "دانلود این کاتالوگ"
#: templates/rosetta/pofile.html:25
#, python-format
msgid "Progress: %(percent_translated)s%%"
msgstr "درصد پیشرفت: %(percent_translated)s%%"
#: templates/rosetta/pofile.html:27
msgid "File is read-only: download the file when done editing!"
msgstr ""
"فایل ترجمه غیر قابل نوشتن است، پس از اتمام ترجمه فایل حاصل را دانلود نمایید!"
#: templates/rosetta/pofile.html:28
msgid ""
"Some items in your last translation block couldn't be saved: this usually "
"happens when the catalog file changes on disk after you last loaded it."
msgstr ""
"برخی موارد در آخرین دسته‌ی ترجمه قابل ذخیره نبود: این مشکل معمولا زمانی رخ "
"می‌دهد که فایل کاتالوگ روی دیسک پس از آخرین دفعه‌ی بارگزاری آن، تغییر کرده "
"باشد."
#: templates/rosetta/pofile.html:32
#, python-format
msgid "Translate into %(rosetta_i18n_lang_name)s"
msgstr "در حال ترجمه به %(rosetta_i18n_lang_name)s"
#: templates/rosetta/pofile.html:35
msgid "Display:"
msgstr "نمایش:"
#: templates/rosetta/pofile.html:36
msgid "Untranslated only"
msgstr "فقط ترجمه نشده‌ها"
#: templates/rosetta/pofile.html:37
msgid "Translated only"
msgstr "فقط ترجمه شده‌ها"
#: templates/rosetta/pofile.html:38
msgid "Fuzzy only"
msgstr "فقط موارد برای بازبینی"
#: templates/rosetta/pofile.html:46
msgid "Search"
msgstr "جستجو"
#: templates/rosetta/pofile.html:48
msgid "Go"
msgstr "برو"
#: templates/rosetta/pofile.html:68
msgid "Original"
msgstr "اصلی"
#: templates/rosetta/pofile.html:72
msgid "Occurrences(s)"
msgstr "موارد رخداد"
#: templates/rosetta/pofile.html:86 templates/rosetta/pofile.html.py:89
#: templates/rosetta/pofile.html:104 templates/rosetta/pofile.html.py:107
msgid "Context hint"
msgstr "راهنمای محلی"
#: templates/rosetta/pofile.html:114
msgid "suggest"
msgstr "پیشنهاد"
#: templates/rosetta/pofile.html:125
#, python-format
msgid "%(more_count)s more"
msgid_plural "%(more_count)s more"
msgstr[0] "%(more_count)s مورد بیش‌تر"
msgstr[1] "%(more_count)s مورد بیش‌تر"
#: templates/rosetta/pofile.html:137
msgid "Save and translate next block"
msgstr "ذخیره و ترجمه‌ی دسته‌ی بعدی"
#: templates/rosetta/pofile.html:141
msgid "Skip to page:"
msgstr "پرش به صفحه:"
#: templates/rosetta/pofile.html:154
msgid "Displaying:"
msgstr "در حال نمایش:"
#: templates/rosetta/pofile.html:158
#, python-format
msgid "%(hits)s/%(message_number)s message"
msgid_plural "%(hits)s/%(message_number)s messages"
msgstr[0] "%(hits)s از %(message_number)s پیام"
msgstr[1] "%(hits)s از %(message_number)s پیام"

View file

@ -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 <izimobil@gmail.com>'
__version__ = '0.7.0'
__all__ = ['pofile', 'POFile', 'POEntry', 'mofile', 'MOFile', 'MOEntry',
'detect_encoding', 'escape', 'unescape', 'detect_encoding',]
__author__ = 'David Jean Louis <izimobil@gmail.com>'
__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('<h', 1):
@ -523,13 +544,19 @@ class _BaseFile(list):
output = struct.pack(
"Iiiiiii",
magic_number, # Magic number
0, # Version
entries_len, # # of entries
7*4, # start of key index
7*4+entries_len*8, # start of value index
0, keystart # size and offset of hash table
# Important: we don't use hash tables
# Magic number
magic_number,
# Version
0,
# number of entries
entries_len,
# start of key index
7 * 4,
# start of value index
7 * 4 + entries_len * 8,
# size and offset of hash table, we don't use hash tables
0, keystart
)
if PY3 and sys.version_info.minor > 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 "<string>")
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):
@ -1010,6 +1049,9 @@ class POEntry(_BaseEntry):
def __ne__(self, other):
return self.__cmp__(other) != 0
def __hash__(self):
return hash((self.msgid, self.msgstr))
def translated(self):
"""
Returns ``True`` if the entry has been translated or ``False``
@ -1050,19 +1092,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 +1138,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 +1184,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 +1219,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 +1234,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 +1296,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 +1314,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 +1378,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 +1396,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 +1423,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 +1503,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 +1550,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 +1596,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 +1615,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 +1632,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 +1656,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 +1730,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 +1739,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 +1751,4 @@ def wrap(text, width=70, **kwargs):
return TextWrapper(width=width, **kwargs).wrap(text)
return textwrap.wrap(text, width=width, **kwargs)
#}}}
# }}}

View file

@ -1,14 +1,39 @@
import os
import django
from datetime import datetime
from django.conf import settings
from django.core.cache import get_cache
from rosetta.conf import settings as rosetta_settings
from django.core.cache import cache
import django
import os
try:
from django.utils import timezone
except:
timezone = None
try:
set
except NameError:
from sets import Set as set # Python 2.3 fallback
cache = get_cache(rosetta_settings.ROSETTA_CACHE_NAME)
def timestamp_with_timezone(dt=None):
"""
Return a timestamp with a timezone for the configured locale. If all else
fails, consider localtime to be UTC.
"""
dt = dt or datetime.now()
if timezone is None:
return dt.strftime('%Y-%m-%d %H:%M%z')
if not dt.tzinfo:
tz = timezone.get_current_timezone()
if not tz:
tz = timezone.utc
dt = dt.replace(tzinfo=timezone.get_current_timezone())
return dt.strftime("%Y-%m-%d %H:%M%z")
def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False):
"""
@ -24,7 +49,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:
@ -48,13 +76,12 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False)
continue
p = appname.rfind('.')
if p >= 0:
app = getattr(__import__(appname[:p], {}, {}, [appname[p + 1:]]), appname[p + 1:])
app = getattr(__import__(appname[:p], {}, {}, [str(appname[p + 1:])]), appname[p + 1:])
else:
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:
continue
@ -71,19 +98,17 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False)
if os.path.isdir(apppath):
paths.append(apppath)
ret = set()
langs = (lang,)
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')
@ -91,21 +116,29 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False)
filename = os.path.join(dirname, fn)
if os.path.isfile(filename):
ret.add(os.path.abspath(filename))
return list(ret)
return list(sorted(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 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 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 last - 1 > first:
r.append(last - 1)
r.append(last)
r = list(set(r))

View file

@ -1,7 +1,15 @@
from django.core.cache import cache
from django.core.cache import get_cache
from django.conf import settings
from django.utils import importlib
from django.core.exceptions import ImproperlyConfigured
from rosetta.conf import settings as rosetta_settings
import hashlib
import time
import six
import django
cache = get_cache(rosetta_settings.ROSETTA_CACHE_NAME)
class BaseRosettaStorage(object):
@ -36,6 +44,12 @@ class DummyRosettaStorage(BaseRosettaStorage):
class SessionRosettaStorage(BaseRosettaStorage):
def __init__(self, request):
super(SessionRosettaStorage, self).__init__(request)
if 'signed_cookies' in settings.SESSION_ENGINE and django.VERSION[1] >= 6 and 'pickle' not in settings.SESSION_SERIALIZER.lower():
raise ImproperlyConfigured("Sorry, but django-rosetta doesn't support the `signed_cookies` SESSION_ENGINE in Django >= 1.6, because rosetta specific session files cannot be serialized.")
def get(self, key, default=None):
if key in self.request.session:
return self.request.session[key]
@ -56,19 +70,35 @@ class CacheRosettaStorage(BaseRosettaStorage):
# so we need to per-user key prefix, which we store in the session
def __init__(self, request):
super(CacheRosettaStorage, self).__init__(request)
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:
raise ImproperlyConfigured("You can't use the CacheRosettaStorage because your Django Session storage doesn't seem to be working. The CacheRosettaStorage relies on the Django Session storage to avoid conflicts.")
# Make sure we're not using DummyCache
if 'dummycache' in settings.CACHES[rosetta_settings.ROSETTA_CACHE_NAME]['BACKEND'].lower():
raise ImproperlyConfigured("You can't use the CacheRosettaStorage if your cache isn't correctly set up (you are use the DummyCache cache backend).")
# Make sure the actually actually works
try:
self.set('rosetta_cache_test', 'rosetta')
if not self.get('rosetta_cache_test') == 'rosetta':
raise ImproperlyConfigured("You can't use the CacheRosettaStorage if your cache isn't correctly set up, please double check your Django DATABASES setting and that the cache server is responding.")
finally:
self.delete('rosetta_cache_test')
def get(self, key, default=None):
#print ('get', self._key_prefix + key)
return cache.get(self._key_prefix + key, default)
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)

View file

@ -1,20 +1,20 @@
<!DOCTYPE html>
<!DOCTYPE html>{% load url from future %}{% load admin_static %}
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>{% block pagetitle %}Rosetta{% endblock %}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="{{ADMIN_MEDIA_PREFIX}}css/base.css" type="text/css"/>
<link rel="stylesheet" href="{{ADMIN_MEDIA_PREFIX}}css/forms.css" type="text/css"/>
<link rel="stylesheet" href="{{ADMIN_MEDIA_PREFIX}}css/changelists.css" type="text/css"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="{% static "admin/css/base.css" %}" type="text/css"/>
<link rel="stylesheet" href="{% static "admin/css/forms.css" %}" type="text/css"/>
<link rel="stylesheet" href="{% static "admin/css/changelists.css" %}" type="text/css"/>
<style type="text/css" media="screen">
{% include 'rosetta/css/rosetta.css' %}
</style>
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
//<!--
google.load("jquery", "1.3");
{% if ENABLE_TRANSLATION_SUGGESTIONS %}google.load("language", "1");{% endif %}
{% if rosetta_settings.ENABLE_TRANSLATION_SUGGESTIONS %}google.load("language", "1");{% endif %}
{% include 'rosetta/js/rosetta.js' %}
//-->
</script>
@ -24,7 +24,7 @@
<div id="header">
{% block header %}
<div id="branding">
<h1 id="site-name"><a href="{% url rosetta-pick-file %}">Rosetta</a> </h1>
<h1 id="site-name"><a href="{% url 'rosetta-pick-file' %}">Rosetta</a> </h1>
</div>
{% endblock %}
</div>

View file

@ -7,39 +7,72 @@ google.setOnLoadCallback(function() {
$('.hide', $(this).parent()).hide();
});
{% if ENABLE_TRANSLATION_SUGGESTIONS and BING_APP_ID %}
{% if rosetta_settings.ENABLE_TRANSLATION_SUGGESTIONS and rosetta_settings.AZURE_CLIENT_ID and rosetta_settings.AZURE_CLIENT_SECRET %}
$('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 apiUrl = "http://api.microsofttranslator.com/V2/Ajax.svc/Translate";
orig = unescape(orig).replace(/<br\s?\/?>/g,'\n').replace(/<code>/,'').replace(/<\/code>/g,'').replace(/&gt;/g,'>').replace(/&lt;/g,'<');
a.attr('class','suggesting').html('...');
window.onTranslationComplete = function(resp) {
trans.val(unescape(resp).replace(/&#39;/g,'\'').replace(/&quot;/g,'"').replace(/%\s+(\([^\)]+\))\s*s/g,' %$1s '));
a.hide();
};
window.onTranslationError = function(response){
a.text(response);
};
$.getJSON("/rosetta/translate/", {
from: sourceLang,
to: destLang,
text: orig
},
function(data) {
if (data.success){
trans.val(unescape(data.translation).replace(/&#39;/g,'\'').replace(/&quot;/g,'"').replace(/%\s+(\([^\)]+\))\s*s/g,' %$1s '));
a.hide();
} else {
a.text(data.error);
}
}
);
});
{% endif %}
{% if rosetta_settings.ENABLE_TRANSLATION_SUGGESTIONS and rosetta_settings.YANDEX_TRANSLATE_KEY %}
$('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 apiUrl = "https://translate.yandex.net/api/v1.5/tr.json/translate";
a.attr('class','suggesting').html('...');
var apiData = {
onerror: 'onTranslationError',
appid: app_id,
from: sourceLang,
to: destLang,
oncomplete: "onTranslationComplete",
error: 'onTranslationError',
success: 'onTranslationComplete',
lang: '{{ rosetta_settings.MESSAGES_SOURCE_LANGUAGE_CODE }}-{{ rosetta_i18n_lang_code }}',
key: '{{ rosetta_settings.YANDEX_TRANSLATE_KEY }}',
format: 'html',
text: orig
};
$.ajax({
url: apiUrl,
data: apiData,
dataType: 'jsonp'});
dataType: 'jsonp',
success: function(response) {
if (response.code == 200) {
trans.val(response.text[0]);
a.hide();
} else {
a.text(response);
}
},
error: function(response) {
a.text(response);
}
});
});
{% endif %}
@ -70,7 +103,7 @@ google.setOnLoadCallback(function() {
} else {
if (!(origs === null && trads === null)) {
$(this).before(error);
return false;
return false;
}
}
return true;

View file

@ -1,10 +1,11 @@
{% extends "rosetta/base.html" %}
{% load i18n %}
{% load url from future %}
{% block pagetitle %}{{block.super}} - {% trans "Language selection" %}{% endblock %}
{% block breadcumbs %}
<div><a href="{% url rosetta-pick-file %}">{% trans "Home" %}</a> &rsaquo; {% trans "Language selection" %}</div>
<div><a href="{% url 'rosetta-pick-file' %}">{% trans "Home" %}</a> &rsaquo; {% trans "Language selection" %}</div>
{% if do_session_warn %}<p class="errornote session-warn">{% trans "Couldn't load the specified language file. This usually happens when using the Encrypted Cookies Session Storage backend on Django 1.4 or higher.<br/>Setting ROSETTA_STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage' in your settings file should fix this." %}</p>{% endif %}
{% endblock %}
@ -22,7 +23,7 @@
{% for lid,language,pos in languages %}
{% if pos %}
<div class="module">
<h2>{{language}}</h2>
<table cellspacing="0">
@ -40,7 +41,7 @@
<tbody>
{% for app,path,po in pos %}
<tr class="{% cycle row1,row2 %}">
<td><a href="{% url rosetta-language-selection lid,forloop.counter0 %}{% if do_django %}?django{% endif %}{% if do_rosetta %}?rosetta{% endif %}">{{ app|title }}</a></td>
<td><a href="{% url 'rosetta-language-selection' lid forloop.counter0 %}{% if do_django %}?django{% endif %}{% if do_rosetta %}?rosetta{% endif %}">{{ app|title }}</a></td>
<td class="ch-progress r">{{po.percent_translated|floatformat:2}}%</td>
{% with po.untranslated_entries|length as len_untranslated_entries %}
<td class="ch-messages r">{{po.translated_entries|length|add:len_untranslated_entries}}</td>
@ -55,7 +56,7 @@
</table>
</div>
{% endif %}
{% endfor %}
{% endfor %}
{% else %}
<h1>{% trans "Nothing to translate!" %}</h1>
<p>{% trans "You haven't specified any languages in your settings file, or haven't yet generated a batch of translation catalogs." %}</p>

View file

@ -1,26 +1,27 @@
{% extends "rosetta/base.html" %}
{% extends "rosetta/base.html" %}{% load admin_static %}
{% load rosetta i18n %}
{% load url from future %}
{% block header %}
{{block.super}}
<div id="user-tools">
<p>
<span><a href="{% url rosetta-pick-file %}">{% trans "Pick another file" %}</a> /
<a href="{% url rosetta-download-file %}">{% trans "Download this catalog" %}</a></span>
<span><a href="{% url 'rosetta-pick-file' %}">{% trans "Pick another file" %}</a> /
<a href="{% url 'rosetta-download-file' %}">{% trans "Download this catalog" %}</a></span>
</p>
</div>
<script type="text/javascript">
</script>
{% 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 %}
<div>
<a href="{% url rosetta-pick-file %}">{% trans "Home" %}</a> &rsaquo;
{{ rosetta_i18n_lang_name }} &rsaquo;
{{ rosetta_i18n_app|title }} &rsaquo;
<a href="{% url 'rosetta-pick-file' %}">{% trans "Home" %}</a> &rsaquo;
{{ rosetta_i18n_lang_name }} &rsaquo;
{{ rosetta_i18n_app|title }} &rsaquo;
{% blocktrans with rosetta_i18n_pofile.percent_translated|floatformat:2 as percent_translated %}Progress: {{ percent_translated }}%{% endblocktrans %}
</div>
{% if not rosetta_i18n_write %}<p class="errornote read-only">{% trans "File is read-only: download the file when done editing!" %}</p>{% endif %}
@ -29,35 +30,26 @@
{% block main %}
<h1>{% blocktrans %}Translate into {{rosetta_i18n_lang_name}}{% endblocktrans %}</h1>
<ul class="object-tools">
<li class="nobubble">{% trans "Display:" %}</li>
<li {% ifequal rosetta_i18n_filter 'untranslated' %}class="active"{% endifequal %}><a href="?filter=untranslated">{% trans "Untranslated only" %}</a></li>
<li {% ifequal rosetta_i18n_filter 'translated' %}class="active"{% endifequal %}><a href="?filter=translated">{% trans "Translated only" %}</a></li>
<li {% ifequal rosetta_i18n_filter 'fuzzy' %}class="active"{% endifequal %}><a href="?filter=fuzzy">{% trans "Fuzzy only" %}</a></li>
<li {% ifequal rosetta_i18n_filter 'all' %}class="active"{% endifequal %}><a href="?filter=all">{% trans "All" %}</a></li>
<li {% if rosetta_i18n_filter == 'untranslated' %}class="active"{% endif %}><a href="?filter=untranslated">{% trans "Untranslated only" %}</a></li>
<li {% if rosetta_i18n_filter == 'translated' %}class="active"{% endif %}><a href="?filter=translated">{% trans "Translated only" %}</a></li>
<li {% if rosetta_i18n_filter == 'fuzzy' %}class="active"{% endif %}><a href="?filter=fuzzy">{% trans "Fuzzy only" %}</a></li>
<li {% if rosetta_i18n_filter == 'all' %}class="active"{% endif %}><a href="?filter=all">{% trans "All" %}</a></li>
</ul>
<div id="changelist" class="module{% if rosetta_i18n_lang_bidi %} rtl{% endif %}">
<div id="toolbar">
<form id="changelist-search" action="" method="post">
<div><!-- DIV needed for valid HTML -->
{% rosetta_csrf_token %}
<label for="searchbar"><img src="{{ADMIN_IMAGE_DIR}}/icon_searchbox.png" alt="{% trans "Search" %}" /></label>
<input type="text" size="40" name="query" value="{% if query %}{{query}}{% endif %}" id="searchbar" tabindex="0" />
<input type="submit" name="search" value="{% trans "Go" %}" />
</div>
</form>
{% comment %}
{% if ENABLE_TRANSLATION_SUGGESTIONS %}
<form id="translate-all">
<div>
<input type="submit" class="googleall" value="{% trans "Suggest All Translations" %}" />
</div>
</form>
{% endif %}
{% endcomment %}
<form id="changelist-search" action="" method="post">
<div>
{% rosetta_csrf_token %}
<label for="searchbar"><img src="{% static "admin/img/icon_searchbox.png" %}" alt="{% trans "Search" %}" /></label>
<input type="text" size="40" name="query" value="{% if query %}{{query}}{% endif %}" id="searchbar" tabindex="0" />
<input type="submit" name="search" value="{% trans "Go" %}" />
</div>
</form>
</div>
<form method="post" action="">
<table>
<thead>
@ -79,7 +71,7 @@
<span class="part">{{message.msgid|format_message|linebreaksbr}}</span>
<span class="part">{{message.msgid_plural|format_message|linebreaksbr}}</span>
</div>
{% if message.msgctxt %}
<span class="context">{% trans "Context hint" %}: {{message.msgctxt|safe}}</span>
{% else %}
@ -87,7 +79,7 @@
<span class="context">{% trans "Context hint" %}: {{message.comment|safe}}</span>
{% endif %}
{% endif %}
</td>
<td class="translation">
{% for k, msgstr in message.msgstr_plural.items|dictsort:"0" %}
@ -103,13 +95,13 @@
{% else %}
{% if message.comment %}
<span class="context">{% trans "Context hint" %}: {{message.comment|safe}}</span>
{% endif %}
{% endif %}
{% endif %}
</td>
{% if main_language %}<td class="original">{{ message.main_lang|format_message|linebreaksbr }}</td>{% endif %}
<td class="translation">
<textarea rows="{{message.msgid|format_message|lines_count}}" cols="40" name="m_{{message.md5hash}}" tabindex="{% increment tab_idx %}">{{message.msgstr}}</textarea>
{% if ENABLE_TRANSLATION_SUGGESTIONS %}<a href="#" class="suggest">{% trans "suggest" %}</a>{% endif %}
{% if rosetta_settings.ENABLE_TRANSLATION_SUGGESTIONS %}<a href="#" class="suggest">{% trans "suggest" %}</a>{% endif %}
</td>
{% endif %}
<td class="c">
@ -117,7 +109,7 @@
</td>
<td class="location">
{% for fn,lineno in message.occurrences %}
<code{% if forloop.counter|gt:"3" %} class="hide"{% endif %}>{{ fn }}:{{lineno}}</code>
<code{% if forloop.counter|gt:"3" %} class="hide"{% endif %}>{{ fn }}:{{lineno}}</code>
{% endfor %}
{% if message.occurrences|length|gt:"3" %}
<a href="#">&hellip; ({% blocktrans count message.occurrences|length|minus:"3" as more_count %}{{more_count}} more{% plural %}{{more_count}} more{% endblocktrans %})</a>
@ -133,30 +125,30 @@
<input type="hidden" name="query" value="{{query}}" />
{% endif %}
<input type="submit" class="default" name="_next" value="{% trans "Save and translate next block" %}" tabindex="{% increment tab_idx %}"/>
{% if needs_pagination %}
{% trans "Skip to page:" %}
{% for i in page_range %}
{% ifequal i '...' %}
{% if i == '...' %}
<span class="space">{{ i }}</span>
{% else %}
{% ifequal i page %}
{% if i == page %}
<span class="this-page">{{i}}</span>
{% else %}
{% else %}
<a href="?page={{i}}{% if query %}&amp;query={{query}}{% endif %}">{{i}}</a>
{% endifequal %}
{% endifequal %}
{% endif %}
{% endif %}
{% endfor %}
{% else %}
{% trans "Displaying:" %}
{% endif %}
{% with paginator.object_list|length as hits %}
<strong>{% blocktrans count rosetta_i18n_pofile|length as message_number %}{{hits}}/{{message_number}} message{% plural %}{{hits}}/{{message_number}} messages{% endblocktrans %}</strong>
<strong>{% blocktrans count rosetta_i18n_pofile|length as message_number %}{{hits}}/{{message_number}} message{% plural %}{{hits}}/{{message_number}} messages{% endblocktrans %}</strong>
{% endwith %}
</p>
</div>
</form>

View file

@ -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
import six
register = template.Library()
rx = re.compile(r'(%(\([^\s\)]*\))?[sd])')
def format_message(message):
return mark_safe(rx.sub('<code>\\1</code>', escape(message).replace(r'\n','<br />\n')))
format_message=register.filter(format_message)
return mark_safe(rx.sub('<code>\\1</code>', escape(message).replace(r'\n', '<br />\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 six.text_type(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"<!-- csrf token placeholder -->")
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)

View file

@ -1,490 +1,2 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.template.defaultfilters import floatformat
from django.test import TestCase
from django.test.client import Client
from rosetta.conf import settings as rosetta_settings
from rosetta.signals import entry_changed, post_save
import os
import shutil
import django
try:
from django.dispatch import receiver
except ImportError:
# We might be in django < 1.3, so backport this function
def receiver(signal, **kwargs):
def _decorator(func):
signal.connect(func, **kwargs)
return func
return _decorator
class RosettaTestCase(TestCase):
urls = 'rosetta.tests.urls'
def __init__(self, *args, **kwargs):
super(RosettaTestCase, self).__init__(*args, **kwargs)
self.curdir = os.path.dirname(__file__)
self.dest_file = os.path.normpath(os.path.join(self.curdir, '../locale/xx/LC_MESSAGES/django.po'))
self.django_version_major, self.django_version_minor = django.VERSION[0], django.VERSION[1]
def setUp(self):
user = User.objects.create_user('test_admin', 'test@test.com', 'test_password')
user2 = User.objects.create_user('test_admin2', 'test@test2.com', 'test_password')
user3 = User.objects.create_user('test_admin3', 'test@test2.com', 'test_password')
user.is_superuser, user2.is_superuser, user3.is_superuser = True, True, True
user.is_staff, user2.is_staff, user3.is_staff = True, True, False
user.save()
user2.save()
user3.save()
self.client2 = Client()
self.client.login(username='test_admin', password='test_password')
self.client2.login(username='test_admin2', password='test_password')
settings.LANGUAGES = (('xx', 'dummy language'),)
shutil.copy(self.dest_file, self.dest_file + '.orig')
def tearDown(self):
shutil.move(self.dest_file + '.orig', self.dest_file)
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)
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)
def test_3_DownloadZIP(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'))
r = self.client.get(reverse('rosetta-download-file') + '?rosetta')
self.assertTrue('content-type' in r._headers.keys())
self.assertTrue('application/x-zip' in r._headers.get('content-type'))
def test_4_DoChanges(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
# Load the template file
r = 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') + '?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)
# post a translation
r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next'))
# reload all untranslated strings
r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict()) + '?rosetta')
r = self.client.get(reverse('rosetta-home') + '?filter=untranslated')
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)
# 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)
def test_5_TestIssue67(self):
# testcase for issue 67: http://code.google.com/p/django-rosetta/issues/detail?id=67
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue67.template')), self.dest_file)
# Make sure the plurals string is valid
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)
del(content)
# Load the template file
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') + '?filter=untranslated')
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)
# post a translation
r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next'))
# Make sure the plurals string is still valid
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)
del(content)
def test_6_ExcludedApps(self):
rosetta_settings.EXCLUDED_APPLICATIONS = ('rosetta',)
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)
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)
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.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)
def test_8_hideObsoletes(self):
r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
r = self.client.get(reverse('rosetta-pick-file'))
r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict()))
# 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)
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)
def test_9_concurrency(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
r = self.client2.get(reverse('rosetta-pick-file') + '?filter=third-party')
self.client.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict()))
self.client2.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict()))
# Load the template file
r = self.client.get(reverse('rosetta-home') + '?filter=untranslated')
r = self.client.get(reverse('rosetta-home'))
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 r2.content)
self.assertTrue('m_08e4e11e2243d764fc45a5a4fba5d0f2' in 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)
r = self.client.get(reverse('rosetta-home') + '?filter=untranslated')
r = self.client.get(reverse('rosetta-home'))
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)
# 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)
# 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)
# client 2 won
pofile_content = open(self.dest_file, 'r').read()
self.assertTrue('Hello, world, from client two!' in pofile_content)
# 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)
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)
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 r2.content)
self.assertTrue('save-conflict' not in r2.content)
self.assertTrue('save-conflict' not in 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('<td class="ch-messages r">1</td>' in r.content)
self.assertTrue('<td class="ch-progress r">%s%%</td>' % str(floatformat(0.0, 2)) in r.content)
self.assertTrue('<td class="ch-obsolete r">1</td>' in 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)
def test_12_issue_82_staff_user(self):
settings.ROSETTA_REQUIRES_AUTH = True
self.client3 = Client()
self.client3.login(username='test_admin3', password='test_password')
self.client3.get(reverse('rosetta-pick-file') + '?filter=third-party')
r = self.client3.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict()))
r = self.client3.get(reverse('rosetta-home'))
self.assertTrue(not r.content)
settings.ROSETTA_REQUIRES_AUTH = False
self.client3.get(reverse('rosetta-pick-file') + '?filter=third-party')
r = self.client3.get(reverse('rosetta-language-selection', args=('xx', 0,), kwargs=dict()))
r = self.client3.get(reverse('rosetta-home'))
self.assertFalse(not r.content)
def test_13_catalog_filters(self):
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.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)
if self.django_version_major >= 1 and self.django_version_minor >= 3:
self.assertTrue(('contrib') in 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)
if self.django_version_major >= 1 and self.django_version_minor >= 3:
self.assertTrue(('contrib') in 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)
if self.django_version_major >= 1 and self.django_version_minor >= 3:
self.assertTrue(('contrib') not in 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)
def test_14_issue_87_entry_changed_signal(self):
# copy the template file
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
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'))
@receiver(entry_changed)
def test_receiver(sender, **kwargs):
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)
# post a translation
r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next'))
self.assertTrue(self.test_old_msgstr == '')
self.assertTrue(self.test_new_msgstr == 'Hello, world')
self.assertTrue(self.test_msg_id == 'String 2')
del(self.test_old_msgstr, self.test_new_msgstr, self.test_msg_id)
def test_15_issue_101_post_save_signal(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
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'))
@receiver(post_save)
def test_receiver(sender, **kwargs):
self.test_sig_lang = kwargs.get('language_code')
self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content)
# post a translation
r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next'))
self.assertTrue(self.test_sig_lang == 'xx')
del(self.test_sig_lang)
def test_16_issue_103_post_save_signal_has_request(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
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'))
@receiver(post_save)
def test_receiver(sender, **kwargs):
self.test_16_has_request = 'request' in kwargs
self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content)
# post a translation
r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next'))
self.assertTrue(self.test_16_has_request)
del(self.test_16_has_request)
# reset the original file
def test_17_Test_Issue_gh24(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue24gh.template')), self.dest_file)
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)
# 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'))
pofile_content = open(self.dest_file, 'r').read()
self.assertTrue('"pede mollis pretium."' in pofile_content)
# Again, with unwrapped lines
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue24gh.template')), self.dest_file)
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)
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()
self.assertTrue('felis eu pede mollis pretium."' in pofile_content)
def test_18_Test_Issue_gh34(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue34gh.template')), self.dest_file)
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)
# post a translation, it should have properly wrapped lines
r = self.client.post(reverse('rosetta-home'), dict(
m_ff7060c1a9aae9c42af4d54ac8551f67_0='Foo %s',
m_ff7060c1a9aae9c42af4d54ac8551f67_1='Bar %s',
m_09f7e02f1290be211da707a266f153b3='Salut', _next='_next'))
pofile_content = open(self.dest_file, 'r').read()
self.assertTrue('msgstr "Salut\\n"' in pofile_content)
self.assertTrue('msgstr[0] ""\n"\\n"\n"Foo %s\\n"' in pofile_content)
self.assertTrue('msgstr[1] ""\n"\\n"\n"Bar %s\\n"' in pofile_content)
def test_19_Test_Issue_gh38(self):
if self.django_version_minor >= 4 and self.django_version_major >= 1:
self.assertTrue('django.contrib.sessions.middleware.SessionMiddleware' in settings.MIDDLEWARE_CLASSES)
settings.SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
# One: cache backend
rosetta_settings.STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue38gh.template')), self.dest_file)
self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
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)
r = self.client.post(reverse('rosetta-home'), dict(
m_9efd113f7919952523f06e0d88da9c54='Testing cookie length',
_next='_next'
))
pofile_content = open(self.dest_file, 'r').read()
self.assertTrue('Testing cookie length' in pofile_content)
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)
# Two, the cookie backend
rosetta_settings.STORAGE_CLASS = 'rosetta.storage.SessionRosettaStorage'
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue38gh.template')), self.dest_file)
self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
self.client.get(reverse('rosetta-language-selection', args=('xx', 0, ), kwargs=dict()))
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]
r = self.client.get(reverse('rosetta-home'))
self.assertFalse('m_9efd113f7919952523f06e0d88da9c54' in r.content)
def test_20_concurrency_of_cache_backend(self):
rosetta_settings.STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue38gh.template')), self.dest_file)
self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
self.client.get(reverse('rosetta-language-selection', args=('xx', 0, ), kwargs=dict()))
self.client2.get(reverse('rosetta-pick-file') + '?filter=third-party')
self.client2.get(reverse('rosetta-language-selection', args=('xx', 0, ), kwargs=dict()))
self.assertTrue(self.client.session.get('rosetta_cache_storage_key_prefix') != self.client2.session.get('rosetta_cache_storage_key_prefix'))
def test_21_Test_Issue_gh39(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue39gh.template')), self.dest_file)
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'))
# 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)
from .tests import *
from ..utils.microsofttranslator.test import *

601
rosetta/tests/tests.py Normal file
View file

@ -0,0 +1,601 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse, resolve
from django.core.exceptions import ImproperlyConfigured
from django.core.cache import cache
from django.template.defaultfilters import floatformat
from django.test import TestCase
from django.test.client import Client
from rosetta.conf import settings as rosetta_settings
from rosetta.signals import entry_changed, post_save
import os
import shutil
import six
import django
try:
from django.dispatch import receiver
except ImportError:
# We might be in django < 1.3, so backport this function
def receiver(signal, **kwargs):
def _decorator(func):
signal.connect(func, **kwargs)
return func
return _decorator
class RosettaTestCase(TestCase):
urls = 'rosetta.tests.urls'
def __init__(self, *args, **kwargs):
super(RosettaTestCase, self).__init__(*args, **kwargs)
self.curdir = os.path.dirname(__file__)
self.dest_file = os.path.normpath(os.path.join(self.curdir, '../locale/xx/LC_MESSAGES/django.po'))
self.django_version_major, self.django_version_minor = django.VERSION[0], django.VERSION[1]
def setUp(self):
user = User.objects.create_user('test_admin', 'test@test.com', 'test_password')
user2 = User.objects.create_user('test_admin2', 'test@test2.com', 'test_password')
user3 = User.objects.create_user('test_admin3', 'test@test2.com', 'test_password')
user.is_superuser, user2.is_superuser, user3.is_superuser = True, True, True
user.is_staff, user2.is_staff, user3.is_staff = True, True, False
user.save()
user2.save()
user3.save()
self.client2 = Client()
self.client.login(username='test_admin', password='test_password')
self.client2.login(username='test_admin2', password='test_password')
self.__old_settings_languages = settings.LANGUAGES
settings.LANGUAGES = (('xx', 'dummy language'), ('fr_FR.utf8', 'French (France), UTF8'))
self.__session_engine = settings.SESSION_ENGINE
self.__storage_class = rosetta_settings.STORAGE_CLASS
self.__require_auth = rosetta_settings.ROSETTA_REQUIRES_AUTH
shutil.copy(self.dest_file, self.dest_file + '.orig')
def tearDown(self):
settings.LANGUAGES = self.__old_settings_languages
settings.SESSION_ENGINE = self.__session_engine
rosetta_settings.STORAGE_CLASS = self.__storage_class
rosetta_settings.ROSETTA_REQUIRES_AUTH = self.__require_auth
shutil.move(self.dest_file + '.orig', self.dest_file)
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 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 str(r.content))
def test_3_DownloadZIP(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'))
r = self.client.get(reverse('rosetta-download-file') + '?rosetta')
self.assertTrue('content-type' in r._headers.keys())
self.assertTrue('application/x-zip' in r._headers.get('content-type'))
def test_4_DoChanges(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
# Load the template file
r = 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') + '?filter=untranslated')
r = self.client.get(reverse('rosetta-home'))
# make sure both strings are untranslated
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'))
# reload all untranslated strings
r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict()) + '?rosetta')
r = self.client.get(reverse('rosetta-home') + '?filter=untranslated')
r = self.client.get(reverse('rosetta-home'))
# the translated string no longer is up for translation
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 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
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue67.template')), self.dest_file)
# Make sure the plurals string is valid
f_ = open(self.dest_file, 'rb')
content = f_.read()
f_.close()
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
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') + '?filter=untranslated')
r = self.client.get(reverse('rosetta-home'))
# make sure all strings are untranslated
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'))
# Make sure the plurals string is still valid
f_ = open(self.dest_file, 'rb')
content = f_.read()
f_.close()
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):
rosetta_settings.EXCLUDED_APPLICATIONS = ('rosetta',)
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 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 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 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 str(r.content))
def test_8_hideObsoletes(self):
r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
r = self.client.get(reverse('rosetta-pick-file'))
r = self.client.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict()))
# not in listing
for p in range(1, 5):
r = self.client.get(reverse('rosetta-home') + '?page=%d' % p)
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 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)
r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
r = self.client2.get(reverse('rosetta-pick-file') + '?filter=third-party')
self.client.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict()))
self.client2.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict()))
# Load the template file
r = self.client.get(reverse('rosetta-home') + '?filter=untranslated')
r = self.client.get(reverse('rosetta-home'))
r2 = self.client2.get(reverse('rosetta-home') + '?filter=untranslated')
r2 = self.client2.get(reverse('rosetta-home'))
self.assertTrue('String 1' in str(r.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 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'))
r2 = self.client2.get(reverse('rosetta-home') + '?filter=untranslated')
r2 = self.client2.get(reverse('rosetta-home'))
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)
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)
# An error message is displayed
self.assertTrue('save-conflict' in str(r.content))
# client 2 won
pofile_content = open(self.dest_file, 'r').read()
self.assertTrue('Hello, world, from client two!' in pofile_content)
# Both clients show all strings, error messages are gone
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 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 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 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):
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('<td class="ch-messages r">1</td>' in str(r.content))
self.assertTrue('<td class="ch-progress r">%s%%</td>' % str(floatformat(0.0, 2)) in str(r.content))
self.assertTrue('<td class="ch-obsolete r">1</td>' 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 str(r.content))
def test_12_issue_82_staff_user(self):
settings.ROSETTA_REQUIRES_AUTH = True
self.client3 = Client()
self.client3.login(username='test_admin3', password='test_password')
self.client3.get(reverse('rosetta-pick-file') + '?filter=third-party')
r = self.client3.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict()))
r = self.client3.get(reverse('rosetta-home'))
self.assertTrue(not r.content)
settings.ROSETTA_REQUIRES_AUTH = False
self.client3.get(reverse('rosetta-pick-file') + '?filter=third-party')
r = self.client3.get(reverse('rosetta-language-selection', args=('xx', 0,), kwargs=dict()))
r = self.client3.get(reverse('rosetta-home'))
self.assertFalse(not r.content)
def test_13_catalog_filters(self):
settings.LANGUAGES = (('fr', 'French'), ('xx', 'Dummy Language'),)
cache.delete('rosetta_django_paths')
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 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 str(r.content))
if self.django_version_major >= 1 and self.django_version_minor >= 3:
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 str(r.content))
if self.django_version_major >= 1 and self.django_version_minor >= 3:
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 str(r.content))
if self.django_version_major >= 1 and self.django_version_minor >= 3:
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 str(r.content))
self.assertTrue('Context hint' in str(r.content))
def test_15_issue_87_entry_changed_signal(self):
# copy the template file
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
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'))
@receiver(entry_changed)
def test_receiver(sender, **kwargs):
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 str(r.content))
# post a translation
r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next'))
self.assertTrue(self.test_old_msgstr == '')
self.assertTrue(self.test_new_msgstr == 'Hello, world')
self.assertTrue(self.test_msg_id == 'String 2')
del(self.test_old_msgstr, self.test_new_msgstr, self.test_msg_id)
def test_16_issue_101_post_save_signal(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
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'))
@receiver(post_save)
def test_receiver(sender, **kwargs):
self.test_sig_lang = kwargs.get('language_code')
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'))
self.assertTrue(self.test_sig_lang == 'xx')
del(self.test_sig_lang)
def test_17_issue_103_post_save_signal_has_request(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
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'))
@receiver(post_save)
def test_receiver(sender, **kwargs):
self.test_16_has_request = 'request' in kwargs
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'))
self.assertTrue(self.test_16_has_request)
del(self.test_16_has_request)
# reset the original file
def test_18_Test_Issue_gh24(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue24gh.template')), self.dest_file)
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 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'))
pofile_content = open(self.dest_file, 'r').read()
self.assertTrue('"pede mollis pretium."' in pofile_content)
# Again, with unwrapped lines
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue24gh.template')), self.dest_file)
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 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()
self.assertTrue('felis eu pede mollis pretium."' in pofile_content)
def test_19_Test_Issue_gh34(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue34gh.template')), self.dest_file)
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 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(
m_ff7060c1a9aae9c42af4d54ac8551f67_0='Foo %s',
m_ff7060c1a9aae9c42af4d54ac8551f67_1='Bar %s',
m_09f7e02f1290be211da707a266f153b3='Salut', _next='_next'))
pofile_content = open(self.dest_file, 'r').read()
self.assertTrue('msgstr "Salut\\n"' in pofile_content)
self.assertTrue('msgstr[0] ""\n"\\n"\n"Foo %s\\n"' in pofile_content)
self.assertTrue('msgstr[1] ""\n"\\n"\n"Bar %s\\n"' in pofile_content)
def test_20_Test_Issue_gh38(self):
if self.django_version_minor >= 4 and self.django_version_major >= 1:
self.assertTrue('django.contrib.sessions.middleware.SessionMiddleware' in settings.MIDDLEWARE_CLASSES)
settings.SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
# One: cache backend
rosetta_settings.STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue38gh.template')), self.dest_file)
self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
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 str(r.content))
r = self.client.post(reverse('rosetta-home'), dict(
m_9efd113f7919952523f06e0d88da9c54='Testing cookie length',
_next='_next'
))
pofile_content = open(self.dest_file, 'r').read()
self.assertTrue('Testing cookie length' in pofile_content)
self.client.get(reverse('rosetta-home') + '?filter=translated')
r = self.client.get(reverse('rosetta-home'))
self.assertTrue('Testing cookie length' in str(r.content))
self.assertTrue('m_9f6c442c6d579707440ba9dada0fb373' in str(r.content))
# Two, the cookie backend
if self.django_version_minor < 6:
rosetta_settings.STORAGE_CLASS = 'rosetta.storage.SessionRosettaStorage'
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue38gh.template')), self.dest_file)
self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
self.client.get(reverse('rosetta-language-selection', args=('xx', 0, ), kwargs=dict()))
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'] = six.text_type(self.client.cookies.get('sessionid'))[:4096]
r = self.client.get(reverse('rosetta-home'))
self.assertFalse('m_9efd113f7919952523f06e0d88da9c54' in str(r.content))
def test_21_concurrency_of_cache_backend(self):
rosetta_settings.STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue38gh.template')), self.dest_file)
self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
self.client.get(reverse('rosetta-language-selection', args=('xx', 0, ), kwargs=dict()))
self.client2.get(reverse('rosetta-pick-file') + '?filter=third-party')
self.client2.get(reverse('rosetta-language-selection', args=('xx', 0, ), kwargs=dict()))
self.assertTrue(self.client.session.get('rosetta_cache_storage_key_prefix') != self.client2.session.get('rosetta_cache_storage_key_prefix'))
def test_22_Test_Issue_gh39(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.issue39gh.template')), self.dest_file)
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'))
# 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))
def test_23_save_header_data(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
unicode_user = User.objects.create_user('test_unicode', 'save_header_data@test.com', 'test_unicode')
unicode_user.first_name = "aéaéaé aàaàaàa"
unicode_user.last_name = "aâââ üüüü"
unicode_user.is_superuser, unicode_user.is_staff = True, True
unicode_user.save()
self.client.login(username='test_unicode', password='test_unicode')
# Load the template file
r = 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') + '?filter=untranslated')
r = self.client.get(reverse('rosetta-home'))
# make sure both strings are untranslated
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'))
# read the result
f_ = open(self.dest_file, 'rb')
content = six.text_type(f_.read())
f_.close()
#print (content)
# make sure unicode data was properly converted to ascii
self.assertTrue('Hello, world' in content)
self.assertTrue('save_header_data@test.com' in content)
self.assertTrue('aeaeae aaaaaaa aaaa uuuu' in content)
def test_24_percent_transaltion(self):
shutil.copy(os.path.normpath(os.path.join(self.curdir, './django.po.template')), self.dest_file)
# Load the template file
r = 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') + '?filter=untranslated')
r = self.client.get(reverse('rosetta-home'))
self.assertTrue('Progress: 0.00%' in str(r.content))
r = self.client.post(reverse('rosetta-home'), dict(m_e48f149a8b2e8baa81b816c0edf93890='Hello, world', _next='_next'))
r = self.client.get(reverse('rosetta-home'))
self.assertTrue('Progress: 25.00%' in str(r.content))
def test_25_replace_access_control(self):
# Test default access control allows access
url = reverse('rosetta-home')
response = self.client.get(url)
self.assertEqual(200, response.status_code)
# Now replace access control, and check we get redirected
settings.ROSETTA_ACCESS_CONTROL_FUNCTION = 'rosetta.tests.no_access'
response = self.client.get(url)
self.assertEqual(302, response.status_code)
# Restore setting to default
settings.ROSETTA_ACCESS_CONTROL_FUNCTION = None
def test_26_urlconf_accept_dots_and_underscores(self):
resolver_match = resolve("/rosetta/select/fr_FR.utf8/0/")
self.assertEqual(resolver_match.url_name, "rosetta-language-selection")
self.assertEqual(resolver_match.kwargs['langid'], 'fr_FR.utf8')
def test_27_extended_urlconf_language_code_loads_file(self):
r = self.client.get(reverse('rosetta-pick-file') + '?filter=all')
r = self.client.get(reverse('rosetta-language-selection', args=('fr_FR.utf8', 0), kwargs=dict()))
r = self.client.get(reverse('rosetta-home'))
self.assertTrue('French (France), UTF8' in str(r.content))
self.assertTrue('m_71a6479faf8712e37dd5755cd1d11804' in str(r.content))
def test_28_issue_gh87(self):
"make sure that rosetta_i18n_catalog_filter is passed into the context"
r = self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
r = self.client.get(reverse('rosetta-pick-file'))
self.assertTrue('<li class="active"><a href="?filter=third-party">' in str(r.content))
def test_29_unsupported_p3_django_16_storage(self):
if self.django_version_minor >= 6 and self.django_version_major >= 1:
self.assertTrue('django.contrib.sessions.middleware.SessionMiddleware' in settings.MIDDLEWARE_CLASSES)
settings.SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
rosetta_settings.STORAGE_CLASS = 'rosetta.storage.SessionRosettaStorage'
try:
self.client.get(reverse('rosetta-pick-file') + '?filter=third-party')
self.fail()
except ImproperlyConfigured:
pass
# Stubbed access control function
def no_access(user):
return False

View file

@ -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')

View file

@ -1,8 +1,13 @@
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'),
url(r'^pick/$', 'list_languages', name='rosetta-pick-file'),
url(r'^download/$', 'download_file', name='rosetta-download-file'),
url(r'^select/(?P<langid>[\w\-]+)/(?P<idx>\d+)/$', 'lang_sel', name='rosetta-language-selection'),
url(r'^select/(?P<langid>[\w\-_\.]+)/(?P<idx>\d+)/$', 'lang_sel', name='rosetta-language-selection'),
url(r'^translate/$', 'translate_text', name='translate_text'),
)

View file

View file

@ -0,0 +1,5 @@
language: python
python:
- "2.7"
install: python setup.py install
script: python setup.py test

View file

@ -0,0 +1,12 @@
Changelog
=========
Version 0.4
-----------
* Updated to use the Oauth based token issued by Bing
* This release is not backward compatibleas the class signature has changed
Version 0.3
-----------
* Encode text as UTF-8 before sending to translator
* Improved JSON Error handling

View file

@ -0,0 +1,32 @@
Copyright (c) 2011 by Openlabs Technologies & Consulting (P) Limited.
Some rights reserved.
Redistribution and use in source and binary forms of the software as well
as documentation, with or without modification, are permitted provided
that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

View file

@ -0,0 +1,2 @@
include LICENSE
include README.rst

View file

@ -0,0 +1,45 @@
Microsoft Translator V2 -- Python API
=====================================
:Version: 0.4
:Web: http://openlabs.co.in/
:keywords: Micrsoft Translator
:copyright: Openlabs Technologies & Consulting (P) LTD
.. image:: https://secure.travis-ci.org/openlabs/Microsoft-Translator-Python-API.png?branch=master
:target: http://travis-ci.org/#!/openlabs/Microsoft-Translator-Python-API
This python API implements the Microsoft Translator services which can be used
in web or client applications to perform language translation operations. The
services support users who are not familiar with the default language of a page
or application, or those desiring to communicate with people of a different
language group.
Example Usage:
::
>>> from microsofttranslator import Translator
>>> translator = Translator('<Your Client ID>', '<Your Client Secret>')
>>> print translator.translate("Hello", "pt")
"Olá"
Registering your application
----------------------------
To register your application with Azure DataMarket,
visit https://datamarket.azure.com/developer/applications/ using the
LiveID credentials from step 1, and click on “Register”. In the
“Register your application” dialog box, you can define your own
Client ID and Name. The redirect URI is not used for the Microsoft
Translator API. However, the redirect URI field is a mandatory field,
and you must provide a URI to obtain the access code. A description is
optional.
Take a note of the client ID and the client secret value.
Bugs and Development on Github
------------------------------
https://github.com/openlabs/Microsoft-Translator-Python-API

View file

@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
"""
__init__
A translator using the micrsoft translation engine documented here:
http://msdn.microsoft.com/en-us/library/ff512419.aspx
:copyright: © 2011 by Openlabs Technologies & Consulting (P) Limited
:license: BSD, see LICENSE for more details.
"""
__all__ = ['Translator', 'TranslateApiException']
import requests
import warnings
import logging
class ArgumentOutOfRangeException(Exception):
def __init__(self, message):
self.message = message.replace('ArgumentOutOfRangeException: ', '')
super(ArgumentOutOfRangeException, self).__init__(self.message)
class TranslateApiException(Exception):
def __init__(self, message, *args):
self.message = message.replace('TranslateApiException: ', '')
super(TranslateApiException, self).__init__(self.message, *args)
class Translator(object):
"""Implements AJAX API for the Microsoft Translator service
:param app_id: A string containing the Bing AppID. (Deprecated)
"""
def __init__(self, client_id, client_secret,
scope="http://api.microsofttranslator.com",
grant_type="client_credentials", app_id=None, debug=False):
"""
:param client_id: The client ID that you specified when you registered
your application with Azure DataMarket.
:param client_secret: The client secret value that you obtained when
you registered your application with Azure
DataMarket.
:param scope: Defaults to http://api.microsofttranslator.com
;param grant_type: Defaults to "client_credentials"
:param app_id: Deprecated
:param debug: If true, the logging level will be set to debug
.. versionchanged: 0.4
Bing AppID mechanism is deprecated and is no longer supported.
See: http://msdn.microsoft.com/en-us/library/hh454950
"""
if app_id is not None:
warnings.warn("""app_id is deprected since v0.4.
See: http://msdn.microsoft.com/en-us/library/hh454950
""", DeprecationWarning, stacklevel=2)
self.client_id = client_id
self.client_secret = client_secret
self.scope = scope
self.grant_type = grant_type
self.access_token = None
self.debug = debug
self.logger = logging.getLogger("microsofttranslator")
if self.debug:
self.logger.setLevel(level=logging.DEBUG)
def get_access_token(self):
"""Bing AppID mechanism is deprecated and is no longer supported.
As mentioned above, you must obtain an access token to use the
Microsoft Translator API. The access token is more secure, OAuth
standard compliant, and more flexible. Users who are using Bing AppID
are strongly recommended to get an access token as soon as possible.
.. note::
The value of access token can be used for subsequent calls to the
Microsoft Translator API. The access token expires after 10
minutes. It is always better to check elapsed time between time at
which token issued and current time. If elapsed time exceeds 10
minute time period renew access token by following obtaining
access token procedure.
:return: The access token to be used with subsequent requests
"""
args = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'scope': self.scope,
'grant_type': self.grant_type
}
response = requests.post(
'https://datamarket.accesscontrol.windows.net/v2/OAuth2-13',
data=args
).json()
self.logger.debug(response)
if "error" in response:
raise TranslateApiException(
response.get('error_description', 'No Error Description'),
response.get('error', 'Unknown Error')
)
return response['access_token']
def call(self, url, params):
"""Calls the given url with the params urlencoded
"""
if not self.access_token:
self.access_token = self.get_access_token()
resp = requests.get(
"%s" % url,
params=params,
headers={'Authorization': 'Bearer %s' % self.access_token}
)
resp.encoding = 'UTF-8-sig'
rv = resp.json()
#rv = json.loads(response.decode("UTF-8-sig"))
if isinstance(rv, str) and \
rv.startswith("ArgumentOutOfRangeException"):
raise ArgumentOutOfRangeException(rv)
if isinstance(rv, str) and \
rv.startswith("TranslateApiException"):
raise TranslateApiException(rv)
return rv
def translate(self, text, to_lang, from_lang=None,
content_type='text/plain', category='general'):
"""Translates a text string from one language to another.
:param text: A string representing the text to translate.
:param to_lang: A string representing the language code to
translate the text into.
:param from_lang: A string representing the language code of the
translation text. If left None the response will include the
result of language auto-detection. (Default: None)
:param content_type: The format of the text being translated.
The supported formats are "text/plain" and "text/html". Any HTML
needs to be well-formed.
:param category: The category of the text to translate. The only
supported category is "general".
"""
params = {
'text': text.encode('utf8'),
'to': to_lang,
'contentType': content_type,
'category': category,
}
if from_lang is not None:
params['from'] = from_lang
return self.call(
"http://api.microsofttranslator.com/V2/Ajax.svc/Translate",
params)
def translate_array(self, texts, to_lang, from_lang=None, **options):
"""Translates an array of text strings from one language to another.
:param texts: A list containing texts for translation.
:param to_lang: A string representing the language code to
translate the text into.
:param from_lang: A string representing the language code of the
translation text. If left None the response will include the
result of language auto-detection. (Default: None)
:param options: A TranslateOptions element containing the values below.
They are all optional and default to the most common settings.
Category: A string containing the category (domain) of the
translation. Defaults to "general".
ContentType: The format of the text being translated. The
supported formats are "text/plain" and "text/html". Any
HTML needs to be well-formed.
Uri: A string containing the content location of this
translation.
User: A string used to track the originator of the submission.
State: User state to help correlate request and response. The
same contents will be returned in the response.
"""
options = {
'Category': "general",
'Contenttype': "text/plain",
'Uri': '',
'User': 'default',
'State': ''
}.update(options)
params = {
'texts': json.dumps(texts),
'to': to_lang,
'options': json.dumps(options),
}
if from_lang is not None:
params['from'] = from_lang
return self.call(
"http://api.microsofttranslator.com/V2/Ajax.svc/TranslateArray",
params)

View file

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
"""
Microsoft translator API
The Microsoft Translator services can be used in web or client
applications to perform language translation operations. The services
support users who are not familiar with the default language of a page or
application, or those desiring to communicate with people of a different
language group.
This module implements the AJAX API for the Microsoft Translator service.
An example::
>>> from microsofttranslator import Translator
>>> translator = Translator('<Your API Key>')
>>> print translator.translate("Hello", "pt")
"Olá"
The documentation for the service can be obtained here:
http://msdn.microsoft.com/en-us/library/ff512423.aspx
The project is hosted on GitHub where your could fork the project or report
issues. Visit https://github.com/openlabs/Microsoft-Translator-Python-API
:copyright: © 2011 by Openlabs Technologies & Consulting (P) Limited
:license: BSD, see LICENSE for more details.
"""
import os
from setuptools import setup
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
setup(
name = "microsofttranslator",
version = "0.4",
packages = [
'microsofttranslator',
],
package_dir = {
'microsofttranslator': '.'
},
author = "Openlabs Technologies & Consulting (P) Limited",
author_email = "info@openlabs.co.in",
description = "Microsoft Translator V2 - Python API",
long_description = read('README.rst'),
license = "BSD",
keywords = "translation microsoft",
url = "http://openlabs.co.in/",
include_package_data = True,
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Topic :: Software Development :: Internationalization",
"Topic :: Utilities"
],
test_suite = "microsofttranslator.test.test_all",
install_requires=[
'requests >= 1.2.3',
]
)

View file

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
"""
test
Test the translator
:copyright: (c) 2012 by Openlabs Technologies & Consulting (P) Limited
:license: BSD, see LICENSE for more details.
"""
import unittest
from rosetta.utils.microsofttranslator import Translator, TranslateApiException
client_id = "translaterpythonapi"
client_secret = "FLghnwW4LJmNgEG+EZkL8uE+wb7+6tkOS8eejHg3AaI="
class TestTranslator(unittest.TestCase):
def test_translate(self):
client = Translator(client_id, client_secret, debug=True)
self.assertEqual(client.translate("hello", "pt"), u'Ol\xe1')
def test_invalid_client_id(self):
client = Translator("foo", "bar")
with self.assertRaises(TranslateApiException):
client.translate("hello", "pt")
def test_all():
loader = unittest.TestLoader()
suite = unittest.TestSuite()
suite.addTests(loader.loadTestsFromTestCase(TestTranslator))
return suite
if __name__ == '__main__':
unittest.main()

View file

@ -5,22 +5,30 @@ 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.utils.microsofttranslator import Translator, TranslateApiException
from rosetta.conf import settings as rosetta_settings
from rosetta.polib import pofile
from rosetta.poutil import find_pos, pagination_range
from rosetta.poutil import find_pos, pagination_range, timestamp_with_timezone
from rosetta.signals import entry_changed, post_save
from rosetta.storage import get_storage
from rosetta.access import can_translate
import json
import re
import rosetta
import datetime
import unicodedata
import hashlib
import os
import six
@never_cache
@user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)
def home(request):
"""
Displays a list of messages to be translated
@ -47,7 +55,7 @@ def home(request):
return out_
storage = get_storage(request)
version = rosetta.get_version(True)
query = ''
if storage.has('rosetta_i18n_fn'):
rosetta_i18n_fn = storage.get('rosetta_i18n_fn')
rosetta_i18n_app = get_app_name(rosetta_i18n_fn)
@ -58,9 +66,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 or "")).encode('utf8')
).hexdigest()
else:
@ -87,7 +95,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])
@ -136,7 +144,7 @@ def home(request):
rosetta_i18n_pofile.metadata['Last-Translator'] = unicodedata.normalize('NFKD', u"%s %s <%s>" % (request.user.first_name, request.user.last_name, request.user.email)).encode('ascii', 'ignore')
rosetta_i18n_pofile.metadata['X-Translated-Using'] = u"django-rosetta %s" % rosetta.get_version(False)
rosetta_i18n_pofile.metadata['PO-Revision-Date'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M%z')
rosetta_i18n_pofile.metadata['PO-Revision-Date'] = timestamp_with_timezone()
except UnicodeDecodeError:
pass
@ -148,7 +156,7 @@ def home(request):
post_save.send(sender=None, language_code=rosetta_i18n_lang_code, request=request)
# Try auto-reloading via the WSGI daemon mode reload mechanism
if rosetta_settings.WSGI_AUTO_RELOAD and \
if rosetta_settings.WSGI_AUTO_RELOAD and \
'mod_wsgi.process_group' in request.environ and \
request.environ.get('mod_wsgi.process_group', None) and \
'SCRIPT_FILENAME' in request.environ and \
@ -178,13 +186,12 @@ def home(request):
if 'page' in request.GET:
query_arg += '&page=%d&_next=1' % int(request.GET.get('page'))
return HttpResponseRedirect(reverse('rosetta-home') + iri_to_uri(query_arg))
rosetta_i18n_lang_name = _(storage.get('rosetta_i18n_lang_name'))
rosetta_i18n_lang_code = storage.get('rosetta_i18n_lang_code')
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)
@ -208,9 +215,8 @@ def home(request):
return HttpResponseRedirect(reverse('rosetta-home') + iri_to_uri(query_arg))
rosetta_messages = paginator.page(page).object_list
main_language = None
if rosetta_settings.MAIN_LANGUAGE and rosetta_settings.MAIN_LANGUAGE != rosetta_i18n_lang_code:
main_language = None
for language in settings.LANGUAGES:
if language[0] == rosetta_settings.MAIN_LANGUAGE:
main_language = _(language[1])
@ -219,7 +225,6 @@ def home(request):
fl = ("/%s/" % rosetta_settings.MAIN_LANGUAGE).join(rosetta_i18n_fn.split("/%s/" % rosetta_i18n_lang_code))
po = pofile(fl)
main_messages = []
for message in rosetta_messages:
message.main_lang = po.find(message.msgid).msgstr
@ -235,24 +240,42 @@ def home(request):
except AttributeError:
ADMIN_MEDIA_PREFIX = settings.STATIC_URL + 'admin/'
ADMIN_IMAGE_DIR = ADMIN_MEDIA_PREFIX + 'img/'
ENABLE_TRANSLATION_SUGGESTIONS = rosetta_settings.BING_APP_ID and rosetta_settings.ENABLE_TRANSLATION_SUGGESTIONS
BING_APP_ID = rosetta_settings.BING_APP_ID
MESSAGES_SOURCE_LANGUAGE_NAME = rosetta_settings.MESSAGES_SOURCE_LANGUAGE_NAME
MESSAGES_SOURCE_LANGUAGE_CODE = rosetta_settings.MESSAGES_SOURCE_LANGUAGE_CODE
if storage.has('rosetta_last_save_error'):
storage.delete('rosetta_last_save_error')
rosetta_last_save_error = True
else:
rosetta_last_save_error = False
return render_to_response('rosetta/pofile.html', locals(), context_instance=RequestContext(request))
return render_to_response('rosetta/pofile.html', dict(
version=rosetta.get_version(True),
ADMIN_MEDIA_PREFIX=ADMIN_MEDIA_PREFIX,
ADMIN_IMAGE_DIR=ADMIN_IMAGE_DIR,
rosetta_settings=rosetta_settings,
rosetta_i18n_lang_name=_(storage.get('rosetta_i18n_lang_name')),
rosetta_i18n_lang_code=rosetta_i18n_lang_code,
rosetta_i18n_lang_bidi=rosetta_i18n_lang_bidi,
rosetta_last_save_error=rosetta_last_save_error,
rosetta_i18n_filter=rosetta_i18n_filter,
rosetta_i18n_write=rosetta_i18n_write,
rosetta_messages=rosetta_messages,
page_range=needs_pagination and page_range,
needs_pagination=needs_pagination,
main_language=main_language,
rosetta_i18n_app=rosetta_i18n_app,
page=page,
query=query,
paginator=paginator,
rosetta_i18n_pofile=rosetta_i18n_pofile
), context_instance=RequestContext(request))
else:
return list_languages(request, do_session_warn=True)
home = never_cache(home)
home = user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)(home)
@never_cache
@user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)
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,9 +293,9 @@ 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, 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)
@ -284,10 +307,10 @@ def download_file(request):
except Exception:
return HttpResponseRedirect(reverse('rosetta-home'))
download_file = never_cache(download_file)
download_file = user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)(download_file)
@never_cache
@user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)
def list_languages(request, do_session_warn=False):
"""
Lists the languages for the current project, the gettext catalog files
@ -315,19 +338,23 @@ def list_languages(request, do_session_warn=False):
languages.append(
(language[0],
_(language[1]),
[(get_app_name(l), os.path.realpath(l), pofile(l)) for l in pos],
sorted([(get_app_name(l), os.path.realpath(l), pofile(l)) for l in pos], key=lambda app: app[0]),
)
)
try:
ADMIN_MEDIA_PREFIX = settings.ADMIN_MEDIA_PREFIX
except AttributeError:
ADMIN_MEDIA_PREFIX = settings.STATIC_URL + 'admin/'
version = rosetta.get_version(True)
do_session_warn = do_session_warn and 'SessionRosettaStorage' in rosetta_settings.STORAGE_CLASS and 'signed_cookies' in settings.SESSION_ENGINE
return render_to_response('rosetta/languages.html', locals(), context_instance=RequestContext(request))
list_languages = never_cache(list_languages)
list_languages = user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)(list_languages)
return render_to_response('rosetta/languages.html', dict(
version=rosetta.get_version(True),
ADMIN_MEDIA_PREFIX=ADMIN_MEDIA_PREFIX,
do_session_warn=do_session_warn,
languages=languages,
has_pos=has_pos,
rosetta_i18n_catalog_filter=rosetta_i18n_catalog_filter
), context_instance=RequestContext(request))
def get_app_name(path):
@ -335,6 +362,8 @@ def get_app_name(path):
return app
@never_cache
@user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)
def lang_sel(request, langid, idx):
"""
Selects a file to be translated
@ -349,17 +378,17 @@ def lang_sel(request, langid, idx):
third_party_apps = rosetta_i18n_catalog_filter in ('all', 'third-party')
django_apps = rosetta_i18n_catalog_filter in ('all', 'django')
project_apps = rosetta_i18n_catalog_filter in ('all', 'project')
file_ = find_pos(langid, project_apps=project_apps, django_apps=django_apps, third_party_apps=third_party_apps)[int(idx)]
file_ = sorted(find_pos(langid, project_apps=project_apps, django_apps=django_apps, third_party_apps=third_party_apps), key=get_app_name)[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_fn', file_)
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 or "")).encode('utf8')
).hexdigest()
storage.set('rosetta_i18n_pofile', po)
@ -370,22 +399,27 @@ def lang_sel(request, langid, idx):
storage.set('rosetta_i18n_write', False)
return HttpResponseRedirect(reverse('rosetta-home'))
lang_sel = never_cache(lang_sel)
lang_sel = user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)(lang_sel)
def can_translate(user):
if not getattr(settings, 'ROSETTA_REQUIRES_AUTH', True):
return True
if not user.is_authenticated():
return False
elif user.is_superuser and user.is_staff:
return True
@user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)
def translate_text(request):
language_from = request.GET.get('from', None)
language_to = request.GET.get('to', None)
text = request.GET.get('text', None)
if language_from == language_to:
data = {'success': True, 'translation': text}
else:
try:
from django.contrib.auth.models import Group
translators = Group.objects.get(name='translators')
return translators in user.groups.all()
except Group.DoesNotExist:
return False
# run the translation:
AZURE_CLIENT_ID = getattr(settings, 'AZURE_CLIENT_ID', None)
AZURE_CLIENT_SECRET = getattr(settings, 'AZURE_CLIENT_SECRET', None)
translator = Translator(AZURE_CLIENT_ID, AZURE_CLIENT_SECRET)
try:
translated_text = translator.translate(text, language_to)
data = {'success': True, 'translation': translated_text}
except TranslateApiException as e:
data = {'success': False, 'error': "Translation API Exception: {0}".format(e.message)}
return HttpResponse(json.dumps(data), mimetype='application/json')

11
runtests.sh Normal file → Executable file
View file

@ -1,5 +1,16 @@
#!/bin/bash
if [ ! -d venv_13 ]
then
virtualenv --no-site-packages --distribute --python=python2 venv_13
. venv_13/bin/activate
pip install Django==1.3 coverage python-memcached six
deactivate
fi
. venv_13/bin/activate
cd testproject
python manage.py test rosetta
cd ..
deactivate

16
runtests_coverage.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/bash
if [ ! -d venv_13 ]
then
virtualenv --no-site-packages --distribute --python=python2 venv_13
. venv_13/bin/activate
pip install Django==1.3 coverage python-memcached six microsofttranslator
deactivate
fi
. venv_13/bin/activate
cd testproject
coverage run --rcfile=.coveragerc manage.py test --failfast rosetta
coverage xml
coverage html
cd ..

78
runtests_multi_venv.sh Executable file
View file

@ -0,0 +1,78 @@
#!/bin/bash
if [ ! -d .venv_14 ]
then
virtualenv --no-site-packages --distribute --python=python2 .venv_14
. .venv_14/bin/activate
pip install --use-mirrors Django==1.4 coverage python-memcached six requests==2.1.0
deactivate
fi
if [ ! -d .venv_15 ]
then
virtualenv --no-site-packages --distribute --python=python2 .venv_15
. .venv_15/bin/activate
pip install --use-mirrors Django==1.5 coverage python-memcached six requests==2.1.0
deactivate
fi
if [ ! -d .venv_15_p3 ]
then
virtualenv --no-site-packages --distribute --python=python3 .venv_15_p3
. .venv_15_p3/bin/activate
pip install --use-mirrors Django==1.5 coverage python3-memcached six requests==2.1.0
deactivate
fi
if [ ! -d .venv_16 ]
then
virtualenv --no-site-packages --distribute --python=python2 .venv_16
. .venv_16/bin/activate
pip install --use-mirrors coverage python-memcached six Django==1.6.1 requests==2.1.0
deactivate
fi
if [ ! -d .venv_16_p3 ]
then
virtualenv --no-site-packages --distribute --python=python3 .venv_16_p3
. .venv_16_p3/bin/activate
pip install --use-mirrors coverage python3-memcached six Django==1.6.1 requests==2.1.0
deactivate
fi
. .venv_14/bin/activate
cd testproject
python manage.py --version
python --version
python manage.py test rosetta
cd ..
deactivate
. .venv_15/bin/activate
cd testproject
python manage.py --version
python --version
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
. .venv_16/bin/activate
cd testproject
python manage.py --version
python --version
python manage.py test rosetta
cd ..
deactivate
. .venv_16_p3/bin/activate
cd testproject
python manage.py --version
python --version
python manage.py test rosetta
cd ..
deactivate

View file

@ -18,7 +18,15 @@ setup(
'Topic :: Software Development :: Localization',
'Topic :: Software Development :: Internationalization',
'Framework :: Django',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
],
include_package_data=True,
zip_safe=False
zip_safe=False,
install_requires=[
'six >=1.2.0',
'Django >= 1.3',
'requests >= 2.1.0',
]
)

10
testproject/.coveragerc Normal file
View file

@ -0,0 +1,10 @@
[run]
branch = True
source =
rosetta
omit =
../*migrations*
../*tests*
../*polib*
[report]
precision = 2

View file

@ -0,0 +1,34 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-04-30 14:37+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: templates/test.html:3
msgid "Some text to translate"
msgstr ""
#: templates/test.html:5
#, python-format
msgid ""
"\n"
"one bottle of beer on the wall\n"
msgid_plural ""
"\n"
"%(num_bottles)s bottles of beer on the wall\n"
msgstr[0] ""
msgstr[1] ""

View file

@ -0,0 +1,34 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-04-30 14:37+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: templates/test.html:3
msgid "Some text to translate"
msgstr ""
#: templates/test.html:5
#, python-format
msgid ""
"\n"
"one bottle of beer on the wall\n"
msgid_plural ""
"\n"
"%(num_bottles)s bottles of beer on the wall\n"
msgstr[0] ""
msgstr[1] ""

View file

@ -2,617 +2,66 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
msgid ""
msgstr ""
"Project-Id-Version: Rosetta\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-10-21 12:21+0200\n"
"POT-Creation-Date: 2013-04-30 14:37+0200\n"
"PO-Revision-Date: 2012-05-27 14:59\n"
"Last-Translator: <admin@admin.com>\n"
"Language-Team: French <LL@li.org>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2)\n"
"X-Translated-Using: django-rosetta 0.6.7\n"
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 1"
msgstr "aaa"
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 2"
msgstr "111"
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 3"
msgstr "asdasd"
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 4"
msgstr "aa"
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 5"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 6"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 7"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 8"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 9"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 10"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 11"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 12"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 13"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 14"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 15"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 16"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 17"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 18"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 19"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 20"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 21"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 22"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 23"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 24"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 25"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 26"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 27"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 28"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 29"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 30"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 31"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 32"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 33"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 34"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 35"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 36"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 37"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 38"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 39"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 40"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 41"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 42"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 43"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 44"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 45"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 46"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 47"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 48"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 49"
msgstr ""
msgid ""
"0_81EhfRiLPoxAH5j3f1D2P33S_-"
"Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
"kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 50"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 1"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 2"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 3"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 4"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 5"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 6"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 7"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 8"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 9"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 10"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 11"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 12"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 13"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 14"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 15"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 16"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 17"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 18"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 19"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 20"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 21"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 22"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 23"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 24"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 25"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 26"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 27"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 28"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 29"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 30"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 31"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 32"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 33"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 34"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 35"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 36"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 37"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 38"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 39"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 40"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 41"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 42"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 43"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 44"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 45"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 46"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 47"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 48"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 49"
msgstr ""
msgid ""
"xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
"aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
"qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 50"
msgstr "aaa2"
#: templates/test.html:3
msgid "Some text to translate"
msgstr ""
#: templates/test.html:5
#, python-format
msgid ""
"\n"
"one bottle of beer on the wall\n"
msgid_plural ""
"\n"
"%(num_bottles)s bottles of beer on the wall\n"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
#~ msgid ""
#~ "0_81EhfRiLPoxAH5j3f1D2P33S_-"
#~ "Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
#~ "kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 1"
#~ msgstr "aaa"
#~ msgid ""
#~ "0_81EhfRiLPoxAH5j3f1D2P33S_-"
#~ "Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
#~ "kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 2"
#~ msgstr "111"
#~ msgid ""
#~ "0_81EhfRiLPoxAH5j3f1D2P33S_-"
#~ "Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
#~ "kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 3"
#~ msgstr "asdasd"
#~ msgid ""
#~ "0_81EhfRiLPoxAH5j3f1D2P33S_-"
#~ "Fe_7Mn_R_t9V8Bysr01xWAgvVEyyA190oYYTnzJCBucLBWKiqaAIjWAvQ3ZfrbCkDRe02iESoGhHEIBmmDI49SY6cQQk0AxGoB-"
#~ "kmZfloBiMsQraFOWetVEF4SRTHlxhNEPA-kCYB4LUCfl-l String 4"
#~ msgstr "aa"
#~ msgid ""
#~ "xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-"
#~ "aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-"
#~ "qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String "
#~ "50"
#~ msgstr "aaa2"

View file

@ -1,16 +1,26 @@
#!/usr/bin/env python
from django.core.management import execute_manager
import sys
import os
try:
import settings # Assumed to be in the same directory.
from django.core.management import execute_manager
OLD_DJANGO = True
except ImportError:
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
from django.core.management import execute_from_command_line
OLD_DJANGO = False
if OLD_DJANGO:
try:
import settings # Assumed to be in the same directory.
except ImportError:
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, BASEDIR)
if __name__ == "__main__":
execute_manager(settings)
os.environ["DJANGO_SETTINGS_MODULE"] = "testproject.settings"
if OLD_DJANGO:
execute_manager(settings)
else:
execute_from_command_line(sys.argv)

View file

@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
#from __future__ import unicode_literals
import django
import os
import sys
SITE_ID = 1
PROJECT_PATH = os.path.abspath(os.path.dirname(__file__))
@ -17,6 +19,17 @@ DATABASES = {
}
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
'KEY_PREFIX': 'ROSETTA_TEST'
}
}
#CACHES = {'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}}
TEST_DATABASE_CHARSET = "utf8"
TEST_DATABASE_COLLATION = "utf8_general_ci"
@ -34,10 +47,14 @@ INSTALLED_APPS = [
LANGUAGE_CODE = "en"
LANGUAGES = (
('en', 'English'),
('ja', u'日本語'),
('xx', u'XXXXX'),
('fr', u'French'),
('fr_FR.utf8', u'French (France), UTF8'),
)
LOCALE_PATHS = [
os.path.join(PROJECT_PATH, 'locale'),
]
SOUTH_TESTS_MIGRATE = False
@ -54,4 +71,4 @@ STATIC_URL = '/static/'
#SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
#ROSETTA_STORAGE_CLASS = 'rosetta.storage.SessionRosettaStorage'
ROSETTA_STORAGE_CLASS = 'rosetta.storage.CacheRosettaStorage'
SECRET_KEY = 'empty'

View file

@ -0,0 +1,9 @@
{% load i18n %}
{% trans "Some text to translate" %}
{% blocktrans %}
one bottle of beer on the wall
{% plural %}
{{num_bottles}} bottles of beer on the wall
{% endblocktrans %}

View file

@ -1,4 +1,9 @@
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 django.contrib import admin
@ -16,3 +21,5 @@ urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^rosetta/', include('rosetta.urls'))
)
urlpatterns += staticfiles_urlpatterns()