mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-05-30 05:20:57 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c28d639019 | |||
| 15be5a62db | |||
| a2fa9a9e7b | |||
| 972d1206e8 | |||
| bbf56e2253 | |||
| dfc6eaf340 | |||
| 08d30c6f22 | |||
| ab19cb3e4f | |||
| 68ea3b2ac5 | |||
| 88f4beb08f | |||
| e45f87578b | |||
| 281c1ea7e1 | |||
| cf31823d53 | |||
| aadf8df7ae | |||
| 44ac324a41 | |||
| 7831a499b2 | |||
| e25387f588 | |||
| e4bc048280 | |||
| 2839a4276e |
@@ -11,8 +11,8 @@ jobs:
|
||||
- name: Lint with Ruff
|
||||
run: |
|
||||
pip install ruff
|
||||
# Check for syntax errors and undefined names
|
||||
ruff check . --select E9,F63,F7,F82
|
||||
# Check for syntax errors and undefined names, and gettext misuse
|
||||
ruff check . --select E9,F63,F7,F82,INT
|
||||
# Complete check with errors treated as warnings
|
||||
ruff check . --exit-zero
|
||||
- name: Validate OpenAPI spec
|
||||
@@ -31,6 +31,33 @@ jobs:
|
||||
echo "Checking $f"
|
||||
msgfmt --check-format -o /dev/null "$f"
|
||||
done
|
||||
- name: Lint .po/.pot files with dennis (errors only)
|
||||
run: |
|
||||
pip install "$(grep -E '^dennis ?>=' requirements.txt)"
|
||||
dennis-cmd lint --errorsonly changedetectionio/translations/
|
||||
- name: Lint .pot template with dennis (warnings)
|
||||
run: |
|
||||
output=$(dennis-cmd lint changedetectionio/translations/messages.pot)
|
||||
echo "$output"
|
||||
warnings=$(echo "$output" | awk '/Warnings:/ {print $NF; exit}')
|
||||
if (( ${warnings:-0} > 0 )); then
|
||||
echo "ERROR: ${warnings} dennis warning(s) detected in messages.pot"
|
||||
echo "Fix the warning(s)."
|
||||
exit 1
|
||||
fi
|
||||
- name: Lint .po files with dennis (warnings)
|
||||
# W302 (unchanged) is excluded due to high false-positive rate in this codebase:
|
||||
# many msgstrs intentionally match msgid (units like "AI", "LLM", and proper nouns).
|
||||
run: |
|
||||
output=$(dennis-cmd lint --excluderules=W302 \
|
||||
changedetectionio/translations/*/LC_MESSAGES/messages.po)
|
||||
echo "$output"
|
||||
warnings=$(echo "$output" | awk '/Total number of warnings:/ {print $NF; exit}')
|
||||
if (( ${warnings:-0} > 0 )); then
|
||||
echo "ERROR: ${warnings} dennis warning(s) detected in .po files"
|
||||
echo "Fix the warning(s)."
|
||||
exit 1
|
||||
fi
|
||||
- name: Check translation catalog is up-to-date
|
||||
run: |
|
||||
pip install "$(grep -E '^babel==' requirements.txt)"
|
||||
|
||||
+5
-1
@@ -20,10 +20,11 @@ exclude = [
|
||||
select = [
|
||||
"B", # flake8-bugbear
|
||||
"B9",
|
||||
"C",
|
||||
"C",
|
||||
"E", # pycodestyle
|
||||
"F", # Pyflakes
|
||||
"I", # isort
|
||||
"INT", # flake8-gettext
|
||||
"N", # pep8-naming
|
||||
"UP", # pyupgrade
|
||||
"W", # pycodestyle
|
||||
@@ -43,6 +44,9 @@ ignore = [
|
||||
[lint.mccabe]
|
||||
max-complexity = 12
|
||||
|
||||
[lint.flake8-gettext]
|
||||
extend-function-names = ["_l", "lazy_gettext", "pgettext", "npgettext"]
|
||||
|
||||
[format]
|
||||
indent-style = "space"
|
||||
quote-style = "preserve"
|
||||
|
||||
@@ -30,7 +30,7 @@ Stop drowning in noise. Connect any LLM (OpenAI, Gemini, Anthropic, Ollama and m
|
||||
|
||||
**AI change summaries** — instead of staring at a raw diff, your notification reads _"Price dropped from $89.99 to $67.00"_ or _"3 new products added to the listing"_. Works globally or per-watch, with full control over the prompt.
|
||||
|
||||
Works with any model you already pay for — GPT-4o-mini and Gemini Flash handle this well at fractions of a cent per check. Or run it entirely locally with Ollama. Powered by [LiteLLM](https://github.com/BerriAI/litellm), giving you seamless access to [100+ supported providers and models](https://docs.litellm.ai/docs/providers).
|
||||
Works with any model you already pay for — GPT-4o-mini and Gemini Flash handle this well at fractions of a cent per check. Or run it entirely locally with **Ollama**, **vLLM**, **LM Studio**, or any **OpenAI-compatible self-hosted endpoint** — pick the *OpenAI-compatible (vLLM, LM Studio, llama.cpp)* option in the provider dropdown and point it at your server's `/v1` URL. Powered by [LiteLLM](https://github.com/BerriAI/litellm), giving you seamless access to [100+ supported providers and models](https://docs.litellm.ai/docs/providers).
|
||||
|
||||
[<img src="./docs/LLM-change-summary.jpeg" style="max-width:100%;" alt="AI-powered website change detection — plain language change summaries and smart alert rules" title="AI website change detection with LLM change summaries and intelligent alert filtering" />](https://changedetection.io?src=github)
|
||||
|
||||
|
||||
@@ -103,7 +103,28 @@ class Watch(Resource):
|
||||
# attr .last_changed will check for the last written text snapshot on change
|
||||
watch['last_changed'] = watch_obj.last_changed
|
||||
watch['viewed'] = watch_obj.viewed
|
||||
watch['link'] = watch_obj.link,
|
||||
watch['link'] = watch_obj.link
|
||||
|
||||
# Resolved processor config: tag override wins over watch-level config (mirrors restock processor logic)
|
||||
import json
|
||||
_restock_path = os.path.join(watch_obj.data_dir, 'restock_diff.json') if watch_obj.data_dir else None
|
||||
restock_config = {}
|
||||
if _restock_path and os.path.isfile(_restock_path):
|
||||
try:
|
||||
with open(_restock_path, 'r', encoding='utf-8') as _f:
|
||||
restock_config = json.load(_f).get('restock_diff') or {}
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"Failed to read restock_diff.json for watch {uuid}: {e}")
|
||||
restock_source = 'watch'
|
||||
tags = self.datastore.data['settings']['application'].get('tags', {})
|
||||
for tag_uuid in (watch_obj.get('tags') or []):
|
||||
tag = tags.get(tag_uuid, {})
|
||||
if tag.get('overrides_watch'):
|
||||
restock_config = dict(tag.get('processor_config_restock_diff') or {})
|
||||
restock_source = f'tag:{tag_uuid}'
|
||||
break
|
||||
watch['processor_config_restock_diff'] = restock_config
|
||||
watch['processor_config_restock_diff_source'] = restock_source
|
||||
|
||||
return watch
|
||||
|
||||
|
||||
@@ -3,6 +3,16 @@ from functools import wraps
|
||||
from flask import current_app, redirect, request
|
||||
from loguru import logger
|
||||
|
||||
# Endpoints exempt from auth when `shared_diff_access` is enabled.
|
||||
# Must be exact endpoint names — substring matching (GHSA-vwgh-2hvh-4xm5)
|
||||
# let the state-changing `/diff/<uuid>/extract` endpoints slip through
|
||||
# because their names share the `diff_history_page` prefix.
|
||||
SHARED_DIFF_READ_ONLY_ENDPOINTS = frozenset({
|
||||
'ui.ui_diff.diff_history_page',
|
||||
'ui.ui_diff.processor_asset',
|
||||
'ui.ui_diff.download_patch',
|
||||
})
|
||||
|
||||
def login_optionally_required(func):
|
||||
"""
|
||||
If password authentication is enabled, verify the user is logged in.
|
||||
@@ -20,7 +30,7 @@ def login_optionally_required(func):
|
||||
has_password_enabled = datastore.data['settings']['application'].get('password') or os.getenv("SALTED_PASS", False)
|
||||
|
||||
# Permitted
|
||||
if request.endpoint and 'diff_history_page' in request.endpoint and datastore.data['settings']['application'].get('shared_diff_access'):
|
||||
if request.endpoint in SHARED_DIFF_READ_ONLY_ENDPOINTS and datastore.data['settings']['application'].get('shared_diff_access'):
|
||||
return func(*args, **kwargs)
|
||||
elif request.method in flask_login.config.EXEMPT_METHODS:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
@@ -75,7 +75,7 @@ class import_url_list(Importer):
|
||||
self.remaining_data = []
|
||||
self.remaining_data.append(url)
|
||||
|
||||
flash(gettext("{} Imported from list in {:.2f}s, {} Skipped.").format(good, time.time() - now, len(self.remaining_data)))
|
||||
flash(gettext("{count} Imported from list in {duration}s, {skipped_count} Skipped.").format(count=good, duration=f"{time.time() - now:.2f}", skipped_count=len(self.remaining_data)))
|
||||
|
||||
|
||||
class import_distill_io_json(Importer):
|
||||
@@ -136,7 +136,7 @@ class import_distill_io_json(Importer):
|
||||
self.new_uuids.append(new_uuid)
|
||||
good += 1
|
||||
|
||||
flash(gettext("{} Imported from Distill.io in {:.2f}s, {} Skipped.").format(len(self.new_uuids), time.time() - now, len(self.remaining_data)))
|
||||
flash(gettext("{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped.").format(count=len(self.new_uuids), duration=f"{time.time() - now:.2f}", skipped_count=len(self.remaining_data)))
|
||||
|
||||
|
||||
class import_xlsx_wachete(Importer):
|
||||
@@ -212,7 +212,7 @@ class import_xlsx_wachete(Importer):
|
||||
logger.error(e)
|
||||
flash(gettext("Error processing row number {}, check all cell data types are correct, row was skipped.").format(row_id), 'error')
|
||||
|
||||
flash(gettext("{} imported from Wachete .xlsx in {:.2f}s").format(len(self.new_uuids), time.time() - now))
|
||||
flash(gettext("{count} imported from Wachete .xlsx in {duration}s").format(count=len(self.new_uuids), duration=f"{time.time() - now:.2f}"))
|
||||
|
||||
|
||||
class import_xlsx_custom(Importer):
|
||||
@@ -293,4 +293,4 @@ class import_xlsx_custom(Importer):
|
||||
logger.error(e)
|
||||
flash(gettext("Error processing row number {}, check all cell data types are correct, row was skipped.").format(row_i), 'error')
|
||||
|
||||
flash(gettext("{} imported from custom .xlsx in {:.2f}s").format(len(self.new_uuids), time.time() - now))
|
||||
flash(gettext("{count} imported from custom .xlsx in {duration}s").format(count=len(self.new_uuids), duration=f"{time.time() - now:.2f}"))
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="tabs collapsable">
|
||||
<ul>
|
||||
<li class="tab" id=""><a href="#url-list">{{ _('URL List') }}</a></li>
|
||||
<li class="tab"><a href="#distill-io">{{ _('Distill.io') }}</a></li>
|
||||
<li class="tab"><a href="#distill-io">Distill.io</a></li>
|
||||
<li class="tab"><a href="#xlsx">{{ _('.XLSX & Wachete') }}</a></li>
|
||||
<li class="tab"><a href="{{url_for('backups.restore.restore')}}">{{ _('Backup Restore') }}</a></li>
|
||||
</ul>
|
||||
@@ -45,6 +45,7 @@
|
||||
<div class="tab-pane-inner" id="distill-io">
|
||||
<div class="pure-control-group">
|
||||
{{ _('Copy and Paste your Distill.io watch \'export\' file, this should be a JSON file.') }}<br>
|
||||
{# TRANSLATORS: CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303 #}
|
||||
{{ _('This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, <code>config:selections</code>, the rest (including <code>schedule</code>) are ignored.')|safe }}
|
||||
<br>
|
||||
<p>
|
||||
@@ -103,7 +104,7 @@
|
||||
{% for n in range(4) %}
|
||||
<td><select name="custom_xlsx[col_type_{{n}}]">
|
||||
<option value="" style="color: #aaa"> -- {{ _('none') }} --</option>
|
||||
<option value="url">{{ _('URL') }}</option>
|
||||
<option value="url">URL</option>
|
||||
<option value="title">{{ _('Title') }}</option>
|
||||
<option value="include_filters">{{ _('CSS/xPath filter') }}</option>
|
||||
<option value="tag">{{ _('Group / Tag name(s)') }}</option>
|
||||
|
||||
@@ -36,6 +36,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
default['llm'] = {
|
||||
'llm_model': _stored_llm.get('model', ''),
|
||||
'llm_api_base': _stored_llm.get('api_base', ''),
|
||||
'llm_provider_kind': _stored_llm.get('provider_kind', ''),
|
||||
'llm_local_token_multiplier': _stored_llm.get('local_token_multiplier', 5),
|
||||
'llm_change_summary_default': datastore.data['settings']['application'].get('llm_change_summary_default', ''),
|
||||
'llm_override_diff_with_summary': datastore.data['settings']['application'].get('llm_override_diff_with_summary', True),
|
||||
'llm_restock_use_fallback_extract': datastore.data['settings']['application'].get('llm_restock_use_fallback_extract', True),
|
||||
@@ -148,6 +150,10 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
'model': (llm_data.get('llm_model') or '').strip(),
|
||||
'api_key': effective_api_key,
|
||||
'api_base': (llm_data.get('llm_api_base') or '').strip(),
|
||||
# Identifies a self-hosted OpenAI-compatible endpoint so reasoning-friendly
|
||||
# token caps can be applied conditionally (cloud-LLM defaults stay tight).
|
||||
'provider_kind': (llm_data.get('llm_provider_kind') or '').strip(),
|
||||
'local_token_multiplier': int(llm_data.get('llm_local_token_multiplier') or 5),
|
||||
'token_budget_month': existing_llm.get('token_budget_month', 0),
|
||||
'max_input_chars': existing_llm.get('max_input_chars', 0),
|
||||
**preserved_counters,
|
||||
@@ -181,8 +187,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
# Check CPU core availability and warn if worker count is high
|
||||
cpu_count = os.cpu_count()
|
||||
if cpu_count and new_worker_count >= (cpu_count * 0.9):
|
||||
flash(gettext("Warning: Worker count ({}) is close to or exceeds available CPU cores ({})").format(
|
||||
new_worker_count, cpu_count), 'warning')
|
||||
flash(gettext("Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})").format(
|
||||
worker_count=new_worker_count, cpu_count=cpu_count), 'warning')
|
||||
|
||||
result = worker_pool.adjust_async_worker_count(
|
||||
new_count=new_worker_count,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from flask import Blueprint, jsonify, redirect, url_for, flash
|
||||
from flask_babel import gettext
|
||||
@@ -8,6 +11,44 @@ from changedetectionio.store import ChangeDetectionStore
|
||||
from changedetectionio.auth_decorator import login_optionally_required
|
||||
|
||||
|
||||
class _LiteLLMWarningCapture(logging.Handler):
|
||||
"""Capture warnings emitted on the 'LiteLLM' stdlib logger during a single call.
|
||||
|
||||
litellm.get_valid_models() catches HTTP/auth errors internally, logs a warning,
|
||||
and returns []. Without capturing that warning we can't tell the user *why*
|
||||
no models came back (bad key vs. offline vs. genuinely empty model list).
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__(level=logging.WARNING)
|
||||
self.messages = []
|
||||
|
||||
def emit(self, record):
|
||||
try:
|
||||
self.messages.append(record.getMessage())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _humanize_litellm_error(raw: str) -> str:
|
||||
# litellm warnings typically look like:
|
||||
# "Error getting valid models: Failed to get models: { 'error': { 'message': '...' } }"
|
||||
# Pull the inner provider message when present; otherwise trim the boilerplate.
|
||||
if not raw:
|
||||
return raw
|
||||
m = re.search(r'\{.*\}', raw, re.DOTALL)
|
||||
if m:
|
||||
try:
|
||||
body = json.loads(m.group(0))
|
||||
inner = (body.get('error') or {}).get('message') or body.get('message')
|
||||
if inner:
|
||||
return inner
|
||||
except Exception:
|
||||
pass
|
||||
cleaned = re.sub(r'^Error getting valid models:\s*', '', raw)
|
||||
cleaned = re.sub(r'^Failed to get models:\s*', '', cleaned).strip()
|
||||
return cleaned[:500]
|
||||
|
||||
|
||||
def construct_llm_blueprint(datastore: ChangeDetectionStore):
|
||||
llm_blueprint = Blueprint('llm', __name__)
|
||||
|
||||
@@ -30,19 +71,38 @@ def construct_llm_blueprint(datastore: ChangeDetectionStore):
|
||||
api_key = (datastore.data['settings']['application'].get('llm') or {}).get('api_key', '')
|
||||
logger.debug("LLM model list: no api_key in request, using stored key")
|
||||
|
||||
_PREFIXES = {'gemini': 'gemini/', 'ollama': 'ollama/', 'openrouter': 'openrouter/'}
|
||||
_PREFIXES = {'gemini': 'gemini/', 'ollama': 'ollama/', 'openrouter': 'openrouter/',
|
||||
'openai_compatible': 'openai/'}
|
||||
# vLLM / LM Studio / llama.cpp speak OpenAI's wire format — route through litellm's
|
||||
# 'openai' provider but keep the UI-level name distinct from cloud OpenAI.
|
||||
_LITELLM_PROVIDER = {'openai_compatible': 'openai'}
|
||||
prefix = _PREFIXES.get(provider, '')
|
||||
litellm_provider = _LITELLM_PROVIDER.get(provider, provider)
|
||||
|
||||
try:
|
||||
import litellm
|
||||
logger.debug(f"LLM model list: calling litellm.get_valid_models provider={provider!r} api_base={api_base!r}")
|
||||
raw = litellm.get_valid_models(
|
||||
check_provider_endpoint=True,
|
||||
custom_llm_provider=provider,
|
||||
api_key=api_key or None,
|
||||
api_base=api_base or None,
|
||||
) or []
|
||||
logger.debug(f"LLM model list: calling litellm.get_valid_models provider={provider!r} (litellm={litellm_provider!r}) api_base={api_base!r}")
|
||||
|
||||
capture = _LiteLLMWarningCapture()
|
||||
litellm_logger = logging.getLogger('LiteLLM')
|
||||
litellm_logger.addHandler(capture)
|
||||
try:
|
||||
raw = litellm.get_valid_models(
|
||||
check_provider_endpoint=True,
|
||||
custom_llm_provider=litellm_provider,
|
||||
api_key=api_key or None,
|
||||
api_base=api_base or None,
|
||||
) or []
|
||||
finally:
|
||||
litellm_logger.removeHandler(capture)
|
||||
|
||||
models = sorted({(m if m.startswith(prefix) else prefix + m) for m in raw})
|
||||
|
||||
if not models and capture.messages:
|
||||
err = _humanize_litellm_error(capture.messages[-1])
|
||||
logger.debug(f"LLM model list: 0 models, surfacing captured litellm warning: {err!r}")
|
||||
return jsonify({'models': [], 'error': err}), 400
|
||||
|
||||
logger.debug(f"LLM model list: got {len(models)} models for provider={provider!r}")
|
||||
return jsonify({'models': models, 'error': None})
|
||||
except Exception as e:
|
||||
@@ -67,14 +127,18 @@ def construct_llm_blueprint(datastore: ChangeDetectionStore):
|
||||
|
||||
try:
|
||||
logger.debug(f"LLM connection test: sending test prompt to model={model!r}")
|
||||
# Reuse the same multiplier path the production calls use, so cloud providers
|
||||
# stay on a small base cap (matching upstream's pre-existing behavior) and only
|
||||
# 'openai_compatible' endpoints opt into the reasoning-friendly headroom.
|
||||
from changedetectionio.llm.evaluator import apply_local_token_multiplier
|
||||
text, total_tokens, input_tokens, output_tokens = completion(
|
||||
model=model,
|
||||
messages=[{'role': 'user', 'content':
|
||||
'Reply with exactly five words confirming you are ready.'}],
|
||||
'Respond with just the word: ready'}],
|
||||
api_key=llm_cfg.get('api_key') or None,
|
||||
api_base=api_base or None,
|
||||
timeout=20,
|
||||
max_tokens=200,
|
||||
timeout=30,
|
||||
max_tokens=apply_local_token_multiplier(200, llm_cfg),
|
||||
)
|
||||
reply = text.strip()
|
||||
if not reply:
|
||||
@@ -122,7 +186,7 @@ def construct_llm_blueprint(datastore: ChangeDetectionStore):
|
||||
except OSError as e:
|
||||
logger.warning(f"Could not remove LLM summary cache file {f}: {e}")
|
||||
logger.info(f"LLM summary cache cleared: {count} file(s) removed")
|
||||
flash(gettext("AI summary cache cleared (%(n)s file(s) removed).", n=count), 'notice')
|
||||
flash(gettext("AI summary cache cleared ({} file(s) removed).").format(count), 'notice')
|
||||
return redirect(url_for('settings.settings_page') + '#ai')
|
||||
|
||||
return llm_blueprint
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
<li class="tab"><a href="#fetching">{{ _('Fetching') }}</a></li>
|
||||
<li class="tab"><a href="#filters">{{ _('Global Filters') }}</a></li>
|
||||
<li class="tab"><a href="#ui-options">{{ _('UI Options') }}</a></li>
|
||||
<li class="tab"><a href="#api">{{ _('API') }}</a></li>
|
||||
<li class="tab"><a href="#rss">{{ _('RSS') }}</a></li>
|
||||
<li class="tab"><a href="#api">API</a></li>
|
||||
<li class="tab"><a href="#rss">RSS</a></li>
|
||||
<li class="tab"><a href="{{ url_for('backups.create') }}">{{ _('Backups') }}</a></li>
|
||||
<li class="tab"><a href="#timedate">{{ _('Time & Date') }}</a></li>
|
||||
<li class="tab"><a href="#proxies">{{ _('CAPTCHA & Proxies') }}</a></li>
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
</optgroup>
|
||||
<optgroup label="{{ _('Local / Self-hosted') }}">
|
||||
<option value="ollama">Ollama (local)</option>
|
||||
<option value="openai_compatible">{{ _('OpenAI-compatible (vLLM, LM Studio, llama.cpp)') }}</option>
|
||||
</optgroup>
|
||||
<optgroup label="OpenRouter">
|
||||
<option value="openrouter">OpenRouter (200+ models)</option>
|
||||
@@ -127,6 +128,18 @@
|
||||
<span class="pure-form-message-inline">{{ _('Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers.') }}</span>
|
||||
</div>
|
||||
|
||||
{# Hidden field carrying the dropdown selection so the backend knows when to apply
|
||||
reasoning-friendly token caps (only for self-hosted OpenAI-compatible endpoints). #}
|
||||
{{ form.llm.form.llm_provider_kind() }}
|
||||
|
||||
<div class="pure-control-group" id="llm-local-advanced-group" style="display:none">
|
||||
<label for="{{ form.llm.form.llm_local_token_multiplier.id }}">{{ form.llm.form.llm_local_token_multiplier.label.text }}</label>
|
||||
{{ form.llm.form.llm_local_token_multiplier() }}
|
||||
<span class="pure-form-message-inline">
|
||||
{{ _('Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to %(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps.', default='5x') | safe }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group" id="llm-fetch-group" style="display:none">
|
||||
<label></label>
|
||||
<button type="button" id="llm-fetch-btn" class="pure-button button-xsmall" onclick="llmFetchModels()"
|
||||
@@ -333,7 +346,7 @@
|
||||
<span class="llm-env-badge">{{ _('(set via <code>LLM_MAX_INPUT_CHARS</code>)') | safe }}</span>
|
||||
{% else %}
|
||||
{{ form.llm.form.llm_max_input_chars(placeholder='100000', value=llm_stored.get('max_input_chars', 100000) or '') }}
|
||||
<span class="llm-field-hint">{{ _('characters — currently enforcing: %(n)s', n='{:,}'.format(llm_effective_max_input_chars)) }}</span>
|
||||
<span class="llm-field-hint">{{ _('characters — currently enforcing: %(limit)s', limit='{:,}'.format(llm_effective_max_input_chars)) }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
@@ -364,7 +377,7 @@
|
||||
<span class="llm-env-badge">{{ _('(set via <code>LLM_MAX_INPUT_CHARS</code>)') | safe }}</span>
|
||||
{% else %}
|
||||
{{ form.llm.form.llm_max_input_chars(placeholder='100000', value=llm_stored.get('max_input_chars', 100000) or '') }}
|
||||
<span class="llm-field-hint">{{ _('characters — currently enforcing: %(n)s', n='{:,}'.format(llm_effective_max_input_chars)) }}</span>
|
||||
<span class="llm-field-hint">{{ _('characters — currently enforcing: %(limit)s', limit='{:,}'.format(llm_effective_max_input_chars)) }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
@@ -377,14 +390,15 @@
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const LIVE_PROVIDERS = ['openai', 'anthropic', 'gemini', 'ollama', 'openrouter'];
|
||||
const LIVE_PROVIDERS = ['openai', 'anthropic', 'gemini', 'ollama', 'openai_compatible', 'openrouter'];
|
||||
const BASE_DEFAULTS = { ollama: 'http://localhost:11434' };
|
||||
const KEY_HINTS = {
|
||||
openai: '{{ _("platform.openai.com → API keys") }}',
|
||||
anthropic: '{{ _("console.anthropic.com → API keys") }}',
|
||||
gemini: '{{ _("aistudio.google.com → Get API key") }}',
|
||||
ollama: '{{ _("No API key needed for local Ollama") }}',
|
||||
openrouter: '{{ _("openrouter.ai → Keys") }}',
|
||||
openai: '{{ _("platform.openai.com → API keys") }}',
|
||||
anthropic: '{{ _("console.anthropic.com → API keys") }}',
|
||||
gemini: '{{ _("aistudio.google.com → Get API key") }}',
|
||||
ollama: '{{ _("No API key needed for local Ollama") }}',
|
||||
openai_compatible: '{{ _("Bearer token for your self-hosted server (vLLM, LM Studio, etc.)") }}',
|
||||
openrouter: '{{ _("openrouter.ai → Keys") }}',
|
||||
};
|
||||
|
||||
window.llmDisclaimerToggle = function (cb) {
|
||||
@@ -393,20 +407,31 @@
|
||||
};
|
||||
|
||||
window.llmOnProviderChange = function (provider) {
|
||||
const fetchGroup = document.getElementById('llm-fetch-group');
|
||||
const baseGroup = document.getElementById('llm-base-group');
|
||||
const modelSelGrp = document.getElementById('llm-model-select-group');
|
||||
const baseField = document.querySelector('[name="llm-llm_api_base"]');
|
||||
const hint = document.getElementById('llm-key-hint');
|
||||
const fetchGroup = document.getElementById('llm-fetch-group');
|
||||
const baseGroup = document.getElementById('llm-base-group');
|
||||
const modelSelGrp = document.getElementById('llm-model-select-group');
|
||||
const localAdvGrp = document.getElementById('llm-local-advanced-group');
|
||||
const baseField = document.querySelector('[name="llm-llm_api_base"]');
|
||||
const kindField = document.querySelector('[name="llm-llm_provider_kind"]');
|
||||
const hint = document.getElementById('llm-key-hint');
|
||||
|
||||
fetchGroup.style.display = LIVE_PROVIDERS.includes(provider) ? '' : 'none';
|
||||
|
||||
const needsBase = provider === 'ollama';
|
||||
const needsBase = provider === 'ollama' || provider === 'openai_compatible';
|
||||
baseGroup.style.display = needsBase ? '' : 'none';
|
||||
if (BASE_DEFAULTS[provider] !== undefined) {
|
||||
if (!baseField.value) baseField.value = BASE_DEFAULTS[provider];
|
||||
}
|
||||
|
||||
// Persist the dropdown selection so the backend can branch on provider kind
|
||||
// (currently only 'openai_compatible' triggers the local-multiplier code path).
|
||||
if (kindField) kindField.value = provider || '';
|
||||
|
||||
// Show the local-endpoint advanced settings (token multiplier) only for the
|
||||
// OpenAI-compatible self-hosted option. Cloud providers and Ollama get the
|
||||
// original tight caps and don't see this section at all.
|
||||
if (localAdvGrp) localAdvGrp.style.display = (provider === 'openai_compatible') ? '' : 'none';
|
||||
|
||||
hint.textContent = KEY_HINTS[provider] || '';
|
||||
modelSelGrp.style.display = 'none';
|
||||
document.getElementById('llm-fetch-status').textContent = '';
|
||||
@@ -444,7 +469,7 @@
|
||||
|
||||
if (!data.models || data.models.length === 0) {
|
||||
statusEl.style.color = '#e67e22';
|
||||
statusEl.textContent = '{{ _("No models returned — check your API key.") }}';
|
||||
statusEl.textContent = '{{ _("No models returned by the provider.") }}';
|
||||
selGroup.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
@@ -516,6 +541,11 @@
|
||||
if (m.startsWith('gemini/')) guessed = 'gemini';
|
||||
else if (m.startsWith('ollama/')) guessed = 'ollama';
|
||||
else if (m.startsWith('openrouter/')) guessed = 'openrouter';
|
||||
else if (m.startsWith('openai/')) {
|
||||
// openai/<model> + custom api_base = self-hosted OpenAI-compatible (vLLM etc.)
|
||||
const baseField = document.querySelector('[name="llm-llm_api_base"]');
|
||||
guessed = (baseField && baseField.value.trim()) ? 'openai_compatible' : 'openai';
|
||||
}
|
||||
else if (m.startsWith('claude')) guessed = 'anthropic';
|
||||
else if (m.startsWith('gpt') || m.startsWith('o1') || m.startsWith('o3')) guessed = 'openai';
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="tab-pane-inner" id="filters-and-triggers">
|
||||
{# TRANSLATORS: CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303 #}
|
||||
<p>{{ _('These settings are <strong><i>added</i></strong> to any existing watch configurations.')|safe }}</p>
|
||||
|
||||
{% include "edit/include_subtract.html" %}
|
||||
|
||||
@@ -307,8 +307,8 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, worker_pool,
|
||||
# Provide feedback about skipped watches
|
||||
skipped_count = len(watches_to_queue) - len(watches_to_queue_filtered)
|
||||
if skipped_count > 0:
|
||||
flash(gettext("Queued {} watches for rechecking ({} already queued or running).").format(
|
||||
len(watches_to_queue_filtered), skipped_count))
|
||||
flash(gettext("Queued {count} watches for rechecking ({skipped_count} already queued or running).").format(
|
||||
count=len(watches_to_queue_filtered), skipped_count=skipped_count))
|
||||
else:
|
||||
if len(watches_to_queue_filtered) == 1:
|
||||
flash(gettext("Queued 1 watch for rechecking."))
|
||||
|
||||
@@ -198,10 +198,12 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
best_from = watch.get_from_version_based_on_last_viewed
|
||||
from_version = request.args.get('from_version', best_from if best_from else dates[-2])
|
||||
to_version = request.args.get('to_version', dates[-1])
|
||||
all_changes = request.args.get('all_changes', '0') == '1'
|
||||
ignore_whitespace = request.args.get('ignore_whitespace', '0') == '1'
|
||||
show_removed = request.args.get('removed', '1') == '1'
|
||||
show_added = request.args.get('added', '1') == '1'
|
||||
from changedetectionio.llm.evaluator import DiffPrefs
|
||||
prefs = DiffPrefs.from_request_args(request.args)
|
||||
all_changes = prefs.all_changes
|
||||
ignore_whitespace = prefs.ignore_whitespace
|
||||
show_removed = prefs.show_removed
|
||||
show_added = prefs.show_added
|
||||
|
||||
def _prep(text):
|
||||
"""Optionally normalise whitespace on each line before diffing."""
|
||||
@@ -263,26 +265,24 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
return jsonify({'summary': None, 'error': 'No differences found'})
|
||||
|
||||
from changedetectionio.llm.evaluator import (
|
||||
summarise_change, get_effective_summary_prompt,
|
||||
summarise_change, get_effective_summary_prompt, build_summary_cache_prompt,
|
||||
is_global_token_budget_exceeded, get_global_token_budget_month,
|
||||
LLMInputTooLargeError,
|
||||
)
|
||||
|
||||
effective_prompt = get_effective_summary_prompt(watch, datastore)
|
||||
from changedetectionio.llm.prompt_builder import build_change_summary_system_prompt
|
||||
# Diff-pref flags + system prompt are part of the cache key so prompt changes bust the cache
|
||||
# Diff-pref flags + system prompt are part of the cache key so prompt changes bust the cache.
|
||||
_max_summary_tokens = datastore.data['settings']['application'].get('llm_max_summary_tokens', 3000)
|
||||
cache_prompt = (
|
||||
effective_prompt
|
||||
+ f'\x00prefs:all={int(all_changes)},ws={int(ignore_whitespace)}'
|
||||
f',rm={int(show_removed)},add={int(show_added)}'
|
||||
+ f'\x00sys:{build_change_summary_system_prompt()}'
|
||||
+ f'\x00max_tokens:{_max_summary_tokens}'
|
||||
cache_prompt = build_summary_cache_prompt(
|
||||
effective_prompt=get_effective_summary_prompt(watch, datastore),
|
||||
max_summary_tokens=_max_summary_tokens,
|
||||
prefs=prefs,
|
||||
)
|
||||
|
||||
# Check cache — keyed by version pair + prompt hash (invalidates if prompt changes)
|
||||
cached = watch.get_llm_diff_summary(from_version, to_version, prompt=cache_prompt)
|
||||
if cached:
|
||||
import time
|
||||
datastore.set_last_viewed(uuid, int(time.time()))
|
||||
return jsonify({'summary': cached, 'error': None, 'cached': True})
|
||||
|
||||
# Check global monthly token budget before making an LLM call
|
||||
@@ -316,6 +316,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not cache llm summary for {uuid}: {e}")
|
||||
|
||||
import time
|
||||
datastore.set_last_viewed(uuid, int(time.time()))
|
||||
return jsonify({'summary': summary, 'error': None, 'cached': False})
|
||||
|
||||
@diff_blueprint.route("/diff/<uuid_str:uuid>/extract", methods=['GET'])
|
||||
|
||||
@@ -365,6 +365,7 @@ Math: {{ 1 + 1 }}") }}
|
||||
</fieldset>
|
||||
<fieldset class="pure-control-group">
|
||||
{{ render_checkbox_field(form.sort_text_alphabetically) }}
|
||||
{# TRANSLATORS: CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303 #}
|
||||
<span class="pure-form-message-inline">{{ _('Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below.')|safe }}</span>
|
||||
</fieldset>
|
||||
<fieldset class="pure-control-group">
|
||||
|
||||
@@ -163,12 +163,13 @@ window.watchOverviewI18n = {
|
||||
data-confirm-type="danger"
|
||||
data-confirm-title="{{ _('Clear Histories') }}"
|
||||
data-confirm-message="{{ _('<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>') }}"
|
||||
{# TRANSLATORS: Universally recognized; typically left as-is. dennis-ignore: W302 #}
|
||||
data-confirm-button="{{ _('OK') }}"><i data-feather="trash-2" style="width: 14px; height: 14px; stroke: white; margin-right: 4px;"></i>{{ _('Clear/reset history') }}</button>
|
||||
<button class="pure-button button-secondary button-xsmall" style="background: #dd4242;" name="op" value="delete"
|
||||
data-requires-confirm
|
||||
data-confirm-type="danger"
|
||||
data-confirm-title="{{ _('Delete Watches?') }}"
|
||||
data-confirm-message="{{ _('<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>') }}"
|
||||
data-confirm-message="{{ _('<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>') }}"
|
||||
data-confirm-button="{{ _('Delete') }}"><i data-feather="trash" style="width: 14px; height: 14px; stroke: white; margin-right: 4px;"></i>{{ _('Delete') }}</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -414,7 +414,7 @@ def _jinja2_filter_sanitize_tag_class(tag_title):
|
||||
return sanitized if sanitized else 'tag'
|
||||
|
||||
# Import login_optionally_required from auth_decorator
|
||||
from changedetectionio.auth_decorator import login_optionally_required
|
||||
from changedetectionio.auth_decorator import SHARED_DIFF_READ_ONLY_ENDPOINTS, login_optionally_required
|
||||
|
||||
# When nobody is logged in Flask-Login's current_user is set to an AnonymousUser object.
|
||||
class User(flask_login.UserMixin):
|
||||
@@ -541,7 +541,7 @@ def changedetection_app(config=None, datastore_o=None):
|
||||
# Permitted
|
||||
elif request.endpoint and 'login' in request.endpoint:
|
||||
return None
|
||||
elif request.endpoint and 'diff_history_page' in request.endpoint and datastore.data['settings']['application'].get('shared_diff_access'):
|
||||
elif request.endpoint in SHARED_DIFF_READ_ONLY_ENDPOINTS and datastore.data['settings']['application'].get('shared_diff_access'):
|
||||
return None
|
||||
elif request.method in flask_login.config.EXEMPT_METHODS:
|
||||
return None
|
||||
|
||||
+62
-10
@@ -17,6 +17,7 @@ from wtforms import (
|
||||
Form,
|
||||
Field,
|
||||
FloatField,
|
||||
HiddenField,
|
||||
IntegerField,
|
||||
PasswordField,
|
||||
RadioField,
|
||||
@@ -279,12 +280,44 @@ class TimeBetweenCheckForm(Form):
|
||||
return True
|
||||
|
||||
|
||||
class LabelAfterInputTableWidget(widgets.TableWidget):
|
||||
"""
|
||||
Variant of WTForms' TableWidget that renders the input cell before the label cell,
|
||||
so each row is <td>input</td><th>label</th> instead of the default <th>label</th><td>input</td>.
|
||||
"""
|
||||
|
||||
def __call__(self, field, **kwargs):
|
||||
from markupsafe import Markup
|
||||
from wtforms.widgets import html_params
|
||||
|
||||
html = []
|
||||
if self.with_table_tag:
|
||||
kwargs.setdefault("id", field.id)
|
||||
html.append(f"<table {html_params(**kwargs)}>")
|
||||
hidden = ""
|
||||
for subfield in field:
|
||||
if subfield.type in ("HiddenField", "CSRFTokenField"):
|
||||
hidden += str(subfield)
|
||||
else:
|
||||
html.append(
|
||||
f"<tr><td>{hidden}{subfield}</td><th>{subfield.label}</th></tr>"
|
||||
)
|
||||
hidden = ""
|
||||
if self.with_table_tag:
|
||||
html.append("</table>")
|
||||
if hidden:
|
||||
html.append(hidden)
|
||||
return Markup("".join(html))
|
||||
|
||||
|
||||
class EnhancedFormField(FormField):
|
||||
"""
|
||||
An enhanced FormField that supports conditional validation with top-level error messages.
|
||||
Adds a 'top_errors' property for validation errors at the FormField level.
|
||||
"""
|
||||
|
||||
widget = LabelAfterInputTableWidget()
|
||||
|
||||
def __init__(self, form_class, label=None, validators=None, separator="-",
|
||||
conditional_field=None, conditional_message=None, conditional_test_function=None, **kwargs):
|
||||
"""
|
||||
@@ -618,8 +651,8 @@ class ValidateCSSJSONXPATHInput(object):
|
||||
try:
|
||||
elementpath.select(tree, line.strip(), parser=SafeXPath3Parser)
|
||||
except elementpath.ElementPathError as e:
|
||||
message = field.gettext('\'%s\' is not a valid XPath expression. (%s)')
|
||||
raise ValidationError(message % (line, str(e)))
|
||||
message = field.gettext('\'%(expression)s\' is not a valid XPath expression. (%(error)s)')
|
||||
raise ValidationError(message % {'expression': line, 'error': str(e)})
|
||||
except:
|
||||
raise ValidationError("A system-error occurred when validating your XPath expression")
|
||||
|
||||
@@ -633,8 +666,8 @@ class ValidateCSSJSONXPATHInput(object):
|
||||
try:
|
||||
tree.xpath(line.strip())
|
||||
except etree.XPathEvalError as e:
|
||||
message = field.gettext('\'%s\' is not a valid XPath expression. (%s)')
|
||||
raise ValidationError(message % (line, str(e)))
|
||||
message = field.gettext('\'%(expression)s\' is not a valid XPath expression. (%(error)s)')
|
||||
raise ValidationError(message % {'expression': line, 'error': str(e)})
|
||||
except:
|
||||
raise ValidationError("A system-error occurred when validating your XPath expression")
|
||||
|
||||
@@ -653,8 +686,8 @@ class ValidateCSSJSONXPATHInput(object):
|
||||
try:
|
||||
parse(input)
|
||||
except (JsonPathParserError, JsonPathLexerError) as e:
|
||||
message = field.gettext('\'%s\' is not a valid JSONPath expression. (%s)')
|
||||
raise ValidationError(message % (input, str(e)))
|
||||
message = field.gettext('\'%(expression)s\' is not a valid JSONPath expression. (%(error)s)')
|
||||
raise ValidationError(message % {'expression': input, 'error': str(e)})
|
||||
except:
|
||||
raise ValidationError("A system-error occurred when validating your JSONPath expression")
|
||||
|
||||
@@ -677,8 +710,8 @@ class ValidateCSSJSONXPATHInput(object):
|
||||
validate_jq_expression(input)
|
||||
jq.compile(input)
|
||||
except (ValueError) as e:
|
||||
message = field.gettext('\'%s\' is not a valid jq expression. (%s)')
|
||||
raise ValidationError(message % (input, str(e)))
|
||||
message = field.gettext('\'%(expression)s\' is not a valid jq expression. (%(error)s)')
|
||||
raise ValidationError(message % {'expression': input, 'error': str(e)})
|
||||
except:
|
||||
raise ValidationError("A system-error occurred when validating your jq expression")
|
||||
|
||||
@@ -728,7 +761,7 @@ class ValidateStartsWithRegex(object):
|
||||
raise ValidationError(self.message or _l("Invalid value."))
|
||||
|
||||
class quickWatchForm(Form):
|
||||
url = StringField(_l('URL'), validators=[validateURL()])
|
||||
url = StringField('URL', validators=[validateURL()])
|
||||
tags = StringTagUUID(_l('Group tag'), validators=[validators.Optional()])
|
||||
watch_submit_button = SubmitField(_l('Watch'), render_kw={"class": "pure-button pure-button-primary"})
|
||||
processor = RadioField(_l('Processor'), choices=lambda: processors.available_processors(), default=processors.get_default_processor)
|
||||
@@ -843,6 +876,7 @@ class processor_text_json_diff_form(commonSettingsForm):
|
||||
|
||||
conditions_match_logic = RadioField(_l('Match'), choices=[('ALL', _l('Match all of the following')),('ANY', _l('Match any of the following'))], default='ALL')
|
||||
conditions = FieldList(FormField(ConditionFormRow), min_entries=1) # Add rule logic here
|
||||
# dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
use_page_title_in_list = TernaryNoneBooleanField(_l('Use page <title> in list'), default=None)
|
||||
|
||||
history_snapshot_max_length = IntegerField(_l('Number of history items per watch to keep'), render_kw={"style": "width: 5em;"}, validators=[validators.Optional(), validators.NumberRange(min=2)])
|
||||
@@ -991,6 +1025,7 @@ class globalSettingsApplicationUIForm(Form):
|
||||
open_diff_in_new_tab = BooleanField(_l("Open 'History' page in a new tab"), default=True, validators=[validators.Optional()])
|
||||
socket_io_enabled = BooleanField(_l('Realtime UI Updates Enabled'), default=True, validators=[validators.Optional()])
|
||||
favicons_enabled = BooleanField(_l('Favicons Enabled'), default=True, validators=[validators.Optional()])
|
||||
# dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
use_page_title_in_list = BooleanField(_l('Use page <title> in watch overview list')) #BooleanField=True
|
||||
|
||||
# datastore.data['settings']['application']..
|
||||
@@ -1071,7 +1106,6 @@ class globalSettingsLLMForm(Form):
|
||||
_l('API Key'),
|
||||
validators=[validators.Optional()],
|
||||
render_kw={
|
||||
"placeholder": _l('Leave blank to use LITELLM_API_KEY env var'),
|
||||
"autocomplete": "off",
|
||||
"style": "width: 24em;",
|
||||
},
|
||||
@@ -1084,6 +1118,24 @@ class globalSettingsLLMForm(Form):
|
||||
"style": "width: 24em;",
|
||||
},
|
||||
)
|
||||
# Persisted by the Provider dropdown JS — lets the backend distinguish a self-hosted
|
||||
# OpenAI-compatible endpoint (vLLM, LM Studio, llama.cpp) from cloud OpenAI, so we can
|
||||
# apply reasoning-friendly token caps only when the user opted in.
|
||||
llm_provider_kind = HiddenField(
|
||||
validators=[validators.Optional()],
|
||||
default='',
|
||||
)
|
||||
# Multiplier applied to LLM max_tokens caps when provider_kind == 'openai_compatible'.
|
||||
# Reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought into
|
||||
# message.reasoning_content before the final answer lands in message.content.
|
||||
# Local self-hosted models cost no per-token money, so giving them headroom is cheap;
|
||||
# cloud providers stay on the original tight caps so existing users see no cost change.
|
||||
llm_local_token_multiplier = IntegerField(
|
||||
_l('Token multiplier for local reasoning models'),
|
||||
validators=[validators.Optional(), validators.NumberRange(min=1, max=20)],
|
||||
default=5,
|
||||
render_kw={"placeholder": "5", "style": "width: 6em;"},
|
||||
)
|
||||
llm_change_summary_default = TextAreaField(
|
||||
_l('Default AI Change Summary prompt'),
|
||||
validators=[validators.Optional(), validators.Length(max=2000)],
|
||||
|
||||
@@ -16,6 +16,7 @@ Environment variable overrides (take priority over datastore settings):
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from loguru import logger
|
||||
|
||||
@@ -81,8 +82,13 @@ def _cached_system(text: str, model: str = '') -> dict:
|
||||
|
||||
LLM_DEFAULT_MAX_SUMMARY_TOKENS = 3000
|
||||
|
||||
# Output-token cap for the JSON-returning calls (intent eval, preview, setup/prefilter).
|
||||
# Mirrors client.py's _MAX_COMPLETION_TOKENS so the multiplier helper has a base value
|
||||
# to scale; cloud-LLM users hit this default unmodified, preserving prior cost defaults.
|
||||
JSON_RESPONSE_MAX_TOKENS = 400
|
||||
|
||||
# Default prompt used when the user hasn't configured llm_change_summary
|
||||
DEFAULT_CHANGE_SUMMARY_PROMPT = "Describe in plain English what changed — list what was added or removed as bullet points, including key details for each item. Be careful of content that merely just moved around, you should mention that it moved but dont report that it was added/removed etc. Be considerate of the style content you are summarising the change of, adjust your report accordingly. Do not quote non-English text verbatim; translate and summarise all content into English. Your entire response must be in English."
|
||||
DEFAULT_CHANGE_SUMMARY_PROMPT = "You are given a standard unix patch/diff document (lines starting with + are added, lines starting with - are removed, and lines starting with ~ are content that was merely moved/reordered and exists on both sides — do NOT report ~ lines as added or removed). Describe in plain English what changed — first you will scan for items that were simply moved around in the order and just mention that they were changed. list what was added or removed as bullet points, including key details for each item. Be careful of content that merely just moved around, you should mention that it moved but dont report that it was added/removed etc. Be considerate of the style content you are summarising the change of, adjust your report accordingly. Do not quote non-English text verbatim; translate and summarise all content into English. Your entire response must be in English."
|
||||
|
||||
|
||||
def _summary_max_tokens(diff: str, max_cap: int = LLM_DEFAULT_MAX_SUMMARY_TOKENS) -> int:
|
||||
@@ -90,6 +96,37 @@ def _summary_max_tokens(diff: str, max_cap: int = LLM_DEFAULT_MAX_SUMMARY_TOKENS
|
||||
return max(400, min(len(diff) // 4, max_cap))
|
||||
|
||||
|
||||
def apply_local_token_multiplier(base_max_tokens: int, llm_cfg: dict) -> int:
|
||||
"""
|
||||
Scale max_tokens for self-hosted OpenAI-compatible endpoints (vLLM, LM Studio, llama.cpp).
|
||||
|
||||
Reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought into
|
||||
`message.reasoning_content` BEFORE the final answer lands in `message.content`.
|
||||
Without enough headroom the request truncates mid-thought (`finish_reason='length'`)
|
||||
and the answer never lands — callers see an empty string and silently fall through
|
||||
to safe defaults, hiding the problem.
|
||||
|
||||
Local self-hosted models cost no per-token money, so headroom is cheap; cloud
|
||||
providers (OpenAI, Anthropic, Gemini, OpenRouter) keep their original tight caps
|
||||
so existing users see no cost change.
|
||||
|
||||
Activated only when `llm_cfg['provider_kind'] == 'openai_compatible'`.
|
||||
Multiplier defaults to 5x and is user-configurable in Settings → AI → Provider.
|
||||
"""
|
||||
if (llm_cfg or {}).get('provider_kind') != 'openai_compatible':
|
||||
return base_max_tokens
|
||||
try:
|
||||
multiplier = int(llm_cfg.get('local_token_multiplier') or 5)
|
||||
except (TypeError, ValueError):
|
||||
multiplier = 5
|
||||
# Clamp to the same 1-20 range the form enforces. Defense-in-depth against
|
||||
# corrupted datastore values that bypassed form validation (manual JSON edits,
|
||||
# future migrations, plugins): a runaway multiplier could otherwise produce
|
||||
# absurdly large max_tokens caps and exhaust local-endpoint memory.
|
||||
multiplier = max(1, min(multiplier, 20))
|
||||
return base_max_tokens * multiplier
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Intent resolution
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -338,6 +375,7 @@ def run_setup(watch, datastore, snapshot_text: str) -> None:
|
||||
],
|
||||
api_key=cfg.get('api_key'),
|
||||
api_base=cfg.get('api_base'),
|
||||
max_tokens=apply_local_token_multiplier(JSON_RESPONSE_MAX_TOKENS, cfg),
|
||||
extra_body=_thinking_extra_body(cfg['model'], int(datastore.data['settings']['application'].get('llm_thinking_budget', LLM_DEFAULT_THINKING_BUDGET) or 0)),
|
||||
)
|
||||
_check_token_budget(watch, cfg, tokens)
|
||||
@@ -379,6 +417,58 @@ def compute_summary_cache_key(diff_text: str, prompt: str) -> str:
|
||||
return h.hexdigest()[:16]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DiffPrefs:
|
||||
"""
|
||||
User-facing diff display preferences. Part of the LLM summary cache key so
|
||||
that toggling a preference produces a fresh summary.
|
||||
|
||||
Field defaults are the single source of truth — the UI query-arg defaults in
|
||||
diff.py's from_request_args() and the worker pre-cache's bare DiffPrefs()
|
||||
both rely on these.
|
||||
"""
|
||||
all_changes: bool = False
|
||||
ignore_whitespace: bool = False
|
||||
show_removed: bool = True
|
||||
show_added: bool = True
|
||||
|
||||
@classmethod
|
||||
def from_request_args(cls, args) -> 'DiffPrefs':
|
||||
"""Parse from a Flask request.args (or any .get(key, default)-shaped mapping)."""
|
||||
return cls(
|
||||
all_changes = args.get('all_changes', '0') == '1',
|
||||
ignore_whitespace = args.get('ignore_whitespace', '0') == '1',
|
||||
show_removed = args.get('removed', '1') == '1',
|
||||
show_added = args.get('added', '1') == '1',
|
||||
)
|
||||
|
||||
def cache_key_suffix(self) -> str:
|
||||
return (
|
||||
f'\x00prefs:all={int(self.all_changes)},ws={int(self.ignore_whitespace)}'
|
||||
f',rm={int(self.show_removed)},add={int(self.show_added)}'
|
||||
)
|
||||
|
||||
|
||||
def build_summary_cache_prompt(effective_prompt: str, max_summary_tokens: int,
|
||||
prefs: DiffPrefs = None) -> str:
|
||||
"""
|
||||
Compose the full cache-key string passed to save/get_llm_diff_summary.
|
||||
|
||||
Default prefs are DiffPrefs() — must match the UI's query-arg defaults so a
|
||||
worker-side pre-cache is hit by an unmodified UI request. Same helper must
|
||||
be used by both the worker pre-cache write and the UI diff route read,
|
||||
otherwise the prompt hashes diverge and the cache file isn't found.
|
||||
"""
|
||||
if prefs is None:
|
||||
prefs = DiffPrefs()
|
||||
return (
|
||||
effective_prompt
|
||||
+ prefs.cache_key_suffix()
|
||||
+ f'\x00sys:{build_change_summary_system_prompt()}'
|
||||
+ f'\x00max_tokens:{max_summary_tokens}'
|
||||
)
|
||||
|
||||
|
||||
def summarise_change(watch, datastore, diff: str, current_snapshot: str = '') -> str:
|
||||
"""
|
||||
Generate a plain-language summary of the change using the watch's
|
||||
@@ -431,9 +521,12 @@ def summarise_change(watch, datastore, diff: str, current_snapshot: str = '') ->
|
||||
],
|
||||
api_key=cfg.get('api_key'),
|
||||
api_base=cfg.get('api_base'),
|
||||
max_tokens=_summary_max_tokens(
|
||||
diff,
|
||||
max_cap=int(datastore.data['settings']['application'].get('llm_max_summary_tokens', LLM_DEFAULT_MAX_SUMMARY_TOKENS) or LLM_DEFAULT_MAX_SUMMARY_TOKENS),
|
||||
max_tokens=apply_local_token_multiplier(
|
||||
_summary_max_tokens(
|
||||
diff,
|
||||
max_cap=int(datastore.data['settings']['application'].get('llm_max_summary_tokens', LLM_DEFAULT_MAX_SUMMARY_TOKENS) or LLM_DEFAULT_MAX_SUMMARY_TOKENS),
|
||||
),
|
||||
cfg,
|
||||
),
|
||||
extra_body=_extra_body,
|
||||
)
|
||||
@@ -496,6 +589,7 @@ def preview_extract(watch, datastore, content: str) -> dict | None:
|
||||
],
|
||||
api_key=cfg.get('api_key'),
|
||||
api_base=cfg.get('api_base'),
|
||||
max_tokens=apply_local_token_multiplier(JSON_RESPONSE_MAX_TOKENS, cfg),
|
||||
extra_body=_thinking_extra_body(cfg['model'], int(datastore.data['settings']['application'].get('llm_thinking_budget', LLM_DEFAULT_THINKING_BUDGET) or 0)),
|
||||
)
|
||||
accumulate_global_tokens(datastore, tokens, model=cfg['model'])
|
||||
@@ -579,6 +673,7 @@ def evaluate_change(watch, datastore, diff: str, current_snapshot: str = '') ->
|
||||
],
|
||||
api_key=cfg.get('api_key'),
|
||||
api_base=cfg.get('api_base'),
|
||||
max_tokens=apply_local_token_multiplier(JSON_RESPONSE_MAX_TOKENS, cfg),
|
||||
extra_body=_thinking_extra_body(cfg['model'], int(datastore.data['settings']['application'].get('llm_thinking_budget', LLM_DEFAULT_THINKING_BUDGET) or 0)),
|
||||
)
|
||||
raw, tokens = _resp[0], _resp[1]
|
||||
|
||||
@@ -1024,8 +1024,10 @@ class model(EntityPersistenceMixin, watch_base):
|
||||
prompt_hash = self._llm_summary_prompt_hash(prompt)
|
||||
fname = os.path.join(self.data_dir, f'change-summary-{from_version}-to-{to_version}-{prompt_hash}.txt')
|
||||
if not os.path.isfile(fname):
|
||||
logger.debug(f"LLM cached diff summary '{fname}' NOT found")
|
||||
return ''
|
||||
with open(fname, 'r', encoding='utf-8') as f:
|
||||
logger.debug(f"LLM cached diff summary '{fname}' FOUND")
|
||||
return f.read().strip()
|
||||
|
||||
def save_llm_diff_summary(self, summary: str, from_version, to_version, prompt: str = ''):
|
||||
|
||||
@@ -65,6 +65,9 @@ def notification_format_align_with_apprise(n_format : str):
|
||||
:return:
|
||||
"""
|
||||
|
||||
if not n_format:
|
||||
return NotifyFormat.TEXT.value
|
||||
|
||||
if n_format.startswith('html'):
|
||||
# Apprise only knows 'html' not 'htmlcolor' etc, which shouldnt matter here
|
||||
n_format = NotifyFormat.HTML.value
|
||||
@@ -379,6 +382,23 @@ def process_notification(n_object: NotificationContextData, datastore):
|
||||
n_object['llm_summary'] = _llm_change_summary or (n_object.get('_llm_result') or {}).get('summary', '')
|
||||
n_object['llm_intent'] = n_object.get('_llm_intent', '')
|
||||
|
||||
# Escape diff/snapshot variables before Jinja renders them into an HTML notification.
|
||||
# GHSA-q8xq-qg4x-wphg: inscriptis decodes HTML entities when converting text/html
|
||||
# pages to snapshot text, so a page that visibly displays "<a href...>" yields
|
||||
# literal "<a href...>" in the snapshot — which would otherwise render as live
|
||||
# markup in HTML emails / Telegram (parse_mode=html) / Discord embeds, letting a
|
||||
# watched page inject phishing links into the operator's notification channel.
|
||||
# Also covers #3529 — raw '<' chars from text/plain pages breaking HTML email layout.
|
||||
# The operator's own template HTML (e.g. <a href="{{watch_url}}">) is outside the
|
||||
# variable values so it stays untouched. Diff placemarkers contain no HTML chars,
|
||||
# so they survive escape and are still replaced with <span> tags later.
|
||||
if 'html' in requested_output_format:
|
||||
from markupsafe import escape as html_escape
|
||||
_page_content_keys = {'raw_diff', 'current_snapshot', 'prev_snapshot', 'triggered_text'}
|
||||
for key in [k for k in notification_parameters if k.startswith('diff') or k in _page_content_keys]:
|
||||
if notification_parameters.get(key):
|
||||
notification_parameters[key] = str(html_escape(str(notification_parameters[key])))
|
||||
|
||||
with (apprise.LogCapture(level=apprise.logging.DEBUG) as logs):
|
||||
for url in n_object['notification_urls']:
|
||||
|
||||
@@ -396,13 +416,6 @@ def process_notification(n_object: NotificationContextData, datastore):
|
||||
logger.info(f">> Process Notification: AppRise start notifying '{url}'")
|
||||
url = jinja_render(template_str=url, **notification_parameters)
|
||||
|
||||
# If it's a plaintext document, and they want HTML type email/alerts, so it needs to be escaped
|
||||
watch_mime_type = n_object.get('watch_mime_type')
|
||||
if watch_mime_type and 'text/' in watch_mime_type.lower() and not 'html' in watch_mime_type.lower():
|
||||
if 'html' in requested_output_format:
|
||||
from markupsafe import escape
|
||||
n_body = str(escape(n_body))
|
||||
|
||||
if 'html' in requested_output_format:
|
||||
# Since the n_body is always some kind of text from the 'diff' engine, attempt to preserve whitespaces that get sent to the HTML output
|
||||
# But only where its more than 1 consecutive whitespace, otherwise "and this" becomes "and this" etc which is too much.
|
||||
|
||||
@@ -29,7 +29,7 @@ def _check_cascading_vars(datastore, var_name, watch):
|
||||
v = watch.get(var_name)
|
||||
if v and not watch.get('notification_muted'):
|
||||
if var_name == 'notification_format' and v == USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH:
|
||||
return datastore.data['settings']['application'].get('notification_format')
|
||||
return datastore.data['settings']['application'].get('notification_format') or default_notification_format
|
||||
|
||||
return v
|
||||
|
||||
@@ -457,7 +457,7 @@ Thanks - Your omniscient changedetection.io installation.
|
||||
'notification_body': body,
|
||||
'notification_format': _check_cascading_vars(self.datastore, 'notification_format', watch),
|
||||
})
|
||||
n_object['markup_text_links_to_html_links'] = n_object.get('notification_format').startswith('html')
|
||||
n_object['markup_text_links_to_html_links'] = (n_object.get('notification_format') or '').startswith('html')
|
||||
|
||||
if len(watch['notification_urls']):
|
||||
n_object['notification_urls'] = watch['notification_urls']
|
||||
@@ -506,7 +506,7 @@ Thanks - Your omniscient changedetection.io installation.
|
||||
'notification_body': body,
|
||||
'notification_format': _check_cascading_vars(self.datastore, 'notification_format', watch),
|
||||
})
|
||||
n_object['markup_text_links_to_html_links'] = n_object.get('notification_format').startswith('html')
|
||||
n_object['markup_text_links_to_html_links'] = (n_object.get('notification_format') or '').startswith('html')
|
||||
|
||||
if len(watch['notification_urls']):
|
||||
n_object['notification_urls'] = watch['notification_urls']
|
||||
|
||||
@@ -13,6 +13,7 @@ import json
|
||||
import re
|
||||
from loguru import logger
|
||||
from changedetectionio.pluggy_interface import hookimpl
|
||||
from changedetectionio.llm.evaluator import apply_local_token_multiplier
|
||||
|
||||
# Injected at startup by inject_datastore_into_plugins()
|
||||
datastore = None
|
||||
@@ -234,7 +235,10 @@ def get_itemprop_availability_override(content, fetcher_name, fetcher_instance,
|
||||
],
|
||||
api_key=llm_cfg.get('api_key'),
|
||||
api_base=llm_cfg.get('api_base'),
|
||||
max_tokens=80,
|
||||
# 80 fits a {price, currency, availability} JSON answer comfortably for cloud
|
||||
# models. Local reasoning models burn most of that on chain-of-thought before
|
||||
# the JSON lands — the multiplier scales it up only when provider_kind says so.
|
||||
max_tokens=apply_local_token_multiplier(80, llm_cfg),
|
||||
)
|
||||
|
||||
accumulate_global_tokens(
|
||||
|
||||
@@ -210,10 +210,19 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect,
|
||||
llm_summary_prompt = ''
|
||||
if llm_configured:
|
||||
try:
|
||||
from changedetectionio.llm.evaluator import get_effective_summary_prompt
|
||||
from changedetectionio.llm.evaluator import (
|
||||
get_effective_summary_prompt, build_summary_cache_prompt,
|
||||
)
|
||||
_prompt = get_effective_summary_prompt(watch, datastore)
|
||||
llm_summary_prompt = _prompt
|
||||
llm_diff_summary = watch.get_llm_diff_summary(from_version, to_version, prompt=_prompt)
|
||||
# Must match the cache_prompt the worker writes and the UI ajax route reads —
|
||||
# using UI default diff prefs so the initial render finds the worker's pre-cache.
|
||||
_max_summary_tokens = datastore.data['settings']['application'].get('llm_max_summary_tokens', 3000)
|
||||
_cache_prompt = build_summary_cache_prompt(
|
||||
effective_prompt=_prompt,
|
||||
max_summary_tokens=_max_summary_tokens,
|
||||
)
|
||||
llm_diff_summary = watch.get_llm_diff_summary(from_version, to_version, prompt=_cache_prompt)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load llm-diff-summary for {uuid}: {e}")
|
||||
|
||||
|
||||
@@ -743,7 +743,7 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore):
|
||||
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 ({}/{} watches). Cannot add more watches.").format(current_watch_count, page_watch_limit), 'error')
|
||||
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")
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ '{{watch_title}}' }}</code></td>
|
||||
{# TRANSLATORS: dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213 #}
|
||||
<td>{{ _('The page title of the watch, uses <title> if not set, falls back to URL') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -46,7 +47,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ '{{change_datetime}}' }}</code></td>
|
||||
<td>{{ _('Date/time of the change, accepts format=, change_datetime(format=\'%A\')\', default is \'%Y-%m-%d %H:%M:%S %Z\'') }}</td>
|
||||
<td>{{ _("Date/time of the change, accepts format=, %(call)s, default is '%(default)s'", call="change_datetime(format='%A')", default="%Y-%m-%d %H:%M:%S %Z") }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ '{{diff_url}}' }}</code></td>
|
||||
@@ -159,7 +160,7 @@
|
||||
<div class="pure-form-message-inline">
|
||||
<p>
|
||||
<strong>{{ _('Tip:') }}</strong> {{ _('Use <a target="newwindow" href="%(url)s">AppRise Notification URLs</a> for notification to just about any service!',
|
||||
url='https://github.com/caronc/apprise')|safe }} <a target="newwindow" href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes">{{ _('<i>Please read the notification services wiki here for important configuration notes</i>')|safe }}</a>.<br>
|
||||
url='https://github.com/caronc/apprise')|safe }} <a target="newwindow" href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes">{# TRANSLATORS: CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303 #}{{ _('<i>Please read the notification services wiki here for important configuration notes</i>')|safe }}</a>.<br>
|
||||
</p>
|
||||
<div data-target="#advanced-help-notifications" class="toggle-show pure-button button-tag button-xsmall">{{ _('Show advanced help and tips') }}</div>
|
||||
<ul style="display: none" id="advanced-help-notifications">
|
||||
|
||||
@@ -9,6 +9,7 @@ xpath://body/div/span[contains(@class, 'example-class')]",
|
||||
{% if '/text()' in field %}
|
||||
<span class="pure-form-message-inline"><strong>{{ _('Note!: //text() function does not work where the <element> contains <![CDATA[]]>') }}</strong></span><br>
|
||||
{% endif %}
|
||||
{# TRANSLATORS: CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303 #}
|
||||
<span class="pure-form-message-inline">{{ _('One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used.') | safe }}<br>
|
||||
<span data-target="#advanced-help-selectors" class="toggle-show pure-button button-tag button-xsmall">{{ _('Show advanced help and tips') }}</span><br>
|
||||
<ul id="advanced-help-selectors" style="display: none;">
|
||||
|
||||
@@ -108,7 +108,9 @@ def test_check_notification_email_formats_default_HTML(client, live_server, meas
|
||||
html_content = html_part.get_content()
|
||||
assert 'some text<br>' in html_content # We converted \n from the notification body
|
||||
assert 'fallback-body<br>' in html_content # kept the original <br>
|
||||
assert '(added) So let\'s see what happens.<br>' in html_content # the html part
|
||||
# GHSA-q8xq-qg4x-wphg: apostrophes in diff content are escaped (') for HTML notifications.
|
||||
# Renders as ' in the recipient's email client; only the byte-source differs.
|
||||
assert '(added) So let's see what happens.<br>' in html_content # the html part
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
@@ -452,7 +454,8 @@ def test_check_notification_email_formats_default_Text_override_HTML(client, liv
|
||||
html_part = parts[1]
|
||||
assert html_part.get_content_type() == 'text/html'
|
||||
html_content = html_part.get_content()
|
||||
assert '(removed) So let\'s see what happens.' in html_content # the html part
|
||||
# GHSA-q8xq-qg4x-wphg: apostrophes in diff content are escaped (') for HTML notifications.
|
||||
assert '(removed) So let's see what happens.' in html_content # the html part
|
||||
assert '<!DOCTYPE html' not in html_content
|
||||
assert '<!DOCTYPE html' in html_content # Our original template is working correctly
|
||||
|
||||
@@ -792,5 +795,6 @@ def test_check_html_notification_with_apprise_format_is_html(client, live_server
|
||||
html_content = html_part.get_content()
|
||||
assert 'some text<br>' in html_content # We converted \n from the notification body
|
||||
assert 'fallback-body<br>' in html_content # kept the original <br>
|
||||
assert '(added) So let\'s see what happens.<br>' in html_content # the html part
|
||||
# GHSA-q8xq-qg4x-wphg: apostrophes in diff content are escaped (') for HTML notifications.
|
||||
assert '(added) So let's see what happens.<br>' in html_content # the html part
|
||||
delete_all_watches(client)
|
||||
@@ -48,6 +48,32 @@ def test_check_access_control(app, client, live_server, measure_memory_usage, da
|
||||
res = c.get(url_for("ui.ui_diff.diff_history_page", uuid="first"))
|
||||
assert b'Random content' in res.data
|
||||
|
||||
# GHSA-vwgh-2hvh-4xm5: shared_diff_access only covers the read-only
|
||||
# diff page — the extract endpoints (which run an attacker-supplied
|
||||
# regex against history and write a CSV to disk) must still require
|
||||
# auth even when the share flag is enabled.
|
||||
res = c.get(url_for("ui.ui_diff.diff_history_page_extract_GET", uuid="first"))
|
||||
assert res.status_code == 302, "Extract form GET must redirect to login for anonymous users"
|
||||
assert b'/login' in res.data or b'login' in res.headers.get('Location', '').encode()
|
||||
|
||||
res = c.post(
|
||||
url_for("ui.ui_diff.diff_history_page_extract_POST", uuid="first"),
|
||||
data={"extract_regex": ".*", "extract_submit_button": "Extract as CSV"},
|
||||
)
|
||||
assert res.status_code == 302, "Extract POST must redirect to login for anonymous users"
|
||||
assert b'login' in res.headers.get('Location', '').encode()
|
||||
|
||||
# But sub-resources the diff page legitimately loads should still pass the gate.
|
||||
# download_patch is linked from diff.html — anonymous viewers must be able to fetch it.
|
||||
# (We don't care about the body here, just that auth doesn't block it.)
|
||||
res = c.get(url_for("ui.ui_diff.download_patch", uuid="first"))
|
||||
assert res.status_code != 302, "download_patch must be reachable for shared diff viewers"
|
||||
|
||||
# processor_asset (used for screenshots embedded in image_ssim_diff watches) must also be reachable.
|
||||
# For a text watch the processor has no such asset so 404 is fine — what matters is no auth redirect.
|
||||
res = c.get(url_for("ui.ui_diff.processor_asset", uuid="first", asset_name="before"))
|
||||
assert res.status_code != 302, "processor_asset must be reachable for shared diff viewers"
|
||||
|
||||
# access to assets should work (check_authentication)
|
||||
res = c.get(url_for('static_content', group='js', filename='jquery-3.6.0.min.js'))
|
||||
assert res.status_code == 200
|
||||
|
||||
@@ -102,6 +102,8 @@ def test_api_simple(client, live_server, measure_memory_usage, datastore_path):
|
||||
#705 `last_changed` should be zero on the first check
|
||||
assert before_recheck_info['last_changed'] == 0
|
||||
assert before_recheck_info['title'] == 'My test URL'
|
||||
assert isinstance(before_recheck_info['link'], str), "link must be a plain string, not a tuple or list"
|
||||
assert before_recheck_info['link'] == test_url
|
||||
|
||||
# Check the limit by tag doesnt return anything when nothing found
|
||||
res = client.get(
|
||||
@@ -903,6 +905,101 @@ def test_api_restock_processor_config(client, live_server, measure_memory_usage,
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_api_watch_get_returns_resolved_restock_processor_config(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
GET /api/v1/watch/{uuid} must include processor_config_restock_diff and
|
||||
processor_config_restock_diff_source in the response.
|
||||
|
||||
Two cases:
|
||||
- Watch-level config only: source == 'watch', config reflects the watch's own settings.
|
||||
- Tag with overrides_watch=True: source == 'tag:<uuid>', config reflects the tag's settings
|
||||
regardless of what the watch itself has stored.
|
||||
"""
|
||||
api_key = live_server.app.config['DATASTORE'].data['settings']['application'].get('api_access_token')
|
||||
test_url = url_for('test_endpoint', _external=True)
|
||||
|
||||
# --- Case 1: watch-level config, no tag override ---
|
||||
res = client.post(
|
||||
url_for("createwatch"),
|
||||
data=json.dumps({
|
||||
"url": test_url,
|
||||
"processor": "restock_diff",
|
||||
"processor_config_restock_diff": {
|
||||
"in_stock_processing": "all_changes",
|
||||
"follow_price_changes": False,
|
||||
"price_change_min": 1.23,
|
||||
}
|
||||
}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201
|
||||
watch_uuid = res.json.get('uuid')
|
||||
|
||||
res = client.get(url_for("watch", uuid=watch_uuid), headers={'x-api-key': api_key})
|
||||
assert res.status_code == 200
|
||||
data = res.json
|
||||
assert 'processor_config_restock_diff' in data, "GET should include processor_config_restock_diff"
|
||||
assert 'processor_config_restock_diff_source' in data, "GET should include processor_config_restock_diff_source"
|
||||
assert data['processor_config_restock_diff_source'] == 'watch'
|
||||
assert data['processor_config_restock_diff'].get('in_stock_processing') == 'all_changes'
|
||||
assert data['processor_config_restock_diff'].get('follow_price_changes') == False
|
||||
assert data['processor_config_restock_diff'].get('price_change_min') == 1.23
|
||||
|
||||
# --- Case 2: tag with overrides_watch=True overrides watch-level config ---
|
||||
res = client.post(
|
||||
url_for("tag"),
|
||||
data=json.dumps({
|
||||
"title": "Override tag",
|
||||
"overrides_watch": True,
|
||||
"processor_config_restock_diff": {
|
||||
"in_stock_processing": "in_stock_only",
|
||||
"follow_price_changes": True,
|
||||
"price_change_min": 999.0,
|
||||
}
|
||||
}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201
|
||||
tag_uuid = res.json.get('uuid')
|
||||
|
||||
# Assign the tag to the watch
|
||||
res = client.put(
|
||||
url_for("watch", uuid=watch_uuid),
|
||||
data=json.dumps({"tags": [tag_uuid]}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 200
|
||||
|
||||
res = client.get(url_for("watch", uuid=watch_uuid), headers={'x-api-key': api_key})
|
||||
assert res.status_code == 200
|
||||
data = res.json
|
||||
assert data['processor_config_restock_diff_source'] == f'tag:{tag_uuid}', \
|
||||
"Source should show the overriding tag UUID"
|
||||
assert data['processor_config_restock_diff'].get('in_stock_processing') == 'in_stock_only', \
|
||||
"Tag config should override watch-level config"
|
||||
assert data['processor_config_restock_diff'].get('price_change_min') == 999.0, \
|
||||
"Tag price_change_min should override watch-level value"
|
||||
|
||||
# processor_config_restock_diff is readonly — PUT attempts to set the resolved field should be
|
||||
# silently ignored (the field is stripped before the watch is updated, same as other readOnly fields)
|
||||
res = client.put(
|
||||
url_for("watch", uuid=watch_uuid),
|
||||
data=json.dumps({"processor_config_restock_diff": {"in_stock_processing": "off"}}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
# PUT with processor_config_restock_diff is still valid (sets watch-level config),
|
||||
# but the GET response continues to reflect the tag override
|
||||
assert res.status_code == 200
|
||||
res = client.get(url_for("watch", uuid=watch_uuid), headers={'x-api-key': api_key})
|
||||
data = res.json
|
||||
assert data['processor_config_restock_diff_source'] == f'tag:{tag_uuid}', \
|
||||
"Tag override should still be active after PUT"
|
||||
assert data['processor_config_restock_diff'].get('in_stock_processing') == 'in_stock_only', \
|
||||
"Tag config should still win after PUT attempted to change watch-level config"
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_api_conflict_UI_password(client, live_server, measure_memory_usage, datastore_path):
|
||||
|
||||
|
||||
|
||||
@@ -336,6 +336,58 @@ def test_hardcoded_fallback_when_nothing_set(
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_llm_summary_ajax_sets_last_viewed(
|
||||
client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
Calling /diff/<uuid>/llm-summary via AJAX should mark the watch as viewed
|
||||
(set last_viewed) for both fresh and cached responses.
|
||||
"""
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
_configure_llm(client)
|
||||
ds = client.application.config.get('DATASTORE')
|
||||
|
||||
test_url = url_for('test_endpoint', content_type='text/html', content='v1', _external=True)
|
||||
uuid = ds.add_watch(url=test_url)
|
||||
watch = ds.data['watching'][uuid]
|
||||
|
||||
watch.save_history_blob('old content\n', '4000000000', 'snap-old')
|
||||
watch.save_history_blob('new content\n', '4000000001', 'snap-new')
|
||||
|
||||
assert watch['last_viewed'] == 0, "last_viewed should start at 0"
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock()]
|
||||
mock_response.choices[0].message.content = 'Content changed from old to new.'
|
||||
mock_response.usage = MagicMock(total_tokens=50, prompt_tokens=40, completion_tokens=10)
|
||||
|
||||
with patch('litellm.completion', return_value=mock_response):
|
||||
res = client.get(
|
||||
url_for('ui.ui_diff.diff_llm_summary', uuid=uuid,
|
||||
from_version='4000000000', to_version='4000000001'),
|
||||
)
|
||||
|
||||
assert res.status_code == 200
|
||||
data = res.get_json()
|
||||
assert data['summary'] == 'Content changed from old to new.'
|
||||
assert watch['last_viewed'] > 0, "last_viewed should be set after fresh LLM summary"
|
||||
|
||||
# Reset and verify the cached path also sets last_viewed
|
||||
watch['last_viewed'] = 0
|
||||
with patch('litellm.completion', return_value=mock_response):
|
||||
res2 = client.get(
|
||||
url_for('ui.ui_diff.diff_llm_summary', uuid=uuid,
|
||||
from_version='4000000000', to_version='4000000001'),
|
||||
)
|
||||
|
||||
assert res2.status_code == 200
|
||||
data2 = res2.get_json()
|
||||
assert data2.get('cached') is True
|
||||
assert watch['last_viewed'] > 0, "last_viewed should be set even when returning cached summary"
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_global_default_saved_and_loaded_via_settings_form(
|
||||
client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
|
||||
@@ -541,6 +541,39 @@ def test_single_send_test_notification_on_watch(client, live_server, measure_mem
|
||||
assert 'Current snapshot: Example text: example test' in x
|
||||
os.unlink(os.path.join(datastore_path, "notification.txt"))
|
||||
|
||||
# Regression test for #4119 - sending a test notification with 'System default' format caused a crash
|
||||
def test_send_test_notification_with_system_default_format(client, live_server, measure_memory_usage, datastore_path):
|
||||
|
||||
set_original_response(datastore_path=datastore_path)
|
||||
if os.path.isfile(os.path.join(datastore_path, "notification.txt")):
|
||||
os.unlink(os.path.join(datastore_path, "notification.txt"))
|
||||
|
||||
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://') + "?status_code=204"
|
||||
|
||||
test_url = url_for('test_endpoint', _external=True)
|
||||
uuid = client.application.config.get('DATASTORE').add_watch(url=test_url)
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
|
||||
# New watches default to USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH.
|
||||
# The JS sends this value verbatim from the select; it must not crash.
|
||||
res = client.post(
|
||||
url_for("ui.ui_notification.ajax_callback_send_notification_test") + f"/{uuid}",
|
||||
data={
|
||||
"notification_urls": test_notification_url,
|
||||
"notification_body": default_notification_body,
|
||||
"notification_title": default_notification_title,
|
||||
"notification_format": USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH,
|
||||
},
|
||||
follow_redirects=True
|
||||
)
|
||||
|
||||
assert res.status_code != 400
|
||||
assert res.status_code != 500
|
||||
|
||||
client.get(url_for("ui.form_delete", uuid="all"), follow_redirects=True)
|
||||
|
||||
|
||||
def _test_color_notifications(client, notification_body_token, datastore_path):
|
||||
|
||||
set_original_response(datastore_path=datastore_path)
|
||||
@@ -602,3 +635,234 @@ def test_html_color_notifications(client, live_server, measure_memory_usage, dat
|
||||
_test_color_notifications(client, '{{diff}}',datastore_path=datastore_path)
|
||||
_test_color_notifications(client, '{{diff_full}}',datastore_path=datastore_path)
|
||||
|
||||
|
||||
def _test_custom_html_in_notification_body_not_escaped(client, datastore_path, content_type=None):
|
||||
"""
|
||||
#4121 - The operator's own HTML in the notification body template (e.g.
|
||||
<a href="{{watch_url}}">) must survive unescaped regardless of the watched page's
|
||||
Content-Type. The escape pass in handler.py only touches the variable *values*
|
||||
(diff/snapshot content from the page — see GHSA-q8xq-qg4x-wphg) — it leaves the
|
||||
surrounding template HTML alone.
|
||||
"""
|
||||
set_original_response(datastore_path=datastore_path)
|
||||
|
||||
if os.path.isfile(os.path.join(datastore_path, "notification.txt")):
|
||||
os.unlink(os.path.join(datastore_path, "notification.txt"))
|
||||
|
||||
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')
|
||||
|
||||
kwargs = {'content_type': content_type} if content_type else {}
|
||||
test_url = url_for('test_endpoint', _external=True, **kwargs)
|
||||
|
||||
res = client.post(
|
||||
url_for("settings.settings_page"),
|
||||
data={
|
||||
"application-fetch_backend": "html_requests",
|
||||
"application-minutes_between_check": 180,
|
||||
"application-notification_body": '<a href="{{watch_url}}">Watch Link</a> had changes\n\n{{diff}}',
|
||||
"application-notification_format": "htmlcolor",
|
||||
"application-notification_urls": test_notification_url,
|
||||
"application-notification_title": "Change detected",
|
||||
},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b'Settings updated' in res.data
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_views.form_quick_watch_add"),
|
||||
data={"url": test_url, "tags": ''},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Watch added" in res.data
|
||||
|
||||
wait_for_all_checks(client)
|
||||
set_modified_response(datastore_path=datastore_path)
|
||||
|
||||
res = client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
assert b'Queued 1 watch for rechecking.' in res.data
|
||||
|
||||
wait_for_all_checks(client)
|
||||
wait_for_notification_endpoint_output(datastore_path=datastore_path)
|
||||
|
||||
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
|
||||
x = f.read()
|
||||
|
||||
assert '<a href=' not in x, f"Custom HTML <a> tag was incorrectly escaped (content_type={content_type})"
|
||||
assert '<a href=' in x, f"Custom HTML <a> tag not found unescaped (content_type={content_type})"
|
||||
assert '<span' in x, f"Expected color <span> tags not found (content_type={content_type})"
|
||||
|
||||
client.get(url_for("ui.form_delete", uuid="all"), follow_redirects=True)
|
||||
|
||||
|
||||
def test_plaintext_watch_custom_html_in_notification_body_not_escaped(client, live_server, measure_memory_usage, datastore_path):
|
||||
# Diff/snapshot values are escaped for HTML notifications (covered by
|
||||
# test_html_watch_diff_content_escaped_in_html_notification). What this test
|
||||
# locks in is that the *surrounding* template HTML is left alone in every case.
|
||||
_test_custom_html_in_notification_body_not_escaped(client, datastore_path, content_type="text/plain")
|
||||
_test_custom_html_in_notification_body_not_escaped(client, datastore_path, content_type="text/html")
|
||||
_test_custom_html_in_notification_body_not_escaped(client, datastore_path, content_type=None)
|
||||
|
||||
|
||||
def test_html_watch_diff_content_escaped_in_html_notification(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
GHSA-q8xq-qg4x-wphg — diff/snapshot content from the watched page must be
|
||||
HTML-escaped before it is rendered into an HTML-format notification, regardless
|
||||
of the watched page's Content-Type.
|
||||
|
||||
Inscriptis (used to convert text/html pages to snapshot text) decodes HTML
|
||||
entities — so a page that visibly displays "<a href=...>" produces snapshot
|
||||
text containing literal "<a href=...>". The previous gate at handler.py:391
|
||||
only escaped when watch_mime_type matched 'text/' and not 'html', which let
|
||||
that decoded markup through to HTML emails / Telegram (parse_mode=html) /
|
||||
Discord embeds, where it renders as a real clickable link — i.e. an attacker
|
||||
who controls a watched page can inject phishing links into the operator's
|
||||
trusted notification channel.
|
||||
"""
|
||||
from .util import write_test_file_and_sync
|
||||
|
||||
if os.path.isfile(os.path.join(datastore_path, "notification.txt")):
|
||||
os.unlink(os.path.join(datastore_path, "notification.txt"))
|
||||
|
||||
# Baseline: an innocuous text/html page.
|
||||
baseline_html = "<html><body><p>nothing to see here</p></body></html>"
|
||||
write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), baseline_html)
|
||||
|
||||
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')
|
||||
# Pass content_type=text/html so the watch records 'text/html' as its content-type
|
||||
# — this is the branch the previous gate skipped escaping for.
|
||||
test_url = url_for('test_endpoint', _external=True, content_type='text/html')
|
||||
|
||||
# HTML-format notification body that embeds the snapshot directly. Operators do this
|
||||
# when they want the full changed content in the alert (e.g. an email digest).
|
||||
res = client.post(
|
||||
url_for("settings.settings_page"),
|
||||
data={
|
||||
"application-fetch_backend": "html_requests",
|
||||
"application-minutes_between_check": 180,
|
||||
"application-notification_body": 'Watch had changes:\n{{current_snapshot}}',
|
||||
"application-notification_format": "html",
|
||||
"application-notification_urls": test_notification_url,
|
||||
"application-notification_title": "Change detected",
|
||||
},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b'Settings updated' in res.data
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_views.form_quick_watch_add"),
|
||||
data={"url": test_url, "tags": ''},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Watch added" in res.data
|
||||
|
||||
wait_for_all_checks(client)
|
||||
|
||||
# Now flip the page to something whose *visible* text contains entity-encoded
|
||||
# angle brackets — exactly the pattern a forum / pastebin / code-sample site uses
|
||||
# to display literal HTML on the page. Inscriptis will decode </> back to
|
||||
# literal < / > in the stored snapshot.
|
||||
attacker_html = (
|
||||
'<html><body><pre>'
|
||||
'<a href="https://attacker.example/payment">ACTION REQUIRED</a>'
|
||||
'<img src="https://attacker.example/track" width="1" height="1">'
|
||||
'</pre></body></html>'
|
||||
)
|
||||
write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), attacker_html)
|
||||
|
||||
res = client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
assert b'Queued 1 watch for rechecking.' in res.data
|
||||
|
||||
wait_for_all_checks(client)
|
||||
wait_for_notification_endpoint_output(datastore_path=datastore_path)
|
||||
|
||||
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
|
||||
body = f.read()
|
||||
|
||||
# Sanity: the snapshot really did contain the decoded markup (otherwise the test
|
||||
# would pass for the wrong reason). The escaped form must appear somewhere.
|
||||
assert '<a href=' in body or '&lt;a href=' in body, \
|
||||
f"Expected escaped attacker markup in notification body, got: {body!r}"
|
||||
|
||||
# The bug: a live <a href="https://attacker..."> ends up in the HTML notification.
|
||||
assert '<a href="https://attacker.example/payment"' not in body, \
|
||||
f"Diff content from text/html page was NOT escaped — phishing link reached HTML notification: {body!r}"
|
||||
assert '<img src="https://attacker.example/track"' not in body, \
|
||||
f"Diff content from text/html page was NOT escaped — tracking pixel reached HTML notification: {body!r}"
|
||||
|
||||
client.get(url_for("ui.form_delete", uuid="all"), follow_redirects=True)
|
||||
|
||||
|
||||
def test_source_url_diff_content_escaped_in_html_notification(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
GHSA-q8xq-qg4x-wphg — companion to the inscriptis test. `source:`-prefixed
|
||||
URLs short-circuit the HTML→text step (processor.py:509-511) and store the
|
||||
raw HTML body verbatim as the snapshot. That gives an attacker who controls
|
||||
a watched page a *direct* injection path — no entity-encoding tricks needed,
|
||||
any live `<a>` / `<img>` / `<script>` on the page lands straight into
|
||||
current_snapshot / raw_diff. The escape pass must catch this too.
|
||||
"""
|
||||
from .util import write_test_file_and_sync
|
||||
|
||||
if os.path.isfile(os.path.join(datastore_path, "notification.txt")):
|
||||
os.unlink(os.path.join(datastore_path, "notification.txt"))
|
||||
|
||||
# Baseline: innocuous raw HTML.
|
||||
baseline_html = "<html><body><p>nothing to see here</p></body></html>"
|
||||
write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), baseline_html)
|
||||
|
||||
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')
|
||||
# `source:` prefix → raw HTML body is stored as-is in the snapshot (no inscriptis).
|
||||
test_url = 'source:' + url_for('test_endpoint', _external=True, content_type='text/html')
|
||||
|
||||
res = client.post(
|
||||
url_for("settings.settings_page"),
|
||||
data={
|
||||
"application-fetch_backend": "html_requests",
|
||||
"application-minutes_between_check": 180,
|
||||
"application-notification_body": 'Watch had changes:\n{{current_snapshot}}',
|
||||
"application-notification_format": "html",
|
||||
"application-notification_urls": test_notification_url,
|
||||
"application-notification_title": "Change detected",
|
||||
},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b'Settings updated' in res.data
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_views.form_quick_watch_add"),
|
||||
data={"url": test_url, "tags": ''},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Watch added" in res.data
|
||||
|
||||
wait_for_all_checks(client)
|
||||
|
||||
# Modified page contains LIVE HTML directly — no entity encoding. With source:
|
||||
# this lands in the snapshot verbatim.
|
||||
attacker_html = (
|
||||
'<html><body>'
|
||||
'<a href="https://attacker.example/payment">ACTION REQUIRED</a>'
|
||||
'<img src="https://attacker.example/track" width="1" height="1">'
|
||||
'</body></html>'
|
||||
)
|
||||
write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), attacker_html)
|
||||
|
||||
res = client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
assert b'Queued 1 watch for rechecking.' in res.data
|
||||
|
||||
wait_for_all_checks(client)
|
||||
wait_for_notification_endpoint_output(datastore_path=datastore_path)
|
||||
|
||||
with open(os.path.join(datastore_path, "notification.txt"), 'r') as f:
|
||||
body = f.read()
|
||||
|
||||
# Sanity: snapshot really did carry the markup through. Escaped form must show up.
|
||||
assert '<a href=' in body or '&lt;a href=' in body, \
|
||||
f"Expected escaped attacker markup in notification body, got: {body!r}"
|
||||
|
||||
assert '<a href="https://attacker.example/payment"' not in body, \
|
||||
f"source: URL raw HTML was NOT escaped — phishing link reached HTML notification: {body!r}"
|
||||
assert '<img src="https://attacker.example/track"' not in body, \
|
||||
f"source: URL raw HTML was NOT escaped — tracking pixel reached HTML notification: {body!r}"
|
||||
|
||||
client.get(url_for("ui.form_delete", uuid="all"), follow_redirects=True)
|
||||
|
||||
@@ -223,6 +223,58 @@ Babel auto-discovers the new language on subsequent runs.
|
||||
|
||||
---
|
||||
|
||||
## Dennis linter
|
||||
|
||||
We use [mozilla/dennis](https://github.com/mozilla/dennis) to enforce technical correctness in `.po` and `.pot` files.
|
||||
See the [Table of Warnings and Errors](https://dennis.readthedocs.io/en/latest/linting.html#table-of-warnings-and-errors)
|
||||
for the full list of rules.
|
||||
|
||||
### Running the linter locally
|
||||
|
||||
To match the CI checks, run the following commands:
|
||||
|
||||
```bash
|
||||
# Check for errors only (always enforced)
|
||||
dennis-cmd lint --errorsonly changedetectionio/translations/
|
||||
|
||||
# Check for warnings (excluding W302 unchanged translations)
|
||||
dennis-cmd lint --excluderules=W302 changedetectionio/translations/
|
||||
```
|
||||
|
||||
### Common problems and resolutions
|
||||
|
||||
#### HTML tag mismatch (`W303`)
|
||||
|
||||
The `W303` rule ensures that HTML tags in the `msgstr` match the `msgid`. This is crucial for catching broken markup (e.g., missing closing tags).
|
||||
|
||||
##### Handling intentional deviations and false positives
|
||||
|
||||
Some W303 warnings are intentional or result from upstream false positives.
|
||||
Use the `dennis-ignore: W303` comment in the source files (templates or Python code) within a `TRANSLATORS` comment to suppress these warnings.
|
||||
This ensures the ignore instruction is extracted into the `.po` files.
|
||||
|
||||
- **CJK italic policy**: When replacing `<i>` with locale-conventional quotation marks, tags will no longer match.
|
||||
- **Upstream false positive**: Dennis misinterprets certain HTML tags (e.g., `<title>`) within `msgstr`. See https://github.com/mozilla/dennis/issues/213.
|
||||
|
||||
**Examples in Jinja2 templates:**
|
||||
|
||||
```jinja
|
||||
{# TRANSLATORS: CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303 #}
|
||||
<p>{{ _('These settings are <strong><i>added</i></strong> to any existing watch configurations.')|safe }}</p>
|
||||
|
||||
{# TRANSLATORS: dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213 #}
|
||||
<td>{{ _('The page title of the watch, uses <title> if not set, falls back to URL') }}</td>
|
||||
```
|
||||
|
||||
**Example in Python source:**
|
||||
|
||||
```python
|
||||
# dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
use_page_title_in_list = BooleanField(_l('Use page <title> in watch overview list'))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI linter
|
||||
|
||||
A GitHub Actions job (`lint-template-i18n`) checks for adjacent `{{ _(...) }}` calls on the same line
|
||||
|
||||
Binary file not shown.
@@ -160,8 +160,8 @@ msgstr "Importuje se prvních 5000 URL adres, další lze načíst opakovaným i
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "{} importováno ze seznamu za {:.2f}s, {} přeskočeno."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} importováno ze seznamu za {duration}s, {skipped_count} přeskočeno."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -173,8 +173,8 @@ msgstr "Strukturovaný JSON text je neplatný, byl poškozen?"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "{} importováno z Distill.io za {:.2f}s, {} přeskočeno."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} importováno z Distill.io za {duration}s, {skipped_count} přeskočeno."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -192,22 +192,18 @@ msgstr "Chyba při zpracování řádku {}, zkontrolujte že všechny typy dat v
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "{} importováno z Wachete .xlsx za {:.2f}s"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "{count} importováno z Wachete .xlsx za {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "{} importováno z vlastního .xlsx za {:.2f}s"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "{count} importováno z vlastního .xlsx za {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "Seznam adres URL"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX a Wachete"
|
||||
@@ -241,6 +237,7 @@ msgstr "URL, které neprojdou validací, zůstanou v textové oblasti."
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr "Nakopírujte a vložte exportovaný Distill.io soubor, měl by být ve formátu JSON."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -313,8 +310,8 @@ msgstr "Ochrana heslem odebrána."
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "Upozornění: Počet pracovních procesů ({}) se blíží nebo překračuje počet CPU jader ({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr "Upozornění: Počet pracovních procesů ({worker_count}) se blíží nebo překračuje počet CPU jader ({cpu_count})"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -368,8 +365,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -393,14 +390,6 @@ msgstr "Globální filtry"
|
||||
msgid "UI Options"
|
||||
msgstr "Možnosti uživatelského rozhraní"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "Zálohy"
|
||||
@@ -903,10 +892,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1071,7 +1073,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1102,6 +1104,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1115,7 +1121,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1213,6 +1219,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr ""
|
||||
@@ -1429,8 +1436,8 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "Do fronty přidáno {} sledování k opětovné kontrole ({} již ve frontě nebo běží)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "Do fronty přidáno {count} sledování k opětovné kontrole ({skipped_count} již ve frontě nebo běží)."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1919,6 +1926,7 @@ msgid ""
|
||||
"lines against all history for this watch."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2176,6 +2184,7 @@ msgstr "Vymazat historie"
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>Opravdu chcete vyčistit historii u vybraných položek?</p><p>Tuto akci nelze vzít zpět.</p>"
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
@@ -2189,8 +2198,8 @@ msgid "Delete Watches?"
|
||||
msgstr "Smazat sledování?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>Opravdu chcete smazat vybraná sledování?</p><p>Tuto akci nelze vzít zpět.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p><strong>Opravdu chcete smazat vybraná sledování?</strong></p><p>Tuto akci nelze vzít zpět.</p>"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued size"
|
||||
@@ -2668,18 +2677,18 @@ msgstr "RegEx '%s' není platný regulární výraz."
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "'%s' není platný výraz XPath. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' není platný výraz XPath. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "'%s' není platný výraz JSONPath. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' není platný výraz JSONPath. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "'%s' není platný výraz jq. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' není platný výraz jq. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2689,10 +2698,6 @@ msgstr "Prázdná hodnota není povolena."
|
||||
msgid "Invalid value."
|
||||
msgstr "Neplatná hodnota."
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "Skupina / Značka"
|
||||
@@ -2928,6 +2933,7 @@ msgstr "Spojit všechny následující položky"
|
||||
msgid "Match any of the following"
|
||||
msgstr "Přiřaďte kteroukoli z následujících možností"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "V seznamu použijte stránku <title>"
|
||||
@@ -3027,6 +3033,7 @@ msgstr "Aktualizace UI v reálném čase"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "Povolit favikony"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "Použijte stránku <title> v přehledu sledování"
|
||||
@@ -3128,11 +3135,11 @@ msgid "API Key"
|
||||
msgstr "API klíč"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3381,7 +3388,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3416,6 +3423,7 @@ msgstr ""
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "UUID monitoru."
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr ""
|
||||
@@ -3430,7 +3438,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3552,6 +3560,7 @@ msgstr "Více zde"
|
||||
msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a> for notification to just about any service!"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr ""
|
||||
@@ -3913,6 +3922,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -162,8 +162,8 @@ msgstr "Es werden 5.000 der ersten URLs aus Ihrer Liste importiert, der Rest kan
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "{} aus Liste importiert in {:.2f}s, {} übersprungen."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} aus Liste importiert in {duration}s, {skipped_count} übersprungen."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -175,8 +175,8 @@ msgstr "JSON-Struktur sieht ungültig aus, ist sie beschädigt?"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "{} aus Distill.io importiert in {:.2f}s, {} übersprungen."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} aus Distill.io importiert in {duration}s, {skipped_count} übersprungen."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -194,22 +194,18 @@ msgstr "Fehler bei der Verarbeitung von Zeile {}, prüfen Sie, ob alle Zelldaten
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "{} aus Wachete .xlsx importiert in {:.2f}s"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "{count} aus Wachete .xlsx importiert in {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "{} aus benutzerdefinierter .xlsx importiert in {:.2f}s"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "{count} aus benutzerdefinierter .xlsx importiert in {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "URL-Liste"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX & Wachete"
|
||||
@@ -247,6 +243,7 @@ msgstr ""
|
||||
"Kopieren Sie Ihre Distill.io-Watch-„Export“-Datei und fügen Sie sie ein. Dabei sollte es sich um eine JSON-Datei "
|
||||
"handeln."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -319,8 +316,8 @@ msgstr "Passwortschutz entfernt."
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "Warnung: Anzahl der Worker ({}) nähert sich oder überschreitet die verfügbaren CPU-Kerne ({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr "Warnung: Anzahl der Worker ({worker_count}) nähert sich oder überschreitet die verfügbaren CPU-Kerne ({cpu_count})"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -374,8 +371,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -399,14 +396,6 @@ msgstr "Globale Filter"
|
||||
msgid "UI Options"
|
||||
msgstr "UI-Optionen"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "Backups"
|
||||
@@ -919,10 +908,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1087,7 +1089,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1118,6 +1120,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1131,7 +1137,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1229,6 +1235,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr "Diese Einstellungen werden zu allen vorhandenen Überwachungskonfigurationen <strong><i>hinzugefügt</i></strong>."
|
||||
@@ -1339,7 +1346,7 @@ msgid ""
|
||||
"watches will be removed from it.</p>"
|
||||
msgstr ""
|
||||
"<p>Möchten Sie wirklich alle Beobachtungen aus der Gruppe <strong>%(title)s</strong> entfernen?</p><p>Das Tag bleibt "
|
||||
"erhalten, aber die Beobachtungen werden daraus entfernt."
|
||||
"erhalten, aber die Beobachtungen werden daraus entfernt.</p>"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
msgid "Unlink"
|
||||
@@ -1452,8 +1459,8 @@ msgstr "1 Überwachung zur erneuten Überprüfung in Warteschlange gestellt."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "{} Überwachungen zur erneuten Überprüfung eingereiht ({} bereits in Warteschlange oder laufend)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "{count} Überwachungen zur erneuten Überprüfung eingereiht ({skipped_count} bereits in Warteschlange oder laufend)."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1960,6 +1967,7 @@ msgstr ""
|
||||
"Gut geeignet für Websites, auf denen nur Inhalte verschoben werden und Sie wissen möchten, wann NEUE Inhalte "
|
||||
"hinzugefügt werden. Vergleicht neue Zeilen mit dem gesamten Verlauf dieser Überwachung."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2225,6 +2233,7 @@ msgstr ""
|
||||
"<p>Möchten Sie den Verlauf für die ausgewählten Elemente wirklich löschen?</p><p>Diese Aktion kann nicht rückgängig "
|
||||
"gemacht werden.</p>"
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
@@ -2238,10 +2247,10 @@ msgid "Delete Watches?"
|
||||
msgstr "Überwachungen löschen?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
"<p>Möchten Sie die ausgewählten Überwachungen wirklich löschen?</strong></p><p>Diese Aktion kann nicht rückgängig "
|
||||
"gemacht werden.</p>"
|
||||
"<p><strong>Möchten Sie die ausgewählten Überwachungen wirklich löschen?</strong></p><p>Diese Aktion kann nicht "
|
||||
"rückgängig gemacht werden.</p>"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued size"
|
||||
@@ -2719,18 +2728,18 @@ msgstr "RegEx „%s“ ist kein gültiger regulärer Ausdruck."
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "„%s“ ist kein gültiger XPath-Ausdruck. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "„%(expression)s“ ist kein gültiger XPath-Ausdruck. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "„%s“ ist kein gültiger JSONPath-Ausdruck. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "„%(expression)s“ ist kein gültiger JSONPath-Ausdruck. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "„%s“ ist kein gültiger JQ-Ausdruck. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "„%(expression)s“ ist kein gültiger JQ-Ausdruck. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2740,10 +2749,6 @@ msgstr "Leerer Wert nicht zulässig."
|
||||
msgid "Invalid value."
|
||||
msgstr "Ungültiger Wert."
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "Gruppe / Label"
|
||||
@@ -2980,9 +2985,10 @@ msgstr "Passen Sie alle folgenden Punkte an"
|
||||
msgid "Match any of the following"
|
||||
msgstr "Entspricht einer der folgenden Bedingungen"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "Verwenden Sie Seite <Titel> in der Liste"
|
||||
msgstr "Verwenden Sie Seite <title> in der Liste"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Number of history items per watch to keep"
|
||||
@@ -3079,9 +3085,10 @@ msgstr "Echtzeit-UI-Updates aktiviert"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "Favicons Aktiviert"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "Verwenden Sie die Seite <Titel> in der Übersichtsliste der Beobachtungen"
|
||||
msgstr "Verwenden Sie die Seite <title> in der Übersichtsliste der Beobachtungen"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API access token security check enabled"
|
||||
@@ -3133,7 +3140,7 @@ msgstr "RSS-Inhaltsformat"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "RSS <description> body built from"
|
||||
msgstr "RSS-<Beschreibung>-Körper erstellt aus"
|
||||
msgstr "RSS-<description>-Körper erstellt aus"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "RSS \"System default\" template override"
|
||||
@@ -3180,11 +3187,11 @@ msgid "API Key"
|
||||
msgstr "API-Schlüssel"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3435,7 +3442,7 @@ 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 ({}/{} watches). Cannot add more watches."
|
||||
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3470,6 +3477,7 @@ msgstr ""
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "Die UUID der Überwachung."
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr ""
|
||||
@@ -3484,7 +3492,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3606,6 +3614,7 @@ msgstr "Mehr hier"
|
||||
msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a> for notification to just about any service!"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr ""
|
||||
@@ -3969,6 +3978,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
@@ -160,7 +160,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
@@ -173,7 +173,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
@@ -192,22 +192,18 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ""
|
||||
@@ -241,6 +237,7 @@ msgstr ""
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -311,7 +308,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
@@ -366,8 +363,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -391,14 +388,6 @@ msgstr ""
|
||||
msgid "UI Options"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr ""
|
||||
@@ -901,10 +890,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1069,7 +1071,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1100,6 +1102,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1113,7 +1119,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1211,6 +1217,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr ""
|
||||
@@ -1425,7 +1432,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
@@ -1915,6 +1922,7 @@ msgid ""
|
||||
"lines against all history for this watch."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2172,6 +2180,7 @@ msgstr ""
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
@@ -2185,7 +2194,7 @@ msgid "Delete Watches?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
@@ -2662,17 +2671,17 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -2683,10 +2692,6 @@ msgstr ""
|
||||
msgid "Invalid value."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr ""
|
||||
@@ -2922,6 +2927,7 @@ msgstr ""
|
||||
msgid "Match any of the following"
|
||||
msgstr ""
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr ""
|
||||
@@ -3021,6 +3027,7 @@ msgstr ""
|
||||
msgid "Favicons Enabled"
|
||||
msgstr ""
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr ""
|
||||
@@ -3122,11 +3129,11 @@ msgid "API Key"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3375,7 +3382,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3410,6 +3417,7 @@ msgstr ""
|
||||
msgid "The UUID of the watch."
|
||||
msgstr ""
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr ""
|
||||
@@ -3424,7 +3432,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3546,6 +3554,7 @@ msgstr ""
|
||||
msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a> for notification to just about any service!"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr ""
|
||||
@@ -3907,6 +3916,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
@@ -160,7 +160,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
@@ -173,7 +173,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
@@ -192,22 +192,18 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ""
|
||||
@@ -241,6 +237,7 @@ msgstr ""
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -311,7 +308,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
@@ -366,8 +363,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -391,14 +388,6 @@ msgstr ""
|
||||
msgid "UI Options"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr ""
|
||||
@@ -901,10 +890,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1069,7 +1071,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1100,6 +1102,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1113,7 +1119,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1211,6 +1217,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr ""
|
||||
@@ -1425,7 +1432,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
@@ -1915,6 +1922,7 @@ msgid ""
|
||||
"lines against all history for this watch."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2172,6 +2180,7 @@ msgstr ""
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
@@ -2185,7 +2194,7 @@ msgid "Delete Watches?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
@@ -2662,17 +2671,17 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -2683,10 +2692,6 @@ msgstr ""
|
||||
msgid "Invalid value."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr ""
|
||||
@@ -2922,6 +2927,7 @@ msgstr ""
|
||||
msgid "Match any of the following"
|
||||
msgstr ""
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr ""
|
||||
@@ -3021,6 +3027,7 @@ msgstr ""
|
||||
msgid "Favicons Enabled"
|
||||
msgstr ""
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr ""
|
||||
@@ -3122,11 +3129,11 @@ msgid "API Key"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3375,7 +3382,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3410,6 +3417,7 @@ msgstr ""
|
||||
msgid "The UUID of the watch."
|
||||
msgstr ""
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr ""
|
||||
@@ -3424,7 +3432,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3546,6 +3554,7 @@ msgstr ""
|
||||
msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a> for notification to just about any service!"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr ""
|
||||
@@ -3907,6 +3916,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -160,8 +160,8 @@ msgstr "Importando 5.000 de las primeras URL de tu lista, el resto se puede impo
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "{} importado de la lista en {:.2f}s, {} omitido."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} importado de la lista en {duration}s, {skipped_count} omitido."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -173,8 +173,8 @@ msgstr "La estructura JSON parece no válida, ¿estaba rota?"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "{} importado de Distill.io en {:.2f}s, {} omitido."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} importado de Distill.io en {duration}s, {skipped_count} omitido."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -194,22 +194,18 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "{} importado de Wachete .xlsx en {:.2f}s"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "{count} importado de Wachete .xlsx en {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "{} importado desde .xlsx personalizado en {:.2f}s"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "{count} importado desde .xlsx personalizado en {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "Lista de URL"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX y Wachete"
|
||||
@@ -245,6 +241,7 @@ msgstr "Las URL que no pasen la validación permanecerán en el área de texto."
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr "Copie y pegue el archivo de 'exportación' del monitor Distill.io, este debería ser un archivo JSON."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -317,8 +314,10 @@ msgstr "Se eliminó la protección con contraseña."
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "Advertencia: recuento de trabajadores ({} ) está cerca o excede los núcleos de CPU disponibles ({} )"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr ""
|
||||
"Advertencia: recuento de trabajadores ({worker_count} ) está cerca o excede los núcleos de CPU disponibles "
|
||||
"({cpu_count} )"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -372,8 +371,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -397,14 +396,6 @@ msgstr "Filtros globales"
|
||||
msgid "UI Options"
|
||||
msgstr "Opciones de interfaz de usuario"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "Copias de seguridad"
|
||||
@@ -937,10 +928,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1105,7 +1109,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1136,6 +1140,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1149,7 +1157,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1247,6 +1255,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr "Estas configuraciones son <strong><i>agregadas</i></strong> a cualquier configuración de monitor existente."
|
||||
@@ -1470,8 +1479,8 @@ msgstr "1 monitor en cola para volver a verificar."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "{} monitores en cola para volver a comprobar ({} ya en cola o en ejecución)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "{count} monitores en cola para volver a comprobar ({skipped_count} ya en cola o en ejecución)."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1972,6 +1981,7 @@ msgstr ""
|
||||
"Bueno para sitios web que simplemente mueven el contenido y desea saber cuándo se agrega contenido NUEVO, compara "
|
||||
"nuevas líneas con todo el historial de este monitor."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2239,6 +2249,7 @@ msgstr ""
|
||||
"<p>¿Está seguro de que desea borrar el historial de los elementos seleccionados?</p><p>Esta acción no se puede "
|
||||
"deshacer.</p>"
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
@@ -2252,9 +2263,9 @@ msgid "Delete Watches?"
|
||||
msgstr "¿Eliminar monitores?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
"<p>¿Está seguro de que desea eliminar los monitores seleccionados?</strong></p><p>Esta acción no se puede "
|
||||
"<p><strong>¿Está seguro de que desea eliminar los monitores seleccionados?</strong></p><p>Esta acción no se puede "
|
||||
"deshacer.</p>"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
@@ -2733,18 +2744,18 @@ msgstr "Expresión regular '%s' no es una expresión regular válida."
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "'%s' no es una expresión XPath válida. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' no es una expresión XPath válida. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "'%s' no es una expresión JSONPath válida. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' no es una expresión JSONPath válida. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "'%s' no es una expresión jq válida. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' no es una expresión jq válida. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2754,10 +2765,6 @@ msgstr "Valor vacío no permitido."
|
||||
msgid "Invalid value."
|
||||
msgstr "Valor no válido."
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "Etiqueta de grupo"
|
||||
@@ -2993,9 +3000,10 @@ msgstr "Coincide con todo lo siguiente"
|
||||
msgid "Match any of the following"
|
||||
msgstr "Coincide con cualquiera de los siguientes"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "Usar página<title>en la lista"
|
||||
msgstr "Usar página <title> en la lista"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Number of history items per watch to keep"
|
||||
@@ -3092,6 +3100,7 @@ msgstr "Actualizaciones de UI en tiempo real habilitadas"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "Favicones habilitados"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "Usar <title> de la página en la lista general de monitores"
|
||||
@@ -3193,11 +3202,11 @@ msgid "API Key"
|
||||
msgstr "Clave API"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3446,8 +3455,8 @@ msgstr "El protocolo de visualización no está permitido o el formato de URL no
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgstr "Límite de visualización alcanzado ({} /{} monitores). No se pueden agregar más monitores."
|
||||
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"
|
||||
@@ -3481,9 +3490,10 @@ msgstr "La URL que se está viendo."
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "El UUID del monitor."
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr "El título de la página del monitor, utiliza<title>si no se establece, vuelve a la URL"
|
||||
msgstr "El título de la página del monitor, utiliza <title> si no se establece, vuelve a la URL"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The watch group / tag"
|
||||
@@ -3495,7 +3505,7 @@ msgstr "La URL de la página de vista previa generada por changetection.io."
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3619,6 +3629,7 @@ msgstr ""
|
||||
"¡Use las <a target=\"newwindow\" href=\"%(url)s\">URL de notificación de AppRise</a> para notificar a casi cualquier "
|
||||
"servicio!"
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr "<i>Lea la wiki de servicios de notificación aquí para obtener notas de configuración importantes</i>"
|
||||
@@ -3982,6 +3993,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -160,8 +160,8 @@ msgstr "Importation de 5 000 des premières URL de votre liste, le reste peut ê
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "{} importées de la liste en {:.2f}s, {} ignorées."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} importées de la liste en {duration}s, {skipped_count} ignorées."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -173,8 +173,8 @@ msgstr "La structure JSON semble invalide, est-elle corrompue ?"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "{} importées de Distill.io en {:.2f}s, {} ignorées."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} importées de Distill.io en {duration}s, {skipped_count} ignorées."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -194,22 +194,18 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "{} importées de Wachete .xlsx en {:.2f}s"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "{count} importées de Wachete .xlsx en {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "{} importées de .xlsx personnalisé en {:.2f}s"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "{count} importées de .xlsx personnalisé en {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "Liste d'URL"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX et Wachete"
|
||||
@@ -243,6 +239,7 @@ msgstr "Les URL qui ne passent pas la validation resteront dans la zone de texte
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -315,8 +312,8 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "Avertissement: Le nombre de workers ({}) approche ou dépasse les cœurs CPU disponibles ({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr "Avertissement: Le nombre de workers ({worker_count}) approche ou dépasse les cœurs CPU disponibles ({cpu_count})"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -370,8 +367,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -395,14 +392,6 @@ msgstr "Filtres globaux"
|
||||
msgid "UI Options"
|
||||
msgstr "Options de l'interface utilisateur"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "SAUVEGARDES"
|
||||
@@ -907,10 +896,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1075,7 +1077,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1106,6 +1108,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1119,7 +1125,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1217,6 +1223,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr ""
|
||||
@@ -1434,8 +1441,8 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "{} moniteurs mis en file d'attente ({} déjà en file ou en cours)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "{count} moniteurs mis en file d'attente ({skipped_count} déjà en file ou en cours)."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1926,6 +1933,7 @@ msgid ""
|
||||
"lines against all history for this watch."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2183,6 +2191,7 @@ msgstr "Effacer les historiques"
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "D'ACCORD"
|
||||
@@ -2196,7 +2205,7 @@ msgid "Delete Watches?"
|
||||
msgstr "Supprimer les montres ?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
@@ -2675,18 +2684,18 @@ msgstr "RegEx '%s' n'est pas une expression régulière valide."
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "'%s' n'est pas une expression XPath valide. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' n'est pas une expression XPath valide. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "'%s' n'est pas une expression JSONPath valide. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' n'est pas une expression JSONPath valide. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "'%s' n'est pas une expression jq valide. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' n'est pas une expression jq valide. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2696,10 +2705,6 @@ msgstr "Valeur vide non autorisée."
|
||||
msgid "Invalid value."
|
||||
msgstr "Valeur invalide."
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "Groupe / Étiquette"
|
||||
@@ -2935,9 +2940,10 @@ msgstr "Faites correspondre tous les éléments suivants"
|
||||
msgid "Match any of the following"
|
||||
msgstr "Faites correspondre l'un des éléments suivants"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "Utiliser la page <titre> dans la liste"
|
||||
msgstr "Utiliser la page <title> dans la liste"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Number of history items per watch to keep"
|
||||
@@ -3034,9 +3040,10 @@ msgstr "Mises à jour en temps réel hors ligne"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "Favicons Activés"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "Utiliser la page <titre> dans la liste de présentation des moniteurs"
|
||||
msgstr "Utiliser la page <title> dans la liste de présentation des moniteurs"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API access token security check enabled"
|
||||
@@ -3135,11 +3142,11 @@ msgid "API Key"
|
||||
msgstr "Clé API"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3388,7 +3395,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3423,6 +3430,7 @@ msgstr ""
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "L'UUID du moniteur."
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr ""
|
||||
@@ -3437,7 +3445,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3559,6 +3567,7 @@ msgstr "Plus d'infos ici"
|
||||
msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a> for notification to just about any service!"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr ""
|
||||
@@ -3922,6 +3931,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -160,8 +160,8 @@ msgstr "Importazione delle prime 5.000 URL dalla tua lista, il resto può essere
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "{} Importate dalla lista in {:.2f}s, {} Ignorate."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} Importate dalla lista in {duration}s, {skipped_count} Ignorate."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -173,8 +173,8 @@ msgstr "La struttura JSON sembra non valida, è danneggiata?"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "{} Importate da Distill.io in {:.2f}s, {} Ignorate."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} Importate da Distill.io in {duration}s, {skipped_count} Ignorate."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -194,22 +194,18 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "{} importate da Wachete .xlsx in {:.2f}s"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "{count} importate da Wachete .xlsx in {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "{} importate da .xlsx personalizzato in {:.2f}s"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "{count} importate da .xlsx personalizzato in {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "Lista URL"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX & Wachete"
|
||||
@@ -243,6 +239,7 @@ msgstr ""
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -313,8 +310,8 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "Avviso: Il numero di worker ({}) si avvicina o supera i core CPU disponibili ({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr "Avviso: Il numero di worker ({worker_count}) si avvicina o supera i core CPU disponibili ({cpu_count})"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -368,8 +365,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -393,14 +390,6 @@ msgstr "Filtri globali"
|
||||
msgid "UI Options"
|
||||
msgstr "Opzioni interfaccia"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "Backup"
|
||||
@@ -903,10 +892,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1071,7 +1073,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1102,6 +1104,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1115,7 +1121,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1213,6 +1219,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr ""
|
||||
@@ -1427,8 +1434,8 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "{} monitor in coda ({} già in coda o in esecuzione)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "{count} monitor in coda ({skipped_count} già in coda o in esecuzione)."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1917,6 +1924,7 @@ msgid ""
|
||||
"lines against all history for this watch."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2174,6 +2182,7 @@ msgstr "Cancella cronologie"
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
@@ -2187,7 +2196,7 @@ msgid "Delete Watches?"
|
||||
msgstr "Eliminare monitoraggi?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
@@ -2664,18 +2673,18 @@ msgstr "La RegEx '%s' non è un'espressione regolare valida."
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "'%s' non è un'espressione XPath valida. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' non è un'espressione XPath valida. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "'%s' non è un'espressione JSONPath valida. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' non è un'espressione JSONPath valida. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "'%s' non è un'espressione jq valida. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' non è un'espressione jq valida. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2685,10 +2694,6 @@ msgstr "Valore vuoto non consentito."
|
||||
msgid "Invalid value."
|
||||
msgstr "Valore non valido."
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "Gruppo / Etichetta"
|
||||
@@ -2924,6 +2929,7 @@ msgstr "Corrisponde a tutti i seguenti"
|
||||
msgid "Match any of the following"
|
||||
msgstr "Corrisponde a uno qualsiasi dei seguenti"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "Usa <title> pagina nell'elenco"
|
||||
@@ -3023,6 +3029,7 @@ msgstr "Aggiornamenti UI in tempo reale attivi"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "Favicon attive"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "Usa <title> pagina nell'elenco osservati"
|
||||
@@ -3124,11 +3131,11 @@ msgid "API Key"
|
||||
msgstr "Chiave API"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3377,7 +3384,7 @@ msgstr "Protocollo non consentito o formato URL non valido"
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3412,6 +3419,7 @@ msgstr ""
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "L'UUID del monitor."
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr ""
|
||||
@@ -3426,7 +3434,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3548,6 +3556,7 @@ msgstr ""
|
||||
msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a> for notification to just about any service!"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr ""
|
||||
@@ -3909,6 +3918,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -161,8 +161,8 @@ msgstr "リストの最初の5,000件のURLをインポートしています。
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "{} 件をリストから {:.2f}秒でインポートしました。{} 件をスキップしました。"
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} 件をリストから {duration}秒でインポートしました。{skipped_count} 件をスキップしました。"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -174,8 +174,8 @@ msgstr "JSONの構造が無効のようです。ファイルが壊れていま
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "{} 件を Distill.io から {:.2f}秒でインポートしました。{} 件をスキップしました。"
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} 件を Distill.io から {duration}秒でインポートしました。{skipped_count} 件をスキップしました。"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -193,22 +193,18 @@ msgstr "行番号 {} の処理中にエラーが発生しました。すべて
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "{} 件を Wachete .xlsx から {:.2f}秒でインポートしました"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "{count} 件を Wachete .xlsx から {duration}秒でインポートしました"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "{} 件をカスタム .xlsx から {:.2f}秒でインポートしました"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "{count} 件をカスタム .xlsx から {duration}秒でインポートしました"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "URLリスト"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX & Wachete"
|
||||
@@ -243,6 +239,7 @@ msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON
|
||||
msgstr "Distill.io ウォッチの「エクスポート」ファイルをコピーして貼り付けてください。JSONファイルである必要があります。"
|
||||
|
||||
# 訳注: 日本語を斜体にすると字形が崩れるため、強調表示は<strong>に置き換える。
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -315,8 +312,8 @@ msgstr "パスワード保護が解除されました。"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "警告:ワーカー数({})が利用可能なCPUコア数({})に近いか超えています"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr "警告:ワーカー数({worker_count})が利用可能なCPUコア数({cpu_count})に近いか超えています"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -370,8 +367,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -395,14 +392,6 @@ msgstr "グローバルフィルタ"
|
||||
msgid "UI Options"
|
||||
msgstr "UI オプション"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "バックアップ"
|
||||
@@ -908,10 +897,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1076,7 +1078,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1107,6 +1109,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1120,7 +1126,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1219,6 +1225,7 @@ msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr "タグ名に基づく自動生成色を使用する場合はチェックを外してください。"
|
||||
|
||||
# 訳注: 日本語を斜体にすると字形が崩れるため、<strong> のみで強調し <i> は外す
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr "これらの設定は既存のすべてのウォッチ設定に<strong>追加</strong>されます。"
|
||||
@@ -1433,8 +1440,8 @@ msgstr "1件のウォッチを再チェックのためキューに追加しま
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "{} 件のウォッチを再チェックのためキューに追加しました({} 件はすでにキュー済みまたは実行中)。"
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "{count} 件のウォッチを再チェックのためキューに追加しました({skipped_count} 件はすでにキュー済みまたは実行中)。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1930,6 +1937,7 @@ msgid ""
|
||||
msgstr "コンテンツを移動させるだけのウェブサイトに適しています。新しいコンテンツが追加されたときに知りたい場合に便利で、このウォッチのすべての履歴と新しい行を比較します。"
|
||||
|
||||
# 訳注: 日本語を斜体にすると字形が崩れるため、参照は「」で囲む。
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr "サイトが行を並べ替えることで発生する不要な変更検知を減らすのに役立ちます。下の「ユニーク行をチェック」と組み合わせてください。"
|
||||
@@ -2191,6 +2199,7 @@ msgstr "履歴をすべてクリア"
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>選択した項目の履歴をクリアしてもよいですか?</p><p>この操作は元に戻せません。</p>"
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
@@ -2204,8 +2213,8 @@ msgid "Delete Watches?"
|
||||
msgstr "ウォッチを削除しますか?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>選択したウォッチを削除してもよいですか?</strong></p><p>この操作は元に戻せません。</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p><strong>選択したウォッチを削除してもよいですか?</strong></p><p>この操作は元に戻せません。</p>"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued size"
|
||||
@@ -2681,18 +2690,18 @@ msgstr "正規表現 '%s' は有効な正規表現ではありません。"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "'%s' は有効なXPath式ではありません。(%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' は有効なXPath式ではありません。(%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "'%s' は有効なJSONPath式ではありません。(%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' は有効なJSONPath式ではありません。(%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "'%s' は有効なjq式ではありません。(%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' は有効なjq式ではありません。(%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2702,10 +2711,6 @@ msgstr "空の値は許可されていません。"
|
||||
msgid "Invalid value."
|
||||
msgstr "無効な値です。"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "グループタグ"
|
||||
@@ -2941,6 +2946,7 @@ msgstr "以下のすべてに一致"
|
||||
msgid "Match any of the following"
|
||||
msgstr "以下のいずれかに一致"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "リストでページの <title> を使用"
|
||||
@@ -3040,6 +3046,7 @@ msgstr "リアルタイムUI更新を有効化"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "ファビコンを有効化"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "ウォッチ一覧リストでページの <title> を使用"
|
||||
@@ -3141,11 +3148,11 @@ msgid "API Key"
|
||||
msgstr "APIキー"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3394,8 +3401,8 @@ msgstr "ウォッチのプロトコルが許可されていないか、URL形式
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgstr "ウォッチの上限に達しました({}/{} ウォッチ)。これ以上ウォッチを追加できません。"
|
||||
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"
|
||||
@@ -3429,6 +3436,7 @@ msgstr "監視中のURL。"
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "ウォッチのUUID。"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr "ウォッチのページタイトル。設定されていない場合は <title> を使用し、それもなければURLにフォールバックします。"
|
||||
@@ -3443,8 +3451,8 @@ msgstr "changedetection.io が生成したプレビューページのURL。"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr "変更の日時。format= を受け付けます(例: change_datetime(format='%A'))。デフォルトは '%Y-%m-%d %H:%M:%S %Z'。"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr "変更の日時。format= を受け付けます(例: %(call)s)。デフォルトは '%(default)s'。"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
@@ -3572,6 +3580,7 @@ msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a
|
||||
msgstr "<a target=\"newwindow\" href=\"%(url)s\">AppRise 通知URL</a> を使えば、ほぼすべてのサービスへの通知に対応できます!"
|
||||
|
||||
# 訳注: 日本語を斜体にすると字形が崩れるため、強調表示は<strong>に置き換える。
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr "<strong>重要な設定に関するメモについては、通知サービスのWikiをこちらでお読みください</strong>"
|
||||
@@ -3941,6 +3950,7 @@ msgid "Note!: //text() function does not work where the <element> contains <![CD
|
||||
msgstr "注意: <element> が <![CDATA[]]> を含む場合、//text() 関数は動作しません"
|
||||
|
||||
# 訳注: 日本語を斜体にすると字形が崩れるため、強調表示は<strong>に置き換える。
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr "1行につき1つのCSS、xPath 1 & 2、JSON Path/JQセレクタを指定してください。一致した<strong>すべての</strong>ルールが使用されます。"
|
||||
|
||||
Binary file not shown.
@@ -160,8 +160,8 @@ msgstr "목록의 처음 5,000개 URL만 가져옵니다. 나머지는 다시
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "목록에서 {}개를 {:.2f}초 만에 가져왔습니다. {}개는 건너뛰었습니다."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "목록에서 {count}개를 {duration}초 만에 가져왔습니다. {skipped_count}개는 건너뛰었습니다."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -173,8 +173,8 @@ msgstr "JSON 구조가 올바르지 않습니다. 파일이 손상되었나요?"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "Distill.io에서 {}개를 {:.2f}초 만에 가져왔습니다. {}개는 건너뛰었습니다."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "Distill.io에서 {count}개를 {duration}초 만에 가져왔습니다. {skipped_count}개는 건너뛰었습니다."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -192,22 +192,18 @@ msgstr "{}행 처리 중 오류가 발생했습니다. 모든 셀 데이터 형
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "Wachete .xlsx에서 {}개를 {:.2f}초 만에 가져왔습니다."
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "Wachete .xlsx에서 {count}개를 {duration}초 만에 가져왔습니다."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "사용자 지정 .xlsx에서 {}개를 {:.2f}초 만에 가져왔습니다."
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "사용자 지정 .xlsx에서 {count}개를 {duration}초 만에 가져왔습니다."
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "URL 목록"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX 및 Wachete"
|
||||
@@ -241,6 +237,7 @@ msgstr "유효성 검사를 통과하지 못한 URL은 텍스트 영역에 유
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr "Distill.io 모니터링 '내보내기' 파일을 복사해 붙여 넣어 주세요. JSON 파일이어야 합니다."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -313,8 +310,8 @@ msgstr "비밀번호 보호가 해제되었습니다."
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "경고: 워커 수({})가 사용 가능한 CPU 코어 수({})에 근접하거나 초과합니다"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr "경고: 워커 수({worker_count})가 사용 가능한 CPU 코어 수({cpu_count})에 근접하거나 초과합니다"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -368,9 +365,9 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr "AI / LLM 설정이 제거되었습니다."
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
msgstr "AI 요약 캐시가 지워졌습니다(%(n)s개 파일 제거됨)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr "AI 요약 캐시가 지워졌습니다({}개 파일 제거됨)."
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
msgid "Notification debug log"
|
||||
@@ -393,14 +390,6 @@ msgstr "전역 필터"
|
||||
msgid "UI Options"
|
||||
msgstr "UI 옵션"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "백업"
|
||||
@@ -909,10 +898,23 @@ msgstr "프로바이더 선택"
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr "로컬 / 자체 호스팅"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr "Ollama 또는 사용자 지정/자체 호스팅 엔드포인트에만 필요합니다. 클라우드 프로바이더는 비워 두세요."
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr "사용 가능한 모델 불러오기"
|
||||
@@ -1077,8 +1079,8 @@ msgstr "(<code>LLM_MAX_INPUT_CHARS</code>로 설정됨)"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgstr "문자 - 현재 적용 중: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr "문자 - 현재 적용 중: %(limit)s"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No AI usage recorded yet."
|
||||
@@ -1108,6 +1110,10 @@ msgstr "aistudio.google.com → API 키 받기"
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr "로컬 Ollama에는 API 키가 필요 없습니다"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr "openrouter.ai → 키"
|
||||
@@ -1121,8 +1127,8 @@ msgid "Loading…"
|
||||
msgstr "불러오는 중..."
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgstr "반환된 모델이 없습니다. API 키를 확인하세요."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "— choose a model —"
|
||||
@@ -1219,6 +1225,7 @@ msgstr "사용자 지정 색상"
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr "체크하지 않으면 태그 이름을 기준으로 자동 생성된 색상을 사용합니다."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr "이 설정은 기존 모니터링 설정에 <strong><i>추가</i></strong>됩니다."
|
||||
@@ -1435,8 +1442,8 @@ msgstr "모니터링 1개를 재확인 대기열에 추가했습니다."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "{}개 모니터링을 재확인 대기열에 추가했습니다. ({}개는 이미 대기 중이거나 실행 중)"
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "{count}개 모니터링을 재확인 대기열에 추가했습니다. ({skipped_count}개는 이미 대기 중이거나 실행 중)"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1925,6 +1932,7 @@ msgid ""
|
||||
"lines against all history for this watch."
|
||||
msgstr "사이트가 콘텐츠 위치만 자주 바꾸고 새 콘텐츠가 추가될 때만 알고 싶을 때 유용합니다. 새 줄을 이 모니터링의 전체 기록과 비교합니다."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr "사이트가 줄 순서를 섞어 발생하는 변경 감지를 줄이는 데 도움이 됩니다. 아래의 <i>고유한 줄 확인</i>과 함께 사용해 보세요."
|
||||
@@ -2182,6 +2190,7 @@ msgstr "기록 지우기"
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>선택한 항목의 기록을 지우시겠습니까?</p><p>이 작업은 되돌릴 수 없습니다.</p>"
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "확인"
|
||||
@@ -2195,8 +2204,8 @@ msgid "Delete Watches?"
|
||||
msgstr "모니터링을 삭제하시겠습니까?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>선택한 모니터링을 삭제하시겠습니까?</p><p>이 작업은 되돌릴 수 없습니다.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p><strong>선택한 모니터링을 삭제하시겠습니까?</strong></p><p>이 작업은 되돌릴 수 없습니다.</p>"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued size"
|
||||
@@ -2672,18 +2681,18 @@ msgstr "정규식 '%s'은(는) 유효하지 않습니다."
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "'%s'은(는) 유효한 XPath 표현식이 아닙니다. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s'은(는) 유효한 XPath 표현식이 아닙니다. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "'%s'은(는) 유효한 JSONPath 표현식이 아닙니다. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s'은(는) 유효한 JSONPath 표현식이 아닙니다. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "'%s'은(는) 유효한 jq 표현식이 아닙니다. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "'%(expression)s'은(는) 유효한 jq 표현식이 아닙니다. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2693,10 +2702,6 @@ msgstr "빈 값은 허용되지 않습니다."
|
||||
msgid "Invalid value."
|
||||
msgstr "값이 잘못되었습니다."
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "그룹 / 태그"
|
||||
@@ -2932,6 +2937,7 @@ msgstr "다음 모두와 일치"
|
||||
msgid "Match any of the following"
|
||||
msgstr "다음 중 하나와 일치"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "목록에 페이지 <title> 사용"
|
||||
@@ -3031,6 +3037,7 @@ msgstr "실시간 UI 업데이트 활성화"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "파비콘 활성화"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "모니터링 목록에 페이지 <title> 사용"
|
||||
@@ -3131,14 +3138,14 @@ msgstr "모델"
|
||||
msgid "API Key"
|
||||
msgstr "API 키"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgstr "LITELLM_API_KEY 환경 변수를 사용하려면 비워 두세요"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgstr "API 기본 URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Default AI Change Summary prompt"
|
||||
msgstr "기본 AI 변경 요약 프롬프트"
|
||||
@@ -3385,8 +3392,8 @@ msgstr "모니터링 프로토콜이 허용되지 않거나 URL 형식이 올바
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgstr "모니터링 한도에 도달했습니다. ({}/{}개) 더 이상 모니터링을 추가할 수 없습니다."
|
||||
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"
|
||||
@@ -3420,9 +3427,10 @@ msgstr "모니터링 중인 URL입니다."
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "모니터링 UUID입니다."
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr "모니터링의 페이지 제목입니다. 설정되지 않았으면 <title>을 사용하고, 없으면 URL을 사용합니다."
|
||||
msgstr "모니터링의 페이지 제목입니다. 설정되지 않았으면 <title> 을 사용하고, 없으면 URL을 사용합니다."
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The watch group / tag"
|
||||
@@ -3434,8 +3442,8 @@ msgstr "changedetection.io가 생성한 미리보기 페이지의 URL입니다."
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr "변경 발생 일시입니다. format= 인자를 사용할 수 있으며 change_datetime(format='%A') 형식입니다. 기본값은 '%Y-%m-%d %H:%M:%S %Z'입니다."
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr "변경 발생 일시입니다. format= 인자를 사용할 수 있으며 %(call)s 형식입니다. 기본값은 '%(default)s'입니다."
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
@@ -3556,6 +3564,7 @@ msgstr "더보기"
|
||||
msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a> for notification to just about any service!"
|
||||
msgstr "거의 모든 서비스에 알림을 보내려면 <a target=\"newwindow\" href=\"%(url)s\">AppRise 알림 URL</a>을 사용하세요."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr "<i>중요한 설정 참고 사항은 여기의 알림 서비스 위키를 읽어 주세요</i>"
|
||||
@@ -3843,7 +3852,7 @@ msgstr "이 태그/그룹의 모든 모니터링에 판단 기준을 설정합
|
||||
#: changedetectionio/templates/edit/include_llm_intent.html
|
||||
#, python-format
|
||||
msgid "From group '%(name)s': %(value)s"
|
||||
msgstr "'%(name)s' 그룹에서 가져옴: %(value)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/include_llm_intent.html
|
||||
msgid "e.g. Alert me when the price drops below $300, or a new product is launched. Ignore footer and navigation changes."
|
||||
@@ -3917,6 +3926,7 @@ msgstr "이 그룹의 AI 기능을 사용하려면 <a href=\"%(url)s\">설정
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr "참고: <element>에 <![CDATA[]]>가 포함된 경우 //text() 함수는 동작하지 않습니다."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr "한 줄에 CSS, XPath 1/2, JSON Path/JQ 선택자를 하나씩 입력하세요. 일치하는 규칙은 <i>모두</i> 사용됩니다."
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io 0.55.3\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-04-28 15:26+1000\n"
|
||||
"POT-Creation-Date: 2026-05-12 11:08+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"
|
||||
@@ -159,7 +159,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
@@ -172,7 +172,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
@@ -191,22 +191,18 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ""
|
||||
@@ -240,6 +236,7 @@ msgstr ""
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -310,7 +307,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
@@ -365,8 +362,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -390,14 +387,6 @@ msgstr ""
|
||||
msgid "UI Options"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr ""
|
||||
@@ -900,10 +889,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1068,7 +1070,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1099,6 +1101,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1112,7 +1118,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1210,6 +1216,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr ""
|
||||
@@ -1424,7 +1431,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
@@ -1914,6 +1921,7 @@ msgid ""
|
||||
"lines against all history for this watch."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2171,6 +2179,7 @@ msgstr ""
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
@@ -2184,7 +2193,7 @@ msgid "Delete Watches?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
@@ -2661,17 +2670,17 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -2682,10 +2691,6 @@ msgstr ""
|
||||
msgid "Invalid value."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr ""
|
||||
@@ -2921,6 +2926,7 @@ msgstr ""
|
||||
msgid "Match any of the following"
|
||||
msgstr ""
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr ""
|
||||
@@ -3020,6 +3026,7 @@ msgstr ""
|
||||
msgid "Favicons Enabled"
|
||||
msgstr ""
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr ""
|
||||
@@ -3121,11 +3128,11 @@ msgid "API Key"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3374,7 +3381,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3409,6 +3416,7 @@ msgstr ""
|
||||
msgid "The UUID of the watch."
|
||||
msgstr ""
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr ""
|
||||
@@ -3423,7 +3431,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3545,6 +3553,7 @@ msgstr ""
|
||||
msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a> for notification to just about any service!"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr ""
|
||||
@@ -3906,6 +3915,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -161,8 +161,8 @@ msgstr "Importando as primeiras 5.000 URLs da sua lista, o restante pode ser imp
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "{} Importados da lista em {:.2f}s, {} Ignorados."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} Importados da lista em {duration}s, {skipped_count} Ignorados."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -174,8 +174,8 @@ msgstr "A estrutura do JSON parece inválida, ele está corrompido?"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "{} Importados do Distill.io em {:.2f}s, {} Ignorados."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} Importados do Distill.io em {duration}s, {skipped_count} Ignorados."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -193,22 +193,18 @@ msgstr "Erro ao processar a linha {}, verifique se os tipos de dados das célula
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "{} importados do Wachete .xlsx em {:.2f}s"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "{count} importados do Wachete .xlsx em {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "{} importados do .xlsx personalizado em {:.2f}s"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "{count} importados do .xlsx personalizado em {duration}s"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "Lista de URLs"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX & Wachete"
|
||||
@@ -242,6 +238,7 @@ msgstr "URLs que não passarem na validação permanecerão na caixa de texto."
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr "Copie e cole seu arquivo de 'exportação' do Distill.io, que deve ser um arquivo JSON."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -316,8 +313,8 @@ msgstr "Proteção por senha removida."
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "Aviso: O número de workers ({}) está próximo ou excede os núcleos de CPU disponíveis ({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr "Aviso: O número de workers ({worker_count}) está próximo ou excede os núcleos de CPU disponíveis ({cpu_count})"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -371,8 +368,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -396,14 +393,6 @@ msgstr "Filtros Globais"
|
||||
msgid "UI Options"
|
||||
msgstr "Opções de Interface"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "Backups"
|
||||
@@ -926,10 +915,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1094,7 +1096,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1125,6 +1127,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1138,7 +1144,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1236,6 +1242,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr "Estas configurações são <strong><i>adicionadas</i></strong> a quaisquer configurações de monitoramento existentes."
|
||||
@@ -1457,8 +1464,8 @@ msgstr "1 monitoramento enfileirado para rechecagem."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "{} monitoramentos enfileirados para rechecagem ({} já na fila ou rodando)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "{count} monitoramentos enfileirados para rechecagem ({skipped_count} já na fila ou rodando)."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1959,6 +1966,7 @@ msgstr ""
|
||||
"Útil para sites que apenas movem o conteúdo de lugar. Se você quer saber quando um NOVO conteúdo é adicionado, isso "
|
||||
"compara novas linhas contra todo o histórico."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2220,6 +2228,7 @@ msgstr "Limpar Históricos"
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>Tem certeza que deseja limpar o histórico para os itens selecionados?</p><p>Esta ação não pode ser desfeita.</p>"
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
@@ -2233,8 +2242,10 @@ msgid "Delete Watches?"
|
||||
msgstr "Excluir Monitoramentos?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>Tem certeza que deseja excluir os monitoramentos selecionados?</strong></p><p>Esta ação não pode ser desfeita.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr ""
|
||||
"<p><strong>Tem certeza que deseja excluir os monitoramentos selecionados?</strong></p><p>Esta ação não pode ser "
|
||||
"desfeita.</p>"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued size"
|
||||
@@ -2710,18 +2721,18 @@ msgstr "RegEx '%s' não é uma expressão regular válida."
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "'%s' não é uma expressão XPath válida. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' não é uma expressão XPath válida. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "'%s' não é uma expressão JSONPath válida. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' não é uma expressão JSONPath válida. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "'%s' não é uma expressão jq válida. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' não é uma expressão jq válida. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2731,10 +2742,6 @@ msgstr "Valor vazio não permitido."
|
||||
msgid "Invalid value."
|
||||
msgstr "Valor inválido."
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "Tag de grupo"
|
||||
@@ -2970,6 +2977,7 @@ msgstr "Corresponder a TODOS os seguintes"
|
||||
msgid "Match any of the following"
|
||||
msgstr "Corresponder a QUALQUER um dos seguintes"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "Usar <title> da página na lista"
|
||||
@@ -3069,6 +3077,7 @@ msgstr "Atualizações de Interface em Tempo Real Ativadas"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "Favicons Ativados"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "Usar <title> da página na lista de visão geral"
|
||||
@@ -3170,11 +3179,11 @@ msgid "API Key"
|
||||
msgstr "Chave da API"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3423,8 +3432,8 @@ msgstr "O protocolo de monitoramento não é permitido ou o formato da URL é in
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgstr "Limite de monitoramentos atingido ({}/{}). Não é possível adicionar mais."
|
||||
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"
|
||||
@@ -3458,6 +3467,7 @@ msgstr "A URL que está sendo monitorada."
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "O UUID do monitoramento."
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr "O título da página do monitoramento, usa <title> se não definido, ou a URL"
|
||||
@@ -3472,8 +3482,8 @@ msgstr "A URL da página de pré-visualização gerada pelo changedetection.io."
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr "Data/hora da mudança, aceita format=, change_datetime(format='%A')', o padrão é '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr "Data/hora da mudança, aceita format=, %(call)s, o padrão é '%(default)s'"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
@@ -3596,6 +3606,7 @@ msgstr ""
|
||||
"Use as <a target=\"newwindow\" href=\"%(url)s\">URLs de Notificação AppRise</a> para enviar notificações para quase "
|
||||
"qualquer serviço!"
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr "<i>Por favor, leia a wiki dos serviços de notificação aqui para notas importantes de configuração</i>"
|
||||
@@ -3957,6 +3968,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -165,8 +165,8 @@ msgstr "Listenizden ilk 5.000 URL içe aktarılıyor, geri kalanı daha sonra te
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "{} kayıt listeden {:.2f} saniyede içe aktarıldı, {} atlandı."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} kayıt listeden {duration} saniyede içe aktarıldı, {skipped_count} atlandı."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -178,8 +178,8 @@ msgstr "JSON yapısı geçersiz görünüyor, bozuk olabilir mi?"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "{} kayıt Distill.io'dan {:.2f} saniyede içe aktarıldı, {} atlandı."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} kayıt Distill.io'dan {duration} saniyede içe aktarıldı, {skipped_count} atlandı."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -197,22 +197,18 @@ msgstr "Satır numarası {} işlenirken hata oluştu, tüm hücre veri tiplerini
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "{} kayıt Wachete .xlsx dosyasından {:.2f} saniyede içe aktarıldı"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "{count} kayıt Wachete .xlsx dosyasından {duration} saniyede içe aktarıldı"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "{} kayıt özel .xlsx dosyasından {:.2f} saniyede içe aktarıldı"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "{count} kayıt özel .xlsx dosyasından {duration} saniyede içe aktarıldı"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "URL Listesi"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX ve Wachete"
|
||||
@@ -248,6 +244,7 @@ msgstr "Doğrulamadan geçemeyen URL'ler metin alanında kalacaktır."
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr "Distill.io izleyici 'dışa aktarım' dosyanızı kopyalayıp yapıştırın, bu bir JSON dosyası olmalıdır."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -320,8 +317,8 @@ msgstr "Parola koruması kaldırıldı."
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "Uyarı: Çalışan sayısı ({}), mevcut CPU çekirdek sayısına ({}) yakın veya bunu aşıyor"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr "Uyarı: Çalışan sayısı ({worker_count}), mevcut CPU çekirdek sayısına ({cpu_count}) yakın veya bunu aşıyor"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -375,8 +372,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -400,14 +397,6 @@ msgstr "Genel Filtreler"
|
||||
msgid "UI Options"
|
||||
msgstr "Arayüz Seçenekleri"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "Yedeklemeler"
|
||||
@@ -936,10 +925,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1104,7 +1106,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1135,6 +1137,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1148,7 +1154,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1246,6 +1252,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr "Bu ayarlar, mevcut izleyici yapılandırmalarına <strong><i>eklenerek</i></strong> uygulanır."
|
||||
@@ -1464,8 +1471,8 @@ msgstr "1 izleyici yeniden kontrol için sıraya alındı."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "{} izleyici yeniden kontrol için sıraya alındı ({} tanesi zaten sırada veya çalışıyor)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "{count} izleyici yeniden kontrol için sıraya alındı ({skipped_count} tanesi zaten sırada veya çalışıyor)."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1962,6 +1969,7 @@ msgstr ""
|
||||
"Yalnızca içeriği hareket ettiren web siteleri için iyidir ve YENİ içerik eklendiğinde bilmek istersiniz, yeni "
|
||||
"satırları bu izleyicinin tüm geçmişiyle karşılaştırır."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2225,6 +2233,7 @@ msgstr "Geçmişleri Temizle"
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>Seçili öğeler için geçmişi temizlemek istediğinizden emin misiniz?</p><p>Bu işlem geri alınamaz.</p>"
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "Tamam"
|
||||
@@ -2238,8 +2247,8 @@ msgid "Delete Watches?"
|
||||
msgstr "İzleyiciler Silinsin mi?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>Seçili izleyicileri silmek istediğinizden emin misiniz?</p><p>Bu işlem geri alınamaz.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p><strong>Seçili izleyicileri silmek istediğinizden emin misiniz?</strong></p><p>Bu işlem geri alınamaz.</p>"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued size"
|
||||
@@ -2715,18 +2724,18 @@ msgstr "'%s' RegEx'i geçerli bir düzenli ifade değil."
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "'%s' geçerli bir XPath ifadesi değil. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' geçerli bir XPath ifadesi değil. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "'%s' geçerli bir JSONPath ifadesi değil. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' geçerli bir JSONPath ifadesi değil. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "'%s' geçerli bir jq ifadesi değil. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' geçerli bir jq ifadesi değil. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2736,10 +2745,6 @@ msgstr "Boş değere izin verilmez."
|
||||
msgid "Invalid value."
|
||||
msgstr "Geçersiz değer."
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "Grup etiketi"
|
||||
@@ -2975,6 +2980,7 @@ msgstr "Aşağıdakilerin tümünü eşleştir"
|
||||
msgid "Match any of the following"
|
||||
msgstr "Aşağıdakilerden herhangi birini eşleştir"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "Listede sayfa <title>'ını kullan"
|
||||
@@ -3074,6 +3080,7 @@ msgstr "Gerçek Zamanlı Arayüz Güncellemeleri Etkin"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "Favicon'lar Etkin"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "İzleyici genel bakış listesinde sayfa <title>'ını kullan"
|
||||
@@ -3175,11 +3182,11 @@ msgid "API Key"
|
||||
msgstr "API Anahtarı"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3428,8 +3435,8 @@ msgstr "İzleyici protokolüne izin verilmiyor veya geçersiz URL formatı"
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgstr "İzleyici sınırına ulaşıldı ({}/{} izleyici). Daha fazla izleyici eklenemez."
|
||||
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"
|
||||
@@ -3463,6 +3470,7 @@ msgstr "İzlenen URL."
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "İzleyicinin UUID'si."
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr "İzleyicinin sayfa başlığı, ayarlanmamışsa <title> kullanır, URL'ye geri döner"
|
||||
@@ -3477,7 +3485,7 @@ msgstr "changedetection.io tarafından oluşturulan önizleme sayfasının URL's
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3601,6 +3609,7 @@ msgstr ""
|
||||
"<a target=\"newwindow\" href=\"%(url)s\">AppRise Bildirim URL'leri</a> ile hemen hemen her hizmete bildirim "
|
||||
"gönderebilirsiniz!"
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr "<i>Önemli yapılandırma notları için lütfen buradaki bildirim hizmetleri wiki'sini okuyun</i>"
|
||||
@@ -3964,6 +3973,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -159,8 +159,8 @@ msgstr "Імпортуються перші 5000 URL з вашого списк
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "{} Імпортовано зі списку за {:.2f}с, {} Пропущено."
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} Імпортовано зі списку за {duration}с, {skipped_count} Пропущено."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -172,8 +172,8 @@ msgstr "Структура JSON виглядає некоректною, мож
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "{} Імпортовано з Distill.io за {:.2f}с, {} Пропущено."
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} Імпортовано з Distill.io за {duration}с, {skipped_count} Пропущено."
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -191,22 +191,18 @@ msgstr "Помилка обробки рядка {}, перевірте прав
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "{} імпортовано з Wachete .xlsx за {:.2f}с"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "{count} імпортовано з Wachete .xlsx за {duration}с"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "{} імпортовано з власного .xlsx за {:.2f}с"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "{count} імпортовано з власного .xlsx за {duration}с"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "Список URL"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX та Wachete"
|
||||
@@ -240,6 +236,7 @@ msgstr "URL, що не пройшли перевірку, залишаться
|
||||
msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON file."
|
||||
msgstr "Скопіюйте та вставте вміст файлу експорту з Distill.io (файл JSON)."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -312,8 +309,10 @@ msgstr "Захист паролем вимкнено."
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "Увага: Кількість воркерів ({}) наближається до кількості доступних ядер процесора або перевищує її ({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr ""
|
||||
"Увага: Кількість воркерів ({worker_count}) наближається до кількості доступних ядер процесора або перевищує її "
|
||||
"({cpu_count})"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -367,8 +366,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -392,14 +391,6 @@ msgstr "Глобальні фільтри"
|
||||
msgid "UI Options"
|
||||
msgstr "Налаштування інтерфейсу"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "Резервні копії"
|
||||
@@ -914,10 +905,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1082,7 +1086,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1113,6 +1117,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1126,7 +1134,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1224,6 +1232,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr "Ці налаштування <strong><i>додаються</i></strong> до будь-яких існуючих конфігурацій завдань."
|
||||
@@ -1443,8 +1452,8 @@ msgstr "1 завдання додано в чергу на перевірку."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "Додано {} завдань у чергу ({} вже в черзі або виконуються)."
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "Додано {count} завдань у чергу ({skipped_count} вже в черзі або виконуються)."
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1941,6 +1950,7 @@ msgstr ""
|
||||
"Корисно для сайтів, які просто переміщують контент, коли ви хочете знати лише про НОВИЙ контент. Порівнює нові рядки "
|
||||
"з усією історією цього завдання."
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr ""
|
||||
@@ -2202,6 +2212,7 @@ msgstr "Очистити історії"
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>Ви впевнені, що хочете очистити історію для вибраних елементів?</p><p>Цю дію неможливо скасувати.</p>"
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "ОК"
|
||||
@@ -2215,8 +2226,8 @@ msgid "Delete Watches?"
|
||||
msgstr "Видалити завдання?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>Ви впевнені, що хочете видалити вибрані завдання?</p><p>Цю дію неможливо скасувати.</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p><strong>Ви впевнені, що хочете видалити вибрані завдання?</strong></p><p>Цю дію неможливо скасувати.</p>"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued size"
|
||||
@@ -2692,18 +2703,18 @@ msgstr "RegEx '%s' не є допустимим регулярним вираз
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "'%s' не є допустимим виразом XPath. (%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' не є допустимим виразом XPath. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "'%s' не є допустимим виразом JSONPath. (%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' не є допустимим виразом JSONPath. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "'%s' не є допустимим виразом jq. (%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "'%(expression)s' не є допустимим виразом jq. (%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2713,10 +2724,6 @@ msgstr "Порожнє значення неприпустиме."
|
||||
msgid "Invalid value."
|
||||
msgstr "Неприпустиме значення."
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "Тег групи"
|
||||
@@ -2952,6 +2959,7 @@ msgstr "Збіг усіх наступних умов"
|
||||
msgid "Match any of the following"
|
||||
msgstr "Збіг будь-якої з наступних умов"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "Використовувати <title> сторінки у списку"
|
||||
@@ -3051,6 +3059,7 @@ msgstr "Оновлення UI в реальному часі увімкнено"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "Фавіконки увімкнено"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "Використовувати <title> сторінки у списку огляду завдань"
|
||||
@@ -3152,11 +3161,11 @@ msgid "API Key"
|
||||
msgstr "Ключ API"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3405,8 +3414,8 @@ msgstr "Протокол завдання не дозволено або нев
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgstr "Досягнуто ліміту завдань ({}/{}). Неможливо додати більше."
|
||||
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"
|
||||
@@ -3440,6 +3449,7 @@ msgstr "URL, за яким ведеться спостереження."
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "UUID завдання."
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr "Заголовок сторінки завдання, використовує <title>, якщо не задано - URL"
|
||||
@@ -3454,7 +3464,7 @@ msgstr "URL сторінки попереднього перегляду, ств
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3578,6 +3588,7 @@ msgstr ""
|
||||
"Використовуйте <a target=\"newwindow\" href=\"%(url)s\">URL сповіщень AppRise</a> для сповіщень практично в будь-який"
|
||||
" сервіс!"
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr "<i>Будь ласка, прочитайте вікі по сервісах сповіщень тут для важливих нотаток щодо конфігурації</i>"
|
||||
@@ -3939,6 +3950,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -160,8 +160,8 @@ msgstr "仅导入列表前 5,000 个 URL,其余可稍后继续导入。"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "从列表导入 {} 条,用时 {:.2f} 秒,跳过 {} 条。"
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "从列表导入 {count} 条,用时 {duration} 秒,跳过 {skipped_count} 条。"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -173,8 +173,8 @@ msgstr "JSON 结构无效,文件是否损坏?"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "从 Distill.io 导入 {} 条,用时 {:.2f} 秒,跳过 {} 条。"
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "从 Distill.io 导入 {count} 条,用时 {duration} 秒,跳过 {skipped_count} 条。"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -192,22 +192,18 @@ msgstr "处理第 {} 行时出错,请检查单元格数据类型是否正确
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "从 Wachete .xlsx 导入 {} 条,用时 {:.2f} 秒"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "从 Wachete .xlsx 导入 {count} 条,用时 {duration} 秒"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "从自定义 .xlsx 导入 {} 条,用时 {:.2f} 秒"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "从自定义 .xlsx 导入 {count} 条,用时 {duration} 秒"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "URL 列表"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX 与 Wachete"
|
||||
@@ -242,6 +238,7 @@ msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON
|
||||
msgstr "复制并粘贴 Distill.io 监控的“导出”文件(JSON)。"
|
||||
|
||||
# TN: CJK scripts degrade when italicized; emphasis is rendered with <strong> instead.
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -315,8 +312,8 @@ msgstr "已移除密码保护。"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "警告:工作线程数({})接近或超过可用CPU核心数({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr "警告:工作线程数({worker_count})接近或超过可用CPU核心数({cpu_count})"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -370,8 +367,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -395,14 +392,6 @@ msgstr "全局过滤器"
|
||||
msgid "UI Options"
|
||||
msgstr "界面选项"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "备份"
|
||||
@@ -905,10 +894,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1073,7 +1075,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1104,6 +1106,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1117,7 +1123,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1215,6 +1221,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr "这些设置会<strong><i>应用</i></strong>到现有的所有监控项配置中。"
|
||||
@@ -1429,8 +1436,8 @@ msgstr "已将 1 个监控项加入重新检查队列。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "{}个监视器已加入队列({}个已在队列中)。"
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "{count}个监视器已加入队列({skipped_count}个已在队列中)。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1920,6 +1927,7 @@ msgid ""
|
||||
msgstr "适合仅移动内容的网站,想知道新增内容时使用,会将新行与该监控项的全部历史进行比对。"
|
||||
|
||||
# TN: CJK scripts degrade when italicized; reference is wrapped in “” instead.
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr "有助于减少因行顺序变化导致的变更,可结合下方的“检查唯一行”一起使用。"
|
||||
@@ -2177,6 +2185,7 @@ msgstr "清除历史记录"
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>确定要清除所选项的历史记录吗?</p><p>此操作不可撤销。</p>"
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "确定"
|
||||
@@ -2190,8 +2199,8 @@ msgid "Delete Watches?"
|
||||
msgstr "删除监控项?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>确定要删除所选监控项吗?</p><p>此操作不可撤销。</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p><strong>确定要删除所选监控项吗?</strong></p><p>此操作不可撤销。</p>"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued size"
|
||||
@@ -2667,18 +2676,18 @@ msgstr "正则表达式“%s”无效。"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "“%s”不是有效的 XPath 表达式。(%s)"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "“%(expression)s”不是有效的 XPath 表达式。(%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "“%s”不是有效的 JSONPath 表达式。(%s)"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "“%(expression)s”不是有效的 JSONPath 表达式。(%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "“%s”不是有效的 jq 表达式。(%s)"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "“%(expression)s”不是有效的 jq 表达式。(%(error)s)"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2688,10 +2697,6 @@ msgstr "不允许为空。"
|
||||
msgid "Invalid value."
|
||||
msgstr "值无效。"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "分组 / 标签"
|
||||
@@ -2927,6 +2932,7 @@ msgstr "匹配以下全部"
|
||||
msgid "Match any of the following"
|
||||
msgstr "匹配以下任意"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "列表中使用页面 <title>"
|
||||
@@ -3026,6 +3032,7 @@ msgstr "启用实时界面更新"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "启用站点图标"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "在监控概览列表中使用页面 <title>"
|
||||
@@ -3092,7 +3099,7 @@ msgstr "移除密码"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Render anchor tag content"
|
||||
msgstr "渲染 <a> 标签内容"
|
||||
msgstr "渲染 a 标签内容"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Allow anonymous access to watch history page when password is enabled"
|
||||
@@ -3127,11 +3134,11 @@ msgid "API Key"
|
||||
msgstr "API密钥"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3380,7 +3387,7 @@ msgstr "监控协议不允许或 URL 格式无效"
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3415,6 +3422,7 @@ msgstr "被监控的 URL。"
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "监视器的UUID。"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr "监控项的页面标题,未设置时使用 <title>,否则回退为 URL"
|
||||
@@ -3429,7 +3437,7 @@ msgstr "changedetection.io 生成的预览页面 URL。"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3552,6 +3560,7 @@ msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a
|
||||
msgstr "使用 <a target=\"newwindow\" href=\"%(url)s\">AppRise通知URL</a>,向几乎任何服务发送通知!"
|
||||
|
||||
# TN: CJK scripts degrade when italicized; emphasis is rendered with <strong> instead.
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr "<strong>请阅读通知服务 Wiki 以了解重要配置说明</strong>"
|
||||
@@ -3913,6 +3922,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -160,8 +160,8 @@ msgstr "正在匯入清單中的前 5,000 個 URL,其餘的可以再次匯入
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr "{} 已從清單匯入,耗時 {:.2f} 秒,跳過 {} 筆。"
|
||||
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} 已從清單匯入,耗時 {duration} 秒,跳過 {skipped_count} 筆。"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read JSON file, was it broken?"
|
||||
@@ -173,8 +173,8 @@ msgstr "JSON 結構看起來無效,檔案是否已損毀?"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr "{} 已從 Distill.io 匯入,耗時 {:.2f} 秒,跳過 {} 筆。"
|
||||
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
|
||||
msgstr "{count} 已從 Distill.io 匯入,耗時 {duration} 秒,跳過 {skipped_count} 筆。"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
@@ -192,22 +192,18 @@ msgstr "處理第 {} 行時發生錯誤,請檢查所有儲存格資料類型
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr "{} 已從 Wachete .xlsx 匯入,耗時 {:.2f} 秒"
|
||||
msgid "{count} imported from Wachete .xlsx in {duration}s"
|
||||
msgstr "{count} 已從 Wachete .xlsx 匯入,耗時 {duration} 秒"
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr "{} 已從自訂 .xlsx 匯入,耗時 {:.2f} 秒"
|
||||
msgid "{count} imported from custom .xlsx in {duration}s"
|
||||
msgstr "{count} 已從自訂 .xlsx 匯入,耗時 {duration} 秒"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "URL List"
|
||||
msgstr "URL 列表"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Distill.io"
|
||||
msgstr "Distill.io"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX 和 Wachete"
|
||||
@@ -242,6 +238,7 @@ msgid "Copy and Paste your Distill.io watch 'export' file, this should be a JSON
|
||||
msgstr "複製並貼上您的 Distill.io 監測任務「匯出」檔案,這應該是一個 JSON 檔案。"
|
||||
|
||||
# TN: CJK scripts degrade when italicized; emphasis is rendered with <strong> instead.
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid ""
|
||||
"This is <i>experimental</i>, supported fields are <code>name</code>, <code>uri</code>, <code>tags</code>, "
|
||||
@@ -314,8 +311,8 @@ msgstr "密碼保護已移除。"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr "警告:工作程式數量({})接近或超過可用CPU核心數({})"
|
||||
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
|
||||
msgstr "警告:工作程式數量({worker_count})接近或超過可用CPU核心數({cpu_count})"
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -369,8 +366,8 @@ msgid "AI / LLM configuration removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/llm.py
|
||||
#, python-format
|
||||
msgid "AI summary cache cleared (%(n)s file(s) removed)."
|
||||
#, python-brace-format
|
||||
msgid "AI summary cache cleared ({} file(s) removed)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/notification-log.html
|
||||
@@ -394,14 +391,6 @@ msgstr "全域過濾器"
|
||||
msgid "UI Options"
|
||||
msgstr "介面選項"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "API"
|
||||
msgstr "API"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "RSS"
|
||||
msgstr "RSS"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Backups"
|
||||
msgstr "備份"
|
||||
@@ -904,10 +893,23 @@ msgstr ""
|
||||
msgid "Local / Self-hosted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "OpenAI-compatible (vLLM, LM Studio, llama.cpp)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Only needed for Ollama or custom/self-hosted endpoints. Leave blank for cloud providers."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Local reasoning models (Qwen3, DeepSeek-R1, Gemma 3, etc.) emit chain-of-thought before the final answer. This "
|
||||
"multiplier scales every <code>max_tokens</code> cap for this endpoint to leave reasoning room. Defaults to "
|
||||
"%(default)s; raise it if responses come back truncated, lower it if you want tighter limits. Only applied to self-"
|
||||
"hosted OpenAI-compatible endpoints — cloud providers (OpenAI, Anthropic, Gemini) keep their original tight caps."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Load available models"
|
||||
msgstr ""
|
||||
@@ -1072,7 +1074,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
#, python-format
|
||||
msgid "characters — currently enforcing: %(n)s"
|
||||
msgid "characters — currently enforcing: %(limit)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1103,6 +1105,10 @@ msgstr ""
|
||||
msgid "No API key needed for local Ollama"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Bearer token for your self-hosted server (vLLM, LM Studio, etc.)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "openrouter.ai → Keys"
|
||||
msgstr ""
|
||||
@@ -1116,7 +1122,7 @@ msgid "Loading…"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "No models returned — check your API key."
|
||||
msgid "No models returned by the provider."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
@@ -1214,6 +1220,7 @@ msgstr ""
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are <strong><i>added</i></strong> to any existing watch configurations."
|
||||
msgstr "這些設定會<strong><i>新增</i></strong>至任何現有的監測設定中。"
|
||||
@@ -1428,8 +1435,8 @@ msgstr "已將 1 個監測任務排入複查佇列。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "已將 {} 個監測任務排入複查佇列({} 個已在佇列中或正在執行)。"
|
||||
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
|
||||
msgstr "已將 {count} 個監測任務排入複查佇列({skipped_count} 個已在佇列中或正在執行)。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -1919,6 +1926,7 @@ msgid ""
|
||||
msgstr "適用於內容僅會移動的網站,且您想知道何時新增了「新」內容,此功能會將新行與此監測任務的所有歷史記錄進行比較。"
|
||||
|
||||
# TN: CJK scripts degrade when italicized; UI label reference is wrapped in 「」 instead.
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below."
|
||||
msgstr "有助於減少因網站重新排列行而檢測到的變更,結合下方的「檢查獨特行」使用。"
|
||||
@@ -2176,6 +2184,7 @@ msgstr "清除歷史記錄"
|
||||
msgid "<p>Are you sure you want to clear history for the selected items?</p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>您確定要清除所選項目的歷史記錄嗎?</p><p>此動作無法復原。</p>"
|
||||
|
||||
#. Universally recognized; typically left as-is. dennis-ignore: W302
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "OK"
|
||||
msgstr "確定"
|
||||
@@ -2189,8 +2198,8 @@ msgid "Delete Watches?"
|
||||
msgstr "刪除監測任務?"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p>您確定要刪除所選的監測任務嗎?</strong></p><p>此動作無法復原。</p>"
|
||||
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
|
||||
msgstr "<p><strong>您確定要刪除所選的監測任務嗎?</strong></p><p>此動作無法復原。</p>"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued size"
|
||||
@@ -2666,18 +2675,18 @@ msgstr "RegEx 「%s」不是有效的正規表示式。"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid XPath expression. (%s)"
|
||||
msgstr "「%s」不是有效的 XPath 表達式 (%s)。"
|
||||
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
|
||||
msgstr "「%(expression)s」不是有效的 XPath 表達式 (%(error)s)。"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid JSONPath expression. (%s)"
|
||||
msgstr "「%s」不是有效的 JSONPath 表達式 (%s)。"
|
||||
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
|
||||
msgstr "「%(expression)s」不是有效的 JSONPath 表達式 (%(error)s)。"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#, python-format
|
||||
msgid "'%s' is not a valid jq expression. (%s)"
|
||||
msgstr "「%s」不是有效的 jq 表達式 (%s)。"
|
||||
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
|
||||
msgstr "「%(expression)s」不是有效的 jq 表達式 (%(error)s)。"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Empty value not allowed."
|
||||
@@ -2687,10 +2696,6 @@ msgstr "不允許空值。"
|
||||
msgid "Invalid value."
|
||||
msgstr "數值無效。"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group tag"
|
||||
msgstr "群組 / 標籤"
|
||||
@@ -2926,6 +2931,7 @@ msgstr "符合以下所有條件"
|
||||
msgid "Match any of the following"
|
||||
msgstr "符合以下任一條件"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in list"
|
||||
msgstr "在列表中使用頁面 <title>"
|
||||
@@ -3025,6 +3031,7 @@ msgstr "已啟用即時 UI 更新"
|
||||
msgid "Favicons Enabled"
|
||||
msgstr "啟用網站圖示 (Favicons)"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Use page <title> in watch overview list"
|
||||
msgstr "在監測概覽列表中使用頁面 <title>"
|
||||
@@ -3126,11 +3133,11 @@ msgid "API Key"
|
||||
msgstr "API 金鑰"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Leave blank to use LITELLM_API_KEY env var"
|
||||
msgid "API Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "API Base URL"
|
||||
msgid "Token multiplier for local reasoning models"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3379,7 +3386,7 @@ msgstr "監測協定不被允許或 URL 格式無效"
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3414,6 +3421,7 @@ msgstr ""
|
||||
msgid "The UUID of the watch."
|
||||
msgstr "監測任務的 UUID。"
|
||||
|
||||
#. dennis-ignore: W303 - False positive caused by <title>. https://github.com/mozilla/dennis/issues/213
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The page title of the watch, uses <title> if not set, falls back to URL"
|
||||
msgstr ""
|
||||
@@ -3428,7 +3436,7 @@ msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
@@ -3550,6 +3558,7 @@ msgstr "更多資訊"
|
||||
msgid "Use <a target=\"newwindow\" href=\"%(url)s\">AppRise Notification URLs</a> for notification to just about any service!"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "<i>Please read the notification services wiki here for important configuration notes</i>"
|
||||
msgstr ""
|
||||
@@ -3911,6 +3920,7 @@ msgstr ""
|
||||
msgid "Note!: //text() function does not work where the <element> contains <![CDATA[]]>"
|
||||
msgstr ""
|
||||
|
||||
#. CJK fonts lack native italics; allow substitution with conventional local styling. dennis-ignore: W303
|
||||
#: changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "One CSS, xPath 1 & 2, JSON Path/JQ selector per line, <i>any</i> rules that matches will be used."
|
||||
msgstr ""
|
||||
|
||||
+25
-16
@@ -478,22 +478,6 @@ async def async_update_worker(worker_id, q, notification_q, app, datastore, exec
|
||||
|
||||
datastore.update_watch(uuid=uuid, update_obj=update_obj)
|
||||
|
||||
# Save AI summary file now that the new snapshot has been committed
|
||||
# and its version timestamp is the last key in history
|
||||
if update_obj.get('_llm_change_summary') and _llm_from_version:
|
||||
try:
|
||||
from changedetectionio.llm.evaluator import get_effective_summary_prompt
|
||||
_llm_to_version = list(watch.history.keys())[-1]
|
||||
_llm_prompt = get_effective_summary_prompt(watch, datastore)
|
||||
watch.save_llm_diff_summary(
|
||||
update_obj['_llm_change_summary'],
|
||||
_llm_from_version,
|
||||
_llm_to_version,
|
||||
prompt=_llm_prompt,
|
||||
)
|
||||
except Exception as _fe:
|
||||
logger.warning(f"Could not write change-summary file for {uuid}: {_fe}")
|
||||
|
||||
if changed_detected or not watch.history_n:
|
||||
if update_handler.screenshot:
|
||||
watch.save_screenshot(screenshot=update_handler.screenshot)
|
||||
@@ -519,6 +503,31 @@ async def async_update_worker(worker_id, q, notification_q, app, datastore, exec
|
||||
timestamp=int(fetch_start_time),
|
||||
snapshot_id=update_obj.get('previous_md5', 'none'))
|
||||
|
||||
# Save AI summary file now that the new snapshot is committed —
|
||||
# watch.history.keys()[-1] now reflects the just-saved version,
|
||||
# so the cache filename matches what the UI will later look up.
|
||||
# Cache key must use build_summary_cache_prompt() with UI defaults so
|
||||
# the worker write and the UI read hash to the same prompt_hash.
|
||||
if update_obj.get('_llm_change_summary') and _llm_from_version:
|
||||
try:
|
||||
from changedetectionio.llm.evaluator import (
|
||||
get_effective_summary_prompt, build_summary_cache_prompt,
|
||||
)
|
||||
_llm_to_version = list(watch.history.keys())[-1]
|
||||
_llm_max_summary_tokens = datastore.data['settings']['application'].get('llm_max_summary_tokens', 3000)
|
||||
_llm_cache_prompt = build_summary_cache_prompt(
|
||||
effective_prompt=get_effective_summary_prompt(watch, datastore),
|
||||
max_summary_tokens=_llm_max_summary_tokens,
|
||||
)
|
||||
watch.save_llm_diff_summary(
|
||||
update_obj['_llm_change_summary'],
|
||||
_llm_from_version,
|
||||
_llm_to_version,
|
||||
prompt=_llm_cache_prompt,
|
||||
)
|
||||
except Exception as _fe:
|
||||
logger.warning(f"Could not write change-summary file for {uuid}: {_fe}")
|
||||
|
||||
empty_pages_are_a_change = datastore.data['settings']['application'].get('empty_pages_are_a_change', False)
|
||||
if update_handler.fetcher.content or (not update_handler.fetcher.content and empty_pages_are_a_change):
|
||||
watch.save_last_fetched_html(contents=update_handler.fetcher.content, timestamp=int(fetch_start_time))
|
||||
|
||||
+32
-1
@@ -28,7 +28,7 @@ info:
|
||||
|
||||
For example: `x-api-key: YOUR_API_KEY`
|
||||
|
||||
version: 0.1.6
|
||||
version: 0.1.7
|
||||
contact:
|
||||
name: ChangeDetection.io
|
||||
url: https://github.com/dgtlmoon/changedetection.io
|
||||
@@ -727,6 +727,37 @@ components:
|
||||
description: Number of history snapshots available
|
||||
readOnly: true
|
||||
x-computed: true
|
||||
processor_config_restock_diff:
|
||||
type: object
|
||||
readOnly: true
|
||||
x-computed: true
|
||||
description: |
|
||||
Resolved restock/price processor config for this watch.
|
||||
If a tag with `overrides_watch: true` is assigned to this watch, the tag's config is
|
||||
returned here instead of the watch's own config. Use `processor_config_restock_diff_source`
|
||||
to determine where the config originated.
|
||||
properties:
|
||||
in_stock_processing:
|
||||
type: string
|
||||
enum: [in_stock_only, all_changes, 'off']
|
||||
follow_price_changes:
|
||||
type: boolean
|
||||
price_change_min:
|
||||
type: [number, 'null']
|
||||
price_change_max:
|
||||
type: [number, 'null']
|
||||
price_change_threshold_percent:
|
||||
type: [number, 'null']
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
processor_config_restock_diff_source:
|
||||
type: string
|
||||
readOnly: true
|
||||
x-computed: true
|
||||
description: |
|
||||
Indicates the origin of `processor_config_restock_diff`.
|
||||
- `watch`: config comes from the watch itself
|
||||
- `tag:<uuid>`: config is overridden by the tag with the given UUID
|
||||
|
||||
CreateWatch:
|
||||
allOf:
|
||||
|
||||
+23
-7
File diff suppressed because one or more lines are too long
+10
-3
@@ -99,6 +99,12 @@ pytest-mock ~=3.15
|
||||
|
||||
# OpenAPI validation support
|
||||
openapi-core[flask] ~= 0.23
|
||||
# openapi-spec-validator (pulled in by openapi-core) requires jsonschema>=4.24.0.
|
||||
# litellm 1.83.1–1.83.14 exact-pin jsonschema==4.23.0, which is below that floor —
|
||||
# the two can never coexist. Without this pin, pip walks back through ~14 litellm
|
||||
# patch releases before finding 1.83.0 (jsonschema>=4.23.0,<5.0.0, accepts 4.24.x).
|
||||
# Pinning >=4.24.0 here lets the resolver reject incompatible litellm versions immediately.
|
||||
jsonschema>=4.24.0,<5.0.0
|
||||
|
||||
loguru
|
||||
|
||||
@@ -120,7 +126,7 @@ greenlet >= 3.0.3
|
||||
# Default SOCKETIO_MODE=threading is recommended for better compatibility
|
||||
gevent
|
||||
|
||||
referencing # Don't pin — jsonschema-path (required by openapi-core>=0.18) caps referencing<0.37.0, so pinning 0.37.0 forces openapi-core back to 0.17.2. Revisit once jsonschema-path>=0.3.5 relaxes the cap.
|
||||
referencing==0.37.0 # jsonschema-path>=0.4.x allows <0.38.0; 0.37.0 is current latest
|
||||
|
||||
# For conditions
|
||||
panzi-json-logic
|
||||
@@ -131,7 +137,7 @@ price-parser
|
||||
|
||||
# Lightweight MIME type detection (saves ~14MB memory vs python-magic/libmagic)
|
||||
# Used for detecting correct favicon type and content-type detection
|
||||
puremagic
|
||||
puremagic<2.0 # 2.x requires Python >=3.12; unpin once 3.10/3.11 support is dropped
|
||||
|
||||
# Scheduler - Windows seemed to miss a lot of default timezone info (even "UTC" !)
|
||||
tzdata
|
||||
@@ -141,7 +147,7 @@ tzdata
|
||||
pluggy ~= 1.6
|
||||
|
||||
# LLM intent-based change evaluation (multi-provider via litellm)
|
||||
litellm>=1.40.0
|
||||
litellm>=1.40.0,<1.83.1 # 1.83.1–1.83.14 exact-pin jsonschema==4.23.0, conflicting with openapi-spec-validator's >=4.24.0 floor; re-evaluate when litellm fixes this
|
||||
# BM25 relevance trimming for large snapshots (pure Python, no ML)
|
||||
rank-bm25>=0.2.2
|
||||
|
||||
@@ -150,6 +156,7 @@ psutil==7.2.2
|
||||
|
||||
ruff >= 0.11.2
|
||||
pre_commit >= 4.2.0
|
||||
dennis >= 1.2.0
|
||||
|
||||
# For events between checking and socketio updates
|
||||
blinker
|
||||
|
||||
@@ -7,7 +7,7 @@ mapping_file = babel.cfg
|
||||
output_file = changedetectionio/translations/messages.pot
|
||||
input_paths = changedetectionio
|
||||
keywords = _ _l gettext pgettext:1c,2
|
||||
add_comments = TRANSLATORS:
|
||||
add_comments = TRANSLATORS:,dennis-ignore:
|
||||
# Options to reduce unnecessary changes in .pot files
|
||||
sort_by_file = true
|
||||
width = 120
|
||||
|
||||
Reference in New Issue
Block a user