diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 8239350..94d03fe 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -9,7 +9,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
- python-version: [3.8, 3.9, "3.10"]
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v1
diff --git a/CHANGES b/CHANGES
index ba8e238..0b561af 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,14 +1,23 @@
Version History
===============
-Version 0.9.9 (unreleased)
---------------------------
+Version 0.10.0
+--------------
+* Fix link to polib. (#277, thanks @gamboz)
+* Deepl: use the PRO API endpoint when using a PRO API key. (#278, thanks @nullcode)
+* Limit supported versions to Django 4.2 and 5.0, using Python 3.9, 3.10, 3.11 and 3.12
+
+
+Version 0.9.9
+-------------
+* Use packaged jQuery instead of the CDN version. (#274, thanks @stephanema1)
* Test test_47_azure_ajax_translation: avoid DNS lookup for better isolation. Should fix #233
* Adds Chinese (Simplified) translation. (#266 thanks @chenluyong)
-* Test against Django 4.1a
+* Test against Django 4.1 and 4.2
* 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
+* Replace case sensitivity check with setting (#273 thanks @patroqueeet)
Version 0.9.8
diff --git a/LICENSE b/LICENSE
index 48ad830..71cca5a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2008-2010 Marco Bonetti
+Copyright (c) 2008-2023 Marco Bonetti
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/README.rst b/README.rst
index 0204e96..83dd5ad 100644
--- a/README.rst
+++ b/README.rst
@@ -16,7 +16,12 @@ Rosetta is a `Django `_ application that facilita
Because it doesn't export any models, Rosetta doesn't create any tables in your project's database. Rosetta can be installed and uninstalled by simply adding and removing a single entry in your project's `INSTALLED_APPS` and a single line in your main ``urls.py`` file.
-Note: as of version 0.9.0, django-rosetta requires Django 1.11 or later, as of version 0.9.6, django-rosetta requires Django 2.2 or later, and as of version 0.9.9 django-rosetta supports Django 3.2 or later.
+Note:
+
+* As of version 0.9.0, django-rosetta requires Django 1.11 or later
+* As of version 0.9.6, django-rosetta requires Django 2.2 or later
+* As of version 0.9.9 django-rosetta supports Django 3.2 or later
+* As of version 0.10.0, django-rosetta requires Django 4.2 or later
********
Features
diff --git a/docs/conf.py b/docs/conf.py
index 0c78b22..c91058c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -60,7 +60,7 @@ master_doc = "index"
# General information about the project.
project = "Django Rosetta"
-copyright = "2008 – 2021 Marco Bonetti and contributors"
+copyright = "2008 – 2023 Marco Bonetti and contributors"
author = "Marco Bonetti"
@@ -73,7 +73,7 @@ release = get_version()
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
-language = None
+language = "en"
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
diff --git a/docs/settings.rst b/docs/settings.rst
index 55d5d08..a26780d 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -27,6 +27,7 @@ Rosetta can be configured via the following parameters, to be defined in your pr
* ``ROSETTA_LOGIN_URL``: Use this if you want to override the login URL for rosetta. Defaults to ``settings.LOGIN_URL``.
* ``ROSETTA_LANGUAGES``: List of languages that Rosetta will offer to translate. This is useful when you wish to translate a language that is not yet defined in ``settings.LANGUAGES``. Defaults to ``settings.LANGUAGES``.
* ``ROSETTA_SHOW_OCCURRENCES``: Determines whether occurrences (where the original text appears) should be shown next to the translations for context. Defaults to ``True``.
+* ``ROSETTA_CASE_SENSITIVE_FILESYSTEM``: Overrides auto-detection of case sensitive OS. Defaults to ``None`` which enables auto-detection. Useful when running case sensitive OS (e.g. Ubuntu) in docker on case insensitive OS (e.g. MacOS).
diff --git a/docs/usage.rst b/docs/usage.rst
index 5d6068e..518d289 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -38,4 +38,4 @@ By default Rosetta hides its own catalog files in the file selection interface (
Acknowledgments
***************
-* Rosetta uses the excellent `polib `_ library to parse and handle gettext files.
+* Rosetta uses the excellent `polib `_ library to parse and handle gettext files.
diff --git a/pyproject.toml b/pyproject.toml
index 9a9c3de..7e03953 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ 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"
+sections = "FUTURE,STDLIB,THIRDPARTY,DJANGO,DJANGO_THIRD_PARTY,FIRSTPARTY,LOCALFOLDER"
skip_glob= "**/migrations/**"
diff --git a/rosetta/__init__.py b/rosetta/__init__.py
index ff477d9..ab09801 100644
--- a/rosetta/__init__.py
+++ b/rosetta/__init__.py
@@ -1,12 +1,4 @@
-try:
- import django
-
- if django.VERSION[:3] <= (3, 2, 0):
- default_app_config = "rosetta.apps.RosettaAppConfig"
-except ImportError:
- pass
-
-VERSION = (0, 9, 9)
+VERSION = (0, 10, 0)
def get_version(limit=3):
diff --git a/rosetta/poutil.py b/rosetta/poutil.py
index da9d28b..011e4b7 100644
--- a/rosetta/poutil.py
+++ b/rosetta/poutil.py
@@ -71,11 +71,18 @@ def find_pos(lang, project_apps=True, django_apps=False, third_party_apps=False)
)
)
- case_sensitive_file_system = True
- tmphandle, tmppath = tempfile.mkstemp()
- if os.path.exists(tmppath.upper()):
- # Case insensitive file system.
- case_sensitive_file_system = False
+ # is OS case sensitive? settings preferred over auto detection
+ case_sensitive_file_system = getattr(
+ settings, "ROSETTA_CASE_SENSITIVE_FILESYSTEM", None
+ )
+
+ # in case of no settings, attempt auto detection
+ if case_sensitive_file_system is None:
+ case_sensitive_file_system = True
+ tmphandle, tmppath = tempfile.mkstemp()
+ if os.path.exists(tmppath.upper()):
+ # Case insensitive file system.
+ case_sensitive_file_system = False
# django/locale
if django_apps:
diff --git a/rosetta/templates/rosetta/base.html b/rosetta/templates/rosetta/base.html
index 48611dd..ad9cdfe 100644
--- a/rosetta/templates/rosetta/base.html
+++ b/rosetta/templates/rosetta/base.html
@@ -11,7 +11,7 @@
{% include 'rosetta/css/rosetta.css' %}
{% block extra_styles %}{% endblock %}
-
+
diff --git a/rosetta/templates/rosetta/js/rosetta.js b/rosetta/templates/rosetta/js/rosetta.js
index b2f560f..a3e4ab2 100644
--- a/rosetta/templates/rosetta/js/rosetta.js
+++ b/rosetta/templates/rosetta/js/rosetta.js
@@ -93,7 +93,7 @@ $(document).ready(function() {
if($(this).val()) {
$('.alert', $(this).parents('tr')).remove();
var RX = /%(?:\([^\s\)]*\))?[sdf]|\{[\w\d_]+?\}/g,
- origs=$(this).parents('tr').find('.original>.message').html().match(RX),
+ origs=$(this).parents('tr').find('.original span').html().match(RX),
trads=$(this).val().match(RX),
error = $('Unmatched variables');
diff --git a/rosetta/tests/tests.py b/rosetta/tests/tests.py
index 9015ad1..ece2fc6 100644
--- a/rosetta/tests/tests.py
+++ b/rosetta/tests/tests.py
@@ -2,6 +2,7 @@ import filecmp
import hashlib
import os
import shutil
+from unittest import mock
from urllib.parse import urlencode
import vcr
@@ -17,6 +18,7 @@ from django.urls import resolve, reverse
from django.utils.encoding import force_bytes
from rosetta import views
+from rosetta.poutil import find_pos
from rosetta.signals import entry_changed, post_save
from rosetta.storage import get_storage
@@ -382,14 +384,16 @@ class RosettaTestCase(TestCase):
# 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:
@@ -762,8 +766,9 @@ class RosettaTestCase(TestCase):
msg_hashes = message_hashes()
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
@@ -1053,6 +1058,23 @@ class RosettaTestCase(TestCase):
resp = self.client.get(reverse("admin:index"))
self.assertNotContains(resp, "rosetta-content-main")
+ @mock.patch("rosetta.poutil.os.path.exists")
+ def test_273_override_case_sensitivity(self, path_mock):
+ path_mock.exists.return_value = False
+ # no setting
+ find_pos("en")
+ path_mock.assert_called_with(mock.ANY)
+
+ path_mock.reset_mock()
+ with override_settings(ROSETTA_CASE_SENSITIVE_FILESYSTEM=False):
+ find_pos("en")
+ path_mock.isfile.assert_not_called()
+
+ path_mock.reset_mock()
+ with override_settings(ROSETTA_CASE_SENSITIVE_FILESYSTEM=True):
+ find_pos("en")
+ path_mock.isfile.assert_not_called()
+
# Stubbed access control function
def no_access(user):
diff --git a/rosetta/translate_utils.py b/rosetta/translate_utils.py
index 28032c0..b4f2b52 100644
--- a/rosetta/translate_utils.py
+++ b/rosetta/translate_utils.py
@@ -48,8 +48,13 @@ def translate(text, from_language, to_language):
def translate_by_deepl(text, to_language, auth_key):
+ if auth_key.lower().endswith(":fx"):
+ endpoint = "https://api-free.deepl.com"
+ else:
+ endpoint = "https://api.deepl.com"
+
r = requests.post(
- "https://api-free.deepl.com/v2/translate",
+ f"{endpoint}/v2/translate",
headers={"Authorization": f"DeepL-Auth-Key {auth_key}"},
data={
"target_lang": to_language.upper(),
diff --git a/rosetta/urls.py b/rosetta/urls.py
index 2843a42..6c53856 100644
--- a/rosetta/urls.py
+++ b/rosetta/urls.py
@@ -27,12 +27,12 @@ urlpatterns = [
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",
),
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",
),
diff --git a/setup.py b/setup.py
index e8a1cf4..ec01032 100644
--- a/setup.py
+++ b/setup.py
@@ -52,19 +52,16 @@ setup(
"Topic :: Software Development :: Localization",
"Topic :: Software Development :: Internationalization",
"Framework :: Django",
- "Framework :: Django :: 3.0",
- "Framework :: Django :: 3.1",
- "Framework :: Django :: 3.2",
- "Framework :: Django :: 4.0",
- "Programming Language :: Python :: 3.6",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3.8",
+ "Framework :: Django :: 4.2",
+ "Framework :: Django :: 5.0",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
],
include_package_data=True,
zip_safe=False,
- install_requires=["Django >= 2.2", "requests >= 2.1.0", "polib >= 1.1.0"],
+ install_requires=["Django >= 4.2", "requests >= 2.30.0", "polib >= 1.1.0"],
tests_require=["tox", "vcrpy"],
cmdclass={"test": Tox},
)
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
index c633f57..bab63a5 100644
--- a/testproject/fixtures/vcr_cassettes/test_47_2_deeps_ajax_translation.yaml
+++ b/testproject/fixtures/vcr_cassettes/test_47_2_deeps_ajax_translation.yaml
@@ -17,7 +17,7 @@ interactions:
User-Agent:
- python-requests/2.26.0
method: POST
- uri: https://api-free.deepl.com/v2/translate
+ uri: https://api.deepl.com/v2/translate
response:
body: {string: '{"translations": [{"detected_source_language": "EN", "text": "Salut tout le monde"}]}'}
headers:
diff --git a/testproject/settings.py b/testproject/settings.py
index 0276dd3..c45aa7a 100644
--- a/testproject/settings.py
+++ b/testproject/settings.py
@@ -1,8 +1,6 @@
import os
import sys
-import django
-
SITE_ID = 1
@@ -17,22 +15,13 @@ DATABASES = {
}
}
-if django.VERSION[:3] >= (3, 2, 0):
- CACHES = {
- "default": {
- "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
- "LOCATION": "127.0.0.1:11211",
- "KEY_PREFIX": "ROSETTA_TEST",
- }
- }
-else:
- CACHES = {
- "default": {
- "BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
- "LOCATION": "127.0.0.1:11211",
- "KEY_PREFIX": "ROSETTA_TEST",
- }
+CACHES = {
+ "default": {
+ "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
+ "LOCATION": "127.0.0.1:11211",
+ "KEY_PREFIX": "ROSETTA_TEST",
}
+}
# CACHES = {'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}}
@@ -52,11 +41,9 @@ INSTALLED_APPS = [
"django.contrib.sites",
"django.contrib.messages",
"rosetta",
+ "rosetta.tests.test_app.apps.TestAppConfig",
]
-if django.VERSION[0:2] >= (1, 7):
- INSTALLED_APPS.append("rosetta.tests.test_app.apps.TestAppConfig")
-
LANGUAGE_CODE = "en"
MIDDLEWARE = (
@@ -84,8 +71,6 @@ SILENCED_SYSTEM_CHECKS = ["translation.E002"]
LOCALE_PATHS = [os.path.join(PROJECT_PATH, "locale")]
-SOUTH_TESTS_MIGRATE = False
-
FIXTURE_DIRS = (os.path.join(PROJECT_PATH, "fixtures"),)
STATIC_URL = "/static/"
ROOT_URLCONF = "testproject.urls"
@@ -112,6 +97,8 @@ TEMPLATES = [
}
]
+USE_TZ = True
+
STATIC_URL = "/static/"
# SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
diff --git a/tox.ini b/tox.ini
index 8bacb0d..dd04665 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,16 +1,18 @@
[tox]
envlist =
flake8,
- py{38,39,310}-django{32,40,41},
+ py{38,39,310}-django42,
+ py{310,311,312}-django50,
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.12: py312-django50
+ 3.11: py311-django42, py311-django50
+ 3.10: py310-django42, py310-django50
+ 3.9: py39-django42
skipsdist = True
@@ -27,9 +29,8 @@ setenv =
PYTHONDONTWRITEBYTECODE=1
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
+ django50: Django>=5.0,<5.1
pymemcache
requests