Env var - PAGE_WATCH_LIMIT enhancements (#4359)

This commit is contained in:
dgtlmoon
2026-09-02 13:37:55 +02:00
committed by GitHub
parent af234636b9
commit 68a445df1b
41 changed files with 290 additions and 117 deletions
+9
View File
@@ -194,6 +194,15 @@ class Import(Resource):
urls_to_import.append(url)
# PAGE_WATCH_LIMIT - refuse the whole batch rather than importing an arbitrary prefix of
# it, so a 429 always means "nothing was created" and the caller can retry as-is
watch_limit = self.datastore.watch_limit
if watch_limit is not None:
current_watch_count = len(self.datastore.data['watching'])
if current_watch_count + len(urls_to_import) > watch_limit:
return (f"Watch limit reached ({current_watch_count}/{watch_limit} watches), importing "
f"{len(urls_to_import)} URL(s) would exceed it. No watches were imported.", 429)
# For small imports, process synchronously for immediate feedback
if len(urls_to_import) < IMPORT_SWITCH_TO_BACKGROUND_THRESHOLD:
added = []
+6 -10
View File
@@ -560,6 +560,12 @@ class CreateWatch(Resource):
del extras['url']
# PAGE_WATCH_LIMIT - checked up front so a blocked add is reported as 429 rather than
# being guessed at from add_watch() returning None
if self.datastore.watch_limit_reached():
current_watch_count = len(self.datastore.data['watching'])
return f"Watch limit reached ({current_watch_count}/{self.datastore.watch_limit} watches). Cannot add more watches.", 429
new_uuid = self.datastore.add_watch(url=url, extras=extras, tag=tags)
# Save processor config to separate JSON file
@@ -570,16 +576,6 @@ class CreateWatch(Resource):
# worker_pool.queue_item_async_safe(self.update_q, queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': new_uuid}))
return {'uuid': new_uuid}, 201
else:
# Check if it was a limit issue
page_watch_limit = os.getenv('PAGE_WATCH_LIMIT')
if page_watch_limit:
try:
page_watch_limit = int(page_watch_limit)
current_watch_count = len(self.datastore.data['watching'])
if current_watch_count >= page_watch_limit:
return f"Watch limit reached ({current_watch_count}/{page_watch_limit} watches). Cannot add more watches.", 429
except ValueError:
pass
return "Invalid or unsupported URL", 400
@auth.check_token
@@ -17,6 +17,7 @@ class Importer():
self.good = 0
self.remaining_data = []
self.import_profile = None
self.limit_reported = False
@abstractmethod
def run(self,
@@ -25,6 +26,21 @@ class Importer():
datastore):
pass
def watch_limit_hit(self, datastore, flash):
"""True once PAGE_WATCH_LIMIT leaves no room - the caller must then stop importing.
Asked before each add_watch() so the limit is reported once for the whole file.
add_watch() would otherwise flash the same error again for every remaining row.
"""
if not datastore.watch_limit_reached():
return False
if not self.limit_reported:
self.limit_reported = True
flash(datastore.watch_limit_message(), 'error')
return True
class import_url_list(Importer):
"""
@@ -44,7 +60,7 @@ class import_url_list(Importer):
if (len(urls) > 5000):
flash(gettext("Importing 5,000 of the first URLs from your list, the rest can be imported again."))
for url in urls:
for idx, url in enumerate(urls):
url = url.strip()
if not len(url):
continue
@@ -59,6 +75,12 @@ class import_url_list(Importer):
# Up to 5000 per batch so we dont flood the server
# @todo validators.url will fail when you add your own IP etc
if len(url) and 'http' in url.lower() and good < 5000:
if self.watch_limit_hit(datastore, flash):
# Hand back every line we didn't get to (originals, tags included) so they
# land in the textarea to retry once there's room
self.remaining_data.extend(u.strip() for u in urls[idx:] if u.strip())
break
extras = None
if processor:
extras = {'processor': processor}
@@ -107,6 +129,9 @@ class import_distill_io_json(Importer):
extras = {'title': d.get('name', None)}
if len(d['uri']) and good < 5000:
if self.watch_limit_hit(datastore, flash):
break
try:
# @todo we only support CSS ones at the moment
if d_config['selections'][0]['frames'][0]['excludes'][0]['type'] == 'css':
@@ -200,6 +225,9 @@ class import_xlsx_wachete(Importer):
# Don't bother processing anything else on this row
continue
if self.watch_limit_hit(datastore, flash):
break
new_uuid = datastore.add_watch(url=data['url'].strip(),
extras=extras,
tag=data.get('folder'),
@@ -281,6 +309,9 @@ class import_xlsx_custom(Importer):
# At minimum a URL is required.
if url:
if self.watch_limit_hit(datastore, flash):
break
new_uuid = datastore.add_watch(url=url,
extras=extras,
tag=tags,
@@ -261,6 +261,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
llm_show_costs=llm_show_costs,
python_version=python_version,
uptime_seconds=uptime_seconds,
# None unless PAGE_WATCH_LIMIT is set, which hides the row entirely
watch_limit=datastore.watch_limit,
available_timezones=sorted(available_timezones()),
emailprefix=os.getenv('NOTIFICATION_MAIL_BUTTON_PREFIX', False),
extra_notification_token_placeholder_info=datastore.get_unique_notification_token_placeholders_available(),
@@ -391,6 +391,9 @@ nav
<div class="tab-pane-inner" id="info">
<p><strong>{{ _('Uptime:') }}</strong> {{ uptime_seconds|format_duration }}</p>
<p><strong>{{ _('Python version:') }}</strong> {{ python_version }}</p>
{% if watch_limit is not none %}
<p><strong>{{ _('Maximum number of page watches:') }}</strong> {{ watch_limit }}</p>
{% endif %}
<p><strong>{{ _('Plugins active:') }}</strong></p>
{% if active_plugins %}
<ul>
@@ -293,6 +293,9 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, worker_pool,
uuid = list(datastore.data['watching'].keys()).pop()
new_uuid = datastore.clone(uuid)
if not new_uuid:
# Refused (e.g. PAGE_WATCH_LIMIT) - the reason is already flashed
return redirect(url_for('watchlist.index'))
if not datastore.data['watching'].get(uuid).get('paused'):
worker_pool.queue_item_async_safe(update_q, queuedWatchMetaData.PrioritizedItem(priority=5, item={'uuid': new_uuid}))
+45 -17
View File
@@ -5,7 +5,8 @@ from changedetectionio.strtobool import strtobool
from changedetectionio.validate_url import is_safe_valid_url
from flask import (
flash
flash,
has_request_context
)
from flask_babel import gettext
@@ -661,9 +662,8 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore):
# NOTE: dict() is shallow copy but safe since add_watch() deepcopies it
with self.lock:
extras = dict(self.data['watching'][uuid])
new_uuid = self.add_watch(url=url, extras=extras)
watch = self.data['watching'][new_uuid]
return new_uuid
# None when add_watch() refused it (e.g. PAGE_WATCH_LIMIT), having already flashed why
return self.add_watch(url=url, extras=extras)
def url_exists(self, url):
@@ -679,6 +679,38 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore):
self.__data['watching'][uuid].clear_watch()
self.__data['watching'][uuid].commit()
@property
def watch_limit(self):
"""Total watches allowed by PAGE_WATCH_LIMIT, or None when there is no limit.
None means "unlimited" and is the normal case - the env var being absent, empty or
unparseable all leave the limit switched off entirely. There is no default.
"""
limit = os.getenv('PAGE_WATCH_LIMIT')
if not limit:
return None
try:
return int(limit)
except ValueError:
logger.warning(f"Invalid PAGE_WATCH_LIMIT value: {limit}, ignoring limit check")
return None
def watch_limit_reached(self):
"""True when the limit is set and leaves no room for another watch.
add_watch() enforces this on its own, but it can only return None. Callers that can
report something better - a 429 in the API, one flash instead of one per row in the
importers - should check this first.
"""
limit = self.watch_limit
return limit is not None and len(self.__data['watching']) >= limit
def watch_limit_message(self):
"""The single wording for a blocked add, so every UI surface says the same thing."""
return gettext("Watch limit reached ({current}/{limit} watches). Cannot add more watches.").format(
current=len(self.__data['watching']), limit=self.watch_limit
)
def add_watch(self, url, tag='', extras=None, tag_uuids=None, save_immediately=True, seed_data_dir=None):
"""
seed_data_dir: optional path to an existing directory (already in the watch's
@@ -742,25 +774,21 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore):
return False
if not is_safe_valid_url(url):
from flask import has_request_context
if has_request_context():
flash(gettext('Watch protocol is not permitted or invalid URL format'), 'error')
else:
logger.error(f"add_watch: URL '{url}' is not permitted or invalid, skipping.")
return None
# Check PAGE_WATCH_LIMIT if set
page_watch_limit = os.getenv('PAGE_WATCH_LIMIT')
if page_watch_limit:
try:
page_watch_limit = int(page_watch_limit)
current_watch_count = len(self.__data['watching'])
if current_watch_count >= page_watch_limit:
logger.error(f"Watch limit reached: {current_watch_count}/{page_watch_limit} watches. Cannot add {url}")
flash(gettext("Watch limit reached ({current}/{limit} watches). Cannot add more watches.").format(current=current_watch_count, limit=page_watch_limit), 'error')
return None
except ValueError:
logger.warning(f"Invalid PAGE_WATCH_LIMIT value: {page_watch_limit}, ignoring limit check")
# Backstop for PAGE_WATCH_LIMIT - every add path funnels through here, so nothing can
# get past the limit even if a caller forgets to pre-check watch_limit_reached().
if self.watch_limit_reached():
logger.error(f"Watch limit reached: {len(self.__data['watching'])}/{self.watch_limit} watches. Cannot add {url}")
# The CLI (-u) and the API's background import thread have no request context,
# where flash() raises instead of reporting anything
if has_request_context():
flash(self.watch_limit_message(), 'error')
return None
if tag and type(tag) == str:
# Then it's probably a string of the actual tag by name, split and add it
@@ -861,6 +861,10 @@ msgstr ""
msgid "Python version:"
msgstr "Verze Pythonu:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "Maximální počet sledování stránek:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "Pluginy aktivní:"
@@ -3974,6 +3978,11 @@ msgstr "Změny textu webové stránky/HTML, JSON a PDF"
msgid "Detects all text changes where possible"
msgstr "Detekuje všechny změny textu, kde je to možné"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Dosažen limit sledování ({current}/{limit}). Nelze přidat další."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -3983,11 +3992,6 @@ msgstr ""
msgid "Watch protocol is not permitted or invalid URL format"
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "Tělo pro všechna oznámení — Můžete použít"
@@ -877,6 +877,10 @@ msgstr ""
msgid "Python version:"
msgstr "Python-Version:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "Maximale Anzahl an Seitenüberwachungen:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "Aktive Plugins:"
@@ -4026,6 +4030,11 @@ msgstr "Änderungen an Webseitentext/HTML, JSON und PDF"
msgid "Detects all text changes where possible"
msgstr "Erkennt nach Möglichkeit alle Textänderungen"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Überwachungslimit erreicht ({current}/{limit}). Keine weiteren möglich."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -4035,11 +4044,6 @@ msgstr "Fehler beim Abrufen der Metadaten für {}"
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "Das Protokoll wird nicht unterstützt oder das URL-Format ist ungültig."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "Inhalt für alle Benachrichtigungen — Sie können verwenden"
@@ -859,6 +859,10 @@ msgstr ""
msgid "Python version:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr ""
@@ -3966,6 +3970,11 @@ msgstr ""
msgid "Detects all text changes where possible"
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -3975,11 +3984,6 @@ msgstr ""
msgid "Watch protocol is not permitted or invalid URL format"
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr ""
@@ -859,6 +859,10 @@ msgstr ""
msgid "Python version:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr ""
@@ -3966,6 +3970,11 @@ msgstr ""
msgid "Detects all text changes where possible"
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -3975,11 +3984,6 @@ msgstr ""
msgid "Watch protocol is not permitted or invalid URL format"
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr ""
@@ -897,6 +897,10 @@ msgstr "Tiempo de actividad:"
msgid "Python version:"
msgstr "Versión de Python:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "Número máximo de monitores de páginas:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "Complementos activos:"
@@ -4039,6 +4043,11 @@ msgstr "Cambios en el texto/HTML, JSON y PDF de la página web"
msgid "Detects all text changes where possible"
msgstr "Detecta todos los cambios de texto siempre que sea posible"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Límite de monitores alcanzado ({current}/{limit}). No se pueden añadir más."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -4048,11 +4057,6 @@ msgstr "Error al obtener metadatos para{}"
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "El protocolo de visualización no está permitido o el formato de URL no es válido"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Límite de visualización alcanzado ({current} /{limit} monitores). No se pueden agregar más monitores."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "Cuerpo de todas las notificaciones: puedes utilizar"
@@ -865,6 +865,10 @@ msgstr ""
msgid "Python version:"
msgstr "Version Python :"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "Nombre maximum de moniteurs de pages :"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "Plugins actifs :"
@@ -3979,6 +3983,11 @@ msgstr "Modifications du texte de la page Web/HTML, JSON et PDF"
msgid "Detects all text changes where possible"
msgstr "Détecte toutes les modifications de texte lorsque cela est possible"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Limite de moniteurs atteinte ({current}/{limit}). Impossible d'en ajouter."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -3988,11 +3997,6 @@ msgstr ""
msgid "Watch protocol is not permitted or invalid URL format"
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "Corps pour toutes les notifications — Vous pouvez utiliser"
@@ -861,6 +861,10 @@ msgstr ""
msgid "Python version:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "Numero massimo di monitoraggi di pagine:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr ""
@@ -3968,6 +3972,11 @@ msgstr "Modifiche testo/HTML, JSON e PDF"
msgid "Detects all text changes where possible"
msgstr "Rileva tutte le modifiche di testo possibili"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Limite di monitoraggi raggiunto ({current}/{limit}). Impossibile aggiungerne altri."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -3977,11 +3986,6 @@ msgstr "Errore nel recupero metadati per {}"
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "Protocollo non consentito o formato URL non valido"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "Corpo per tutte le notifiche — Puoi usare"
@@ -866,6 +866,10 @@ msgstr "稼働時間:"
msgid "Python version:"
msgstr "Pythonバージョン:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "ページウォッチの最大数:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "有効なプラグイン:"
@@ -3985,6 +3989,11 @@ msgstr "ウェブページのテキスト/HTML、JSONおよびPDFの変更"
msgid "Detects all text changes where possible"
msgstr "可能な限りすべてのテキスト変更を検知します"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "ウォッチの上限に達しました({current}/{limit} ウォッチ)。これ以上ウォッチを追加できません。"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -3994,11 +4003,6 @@ msgstr "{} のメタデータ取得中にエラーが発生しました"
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "ウォッチのプロトコルが許可されていないか、URL形式が無効です"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "ウォッチの上限に達しました({current}/{limit} ウォッチ)。これ以上ウォッチを追加できません。"
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "すべての通知の本文 — 以下を使用できます:"
@@ -861,6 +861,10 @@ msgstr "가동 시간:"
msgid "Python version:"
msgstr "파이썬 버전:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "최대 페이지 모니터링 수:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "활성화된 플러그인:"
@@ -3976,6 +3980,11 @@ msgstr "웹페이지 텍스트/HTML, JSON 및 PDF 변경"
msgid "Detects all text changes where possible"
msgstr "가능한 경우 모든 텍스트 변경 사항을 감지합니다."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "모니터링 한도에 도달했습니다. ({current}/{limit}개) 더 이상 모니터링을 추가할 수 없습니다."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -3985,11 +3994,6 @@ msgstr "{}의 메타데이터를 가져오는 중 오류가 발생했습니다."
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "모니터링 프로토콜이 허용되지 않거나 URL 형식이 올바르지 않습니다."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "모니터링 한도에 도달했습니다. ({current}/{limit}개) 더 이상 모니터링을 추가할 수 없습니다."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "모든 알림 본문 — 다음을 사용할 수 있습니다:"
+10 -6
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.55.8\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-09-01 19:10+0200\n"
"POT-Creation-Date: 2026-09-02 12:06+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -858,6 +858,10 @@ msgstr ""
msgid "Python version:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr ""
@@ -3965,6 +3969,11 @@ msgstr ""
msgid "Detects all text changes where possible"
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -3974,11 +3983,6 @@ msgstr ""
msgid "Watch protocol is not permitted or invalid URL format"
msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr ""
@@ -919,6 +919,10 @@ msgstr "Czas sprawności:"
msgid "Python version:"
msgstr "Wersja Pythona:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "Maksymalna liczba obserwacji stron:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "Aktywne wtyczki:"
@@ -4127,6 +4131,11 @@ msgstr "Zmiany w treści stron internetowych (HTML), plikach JSON i PDF"
msgid "Detects all text changes where possible"
msgstr "W miarę możliwości wykrywa wszystkie zmiany w tekście"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Osiągnięto limit obserwacji ({current}/{limit}). Nie można dodać kolejnych obserwacji."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -4136,11 +4145,6 @@ msgstr "Wystąpił błąd podczas pobierania metadanych dla {}"
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "Protokół „watch” jest niedozwolony lub format adresu URL jest nieprawidłowy"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Osiągnięto limit obserwacji ({current}/{limit}). Nie można dodać kolejnych obserwacji."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "Treść wszystkich powiadomień — Można użyć"
@@ -884,6 +884,10 @@ msgstr "Tempo de atividade (Uptime):"
msgid "Python version:"
msgstr "Versão do Python:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "Número máximo de monitoramentos de páginas:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "Plugins ativos:"
@@ -4016,6 +4020,11 @@ msgstr "Mudanças em Texto/HTML de páginas, JSON e PDF"
msgid "Detects all text changes where possible"
msgstr "Detecta todas as mudanças de texto onde possível"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Limite de monitoramentos atingido ({current}/{limit}). Não é possível adicionar mais."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -4025,11 +4034,6 @@ msgstr "Erro ao buscar metadados para {}"
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "O protocolo de monitoramento não é permitido ou o formato da URL é inválido"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Limite de monitoramentos atingido ({current}/{limit}). Não é possível adicionar mais."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "Corpo para todas as notificações — Você pode usar"
@@ -898,6 +898,10 @@ msgstr "Время работы:"
msgid "Python version:"
msgstr "Версия Python:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "Максимальное количество отслеживаний страниц:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "Плагины активны:"
@@ -4084,6 +4088,11 @@ msgstr "Изменения текста веб-страницы/HTML, JSON и PD
msgid "Detects all text changes where possible"
msgstr "Обнаруживает все изменения текста, где это возможно."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Достигнут лимит отслеживаний ({current}/{limit}). Больше добавить нельзя."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -4093,11 +4102,6 @@ msgstr "Ошибка при получении метаданных для {}."
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "Протокол просмотра не разрешен или неверный формат URL."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Достигнут лимит просмотра (часы {current}/{limit}). Невозможно добавить больше часов."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "Текст для всех уведомлений — вы можете использовать"
@@ -894,6 +894,10 @@ msgstr "Çalışma Süresi:"
msgid "Python version:"
msgstr "Python sürümü:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "Maksimum sayfa izleyici sayısı:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "Aktif eklentiler:"
@@ -4019,6 +4023,11 @@ msgstr "Web Sayfası Metin/HTML, JSON ve PDF değişiklikleri"
msgid "Detects all text changes where possible"
msgstr "Mümkün olan yerlerde tüm metin değişikliklerini tespit eder"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "İzleyici sınırına ulaşıldı ({current}/{limit} izleyici). Daha fazla izleyici eklenemez."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -4028,11 +4037,6 @@ msgstr "{} için meta veri getirilirken hata oluştu"
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "İzleyici protokolüne izin verilmiyor veya geçersiz URL formatı"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "İzleyici sınırına ulaşıldı ({current}/{limit} izleyici). Daha fazla izleyici eklenemez."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "Tüm bildirimler için gövde — Bildirim başlığı, gövdesi ve URL'sinde"
@@ -874,6 +874,10 @@ msgstr ""
msgid "Python version:"
msgstr "Версія Python:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "Максимальна кількість завдань моніторингу:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "Активні плагіни:"
@@ -3998,6 +4002,11 @@ msgstr "Зміни тексту веб-сторінки/HTML, JSON та PDF"
msgid "Detects all text changes where possible"
msgstr "Виявляє всі текстові зміни, де це можливо"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Досягнуто ліміту завдань ({current}/{limit}). Неможливо додати більше."
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -4007,11 +4016,6 @@ msgstr "Помилка отримання метаданих для {}"
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "Протокол завдання не дозволено або невірний формат URL"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Досягнуто ліміту завдань ({current}/{limit}). Неможливо додати більше."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "Тіло для всіх сповіщень — Ви можете використовувати"
@@ -864,6 +864,10 @@ msgstr ""
msgid "Python version:"
msgstr "Python 版本:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "最大监视器数量:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "已启用插件:"
@@ -3972,6 +3976,11 @@ msgstr "网页文本/HTML、JSON 和 PDF 变更"
msgid "Detects all text changes where possible"
msgstr "尽可能检测所有文本变更"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "已达到监视器上限({current}/{limit})。无法添加更多。"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -3981,11 +3990,6 @@ msgstr "获取 {} 的元数据失败"
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "监控协议不允许或 URL 格式无效"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "所有通知的正文 — 您可以使用"
@@ -863,6 +863,10 @@ msgstr ""
msgid "Python version:"
msgstr "Python 版本:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Maximum number of page watches:"
msgstr "最大監測任務數量:"
#: changedetectionio/blueprint/settings/templates/settings.html
msgid "Plugins active:"
msgstr "啟用的外掛:"
@@ -3972,6 +3976,11 @@ msgstr "網頁文字 / HTML、JSON 和 PDF 變更"
msgid "Detects all text changes where possible"
msgstr "盡可能檢測所有文字變更"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "已達到監測任務上限({current}/{limit})。無法再新增。"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Error fetching metadata for {}"
@@ -3981,11 +3990,6 @@ msgstr "讀取 {} 的中繼資料時發生錯誤"
msgid "Watch protocol is not permitted or invalid URL format"
msgstr "監測協定不被允許或 URL 格式無效"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
msgstr "所有通知的內文 — 您可以使用"