Compare commits

..

1 Commits

Author SHA1 Message Date
dgtlmoon 0149d88b2d [i18n] "Usage" tab label in AI settings is ambiguous across contexts #4086 2026-04-26 22:39:15 +10:00
67 changed files with 1554 additions and 2045 deletions
+2 -29
View File
@@ -11,8 +11,8 @@ jobs:
- name: Lint with Ruff
run: |
pip install ruff
# Check for syntax errors and undefined names, and gettext misuse
ruff check . --select E9,F63,F7,F82,INT
# Check for syntax errors and undefined names
ruff check . --select E9,F63,F7,F82
# Complete check with errors treated as warnings
ruff check . --exit-zero
- name: Validate OpenAPI spec
@@ -31,33 +31,6 @@ 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)"
+1 -5
View File
@@ -20,11 +20,10 @@ exclude = [
select = [
"B", # flake8-bugbear
"B9",
"C",
"C",
"E", # pycodestyle
"F", # Pyflakes
"I", # isort
"INT", # flake8-gettext
"N", # pep8-naming
"UP", # pyupgrade
"W", # pycodestyle
@@ -44,9 +43,6 @@ ignore = [
[lint.mccabe]
max-complexity = 12
[lint.flake8-gettext]
extend-function-names = ["_l", "lazy_gettext", "pgettext", "npgettext"]
[format]
indent-style = "space"
quote-style = "preserve"
+1 -1
View File
@@ -2,7 +2,7 @@
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
# Semver means never use .01, or 00. Should be .1.
__version__ = '0.55.3'
__version__ = '0.54.10'
from changedetectionio.strtobool import strtobool
from json.decoder import JSONDecodeError
+1 -22
View File
@@ -103,28 +103,7 @@ 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
# 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
watch['link'] = watch_obj.link,
return watch
@@ -75,7 +75,7 @@ class import_url_list(Importer):
self.remaining_data = []
self.remaining_data.append(url)
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)))
flash(gettext("{} Imported from list in {:.2f}s, {} Skipped.").format(good, time.time() - now, 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("{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)))
flash(gettext("{} Imported from Distill.io in {:.2f}s, {} Skipped.").format(len(self.new_uuids), time.time() - now, 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("{count} imported from Wachete .xlsx in {duration}s").format(count=len(self.new_uuids), duration=f"{time.time() - now:.2f}"))
flash(gettext("{} imported from Wachete .xlsx in {:.2f}s").format(len(self.new_uuids), time.time() - now))
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("{count} imported from custom .xlsx in {duration}s").format(count=len(self.new_uuids), duration=f"{time.time() - now:.2f}"))
flash(gettext("{} imported from custom .xlsx in {:.2f}s").format(len(self.new_uuids), time.time() - now))
@@ -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,7 +45,6 @@
<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>
@@ -104,7 +103,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>
@@ -181,8 +181,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 ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})").format(
worker_count=new_worker_count, cpu_count=cpu_count), 'warning')
flash(gettext("Warning: Worker count ({}) is close to or exceeds available CPU cores ({})").format(
new_worker_count, cpu_count), 'warning')
result = worker_pool.adjust_async_worker_count(
new_count=new_worker_count,
+1 -1
View File
@@ -122,7 +122,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 ({} file(s) removed).").format(count), 'notice')
flash(gettext("AI summary cache cleared (%(n)s file(s) removed).", n=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>
@@ -333,7 +333,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: %(limit)s', limit='{:,}'.format(llm_effective_max_input_chars)) }}</span>
<span class="llm-field-hint">{{ _('characters — currently enforcing: %(n)s', n='{:,}'.format(llm_effective_max_input_chars)) }}</span>
{% endif %}
</span>
</div>
@@ -364,7 +364,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: %(limit)s', limit='{:,}'.format(llm_effective_max_input_chars)) }}</span>
<span class="llm-field-hint">{{ _('characters — currently enforcing: %(n)s', n='{:,}'.format(llm_effective_max_input_chars)) }}</span>
{% endif %}
</span>
</div>
@@ -377,7 +377,7 @@
<script>
(function () {
const LIVE_PROVIDERS = ['openai', 'anthropic', 'gemini', 'ollama', 'openrouter'];
const LIVE_PROVIDERS = ['openai', 'anthropic', 'gemini', 'ollama'];
const BASE_DEFAULTS = { ollama: 'http://localhost:11434' };
const KEY_HINTS = {
openai: '{{ _("platform.openai.com → API keys") }}',
@@ -401,7 +401,7 @@
fetchGroup.style.display = LIVE_PROVIDERS.includes(provider) ? '' : 'none';
const needsBase = provider === 'ollama';
const needsBase = provider === 'ollama' || provider === 'openrouter';
baseGroup.style.display = needsBase ? '' : 'none';
if (BASE_DEFAULTS[provider] !== undefined) {
if (!baseField.value) baseField.value = BASE_DEFAULTS[provider];
@@ -98,7 +98,6 @@
{% 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" %}
+2 -2
View File
@@ -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 {count} watches for rechecking ({skipped_count} already queued or running).").format(
count=len(watches_to_queue_filtered), skipped_count=skipped_count))
flash(gettext("Queued {} watches for rechecking ({} already queued or running).").format(
len(watches_to_queue_filtered), skipped_count))
else:
if len(watches_to_queue_filtered) == 1:
flash(gettext("Queued 1 watch for rechecking."))
-4
View File
@@ -283,8 +283,6 @@ def construct_blueprint(datastore: ChangeDetectionStore):
# 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
@@ -318,8 +316,6 @@ 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,7 +365,6 @@ 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,13 +163,12 @@ 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><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>') }}"
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-button="{{ _('Delete') }}"><i data-feather="trash" style="width: 14px; height: 14px; stroke: white; margin-right: 4px;"></i>{{ _('Delete') }}</button>
</div>
@@ -224,8 +223,8 @@ window.watchOverviewI18n = {
{%- if any_has_restock_price_processor -%}
<th>{{ _('Restock & Price') }}</th>
{%- endif -%}
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_checked' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_checked', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last Checked') }}</span><span class="hide-on-desktop">{{ _('Checked') }}</span> <span class='arrow {{link_order}}'></span></a></th>
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_changed' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_changed', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last Changed') }}</span><span class="hide-on-desktop">{{ _('Changed') }}</span> <span class='arrow {{link_order}}'></span></a></th>
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_checked' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_checked', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last') }}</span> {{ _('Checked') }} <span class='arrow {{link_order}}'></span></a></th>
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_changed' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_changed', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last') }}</span> {{ _('Changed') }} <span class='arrow {{link_order}}'></span></a></th>
<th class="empty-cell"></th>
</tr>
</thead>
-5
View File
@@ -981,11 +981,6 @@ def changedetection_app(config=None, datastore_o=None):
"queued_data": all_queued
})
if strtobool(os.getenv('HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR', 'False')):
logger.warning("SECURITY WARNING: HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR is enabled — "
"snapshot reads are NOT confined to the watch data directory. "
"This disables protection against path traversal via restored backups (GHSA-8757-69j2-hx56).")
# Start the async workers during app initialization
# Can be overridden by ENV or use the default settings
n_workers = int(os.getenv("FETCH_WORKERS", datastore.data['settings']['requests']['workers']))
+9 -11
View File
@@ -618,8 +618,8 @@ class ValidateCSSJSONXPATHInput(object):
try:
elementpath.select(tree, line.strip(), parser=SafeXPath3Parser)
except elementpath.ElementPathError as e:
message = field.gettext('\'%(expression)s\' is not a valid XPath expression. (%(error)s)')
raise ValidationError(message % {'expression': line, 'error': str(e)})
message = field.gettext('\'%s\' is not a valid XPath expression. (%s)')
raise ValidationError(message % (line, str(e)))
except:
raise ValidationError("A system-error occurred when validating your XPath expression")
@@ -633,8 +633,8 @@ class ValidateCSSJSONXPATHInput(object):
try:
tree.xpath(line.strip())
except etree.XPathEvalError as e:
message = field.gettext('\'%(expression)s\' is not a valid XPath expression. (%(error)s)')
raise ValidationError(message % {'expression': line, 'error': str(e)})
message = field.gettext('\'%s\' is not a valid XPath expression. (%s)')
raise ValidationError(message % (line, str(e)))
except:
raise ValidationError("A system-error occurred when validating your XPath expression")
@@ -653,8 +653,8 @@ class ValidateCSSJSONXPATHInput(object):
try:
parse(input)
except (JsonPathParserError, JsonPathLexerError) as e:
message = field.gettext('\'%(expression)s\' is not a valid JSONPath expression. (%(error)s)')
raise ValidationError(message % {'expression': input, 'error': str(e)})
message = field.gettext('\'%s\' is not a valid JSONPath expression. (%s)')
raise ValidationError(message % (input, str(e)))
except:
raise ValidationError("A system-error occurred when validating your JSONPath expression")
@@ -677,8 +677,8 @@ class ValidateCSSJSONXPATHInput(object):
validate_jq_expression(input)
jq.compile(input)
except (ValueError) as e:
message = field.gettext('\'%(expression)s\' is not a valid jq expression. (%(error)s)')
raise ValidationError(message % {'expression': input, 'error': str(e)})
message = field.gettext('\'%s\' is not a valid jq expression. (%s)')
raise ValidationError(message % (input, str(e)))
except:
raise ValidationError("A system-error occurred when validating your jq expression")
@@ -728,7 +728,7 @@ class ValidateStartsWithRegex(object):
raise ValidationError(self.message or _l("Invalid value."))
class quickWatchForm(Form):
url = StringField('URL', validators=[validateURL()])
url = StringField(_l('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,7 +843,6 @@ 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)])
@@ -992,7 +991,6 @@ 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']..
+2 -2
View File
@@ -282,7 +282,7 @@ def xpath_filter(xpath_filter, html_content, append_pretty_line_formatting=False
try:
if is_xml:
# So that we can keep CDATA for cdata_in_document_to_text() to process
parser = etree.XMLParser(strip_cdata=False, resolve_entities=False, no_network=True)
parser = etree.XMLParser(strip_cdata=False)
# For XML/RSS content, use etree.fromstring to properly handle XML declarations
tree = etree.fromstring(html_content.encode('utf-8') if isinstance(html_content, str) else html_content, parser=parser)
else:
@@ -346,7 +346,7 @@ def xpath1_filter(xpath_filter, html_content, append_pretty_line_formatting=Fals
try:
if is_xml:
# So that we can keep CDATA for cdata_in_document_to_text() to process
parser = etree.XMLParser(strip_cdata=False, resolve_entities=False, no_network=True)
parser = etree.XMLParser(strip_cdata=False)
# For XML/RSS content, use etree.fromstring to properly handle XML declarations
tree = etree.fromstring(html_content.encode('utf-8') if isinstance(html_content, str) else html_content, parser=parser)
else:
+15 -23
View File
@@ -465,21 +465,22 @@ class model(EntityPersistenceMixin, watch_base):
if ',' in i:
k, v = i.strip().split(',', 2)
# Always resolve history entries to within the watch's own data directory.
# Entries restored from backup could contain absolute or traversal paths —
# never trust them. Use realpath to also block symlink-based escapes.
safe_data_dir = os.path.realpath(self.data_dir)
snapshot_fname = os.path.basename(v.strip())
resolved_path = os.path.realpath(os.path.join(self.data_dir, snapshot_fname))
# The index history could contain a relative path, so we need to make the fullpath
# so that python can read it
# Cross-platform: check for any path separator (works on Windows and Unix)
if os.sep not in v and '/' not in v and '\\' not in v:
# Relative filename only, no path separators
v = os.path.join(self.data_dir, v)
else:
# It's possible that they moved the datadir on older versions
# So the snapshot exists but is in a different path
# Cross-platform: use os.path.basename instead of split('/')
snapshot_fname = os.path.basename(v)
proposed_new_path = os.path.join(self.data_dir, snapshot_fname)
if not os.path.exists(v) and os.path.exists(proposed_new_path):
v = proposed_new_path
if not resolved_path.startswith(safe_data_dir + os.sep) and resolved_path != safe_data_dir:
logger.warning(f"Skipping unsafe history entry for {self.get('uuid')}: {v!r}")
continue
if not os.path.exists(resolved_path):
continue
tmp_history[k] = resolved_path
tmp_history[k] = v
if len(tmp_history):
self.__newest_history_key = list(tmp_history.keys())[-1]
@@ -562,15 +563,6 @@ class model(EntityPersistenceMixin, watch_base):
if not filepath:
filepath = self.history[timestamp]
# Confine every read to the watch's own data directory — defence in depth
# against any path that bypasses the history parser (e.g. direct filepath= callers).
# Set HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR=true to disable (not recommended).
if self.data_dir and not strtobool(os.getenv('HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR', 'False')):
safe_data_dir = os.path.realpath(self.data_dir)
resolved = os.path.realpath(filepath)
if not (resolved.startswith(safe_data_dir + os.sep) or resolved == safe_data_dir):
raise PermissionError(f"Snapshot path {filepath!r} is outside the watch data directory")
# Check if binary file (image, PDF, etc.)
# Binary files are NEVER saved with .br compression, only text files are
binary_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.webp', '.pdf', '.bin', '.jfif')
+7 -17
View File
@@ -65,9 +65,6 @@ 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
@@ -382,20 +379,6 @@ 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', '')
# Re #3529: diff content from text/plain pages may contain raw '<' chars that break HTML emails.
# Escape only the diff variables before Jinja2 renders them into the template, so the user's
# own HTML in the notification body (e.g. <a href="{{watch_url}}">) is never touched.
# Diff placemarkers (e.g. @removed_PLACEMARKER_OPEN) contain no HTML chars so they survive
# html_escape and are still replaced with <span> tags by apply_service_tweaks later.
watch_mime_type = n_object.get('watch_mime_type')
if (watch_mime_type and 'text/' in watch_mime_type.lower() and 'html' not in watch_mime_type.lower()
and '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']:
@@ -413,6 +396,13 @@ 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&nbsp;this" etc which is too much.
+3 -3
View File
@@ -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') or default_notification_format
return datastore.data['settings']['application'].get('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') or '').startswith('html')
n_object['markup_text_links_to_html_links'] = n_object.get('notification_format').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') or '').startswith('html')
n_object['markup_text_links_to_html_links'] = n_object.get('notification_format').startswith('html')
if len(watch['notification_urls']):
n_object['notification_urls'] = watch['notification_urls']
+9 -18
View File
@@ -3,13 +3,6 @@
* Provides accessible, animated confirmation dialogs
*/
// Escapes a string for safe insertion via innerHTML
function _modalEscapeHTML(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
const ModalDialog = {
/**
* Show a confirmation dialog
@@ -132,10 +125,9 @@ const ModalDialog = {
* @param {Function} onConfirm - Callback when confirmed
*/
confirmDelete: function(itemName, onConfirm) {
const safeName = _modalEscapeHTML(itemName);
return this.confirm({
title: 'Delete ' + safeName + '?',
message: `<p>Are you sure you want to delete <strong>${safeName}</strong>?</p><p>This action cannot be undone.</p>`,
title: 'Delete ' + itemName + '?',
message: `<p>Are you sure you want to delete <strong>${itemName}</strong>?</p><p>This action cannot be undone.</p>`,
type: 'danger',
confirmText: 'Delete',
cancelText: 'Cancel',
@@ -149,10 +141,9 @@ const ModalDialog = {
* @param {Function} onConfirm - Callback when confirmed
*/
confirmUnlink: function(itemName, onConfirm) {
const safeName = _modalEscapeHTML(itemName);
return this.confirm({
title: 'Unlink ' + safeName + '?',
message: `<p>Are you sure you want to unlink all watches from <strong>${safeName}</strong>?</p><p>The tag will be kept but watches will be removed from it.</p>`,
title: 'Unlink ' + itemName + '?',
message: `<p>Are you sure you want to unlink all watches from <strong>${itemName}</strong>?</p><p>The tag will be kept but watches will be removed from it.</p>`,
type: 'warning',
confirmText: 'Unlink',
cancelText: 'Cancel',
@@ -181,11 +172,11 @@ $(document).ready(function() {
const url = $element.attr('href');
const config = {
type: $element.attr('data-confirm-type') || 'danger',
title: $element.attr('data-confirm-title') || 'Confirm Action',
message: $element.attr('data-confirm-message') || '<p>Are you sure you want to proceed?</p>',
confirmText: $element.attr('data-confirm-button') || 'Confirm',
cancelText: $element.attr('data-cancel-button') || 'Cancel',
type: $element.data('confirm-type') || 'danger',
title: $element.data('confirm-title') || 'Confirm Action',
message: $element.data('confirm-message') || '<p>Are you sure you want to proceed?</p>',
confirmText: $element.data('confirm-button') || 'Confirm',
cancelText: $element.data('cancel-button') || 'Cancel',
onConfirm: function() {
// If it's a link, navigate to the URL
if ($element.is('a')) {
@@ -180,10 +180,4 @@ $grid-gap: 0.5rem;
.pure-table td {
padding: 3px !important;
}
}
@media (min-width: 768px) {
.watch-table thead tr th .hide-on-desktop {
display: none;
}
}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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 ({current}/{limit} watches). Cannot add more watches.").format(current=current_watch_count, limit=page_watch_limit), 'error')
flash(gettext("Watch limit reached ({}/{} watches). Cannot add more watches.").format(current_watch_count, page_watch_limit), 'error')
return None
except ValueError:
logger.warning(f"Invalid PAGE_WATCH_LIMIT value: {page_watch_limit}, ignoring limit check")
@@ -34,7 +34,6 @@
</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>
@@ -47,7 +46,11 @@
</tr>
<tr>
<td><code>{{ '{{change_datetime}}' }}</code></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>
<td>{{ _('Date/time of the change, accepts format=, change_datetime(format=\'%A\')\', default is \'%Y-%m-%d %H:%M:%S %Z\'') }}</td>
</tr>
<tr>
<td><code>{{ '{{diff_url}}' }}</code></td>
<td>{{ _('The URL of the diff output for the watch.') }}</td>
</tr>
<tr>
<td><code>{{ '{{diff_url}}' }}</code></td>
@@ -160,7 +163,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">{# 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>
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>
</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,7 +9,6 @@ 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;">
-97
View File
@@ -102,8 +102,6 @@ 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(
@@ -905,101 +903,6 @@ 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,58 +336,6 @@ 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,39 +541,6 @@ 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)
@@ -635,68 +602,3 @@ 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 - Custom HTML in the notification body (e.g. <a href="{{watch_url}}">) must NOT be
HTML-escaped regardless of the watched page's content-type. Only raw diff content from
text/plain pages needs escaping (to prevent raw '<' chars breaking HTML email rendering).
"""
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 '&lt;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):
# text/plain: diff content may contain raw '<' chars — those must be escaped, but NOT the user's template HTML
_test_custom_html_in_notification_body_not_escaped(client, datastore_path, content_type="text/plain")
# text/html: HTML processor strips tags before diffing, no escaping needed, user's template HTML must be preserved
_test_custom_html_in_notification_body_not_escaped(client, datastore_path, content_type="text/html")
# no MIME type (None): same as HTML case, user's template HTML must be preserved
_test_custom_html_in_notification_body_not_escaped(client, datastore_path, content_type=None)
-76
View File
@@ -1,11 +1,7 @@
import io
import os
import re
import time
import pytest
from flask import url_for
from zipfile import ZipFile, ZIP_DEFLATED
from changedetectionio.tests.util import set_modified_response
from .util import live_server_setup, wait_for_all_checks, delete_all_watches
@@ -827,75 +823,3 @@ def test_unresolvable_hostname_is_allowed(client, live_server, monkeypatch):
res = client.get(url_for('watchlist.index'))
assert b'this-host-does-not-exist-xyz987.invalid' in res.data, \
"Unresolvable hostname watch should appear in the watch overview list"
def test_ghsa_8757_69j2_hx56_backup_restore_history_path_traversal(client, live_server, measure_memory_usage, datastore_path):
"""
GHSA-8757-69j2-hx56: Crafted backup ZIP with absolute path in history.txt must not
expose arbitrary local files through the preview or API endpoints.
Attack chain:
1. Attacker creates a backup ZIP with a malicious history.txt containing an absolute
path (e.g. /etc/passwd) as a snapshot reference.
2. Victim restores the backup.
3. Attacker reads the targeted file via the Preview page.
The fix ensures history entries are always resolved to os.path.basename() joined with
the watch's data_dir, and rejects entries that escape that directory.
"""
set_original_response(datastore_path=datastore_path)
datastore = live_server.app.config['DATASTORE']
watch_url = url_for('test_endpoint', _external=True)
# Create a real watch and trigger a check so we have a valid backup structure
uuid = datastore.add_watch(url=watch_url)
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
wait_for_all_checks(client)
# Download a legitimate backup to use as a template
client.get(url_for("backups.request_backup"), follow_redirects=True)
time.sleep(4)
res = client.get(url_for("backups.download_backup", filename="latest"), follow_redirects=True)
assert res.content_type == "application/zip"
# Tamper: replace the history.txt inside the backup with a malicious entry
# that points at /etc/passwd (a file that exists on any Unix system)
original_zip = ZipFile(io.BytesIO(res.data))
tampered_buf = io.BytesIO()
with ZipFile(tampered_buf, 'w', ZIP_DEFLATED) as new_zip:
for item in original_zip.infolist():
data = original_zip.read(item.filename)
# Replace the watch's history.txt with a malicious absolute path entry
if item.filename.endswith('history.txt') and uuid in item.filename:
data = b'1776969105,/etc/passwd\n'
new_zip.writestr(item, data)
tampered_buf.seek(0)
tampered_zip_data = tampered_buf.read()
# Restore the tampered backup
res = client.post(
url_for("backups.restore.backups_restore_start"),
data={
'zip_file': (io.BytesIO(tampered_zip_data), 'malicious_backup.zip'),
'include_watches': 'y',
'include_watches_replace_existing': 'y',
},
content_type='multipart/form-data',
follow_redirects=True
)
assert res.status_code == 200
time.sleep(2)
# Now try to read the /etc/passwd contents via the Preview page using the injected timestamp
res = client.get(
url_for("ui.ui_preview.preview_page", uuid=uuid) + "?timestamp=1776969105",
follow_redirects=True
)
# The preview must NOT contain typical /etc/passwd content
assert b'root:' not in res.data, \
"Preview must not expose /etc/passwd — history path traversal not blocked"
assert b'/bin/' not in res.data or b'No history' in res.data or res.status_code in [404, 500], \
"Preview must not serve arbitrary local files from a malicious history entry"
@@ -311,72 +311,5 @@ class TestLLMDiffSummaryCache(unittest.TestCase):
assert watch.get_llm_diff_summary('1000', '2000', prompt=self.PROMPT) == 'Updated summary'
class TestHistoryPathTraversal(unittest.TestCase):
"""GHSA-8757-69j2-hx56: history.txt must not allow reads outside the watch data dir."""
def _make_watch(self):
mock_datastore = {'settings': {'application': {}}, 'watching': {}}
watch = Watch.model(datastore_path='/tmp', __datastore=mock_datastore, default={})
watch.ensure_data_dir_exists()
return watch
def _write_history_txt(self, watch, lines):
"""Directly write raw lines to history.txt to simulate a restored backup."""
fname = os.path.join(watch.data_dir, watch.history_index_filename)
with open(fname, 'w', encoding='utf-8') as f:
f.writelines(lines)
def test_absolute_path_in_history_is_rejected(self):
"""An absolute path like /etc/passwd must not appear in history."""
watch = self._make_watch()
self._write_history_txt(watch, ['1000000000,/etc/passwd\n'])
history = watch.history
self.assertEqual(history, {}, "Absolute path entry must be rejected")
def test_traversal_path_in_history_is_rejected(self):
"""A relative traversal path like ../../etc/passwd must not appear in history."""
watch = self._make_watch()
self._write_history_txt(watch, ['1000000000,../../etc/passwd\n'])
history = watch.history
self.assertEqual(history, {}, "Path traversal entry must be rejected")
def test_normal_snapshot_entry_is_accepted(self):
"""A bare filename written by save_history_blob must still load correctly."""
import uuid as uuid_builder
watch = self._make_watch()
watch.save_history_blob(contents="hello world", timestamp=1000000000, snapshot_id=str(uuid_builder.uuid4()))
history = watch.history
self.assertEqual(len(history), 1, "Normal snapshot entry must be accepted")
self.assertTrue(
list(history.values())[0].startswith(watch.data_dir),
"Resolved path must be inside the watch data directory"
)
def test_get_history_snapshot_blocks_outside_path_directly(self):
"""get_history_snapshot(filepath=...) must raise if the path escapes data_dir."""
watch = self._make_watch()
with self.assertRaises(PermissionError):
watch.get_history_snapshot(filepath='/etc/passwd')
def test_get_history_snapshot_blocks_traversal_directly(self):
"""get_history_snapshot(filepath=...) must raise on ../../ traversal paths."""
watch = self._make_watch()
with self.assertRaises(PermissionError):
watch.get_history_snapshot(filepath=os.path.join(watch.data_dir, '../../etc/passwd'))
def test_resolved_path_stays_inside_data_dir(self):
"""All resolved history paths must reside within the watch's data_dir."""
import uuid as uuid_builder
watch = self._make_watch()
for ts in [1000000001, 1000000002, 1000000003]:
watch.save_history_blob(contents=f"content {ts}", timestamp=ts, snapshot_id=str(uuid_builder.uuid4()))
safe_dir = os.path.realpath(watch.data_dir)
for path in watch.history.values():
self.assertTrue(
os.path.realpath(path).startswith(safe_dir),
f"Path {path!r} escapes the watch data directory"
)
if __name__ == '__main__':
unittest.main()
@@ -1,35 +0,0 @@
#!/usr/bin/env python3
# run from dir above changedetectionio/ dir
# python3 -m pytest changedetectionio/tests/unit/test_xml_security.py
import pytest
from changedetectionio import html_tools
def _xxe_payload(file_path: str) -> str:
return f"""<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file://{file_path}">
]>
<root><item>&xxe;</item></root>"""
def test_xxe_not_expanded_xpath_filter(tmp_path):
"""xpath_filter must not expand external entities (CVE-2026-41895)."""
sentinel_file = tmp_path / "sentinel.txt"
sentinel = "xxe_sentinel_should_never_appear_in_output"
sentinel_file.write_text(sentinel)
result = html_tools.xpath_filter("//item", _xxe_payload(sentinel_file), is_xml=True)
assert sentinel not in result
def test_xxe_not_expanded_xpath1_filter(tmp_path):
"""xpath1_filter must not expand external entities (CVE-2026-41895)."""
sentinel_file = tmp_path / "sentinel.txt"
sentinel = "xxe_sentinel_should_never_appear_in_output"
sentinel_file.write_text(sentinel)
result = html_tools.xpath1_filter("//item", _xxe_payload(sentinel_file), is_xml=True)
assert sentinel not in result
-55
View File
@@ -213,9 +213,6 @@ Never fix one language and move on.
pybabel init -i changedetectionio/translations/messages.pot \
-d changedetectionio/translations \
-l NEW_LANG_CODE
# Reset POT-Creation-Date to the sentinel so it matches the other catalogs
sed -i 's|^"POT-Creation-Date: .*\\n"$|"POT-Creation-Date: 1970-01-01 00:00+0000\\n"|' \
changedetectionio/translations/NEW_LANG_CODE/LC_MESSAGES/messages.po
python setup.py compile_catalog
```
@@ -223,58 +220,6 @@ 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
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: cs\n"
@@ -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 "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "{count} importováno ze seznamu za {duration}s, {skipped_count} přeskočeno."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "{} importováno ze seznamu za {:.2f}s, {} 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 "{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."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "{} importováno z Distill.io za {:.2f}s, {} přeskočeno."
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -192,18 +192,22 @@ msgstr "Chyba při zpracování řádku {}, zkontrolujte že všechny typy dat v
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "{count} importováno z Wachete .xlsx za {duration}s"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "{} importováno z Wachete .xlsx za {:.2f}s"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "{count} importováno z vlastního .xlsx za {duration}s"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "{} importováno z vlastního .xlsx za {:.2f}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"
@@ -237,7 +241,6 @@ 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>, "
@@ -310,8 +313,8 @@ msgstr "Ochrana heslem odebrána."
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
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})"
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 ({})"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -365,8 +368,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -390,6 +393,14 @@ 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"
@@ -1060,7 +1071,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1202,7 +1213,6 @@ 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 ""
@@ -1419,8 +1429,8 @@ msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
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ěží)."
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ěží)."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1909,7 +1919,6 @@ 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 ""
@@ -2167,7 +2176,6 @@ 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"
@@ -2181,8 +2189,8 @@ msgid "Delete Watches?"
msgstr "Smazat sledování?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
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>"
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>"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued size"
@@ -2209,17 +2217,13 @@ msgid "Checked"
msgstr "Zkontrolováno"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "Poslední Zkontrolováno"
msgid "Last"
msgstr "Poslední"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Změněno"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Poslední Změněno"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Nejsou nakonfigurována žádná sledování webových stránek, do výše uvedeného pole přidejte adresu URL nebo"
@@ -2268,10 +2272,18 @@ msgstr "Cena"
msgid "No information"
msgstr "Žádné informace"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Probíhá kontrola"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "Ve frontě"
@@ -2660,18 +2672,18 @@ msgstr "RegEx '%s' není platný regulární výraz."
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "'%(expression)s' není platný výraz XPath. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "'%s' není platný výraz XPath. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "'%(expression)s' není platný výraz JSONPath. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "'%s' není platný výraz JSONPath. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "'%(expression)s' není platný výraz jq. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "'%s' není platný výraz jq. (%s)"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2681,6 +2693,10 @@ 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"
@@ -2916,7 +2932,6 @@ 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>"
@@ -3016,7 +3031,6 @@ 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í"
@@ -3371,7 +3385,7 @@ msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3406,7 +3420,6 @@ 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 ""
@@ -3421,7 +3434,7 @@ msgstr ""
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3543,7 +3556,6 @@ 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 ""
@@ -3905,7 +3917,6 @@ 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 ""
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-01-14 03:57+0100\n"
"Last-Translator: \n"
"Language: de\n"
@@ -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 "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "{count} aus Liste importiert in {duration}s, {skipped_count} übersprungen."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "{} aus Liste importiert in {:.2f}s, {} ü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 "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgstr "{count} aus Distill.io importiert in {duration}s, {skipped_count} übersprungen."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "{} aus Distill.io importiert in {:.2f}s, {} übersprungen."
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -194,18 +194,22 @@ msgstr "Fehler bei der Verarbeitung von Zeile {}, prüfen Sie, ob alle Zelldaten
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "{count} aus Wachete .xlsx importiert in {duration}s"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "{} aus Wachete .xlsx importiert in {:.2f}s"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "{count} aus benutzerdefinierter .xlsx importiert in {duration}s"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "{} aus benutzerdefinierter .xlsx importiert in {:.2f}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"
@@ -243,7 +247,6 @@ 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>, "
@@ -316,8 +319,8 @@ msgstr "Passwortschutz entfernt."
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
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})"
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 ({})"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -371,8 +374,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -396,6 +399,14 @@ 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"
@@ -1076,7 +1087,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1218,7 +1229,6 @@ 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>."
@@ -1329,7 +1339,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.</p>"
"erhalten, aber die Beobachtungen werden daraus entfernt."
#: changedetectionio/blueprint/tags/templates/groups-overview.html
msgid "Unlink"
@@ -1442,8 +1452,8 @@ msgstr "1 Überwachung zur erneuten Überprüfung in Warteschlange gestellt."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
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)."
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "{} Überwachungen zur erneuten Überprüfung eingereiht ({} bereits in Warteschlange oder laufend)."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1950,7 +1960,6 @@ 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 ""
@@ -2216,7 +2225,6 @@ 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"
@@ -2230,10 +2238,10 @@ msgid "Delete Watches?"
msgstr "Überwachungen löschen?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
msgstr ""
"<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>"
"<p>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"
@@ -2260,17 +2268,13 @@ msgid "Checked"
msgstr "Geprüft"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "Zuletzt Geprüft"
msgid "Last"
msgstr "Zuletzt"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Geändert"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Zuletzt Geändert"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Es sind keine Website-Überwachungen konfiguriert. Bitte fügen Sie im Feld oben eine URL hinzu, oder"
@@ -2319,10 +2323,18 @@ msgstr "Preis"
msgid "No information"
msgstr "Keine Informationen"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Jetzt prüfen"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "Wartend"
@@ -2711,18 +2723,18 @@ msgstr "RegEx „%s“ ist kein gültiger regulärer Ausdruck."
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "„%(expression)s“ ist kein gültiger XPath-Ausdruck. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "„%s“ ist kein gültiger XPath-Ausdruck. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "„%(expression)s“ ist kein gültiger JSONPath-Ausdruck. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "„%s“ ist kein gültiger JSONPath-Ausdruck. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "„%(expression)s“ ist kein gültiger JQ-Ausdruck. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "„%s“ ist kein gültiger JQ-Ausdruck. (%s)"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2732,6 +2744,10 @@ 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"
@@ -2968,10 +2984,9 @@ 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 <title> in der Liste"
msgstr "Verwenden Sie Seite <Titel> in der Liste"
#: changedetectionio/forms.py
msgid "Number of history items per watch to keep"
@@ -3068,10 +3083,9 @@ 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 <title> in der Übersichtsliste der Beobachtungen"
msgstr "Verwenden Sie die Seite <Titel> in der Übersichtsliste der Beobachtungen"
#: changedetectionio/forms.py
msgid "API access token security check enabled"
@@ -3123,7 +3137,7 @@ msgstr "RSS-Inhaltsformat"
#: changedetectionio/forms.py
msgid "RSS <description> body built from"
msgstr "RSS-<description>-Körper erstellt aus"
msgstr "RSS-<Beschreibung>-Körper erstellt aus"
#: changedetectionio/forms.py
msgid "RSS \"System default\" template override"
@@ -3425,7 +3439,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 ({current}/{limit} watches). Cannot add more watches."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3460,7 +3474,6 @@ 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 ""
@@ -3475,7 +3488,7 @@ msgstr ""
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3597,7 +3610,6 @@ 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 ""
@@ -3961,7 +3973,6 @@ 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 ""
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io\n"
"Report-Msgid-Bugs-To: https://github.com/dgtlmoon/changedetection.io\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-01-12 16:33+0100\n"
"Last-Translator: British English Translation Team\n"
"Language: en_GB\n"
@@ -160,7 +160,7 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
@@ -173,7 +173,7 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
@@ -192,18 +192,22 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgid "{} imported from custom .xlsx in {:.2f}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 ""
@@ -237,7 +241,6 @@ 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>, "
@@ -308,7 +311,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
@@ -363,8 +366,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -388,6 +391,14 @@ 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 ""
@@ -1058,7 +1069,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1200,7 +1211,6 @@ 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 ""
@@ -1415,7 +1425,7 @@ msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
@@ -1905,7 +1915,6 @@ 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 ""
@@ -2163,7 +2172,6 @@ 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 ""
@@ -2177,7 +2185,7 @@ msgid "Delete Watches?"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
msgid "<p>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
@@ -2205,17 +2213,13 @@ msgid "Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgid "Last"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr ""
@@ -2264,10 +2268,18 @@ msgstr ""
msgid "No information"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr ""
@@ -2654,17 +2666,17 @@ msgstr ""
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr ""
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr ""
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr ""
#: changedetectionio/forms.py
@@ -2675,6 +2687,10 @@ msgstr ""
msgid "Invalid value."
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
msgid "URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Group tag"
msgstr ""
@@ -2910,7 +2926,6 @@ 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 ""
@@ -3010,7 +3025,6 @@ 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 ""
@@ -3365,7 +3379,7 @@ msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3400,7 +3414,6 @@ 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 ""
@@ -3415,7 +3428,7 @@ msgstr ""
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3537,7 +3550,6 @@ 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 ""
@@ -3899,7 +3911,6 @@ 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 ""
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: https://github.com/dgtlmoon/changedetection.io\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-01-12 16:37+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en_US\n"
@@ -160,7 +160,7 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
@@ -173,7 +173,7 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
@@ -192,18 +192,22 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgid "{} imported from custom .xlsx in {:.2f}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 ""
@@ -237,7 +241,6 @@ 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>, "
@@ -308,7 +311,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
@@ -363,8 +366,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -388,6 +391,14 @@ 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 ""
@@ -1058,7 +1069,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1200,7 +1211,6 @@ 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 ""
@@ -1415,7 +1425,7 @@ msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
@@ -1905,7 +1915,6 @@ 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 ""
@@ -2163,7 +2172,6 @@ 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 ""
@@ -2177,7 +2185,7 @@ msgid "Delete Watches?"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
msgid "<p>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
@@ -2205,17 +2213,13 @@ msgid "Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgid "Last"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr ""
@@ -2264,10 +2268,18 @@ msgstr ""
msgid "No information"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr ""
@@ -2654,17 +2666,17 @@ msgstr ""
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr ""
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr ""
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr ""
#: changedetectionio/forms.py
@@ -2675,6 +2687,10 @@ msgstr ""
msgid "Invalid value."
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
msgid "URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Group tag"
msgstr ""
@@ -2910,7 +2926,6 @@ 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 ""
@@ -3010,7 +3025,6 @@ 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 ""
@@ -3365,7 +3379,7 @@ msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3400,7 +3414,6 @@ 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 ""
@@ -3415,7 +3428,7 @@ msgstr ""
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3537,7 +3550,6 @@ 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 ""
@@ -3899,7 +3911,6 @@ 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 ""
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.53.6\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-03-20 18:13+0100\n"
"Last-Translator: Adrian Gonzalez <adrian@example.com>\n"
"Language: es\n"
@@ -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 "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "{count} importado de la lista en {duration}s, {skipped_count} omitido."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "{} importado de la lista en {:.2f}s, {} 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 "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgstr "{count} importado de Distill.io en {duration}s, {skipped_count} omitido."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "{} importado de Distill.io en {:.2f}s, {} omitido."
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -194,18 +194,22 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "{count} importado de Wachete .xlsx en {duration}s"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "{} importado de Wachete .xlsx en {:.2f}s"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "{count} importado desde .xlsx personalizado en {duration}s"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "{} importado desde .xlsx personalizado en {:.2f}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"
@@ -241,7 +245,6 @@ 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>, "
@@ -314,10 +317,8 @@ msgstr "Se eliminó la protección con contraseña."
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
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} )"
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 ({} )"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -371,8 +372,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -396,6 +397,14 @@ 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"
@@ -1096,7 +1105,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1238,7 +1247,6 @@ 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."
@@ -1462,8 +1470,8 @@ msgstr "1 monitor en cola para volver a verificar."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
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)."
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "{} monitores en cola para volver a comprobar ({} ya en cola o en ejecución)."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1964,7 +1972,6 @@ 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 ""
@@ -2232,7 +2239,6 @@ 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"
@@ -2246,9 +2252,9 @@ msgid "Delete Watches?"
msgstr "¿Eliminar monitores?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
msgid "<p>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
msgstr ""
"<p><strong>¿Está seguro de que desea eliminar los monitores seleccionados?</strong></p><p>Esta acción no se puede "
"<p>¿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
@@ -2276,17 +2282,13 @@ msgid "Checked"
msgstr "Comprobado"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "Último Comprobado"
msgid "Last"
msgstr "Último"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Cambiadp"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Último Cambiadp"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "No hay monitores de detección de cambios de página web configuradas; agregue una URL en el cuadro de arriba, o"
@@ -2335,10 +2337,18 @@ msgstr "Precio"
msgid "No information"
msgstr "Sin información"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Comprobando ahora"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "En cola"
@@ -2727,18 +2737,18 @@ msgstr "Expresión regular '%s' no es una expresión regular válida."
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "'%(expression)s' no es una expresión XPath válida. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "'%s' no es una expresión XPath válida. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "'%(expression)s' no es una expresión JSONPath válida. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "'%s' no es una expresión JSONPath válida. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "'%(expression)s' no es una expresión jq válida. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "'%s' no es una expresión jq válida. (%s)"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2748,6 +2758,10 @@ 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"
@@ -2983,10 +2997,9 @@ 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"
@@ -3083,7 +3096,6 @@ 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"
@@ -3438,8 +3450,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 ({current}/{limit} watches). Cannot add more watches."
msgstr "Límite de visualización alcanzado ({current} /{limit} monitores). No se pueden agregar más monitores."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr "Límite de visualización alcanzado ({} /{} monitores). No se pueden agregar más monitores."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
@@ -3473,10 +3485,9 @@ 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"
@@ -3488,7 +3499,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=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3612,7 +3623,6 @@ 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>"
@@ -3976,7 +3986,6 @@ 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 ""
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: fr\n"
@@ -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 "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "{count} importées de la liste en {duration}s, {skipped_count} ignorées."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "{} importées de la liste en {:.2f}s, {} 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 "{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."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "{} importées de Distill.io en {:.2f}s, {} ignorées."
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -194,18 +194,22 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "{count} importées de Wachete .xlsx en {duration}s"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "{} importées de Wachete .xlsx en {:.2f}s"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "{count} importées de .xlsx personnalisé en {duration}s"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "{} importées de .xlsx personnalisé en {:.2f}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"
@@ -239,7 +243,6 @@ 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>, "
@@ -312,8 +315,8 @@ msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
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})"
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 ({})"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -367,8 +370,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -392,6 +395,14 @@ 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"
@@ -1064,7 +1075,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1206,7 +1217,6 @@ 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,8 +1434,8 @@ msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
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)."
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "{} moniteurs mis en file d'attente ({} déjà en file ou en cours)."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1916,7 +1926,6 @@ 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,7 +2183,6 @@ 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"
@@ -2188,7 +2196,7 @@ msgid "Delete Watches?"
msgstr "Supprimer les montres ?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
msgid "<p>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
@@ -2216,17 +2224,13 @@ msgid "Checked"
msgstr "Vérification"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "Dernier Vérification"
msgid "Last"
msgstr "Dernier"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Modifié"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Dernier Modifié"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Aucune surveillance de site Web configurée, veuillez ajouter une URL dans la case ci-dessus, ou"
@@ -2275,10 +2279,18 @@ msgstr "Prix"
msgid "No information"
msgstr "Aucune information"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Vérifier maintenant"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "En file d'attente"
@@ -2667,18 +2679,18 @@ msgstr "RegEx '%s' n'est pas une expression régulière valide."
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "'%(expression)s' n'est pas une expression XPath valide. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "'%s' n'est pas une expression XPath valide. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "'%(expression)s' n'est pas une expression JSONPath valide. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "'%s' n'est pas une expression JSONPath valide. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "'%(expression)s' n'est pas une expression jq valide. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "'%s' n'est pas une expression jq valide. (%s)"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2688,6 +2700,10 @@ 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"
@@ -2923,10 +2939,9 @@ 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 <title> dans la liste"
msgstr "Utiliser la page <titre> dans la liste"
#: changedetectionio/forms.py
msgid "Number of history items per watch to keep"
@@ -3023,10 +3038,9 @@ 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 <title> dans la liste de présentation des moniteurs"
msgstr "Utiliser la page <titre> dans la liste de présentation des moniteurs"
#: changedetectionio/forms.py
msgid "API access token security check enabled"
@@ -3378,7 +3392,7 @@ msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3413,7 +3427,6 @@ 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 ""
@@ -3428,7 +3441,7 @@ msgstr ""
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3550,7 +3563,6 @@ 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 ""
@@ -3914,7 +3926,6 @@ 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 ""
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-01-02 15:32+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: it\n"
@@ -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 "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "{count} Importate dalla lista in {duration}s, {skipped_count} Ignorate."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "{} Importate dalla lista in {:.2f}s, {} 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 "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgstr "{count} Importate da Distill.io in {duration}s, {skipped_count} Ignorate."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "{} Importate da Distill.io in {:.2f}s, {} Ignorate."
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -194,18 +194,22 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "{count} importate da Wachete .xlsx in {duration}s"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "{} importate da Wachete .xlsx in {:.2f}s"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "{count} importate da .xlsx personalizzato in {duration}s"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "{} importate da .xlsx personalizzato in {:.2f}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"
@@ -239,7 +243,6 @@ 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,8 +313,8 @@ msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
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})"
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 ({})"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -365,8 +368,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -390,6 +393,14 @@ 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"
@@ -1060,7 +1071,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1202,7 +1213,6 @@ 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 ""
@@ -1417,8 +1427,8 @@ msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
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)."
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "{} monitor in coda ({} già in coda o in esecuzione)."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1907,7 +1917,6 @@ 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 ""
@@ -2165,7 +2174,6 @@ 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"
@@ -2179,7 +2187,7 @@ msgid "Delete Watches?"
msgstr "Eliminare monitoraggi?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
msgid "<p>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
@@ -2207,17 +2215,13 @@ msgid "Checked"
msgstr "Controllo"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "Ultimo Controllo"
msgid "Last"
msgstr "Ultimo"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Modifica"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Ultimo Modifica"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Nessun monitoraggio configurato, aggiungi un URL nella casella sopra, oppure"
@@ -2266,10 +2270,18 @@ msgstr "Prezzo"
msgid "No information"
msgstr "Nessuna informazione"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Controllo in corso"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "In coda"
@@ -2656,18 +2668,18 @@ msgstr "La RegEx '%s' non è un'espressione regolare valida."
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "'%(expression)s' non è un'espressione XPath valida. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "'%s' non è un'espressione XPath valida. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "'%(expression)s' non è un'espressione JSONPath valida. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "'%s' non è un'espressione JSONPath valida. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "'%(expression)s' non è un'espressione jq valida. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "'%s' non è un'espressione jq valida. (%s)"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2677,6 +2689,10 @@ 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"
@@ -2912,7 +2928,6 @@ 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"
@@ -3012,7 +3027,6 @@ 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"
@@ -3367,7 +3381,7 @@ msgstr "Protocollo non consentito o formato URL non valido"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3402,7 +3416,6 @@ 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 ""
@@ -3417,7 +3430,7 @@ msgstr ""
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3539,7 +3552,6 @@ 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 ""
@@ -3901,7 +3913,6 @@ 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 ""
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.53.6\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-03-31 23:52+0900\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: ja\n"
@@ -161,8 +161,8 @@ msgstr "リストの最初の5,000件のURLをインポートしています。
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "{count} 件をリストから {duration}秒でインポートしました。{skipped_count} 件をスキップしました。"
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "{} 件をリストから {:.2f}秒でインポートしました。{} 件をスキップしました。"
#: 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 "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgstr "{count} 件を Distill.io から {duration}秒でインポートしました。{skipped_count} 件をスキップしました。"
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "{} 件を Distill.io から {:.2f}秒でインポートしました。{} 件をスキップしました。"
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -193,18 +193,22 @@ msgstr "行番号 {} の処理中にエラーが発生しました。すべて
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "{count} 件を Wachete .xlsx から {duration}秒でインポートしました"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "{} 件を Wachete .xlsx から {:.2f}秒でインポートしました"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "{count} 件をカスタム .xlsx から {duration}秒でインポートしました"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "{} 件をカスタム .xlsx から {:.2f}秒でインポートしました"
#: 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"
@@ -239,7 +243,6 @@ 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>, "
@@ -312,8 +315,8 @@ msgstr "パスワード保護が解除されました。"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
msgstr "警告:ワーカー数({worker_count})が利用可能なCPUコア数({cpu_count})に近いか超えています"
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
msgstr "警告:ワーカー数({})が利用可能なCPUコア数({})に近いか超えています"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -367,8 +370,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -392,6 +395,14 @@ 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 "バックアップ"
@@ -1065,7 +1076,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1208,7 +1219,6 @@ 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>されます。"
@@ -1423,8 +1433,8 @@ msgstr "1件のウォッチを再チェックのためキューに追加しま
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
msgstr "{count} 件のウォッチを再チェックのためキューに追加しました({skipped_count} 件はすでにキュー済みまたは実行中)。"
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "{} 件のウォッチを再チェックのためキューに追加しました({} 件はすでにキュー済みまたは実行中)。"
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1920,7 +1930,6 @@ 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 "サイトが行を並べ替えることで発生する不要な変更検知を減らすのに役立ちます。下の「ユニーク行をチェック」と組み合わせてください。"
@@ -2182,7 +2191,6 @@ 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"
@@ -2196,8 +2204,8 @@ msgid "Delete Watches?"
msgstr "ウォッチを削除しますか?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
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>"
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>"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued size"
@@ -2224,17 +2232,13 @@ msgid "Checked"
msgstr "チェック済み"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "前回チェック"
msgid "Last"
msgstr "最終"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "変更済み"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "前回更新"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "ウェブページ変更検知ウォッチが設定されていません。上のボックスにURLを追加するか、"
@@ -2283,10 +2287,18 @@ msgstr "価格"
msgid "No information"
msgstr "情報なし"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "前回チェック"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "今すぐチェック"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "前回更新"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "キュー済み"
@@ -2673,18 +2685,18 @@ msgstr "正規表現 '%s' は有効な正規表現ではありません。"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "'%(expression)s' は有効なXPath式ではありません。(%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "'%s' は有効なXPath式ではありません。(%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "'%(expression)s' は有効なJSONPath式ではありません。(%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "'%s' は有効なJSONPath式ではありません。(%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "'%(expression)s' は有効なjq式ではありません。(%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "'%s' は有効なjq式ではありません。(%s)"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2694,6 +2706,10 @@ 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 "グループタグ"
@@ -2929,7 +2945,6 @@ 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> を使用"
@@ -3029,7 +3044,6 @@ 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> を使用"
@@ -3384,8 +3398,8 @@ msgstr "ウォッチのプロトコルが許可されていないか、URL形式
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "ウォッチの上限に達しました({current}/{limit} ウォッチ)。これ以上ウォッチを追加できません。"
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr "ウォッチの上限に達しました({}/{} ウォッチ)。これ以上ウォッチを追加できません。"
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
@@ -3419,7 +3433,6 @@ 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にフォールバックします。"
@@ -3434,8 +3447,8 @@ msgstr "changedetection.io が生成したプレビューページのURL。"
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgstr "変更の日時。format= を受け付けます(例: %(call)s)。デフォルトは '%(default)s'。"
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'。"
#: changedetectionio/templates/_common_fields.html
msgid "The URL of the diff output for the watch."
@@ -3563,7 +3576,6 @@ 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>"
@@ -3933,7 +3945,6 @@ 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>ルールが使用されます。"
File diff suppressed because it is too large Load Diff
+42 -31
View File
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.55.3\n"
"Project-Id-Version: changedetection.io 0.54.10\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-05-02 18:29+0900\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\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 "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
@@ -172,7 +172,7 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
@@ -191,18 +191,22 @@ msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr ""
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgid "{} imported from custom .xlsx in {:.2f}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 ""
@@ -236,7 +240,6 @@ 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>, "
@@ -307,7 +310,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
msgstr ""
#: changedetectionio/blueprint/settings/__init__.py
@@ -362,8 +365,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -387,6 +390,14 @@ 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 ""
@@ -1057,7 +1068,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1199,7 +1210,6 @@ 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 ""
@@ -1414,7 +1424,7 @@ msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr ""
#: changedetectionio/blueprint/ui/__init__.py
@@ -1904,7 +1914,6 @@ 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 ""
@@ -2162,7 +2171,6 @@ 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 ""
@@ -2176,7 +2184,7 @@ msgid "Delete Watches?"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "<p><strong>Are you sure you want to delete the selected watches?</strong></p><p>This action cannot be undone.</p>"
msgid "<p>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
@@ -2204,17 +2212,13 @@ msgid "Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgid "Last"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr ""
@@ -2263,10 +2267,18 @@ msgstr ""
msgid "No information"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr ""
@@ -2653,17 +2665,17 @@ msgstr ""
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr ""
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr ""
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr ""
#: changedetectionio/forms.py
@@ -2674,6 +2686,10 @@ msgstr ""
msgid "Invalid value."
msgstr ""
#: changedetectionio/blueprint/imports/templates/import.html changedetectionio/forms.py
msgid "URL"
msgstr ""
#: changedetectionio/forms.py
msgid "Group tag"
msgstr ""
@@ -2909,7 +2925,6 @@ 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 ""
@@ -3009,7 +3024,6 @@ 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 ""
@@ -3364,7 +3378,7 @@ msgstr ""
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3399,7 +3413,6 @@ 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 ""
@@ -3414,7 +3427,7 @@ msgstr ""
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3536,7 +3549,6 @@ 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 ""
@@ -3898,7 +3910,6 @@ 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 ""
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.54.8\n"
"Report-Msgid-Bugs-To: mstrey@gmail.com\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-04-07 22:00-0300\n"
"Last-Translator: Gemini AI\n"
"Language: pt_BR\n"
@@ -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 "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "{count} Importados da lista em {duration}s, {skipped_count} Ignorados."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "{} Importados da lista em {:.2f}s, {} 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 "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgstr "{count} Importados do Distill.io em {duration}s, {skipped_count} Ignorados."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "{} Importados do Distill.io em {:.2f}s, {} Ignorados."
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -193,18 +193,22 @@ msgstr "Erro ao processar a linha {}, verifique se os tipos de dados das célula
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "{count} importados do Wachete .xlsx em {duration}s"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "{} importados do Wachete .xlsx em {:.2f}s"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "{count} importados do .xlsx personalizado em {duration}s"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "{} importados do .xlsx personalizado em {:.2f}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"
@@ -238,7 +242,6 @@ 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>, "
@@ -313,8 +316,8 @@ msgstr "Proteção por senha removida."
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
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})"
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 ({})"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -368,8 +371,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -393,6 +396,14 @@ 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"
@@ -1083,7 +1094,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1225,7 +1236,6 @@ 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."
@@ -1447,8 +1457,8 @@ msgstr "1 monitoramento enfileirado para rechecagem."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
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)."
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "{} monitoramentos enfileirados para rechecagem ({} já na fila ou rodando)."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1949,7 +1959,6 @@ 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 ""
@@ -2211,7 +2220,6 @@ 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"
@@ -2225,10 +2233,8 @@ msgid "Delete Watches?"
msgstr "Excluir Monitoramentos?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
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>"
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>"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued size"
@@ -2255,17 +2261,13 @@ msgid "Checked"
msgstr "Verificado"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "Último Verificado"
msgid "Last"
msgstr "Último"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Alterado"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Último Alterado"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Nenhum monitoramento configurado, adicione uma URL na caixa acima ou"
@@ -2314,10 +2316,18 @@ msgstr "Preço"
msgid "No information"
msgstr "Sem informações"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Verificando agora"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "Enfileirado"
@@ -2704,18 +2714,18 @@ msgstr "RegEx '%s' não é uma expressão regular válida."
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "'%(expression)s' não é uma expressão XPath válida. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "'%s' não é uma expressão XPath válida. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "'%(expression)s' não é uma expressão JSONPath válida. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "'%s' não é uma expressão JSONPath válida. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "'%(expression)s' não é uma expressão jq válida. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "'%s' não é uma expressão jq válida. (%s)"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2725,6 +2735,10 @@ 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"
@@ -2960,7 +2974,6 @@ 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"
@@ -3060,7 +3073,6 @@ 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"
@@ -3415,8 +3427,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 ({current}/{limit} watches). Cannot add more watches."
msgstr "Limite de monitoramentos atingido ({current}/{limit}). Não é possível adicionar mais."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr "Limite de monitoramentos atingido ({}/{}). Não é possível adicionar mais."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
@@ -3450,7 +3462,6 @@ 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"
@@ -3465,8 +3476,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=, %(call)s, default is '%(default)s'"
msgstr "Data/hora da mudança, aceita format=, %(call)s, o padrão é '%(default)s'"
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'"
#: changedetectionio/templates/_common_fields.html
msgid "The URL of the diff output for the watch."
@@ -3589,7 +3600,6 @@ 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>"
@@ -3951,7 +3961,6 @@ 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 ""
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.53.6\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-04-10 20:38+0300\n"
"Last-Translator: \n"
"Language: tr\n"
@@ -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 "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "{count} kayıt listeden {duration} saniyede içe aktarıldı, {skipped_count} atlandı."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "{} kayıt listeden {:.2f} saniyede içe aktarıldı, {} 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 "{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ı."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "{} kayıt Distill.io'dan {:.2f} saniyede içe aktarıldı, {} atlandı."
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -197,18 +197,22 @@ msgstr "Satır numarası {} işlenirken hata oluştu, tüm hücre veri tiplerini
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "{count} kayıt Wachete .xlsx dosyasından {duration} saniyede içe aktarıldı"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "{} kayıt Wachete .xlsx dosyasından {:.2f} saniyede içe aktarıldı"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "{count} kayıt özel .xlsx dosyasından {duration} saniyede içe aktarıldı"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "{} kayıt özel .xlsx dosyasından {:.2f} 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"
@@ -244,7 +248,6 @@ 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>, "
@@ -317,8 +320,8 @@ msgstr "Parola koruması kaldırıldı."
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
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"
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"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -372,8 +375,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -397,6 +400,14 @@ 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"
@@ -1093,7 +1104,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1235,7 +1246,6 @@ 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."
@@ -1454,8 +1464,8 @@ msgstr "1 izleyici yeniden kontrol için sıraya alındı."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
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)."
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)."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1952,7 +1962,6 @@ 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 ""
@@ -2216,7 +2225,6 @@ 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"
@@ -2230,8 +2238,8 @@ msgid "Delete Watches?"
msgstr "İzleyiciler Silinsin mi?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
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>"
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>"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued size"
@@ -2258,17 +2266,13 @@ msgid "Checked"
msgstr "Kontrol Edildi"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "Son Kontrol Edildi"
msgid "Last"
msgstr "Son"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Değiştirildi"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Son Değiştirildi"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Yapılandırılmış web sayfası değişiklik tespiti izleyicisi yok, lütfen yukarıdaki kutuya bir URL ekleyin veya"
@@ -2317,10 +2321,18 @@ msgstr "Fiyat"
msgid "No information"
msgstr "Bilgi yok"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Şimdi kontrol ediliyor"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "Sırada"
@@ -2707,18 +2719,18 @@ msgstr "'%s' RegEx'i geçerli bir düzenli ifade değil."
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "'%(expression)s' geçerli bir XPath ifadesi değil. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "'%s' geçerli bir XPath ifadesi değil. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "'%(expression)s' geçerli bir JSONPath ifadesi değil. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "'%s' geçerli bir JSONPath ifadesi değil. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "'%(expression)s' geçerli bir jq ifadesi değil. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "'%s' geçerli bir jq ifadesi değil. (%s)"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2728,6 +2740,10 @@ 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"
@@ -2963,7 +2979,6 @@ 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"
@@ -3063,7 +3078,6 @@ 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"
@@ -3418,8 +3432,8 @@ msgstr "İzleyici protokolüne izin verilmiyor veya geçersiz URL formatı"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "İzleyici sınırına ulaşıldı ({current}/{limit} izleyici). Daha fazla izleyici eklenemez."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr "İzleyici sınırına ulaşıldı ({}/{} izleyici). Daha fazla izleyici eklenemez."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
@@ -3453,7 +3467,6 @@ 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"
@@ -3468,7 +3481,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=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3592,7 +3605,6 @@ 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>"
@@ -3956,7 +3968,6 @@ 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 ""
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io\n"
"Report-Msgid-Bugs-To: https://github.com/dgtlmoon/changedetection.io\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-02-19 12:30+0100\n"
"Last-Translator: \n"
"Language: uk\n"
@@ -159,8 +159,8 @@ msgstr "Імпортуються перші 5000 URL з вашого списк
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "{count} Імпортовано зі списку за {duration}с, {skipped_count} Пропущено."
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "{} Імпортовано зі списку за {:.2f}с, {} Пропущено."
#: 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 "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgstr "{count} Імпортовано з Distill.io за {duration}с, {skipped_count} Пропущено."
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "{} Імпортовано з Distill.io за {:.2f}с, {} Пропущено."
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -191,18 +191,22 @@ msgstr "Помилка обробки рядка {}, перевірте прав
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "{count} імпортовано з Wachete .xlsx за {duration}с"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "{} імпортовано з Wachete .xlsx за {:.2f}с"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "{count} імпортовано з власного .xlsx за {duration}с"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "{} імпортовано з власного .xlsx за {:.2f}с"
#: 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"
@@ -236,7 +240,6 @@ 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>, "
@@ -309,10 +312,8 @@ msgstr "Захист паролем вимкнено."
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
msgstr ""
"Увага: Кількість воркерів ({worker_count}) наближається до кількості доступних ядер процесора або перевищує її "
"({cpu_count})"
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
msgstr "Увага: Кількість воркерів ({}) наближається до кількості доступних ядер процесора або перевищує її ({})"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -366,8 +367,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -391,6 +392,14 @@ 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 "Резервні копії"
@@ -1073,7 +1082,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1215,7 +1224,6 @@ 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 +1443,8 @@ msgstr "1 завдання додано в чергу на перевірку."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
msgstr "Додано {count} завдань у чергу ({skipped_count} вже в черзі або виконуються)."
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "Додано {} завдань у чергу ({} вже в черзі або виконуються)."
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1933,7 +1941,6 @@ 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 ""
@@ -2195,7 +2202,6 @@ 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 "ОК"
@@ -2209,8 +2215,8 @@ msgid "Delete Watches?"
msgstr "Видалити завдання?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
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>"
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>"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued size"
@@ -2237,17 +2243,13 @@ msgid "Checked"
msgstr "Перевірено"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "Останній Перевірено"
msgid "Last"
msgstr "Останній"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "Змінено"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "Останній Змінено"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "Немає налаштованих завдань для відстеження змін, будь ласка, додайте URL у поле вище або"
@@ -2296,10 +2298,18 @@ msgstr "Ціна"
msgid "No information"
msgstr "Немає інформації"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "Перевірка..."
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "В черзі"
@@ -2686,18 +2696,18 @@ msgstr "RegEx '%s' не є допустимим регулярним вираз
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "'%(expression)s' не є допустимим виразом XPath. (%(error)s)"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "'%s' не є допустимим виразом XPath. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "'%(expression)s' не є допустимим виразом JSONPath. (%(error)s)"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "'%s' не є допустимим виразом JSONPath. (%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "'%(expression)s' не є допустимим виразом jq. (%(error)s)"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "'%s' не є допустимим виразом jq. (%s)"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2707,6 +2717,10 @@ 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 "Тег групи"
@@ -2942,7 +2956,6 @@ 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> сторінки у списку"
@@ -3042,7 +3055,6 @@ 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> сторінки у списку огляду завдань"
@@ -3397,8 +3409,8 @@ msgstr "Протокол завдання не дозволено або нев
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgstr "Досягнуто ліміту завдань ({current}/{limit}). Неможливо додати більше."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr "Досягнуто ліміту завдань ({}/{}). Неможливо додати більше."
#: changedetectionio/templates/_common_fields.html
msgid "Body for all notifications — You can use"
@@ -3432,7 +3444,6 @@ 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"
@@ -3447,7 +3458,7 @@ msgstr "URL сторінки попереднього перегляду, ств
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3571,7 +3582,6 @@ 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>"
@@ -3933,7 +3943,6 @@ 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 ""
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-01-18 21:31+0800\n"
"Last-Translator: 吾爱分享 <admin@wuaishare.cn>\n"
"Language: zh\n"
@@ -160,8 +160,8 @@ msgstr "仅导入列表前 5,000 个 URL,其余可稍后继续导入。"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "从列表导入 {count} 条,用时 {duration} 秒,跳过 {skipped_count} 条。"
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "从列表导入 {} 条,用时 {:.2f} 秒,跳过 {} 条。"
#: 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 "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgstr "从 Distill.io 导入 {count} 条,用时 {duration} 秒,跳过 {skipped_count} 条。"
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "从 Distill.io 导入 {} 条,用时 {:.2f} 秒,跳过 {} 条。"
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -192,18 +192,22 @@ msgstr "处理第 {} 行时出错,请检查单元格数据类型是否正确
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "从 Wachete .xlsx 导入 {count} 条,用时 {duration} 秒"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "从 Wachete .xlsx 导入 {} 条,用时 {:.2f} 秒"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "从自定义 .xlsx 导入 {count} 条,用时 {duration} 秒"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "从自定义 .xlsx 导入 {} 条,用时 {:.2f} 秒"
#: 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"
@@ -238,7 +242,6 @@ 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>, "
@@ -312,8 +315,8 @@ msgstr "已移除密码保护。"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
msgstr "警告:工作线程数({worker_count})接近或超过可用CPU核心数({cpu_count}"
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
msgstr "警告:工作线程数({})接近或超过可用CPU核心数({})"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -367,8 +370,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -392,6 +395,14 @@ 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 "备份"
@@ -1062,7 +1073,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1204,7 +1215,6 @@ 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>到现有的所有监控项配置中。"
@@ -1419,8 +1429,8 @@ msgstr "已将 1 个监控项加入重新检查队列。"
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
msgstr "{count}个监视器已加入队列({skipped_count}个已在队列中)。"
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "{}个监视器已加入队列({}个已在队列中)。"
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1910,7 +1920,6 @@ 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 "有助于减少因行顺序变化导致的变更,可结合下方的“检查唯一行”一起使用。"
@@ -2168,7 +2177,6 @@ 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 "确定"
@@ -2182,8 +2190,8 @@ msgid "Delete Watches?"
msgstr "删除监控项?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
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>"
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>"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued size"
@@ -2210,17 +2218,13 @@ msgid "Checked"
msgstr "检查"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "最近检查"
msgid "Last"
msgstr "最近"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "变更"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "最近变更"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "尚未配置网站监控项,请在上方输入 URL 或"
@@ -2269,10 +2273,18 @@ msgstr "价格"
msgid "No information"
msgstr "暂无信息"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "正在检查"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "队列中"
@@ -2659,18 +2671,18 @@ msgstr "正则表达式“%s”无效。"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "“%(expression)s”不是有效的 XPath 表达式。(%(error)s"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "“%s”不是有效的 XPath 表达式。(%s)"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "“%(expression)s”不是有效的 JSONPath 表达式。(%(error)s"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "“%s”不是有效的 JSONPath 表达式。(%s"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "“%(expression)s”不是有效的 jq 表达式。(%(error)s"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "“%s”不是有效的 jq 表达式。(%s)"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2680,6 +2692,10 @@ 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 "分组 / 标签"
@@ -2915,7 +2931,6 @@ 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>"
@@ -3015,7 +3030,6 @@ 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>"
@@ -3082,7 +3096,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"
@@ -3370,7 +3384,7 @@ msgstr "监控协议不允许或 URL 格式无效"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3405,7 +3419,6 @@ 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"
@@ -3420,7 +3433,7 @@ msgstr "changedetection.io 生成的预览页面 URL。"
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3543,7 +3556,6 @@ 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>"
@@ -3905,7 +3917,6 @@ 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 ""
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
"POT-Creation-Date: 2026-04-26 22:34+1000\n"
"PO-Revision-Date: 2026-01-15 12:00+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: zh_Hant_TW\n"
@@ -160,8 +160,8 @@ msgstr "正在匯入清單中的前 5,000 個 URL,其餘的可以再次匯入
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} Imported from list in {duration}s, {skipped_count} Skipped."
msgstr "{count} 已從清單匯入,耗時 {duration} 秒,跳過 {skipped_count} 筆。"
msgid "{} Imported from list in {:.2f}s, {} Skipped."
msgstr "{} 已從清單匯入,耗時 {:.2f} 秒,跳過 {} 筆。"
#: 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 "{count} Imported from Distill.io in {duration}s, {skipped_count} Skipped."
msgstr "{count} 已從 Distill.io 匯入,耗時 {duration} 秒,跳過 {skipped_count} 筆。"
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
msgstr "{} 已從 Distill.io 匯入,耗時 {:.2f} 秒,跳過 {} 筆。"
#: changedetectionio/blueprint/imports/importer.py
msgid "Unable to read export XLSX file, something wrong with the file?"
@@ -192,18 +192,22 @@ msgstr "處理第 {} 行時發生錯誤,請檢查所有儲存格資料類型
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from Wachete .xlsx in {duration}s"
msgstr "{count} 已從 Wachete .xlsx 匯入,耗時 {duration} 秒"
msgid "{} imported from Wachete .xlsx in {:.2f}s"
msgstr "{} 已從 Wachete .xlsx 匯入,耗時 {:.2f} 秒"
#: changedetectionio/blueprint/imports/importer.py
#, python-brace-format
msgid "{count} imported from custom .xlsx in {duration}s"
msgstr "{count} 已從自訂 .xlsx 匯入,耗時 {duration} 秒"
msgid "{} imported from custom .xlsx in {:.2f}s"
msgstr "{} 已從自訂 .xlsx 匯入,耗時 {:.2f} 秒"
#: 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"
@@ -238,7 +242,6 @@ 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>, "
@@ -311,8 +314,8 @@ msgstr "密碼保護已移除。"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
msgid "Warning: Worker count ({worker_count}) is close to or exceeds available CPU cores ({cpu_count})"
msgstr "警告:工作程式數量({worker_count})接近或超過可用CPU核心數({cpu_count}"
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
msgstr "警告:工作程式數量({})接近或超過可用CPU核心數({})"
#: changedetectionio/blueprint/settings/__init__.py
#, python-brace-format
@@ -366,8 +369,8 @@ msgid "AI / LLM configuration removed."
msgstr ""
#: changedetectionio/blueprint/settings/llm.py
#, python-brace-format
msgid "AI summary cache cleared ({} file(s) removed)."
#, python-format
msgid "AI summary cache cleared (%(n)s file(s) removed)."
msgstr ""
#: changedetectionio/blueprint/settings/templates/notification-log.html
@@ -391,6 +394,14 @@ 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 "備份"
@@ -1061,7 +1072,7 @@ msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
#, python-format
msgid "characters — currently enforcing: %(limit)s"
msgid "characters — currently enforcing: %(n)s"
msgstr ""
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
@@ -1203,7 +1214,6 @@ 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>至任何現有的監測設定中。"
@@ -1418,8 +1428,8 @@ msgstr "已將 1 個監測任務排入複查佇列。"
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
msgid "Queued {count} watches for rechecking ({skipped_count} already queued or running)."
msgstr "已將 {count} 個監測任務排入複查佇列{skipped_count} 個已在佇列中或正在執行)。"
msgid "Queued {} watches for rechecking ({} already queued or running)."
msgstr "已將 {} 個監測任務排入複查佇列。"
#: changedetectionio/blueprint/ui/__init__.py
#, python-brace-format
@@ -1909,7 +1919,6 @@ 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 "有助於減少因網站重新排列行而檢測到的變更,結合下方的「檢查獨特行」使用。"
@@ -2167,7 +2176,6 @@ 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 "確定"
@@ -2181,8 +2189,8 @@ msgid "Delete Watches?"
msgstr "刪除監測任務?"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
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>"
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>"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued size"
@@ -2209,17 +2217,13 @@ msgid "Checked"
msgstr "檢查"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr "上次檢查"
msgid "Last"
msgstr "上次"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Changed"
msgstr "變更"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr "上次變更"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "No web page change detection watches configured, please add a URL in the box above, or"
msgstr "未設定網站監測任務,請在上方欄位新增 URL,或"
@@ -2268,10 +2272,18 @@ msgstr "價格"
msgid "No information"
msgstr "無資訊"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Checked"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
msgid "Checking now"
msgstr "正在檢查"
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Last Changed"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
msgid "Queued"
msgstr "已排程"
@@ -2658,18 +2670,18 @@ msgstr "RegEx 「%s」不是有效的正規表示式。"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid XPath expression. (%(error)s)"
msgstr "「%(expression)s」不是有效的 XPath 表達式 (%(error)s)。"
msgid "'%s' is not a valid XPath expression. (%s)"
msgstr "「%s」不是有效的 XPath 表達式 (%s)。"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid JSONPath expression. (%(error)s)"
msgstr "「%(expression)s」不是有效的 JSONPath 表達式 (%(error)s)。"
msgid "'%s' is not a valid JSONPath expression. (%s)"
msgstr "「%s」不是有效的 JSONPath 表達式 (%s)。"
#: changedetectionio/forms.py
#, python-format
msgid "'%(expression)s' is not a valid jq expression. (%(error)s)"
msgstr "「%(expression)s」不是有效的 jq 表達式 (%(error)s)。"
msgid "'%s' is not a valid jq expression. (%s)"
msgstr "「%s」不是有效的 jq 表達式 (%s)。"
#: changedetectionio/forms.py
msgid "Empty value not allowed."
@@ -2679,6 +2691,10 @@ 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 "群組 / 標籤"
@@ -2914,7 +2930,6 @@ 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>"
@@ -3014,7 +3029,6 @@ 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>"
@@ -3369,7 +3383,7 @@ msgstr "監測協定不被允許或 URL 格式無效"
#: changedetectionio/store/__init__.py
#, python-brace-format
msgid "Watch limit reached ({current}/{limit} watches). Cannot add more watches."
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3404,7 +3418,6 @@ 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 ""
@@ -3419,7 +3432,7 @@ msgstr ""
#: changedetectionio/templates/_common_fields.html
#, python-format
msgid "Date/time of the change, accepts format=, %(call)s, default is '%(default)s'"
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
msgstr ""
#: changedetectionio/templates/_common_fields.html
@@ -3541,7 +3554,6 @@ 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 ""
@@ -3903,7 +3915,6 @@ 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 ""
+1 -32
View File
@@ -28,7 +28,7 @@ info:
For example: `x-api-key: YOUR_API_KEY`
version: 0.1.7
version: 0.1.6
contact:
name: ChangeDetection.io
url: https://github.com/dgtlmoon/changedetection.io
@@ -727,37 +727,6 @@ 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:
+7 -23
View File
File diff suppressed because one or more lines are too long
+3 -10
View File
@@ -99,12 +99,6 @@ 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.11.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
@@ -126,7 +120,7 @@ greenlet >= 3.0.3
# Default SOCKETIO_MODE=threading is recommended for better compatibility
gevent
referencing==0.37.0 # jsonschema-path>=0.4.x allows <0.38.0; 0.37.0 is current latest
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.
# For conditions
panzi-json-logic
@@ -137,7 +131,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<2.0 # 2.x requires Python >=3.12; unpin once 3.10/3.11 support is dropped
puremagic
# Scheduler - Windows seemed to miss a lot of default timezone info (even "UTC" !)
tzdata
@@ -147,7 +141,7 @@ tzdata
pluggy ~= 1.6
# LLM intent-based change evaluation (multi-provider via litellm)
litellm>=1.40.0,<1.83.1 # 1.83.11.83.14 exact-pin jsonschema==4.23.0, conflicting with openapi-spec-validator's >=4.24.0 floor; re-evaluate when litellm fixes this
litellm>=1.40.0
# BM25 relevance trimming for large snapshots (pure Python, no ML)
rank-bm25>=0.2.2
@@ -156,7 +150,6 @@ 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
+1 -2
View File
@@ -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:,dennis-ignore:
add_comments = TRANSLATORS:
# Options to reduce unnecessary changes in .pot files
sort_by_file = true
width = 120
@@ -22,7 +22,6 @@ domain = messages
# Options for consistent formatting
width = 120
no_fuzzy_matching = true
ignore_pot_creation_date = true
ignore_obsolete = true
[compile_catalog]