diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index b219ea8..78bd2d6 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,3 @@ -- Which version of Django are you using?: +- Which version of Django are you using?: - Which version of django-rosetta are you using?: - Have you looked trough [recent issues](https://github.com/mbi/django-rosetta/issues?utf8=%E2%9C%93&q=is%3Aissue) and checked this isn't a duplicate? diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0c6861a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: fix-encoding-pragma + args: ['--remove'] + - id: debug-statements + - id: check-merge-conflict + - id: check-ast + - id: end-of-file-fixer + - id: trailing-whitespace + +- repo: https://github.com/timothycrosley/isort + rev: 5.7.0 + hooks: + - id: isort + additional_dependencies: [toml] + +- repo: https://github.com/psf/black + rev: 2d0c14989dca41676fc83fb36f2d652cf93fad58 + hooks: + - id: black + +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 + args: ['--config', '.flake8'] + exclude: '.*/migrations/.*|conf' diff --git a/CHANGES b/CHANGES index a7a872b..9e2a8ce 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,9 @@ Version 0.9.9 (unreleased) * Adds Chinese (Simplified) translation. (#266 thanks @chenluyong) * Test against Django 4.1a * Limit supported versions to Django 3.2 and later, using Python 3.8, 3.9 and 3.10 +* Proxy Deepl translations suggestions through the back-end to avoid CORS issues. (#271 thanks @rafaelromon, @biermeester and @matthiask) +* Format code with pre-commit +* Test against Django 4.2a Version 0.9.8 diff --git a/docs/conf.py b/docs/conf.py index 9e43ee8..b042e1e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Django Rosetta documentation build configuration file, created by # sphinx-quickstart on Thu Apr 2 15:19:37 2015. @@ -25,7 +24,7 @@ import sys def get_version(): - sys.path.insert(0, os.path.abspath('..')) + sys.path.insert(0, os.path.abspath("..")) from rosetta import get_version as get_version_ return get_version_() @@ -46,23 +45,23 @@ def get_version(): extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Django Rosetta' -copyright = u'2008 – 2021 Marco Bonetti and contributors' -author = u'Marco Bonetti' +project = "Django Rosetta" +copyright = "2008 – 2021 Marco Bonetti and contributors" +author = "Marco Bonetti" version = get_version() @@ -84,7 +83,7 @@ language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -102,7 +101,7 @@ exclude_patterns = ['_build'] # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -147,7 +146,7 @@ todo_include_todos = False # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -210,7 +209,7 @@ html_static_path = ['_static'] # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'DjangoRosettadoc' +htmlhelp_basename = "DjangoRosettadoc" # -- Options for LaTeX output --------------------------------------------- @@ -231,10 +230,10 @@ latex_elements = { latex_documents = [ ( master_doc, - 'DjangoRosetta.tex', - u'Django Rosetta Documentation', - u'Marco Bonetti', - 'manual', + "DjangoRosetta.tex", + "Django Rosetta Documentation", + "Marco Bonetti", + "manual", ) ] @@ -263,7 +262,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, 'djangorosetta', u'Django Rosetta Documentation', [author], 1)] +man_pages = [(master_doc, "djangorosetta", "Django Rosetta Documentation", [author], 1)] # If true, show URL addresses after external links. # man_show_urls = False @@ -277,12 +276,12 @@ man_pages = [(master_doc, 'djangorosetta', u'Django Rosetta Documentation', [aut texinfo_documents = [ ( master_doc, - 'DjangoRosetta', - u'Django Rosetta Documentation', + "DjangoRosetta", + "Django Rosetta Documentation", author, - 'DjangoRosetta', - 'One line description of project.', - 'Miscellaneous', + "DjangoRosetta", + "One line description of project.", + "Miscellaneous", ) ] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9a9c3de --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[tool.isort] +profile = "black" +lines_after_imports = 2 +multi_line_output = 3 +order_by_type = true +known_django = "django" +known_django_third_party = "django_*" +known_first_party = "apps,rosetta" +sections = "FUTURE,STDLIB,THIRDPARTY,DJANGO,DJANGO_THIRD_PARTY,REST_FRAMEWORK,FIRSTPARTY,LOCALFOLDER" +skip_glob= "**/migrations/**" + + +[tool.black] +line-length = 88 +target-version = ['py38'] +exclude = ''' + +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + | node_modules + )/ + | foo.py # also separately exclude a file named foo.py in + # the root of the project +) +''' diff --git a/rosetta/access.py b/rosetta/access.py index 699617c..792d99a 100644 --- a/rosetta/access.py +++ b/rosetta/access.py @@ -15,12 +15,12 @@ def get_access_control_function(): Return a predicate for determining if a user can access the Rosetta views """ - access_function = getattr(settings, 'ROSETTA_ACCESS_CONTROL_FUNCTION', None) + access_function = getattr(settings, "ROSETTA_ACCESS_CONTROL_FUNCTION", None) if access_function is None: return is_superuser_staff_or_in_translators_group elif isinstance(access_function, str): # Dynamically load a permissions function - perm_module, perm_func = access_function.rsplit('.', 1) + perm_module, perm_func = access_function.rsplit(".", 1) perm_module = importlib.import_module(perm_module) return getattr(perm_module, perm_func) elif callable(access_function): @@ -31,7 +31,7 @@ def get_access_control_function(): # Default access control test def is_superuser_staff_or_in_translators_group(user): - if not getattr(settings, 'ROSETTA_REQUIRES_AUTH', True): + if not getattr(settings, "ROSETTA_REQUIRES_AUTH", True): return True try: if not user.is_authenticated: @@ -39,10 +39,16 @@ def is_superuser_staff_or_in_translators_group(user): elif user.is_superuser and user.is_staff: return True else: - return user.groups.filter(name='translators').exists() + return user.groups.filter(name="translators").exists() except AttributeError: - if not hasattr(user, 'is_authenticated') or not hasattr(user, 'is_superuser') or not hasattr(user, 'groups'): - raise ImproperlyConfigured('If you are using custom User Models you must implement a custom authentication method for Rosetta. See ROSETTA_ACCESS_CONTROL_FUNCTION here: https://django-rosetta.readthedocs.org/en/latest/settings.html') + if ( + not hasattr(user, "is_authenticated") + or not hasattr(user, "is_superuser") + or not hasattr(user, "groups") + ): + raise ImproperlyConfigured( + "If you are using custom User Models you must implement a custom authentication method for Rosetta. See ROSETTA_ACCESS_CONTROL_FUNCTION here: https://django-rosetta.readthedocs.org/en/latest/settings.html" + ) raise @@ -55,9 +61,15 @@ def can_translate_language(user, langid): elif user.is_superuser and user.is_staff: return True else: - return user.groups.filter(name='translators-%s' % langid).exists() + return user.groups.filter(name="translators-%s" % langid).exists() except AttributeError: - if not hasattr(user, 'is_authenticated') or not hasattr(user, 'is_superuser') or not hasattr(user, 'groups'): - raise ImproperlyConfigured('If you are using custom User Models you must implement a custom authentication method for Rosetta. See ROSETTA_ACCESS_CONTROL_FUNCTION here: https://django-rosetta.readthedocs.org/en/latest/settings.html') + if ( + not hasattr(user, "is_authenticated") + or not hasattr(user, "is_superuser") + or not hasattr(user, "groups") + ): + raise ImproperlyConfigured( + "If you are using custom User Models you must implement a custom authentication method for Rosetta. See ROSETTA_ACCESS_CONTROL_FUNCTION here: https://django-rosetta.readthedocs.org/en/latest/settings.html" + ) raise diff --git a/rosetta/apps.py b/rosetta/apps.py index 4b7979b..2236308 100644 --- a/rosetta/apps.py +++ b/rosetta/apps.py @@ -4,10 +4,10 @@ from .conf import settings as rosetta_settings class RosettaAppConfig(AppConfig): - name = 'rosetta' + name = "rosetta" def ready(self): from django.contrib import admin if rosetta_settings.SHOW_AT_ADMIN_PANEL: - admin.site.index_template = 'rosetta/admin_index.html' + admin.site.index_template = "rosetta/admin_index.html" diff --git a/rosetta/conf/__init__.py b/rosetta/conf/__init__.py index c44947c..db55f19 100644 --- a/rosetta/conf/__init__.py +++ b/rosetta/conf/__init__.py @@ -6,7 +6,7 @@ from django.conf import settings as dj_settings from django.core.signals import setting_changed -__all__ = ['settings'] +__all__ = ["settings"] class RosettaSettings(object): @@ -18,75 +18,87 @@ class RosettaSettings(object): """ SETTINGS = { - 'ROSETTA_MESSAGES_PER_PAGE': ('MESSAGES_PER_PAGE', 10), - 'ROSETTA_ENABLE_TRANSLATION_SUGGESTIONS': ( - 'ENABLE_TRANSLATION_SUGGESTIONS', + "ROSETTA_MESSAGES_PER_PAGE": ("MESSAGES_PER_PAGE", 10), + "ROSETTA_ENABLE_TRANSLATION_SUGGESTIONS": ( + "ENABLE_TRANSLATION_SUGGESTIONS", False, ), - 'YANDEX_TRANSLATE_KEY': ('YANDEX_TRANSLATE_KEY', None), - 'AZURE_CLIENT_SECRET': ('AZURE_CLIENT_SECRET', None), - 'GOOGLE_APPLICATION_CREDENTIALS_PATH': ( - 'GOOGLE_APPLICATION_CREDENTIALS_PATH', + "YANDEX_TRANSLATE_KEY": ("YANDEX_TRANSLATE_KEY", None), + "AZURE_CLIENT_SECRET": ("AZURE_CLIENT_SECRET", None), + "GOOGLE_APPLICATION_CREDENTIALS_PATH": ( + "GOOGLE_APPLICATION_CREDENTIALS_PATH", None, ), - 'GOOGLE_PROJECT_ID': ('GOOGLE_PROJECT_ID', None), - 'DEEPL_AUTH_KEY': ('DEEPL_AUTH_KEY', None), - 'ROSETTA_MAIN_LANGUAGE': ('MAIN_LANGUAGE', None), - 'ROSETTA_MESSAGES_SOURCE_LANGUAGE_CODE': ('MESSAGES_SOURCE_LANGUAGE_CODE', 'en'), - 'ROSETTA_MESSAGES_SOURCE_LANGUAGE_NAME': ( - 'MESSAGES_SOURCE_LANGUAGE_NAME', - 'English', + "GOOGLE_PROJECT_ID": ("GOOGLE_PROJECT_ID", None), + "DEEPL_AUTH_KEY": ("DEEPL_AUTH_KEY", None), + "ROSETTA_MAIN_LANGUAGE": ("MAIN_LANGUAGE", None), + "ROSETTA_MESSAGES_SOURCE_LANGUAGE_CODE": ( + "MESSAGES_SOURCE_LANGUAGE_CODE", + "en", ), - 'ROSETTA_ACCESS_CONTROL_FUNCTION': ('ACCESS_CONTROL_FUNCTION', None), - 'ROSETTA_WSGI_AUTO_RELOAD': ('WSGI_AUTO_RELOAD', False), - 'ROSETTA_UWSGI_AUTO_RELOAD': ('UWSGI_AUTO_RELOAD', False), - 'ROSETTA_EXCLUDED_APPLICATIONS': ('EXCLUDED_APPLICATIONS', ()), - 'ROSETTA_POFILE_WRAP_WIDTH': ('POFILE_WRAP_WIDTH', 78), - 'ROSETTA_STORAGE_CLASS': ('STORAGE_CLASS', 'rosetta.storage.CacheRosettaStorage'), - 'ROSETTA_ENABLE_REFLANG': ('ENABLE_REFLANG', False), - 'ROSETTA_POFILENAMES': ('POFILENAMES', ('django.po', 'djangojs.po')), - 'ROSETTA_CACHE_NAME': ( - 'ROSETTA_CACHE_NAME', - 'rosetta' if 'rosetta' in dj_settings.CACHES else 'default', + "ROSETTA_MESSAGES_SOURCE_LANGUAGE_NAME": ( + "MESSAGES_SOURCE_LANGUAGE_NAME", + "English", ), - 'ROSETTA_REQUIRES_AUTH': ('ROSETTA_REQUIRES_AUTH', True), - 'ROSETTA_EXCLUDED_PATHS': ('ROSETTA_EXCLUDED_PATHS', ()), - 'ROSETTA_LANGUAGE_GROUPS': ('ROSETTA_LANGUAGE_GROUPS', False), - 'ROSETTA_AUTO_COMPILE': ('AUTO_COMPILE', True), - 'ROSETTA_SHOW_AT_ADMIN_PANEL': ('SHOW_AT_ADMIN_PANEL', False), - 'ROSETTA_LOGIN_URL': ('LOGIN_URL', dj_settings.LOGIN_URL), - 'ROSETTA_LANGUAGES': ('ROSETTA_LANGUAGES', dj_settings.LANGUAGES), - 'ROSETTA_SHOW_OCCURRENCES': ('SHOW_OCCURRENCES', True), + "ROSETTA_ACCESS_CONTROL_FUNCTION": ("ACCESS_CONTROL_FUNCTION", None), + "ROSETTA_WSGI_AUTO_RELOAD": ("WSGI_AUTO_RELOAD", False), + "ROSETTA_UWSGI_AUTO_RELOAD": ("UWSGI_AUTO_RELOAD", False), + "ROSETTA_EXCLUDED_APPLICATIONS": ("EXCLUDED_APPLICATIONS", ()), + "ROSETTA_POFILE_WRAP_WIDTH": ("POFILE_WRAP_WIDTH", 78), + "ROSETTA_STORAGE_CLASS": ( + "STORAGE_CLASS", + "rosetta.storage.CacheRosettaStorage", + ), + "ROSETTA_ENABLE_REFLANG": ("ENABLE_REFLANG", False), + "ROSETTA_POFILENAMES": ("POFILENAMES", ("django.po", "djangojs.po")), + "ROSETTA_CACHE_NAME": ( + "ROSETTA_CACHE_NAME", + "rosetta" if "rosetta" in dj_settings.CACHES else "default", + ), + "ROSETTA_REQUIRES_AUTH": ("ROSETTA_REQUIRES_AUTH", True), + "ROSETTA_EXCLUDED_PATHS": ("ROSETTA_EXCLUDED_PATHS", ()), + "ROSETTA_LANGUAGE_GROUPS": ("ROSETTA_LANGUAGE_GROUPS", False), + "ROSETTA_AUTO_COMPILE": ("AUTO_COMPILE", True), + "ROSETTA_SHOW_AT_ADMIN_PANEL": ("SHOW_AT_ADMIN_PANEL", False), + "ROSETTA_LOGIN_URL": ("LOGIN_URL", dj_settings.LOGIN_URL), + "ROSETTA_LANGUAGES": ("ROSETTA_LANGUAGES", dj_settings.LANGUAGES), + "ROSETTA_SHOW_OCCURRENCES": ("SHOW_OCCURRENCES", True), # Deepl API language codes are different then those of django, so if this is not set according to your desired languages, # We use the first 2 letters of django language code. # In which case it would work fine for most of the languages, # But for 'en' if you want "EN-GB" for example, please set it in this dictionary. # you can find the supported languages list of DeepL API here: https://www.deepl.com/docs-api/translating-text/request/ # ex: DEEPL_LANGUAGES = {"fr": "FR", "en": "EN-GB", "zh_Hans": "ZH"} - 'DEEPL_LANGUAGES': ('DEEPL_LANGUAGES', {}), + "DEEPL_LANGUAGES": ("DEEPL_LANGUAGES", {}), } def __init__(self): # make sure we don't assign self._settings directly here, to avoid # recursion in __setattr__, we delegate to the parent instead - super(RosettaSettings, self).__setattr__('_settings', {}) + super(RosettaSettings, self).__setattr__("_settings", {}) self.load() def load(self): for user_setting, (rosetta_setting, default) in self.SETTINGS.items(): - self._settings[rosetta_setting] = getattr(dj_settings, user_setting, default) + self._settings[rosetta_setting] = getattr( + dj_settings, user_setting, default + ) def reload(self): self.__init__() def __getattr__(self, attr): if attr not in self._settings: - raise AttributeError("'RosettaSettings' object has not attribute '%s'" % attr) + raise AttributeError( + "'RosettaSettings' object has not attribute '%s'" % attr + ) return self._settings[attr] def __setattr__(self, attr, value): if attr not in self._settings: - raise AttributeError("'RosettaSettings' object has not attribute '%s'" % attr) + raise AttributeError( + "'RosettaSettings' object has not attribute '%s'" % attr + ) self._settings[attr] = value @@ -96,7 +108,7 @@ settings = RosettaSettings() # Signal handler to reload settings when needed def reload_settings(*args, **kwargs): - val = kwargs.get('setting') + val = kwargs.get("setting") if val in settings.SETTINGS: settings.reload() diff --git a/rosetta/locale/fa/LC_MESSAGES/django.po b/rosetta/locale/fa/LC_MESSAGES/django.po index 97f7fb3..8708895 100644 --- a/rosetta/locale/fa/LC_MESSAGES/django.po +++ b/rosetta/locale/fa/LC_MESSAGES/django.po @@ -201,4 +201,3 @@ msgstr "در حال نمایش:" msgid "%(hits)s/%(message_number)s message" msgid_plural "%(hits)s/%(message_number)s messages" msgstr[0] "%(hits)s از %(message_number)s پیام" - diff --git a/rosetta/locale/ky/LC_MESSAGES/django.po b/rosetta/locale/ky/LC_MESSAGES/django.po index 456e197..bec7f8b 100644 --- a/rosetta/locale/ky/LC_MESSAGES/django.po +++ b/rosetta/locale/ky/LC_MESSAGES/django.po @@ -2,10 +2,10 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Soyuzbek Orozbek uulu , 2020. -# +# # Translators: # Soyuzbek Orozbek uulu , 2020 -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" diff --git a/rosetta/locale/pl/LC_MESSAGES/django.po b/rosetta/locale/pl/LC_MESSAGES/django.po index 453b98f..b783e90 100644 --- a/rosetta/locale/pl/LC_MESSAGES/django.po +++ b/rosetta/locale/pl/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" diff --git a/rosetta/locale/xx/LC_MESSAGES/django.po b/rosetta/locale/xx/LC_MESSAGES/django.po index 9a5f7ef..7712727 100644 --- a/rosetta/locale/xx/LC_MESSAGES/django.po +++ b/rosetta/locale/xx/LC_MESSAGES/django.po @@ -31,7 +31,3 @@ msgstr "" msgctxt "Context hint" msgid "String 4" msgstr "" - - - - diff --git a/rosetta/poutil.py b/rosetta/poutil.py index 0cfdd9d..da9d28b 100644 --- a/rosetta/poutil.py +++ b/rosetta/poutil.py @@ -21,7 +21,7 @@ def timestamp_with_timezone(dt=None): """ dt = dt or datetime.now() if timezone is None: - return dt.strftime('%Y-%m-%d %H:%M%z') + return dt.strftime("%Y-%m-%d %H:%M%z") if not dt.tzinfo: tz = timezone.get_current_timezone() if not tz: @@ -41,19 +41,35 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False) # project/locale if settings.SETTINGS_MODULE: - parts = settings.SETTINGS_MODULE.split('.') + parts = settings.SETTINGS_MODULE.split(".") else: # if settings.SETTINGS_MODULE is None, we are probably in "test" mode # and override_settings() was used # see: https://code.djangoproject.com/ticket/25911 - parts = os.environ.get(ENVIRONMENT_VARIABLE).split('.') + parts = os.environ.get(ENVIRONMENT_VARIABLE).split(".") project = __import__(parts[0], {}, {}, []) - abs_project_path = os.path.normpath(os.path.abspath(os.path.dirname(project.__file__))) + abs_project_path = os.path.normpath( + os.path.abspath(os.path.dirname(project.__file__)) + ) if project_apps: - 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'))) + 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") + ) + ) case_sensitive_file_system = True tmphandle, tmppath = tempfile.mkstemp() @@ -63,14 +79,16 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False) # django/locale if django_apps: - django_paths = cache.get('rosetta_django_paths') + django_paths = cache.get("rosetta_django_paths") if django_paths is None: django_paths = [] - for root, dirnames, filename in os.walk(os.path.abspath(os.path.dirname(django.__file__))): - if 'locale' in dirnames: - django_paths.append(os.path.join(root, 'locale')) + for root, dirnames, filename in os.walk( + os.path.abspath(os.path.dirname(django.__file__)) + ): + if "locale" in dirnames: + django_paths.append(os.path.join(root, "locale")) continue - cache.set('rosetta_django_paths', django_paths, 60 * 60) + cache.set("rosetta_django_paths", django_paths, 60 * 60) paths = paths + django_paths # settings for localepath in settings.LOCALE_PATHS: @@ -79,12 +97,15 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False) # project/app/locale for app_ in apps.get_app_configs(): - if rosetta_settings.EXCLUDED_APPLICATIONS and app_.name in rosetta_settings.EXCLUDED_APPLICATIONS: + if ( + rosetta_settings.EXCLUDED_APPLICATIONS + and app_.name in rosetta_settings.EXCLUDED_APPLICATIONS + ): continue app_path = app_.path # django apps - if 'contrib' in app_path and 'django' in app_path and not django_apps: + if "contrib" in app_path and "django" in app_path and not django_apps: continue # third party external @@ -95,19 +116,27 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False) if not project_apps and abs_project_path in app_path: continue - if os.path.exists(os.path.abspath(os.path.join(app_path, 'locale'))): - paths.append(os.path.abspath(os.path.join(app_path, 'locale'))) - if os.path.exists(os.path.abspath(os.path.join(app_path, '..', 'locale'))): - paths.append(os.path.abspath(os.path.join(app_path, '..', 'locale'))) + if os.path.exists(os.path.abspath(os.path.join(app_path, "locale"))): + paths.append(os.path.abspath(os.path.join(app_path, "locale"))) + if os.path.exists(os.path.abspath(os.path.join(app_path, "..", "locale"))): + paths.append(os.path.abspath(os.path.join(app_path, "..", "locale"))) ret = set() langs = [lang] - if u'-' in lang: - _l, _c = map(lambda x: x.lower(), lang.split(u'-', 1)) - langs += [u'%s_%s' % (_l, _c), u'%s_%s' % (_l, _c.upper()), u'%s_%s' % (_l, _c.capitalize())] - elif u'_' in lang: - _l, _c = map(lambda x: x.lower(), lang.split(u'_', 1)) - langs += [u'%s-%s' % (_l, _c), u'%s-%s' % (_l, _c.upper()), u'%s_%s' % (_l, _c.capitalize())] + if "-" in lang: + _l, _c = map(lambda x: x.lower(), lang.split("-", 1)) + langs += [ + "%s_%s" % (_l, _c), + "%s_%s" % (_l, _c.upper()), + "%s_%s" % (_l, _c.capitalize()), + ] + elif "_" in lang: + _l, _c = map(lambda x: x.lower(), lang.split("_", 1)) + langs += [ + "%s-%s" % (_l, _c), + "%s-%s" % (_l, _c.upper()), + "%s_%s" % (_l, _c.capitalize()), + ] paths = map(os.path.normpath, paths) paths = list(set(paths)) @@ -115,7 +144,7 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False) # Exclude paths if path not in rosetta_settings.ROSETTA_EXCLUDED_PATHS: for lang_ in langs: - dirname = os.path.join(path, lang_, 'LC_MESSAGES') + dirname = os.path.join(path, lang_, "LC_MESSAGES") for fn in rosetta_settings.POFILENAMES: filename = os.path.join(dirname, fn) abs_path = os.path.abspath(filename) @@ -159,7 +188,7 @@ def pagination_range(first, last, current): for e in r[:]: if prev + 1 < e: try: - r.insert(r.index(e), '...') + r.insert(r.index(e), "...") except ValueError: pass prev = e diff --git a/rosetta/storage.py b/rosetta/storage.py index c8b9a60..44f62eb 100644 --- a/rosetta/storage.py +++ b/rosetta/storage.py @@ -8,6 +8,7 @@ from django.core.exceptions import ImproperlyConfigured from .conf import settings as rosetta_settings + cache = caches[rosetta_settings.ROSETTA_CACHE_NAME] @@ -47,8 +48,8 @@ class SessionRosettaStorage(BaseRosettaStorage): super(SessionRosettaStorage, self).__init__(request) if ( - 'signed_cookies' in settings.SESSION_ENGINE - and 'pickle' not in settings.SESSION_SERIALIZER.lower() + "signed_cookies" in settings.SESSION_ENGINE + and "pickle" not in settings.SESSION_SERIALIZER.lower() ): raise ImproperlyConfigured( "Sorry, but django-rosetta doesn't support the `signed_cookies` SESSION_ENGINE, because rosetta specific session files cannot be serialized." @@ -75,23 +76,23 @@ class CacheRosettaStorage(BaseRosettaStorage): 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'] + 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()).encode('utf8') + "sha1", str(time.time()).encode("utf8") ).hexdigest() - self.request.session['rosetta_cache_storage_key_prefix'] = self._key_prefix + self.request.session["rosetta_cache_storage_key_prefix"] = self._key_prefix - if 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() + "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 using the DummyCache cache backend)." @@ -99,13 +100,13 @@ class CacheRosettaStorage(BaseRosettaStorage): # Make sure the cache actually works try: - self.set('rosetta_cache_test', 'rosetta') - if not self.get('rosetta_cache_test') == 'rosetta': + 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') + self.delete("rosetta_cache_test") def get(self, key, default=None): # print ('get', self._key_prefix + key) @@ -127,6 +128,6 @@ class CacheRosettaStorage(BaseRosettaStorage): def get_storage(request): from rosetta.conf import settings - storage_module, storage_class = settings.STORAGE_CLASS.rsplit('.', 1) + storage_module, storage_class = settings.STORAGE_CLASS.rsplit(".", 1) storage_module = importlib.import_module(storage_module) return getattr(storage_module, storage_class)(request) diff --git a/rosetta/templates/rosetta/js/rosetta.js b/rosetta/templates/rosetta/js/rosetta.js index 3532b42..b2f560f 100644 --- a/rosetta/templates/rosetta/js/rosetta.js +++ b/rosetta/templates/rosetta/js/rosetta.js @@ -8,42 +8,8 @@ $(document).ready(function() { $('.hide', $(this).parent()).hide(); }); - {% if rosetta_settings.ENABLE_TRANSLATION_SUGGESTIONS %} - {% if rosetta_settings.DEEPL_AUTH_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://api-free.deepl.com/v2/translate"; - {% if deepl_language_code %} - var destLangRoot = '{{ deepl_language_code }}'; - {% else %} - var destLangRoot = '{{ rosetta_i18n_lang_code_normalized }}'.substring(0, 2); - {% endif %} - var sourceLang = '{{ rosetta_settings.MESSAGES_SOURCE_LANGUAGE_CODE }}'.substring(0, 2); - let authKey = '{{ rosetta_settings.DEEPL_AUTH_KEY }}:fx'; - - a.attr('class','suggesting').html('...'); - fetch(apiUrl, { - method: 'POST', - headers: { - "Content-Type": "application/x-www-form-urlencoded" - }, - body: `auth_key=${authKey}&text=${orig}&target_lang=${destLangRoot}` - }).then(response => { - if(response.ok) { - return response.json(); - } - }).then(data => { - trans.val(data.translations[0].text.replace(/
/g, '\n').replace(/<\/?code>/g, '').replace(/</g, '<').replace(/>/g, '>')); - }) - .catch(error => console.log(error)); - }); - {% elif rosetta_settings.AZURE_CLIENT_SECRET or rosetta_settings.GOOGLE_APPLICATION_CREDENTIALS_PATH %} + {% if rosetta_settings.DEEPL_AUTH_KEY or rosetta_settings.AZURE_CLIENT_SECRET or rosetta_settings.GOOGLE_APPLICATION_CREDENTIALS_PATH %} $('a.suggest').click(function(e){ e.preventDefault(); var a = $(this); @@ -59,7 +25,7 @@ $(document).ready(function() { $.getJSON("{% url 'rosetta.translate_text' %}", { from: sourceLang, to: destLang, - text: orig + text: orig, }, function(data) { if (data.success){ diff --git a/rosetta/templatetags/rosetta.py b/rosetta/templatetags/rosetta.py index 4101c16..e55d32e 100644 --- a/rosetta/templatetags/rosetta.py +++ b/rosetta/templatetags/rosetta.py @@ -3,17 +3,19 @@ import re from django import template from django.utils.html import escape from django.utils.safestring import mark_safe + from rosetta.access import can_translate + register = template.Library() -rx = re.compile(r'(%(\([^\s\)]*\))?[sd]|\{[\w\d_]+?\})') +rx = re.compile(r"(%(\([^\s\)]*\))?[sd]|\{[\w\d_]+?\})") can_translate = register.filter(can_translate) def format_message(message): return mark_safe( - rx.sub('\\1', escape(message).replace(r'\n', '
\n')) + rx.sub("\\1", escape(message).replace(r"\n", "
\n")) ) @@ -21,7 +23,7 @@ format_message = register.filter(format_message) def lines_count(message): - return 1 + sum([len(line) / 50 for line in message.split('\n')]) + return 1 + sum([len(line) / 50 for line in message.split("\n")]) lines_count = register.filter(lines_count) @@ -59,14 +61,14 @@ def do_incr(parser, token): if len(args) < 2: raise SyntaxError("'incr' tag requires at least one argument") name = args[1] - if not hasattr(parser, '_namedIncrNodes'): + if not hasattr(parser, "_namedIncrNodes"): parser._namedIncrNodes = {} if name not in parser._namedIncrNodes: parser._namedIncrNodes[name] = IncrNode(0) return parser._namedIncrNodes[name] -do_incr = register.tag('increment', do_incr) +do_incr = register.tag("increment", do_incr) class IncrNode(template.Node): @@ -79,7 +81,7 @@ class IncrNode(template.Node): def is_fuzzy(message): - return message and hasattr(message, 'flags') and 'fuzzy' in message.flags + return message and hasattr(message, "flags") and "fuzzy" in message.flags is_fuzzy = register.filter(is_fuzzy) diff --git a/rosetta/tests/django.po.issue186.template b/rosetta/tests/django.po.issue186.template index 8cd290c..4fa2dc9 100644 --- a/rosetta/tests/django.po.issue186.template +++ b/rosetta/tests/django.po.issue186.template @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" diff --git a/rosetta/tests/django.po.issue24gh.template b/rosetta/tests/django.po.issue24gh.template index 5a407a7..4f429e8 100644 --- a/rosetta/tests/django.po.issue24gh.template +++ b/rosetta/tests/django.po.issue24gh.template @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" diff --git a/rosetta/tests/django.po.issue34gh.template b/rosetta/tests/django.po.issue34gh.template index 3d67935..7d8ab7c 100644 --- a/rosetta/tests/django.po.issue34gh.template +++ b/rosetta/tests/django.po.issue34gh.template @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" diff --git a/rosetta/tests/django.po.issue38gh.template b/rosetta/tests/django.po.issue38gh.template index 0bb1275..cc06124 100644 --- a/rosetta/tests/django.po.issue38gh.template +++ b/rosetta/tests/django.po.issue38gh.template @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" @@ -366,6 +366,3 @@ msgstr "" msgid "xeXu5ur6xtXV69d2-7u7Fz5eD6TpYXyNVcFd28vjsZ7fnYIrzTTMEn__E_5ykGYGm-aY_7JXpx9_fXD9K-75dlH1vTvOv2w2HsZPL9zu7MdvupP-qNh5xo8PjfCLkR1kO4QUmB8CZHeW2BcGw2nYTjt7I7NcBLDuNM9PpbvPQt3le1Pex String 50" msgstr "" - - - diff --git a/rosetta/tests/django.po.issue39gh.template b/rosetta/tests/django.po.issue39gh.template index e34698c..5de40a0 100644 --- a/rosetta/tests/django.po.issue39gh.template +++ b/rosetta/tests/django.po.issue39gh.template @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" diff --git a/rosetta/tests/django.po.issue60.template b/rosetta/tests/django.po.issue60.template index 8fd0ec7..f0e93ff 100644 --- a/rosetta/tests/django.po.issue60.template +++ b/rosetta/tests/django.po.issue60.template @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" @@ -31,7 +31,3 @@ msgstr "" msgctxt "Context hint" msgid "String 4" msgstr "" - - - - diff --git a/rosetta/tests/django.po.issue67.template b/rosetta/tests/django.po.issue67.template index e55a084..8e045c5 100644 --- a/rosetta/tests/django.po.issue67.template +++ b/rosetta/tests/django.po.issue67.template @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" @@ -24,5 +24,3 @@ msgstr "" msgid "String 2" msgstr "" - - diff --git a/rosetta/tests/django.po.issue79.template b/rosetta/tests/django.po.issue79.template index 3e7bb14..024436b 100644 --- a/rosetta/tests/django.po.issue79.template +++ b/rosetta/tests/django.po.issue79.template @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" @@ -22,6 +22,3 @@ msgstr "" #~ msgid "String 2" #~ msgstr "" - - - diff --git a/rosetta/tests/django.po.template b/rosetta/tests/django.po.template index 3ae3af8..0198645 100644 --- a/rosetta/tests/django.po.template +++ b/rosetta/tests/django.po.template @@ -2,7 +2,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# msgid "" msgstr "" "Project-Id-Version: Rosetta\n" @@ -31,7 +31,3 @@ msgstr "" msgctxt "Context hint" msgid "String 4" msgstr "" - - - - diff --git a/rosetta/tests/pr44.po.template b/rosetta/tests/pr44.po.template index a5c30ef..024436b 100644 --- a/rosetta/tests/pr44.po.template +++ b/rosetta/tests/pr44.po.template @@ -22,6 +22,3 @@ msgstr "" #~ msgid "String 2" #~ msgstr "" - - - diff --git a/rosetta/tests/test_app/apps.py b/rosetta/tests/test_app/apps.py index 792790b..17d7e39 100644 --- a/rosetta/tests/test_app/apps.py +++ b/rosetta/tests/test_app/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class TestAppConfig(AppConfig): - name = 'rosetta.tests.test_app' - verbose_name = 'Rosetta test app' + name = "rosetta.tests.test_app" + verbose_name = "Rosetta test app" diff --git a/rosetta/tests/tests.py b/rosetta/tests/tests.py index bcf1d28..9015ad1 100644 --- a/rosetta/tests/tests.py +++ b/rosetta/tests/tests.py @@ -5,6 +5,7 @@ import shutil from urllib.parse import urlencode import vcr + from django import VERSION from django.conf import settings from django.core.exceptions import ImproperlyConfigured @@ -14,6 +15,7 @@ from django.test import RequestFactory, TestCase, override_settings from django.test.client import Client from django.urls import resolve, reverse from django.utils.encoding import force_bytes + from rosetta import views from rosetta.signals import entry_changed, post_save from rosetta.storage import get_storage @@ -24,20 +26,20 @@ class RosettaTestCase(TestCase): 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') + os.path.join(self.curdir, "../locale/xx/LC_MESSAGES/django.po") ) def setUp(self): from django.contrib.auth.models import User user = User.objects.create_superuser( - 'test_admin', 'test@test.com', 'test_password' + "test_admin", "test@test.com", "test_password" ) user2 = User.objects.create_superuser( - 'test_admin2', 'test@test2.com', 'test_password' + "test_admin2", "test@test2.com", "test_password" ) user3 = User.objects.create_superuser( - 'test_admin3', 'test@test2.com', 'test_password' + "test_admin3", "test@test2.com", "test_password" ) user3.is_staff = False @@ -46,13 +48,13 @@ class RosettaTestCase(TestCase): self.user = user self.client2 = Client() - self.client.login(username=user.username, password='test_password') - self.client2.login(username=user2.username, password='test_password') + self.client.login(username=user.username, password="test_password") + self.client2.login(username=user2.username, password="test_password") - shutil.copy(self.dest_file, self.dest_file + '.orig') + shutil.copy(self.dest_file, self.dest_file + ".orig") def tearDown(self): - shutil.move(self.dest_file + '.orig', self.dest_file) + shutil.move(self.dest_file + ".orig", self.dest_file) def copy_po_file_from_template(self, template_path): """Utility method to handle swapping a template po file in place for @@ -63,201 +65,201 @@ class RosettaTestCase(TestCase): @property def xx_form_url(self): - kwargs = {'po_filter': 'third-party', 'lang_id': 'xx', 'idx': 0} - return reverse('rosetta-form', kwargs=kwargs) + kwargs = {"po_filter": "third-party", "lang_id": "xx", "idx": 0} + return reverse("rosetta-form", kwargs=kwargs) @property def all_file_list_url(self): - return reverse('rosetta-file-list', kwargs={'po_filter': 'all'}) + return reverse("rosetta-file-list", kwargs={"po_filter": "all"}) @property def project_file_list_url(self): - return reverse('rosetta-file-list', kwargs={'po_filter': 'project'}) + return reverse("rosetta-file-list", kwargs={"po_filter": "project"}) @property def third_party_file_list_url(self): - return reverse('rosetta-file-list', kwargs={'po_filter': 'third-party'}) + return reverse("rosetta-file-list", kwargs={"po_filter": "third-party"}) def test_1_ListLoading(self): r = self.client.get(self.third_party_file_list_url) self.assertTrue( - os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') + os.path.normpath("rosetta/locale/xx/LC_MESSAGES/django.po") in r.content.decode() ) - @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) + @override_settings(ROSETTA_LANGUAGES=(("xx", "dummy language"),)) def test_2_PickFile(self): r = self.client.get(self.xx_form_url) - self.assertTrue('dummy language' in r.content.decode()) + self.assertTrue("dummy language" in r.content.decode()) def test_3_DownloadZIP(self): - kwargs = {'po_filter': 'third-party', 'lang_id': 'xx', 'idx': 0} - url = reverse('rosetta-download-file', kwargs=kwargs) + kwargs = {"po_filter": "third-party", "lang_id": "xx", "idx": 0} + url = reverse("rosetta-download-file", kwargs=kwargs) r = self.client.get(url) - self.assertTrue('application/x-zip' in r['content-type']) + self.assertTrue("application/x-zip" in r["content-type"]) - @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) + @override_settings(ROSETTA_LANGUAGES=(("xx", "dummy language"),)) def test_4_DoChanges(self): - self.copy_po_file_from_template('./django.po.template') - untranslated_url = self.xx_form_url + '?msg_filter=untranslated' - translated_url = self.xx_form_url + '?msg_filter=translated' + self.copy_po_file_from_template("./django.po.template") + untranslated_url = self.xx_form_url + "?msg_filter=untranslated" + translated_url = self.xx_form_url + "?msg_filter=translated" # Load the template file r = self.client.get(untranslated_url) # make sure both strings are untranslated - self.assertTrue('dummy language' in r.content.decode()) - self.assertTrue('String 1' in r.content.decode()) - self.assertTrue('String 2' in r.content.decode()) - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) + self.assertTrue("dummy language" in r.content.decode()) + self.assertTrue("String 1" in r.content.decode()) + self.assertTrue("String 2" in r.content.decode()) + self.assertTrue("m_e48f149a8b2e8baa81b816c0edf93890" in r.content.decode()) # post a translation - data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} + data = {"m_e48f149a8b2e8baa81b816c0edf93890": "Hello, world"} r = self.client.post(untranslated_url, data, follow=True) # reload all untranslated strings r = self.client.get(untranslated_url) # the translated string no longer is up for translation - self.assertTrue('String 1' in r.content.decode()) - self.assertTrue('String 2' not in r.content.decode()) + self.assertTrue("String 1" in r.content.decode()) + self.assertTrue("String 2" not in r.content.decode()) # display only translated strings r = self.client.get(translated_url) # The translation was persisted - self.assertTrue('String 1' not in r.content.decode()) - self.assertTrue('String 2' in r.content.decode()) - self.assertTrue('Hello, world' in r.content.decode()) + self.assertTrue("String 1" not in r.content.decode()) + self.assertTrue("String 2" in r.content.decode()) + self.assertTrue("Hello, world" in r.content.decode()) - @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) + @override_settings(ROSETTA_LANGUAGES=(("xx", "dummy language"),)) def test_5_TestIssue67(self): # issue 67: http://code.google.com/p/django-rosetta/issues/detail?id=67 - self.copy_po_file_from_template('./django.po.issue67.template') + self.copy_po_file_from_template("./django.po.issue67.template") # Make sure the plurals string is valid - with open(self.dest_file, 'r') as f_: + with open(self.dest_file, "r") as f_: content = f_.read() - self.assertTrue('Hello, world' not in content) - self.assertTrue('|| n%100>=20) ? 1 : 2)' in content) + self.assertTrue("Hello, world" not in content) + self.assertTrue("|| n%100>=20) ? 1 : 2)" in content) del content - r = self.client.get(self.xx_form_url + '?msg_filter=untranslated') + r = self.client.get(self.xx_form_url + "?msg_filter=untranslated") # make sure all strings are untranslated - self.assertTrue('dummy language' in r.content.decode()) - self.assertTrue('String 1' in r.content.decode()) - self.assertTrue('String 2' in r.content.decode()) - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) + self.assertTrue("dummy language" in r.content.decode()) + self.assertTrue("String 1" in r.content.decode()) + self.assertTrue("String 2" in r.content.decode()) + self.assertTrue("m_e48f149a8b2e8baa81b816c0edf93890" in r.content.decode()) # post a translation - data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} - self.client.post(self.xx_form_url + '?msg_filter=untranslated', data) + data = {"m_e48f149a8b2e8baa81b816c0edf93890": "Hello, world"} + self.client.post(self.xx_form_url + "?msg_filter=untranslated", data) # Make sure the plurals string is still valid - with open(self.dest_file, 'r') as f_: + with open(self.dest_file, "r") as f_: content = f_.read() - 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)) + 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 - @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) + @override_settings(ROSETTA_LANGUAGES=(("xx", "dummy language"),)) def test_6_ExcludedApps(self): - with self.settings(ROSETTA_EXCLUDED_APPLICATIONS=('rosetta',)): + with self.settings(ROSETTA_EXCLUDED_APPLICATIONS=("rosetta",)): r = self.client.get(self.third_party_file_list_url) - self.assertNotContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') + self.assertNotContains(r, "rosetta/locale/xx/LC_MESSAGES/django.po") with self.settings(ROSETTA_EXCLUDED_APPLICATIONS=()): r = self.client.get(self.third_party_file_list_url) - self.assertContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') + self.assertContains(r, "rosetta/locale/xx/LC_MESSAGES/django.po") def test_7_selfInApplist(self): r = self.client.get(self.third_party_file_list_url) - self.assertContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') + self.assertContains(r, "rosetta/locale/xx/LC_MESSAGES/django.po") r = self.client.get(self.project_file_list_url) - self.assertNotContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') + self.assertNotContains(r, "rosetta/locale/xx/LC_MESSAGES/django.po") - @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) + @override_settings(ROSETTA_LANGUAGES=(("xx", "dummy language"),)) def test_8_hideObsoletes(self): r = self.client.get(self.xx_form_url) # not in listing for p in range(1, 5): - r = self.client.get(self.xx_form_url + '?page=%d' % p) - self.assertTrue('dummy language' in r.content.decode()) - self.assertTrue('Les deux' not in r.content.decode()) + r = self.client.get(self.xx_form_url + "?page=%d" % p) + self.assertTrue("dummy language" in r.content.decode()) + self.assertTrue("Les deux" not in r.content.decode()) - r = self.client.get(self.xx_form_url + '?query=Les%20Deux') - self.assertContains(r, 'dummy language') - self.assertNotContains(r, 'Les deux') + r = self.client.get(self.xx_form_url + "?query=Les%20Deux") + self.assertContains(r, "dummy language") + self.assertNotContains(r, "Les deux") def test_9_concurrency(self): - self.copy_po_file_from_template('./django.po.template') - translated_url = self.xx_form_url + '?msg_filter=translated' - untranslated_url = self.xx_form_url + '?msg_filter=untranslated' + self.copy_po_file_from_template("./django.po.template") + translated_url = self.xx_form_url + "?msg_filter=translated" + untranslated_url = self.xx_form_url + "?msg_filter=untranslated" # Load the template file r = self.client.get(untranslated_url) r2 = self.client2.get(untranslated_url) - self.assertContains(r, 'String 1') - self.assertContains(r2, 'String 1') - self.assertContains(r, 'm_08e4e11e2243d764fc45a5a4fba5d0f2') + self.assertContains(r, "String 1") + self.assertContains(r2, "String 1") + self.assertContains(r, "m_08e4e11e2243d764fc45a5a4fba5d0f2") - data = {'m_08e4e11e2243d764fc45a5a4fba5d0f2': 'Hello, world'} + data = {"m_08e4e11e2243d764fc45a5a4fba5d0f2": "Hello, world"} r = self.client.post(untranslated_url, data, follow=True) # Client 2 reloads, forces a reload of the catalog; untranslated # string1 is now translated r2 = self.client2.get(untranslated_url, follow=True) - self.assertNotContains(r, 'String 1') - self.assertContains(r, 'String 2') - self.assertNotContains(r2, 'String 1') - self.assertContains(r2, 'String 2') + self.assertNotContains(r, "String 1") + self.assertContains(r, "String 2") + self.assertNotContains(r2, "String 1") + self.assertContains(r2, "String 2") r = self.client.get(untranslated_url) r2 = self.client2.get(untranslated_url) - self.assertContains(r2, 'String 2') - self.assertContains(r2, 'm_e48f149a8b2e8baa81b816c0edf93890') - self.assertContains(r, 'String 2') - self.assertContains(r, 'm_e48f149a8b2e8baa81b816c0edf93890') + self.assertContains(r2, "String 2") + self.assertContains(r2, "m_e48f149a8b2e8baa81b816c0edf93890") + self.assertContains(r, "String 2") + self.assertContains(r, "m_e48f149a8b2e8baa81b816c0edf93890") # client 2 posts! - data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world, from client two!'} + data = {"m_e48f149a8b2e8baa81b816c0edf93890": "Hello, world, from client two!"} r2 = self.client2.post(untranslated_url, data, follow=True) - self.assertNotContains(r2, 'save-conflict') + self.assertNotContains(r2, "save-conflict") # uh-oh here comes client 1 - data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world, from client one!'} + data = {"m_e48f149a8b2e8baa81b816c0edf93890": "Hello, world, from client one!"} r = self.client.post(untranslated_url, data, follow=True) # An error message is displayed - self.assertContains(r, 'save-conflict') + self.assertContains(r, "save-conflict") # client 2 won - with open(self.dest_file, 'r') as po_file: + with open(self.dest_file, "r") as po_file: pofile_content = po_file.read() - self.assertTrue('Hello, world, from client two!' in pofile_content) + self.assertTrue("Hello, world, from client two!" in pofile_content) # Both clients show all strings, error messages are gone r = self.client.get(translated_url) - self.assertNotContains(r, 'save-conflict') + self.assertNotContains(r, "save-conflict") r2 = self.client2.get(translated_url) - self.assertNotContains(r2, 'save-conflict') + self.assertNotContains(r2, "save-conflict") r = self.client.get(self.xx_form_url) - self.assertNotContains(r, 'save-conflict') + self.assertNotContains(r, "save-conflict") r2 = self.client2.get(self.xx_form_url) - self.assertNotContains(r2, 'save-conflict') + self.assertNotContains(r2, "save-conflict") # Both have client's two version - self.assertContains(r, 'Hello, world, from client two!') - self.assertContains(r2, 'Hello, world, from client two!') + self.assertContains(r, "Hello, world, from client two!") + self.assertContains(r2, "Hello, world, from client two!") def test_10_issue_79_num_entries(self): - self.copy_po_file_from_template('./django.po.issue79.template') + self.copy_po_file_from_template("./django.po.issue79.template") r = self.client.get(self.third_party_file_list_url) self.assertContains(r, '1') self.assertContains(r, '0%') @@ -269,7 +271,7 @@ class RosettaTestCase(TestCase): def test_12_issue_82_staff_user(self): self.client3 = Client() - self.client3.login(username='test_admin3', password='test_password') + self.client3.login(username="test_admin3", password="test_password") # When auth is required, we get an empty response (and a redirect) with # this user. @@ -284,178 +286,179 @@ class RosettaTestCase(TestCase): self.assertTrue(r.content.decode()) self.assertEqual(r.status_code, 200) - @override_settings(ROSETTA_LANGUAGES=(('fr', 'French'), ('xx', 'Dummy Language'))) + @override_settings(ROSETTA_LANGUAGES=(("fr", "French"), ("xx", "Dummy Language"))) def test_13_catalog_filters(self): r = self.client.get(self.third_party_file_list_url) self.assertTrue( - os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') + os.path.normpath("rosetta/locale/xx/LC_MESSAGES/django.po") in r.content.decode() ) - url = reverse('rosetta-file-list', kwargs={'po_filter': 'django'}) + url = reverse("rosetta-file-list", kwargs={"po_filter": "django"}) r = self.client.get(url) self.assertTrue( - os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') + os.path.normpath("rosetta/locale/xx/LC_MESSAGES/django.po") not in r.content.decode() ) r = self.client.get(self.all_file_list_url) self.assertTrue( - os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') + os.path.normpath("rosetta/locale/xx/LC_MESSAGES/django.po") in r.content.decode() ) r = self.client.get(self.project_file_list_url) self.assertTrue( - os.path.normpath('rosetta/locale/xx/LC_MESSAGES/django.po') + os.path.normpath("rosetta/locale/xx/LC_MESSAGES/django.po") not in r.content.decode() ) def test_14_issue_99_context_and_comments(self): r = self.client.get(self.xx_form_url) - self.assertTrue('This is a text of the base template' in r.content.decode()) - self.assertTrue('Context hint' in r.content.decode()) + self.assertTrue("This is a text of the base template" in r.content.decode()) + self.assertTrue("Context hint" in r.content.decode()) def test_15_issue_87_entry_changed_signal(self): - self.copy_po_file_from_template('./django.po.template') + self.copy_po_file_from_template("./django.po.template") r = self.client.get(self.xx_form_url) @receiver(entry_changed) def test_receiver(sender, **kwargs): - self.test_old_msgstr = kwargs.get('old_msgstr') + 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.decode()) + self.assertTrue("m_e48f149a8b2e8baa81b816c0edf93890" in r.content.decode()) # post a translation - data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} + data = {"m_e48f149a8b2e8baa81b816c0edf93890": "Hello, world"} self.client.post(self.xx_form_url, data) - self.assertTrue(self.test_old_msgstr == '') - self.assertTrue(self.test_new_msgstr == 'Hello, world') - self.assertTrue(self.test_msg_id == 'String 2') + 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): - self.copy_po_file_from_template('./django.po.template') + self.copy_po_file_from_template("./django.po.template") r = self.client.get(self.xx_form_url) @receiver(post_save) def test_receiver(sender, **kwargs): - self.test_sig_lang = kwargs.get('language_code') + self.test_sig_lang = kwargs.get("language_code") - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) + self.assertTrue("m_e48f149a8b2e8baa81b816c0edf93890" in r.content.decode()) # post a translation - data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} + data = {"m_e48f149a8b2e8baa81b816c0edf93890": "Hello, world"} self.client.post(self.xx_form_url, data) - self.assertTrue(self.test_sig_lang == 'xx') + self.assertTrue(self.test_sig_lang == "xx") del self.test_sig_lang def test_17_issue_103_post_save_signal_has_request(self): - self.copy_po_file_from_template('./django.po.template') + self.copy_po_file_from_template("./django.po.template") r = self.client.get(self.xx_form_url) @receiver(post_save) def test_receiver(sender, **kwargs): - self.test_16_has_request = 'request' in kwargs + self.test_16_has_request = "request" in kwargs - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) + self.assertTrue("m_e48f149a8b2e8baa81b816c0edf93890" in r.content.decode()) # post a translation - data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} + data = {"m_e48f149a8b2e8baa81b816c0edf93890": "Hello, world"} r = self.client.post(self.xx_form_url, data) self.assertTrue(self.test_16_has_request) del self.test_16_has_request def test_18_Test_Issue_gh24(self): - self.copy_po_file_from_template('./django.po.issue24gh.template') + self.copy_po_file_from_template("./django.po.issue24gh.template") r = self.client.get(self.xx_form_url) - self.assertTrue('m_bb9d8fe6159187b9ea494c1b313d23d4' in r.content.decode()) + self.assertTrue("m_bb9d8fe6159187b9ea494c1b313d23d4" in r.content.decode()) # Post a translation, it should have properly wrapped lines data = { - '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.' + "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." } r = self.client.post(self.xx_form_url, data) - with open(self.dest_file, 'r') as po_file: + with open(self.dest_file, "r") as po_file: pofile_content = po_file.read() self.assertTrue('"pede mollis pretium."' in pofile_content) # Again, with unwrapped lines - self.copy_po_file_from_template('./django.po.issue24gh.template') + self.copy_po_file_from_template("./django.po.issue24gh.template") with self.settings(ROSETTA_POFILE_WRAP_WIDTH=0): r = self.client.get(self.xx_form_url) - self.assertTrue('m_bb9d8fe6159187b9ea494c1b313d23d4' in r.content.decode()) + self.assertTrue("m_bb9d8fe6159187b9ea494c1b313d23d4" in r.content.decode()) r = self.client.post(self.xx_form_url, data) - with open(self.dest_file, 'r') as po_file: + with open(self.dest_file, "r") as po_file: pofile_content = po_file.read() self.assertTrue('felis eu pede mollis pretium."' in pofile_content) def test_19_Test_Issue_gh34(self): - self.copy_po_file_from_template('./django.po.issue34gh.template') + self.copy_po_file_from_template("./django.po.issue34gh.template") r = self.client.get(self.xx_form_url) - self.assertTrue('m_ff7060c1a9aae9c42af4d54ac8551f67_1' in r.content.decode()) - self.assertTrue('m_ff7060c1a9aae9c42af4d54ac8551f67_0' in r.content.decode()) - self.assertTrue('m_09f7e02f1290be211da707a266f153b3' in r.content.decode()) + self.assertTrue("m_ff7060c1a9aae9c42af4d54ac8551f67_1" in r.content.decode()) + self.assertTrue("m_ff7060c1a9aae9c42af4d54ac8551f67_0" in r.content.decode()) + self.assertTrue("m_09f7e02f1290be211da707a266f153b3" in r.content.decode()) # post a translation, it should have properly wrapped lines data = { - 'm_ff7060c1a9aae9c42af4d54ac8551f67_0': 'Foo %s', - 'm_ff7060c1a9aae9c42af4d54ac8551f67_1': 'Bar %s', - 'm_09f7e02f1290be211da707a266f153b3': 'Salut', + "m_ff7060c1a9aae9c42af4d54ac8551f67_0": "Foo %s", + "m_ff7060c1a9aae9c42af4d54ac8551f67_1": "Bar %s", + "m_09f7e02f1290be211da707a266f153b3": "Salut", } r = self.client.post(self.xx_form_url, data) - with open(self.dest_file, 'r') as po_file: + with open(self.dest_file, "r") as po_file: pofile_content = po_file.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) @override_settings( - SESSION_ENGINE='django.contrib.sessions.backends.signed_cookies', - ROSETTA_STORAGE_CLASS='rosetta.storage.CacheRosettaStorage', + SESSION_ENGINE="django.contrib.sessions.backends.signed_cookies", + ROSETTA_STORAGE_CLASS="rosetta.storage.CacheRosettaStorage", ) def test_20_Test_Issue_gh38(self): # (Have to log in again, since our session engine changed) - self.client.login(username='test_admin', password='test_password') + self.client.login(username="test_admin", password="test_password") self.assertTrue( - 'django.contrib.sessions.middleware.SessionMiddleware' in settings.MIDDLEWARE + "django.contrib.sessions.middleware.SessionMiddleware" + in settings.MIDDLEWARE ) # Only one backend to test: cache backend - self.copy_po_file_from_template('./django.po.issue38gh.template') + self.copy_po_file_from_template("./django.po.issue38gh.template") r = self.client.get(self.xx_form_url) - self.assertFalse(len(str(self.client.cookies.get('sessionid'))) > 4096) - self.assertTrue('m_9efd113f7919952523f06e0d88da9c54' in r.content.decode()) + self.assertFalse(len(str(self.client.cookies.get("sessionid"))) > 4096) + self.assertTrue("m_9efd113f7919952523f06e0d88da9c54" in r.content.decode()) - data = {'m_9efd113f7919952523f06e0d88da9c54': 'Testing cookie length'} + data = {"m_9efd113f7919952523f06e0d88da9c54": "Testing cookie length"} r = self.client.post(self.xx_form_url, data) - with open(self.dest_file, 'r') as po_file: + with open(self.dest_file, "r") as po_file: pofile_content = po_file.read() - self.assertTrue('Testing cookie length' in pofile_content) + self.assertTrue("Testing cookie length" in pofile_content) - r = self.client.get(self.xx_form_url + '?filter=translated') - self.assertTrue('Testing cookie length' in r.content.decode()) - self.assertTrue('m_9f6c442c6d579707440ba9dada0fb373' in r.content.decode()) + r = self.client.get(self.xx_form_url + "?filter=translated") + self.assertTrue("Testing cookie length" in r.content.decode()) + self.assertTrue("m_9f6c442c6d579707440ba9dada0fb373" in r.content.decode()) - @override_settings(ROSETTA_STORAGE_CLASS='rosetta.storage.CacheRosettaStorage') + @override_settings(ROSETTA_STORAGE_CLASS="rosetta.storage.CacheRosettaStorage") def test_21_concurrency_of_cache_backend(self): - self.copy_po_file_from_template('./django.po.issue38gh.template') + self.copy_po_file_from_template("./django.po.issue38gh.template") # Force caching into play by making .po file read-only os.chmod(self.dest_file, 292) # 0444 @@ -463,68 +466,68 @@ class RosettaTestCase(TestCase): self.client.get(self.xx_form_url) self.client2.get(self.xx_form_url) self.assertNotEqual( - self.client.session.get('rosetta_cache_storage_key_prefix'), - self.client2.session.get('rosetta_cache_storage_key_prefix'), + self.client.session.get("rosetta_cache_storage_key_prefix"), + self.client2.session.get("rosetta_cache_storage_key_prefix"), ) # Clean up (restore perms) os.chmod(self.dest_file, 420) # 0644 def test_22_Test_Issue_gh39(self): - self.copy_po_file_from_template('./django.po.issue39gh.template') + self.copy_po_file_from_template("./django.po.issue39gh.template") r = self.client.get(self.xx_form_url) # We have distinct hashes, even though the msgid and msgstr are identical - self.assertTrue('m_4765f7de94996d3de5975fa797c3451f' in r.content.decode()) - self.assertTrue('m_08e4e11e2243d764fc45a5a4fba5d0f2' in r.content.decode()) + self.assertTrue("m_4765f7de94996d3de5975fa797c3451f" in r.content.decode()) + self.assertTrue("m_08e4e11e2243d764fc45a5a4fba5d0f2" in r.content.decode()) - @override_settings(ROSETTA_LANGUAGES=(('xx', 'dummy language'),)) + @override_settings(ROSETTA_LANGUAGES=(("xx", "dummy language"),)) def test_23_save_header_data(self): from django.contrib.auth.models import User - self.copy_po_file_from_template('./django.po.template') + self.copy_po_file_from_template("./django.po.template") unicode_user = User.objects.create_user( - 'test_unicode', 'save_header_data@test.com', 'test_unicode' + "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') + self.client.login(username="test_unicode", password="test_unicode") # Load the template file - r = self.client.get(self.xx_form_url + '?filter=untranslated') + r = self.client.get(self.xx_form_url + "?filter=untranslated") # make sure both strings are untranslated - self.assertTrue('dummy language' in r.content.decode()) - self.assertTrue('String 1' in r.content.decode()) - self.assertTrue('String 2' in r.content.decode()) - self.assertTrue('m_e48f149a8b2e8baa81b816c0edf93890' in r.content.decode()) + self.assertTrue("dummy language" in r.content.decode()) + self.assertTrue("String 1" in r.content.decode()) + self.assertTrue("String 2" in r.content.decode()) + self.assertTrue("m_e48f149a8b2e8baa81b816c0edf93890" in r.content.decode()) # post a translation - data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} - r = self.client.post(self.xx_form_url + '?filter=untranslated', data) + data = {"m_e48f149a8b2e8baa81b816c0edf93890": "Hello, world"} + r = self.client.post(self.xx_form_url + "?filter=untranslated", data) # read the result - with open(self.dest_file, 'r') as f_: + with open(self.dest_file, "r") as f_: content = f_.read() # 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('aéaéaé aàaàaàa aâââ üüüü' in content) + self.assertTrue("Hello, world" in content) + self.assertTrue("save_header_data@test.com" in content) + self.assertTrue("aéaéaé aàaàaàa aâââ üüüü" in content) def test_24_percent_translation(self): - self.copy_po_file_from_template('./django.po.template') + self.copy_po_file_from_template("./django.po.template") # Load the template file r = self.client.get(self.xx_form_url) - self.assertTrue('Progress: 0%' in r.content.decode()) - data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} + self.assertTrue("Progress: 0%" in r.content.decode()) + data = {"m_e48f149a8b2e8baa81b816c0edf93890": "Hello, world"} r = self.client.post(self.xx_form_url, data, follow=True) - self.assertTrue('Progress: 25%' in r.content.decode()) + self.assertTrue("Progress: 25%" in r.content.decode()) def test_25_replace_access_control(self): # Test default access control allows access @@ -533,7 +536,7 @@ class RosettaTestCase(TestCase): # Now replace access control with a function reference, # and check we get redirected - with self.settings(ROSETTA_ACCESS_CONTROL_FUNCTION='rosetta.tests.no_access'): + with self.settings(ROSETTA_ACCESS_CONTROL_FUNCTION="rosetta.tests.no_access"): response = self.client.get(self.project_file_list_url) self.assertEqual(302, response.status_code) @@ -544,17 +547,18 @@ class RosettaTestCase(TestCase): self.assertEqual(302, response.status_code) def test_26_urlconf_accept_dots_and_underscores(self): - resolver_match = resolve('/rosetta/files/all/fr_FR.utf8/0/') - self.assertEqual(resolver_match.url_name, 'rosetta-form') - self.assertEqual(resolver_match.kwargs['lang_id'], 'fr_FR.utf8') + resolver_match = resolve("/rosetta/files/all/fr_FR.utf8/0/") + self.assertEqual(resolver_match.url_name, "rosetta-form") + self.assertEqual(resolver_match.kwargs["lang_id"], "fr_FR.utf8") def test_27_extended_urlconf_language_code_loads_file(self): url = reverse( - 'rosetta-form', kwargs={'po_filter': 'all', 'lang_id': 'fr_FR.utf8', 'idx': 0} + "rosetta-form", + kwargs={"po_filter": "all", "lang_id": "fr_FR.utf8", "idx": 0}, ) r = self.client.get(url) - self.assertTrue('French (France), UTF8' in r.content.decode()) - self.assertTrue('m_03a603523bd75b00414a413657acdeb2' in r.content.decode()) + self.assertTrue("French (France), UTF8" in r.content.decode()) + self.assertTrue("m_03a603523bd75b00414a413657acdeb2" in r.content.decode()) def test_28_issue_gh87(self): """Make sure that rosetta_i18n_catalog_filter is passed into the context.""" @@ -564,13 +568,13 @@ class RosettaTestCase(TestCase): ) @override_settings( - SESSION_ENGINE='django.contrib.sessions.backends.signed_cookies', - ROSETTA_STORAGE_CLASS='rosetta.storage.SessionRosettaStorage', + SESSION_ENGINE="django.contrib.sessions.backends.signed_cookies", + ROSETTA_STORAGE_CLASS="rosetta.storage.SessionRosettaStorage", ) def test_29_unsupported_p3_django_16_storage(self): if VERSION[0:2] < (2, 0): self.assertTrue( - 'django.contrib.sessions.middleware.SessionMiddleware' + "django.contrib.sessions.middleware.SessionMiddleware" in settings.MIDDLEWARE ) @@ -578,7 +582,7 @@ class RosettaTestCase(TestCase): os.chmod(self.dest_file, 292) # 0444 # (Have to log in again, since our session engine changed) - self.client.login(username='test_admin', password='test_password') + self.client.login(username="test_admin", password="test_password") with self.assertRaises(ImproperlyConfigured): self.client.get(self.xx_form_url) @@ -587,66 +591,68 @@ class RosettaTestCase(TestCase): os.chmod(self.dest_file, 420) # 0644 @override_settings( - ROSETTA_POFILENAMES=('pr44.po',), ROSETTA_LANGUAGES=(('xx', 'dummy language'),) + ROSETTA_POFILENAMES=("pr44.po",), ROSETTA_LANGUAGES=(("xx", "dummy language"),) ) def test_30_pofile_names(self): os.unlink(self.dest_file) destfile = os.path.normpath( - os.path.join(self.curdir, '../locale/xx/LC_MESSAGES/pr44.po') + os.path.join(self.curdir, "../locale/xx/LC_MESSAGES/pr44.po") ) shutil.copy( - os.path.normpath(os.path.join(self.curdir, './pr44.po.template')), destfile + os.path.normpath(os.path.join(self.curdir, "./pr44.po.template")), destfile ) r = self.client.get(self.third_party_file_list_url) - self.assertTrue('xx/LC_MESSAGES/pr44.po' in r.content.decode()) + self.assertTrue("xx/LC_MESSAGES/pr44.po" in r.content.decode()) r = self.client.get(self.xx_form_url) - self.assertTrue('dummy language' in r.content.decode()) + self.assertTrue("dummy language" in r.content.decode()) # (Clean up) os.unlink(destfile) def test_31_pr_102__exclude_paths(self): r = self.client.get(self.third_party_file_list_url) - self.assertContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') - exclude_path = os.path.normpath(os.path.join(self.curdir, '../locale')) + self.assertContains(r, "rosetta/locale/xx/LC_MESSAGES/django.po") + exclude_path = os.path.normpath(os.path.join(self.curdir, "../locale")) with self.settings(ROSETTA_EXCLUDED_PATHS=exclude_path): r = self.client.get(self.third_party_file_list_url) - self.assertNotContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') + self.assertNotContains(r, "rosetta/locale/xx/LC_MESSAGES/django.po") def test_32_pr_103__language_groups(self): - from django.contrib.auth.models import User, Group + from django.contrib.auth.models import Group, User # Default behavior: non-admins need to be in a translators group; they # see all catalogs - translators = Group.objects.create(name='translators') - translators_xx = Group.objects.create(name='translators-xx') + translators = Group.objects.create(name="translators") + translators_xx = Group.objects.create(name="translators-xx") - user4 = User.objects.create_user('test_admin4', 'test@test3.com', 'test_password') + user4 = User.objects.create_user( + "test_admin4", "test@test3.com", "test_password" + ) user4.groups.add(translators) user4.is_superuser = False user4.is_staff = True user4.save() with self.settings(ROSETTA_LANGUAGE_GROUPS=False): - self.client.login(username='test_admin4', password='test_password') + self.client.login(username="test_admin4", password="test_password") r = self.client.get(self.third_party_file_list_url) - self.assertContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') + self.assertContains(r, "rosetta/locale/xx/LC_MESSAGES/django.po") with self.settings(ROSETTA_LANGUAGE_GROUPS=True): r = self.client.get(self.third_party_file_list_url) - self.assertNotContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') + self.assertNotContains(r, "rosetta/locale/xx/LC_MESSAGES/django.po") # Now add them to the custom group user4.groups.add(translators_xx) r = self.client.get(self.third_party_file_list_url) - self.assertContains(r, 'rosetta/locale/xx/LC_MESSAGES/django.po') + self.assertContains(r, "rosetta/locale/xx/LC_MESSAGES/django.po") @override_settings( - ROSETTA_ENABLE_REFLANG=True, ROSETTA_LANGUAGES=(('xx', 'dummy language'),) + ROSETTA_ENABLE_REFLANG=True, ROSETTA_LANGUAGES=(("xx", "dummy language"),) ) def test_33_reflang(self): - self.copy_po_file_from_template('./django.po.issue60.template') + self.copy_po_file_from_template("./django.po.issue60.template") r = self.client.get(self.xx_form_url) # Verify that there's an option to select a reflang @@ -654,7 +660,7 @@ class RosettaTestCase(TestCase): '' in r.content.decode() ) - r = self.client.get(self.xx_form_url + '?ref_lang=xx') + r = self.client.get(self.xx_form_url + "?ref_lang=xx") # The translated string in the test PO file ends up in the "Reference" column self.assertTrue( 'translated-string1' in r.content.decode() @@ -673,94 +679,96 @@ class RosettaTestCase(TestCase): r = self.client.get(self.all_file_list_url) self.assertTrue('rosetta/files/all/xx/1/">Test_App' in r.content.decode()) - @override_settings(ROSETTA_STORAGE_CLASS='rosetta.storage.CacheRosettaStorage') + @override_settings(ROSETTA_STORAGE_CLASS="rosetta.storage.CacheRosettaStorage") def test_35_issue_135_display_exception_messages(self): # Note: the old version of this test looked for a 'Permission denied' # message reflected in the response. That behavior has now changed so # that changes that can't be persisted through the filesystem .po file # are saved to the cached version of the .po file. - self.copy_po_file_from_template('./django.po.template') + self.copy_po_file_from_template("./django.po.template") - r = self.client.get(self.xx_form_url + '?msg_filter=untranslated') - self.assertContains(r, 'm_e48f149a8b2e8baa81b816c0edf93890') + r = self.client.get(self.xx_form_url + "?msg_filter=untranslated") + self.assertContains(r, "m_e48f149a8b2e8baa81b816c0edf93890") # make the pofile read-only os.chmod(self.dest_file, 292) # 0444 # post a translation - data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'} + data = {"m_e48f149a8b2e8baa81b816c0edf93890": "Hello, world"} self.client.post(self.xx_form_url, data, follow=True) # Confirm that the filesystem file hasn't changed - tmpl_path = os.path.normpath(os.path.join(self.curdir, 'django.po.template')) + tmpl_path = os.path.normpath(os.path.join(self.curdir, "django.po.template")) self.assertTrue(filecmp.cmp(tmpl_path, self.dest_file)) # Confirm that the cached version has been updated - cache_key = 'po-file-%s' % self.dest_file + cache_key = "po-file-%s" % self.dest_file request = RequestFactory().get(self.xx_form_url) request.user = self.user request.session = self.client.session storage = get_storage(request) po_file = storage.get(cache_key) - entry = po_file.find('String 2') - self.assertEqual(entry.msgstr, 'Hello, world') + entry = po_file.find("String 2") + self.assertEqual(entry.msgstr, "Hello, world") # cleanup os.chmod(self.dest_file, 420) # 0644 def test_36_issue_142_complex_locales(self): r = self.client.get(self.all_file_list_url) - self.assertContains(r, 'locale/bs-Cyrl-BA/LC_MESSAGES/django.po') + self.assertContains(r, "locale/bs-Cyrl-BA/LC_MESSAGES/django.po") - @override_settings(ROSETTA_LANGUAGES=(('yy-Anot', u'Yet Another dummy language'),)) + @override_settings(ROSETTA_LANGUAGES=(("yy-Anot", "Yet Another dummy language"),)) def test_37_issue_133_complex_locales(self): r = self.client.get(self.all_file_list_url) - self.assertContains(r, 'locale/yy-Anot/LC_MESSAGES/django.po') + self.assertContains(r, "locale/yy-Anot/LC_MESSAGES/django.po") def test_38_issue_161_more_weird_locales(self): r = self.client.get(self.all_file_list_url) - self.assertTrue(r, 'locale/zh_Hans/LC_MESSAGES/django.po') + self.assertTrue(r, "locale/zh_Hans/LC_MESSAGES/django.po") def test_39_invalid_get_page(self): - url = self.xx_form_url + '?filter=untranslated' + url = self.xx_form_url + "?filter=untranslated" r = self.client.get(url) # Page not specified - self.assertEqual(r.context['page'], 1) + self.assertEqual(r.context["page"], 1) - r = self.client.get(url + '&page=') # No number given - self.assertEqual(r.context['page'], 1) + r = self.client.get(url + "&page=") # No number given + self.assertEqual(r.context["page"], 1) - r = self.client.get(url + '&page=9999') # Too-high number given - self.assertEqual(r.context['page'], 1) + r = self.client.get(url + "&page=9999") # Too-high number given + self.assertEqual(r.context["page"], 1) - r = self.client.get(url + '&page=x') # Non-number given - self.assertEqual(r.context['page'], 1) + r = self.client.get(url + "&page=x") # Non-number given + self.assertEqual(r.context["page"], 1) def test_40_issue_155_auto_compile(self): def file_hash(file_string): with open(file_string, encoding="latin-1") as file: - file_content = file.read().encode('utf-8') + file_content = file.read().encode("utf-8") return hashlib.md5(file_content).hexdigest() def message_hashes(): r = self.client.get(self.xx_form_url) - return {m.msgid: 'm_' + m.md5hash for m in r.context['rosetta_messages']} + return {m.msgid: "m_" + m.md5hash for m in r.context["rosetta_messages"]} po_file = self.dest_file - mo_file = self.dest_file[:-3] + '.mo' + mo_file = self.dest_file[:-3] + ".mo" # MO file will be compiled by default. # Get PO and MO files into an initial reference state (MO will be # created or updated) msg_hashes = message_hashes() - data = {msg_hashes['String 1']: 'Translation 1'} + data = {msg_hashes["String 1"]: "Translation 1"} self.client.post(self.xx_form_url, data) - po_file_hash_before, mo_file_hash_before = file_hash(po_file), file_hash(mo_file) + po_file_hash_before, mo_file_hash_before = file_hash(po_file), file_hash( + mo_file + ) # Make a change to the translations msg_hashes = message_hashes() - data = {msg_hashes['String 1']: 'Translation 2'} + data = {msg_hashes["String 1"]: "Translation 2"} self.client.post(self.xx_form_url, data) # Get the new hashes of the PO and MO file contents @@ -778,7 +786,7 @@ class RosettaTestCase(TestCase): mo_file_hash_after, ) msg_hashes = message_hashes() - data = {msg_hashes['String 1']: "Translation 3"} + data = {msg_hashes["String 1"]: "Translation 3"} self.client.post(self.xx_form_url, data) po_file_hash_after, mo_file_hash_after = ( file_hash(po_file), @@ -795,7 +803,7 @@ class RosettaTestCase(TestCase): mo_file_hash_after, ) msg_hashes = message_hashes() - data = {msg_hashes['String 2']: "Translation 4"} + data = {msg_hashes["String 2"]: "Translation 4"} self.client.post(self.xx_form_url, data) po_file_hash_after, mo_file_hash_after = ( file_hash(po_file), @@ -811,7 +819,7 @@ class RosettaTestCase(TestCase): mo_file_hash_after, ) msg_hashes = message_hashes() - data = {msg_hashes['String 2']: "Translation 5"} + data = {msg_hashes["String 2"]: "Translation 5"} self.client.post(self.xx_form_url, data) po_file_hash_after, mo_file_hash_after = ( file_hash(po_file), @@ -822,8 +830,8 @@ class RosettaTestCase(TestCase): self.assertNotEqual(mo_file_hash_before, mo_file_hash_after) def test_41_pr_176_embed_in_admin(self): - resp = self.client.get(reverse('admin:index')) - self.assertContains(resp, 'app-rosetta module') + resp = self.client.get(reverse("admin:index")) + self.assertContains(resp, "app-rosetta module") def _setup_view(self, view, request, *args, **kwargs): """Mimic as_view() returned callable, but returns view instance. @@ -840,12 +848,12 @@ class RosettaTestCase(TestCase): """Confirm that we're accurately determining the filesystem write-perms on our .po file. """ - self.copy_po_file_from_template('./django.po.template') + self.copy_po_file_from_template("./django.po.template") # By default, we're writable request = RequestFactory().get(self.xx_form_url) request.user = self.user - kwargs = {'po_filter': 'third-party', 'lang_id': 'xx', 'idx': 0} + kwargs = {"po_filter": "third-party", "lang_id": "xx", "idx": 0} view = self._setup_view( view=views.TranslationFormView(), request=request, **kwargs ) @@ -867,12 +875,12 @@ class RosettaTestCase(TestCase): """Confirm our class-based views properly parse/validate the path of the .po file in question derived from the url kwargs. """ - self.copy_po_file_from_template('./django.po.template') + self.copy_po_file_from_template("./django.po.template") # By default, when all goes well, we get our existing .po file path request = RequestFactory().get(self.xx_form_url) request.user = self.user - kwargs = {'po_filter': 'third-party', 'lang_id': 'xx', 'idx': 0} + kwargs = {"po_filter": "third-party", "lang_id": "xx", "idx": 0} view = self._setup_view( view=views.TranslationFormView(), request=request, **kwargs ) @@ -880,7 +888,7 @@ class RosettaTestCase(TestCase): # But if the language isn't an option, we get a 404 with self.settings( - ROSETTA_LANGUAGES=[lang for lang, __ in settings.LANGUAGES if lang != 'xx'] + ROSETTA_LANGUAGES=[lang for lang, __ in settings.LANGUAGES if lang != "xx"] ): view = self._setup_view( view=views.TranslationFormView(), request=request, **kwargs @@ -889,12 +897,12 @@ class RosettaTestCase(TestCase): view.po_file_path # And if the index doesn't correspond with a file, we get a 404 - new_kwargs = {'po_filter': 'third-party', 'lang_id': 'xx', 'idx': 9} + new_kwargs = {"po_filter": "third-party", "lang_id": "xx", "idx": 9} view = self._setup_view( view=views.TranslationFormView(), # Recycle request, even though url kwargs conflict with ones below. request=request, - **new_kwargs + **new_kwargs, ) with self.assertRaises(Http404): view.po_file_path @@ -903,8 +911,8 @@ class RosettaTestCase(TestCase): """Confirm that search of the .po file works across the various message fields. """ - self.copy_po_file_from_template('./django.po.test44.template') - url = self.xx_form_url + '?query=%s' + self.copy_po_file_from_template("./django.po.test44.template") + url = self.xx_form_url + "?query=%s" # Here's the message entry we're considering: # #. Translators: consectetur adipisicing @@ -913,33 +921,33 @@ class RosettaTestCase(TestCase): # msgstr "dolor sit amet" # It is buried at the end of the file, so without searching for it, it # shouldn't be on the page - r = self.client.get(url % '') - self.assertNotContains(r, 'Lorem') + r = self.client.get(url % "") + self.assertNotContains(r, "Lorem") # Search msgid - r = self.client.get(url % 'ipsum') - self.assertContains(r, 'Lorem') + r = self.client.get(url % "ipsum") + self.assertContains(r, "Lorem") # Search msgstr - r = self.client.get(url % 'dolor') - self.assertContains(r, 'Lorem') + r = self.client.get(url % "dolor") + self.assertContains(r, "Lorem") # Search occurences - r = self.client.get(url % 'tempor') - self.assertContains(r, 'Lorem') + r = self.client.get(url % "tempor") + self.assertContains(r, "Lorem") # Search comments - r = self.client.get(url % 'adipisicing') - self.assertContains(r, 'Lorem') + r = self.client.get(url % "adipisicing") + self.assertContains(r, "Lorem") # Search context - r = self.client.get(url % 'pellentesque') - self.assertContains(r, 'Lorem') + r = self.client.get(url % "pellentesque") + self.assertContains(r, "Lorem") def test_45_issue186_plural_msg_search(self): """Confirm that search of the .po file works for plurals.""" - self.copy_po_file_from_template('./django.po.issue186.template') - url = self.xx_form_url + '?query=%s' + self.copy_po_file_from_template("./django.po.issue186.template") + url = self.xx_form_url + "?query=%s" # Here's the message entry we're considering: # msgstr "%d Child" @@ -949,38 +957,50 @@ class RosettaTestCase(TestCase): # First, confirm that we don't ALWAYS see this particular message on the # page. - r = self.client.get(url % 'kids') - self.assertNotContains(r, 'Child') + r = self.client.get(url % "kids") + self.assertNotContains(r, "Child") # Search msgid_plural - r = self.client.get(url % 'childrenen') - self.assertContains(r, 'Child') + r = self.client.get(url % "childrenen") + self.assertContains(r, "Child") # Search msgstr[0] - r = self.client.get(url % 'tchilt') - self.assertContains(r, 'Child') + r = self.client.get(url % "tchilt") + self.assertContains(r, "Child") # Search msgstr[1] - r = self.client.get(url % 'tchildren') - self.assertContains(r, 'Child') + r = self.client.get(url % "tchildren") + self.assertContains(r, "Child") def test_46_search_string_with_unicode_symbols(self): """Confirm that search works with unicode symbols""" - url = self.xx_form_url + '?' + urlencode({'query': force_bytes(u'Лорем')}) + url = self.xx_form_url + "?" + urlencode({"query": force_bytes("Лорем")}) # It shouldn't raise r = self.client.get(url) self.assertEqual(r.status_code, 200) @vcr.use_cassette( - 'fixtures/vcr_cassettes/test_47_azure_ajax_translation.yaml', - match_on=['method', 'scheme', 'host', 'port', 'path', 'query', 'raw_body'], - record_mode='once', + "fixtures/vcr_cassettes/test_47_azure_ajax_translation.yaml", + match_on=["method", "scheme", "host", "port", "path", "query", "raw_body"], + record_mode="once", ) @override_settings(DEEPL_AUTH_KEY=None, AZURE_CLIENT_SECRET="FAKE") def test_47_azure_ajax_translation(self): r = self.client.get( - reverse('rosetta.translate_text') + '?from=en&to=fr&text=hello%20world' + reverse("rosetta.translate_text") + "?from=en&to=fr&text=hello%20world" + ) + self.assertContains(r, '"Salut tout le monde"') + + @vcr.use_cassette( + "fixtures/vcr_cassettes/test_47_2_deeps_ajax_translation.yaml", + match_on=["method", "scheme", "host", "port", "path", "query", "raw_body"], + record_mode="once", + ) + @override_settings(DEEPL_AUTH_KEY="FAKE", AZURE_CLIENT_SECRET=None) + def test_47_2_deeps_ajax_translation(self): + r = self.client.get( + reverse("rosetta.translate_text") + "?from=en&to=fr&text=hello%20world" ) self.assertContains(r, '"Salut tout le monde"') @@ -990,7 +1010,7 @@ class RosettaTestCase(TestCase): r = self.client.get(self.all_file_list_url) self.assertRedirects( r, - '{}?next=/rosetta/files/all/'.format(settings.LOGIN_URL), + "{}?next=/rosetta/files/all/".format(settings.LOGIN_URL), fetch_redirect_response=False, ) self.assertEqual(302, r.status_code) @@ -1000,68 +1020,38 @@ class RosettaTestCase(TestCase): r = self.client.get(self.all_file_list_url) self.assertEqual(200, r.status_code) - @override_settings(ROSETTA_REQUIRES_AUTH=True, ROSETTA_LOGIN_URL='/custom-url/') + @override_settings(ROSETTA_REQUIRES_AUTH=True, ROSETTA_LOGIN_URL="/custom-url/") def test_50_custom_login_url(self): self.client.logout() r = self.client.get(self.all_file_list_url) self.assertRedirects( - r, '/custom-url/?next=/rosetta/files/all/', fetch_redirect_response=False + r, "/custom-url/?next=/rosetta/files/all/", fetch_redirect_response=False ) self.assertEqual(302, r.status_code) def test_51_rosetta_languages(self): - self.assertTrue('xx' in dict(settings.LANGUAGES)) - self.assertFalse('yy' in dict(settings.LANGUAGES)) + self.assertTrue("xx" in dict(settings.LANGUAGES)) + self.assertFalse("yy" in dict(settings.LANGUAGES)) - with self.settings(ROSETTA_LANGUAGES=(('xx', 'foo language'),)): + with self.settings(ROSETTA_LANGUAGES=(("xx", "foo language"),)): r = self.client.get(self.project_file_list_url) - self.assertTrue('foo language' in r.content.decode()) - self.assertFalse('bar language' in r.content.decode()) + self.assertTrue("foo language" in r.content.decode()) + self.assertFalse("bar language" in r.content.decode()) with self.settings( - ROSETTA_LANGUAGES=(('xx', 'foo language'), ('yy', 'bar language')) + ROSETTA_LANGUAGES=(("xx", "foo language"), ("yy", "bar language")) ): r = self.client.get(self.project_file_list_url) - self.assertTrue('foo language' in r.content.decode()) - self.assertTrue('bar language' in r.content.decode()) - - def test_52_deepl_languages_handled_correctly(self): - """ - If DEEPL_LANGUAGES set in settings, we use that one, if not, we use django's language code. - """ - if settings.DEEPL_AUTH_KEY: - with self.settings(DEEPL_LANGUAGES={"fr_FR.utf8": "FR"}): - r = self.client.get( - reverse( - "rosetta-form", - kwargs={ - "po_filter": "project", - "lang_id": "fr_FR.utf8", - "idx": "0", - }, - ) - ) - self.assertContains(r, "var destLangRoot = 'FR'") - with self.settings(DEEPL_LANGUAGES=None): - r = self.client.get( - reverse( - "rosetta-form", - kwargs={ - "po_filter": "project", - "lang_id": "fr_FR.utf8", - "idx": "0", - }, - ) - ) - self.assertContains(r, "var destLangRoot = 'fr-FR.utf8'.substring(0, 2)") + self.assertTrue("foo language" in r.content.decode()) + self.assertTrue("bar language" in r.content.decode()) def test_198_embed_in_admin_access_control(self): - resp = self.client.get(reverse('admin:index')) - self.assertContains(resp, 'rosetta-content-main') + resp = self.client.get(reverse("admin:index")) + self.assertContains(resp, "rosetta-content-main") with self.settings(ROSETTA_ACCESS_CONTROL_FUNCTION=lambda user: False): - resp = self.client.get(reverse('admin:index')) - self.assertNotContains(resp, 'rosetta-content-main') + resp = self.client.get(reverse("admin:index")) + self.assertNotContains(resp, "rosetta-content-main") # Stubbed access control function diff --git a/rosetta/tests/urls.py b/rosetta/tests/urls.py index 03da957..06ae34c 100644 --- a/rosetta/tests/urls.py +++ b/rosetta/tests/urls.py @@ -1,7 +1,9 @@ from django.conf.urls import include, url + from .views import dummy + urlpatterns = [ - url(r'^rosetta/', include('rosetta.urls')), - url(r'^admin/$', dummy, name='dummy-login') + url(r"^rosetta/", include("rosetta.urls")), + url(r"^admin/$", dummy, name="dummy-login"), ] diff --git a/rosetta/translate_utils.py b/rosetta/translate_utils.py index 840c32c..28032c0 100644 --- a/rosetta/translate_utils.py +++ b/rosetta/translate_utils.py @@ -1,23 +1,39 @@ import json import uuid -from django.conf import settings - import requests +from django.conf import settings + class TranslationException(Exception): pass def translate(text, from_language, to_language): - AZURE_CLIENT_SECRET = getattr(settings, 'AZURE_CLIENT_SECRET', None) + AZURE_CLIENT_SECRET = getattr(settings, "AZURE_CLIENT_SECRET", None) GOOGLE_APPLICATION_CREDENTIALS_PATH = getattr( - settings, 'GOOGLE_APPLICATION_CREDENTIALS_PATH', None + settings, "GOOGLE_APPLICATION_CREDENTIALS_PATH", None ) - GOOGLE_PROJECT_ID = getattr(settings, 'GOOGLE_PROJECT_ID', None) + GOOGLE_PROJECT_ID = getattr(settings, "GOOGLE_PROJECT_ID", None) + DEEPL_AUTH_KEY = getattr(settings, "DEEPL_AUTH_KEY", None) - if AZURE_CLIENT_SECRET: + if DEEPL_AUTH_KEY: + deepl_language_code = None + DEEPL_LANGUAGES = getattr(settings, "DEEPL_LANGUAGES", None) + if type(DEEPL_LANGUAGES) is dict: + deepl_language_code = DEEPL_LANGUAGES.get(to_language, None) + + if deepl_language_code is None: + deepl_language_code = to_language[:2].upper() + + return translate_by_deepl( + text, + deepl_language_code.upper(), + DEEPL_AUTH_KEY, + ) + + elif AZURE_CLIENT_SECRET: return translate_by_azure(text, from_language, to_language, AZURE_CLIENT_SECRET) elif GOOGLE_APPLICATION_CREDENTIALS_PATH and GOOGLE_PROJECT_ID: return translate_by_google( @@ -28,7 +44,19 @@ def translate(text, from_language, to_language): GOOGLE_PROJECT_ID, ) else: - raise TranslationException('No translation API service is configured.') + raise TranslationException("No translation API service is configured.") + + +def translate_by_deepl(text, to_language, auth_key): + r = requests.post( + "https://api-free.deepl.com/v2/translate", + headers={"Authorization": f"DeepL-Auth-Key {auth_key}"}, + data={ + "target_lang": to_language.upper(), + "text": text, + }, + ) + return r.json().get("translations")[0].get("text") def translate_by_azure(text, from_language, to_language, subscription_key): @@ -43,13 +71,13 @@ def translate_by_azure(text, from_language, to_language, subscription_key): https://docs.microsoft.com/en-us/azure/cognitive-services/translator/reference/v3-0-translate?tabs=curl """ - AZURE_TRANSLATOR_HOST = 'https://api.cognitive.microsofttranslator.com' - AZURE_TRANSLATOR_PATH = '/translate?api-version=3.0' + AZURE_TRANSLATOR_HOST = "https://api.cognitive.microsofttranslator.com" + AZURE_TRANSLATOR_PATH = "/translate?api-version=3.0" headers = { - 'Ocp-Apim-Subscription-Key': subscription_key, - 'Content-type': 'application/json', - 'X-ClientTraceId': str(uuid.uuid4()), + "Ocp-Apim-Subscription-Key": subscription_key, + "Content-type": "application/json", + "X-ClientTraceId": str(uuid.uuid4()), } url_parameters = {"from": from_language, "to": to_language} @@ -112,18 +140,18 @@ def translate_by_google( client = google_translate.TranslationServiceClient.from_service_account_json( creadentials_path ) - parent = "projects/{}/locations/{}".format(project_id, 'global') + parent = "projects/{}/locations/{}".format(project_id, "global") try: api_response = client.translate_text( request=dict( parent=parent, contents=[text], - mime_type='text/plain', + mime_type="text/plain", source_language_code=input_language, - target_language_code=output_language.split('.', 1)[0], + target_language_code=output_language.split(".", 1)[0], ) ) except Exception as e: - raise TranslationException('Google API error: {}'.format(e)) + raise TranslationException("Google API error: {}".format(e)) else: return str(api_response.translations[0].translated_text) diff --git a/rosetta/urls.py b/rosetta/urls.py index 0d8d8b2..2843a42 100644 --- a/rosetta/urls.py +++ b/rosetta/urls.py @@ -3,37 +3,38 @@ from django.views.generic.base import RedirectView from . import views + urlpatterns = [ re_path( - r'^$', + r"^$", RedirectView.as_view( - url=reverse_lazy('rosetta-file-list', kwargs={'po_filter': 'project'}), + url=reverse_lazy("rosetta-file-list", kwargs={"po_filter": "project"}), permanent=False, ), - name='rosetta-old-home-redirect', + name="rosetta-old-home-redirect", ), re_path( - r'^files/$', + r"^files/$", RedirectView.as_view( - url=reverse_lazy('rosetta-file-list', kwargs={'po_filter': 'project'}), + url=reverse_lazy("rosetta-file-list", kwargs={"po_filter": "project"}), permanent=False, ), - name='rosetta-file-list-redirect', + name="rosetta-file-list-redirect", ), re_path( - r'^files/(?P[\w-]+)/$', + r"^files/(?P[\w-]+)/$", views.TranslationFileListView.as_view(), - name='rosetta-file-list', + name="rosetta-file-list", ), re_path( - r'^files/(?P[\w-]+)/(?P[\w\-_\.]+)/(?P\d+)/$', + r"^files/(?P[\w-]+)/(?P[\w\-_\.]+)/(?P\d+)/$", views.TranslationFormView.as_view(), - name='rosetta-form', + name="rosetta-form", ), re_path( - r'^files/(?P[\w-]+)/(?P[\w\-_\.]+)/(?P\d+)/download/$', + r"^files/(?P[\w-]+)/(?P[\w\-_\.]+)/(?P\d+)/download/$", views.TranslationFileDownload.as_view(), - name='rosetta-download-file', + name="rosetta-download-file", ), - re_path(r'^translate/$', views.translate_text, name='rosetta.translate_text'), + re_path(r"^translate/$", views.translate_text, name="rosetta.translate_text"), ] diff --git a/rosetta/views.py b/rosetta/views.py index 58db91e..93cbd4f 100644 --- a/rosetta/views.py +++ b/rosetta/views.py @@ -6,12 +6,13 @@ import zipfile from io import BytesIO from urllib.parse import urlencode +from polib import pofile + from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import user_passes_test from django.core.paginator import Paginator -from django.http import (Http404, HttpResponse, HttpResponseRedirect, - JsonResponse) +from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.encoding import force_bytes @@ -19,7 +20,6 @@ from django.utils.functional import Promise, cached_property from django.utils.translation import gettext_lazy as _ from django.views.decorators.cache import never_cache from django.views.generic import TemplateView, View -from polib import pofile from . import get_version as get_rosetta_version from .access import can_translate, can_translate_language @@ -31,7 +31,7 @@ from .translate_utils import TranslationException, translate def get_app_name(path): - return path.split('/locale')[0].split('/')[-1] + return path.split("/locale")[0].split("/")[-1] class LoginURL(Promise): @@ -43,9 +43,9 @@ class LoginURL(Promise): return rosetta_settings.LOGIN_URL -@method_decorator(never_cache, 'dispatch') +@method_decorator(never_cache, "dispatch") @method_decorator( - user_passes_test(lambda user: can_translate(user), LoginURL()), 'dispatch' + user_passes_test(lambda user: can_translate(user), LoginURL()), "dispatch" ) class RosettaBaseMixin(object): """A mixin class for Rosetta's class-based views. It provides: @@ -64,8 +64,8 @@ class RosettaBaseMixin(object): If the filter isn't in this list, throw a 404. """ - po_filter = self.kwargs.get('po_filter') - if po_filter not in {'all', 'django', 'third-party', 'project'}: + po_filter = self.kwargs.get("po_filter") + if po_filter not in {"all", "django", "third-party", "project"}: raise Http404 return po_filter @@ -97,7 +97,7 @@ class RosettaFileLevelMixin(RosettaBaseMixin): (If either of the above fail, throw a 404.) """ # (Formerly known as "rosetta_i18n_lang_code") - lang_id = self.kwargs['lang_id'] + lang_id = self.kwargs["lang_id"] if lang_id not in {lang[0] for lang in rosetta_settings.ROSETTA_LANGUAGES}: raise Http404 if not can_translate_language(self.request.user, lang_id): @@ -112,12 +112,12 @@ class RosettaFileLevelMixin(RosettaBaseMixin): Throw a 404 if a file isn't found. """ # This was formerly referred to as 'rosetta_i18n_fn' - idx = self.kwargs['idx'] + idx = self.kwargs["idx"] idx = int(idx) # idx matched url re expression; calling int() is safe - third_party_apps = self.po_filter in ('all', 'third-party') - django_apps = self.po_filter in ('all', 'django') - project_apps = self.po_filter in ('all', 'project') + third_party_apps = self.po_filter in ("all", "third-party") + django_apps = self.po_filter in ("all", "django") + project_apps = self.po_filter in ("all", "project") po_paths = find_pos( self.language_id, @@ -153,8 +153,8 @@ class RosettaFileLevelMixin(RosettaBaseMixin): # value of the meat of each entry on its side in an attribute # called "md5hash". str_to_hash = ( - str(entry.msgid) + str(entry.msgstr) + str(entry.msgctxt or '') - ).encode('utf8') + str(entry.msgid) + str(entry.msgstr) + str(entry.msgctxt or "") + ).encode("utf8") entry.md5hash = hashlib.md5(str_to_hash).hexdigest() else: storage = get_storage(self.request) @@ -167,9 +167,9 @@ class RosettaFileLevelMixin(RosettaBaseMixin): # a hashed value of the meat of each entry on its side in # an attribute called "md5hash". str_to_hash = ( - str(entry.msgid) + str(entry.msgstr) + str(entry.msgctxt or '') - ).encode('utf8') - entry.md5hash = hashlib.new('md5', str_to_hash).hexdigest() + str(entry.msgid) + str(entry.msgstr) + str(entry.msgctxt or "") + ).encode("utf8") + entry.md5hash = hashlib.new("md5", str_to_hash).hexdigest() storage.set(self.po_file_cache_key, po_file) return po_file @@ -178,7 +178,7 @@ class RosettaFileLevelMixin(RosettaBaseMixin): """Return the cache key used to save/access the .po file (when actually persisted in cache). """ - return 'po-file-%s' % self.po_file_path + return "po-file-%s" % self.po_file_path @cached_property def po_file_is_writable(self): @@ -194,15 +194,15 @@ class TranslationFileListView(RosettaBaseMixin, TemplateView): and their translation progress for a filtered list of apps/projects. """ - http_method_names = ['get'] - template_name = 'rosetta/file-list.html' + http_method_names = ["get"] + template_name = "rosetta/file-list.html" def get_context_data(self, **kwargs): context = super(TranslationFileListView, self).get_context_data(**kwargs) - third_party_apps = self.po_filter in ('all', 'third-party') - django_apps = self.po_filter in ('all', 'django') - project_apps = self.po_filter in ('all', 'project') + third_party_apps = self.po_filter in ("all", "third-party") + django_apps = self.po_filter in ("all", "django") + project_apps = self.po_filter in ("all", "project") languages = [] has_pos = False @@ -224,10 +224,10 @@ class TranslationFileListView(RosettaBaseMixin, TemplateView): languages.append((language[0], _(language[1]), po_files)) has_pos = has_pos or bool(po_paths) - context['version'] = get_rosetta_version() - context['languages'] = languages - context['has_pos'] = has_pos - context['po_filter'] = self.po_filter + context["version"] = get_rosetta_version() + context["languages"] = languages + context["has_pos"] = has_pos + context["po_filter"] = self.po_filter return context @@ -250,8 +250,8 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): # Note: due to the unorthodox nature of the form itself, we're not using # Django's generic FormView as our base class. - http_method_names = ['get', 'post'] - template_name = 'rosetta/form.html' + http_method_names = ["get", "post"] + template_name = "rosetta/form.html" def fix_nls(self, in_, out_): """Fixes submitted translations by filtering carriage returns and pairing @@ -261,7 +261,7 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): return out_ if "\r" in out_ and "\r" not in in_: - out_ = out_.replace("\r", '') + out_ = out_.replace("\r", "") if "\n" == in_[0] and "\n" != out_[0]: out_ = "\n" + out_ @@ -292,8 +292,8 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): # The message text inputs are captured as hashes of their initial # contents, preceded by "m_". Messages with plurals end with their # variation number. - single_text_input_regex = re.compile(r'^m_([0-9a-f]+)$') - plural_text_input_regex = re.compile(r'^m_([0-9a-f]+)_([0-9]+)$') + single_text_input_regex = re.compile(r"^m_([0-9a-f]+)$") + plural_text_input_regex = re.compile(r"^m_([0-9a-f]+)_([0-9]+)$") file_change = False for field_name, new_msgstr in request.POST.items(): md5hash = None @@ -315,7 +315,7 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): plural_id = None if md5hash is not None: # Empty string should be processed! - entry = self.po_file.find(md5hash, 'md5hash') + entry = self.po_file.find(md5hash, "md5hash") # If someone did a makemessage, some entries might # have been removed, so we need to check. if entry: @@ -327,13 +327,13 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): else: entry.msgstr = self.fix_nls(entry.msgid, new_msgstr) - is_fuzzy = bool(self.request.POST.get('f_%s' % md5hash, False)) - old_fuzzy = 'fuzzy' in entry.flags + is_fuzzy = bool(self.request.POST.get("f_%s" % md5hash, False)) + old_fuzzy = "fuzzy" in entry.flags if old_fuzzy and not is_fuzzy: - entry.flags.remove('fuzzy') + entry.flags.remove("fuzzy") elif not old_fuzzy and is_fuzzy: - entry.flags.append('fuzzy') + entry.flags.append("fuzzy") file_change = True @@ -358,15 +358,15 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): if file_change and self.po_file_is_writable: try: - self.po_file.metadata['Last-Translator'] = "{} {} <{}>".format( - getattr(self.request.user, 'first_name', 'Anonymous'), - getattr(self.request.user, 'last_name', 'User'), - getattr(self.request.user, 'email', 'anonymous@user.tld'), + self.po_file.metadata["Last-Translator"] = "{} {} <{}>".format( + getattr(self.request.user, "first_name", "Anonymous"), + getattr(self.request.user, "last_name", "User"), + getattr(self.request.user, "email", "anonymous@user.tld"), ) - self.po_file.metadata['X-Translated-Using'] = u"django-rosetta %s" % ( + self.po_file.metadata["X-Translated-Using"] = "django-rosetta %s" % ( get_rosetta_version() ) - self.po_file.metadata['PO-Revision-Date'] = timestamp_with_timezone() + self.po_file.metadata["PO-Revision-Date"] = timestamp_with_timezone() except UnicodeDecodeError: pass @@ -375,7 +375,7 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): po_filepath, ext = os.path.splitext(self.po_file_path) if rosetta_settings.AUTO_COMPILE: - self.po_file.save_as_mofile(po_filepath + '.mo') + self.po_file.save_as_mofile(po_filepath + ".mo") post_save.send( sender=None, language_code=self.language_id, request=self.request @@ -383,14 +383,14 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): # Try auto-reloading via the WSGI daemon mode reload mechanism should_try_wsgi_reload = ( rosetta_settings.WSGI_AUTO_RELOAD - and 'mod_wsgi.process_group' in self.request.environ - and self.request.environ.get('mod_wsgi.process_group', None) - and 'SCRIPT_FILENAME' in self.request.environ - and int(self.request.environ.get('mod_wsgi.script_reloading', 0)) + and "mod_wsgi.process_group" in self.request.environ + and self.request.environ.get("mod_wsgi.process_group", None) + and "SCRIPT_FILENAME" in self.request.environ + and int(self.request.environ.get("mod_wsgi.script_reloading", 0)) ) if should_try_wsgi_reload: try: - os.utime(self.request.environ.get('SCRIPT_FILENAME'), None) + os.utime(self.request.environ.get("SCRIPT_FILENAME"), None) except OSError: pass # Try auto-reloading via uwsgi daemon reload mechanism @@ -413,7 +413,7 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): # page number can be incremented. paginator = Paginator(self.get_entries(), rosetta_settings.MESSAGES_PER_PAGE) try: - page = int(self._request_request('page', 1)) + page = int(self._request_request("page", 1)) except ValueError: page = 1 # fall back to page 1 else: @@ -422,16 +422,16 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): if page < paginator.num_pages: page += 1 query_string_args = { - 'msg_filter': self.msg_filter, - 'query': self.query, - 'ref_lang': self.ref_lang, - 'page': page, + "msg_filter": self.msg_filter, + "query": self.query, + "ref_lang": self.ref_lang, + "page": page, } # Winnow down the query string args to non-blank ones query_string_args = {k: v for k, v in query_string_args.items() if v} return HttpResponseRedirect( "{url}?{qs}".format( - url=reverse('rosetta-form', kwargs=self.kwargs), + url=reverse("rosetta-form", kwargs=self.kwargs), qs=urlencode_safe(query_string_args), ) ) @@ -458,11 +458,11 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): # XXX: having "MSGID" at the end of the dropdown is really odd, no? # Why not instead do this? # LANGUAGES = [('', '----')] + list(settings.LANGUAGES) - LANGUAGES.append(('msgid', 'MSGID')) + LANGUAGES.append(("msgid", "MSGID")) # Determine page number & how pagination links should be displayed try: - page = int(self._request_request('page', 1)) + page = int(self._request_request("page", 1)) except ValueError: page = 1 # fall back to page 1 else: @@ -489,7 +489,7 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): break if main_language: main_lang_po_path = self.po_file_path.replace( - '/%s/' % self.language_id, '/%s/' % main_language_id + "/%s/" % self.language_id, "/%s/" % main_language_id ) # XXX: brittle; what if this path doesn't exist? Isn't a .po file? main_lang_po = pofile(main_lang_po_path) @@ -502,52 +502,47 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): dict(rosetta_settings.ROSETTA_LANGUAGES).get(self.language_id) ) # "bidi" as in "bi-directional" - rosetta_i18n_lang_bidi = self.language_id.split('-')[0] in settings.LANGUAGES_BIDI + rosetta_i18n_lang_bidi = ( + self.language_id.split("-")[0] in settings.LANGUAGES_BIDI + ) query_string_args = {} if self.msg_filter: - query_string_args['msg_filter'] = self.msg_filter + query_string_args["msg_filter"] = self.msg_filter if self.query: - query_string_args['query'] = self.query + query_string_args["query"] = self.query if self.ref_lang: - query_string_args['ref_lang'] = self.ref_lang + query_string_args["ref_lang"] = self.ref_lang # Base for pagination links; the page num itself is added in template pagination_query_string_base = urlencode_safe(query_string_args) # Base for msg filter links; it doesn't make sense to persist page # numbers in these links. We just pass in ref_lang, if it's set. filter_query_string_base = urlencode_safe( - {k: v for k, v in query_string_args.items() if k == 'ref_lang'} + {k: v for k, v in query_string_args.items() if k == "ref_lang"} ) - deepl_language_code = None - if rosetta_settings.DEEPL_LANGUAGES: - deepl_language_code = rosetta_settings.DEEPL_LANGUAGES.get( - self.language_id, None - ) - context.update( { - 'version': get_rosetta_version(), - 'LANGUAGES': LANGUAGES, - 'rosetta_settings': rosetta_settings, - 'rosetta_i18n_lang_name': rosetta_i18n_lang_name, - 'rosetta_i18n_lang_code': self.language_id, - 'rosetta_i18n_lang_code_normalized': self.language_id.replace('_', '-'), - 'rosetta_i18n_lang_bidi': rosetta_i18n_lang_bidi, - 'rosetta_i18n_filter': self.msg_filter, - 'rosetta_i18n_write': self.po_file_is_writable, - 'rosetta_messages': rosetta_messages, - 'page_range': needs_pagination and page_range, - 'needs_pagination': needs_pagination, - 'main_language': main_language, - 'rosetta_i18n_app': get_app_name(self.po_file_path), - 'page': page, - 'query': self.query, - 'pagination_query_string_base': pagination_query_string_base, - 'filter_query_string_base': filter_query_string_base, - 'paginator': paginator, - 'rosetta_i18n_pofile': self.po_file, - 'ref_lang': self.ref_lang, - 'deepl_language_code': deepl_language_code, + "version": get_rosetta_version(), + "LANGUAGES": LANGUAGES, + "rosetta_settings": rosetta_settings, + "rosetta_i18n_lang_name": rosetta_i18n_lang_name, + "rosetta_i18n_lang_code": self.language_id, + "rosetta_i18n_lang_code_normalized": self.language_id.replace("_", "-"), + "rosetta_i18n_lang_bidi": rosetta_i18n_lang_bidi, + "rosetta_i18n_filter": self.msg_filter, + "rosetta_i18n_write": self.po_file_is_writable, + "rosetta_messages": rosetta_messages, + "page_range": needs_pagination and page_range, + "needs_pagination": needs_pagination, + "main_language": main_language, + "rosetta_i18n_app": get_app_name(self.po_file_path), + "page": page, + "query": self.query, + "pagination_query_string_base": pagination_query_string_base, + "filter_query_string_base": filter_query_string_base, + "paginator": paginator, + "rosetta_i18n_pofile": self.po_file, + "ref_lang": self.ref_lang, } ) @@ -560,8 +555,8 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): Throw a 404 if it's not in rosetta_settings.ROSETTA_LANGUAGES. """ - ref_lang = self._request_request('ref_lang', 'msgid') - if ref_lang != 'msgid': + ref_lang = self._request_request("ref_lang", "msgid") + if ref_lang != "msgid": allowed_languages = {lang[0] for lang in rosetta_settings.ROSETTA_LANGUAGES} if ref_lang not in allowed_languages: raise Http404 @@ -573,11 +568,13 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): exists, otherwise None. """ ref_pofile = None - if rosetta_settings.ENABLE_REFLANG and self.ref_lang != 'msgid': - replacement = '{separator}locale{separator}{ref_lang}'.format( + if rosetta_settings.ENABLE_REFLANG and self.ref_lang != "msgid": + replacement = "{separator}locale{separator}{ref_lang}".format( separator=os.sep, ref_lang=self.ref_lang ) - pattern = r'\{separator}locale\{separator}[a-z]{{2}}'.format(separator=os.sep) + pattern = r"\{separator}locale\{separator}[a-z]{{2}}".format( + separator=os.sep + ) ref_fn = re.sub(pattern, replacement, self.po_file_path) try: ref_pofile = pofile(ref_fn) @@ -598,10 +595,10 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): if self.query: msg_filter = None else: - msg_filter = self._request_request('msg_filter', 'all') - available_msg_filters = {'untranslated', 'translated', 'fuzzy', 'all'} + msg_filter = self._request_request("msg_filter", "all") + available_msg_filters = {"untranslated", "translated", "fuzzy", "all"} if msg_filter not in available_msg_filters: - msg_filter = 'all' + msg_filter = "all" return msg_filter @cached_property @@ -609,7 +606,7 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): """Strip and return the query (for searching the catalog) from the request, or None. """ - return self._request_request('query', '').strip() or None + return self._request_request("query", "").strip() or None def get_entries(self): """Return a list of the entries (messages) that would be part of the @@ -626,9 +623,9 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): + str(e.msgid) + str(e.msgctxt) + str(e.comment) - + u''.join([o[0] for o in e.occurrences]) + + "".join([o[0] for o in e.occurrences]) + str(e.msgid_plural) - + u''.join(e.msgstr_plural.values()) + + "".join(e.msgstr_plural.values()) ) entries = [ @@ -638,11 +635,11 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView): ] else: # Scenario #2: filtered list of messages - if self.msg_filter == 'untranslated': + if self.msg_filter == "untranslated": entries = self.po_file.untranslated_entries() - elif self.msg_filter == 'translated': + elif self.msg_filter == "translated": entries = self.po_file.translated_entries() - elif self.msg_filter == 'fuzzy': + elif self.msg_filter == "fuzzy": entries = [e_ for e_ in self.po_file.fuzzy_entries() if not e_.obsolete] else: # ("all") @@ -656,16 +653,16 @@ class TranslationFileDownload(RosettaFileLevelMixin, View): disk is unwritable (permissions-wise), return what's in the cache. """ - http_method_names = [u'get'] + http_method_names = ["get"] def get(self, request, *args, **kwargs): try: - if len(self.po_file_path.split('/')) >= 5: - offered_fn = '_'.join(self.po_file_path.split('/')[-5:]) + if len(self.po_file_path.split("/")) >= 5: + offered_fn = "_".join(self.po_file_path.split("/")[-5:]) else: - offered_fn = self.po_file_path.split('/')[-1] - po_fn = str(self.po_file_path.split('/')[-1]) - mo_fn = str(po_fn.replace('.po', '.mo')) # not so smart, huh + offered_fn = self.po_file_path.split("/")[-1] + po_fn = str(self.po_file_path.split("/")[-1]) + mo_fn = str(po_fn.replace(".po", ".mo")) # not so smart, huh zipdata = BytesIO() with zipfile.ZipFile(zipdata, mode="w") as zipf: zipf.writestr(po_fn, str(self.po_file).encode("utf8")) @@ -673,33 +670,33 @@ class TranslationFileDownload(RosettaFileLevelMixin, View): zipdata.seek(0) response = HttpResponse(zipdata.read()) - filename = 'filename=%s.%s.zip' % (offered_fn, self.language_id) - response['Content-Disposition'] = 'attachment; %s' % filename - response['Content-Type'] = 'application/x-zip' + filename = "filename=%s.%s.zip" % (offered_fn, self.language_id) + response["Content-Disposition"] = "attachment; %s" % filename + response["Content-Type"] = "application/x-zip" return response except Exception: # XXX: should add a message! return HttpResponseRedirect( - reverse('rosetta-file-list', kwargs={'po_filter': 'project'}) + reverse("rosetta-file-list", kwargs={"po_filter": "project"}) ) @user_passes_test(lambda user: can_translate(user), LoginURL()) def translate_text(request): - language_from = request.GET.get('from', None) - language_to = request.GET.get('to', None) - text = request.GET.get('text', None) + 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} + data = {"success": True, "translation": text} else: try: translated_text = translate(text, language_from, language_to) - data = {'success': True, 'translation': translated_text} + data = {"success": True, "translation": translated_text} except TranslationException as e: - data = {'success': False, 'error': str(e)} + data = {"success": False, "error": str(e)} return JsonResponse(data) diff --git a/testproject/fixtures/vcr_cassettes/test_47_2_deeps_ajax_translation.yaml b/testproject/fixtures/vcr_cassettes/test_47_2_deeps_ajax_translation.yaml new file mode 100644 index 0000000..c633f57 --- /dev/null +++ b/testproject/fixtures/vcr_cassettes/test_47_2_deeps_ajax_translation.yaml @@ -0,0 +1,37 @@ +interactions: +- request: + body: target_lang=FR&text=hello+world + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - DeepL-Auth-Key FAKE + Connection: + - keep-alive + Content-Length: + - '31' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - python-requests/2.26.0 + method: POST + uri: https://api-free.deepl.com/v2/translate + response: + body: {string: '{"translations": [{"detected_source_language": "EN", "text": "Salut tout le monde"}]}'} + headers: + access-control-allow-origin: + - '*' + content-length: + - '0' + date: + - Sun, 01 Jan 2023 13:07:33 GMT + server: + - nginx + strict-transport-security: + - max-age=63072000; includeSubDomains; preload + status: + code: 200 + message: OK +version: 1 diff --git a/testproject/manage.py b/testproject/manage.py index b428d8d..57fc5ce 100644 --- a/testproject/manage.py +++ b/testproject/manage.py @@ -2,12 +2,15 @@ import os import sys + if __name__ == "__main__": - sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings") import django + django.setup() from django.core.management import execute_from_command_line + execute_from_command_line(sys.argv) diff --git a/testproject/settings.py b/testproject/settings.py index d6a2cc8..0276dd3 100644 --- a/testproject/settings.py +++ b/testproject/settings.py @@ -3,6 +3,7 @@ import sys import django + SITE_ID = 1 PROJECT_PATH = os.path.abspath(os.path.dirname(__file__)) @@ -68,13 +69,13 @@ MIDDLEWARE = ( # Note: languages are overridden in the test runner LANGUAGES = ( - ("en", u"English"), - ("bs-Cyrl-BA", u"Bosnian (Cyrillic) (Bosnia and Herzegovina)"), - ("ja", u"日本語"), - ("xx", u"XXXXX"), - ("fr", u"French"), - ("zh_Hans", u"Chinese (Simplified)"), - ("fr_FR.utf8", u"French (France), UTF8"), + ("en", "English"), + ("bs-Cyrl-BA", "Bosnian (Cyrillic) (Bosnia and Herzegovina)"), + ("ja", "日本語"), + ("xx", "XXXXX"), + ("fr", "French"), + ("zh_Hans", "Chinese (Simplified)"), + ("fr_FR.utf8", "French (France), UTF8"), ) diff --git a/testproject/templates/test.html b/testproject/templates/test.html index c2b2fa4..f056cb9 100644 --- a/testproject/templates/test.html +++ b/testproject/templates/test.html @@ -6,4 +6,4 @@ one bottle of beer on the wall {% plural %} {{num_bottles}} bottles of beer on the wall -{% endblocktrans %} \ No newline at end of file +{% endblocktrans %} diff --git a/testproject/urls.py b/testproject/urls.py index b2782a6..f38e870 100644 --- a/testproject/urls.py +++ b/testproject/urls.py @@ -2,11 +2,12 @@ from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.urls import include, re_path + admin.autodiscover() urlpatterns = [ - re_path(r'^admin/', admin.site.urls), - re_path(r'^rosetta/', include('rosetta.urls')), + re_path(r"^admin/", admin.site.urls), + re_path(r"^rosetta/", include("rosetta.urls")), ] urlpatterns += staticfiles_urlpatterns() diff --git a/tox.ini b/tox.ini index 5545feb..6bdf633 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,16 @@ [tox] envlist = flake8, - py{38,39,310}-django{32,40,41}, + py{38,39,310}-django{32,40,41,42}, gettext, docs [gh-actions] python = - 3.10: py310-django32, py310-django40, py310-django41 - 3.9: py39-django32, py39-django40, py39-django41 - 3.8: py38-django32, py38-django40, py38-django41 + 3.10: py310-django32, py310-django40, py310-django41, py310-django42 + 3.9: py39-django32, py39-django40, py39-django41, py39-django42 + 3.8: py38-django32, py38-django40, py38-django41, py38-django42 skipsdist = True @@ -21,7 +21,7 @@ requires = virtualenv>=20.3.0 [testenv] changedir = testproject commands = - python -Wd manage.py test rosetta + python -Wd manage.py test rosetta {posargs} setenv = PYTHONDONTWRITEBYTECODE=1 @@ -30,6 +30,7 @@ deps = django32: Django>=3.2,<=3.2.99 django40: Django>=4.0,<4.1 django41: Django>=4.1a,<4.2 + django42: Django>=4.2a,<4.3 pymemcache requests