Compare commits

...

12 Commits

Author SHA1 Message Date
Raphael Michel
f51fbd7df3 Review notes 2026-06-02 12:20:36 +02:00
Raphael Michel
2ad2b8515a Make ip block work in dev 2026-06-02 12:20:35 +02:00
Raphael Michel
48933056aa User email verifications: Add rate limiting (Z#23232834)
Also, refactor rate limiting into a central utility
2026-06-02 12:20:35 +02:00
George Hickman
21225e7753 Add the order_approve_info signal (#6228) 2026-06-02 08:25:36 +02:00
dependabot[bot]
759ced7268 Update sentry-sdk requirement from ==2.60.* to ==2.61.*
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.60.0...2.61.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.61.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 08:15:56 +02:00
Hijiri Umemoto
5920419e6b Translations: Update Chinese (Traditional Han script)
Currently translated at 90.0% (5677 of 6302 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/zh_Hant/

powered by weblate
2026-06-02 08:13:02 +02:00
Hijiri Umemoto
7c00383b62 Translations: Update Korean
Currently translated at 49.3% (3113 of 6302 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ko/

powered by weblate
2026-06-02 08:13:02 +02:00
Hijiri Umemoto
4361641857 Translations: Update Japanese
Currently translated at 100.0% (6302 of 6302 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2026-06-02 08:13:02 +02:00
CVZ-es
ac8f40353e Translations: Update Spanish
Currently translated at 100.0% (6302 of 6302 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2026-06-02 08:13:02 +02:00
CVZ-es
d648c83e4c Translations: Update French
Currently translated at 100.0% (6302 of 6302 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2026-06-02 08:13:02 +02:00
dependabot[bot]
5cd1775e1d Update fakeredis requirement from ==2.35.* to ==2.36.*
Updates the requirements on [fakeredis](https://github.com/cunla/fakeredis-py) to permit the latest version.
- [Release notes](https://github.com/cunla/fakeredis-py/releases)
- [Commits](https://github.com/cunla/fakeredis-py/compare/v2.35.0...v2.36.0)

---
updated-dependencies:
- dependency-name: fakeredis
  dependency-version: 2.36.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-30 10:22:59 +02:00
Raphael Michel
15d4676f98 Bump version to 2026.6.0.dev0 2026-05-27 18:16:01 +02:00
19 changed files with 346 additions and 424 deletions

View File

@@ -64,8 +64,8 @@ Backend
.. automodule:: pretix.control.signals
:members: nav_event, html_head, html_page_start, quota_detail_html, nav_topbar, nav_global, nav_organizer, nav_event_settings,
order_info, event_settings_widget, oauth_application_registered, order_position_buttons, subevent_forms,
item_formsets, order_search_filter_q, order_search_forms
order_info, order_approve_info, event_settings_widget, oauth_application_registered,
order_position_buttons, subevent_forms, item_formsets, order_search_filter_q, order_search_forms
.. automodule:: pretix.base.signals
:no-index:

View File

@@ -93,7 +93,7 @@ dependencies = [
"redis==7.4.*",
"reportlab==4.5.*",
"requests==2.32.*",
"sentry-sdk==2.60.*",
"sentry-sdk==2.61.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",
@@ -111,7 +111,7 @@ dev = [
"aiohttp==3.13.*",
"coverage",
"coveralls",
"fakeredis==2.35.*",
"fakeredis==2.36.*",
"flake8==7.3.*",
"freezegun",
"isort==8.0.*",

View File

@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2026.5.0"
__version__ = "2026.6.0.dev0"

View File

@@ -33,8 +33,6 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import hashlib
import ipaddress
import logging
from django import forms
@@ -42,13 +40,12 @@ from django.conf import settings
from django.contrib.auth.password_validation import (
password_validators_help_texts, validate_password,
)
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from pretix.base.metrics import pretix_failed_logins
from pretix.base.models import User
from pretix.helpers.dicts import move_to_end
from pretix.helpers.http import get_client_ip
from pretix.helpers.ratelimit import rate_limit, rate_limit_reset
logger = logging.getLogger(__name__)
@@ -85,45 +82,26 @@ class LoginForm(forms.Form):
else:
move_to_end(self.fields, 'keep_logged_in')
@cached_property
def ratelimit_key(self):
if not settings.HAS_REDIS:
return None
client_ip = get_client_ip(self.request)
if not client_ip:
return None
try:
client_ip = ipaddress.ip_address(client_ip)
except ValueError:
# Web server not set up correctly
return None
if client_ip.is_private:
# This is the private IP of the server, web server not set up correctly
return None
return 'pretix_login_{}'.format(hashlib.sha1(str(client_ip).encode()).hexdigest())
def clean(self):
if all(k in self.cleaned_data for k, f in self.fields.items() if f.required):
if self.ratelimit_key:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
cnt = rc.get(self.ratelimit_key)
if cnt and int(cnt) > 10:
pretix_failed_logins.inc(1, reason="ratelimit")
logger.info("Backend login rejected due to rate limit.")
raise forms.ValidationError(self.error_messages['rate_limit'], code='rate_limit')
rate_limit_kwargs = dict(include_ip_from_request=self.request, max_num=10, expire_time=300)
if rate_limit("login", **rate_limit_kwargs, increase=False):
# Check rate limit without counting up, we increase below only on failed logins
pretix_failed_logins.inc(1, reason="ratelimit")
logger.info("Backend login rejected due to rate limit.")
raise forms.ValidationError(self.error_messages['rate_limit'], code='rate_limit')
self.user_cache = self.backend.form_authenticate(self.request, self.cleaned_data)
if self.user_cache is None:
if self.ratelimit_key:
rc.incr(self.ratelimit_key)
rc.expire(self.ratelimit_key, 300)
logger.info("Backend login invalid.")
pretix_failed_logins.inc(1, reason="invalid")
# Count towards rate limit (result is ignored, we are checking above)
rate_limit("login", **rate_limit_kwargs)
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login'
)
else:
rate_limit_reset("login", include_ip_from_request=self.request)
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data

View File

@@ -33,7 +33,6 @@
# License for the specific language governing permissions and limitations under the License.
from django import forms
from django.conf import settings
from django.contrib.auth.hashers import check_password
from django.contrib.auth.password_validation import (
password_validators_help_texts, validate_password,
@@ -46,6 +45,7 @@ from pytz import common_timezones
from pretix.base.models import User
from pretix.control.forms import SingleLanguageWidget
from pretix.helpers.format import format_map
from pretix.helpers.ratelimit import rate_limit
class UserSettingsForm(forms.ModelForm):
@@ -128,16 +128,11 @@ class UserPasswordChangeForm(forms.Form):
def clean_old_pw(self):
old_pw = self.cleaned_data.get('old_pw')
if settings.HAS_REDIS:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
cnt = rc.incr('pretix_pwchange_%s' % self.user.pk)
rc.expire('pretix_pwchange_%s' % self.user.pk, 300)
if cnt > 10:
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
if rate_limit("pwchange", self.user.pk, max_num=10, expire_time=300):
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
if not check_password(old_pw, self.user.password):
raise forms.ValidationError(
@@ -175,19 +170,35 @@ class UserEmailChangeForm(forms.Form):
error_messages = {
'duplicate_identifier': _("There already is an account associated with this email address. "
"Please choose a different one."),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
}
old_email = forms.EmailField(label=_('Old email address'), disabled=True)
new_email = forms.EmailField(label=_('New email address'))
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
self.request = kwargs.pop('request')
super().__init__(*args, **kwargs)
def clean_new_email(self):
email = self.cleaned_data['new_email']
if rate_limit("emailchange_attempt", include_ip_from_request=self.request, max_num=5, expire_time=300):
# Rate limit lookup for conflicting email addresses to make enumeration harder
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
if User.objects.filter(Q(email__iexact=email) & ~Q(pk=self.user.pk)).exists():
raise forms.ValidationError(
self.error_messages['duplicate_identifier'],
code='duplicate_identifier',
)
if rate_limit("emailchange", self.user.pk, max_num=1, expire_time=300):
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
return email

View File

@@ -261,6 +261,16 @@ As with all event plugin signals, the ``sender`` keyword argument will contain t
Additionally, the argument ``order`` and ``request`` are available.
"""
order_approve_info = EventPluginSignal()
"""
Arguments: ``order``, ``request``
This signal is sent out to display additional information on the order approve page
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
Additionally, the argument ``order`` and ``request`` are available.
"""
order_position_buttons = EventPluginSignal()
"""
Arguments: ``order``, ``position``, ``request``

View File

@@ -1,4 +1,5 @@
{% extends "pretixcontrol/event/base.html" %}
{% load eventsignal %}
{% load i18n %}
{% block title %}
{% trans "Approve order" %}
@@ -7,6 +8,9 @@
<h1>
{% trans "Approve order" %}
</h1>
{% eventsignal request.event "pretix.control.signals.order_approve_info" order=order request=request %}
<p>{% blocktrans trimmed %}
Do you really want to approve this order?
{% endblocktrans %}</p>

View File

@@ -65,6 +65,7 @@ from pretix.base.forms.auth import (
from pretix.base.metrics import pretix_failed_logins, pretix_successful_logins
from pretix.base.models import TeamInvite, U2FDevice, User, WebAuthnDevice
from pretix.helpers.http import get_client_ip, redirect_to_url
from pretix.helpers.ratelimit import rate_limit, rate_limit_reset
from pretix.helpers.security import handle_login_source, session_login
logger = logging.getLogger(__name__)
@@ -318,19 +319,12 @@ class Forgot(TemplateView):
if self.form.is_valid():
email = self.form.cleaned_data['email']
has_redis = settings.HAS_REDIS
try:
user = User.objects.get(is_active=True, auth_backend='native', email__iexact=email)
if has_redis:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
if rc.exists('pretix_pwreset_%s' % (user.id)):
user.log_action('pretix.control.auth.user.forgot_password.denied.repeated')
raise RepeatedResetDenied()
else:
rc.setex('pretix_pwreset_%s' % (user.id), 3600 * 24, '1')
if rate_limit("pwreset", user.pk, max_num=1, expire_time=3600 * 24):
user.log_action('pretix.control.auth.user.forgot_password.denied.repeated')
raise RepeatedResetDenied()
except User.DoesNotExist:
logger.warning('Backend password reset for unregistered e-mail \"' + email + '\" requested.')
@@ -343,6 +337,7 @@ class Forgot(TemplateView):
user.log_action('pretix.control.auth.user.forgot_password.mail_sent')
finally:
has_redis = settings.HAS_REDIS
if has_redis:
messages.info(request, _('If the address is registered to valid account, then we have sent you an email containing further instructions. '
'Please note that we will send at most one email every 24 hours.'))
@@ -411,11 +406,7 @@ class Recover(TemplateView):
messages.success(request, _('You can now login using your new password.'))
user.log_action('pretix.control.auth.user.forgot_password.recovered')
has_redis = settings.HAS_REDIS
if has_redis:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
rc.delete('pretix_pwreset_%s' % user.id)
rate_limit_reset("pwreset", user.pk)
return redirect('control:auth.login')
else:
return self.get(request, *args, **kwargs)

View File

@@ -80,6 +80,7 @@ from pretix.control.permissions import (
)
from pretix.control.views.auth import get_u2f_appid, get_webauthn_rp_id
from pretix.helpers.http import redirect_to_url
from pretix.helpers.ratelimit import rate_limit
from pretix.helpers.security import session_reauth
from pretix.helpers.u2f import websafe_encode
@@ -879,6 +880,7 @@ class UserEmailChangeView(RecentAuthenticationRequiredMixin, FormView):
return {
**super().get_form_kwargs(),
"request": self.request,
"user": self.request.user,
}
@@ -908,6 +910,10 @@ class UserEmailVerifyView(View):
messages.success(self.request, _('Your email address was already verified.'))
return redirect(reverse('control:user.settings', kwargs={}))
if rate_limit("emailverify", self.request.user.pk, max_num=2, expire_time=300):
messages.error(self.request, _("For security reasons, please wait 5 minutes before you try again."))
return redirect(reverse('control:user.settings', kwargs={}))
self.request.user.send_confirmation_code(
session=self.request.session,
reason='email_verify',

View File

@@ -0,0 +1,116 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import hashlib
import ipaddress
import logging
from django.conf import settings
from django.http import HttpRequest
from pretix.helpers.http import get_client_ip
logger = logging.getLogger(__name__)
def _get_key(key, parameters):
return f'pretix:ratelimit:{key}:' + hashlib.sha256(','.join(str(p) for p in parameters).encode()).hexdigest()
def _get_ip(request):
client_ip = get_client_ip(request)
if not client_ip:
return None
try:
client_ip = ipaddress.ip_address(client_ip)
except ValueError:
# Web server not set up correctly
return None
if client_ip.is_private and not settings.DEBUG:
# This is the private IP of the server, web server not set up correctly
return None
return str(client_ip)
def rate_limit(key: str, *parameters, include_ip_from_request: HttpRequest=None, max_num: int, expire_time: int, increase: bool = True):
"""
This is a shared utility to implement simple rate limiting in operations like
password resets.
:param key: The key referring to the feature like "pwreset"
:param parameters: Any number of things to be hashed as the bucket key
:param include_ip_from_request: Add IP address from request to the bucket key. If IP address cannot be determined,
rate limit is not applied.
:param max_num: The maximum number of actions to performed within expire_time of the first action
:param expire_time: The length of the time window in seconds
:param increase: Whether to count the call as an event counted towards the rate, or just check
:return:
"""
if not settings.HAS_REDIS:
# No rate limiting
return False
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
if include_ip_from_request:
ip = _get_ip(include_ip_from_request)
if not ip:
# IP not discovered, can't rate limit
return False
parameters = (*parameters, ip)
redis_key = _get_key(key, parameters)
if increase:
p = rc.pipeline()
p.set(redis_key, 0, nx=True, ex=expire_time) # Start a rate limit window if none is running
p.incr(redis_key)
new_counter = p.execute()[1]
else:
new_counter = int(rc.get(redis_key) or 0)
if new_counter > max_num:
return True
return False
def rate_limit_reset(key: str, *parameters, include_ip_from_request: HttpRequest=None):
"""
Reset a rate limit bucket.
"""
if not settings.HAS_REDIS:
# No rate limiting
return
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
if include_ip_from_request:
ip = _get_ip(include_ip_from_request)
if not ip:
# IP not discovered, can't rate limit
return False
parameters = (*parameters, ip)
redis_key = _get_key(key, parameters)
rc.delete(redis_key)

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-27 15:47+0000\n"
"PO-Revision-Date: 2026-05-01 21:00+0000\n"
"Last-Translator: Paul Berschick <paul@plainschwarz.com>\n"
"PO-Revision-Date: 2026-05-29 17:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\n"
"Language: es\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.17\n"
"X-Generator: Weblate 2026.5\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
#: pretix/control/templates/pretixcontrol/events/index.html:166
@@ -620,16 +620,17 @@ msgstr ""
"como variaciones o paquetes."
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Quota handling"
msgid "Quota changed"
msgstr "Gestión de cuotas"
msgstr "Se ha modificado la cuota"
#: pretix/api/webhooks.py:414
msgid ""
"This includes related events like creation, deletion, opening or closing of "
"quotas. No webhook is sent for changes to the resulting availability."
msgstr ""
"Esto incluye acciones relacionadas, como la creación, la eliminación, la "
"apertura o el cierre de cuotas. No se envía ningún webhook cuando se "
"producen cambios en la disponibilidad resultante."
#: pretix/api/webhooks.py:419
msgid "Shop taken live"
@@ -3418,11 +3419,13 @@ msgid ""
"The field \"%(label)s\" may not contain special characters such as "
"\"%(chars)s\"."
msgstr ""
"El campo «%(label)s» no puede contener caracteres especiales como «%(chars)s"
"»."
#: pretix/base/forms/questions.py:305
#, python-format
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
msgstr ""
msgstr "El campo «%(label)s» no puede contener una URL (%(url)s)."
#: pretix/base/forms/questions.py:338
msgctxt "phonenumber"
@@ -8361,19 +8364,14 @@ msgid "Program times"
msgstr "Horarios del programa"
#: pretix/base/pdf.py:503
#, fuzzy
#| msgid ""
#| "2017-05-31 10:00 12:00\n"
#| "2017-05-31 14:00 16:00\n"
#| "2017-05-31 14:00 2017-06-01 14:00"
msgid ""
"2017-05-31 10:00 12:00, Room 1\n"
"2017-05-31 14:00 16:00, Room 2\n"
"2017-05-31 14:00 2017-06-01 14:00, Building A"
msgstr ""
"2017-05-31 10:00 12:00\n"
"2017-05-31 14:00 16:00\n"
"2017-05-31 14:00 2017-06-01 14:00"
"31 de mayo de 2017, de 10:00 a 12:00, Sala 1\n"
"31 de mayo de 2017, de 14:00 a 16:00, Sala 2\n"
"31 de mayo de 2017, de 14:00 a 1 de junio de 2017, 14:00, Edificio A"
#: pretix/base/pdf.py:507
msgid "Reusable Medium ID"
@@ -8903,13 +8901,7 @@ msgid "This voucher code is not known in our database."
msgstr "Este vale de compra no se conoce en nuestra base de datos."
#: pretix/base/services/cart.py:165
#, fuzzy, python-format
#| msgid ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products."
#| msgid_plural ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products."
#, python-format
msgid ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching product."
@@ -8917,22 +8909,14 @@ msgid_plural ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching products."
msgstr[0] ""
"El vale de compra \"%(voucher)s\" solo se puede utilizar si selecciona al "
"menos %(number)s productos coincidentes."
"El código de descuento «%(voucher)s» solo se puede utilizar si seleccionas "
"al menos%(number)s productos que cumplan los requisitos."
msgstr[1] ""
"Los vales de compra \"%(voucher)s\" solo se pueden utilizar si selecciona al "
"menos %(number)s productos coincidentes."
"El código de descuento «%(voucher)s» solo se puede utilizar si seleccionas "
"al menos %(number)s productos que cumplan los requisitos."
#: pretix/base/services/cart.py:170
#, fuzzy, python-format
#| msgid ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products. We have therefore removed some positions "
#| "from your cart that can no longer be purchased like this."
#| msgid_plural ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products. We have therefore removed some positions "
#| "from your cart that can no longer be purchased like this."
#, python-format
msgid ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching product. We have therefore removed some positions from "
@@ -8942,13 +8926,15 @@ msgid_plural ""
"%(number)s matching products. We have therefore removed some positions from "
"your cart that can no longer be purchased like this."
msgstr[0] ""
"El vale de compra \"%(voucher)s\" solo se puede utilizar si selecciona al "
"menos %(number)s productos coincidentes. Por lo tanto, hemos eliminado "
"algunas posiciones de su carrito que ya no se pueden comprar así."
"El código promocional «%(voucher)s» solo se puede utilizar si seleccionas al "
"menos %(number)s producto que cumpla los requisitos. Por lo tanto, hemos "
"eliminado de tu carrito algunos artículos que ya no se pueden comprar de "
"esta forma."
msgstr[1] ""
"Los vale de compra \"%(voucher)s\" solo se pueden utilizar si selecciona al "
"menos %(number)s productos coincidentes. Por lo tanto, hemos eliminado "
"algunas posiciones de su carrito que ya no se pueden comprar así."
"El código promocional «%(voucher)s» solo se puede utilizar si seleccionas al "
"menos %(number)s productos que cumplan los requisitos. Por lo tanto, hemos "
"eliminado de tu carrito algunos artículos que ya no se pueden comprar de "
"esta forma."
#: pretix/base/services/cart.py:176
msgid ""
@@ -14254,6 +14240,8 @@ msgid ""
"You entered an URL, which is not allowed. Please remove %(match)s from your "
"input."
msgstr ""
"Ha introducido una URL que no está permitida. Elimina %(match)s de su "
"entrada."
#: pretix/base/views/errors.py:48
msgid ""
@@ -16194,14 +16182,8 @@ msgid "inactive"
msgstr "inactivo"
#: pretix/control/forms/item.py:1414
#, fuzzy
#| msgid ""
#| "Sample Conference Center\n"
#| "Heidelberg, Germany"
msgid "Sample Conference Center, Heidelberg, Germany"
msgstr ""
"Ejemplo de Centro de Conferencia \n"
"Heidelberg, Alemania"
msgstr "Ejemplo de Centro de Conferencia : Heidelberg, Alemania"
#: pretix/control/forms/mailsetup.py:42
msgid "Hostname"
@@ -23659,11 +23641,8 @@ msgid "Quota history"
msgstr "Historial de cuotas"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:6
#, fuzzy
#| msgctxt "subevent"
#| msgid "Change multiple dates"
msgid "Change multiple quotas"
msgstr "Cambiar varias fechas"
msgstr "Modificar varias cuotas"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:8
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html:8
@@ -23713,18 +23692,15 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:4
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:6
#, fuzzy
#| msgid "Delete quota"
msgid "Delete quotas"
msgstr "Borrar cuota"
msgstr "Eliminar cuotas"
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:10
#, fuzzy, python-format
#| msgid "Are you sure you want to delete the following dates?"
#, python-format
msgid "Are you sure you want to delete the following quota?"
msgid_plural "Are you sure you want to delete the following %(num)s quotas?"
msgstr[0] "¿Está seguro de que desea borrar las fechas siguientes?"
msgstr[1] "¿Está seguro de que desea borrar las fechas siguientes?"
msgstr[0] "¿Está seguro de que desea eliminar la siguiente cuota?"
msgstr[1] "¿Está seguro de que desea eliminar las siguientes %(num)s cuotas?"
#: pretix/control/templates/pretixcontrol/items/quotas.html:9
msgid ""
@@ -24329,12 +24305,15 @@ msgid ""
"generated once the customer pays the invoice or selects a payment method "
"that requires an invoice."
msgstr ""
"Este pedido se modificó después de que se generara la última factura. Aún no "
"se ha generado una nueva factura, ya que las facturas están configuradas "
"para generarse al realizar el pago o si así lo exige la forma de pago. Se "
"generará una nueva factura una vez que el cliente abone la factura o "
"seleccione una forma de pago que requiera una factura."
#: pretix/control/templates/pretixcontrol/order/index.html:152
#, fuzzy
#| msgid "Request invoice"
msgid "Reissue invoice"
msgstr "Solicitar factura"
msgstr "Reemitir factura"
#: pretix/control/templates/pretixcontrol/order/index.html:161
#: pretix/control/templates/pretixcontrol/order/index.html:413
@@ -24765,23 +24744,16 @@ msgid "How should the refund be sent?"
msgstr "¿Cómo se debe de realizar este reembolso?"
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:25
#, fuzzy
#| msgid ""
#| "Any payments that you selected for automatical refunds will be "
#| "immediately communicate the refund request to the respective payment "
#| "provider. Manual refunds will be created as pending refunds, you can then "
#| "later mark them as done once you actually transferred the money back to "
#| "the customer."
msgid ""
"Any payments you selected for automatic refunds will have the refund request "
"sent immediately to the respective payment provider. Manual refunds will be "
"created as pending refunds, which you can later mark as done once you have "
"actually transferred the money back to the customer."
msgstr ""
"Cualquier pago que haya seleccionado de manera automática para reembolso "
"será comunicado inmediatamente a la entidad de pago correspondiente. Los "
"devoluciones manuales se crearán como reembolsos pendientes, podrá marcarlos "
"como hechos una vez que se haya transferido el dinero al cliente."
"Los pagos que hayas seleccionado para reembolsos automáticos se enviarán "
"inmediatamente al proveedor de pagos correspondiente. Los reembolsos "
"manuales se crearán como reembolsos pendientes, que podrás marcar como "
"completados más adelante, una vez que hayas devuelto el dinero al cliente."
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:32
msgid "Refund to original payment method"
@@ -29337,11 +29309,8 @@ msgid "The new question has been created."
msgstr "La nueva pregunta ha sido creada."
#: pretix/control/views/item.py:918
#, fuzzy
#| msgctxt "subevent"
#| msgid "The selected dates have been deleted or disabled."
msgid "The selected quotas have been deleted or disabled."
msgstr "Las fechas seleccionadas se han borrado o desactivado."
msgstr "Las cuotas seleccionadas se han eliminado o desactivado."
#: pretix/control/views/item.py:1074
msgid "The new quota has been created."
@@ -30073,11 +30042,9 @@ msgstr ""
"Este plugin no está permitido actualmente para su cuenta de organizador."
#: pretix/control/views/organizer.py:832
#, fuzzy, python-brace-format
#| msgid "This plugin can be enabled or disabled for events individually."
#, python-brace-format
msgid "This plugin cannot be activated for event {}."
msgstr ""
"Este plugin se puede activar o desactivar para eventos de forma individual."
msgstr "Este complemento no se puede activar para el evento {}."
#: pretix/control/views/organizer.py:901
msgid "The team has been created. You can now add members to the team."
@@ -31122,10 +31089,9 @@ msgid "{width} x {height} mm label"
msgstr "etiqueta {width} x {height} mm"
#: pretix/plugins/badges/templates.py:265
#, fuzzy, python-brace-format
#| msgid "{width} x {height} mm label"
#, python-brace-format
msgid "{width} x {height} inch label"
msgstr "etiqueta {width} x {height} mm"
msgstr "Etiqueta de {width} x {height} pulgadas"
#: pretix/plugins/badges/templates/pretixplugins/badges/control_order_info.html:16
#: pretix/plugins/badges/templates/pretixplugins/badges/index.html:27

View File

@@ -4,16 +4,16 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-27 15:47+0000\n"
"PO-Revision-Date: 2026-05-08 04:00+0000\n"
"Last-Translator: corentin-spec <corentin@spectentaculaire.fr>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
">\n"
"PO-Revision-Date: 2026-05-29 17:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/"
"fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.17.1\n"
"X-Generator: Weblate 2026.5\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
#: pretix/control/templates/pretixcontrol/events/index.html:166
@@ -618,16 +618,17 @@ msgstr ""
"aux objets imbriqués tels que les variantes ou les lots."
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Quota handling"
msgid "Quota changed"
msgstr "Traitement des quotas"
msgstr "Quota modifié"
#: pretix/api/webhooks.py:414
msgid ""
"This includes related events like creation, deletion, opening or closing of "
"quotas. No webhook is sent for changes to the resulting availability."
msgstr ""
"Cela inclut les événements associés, tels que la création, la suppression, "
"l'ouverture ou la suppression de quotas. Aucun webhook n'est envoyé en cas "
"de modification de la disponibilité qui en résulte."
#: pretix/api/webhooks.py:419
msgid "Shop taken live"
@@ -3422,11 +3423,13 @@ msgid ""
"The field \"%(label)s\" may not contain special characters such as "
"\"%(chars)s\"."
msgstr ""
"Le champ « %(label)s » ne doit pas contenir de caractères spéciaux tels que "
"«%(chars)s »."
#: pretix/base/forms/questions.py:305
#, python-format
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
msgstr ""
msgstr "Le champ « %(label)s » ne doit pas contenir d'URL (%(url)s)."
#: pretix/base/forms/questions.py:338
msgctxt "phonenumber"
@@ -8409,19 +8412,14 @@ msgid "Program times"
msgstr "Horaires du programme"
#: pretix/base/pdf.py:503
#, fuzzy
#| msgid ""
#| "2017-05-31 10:00 12:00\n"
#| "2017-05-31 14:00 16:00\n"
#| "2017-05-31 14:00 2017-06-01 14:00"
msgid ""
"2017-05-31 10:00 12:00, Room 1\n"
"2017-05-31 14:00 16:00, Room 2\n"
"2017-05-31 14:00 2017-06-01 14:00, Building A"
msgstr ""
"2017-05-31 10:00 12:00\n"
"2017-05-31 14:00 16:00\n"
"2017-05-31 14:00 2017-06-01 14:00"
"31 mai 2017, de 10 h à 12 h, salle 1\n"
"31 mai 2017, de 14 h à 16 h, salle 2\n"
"Du 31 mai 2017 à 1 h du matin au 1er juin 2017 à 14 h, bâtiment A"
#: pretix/base/pdf.py:507
msgid "Reusable Medium ID"
@@ -8957,13 +8955,7 @@ msgid "This voucher code is not known in our database."
msgstr "Ce code promotionnel n'est pas connu dans notre base de données."
#: pretix/base/services/cart.py:165
#, fuzzy, python-format
#| msgid ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products."
#| msgid_plural ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products."
#, python-format
msgid ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching product."
@@ -8971,22 +8963,14 @@ msgid_plural ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching products."
msgstr[0] ""
"Le code promo \"%(voucher)s\" ne peut être utilisé que si vous sélectionnez "
"Le code promo « %(voucher)s » ne peut être utilisé que si vous sélectionnez "
"au moins %(number)s produit correspondant."
msgstr[1] ""
"Le code promo \"%(voucher)s\" ne peut être utilisé que si vous sélectionnez "
"Le code promo « %(voucher)s » ne peut être utilisé que si vous sélectionnez "
"au moins %(number)s produits correspondants."
#: pretix/base/services/cart.py:170
#, fuzzy, python-format
#| msgid ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products. We have therefore removed some positions "
#| "from your cart that can no longer be purchased like this."
#| msgid_plural ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products. We have therefore removed some positions "
#| "from your cart that can no longer be purchased like this."
#, python-format
msgid ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching product. We have therefore removed some positions from "
@@ -14379,6 +14363,8 @@ msgid ""
"You entered an URL, which is not allowed. Please remove %(match)s from your "
"input."
msgstr ""
"Vous avez saisi une URL, ce qui n'est pas autorisé. Veuillez supprimer %"
"(match)s de votre saisie."
#: pretix/base/views/errors.py:48
msgid ""
@@ -16328,14 +16314,8 @@ msgid "inactive"
msgstr "inactif"
#: pretix/control/forms/item.py:1414
#, fuzzy
#| msgid ""
#| "Sample Conference Center\n"
#| "Heidelberg, Germany"
msgid "Sample Conference Center, Heidelberg, Germany"
msgstr ""
"Exemple de centre de conférence\n"
"Centre des Congrès, France"
msgstr "Centre de conférences d'exemple, Heidelberg, Allemagne"
#: pretix/control/forms/mailsetup.py:42
msgid "Hostname"
@@ -23831,11 +23811,8 @@ msgid "Quota history"
msgstr "Historique des quotas"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:6
#, fuzzy
#| msgctxt "subevent"
#| msgid "Change multiple dates"
msgid "Change multiple quotas"
msgstr "Modifier plusieurs dates"
msgstr "Modifier plusieurs quotas"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:8
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html:8
@@ -23883,18 +23860,15 @@ msgstr "Les produits suivants pourraient ne plus être disponibles à la vente
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:4
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:6
#, fuzzy
#| msgid "Delete quota"
msgid "Delete quotas"
msgstr "Supprimer le quota"
msgstr "Supprimer les quotas"
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:10
#, fuzzy, python-format
#| msgid "Are you sure you want to delete the following dates?"
#, python-format
msgid "Are you sure you want to delete the following quota?"
msgid_plural "Are you sure you want to delete the following %(num)s quotas?"
msgstr[0] "Voulez-vous vraiment supprimer les dates suivantes ?"
msgstr[1] "Voulez-vous vraiment supprimer les dates suivantes ?"
msgstr[0] "Êtes-vous sûr de vouloir supprimer le quota suivant?"
msgstr[1] "Êtes-vous sûr de vouloir supprimer les %(num)s quotas suivants?"
#: pretix/control/templates/pretixcontrol/items/quotas.html:9
msgid ""
@@ -24503,12 +24477,15 @@ msgid ""
"generated once the customer pays the invoice or selects a payment method "
"that requires an invoice."
msgstr ""
"Cette commande a été modifiée après l'émission de la dernière facture. "
"Aucune nouvelle facture n'a encore été générée, car les factures sont "
"configurées pour être émises lors du paiement ou si le mode de paiement "
"l'exige. Une nouvelle facture sera générée dès que le client aura réglé la "
"facture ou choisi un mode de paiement nécessitant une facture."
#: pretix/control/templates/pretixcontrol/order/index.html:152
#, fuzzy
#| msgid "Request invoice"
msgid "Reissue invoice"
msgstr "Demande de facture"
msgstr "Réémettre une facture"
#: pretix/control/templates/pretixcontrol/order/index.html:161
#: pretix/control/templates/pretixcontrol/order/index.html:413
@@ -24942,25 +24919,17 @@ msgid "How should the refund be sent?"
msgstr "Comment le remboursement doit-il être envoyé ?"
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:25
#, fuzzy
#| msgid ""
#| "Any payments that you selected for automatical refunds will be "
#| "immediately communicate the refund request to the respective payment "
#| "provider. Manual refunds will be created as pending refunds, you can then "
#| "later mark them as done once you actually transferred the money back to "
#| "the customer."
msgid ""
"Any payments you selected for automatic refunds will have the refund request "
"sent immediately to the respective payment provider. Manual refunds will be "
"created as pending refunds, which you can later mark as done once you have "
"actually transferred the money back to the customer."
msgstr ""
"Tous les paiements que vous avez sélectionnés pour des remboursements "
"automatiques seront immédiatement communiqués à la demande de remboursement "
"au fournisseur de paiement respectif. Les remboursements manuels seront "
"cés en tant que remboursements en attente, vous pourrez ensuite les "
"marquer comme terminés une fois que vous aurez effectivement transféré "
"largent au client."
"Pour tous les paiements que vous avez sélectionnés pour un remboursement "
"automatique, la demande de remboursement sera immédiatement transmise au "
"prestataire de paiement concerné. Les remboursements manuels seront "
"enregistrés comme remboursements en attente; vous pourrez les marquer comme "
"effectués une fois que vous aurez effectivement reversé l'argent au client."
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:32
msgid "Refund to original payment method"
@@ -29558,11 +29527,8 @@ msgid "The new question has been created."
msgstr "La nouvelle question a été créée."
#: pretix/control/views/item.py:918
#, fuzzy
#| msgctxt "subevent"
#| msgid "The selected dates have been deleted or disabled."
msgid "The selected quotas have been deleted or disabled."
msgstr "Les dates sélectionnées ont été supprimées ou désactivées."
msgstr "Les quotas sélectionnés ont été supprimés ou désactivés."
#: pretix/control/views/item.py:1074
msgid "The new quota has been created."
@@ -30302,12 +30268,9 @@ msgstr ""
"Ce plugin n'est actuellement pas autorisé pour ce compte d'organisateur."
#: pretix/control/views/organizer.py:832
#, fuzzy, python-brace-format
#| msgid "This plugin can be enabled or disabled for events individually."
#, python-brace-format
msgid "This plugin cannot be activated for event {}."
msgstr ""
"Ce plugin peut être activé ou désactivé individuellement pour chaque "
"événement."
msgstr "Ce plugin ne peut pas être activé pour l'événement {}."
#: pretix/control/views/organizer.py:901
msgid "The team has been created. You can now add members to the team."
@@ -31362,10 +31325,9 @@ msgid "{width} x {height} mm label"
msgstr "{width} x {height} mm étiquette"
#: pretix/plugins/badges/templates.py:265
#, fuzzy, python-brace-format
#| msgid "{width} x {height} mm label"
#, python-brace-format
msgid "{width} x {height} inch label"
msgstr "{width} x {height} mm étiquette"
msgstr "{width} x {height} pouce étiquette"
#: pretix/plugins/badges/templates/pretixplugins/badges/control_order_info.html:16
#: pretix/plugins/badges/templates/pretixplugins/badges/index.html:27

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-27 15:47+0000\n"
"PO-Revision-Date: 2026-05-12 06:34+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"PO-Revision-Date: 2026-06-01 09:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
"ja/>\n"
"Language: ja\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.17.1\n"
"X-Generator: Weblate 2026.5\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
#: pretix/control/templates/pretixcontrol/events/index.html:166
@@ -608,20 +608,20 @@ msgstr ""
"更を含みます。"
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Quota handling"
msgid "Quota changed"
msgstr "クォータの処理"
msgstr "クォータが変更されました"
#: pretix/api/webhooks.py:414
msgid ""
"This includes related events like creation, deletion, opening or closing of "
"quotas. No webhook is sent for changes to the resulting availability."
msgstr ""
"これには、クォータの作成、削除、開始または終了といった関連イベントが含まれま"
"す。結果として得られる可用性の変更については、Webhookが送信されません。"
#: pretix/api/webhooks.py:419
msgid "Shop taken live"
msgstr "ショップが公開中になりました"
msgstr "ショップがオンラインになりました"
#: pretix/api/webhooks.py:423
msgid "Shop taken offline"
@@ -3394,11 +3394,13 @@ msgid ""
"The field \"%(label)s\" may not contain special characters such as "
"\"%(chars)s\"."
msgstr ""
"フィールド「%(label)s」には、\"%(chars)s\" のような特殊文字を含めることはでき"
"ません。"
#: pretix/base/forms/questions.py:305
#, python-format
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
msgstr ""
msgstr "フィールド「%(label)s」には URL (%(url)s) を含めることができません。"
#: pretix/base/forms/questions.py:338
msgctxt "phonenumber"
@@ -8189,19 +8191,14 @@ msgid "Program times"
msgstr "プログラム時間"
#: pretix/base/pdf.py:503
#, fuzzy
#| msgid ""
#| "2017-05-31 10:00 12:00\n"
#| "2017-05-31 14:00 16:00\n"
#| "2017-05-31 14:00 2017-06-01 14:00"
msgid ""
"2017-05-31 10:00 12:00, Room 1\n"
"2017-05-31 14:00 16:00, Room 2\n"
"2017-05-31 14:00 2017-06-01 14:00, Building A"
msgstr ""
"2017-05-31 10:00 12:00\n"
"2017-05-31 14:00 16:00\n"
"2017-05-31 14:00 2017-06-01 14:00"
"2017-05-31 10:00 12:00、部屋1\n"
"2017-05-31 14:00 16:00、部屋2\n"
"2017-05-31 14:00 2017-06-01 14:00、ビルA"
#: pretix/base/pdf.py:507
msgid "Reusable Medium ID"
@@ -8710,13 +8707,7 @@ msgid "This voucher code is not known in our database."
msgstr "このバウチャーコードは、当社のデータベースには登録されていません。"
#: pretix/base/services/cart.py:165
#, fuzzy, python-format
#| msgid ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products."
#| msgid_plural ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products."
#, python-format
msgid ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching product."
@@ -8728,15 +8719,7 @@ msgstr[0] ""
"した場合にのみ使用できます。"
#: pretix/base/services/cart.py:170
#, fuzzy, python-format
#| msgid ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products. We have therefore removed some positions "
#| "from your cart that can no longer be purchased like this."
#| msgid_plural ""
#| "The voucher code \"%(voucher)s\" can only be used if you select at least "
#| "%(number)s matching products. We have therefore removed some positions "
#| "from your cart that can no longer be purchased like this."
#, python-format
msgid ""
"The voucher code \"%(voucher)s\" can only be used if you select at least "
"%(number)s matching product. We have therefore removed some positions from "
@@ -13837,6 +13820,8 @@ msgid ""
"You entered an URL, which is not allowed. Please remove %(match)s from your "
"input."
msgstr ""
"URL を入力しましたが、許可されていません。入力から %(match)s を削除してくださ"
"い。"
#: pretix/base/views/errors.py:48
msgid ""
@@ -15733,14 +15718,8 @@ msgid "inactive"
msgstr "無効"
#: pretix/control/forms/item.py:1414
#, fuzzy
#| msgid ""
#| "Sample Conference Center\n"
#| "Heidelberg, Germany"
msgid "Sample Conference Center, Heidelberg, Germany"
msgstr ""
"サンプル・カンファレンスセンター\n"
"ドイツ、ハイデルベルク"
msgstr "サンプル・カンファレンスセンター, ドイツ, ハイデルベルク"
#: pretix/control/forms/mailsetup.py:42
msgid "Hostname"
@@ -22981,11 +22960,8 @@ msgid "Quota history"
msgstr "クォータ履歴"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:6
#, fuzzy
#| msgctxt "subevent"
#| msgid "Change multiple dates"
msgid "Change multiple quotas"
msgstr "複数の日付を変更"
msgstr "複数のクォータを変更"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html:8
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html:8
@@ -23031,17 +23007,14 @@ msgstr "以下の製品は販売できなくなる可能性があります:"
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:4
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:6
#, fuzzy
#| msgid "Delete quota"
msgid "Delete quotas"
msgstr "クォータを削除"
#: pretix/control/templates/pretixcontrol/items/quota_delete_bulk.html:10
#, fuzzy, python-format
#| msgid "Are you sure you want to delete the following dates?"
#, python-format
msgid "Are you sure you want to delete the following quota?"
msgid_plural "Are you sure you want to delete the following %(num)s quotas?"
msgstr[0] "以下の日付を削除してもよろしいですか?"
msgstr[0] "以下の%(num)sのクォータを削除してもよろしいですか?"
#: pretix/control/templates/pretixcontrol/items/quotas.html:9
msgid ""
@@ -23634,12 +23607,14 @@ msgid ""
"generated once the customer pays the invoice or selects a payment method "
"that requires an invoice."
msgstr ""
"この注文は、最後の請求書が生成された後に変更されました。新しい請求書はまだ作"
"成されていません。請求書は支払い時に生成されるか、支払方法によって必要とされ"
"る場合に設定されているためです。お客様が請求書を支払うか、請求書が必要な支払"
"方法を選択すると、新しい請求書が生成されます。"
#: pretix/control/templates/pretixcontrol/order/index.html:152
#, fuzzy
#| msgid "Request invoice"
msgid "Reissue invoice"
msgstr "請求書を要求"
msgstr "請求書を再発行する"
#: pretix/control/templates/pretixcontrol/order/index.html:161
#: pretix/control/templates/pretixcontrol/order/index.html:413
@@ -24064,22 +24039,15 @@ msgid "How should the refund be sent?"
msgstr "どのように払い戻しますか?"
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:25
#, fuzzy
#| msgid ""
#| "Any payments that you selected for automatical refunds will be "
#| "immediately communicate the refund request to the respective payment "
#| "provider. Manual refunds will be created as pending refunds, you can then "
#| "later mark them as done once you actually transferred the money back to "
#| "the customer."
msgid ""
"Any payments you selected for automatic refunds will have the refund request "
"sent immediately to the respective payment provider. Manual refunds will be "
"created as pending refunds, which you can later mark as done once you have "
"actually transferred the money back to the customer."
msgstr ""
"自動払い戻しに選択した支払いは、該当する決済プロバイダーに払い戻し要求が即座"
"に通知されます。手動払い戻しは保留中の払い戻しとして作成され、実際に顧客に送"
"金した後で完了済みとしてマークできます。"
"自動返金をご選択いただいたすべての支払いについては、返金リクエストが直ちに該"
"当する決済プロバイダーへ送信されます。手動返金は保留中の返金として作成され、"
"実際に顧客に返金した後で完了としてマークできます。"
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:32
msgid "Refund to original payment method"
@@ -28504,11 +28472,8 @@ msgid "The new question has been created."
msgstr "新しい質問が作成されました。"
#: pretix/control/views/item.py:918
#, fuzzy
#| msgctxt "subevent"
#| msgid "The selected dates have been deleted or disabled."
msgid "The selected quotas have been deleted or disabled."
msgstr "選択した日付は削除されたか無効になっています。"
msgstr "選択したクォータは削除されたか無効す。"
#: pretix/control/views/item.py:1074
msgid "The new quota has been created."
@@ -29215,10 +29180,9 @@ msgid "This plugin is currently not allowed for this organizer account."
msgstr "このプラグインは現在、この主催者アカウントでは許可されていません。"
#: pretix/control/views/organizer.py:832
#, fuzzy, python-brace-format
#| msgid "This plugin can be enabled or disabled for events individually."
#, python-brace-format
msgid "This plugin cannot be activated for event {}."
msgstr "このプラグインは、イベントごとに個別に有効化または無効化できま。"
msgstr "このプラグインは、イベント{}に対してアクティベートできません。"
#: pretix/control/views/organizer.py:901
msgid "The team has been created. You can now add members to the team."
@@ -30236,10 +30200,9 @@ msgid "{width} x {height} mm label"
msgstr "{width} x {height} mm ラベル"
#: pretix/plugins/badges/templates.py:265
#, fuzzy, python-brace-format
#| msgid "{width} x {height} mm label"
#, python-brace-format
msgid "{width} x {height} inch label"
msgstr "{width} x {height} mm ラベル"
msgstr "{width} x {height} インチラベル"
#: pretix/plugins/badges/templates/pretixplugins/badges/control_order_info.html:16
#: pretix/plugins/badges/templates/pretixplugins/badges/index.html:27

View File

@@ -8,16 +8,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-27 15:47+0000\n"
"PO-Revision-Date: 2026-02-01 21:00+0000\n"
"Last-Translator: z3rrry <z3rrry@gmail.com>\n"
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix/ko/"
">\n"
"PO-Revision-Date: 2026-06-01 09:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix/"
"ko/>\n"
"Language: ko\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.15.2\n"
"X-Generator: Weblate 2026.5\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html:670
#: pretix/control/templates/pretixcontrol/events/index.html:166
@@ -48,7 +48,7 @@ msgstr "사전판매 시작하지 않음"
#: pretix/control/templates/pretixcontrol/subevents/index.html:176
#: pretix/control/views/dashboards.py:549
msgid "On sale"
msgstr ""
msgstr "세일 중"
#: pretix/_base_settings.py:89
msgid "English"
@@ -427,10 +427,8 @@ msgstr ""
#: pretix/api/serializers/organizer.py:495
#: pretix/control/views/organizer.py:1035
#, fuzzy
#| msgid "pretix account invitation"
msgid "Account invitation"
msgstr "프레틱스 계정 초대"
msgstr "계정 초대"
#: pretix/api/serializers/organizer.py:516
#: pretix/control/views/organizer.py:1134
@@ -18087,10 +18085,8 @@ msgid "A payment has been performed."
msgstr "수동 거래가 수행되었습니다."
#: pretix/control/logdisplay.py:807
#, fuzzy
#| msgid "A manual transaction has been performed."
msgid "A refund has been performed. "
msgstr "수동 거래가 수행되었습니다."
msgstr "환불이 처리되었습니다. "
#: pretix/control/logdisplay.py:808
#, python-brace-format

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-27 15:47+0000\n"
"PO-Revision-Date: 2026-05-21 15:08+0000\n"
"PO-Revision-Date: 2026-06-01 09:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Chinese (Traditional Han script) <https://translate.pretix.eu/"
"projects/pretix/pretix/zh_Hant/>\n"
@@ -595,16 +595,16 @@ msgid ""
msgstr "這包括新增或刪除的產品,以及對變體或捆綁等巢狀物件的更改。"
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Quota handling"
msgid "Quota changed"
msgstr "額度處理"
msgstr "配額改變了"
#: pretix/api/webhooks.py:414
msgid ""
"This includes related events like creation, deletion, opening or closing of "
"quotas. No webhook is sent for changes to the resulting availability."
msgstr ""
"這包括建立、刪除、開啟或關閉配額等相關事件。 沒有傳送webhook來更改結果的可用"
"性。"
#: pretix/api/webhooks.py:419
msgid "Shop taken live"
@@ -650,7 +650,7 @@ msgstr "優惠券已更改"
msgid ""
"Only includes explicit changes to the voucher, not e.g. an increase of the "
"number of redemptions."
msgstr ""
msgstr "僅包括對代金券的明確更改,例如不包括兌換次數的增加。"
#: pretix/api/webhooks.py:460
msgid "Voucher deleted"
@@ -669,22 +669,16 @@ msgid "Customer account anonymized"
msgstr "客戶帳戶已匿名化"
#: pretix/api/webhooks.py:476
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card added"
msgstr "禮品卡代碼"
msgstr "添加了禮品卡"
#: pretix/api/webhooks.py:480
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card modified"
msgstr "禮品卡代碼"
msgstr "禮品卡修改了"
#: pretix/api/webhooks.py:484
#, fuzzy
#| msgid "Gift card transactions"
msgid "Gift card used in transaction"
msgstr "禮品卡交易"
msgstr "交易中使用的禮品卡"
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:1074

View File

@@ -20,8 +20,6 @@
# <https://www.gnu.org/licenses/>.
#
import functools
import hashlib
import ipaddress
import random
from django import forms
@@ -32,7 +30,6 @@ from django.contrib.auth.password_validation import (
)
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.core import signing
from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _
from phonenumber_field.formfields import PhoneNumberField
@@ -44,6 +41,7 @@ from pretix.base.forms.questions import (
from pretix.base.i18n import get_language_without_region
from pretix.base.models import Customer
from pretix.helpers.http import get_client_ip
from pretix.helpers.ratelimit import rate_limit
from pretix.multidomain.urlreverse import build_absolute_uri
@@ -205,23 +203,6 @@ class RegistrationForm(forms.Form):
min_value=0,
)
@cached_property
def ratelimit_key(self):
if not settings.HAS_REDIS:
return None
client_ip = get_client_ip(self.request)
if not client_ip:
return None
try:
client_ip = ipaddress.ip_address(client_ip)
except ValueError:
# Web server not set up correctly
return None
if client_ip.is_private:
# This is the private IP of the server, web server not set up correctly
return None
return 'pretix_customer_registration_{}'.format(hashlib.sha1(str(client_ip).encode()).hexdigest())
def clean(self):
email = self.cleaned_data.get('email')
@@ -255,17 +236,11 @@ class RegistrationForm(forms.Form):
code='incomplete'
)
else:
if self.ratelimit_key:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
cnt = rc.incr(self.ratelimit_key)
rc.expire(self.ratelimit_key, 600)
if cnt > 10:
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
if rate_limit("customer_signup", include_ip_from_request=self.request, max_num=10, expire_time=600):
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
return self.cleaned_data
def create(self):
@@ -370,13 +345,8 @@ class ResetPasswordForm(forms.Form):
def clean(self):
d = super().clean()
if d.get('email') and settings.HAS_REDIS:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
cnt = rc.incr('pretix_pwreset_customer_%s' % self.customer.pk)
rc.expire('pretix_pwreset_customer_%s' % self.customer.pk, 600)
if cnt > 2:
if d.get('email'):
if rate_limit("customer_pwreset", self.customer.pk, max_num=2, expire_time=600):
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
@@ -445,13 +415,8 @@ class ChangePasswordForm(forms.Form):
def clean_password_current(self):
old_pw = self.cleaned_data.get('password_current')
if old_pw and settings.HAS_REDIS:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
cnt = rc.incr('pretix_pwchange_customer_%s' % self.customer.pk)
rc.expire('pretix_pwchange_customer_%s' % self.customer.pk, 300)
if cnt > 10:
if old_pw:
if rate_limit("customer_pwchange", self.customer.pk, max_num=10, expire_time=300):
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
@@ -521,17 +486,11 @@ class ChangeInfoForm(forms.ModelForm):
old_pw = self.cleaned_data.get('password_current')
if old_pw:
if settings.HAS_REDIS:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
cnt = rc.incr('pretix_pwchange_customer_%s' % self.instance.pk)
rc.expire('pretix_pwchange_customer_%s' % self.instance.pk, 300)
if cnt > 10:
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
if rate_limit("customer_pwchange", self.instance.pk, max_num=10, expire_time=300):
raise forms.ValidationError(
self.error_messages['rate_limit'],
code='rate_limit',
)
if not check_password(old_pw, self.instance.password):
raise forms.ValidationError(

View File

@@ -32,7 +32,6 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
from django.conf import settings
from django.contrib import messages
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
@@ -42,6 +41,7 @@ from django.views.generic import TemplateView
from pretix.base.email import get_email_context
from pretix.base.services.mail import INVALID_ADDRESS, mail
from pretix.helpers.http import redirect_to_url
from pretix.helpers.ratelimit import rate_limit
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.forms.user import ResendLinkForm
from pretix.presale.views import EventViewMixin
@@ -61,17 +61,12 @@ class ResendLinkView(EventViewMixin, TemplateView):
user = self.link_form.cleaned_data.get('email')
if settings.HAS_REDIS:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
if rc.exists('pretix_resend_{}_{}'.format(request.event.pk, user)):
messages.error(request, _('If the email address you entered is valid and associated with a ticket, we have '
'already sent you an email with a link to your ticket in the past {number} hours. '
'If the email did not arrive, please check your spam folder and also double check '
'that you used the correct email address.').format(number=24))
return redirect_to_url(eventreverse(self.request.event, 'presale:event.resend_link'))
else:
rc.setex('pretix_resend_{}_{}'.format(request.event.pk, user), 3600 * 24, '1')
if rate_limit("order_resend", self.request.event.pk, user, max_num=1, expire_time=3600 * 24):
messages.error(request, _('If the email address you entered is valid and associated with a ticket, we have '
'already sent you an email with a link to your ticket in the past {number} hours. '
'If the email did not arrive, please check your spam folder and also double check '
'that you used the correct email address.').format(number=24))
return redirect_to_url(eventreverse(self.request.event, 'presale:event.resend_link'))
orders = self.request.event.orders.filter(email__iexact=user)

View File

@@ -120,6 +120,7 @@ def fakeredis_client(monkeypatch):
redis = get_redis_connection("default", True)
redis.flushall()
monkeypatch.setattr('django_redis.get_redis_connection', get_redis_connection, raising=False)
monkeypatch.setattr('pretix.base.metrics.redis', redis, raising=False)
yield redis

View File

@@ -504,33 +504,8 @@ class Login2FAFormTest(TestCase):
assert "recovery code" in djmail.outbox[0].body
class FakeRedis(object):
def get_redis_connection(self, connection_string):
return self
def __init__(self):
self.storage = {}
def pipeline(self):
return self
def hincrbyfloat(self, rkey, key, amount):
return self
def commit(self):
return self
def exists(self, rkey):
return rkey in self.storage
def setex(self, rkey, value, expiration):
self.storage[rkey] = value
def execute(self):
pass
@pytest.mark.usefixtures("class_monkeypatch")
@pytest.mark.usefixtures("fakeredis_client")
class PasswordRecoveryFormTest(TestCase):
def setUp(self):
super().setUp()
@@ -560,11 +535,6 @@ class PasswordRecoveryFormTest(TestCase):
@override_settings(HAS_REDIS=True)
def test_email_reset_twice_redis(self):
fake_redis = FakeRedis()
m = self.monkeypatch
m.setattr('django_redis.get_redis_connection', fake_redis.get_redis_connection, raising=False)
m.setattr('pretix.base.metrics.redis', fake_redis, raising=False)
djmail.outbox = []
response = self.client.post('/control/forgot', {