mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-09-27 15:56:45 +00:00
Env var - PAGE_WATCH_LIMIT enhancements (#4359)
This commit is contained in:
@@ -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 = []
|
||||
|
||||
@@ -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}))
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
Binary file not shown.
@@ -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 ""
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
Binary file not shown.
@@ -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 "すべての通知の本文 — 以下を使用できます:"
|
||||
|
||||
Binary file not shown.
@@ -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 "모든 알림 본문 — 다음을 사용할 수 있습니다:"
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
Binary file not shown.
@@ -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ć"
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
Binary file not shown.
@@ -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 "Текст для всех уведомлений — вы можете использовать"
|
||||
|
||||
Binary file not shown.
@@ -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"
|
||||
|
||||
Binary file not shown.
@@ -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 "Тіло для всіх сповіщень — Ви можете використовувати"
|
||||
|
||||
Binary file not shown.
@@ -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 "所有通知的正文 — 您可以使用"
|
||||
|
||||
Binary file not shown.
@@ -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 "所有通知的內文 — 您可以使用"
|
||||
|
||||
Reference in New Issue
Block a user