mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-04-30 06:47:03 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eac4d1e76a | |||
| 8cfa6eb336 | |||
| 9f3a9fdc18 | |||
| c78b98e3a5 | |||
| 47ffd9aa2e | |||
| b16f74e960 | |||
| a2f26f8d15 | |||
| 37eac0aab4 | |||
| 0121c42f47 | |||
| 70c900804b | |||
| 722644b111 | |||
| 0dbfb02e17 | |||
| caa393d5b9 | |||
| 17ed9536a3 | |||
| b403b08895 | |||
| 9df2e172f4 | |||
| dc037f85ab | |||
| 90f157abde | |||
| 4294b461c7 | |||
| 77116f5203 | |||
| 238d6ba72d | |||
| ede06a92bd | |||
| 9d4249c820 | |||
| b5bac1c868 | |||
| 0479aa9654 | |||
| 746e213398 | |||
| 84d97ec9cf | |||
| c8f13f5084 | |||
| d74b7d5329 | |||
| 31a760c214 | |||
| 43bba5a1b6 | |||
| 7c9eb02df4 | |||
| 0ad4090d68 | |||
| 9a10353d61 | |||
| f8236848ba |
@@ -99,11 +99,7 @@ jobs:
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: |
|
||||
docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_notification_diff'
|
||||
docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_watch_model'
|
||||
docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_jinja2_security'
|
||||
docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_semver'
|
||||
docker run test-changedetectionio bash -c 'python3 -m unittest changedetectionio.tests.unit.test_html_to_text'
|
||||
docker run test-changedetectionio bash -c 'cd changedetectionio;pytest tests/unit/'
|
||||
|
||||
# Basic pytest tests with ancillary services
|
||||
basic-tests:
|
||||
@@ -587,6 +583,10 @@ jobs:
|
||||
run: |
|
||||
docker run -e EXTRA_PACKAGES=changedetection.io-osint-processor test-changedetectionio bash -c 'cd changedetectionio;pytest -vvv -s tests/plugins/test_processor.py::test_check_plugin_processor'
|
||||
|
||||
- name: Plugin get_html_head_extras hook injects into base.html
|
||||
run: |
|
||||
docker run test-changedetectionio bash -c 'cd changedetectionio;pytest -vvv -s tests/plugins/test_html_head_extras.py'
|
||||
|
||||
# Container startup tests
|
||||
container-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[python: **.py]
|
||||
keywords = _:1,_l:1,gettext:1
|
||||
keywords = _ _l gettext
|
||||
|
||||
[jinja2: **/templates/**.html]
|
||||
encoding = utf-8
|
||||
keywords = _ _l gettext
|
||||
|
||||
@@ -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.54.7'
|
||||
__version__ = '0.54.9'
|
||||
|
||||
from changedetectionio.strtobool import strtobool
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
@@ -98,8 +98,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
backups_blueprint.register_blueprint(construct_restore_blueprint(datastore))
|
||||
backup_threads = []
|
||||
|
||||
@login_optionally_required
|
||||
@backups_blueprint.route("/request-backup", methods=['GET'])
|
||||
@login_optionally_required
|
||||
def request_backup():
|
||||
if any(thread.is_alive() for thread in backup_threads):
|
||||
flash(gettext("A backup is already running, check back in a few minutes"), "error")
|
||||
@@ -141,8 +141,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
|
||||
return backup_info
|
||||
|
||||
@login_optionally_required
|
||||
@backups_blueprint.route("/download/<string:filename>", methods=['GET'])
|
||||
@login_optionally_required
|
||||
def download_backup(filename):
|
||||
import re
|
||||
filename = filename.strip()
|
||||
@@ -165,9 +165,9 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
logger.debug(f"Backup download request for '{full_path}'")
|
||||
return send_from_directory(os.path.abspath(datastore.datastore_path), filename, as_attachment=True)
|
||||
|
||||
@login_optionally_required
|
||||
@backups_blueprint.route("/", methods=['GET'])
|
||||
@backups_blueprint.route("/create", methods=['GET'])
|
||||
@login_optionally_required
|
||||
def create():
|
||||
backups = find_backups()
|
||||
output = render_template("backup_create.html",
|
||||
@@ -176,8 +176,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
)
|
||||
return output
|
||||
|
||||
@login_optionally_required
|
||||
@backups_blueprint.route("/remove-backups", methods=['GET'])
|
||||
@login_optionally_required
|
||||
def remove_backups():
|
||||
|
||||
backup_filepath = os.path.join(datastore.datastore_path, BACKUP_FILENAME_FORMAT.format("*"))
|
||||
|
||||
@@ -174,8 +174,8 @@ def construct_restore_blueprint(datastore):
|
||||
restore_blueprint = Blueprint('restore', __name__, template_folder="templates")
|
||||
restore_threads = []
|
||||
|
||||
@login_optionally_required
|
||||
@restore_blueprint.route("/restore", methods=['GET'])
|
||||
@login_optionally_required
|
||||
def restore():
|
||||
form = RestoreForm()
|
||||
return render_template("backup_restore.html",
|
||||
@@ -184,8 +184,8 @@ def construct_restore_blueprint(datastore):
|
||||
max_upload_mb=_MAX_UPLOAD_BYTES // (1024 * 1024),
|
||||
max_decompressed_mb=_MAX_DECOMPRESSED_BYTES // (1024 * 1024))
|
||||
|
||||
@login_optionally_required
|
||||
@restore_blueprint.route("/restore/start", methods=['POST'])
|
||||
@login_optionally_required
|
||||
def backups_restore_start():
|
||||
if any(t.is_alive() for t in restore_threads):
|
||||
flash(gettext("A restore is already running, check back in a few minutes"), "error")
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
<p>{{ _('Restore a backup. Must be a .zip backup file created on/after v0.53.1 (new database layout).') }}</p>
|
||||
<p>{{ _('Note: This does not override the main application settings, only watches and groups.') }}</p>
|
||||
<p class="pure-form-message">
|
||||
{{ _('Max upload size: %(upload)s MB · Max decompressed size: %(decomp)s MB',
|
||||
upload=max_upload_mb, decomp=max_decompressed_mb) }}
|
||||
{{ _('Max upload size: %(upload)s MB, Max decompressed size: %(decomp)s MB', upload=max_upload_mb, decomp=max_decompressed_mb) }}
|
||||
</p>
|
||||
|
||||
<form class="pure-form pure-form-stacked settings"
|
||||
|
||||
@@ -268,8 +268,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
return browsersteps_start_session
|
||||
|
||||
|
||||
@login_optionally_required
|
||||
@browser_steps_blueprint.route("/browsersteps_start_session", methods=['GET'])
|
||||
@login_optionally_required
|
||||
def browsersteps_start_session():
|
||||
# A new session was requested, return sessionID
|
||||
import uuid
|
||||
@@ -304,8 +304,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
logger.debug("Starting connection with playwright - done")
|
||||
return {'browsersteps_session_id': browsersteps_session_id}
|
||||
|
||||
@login_optionally_required
|
||||
@browser_steps_blueprint.route("/browsersteps_image", methods=['GET'])
|
||||
@login_optionally_required
|
||||
def browser_steps_fetch_screenshot_image():
|
||||
from flask import (
|
||||
make_response,
|
||||
@@ -330,8 +330,8 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
return make_response('Unable to fetch image, is the URL correct? does the watch exist? does the step_type-n.jpeg exist?', 401)
|
||||
|
||||
# A request for an action was received
|
||||
@login_optionally_required
|
||||
@browser_steps_blueprint.route("/browsersteps_update", methods=['POST'])
|
||||
@login_optionally_required
|
||||
def browsersteps_ui_update():
|
||||
import base64
|
||||
|
||||
|
||||
@@ -160,8 +160,7 @@ class import_xlsx_wachete(Importer):
|
||||
flash(gettext("Unable to read export XLSX file, something wrong with the file?"), 'error')
|
||||
return
|
||||
|
||||
row_id = 2
|
||||
for row in wb.active.iter_rows(min_row=row_id):
|
||||
for row_id, row in enumerate(wb.active.iter_rows(min_row=2), start=2):
|
||||
try:
|
||||
extras = {}
|
||||
data = {}
|
||||
@@ -212,8 +211,6 @@ class import_xlsx_wachete(Importer):
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
flash(gettext("Error processing row number {}, check all cell data types are correct, row was skipped.").format(row_id), 'error')
|
||||
else:
|
||||
row_id += 1
|
||||
|
||||
flash(gettext("{} imported from Wachete .xlsx in {:.2f}s").format(len(self.new_uuids), time.time() - now))
|
||||
|
||||
@@ -241,10 +238,10 @@ class import_xlsx_custom(Importer):
|
||||
|
||||
# @todo cehck atleast 2 rows, same in other method
|
||||
from changedetectionio.forms import validate_url
|
||||
row_i = 1
|
||||
row_i = 0
|
||||
|
||||
try:
|
||||
for row in wb.active.iter_rows():
|
||||
for row_i, row in enumerate(wb.active.iter_rows(), start=1):
|
||||
url = None
|
||||
tags = None
|
||||
extras = {}
|
||||
@@ -295,7 +292,5 @@ class import_xlsx_custom(Importer):
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
flash(gettext("Error processing row number {}, check all cell data types are correct, row was skipped.").format(row_i), 'error')
|
||||
else:
|
||||
row_i += 1
|
||||
|
||||
flash(gettext("{} imported from custom .xlsx in {:.2f}s").format(len(self.new_uuids), time.time() - now))
|
||||
@@ -9,6 +9,7 @@
|
||||
<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="#xlsx">{{ _('.XLSX & Wachete') }}</a></li>
|
||||
<li class="tab"><a href="{{url_for('backups.restore.restore')}}">{{ _('Backup Restore') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -22,11 +22,14 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
|
||||
tag_count = Counter(tag for watch in datastore.data['watching'].values() if watch.get('tags') for tag in watch['tags'])
|
||||
|
||||
from changedetectionio import processors
|
||||
output = render_template("groups-overview.html",
|
||||
app_rss_token=datastore.data['settings']['application'].get('rss_access_token'),
|
||||
available_tags=sorted_tags,
|
||||
form=add_form,
|
||||
generate_tag_colors=processors.generate_processor_badge_colors,
|
||||
tag_count=tag_count,
|
||||
wcag_text_color=processors.wcag_text_color,
|
||||
)
|
||||
|
||||
return output
|
||||
@@ -208,9 +211,17 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
template = env.from_string(template_str)
|
||||
included_content = template.render(**template_args)
|
||||
|
||||
# Watches whose URL currently matches this tag's pattern
|
||||
matching_watches = {
|
||||
w_uuid: watch
|
||||
for w_uuid, watch in datastore.data['watching'].items()
|
||||
if default.matches_url(watch.get('url', ''))
|
||||
}
|
||||
|
||||
output = render_template("edit-tag.html",
|
||||
extra_form_content=included_content,
|
||||
extra_tab_content=form.extra_tab_content() if form.extra_tab_content() else None,
|
||||
matching_watches=matching_watches,
|
||||
settings_application=datastore.data['settings']['application'],
|
||||
**template_args
|
||||
)
|
||||
|
||||
@@ -5,17 +5,17 @@ from wtforms import (
|
||||
validators,
|
||||
)
|
||||
from wtforms.fields.simple import BooleanField
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
from changedetectionio.processors.restock_diff.forms import processor_settings_form as restock_settings_form
|
||||
|
||||
class group_restock_settings_form(restock_settings_form):
|
||||
overrides_watch = BooleanField('Activate for individual watches in this tag/group?', default=False)
|
||||
overrides_watch = BooleanField(_l('Activate for individual watches in this tag/group?'), default=False)
|
||||
url_match_pattern = StringField(_l('Auto-apply to watches with URLs matching'),
|
||||
render_kw={"placeholder": _l("e.g. *://example.com/* or github.com/myorg")})
|
||||
tag_colour = StringField(_l('Tag colour'), default='')
|
||||
|
||||
class SingleTag(Form):
|
||||
|
||||
name = StringField('Tag name', [validators.InputRequired()], render_kw={"placeholder": "Name"})
|
||||
save_button = SubmitField('Save', render_kw={"class": "pure-button pure-button-primary"})
|
||||
|
||||
|
||||
|
||||
|
||||
name = StringField(_l('Tag name'), [validators.InputRequired()], render_kw={"placeholder": _l("Name")})
|
||||
save_button = SubmitField(_l('Save'), render_kw={"class": "pure-button pure-button-primary"})
|
||||
|
||||
@@ -43,6 +43,46 @@
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.title, placeholder="https://...", required=true, class="m-d") }}
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.url_match_pattern, class="m-d") }}
|
||||
<span class="pure-form-message-inline">{{ _('Automatically applies this tag to any watch whose URL matches. Supports wildcards: <code>*example.com*</code> or plain substring: <code>github.com/myorg</code>')|safe }}</span>
|
||||
</div>
|
||||
{% if matching_watches %}
|
||||
<div class="pure-control-group">
|
||||
<label>{{ _('Currently matching watches') }} ({{ matching_watches|length }})</label>
|
||||
<ul class="tag-url-match-list">
|
||||
{% for w_uuid, w in matching_watches.items() %}
|
||||
<li><a href="{{ url_for('ui.ui_edit.edit_page', uuid=w_uuid) }}">{{ w.label }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="pure-control-group">
|
||||
<label>{{ _('Tag colour') }}</label>
|
||||
<div style="display:flex; align-items:center; gap:0.75em;">
|
||||
<input type="checkbox" id="use_custom_colour"
|
||||
{% if data.get('tag_colour') %}checked{% endif %}>
|
||||
<label for="use_custom_colour" style="margin:0">{{ _('Custom colour') }}</label>
|
||||
<input type="color" id="tag_colour_picker"
|
||||
value="{{ data.get('tag_colour') or '#4f8ef7' }}"
|
||||
{% if not data.get('tag_colour') %}disabled{% endif %}>
|
||||
<input type="hidden" name="tag_colour" id="tag_colour_hidden"
|
||||
value="{{ data.get('tag_colour', '') }}">
|
||||
</div>
|
||||
<span class="pure-form-message-inline">{{ _('Leave unchecked to use the auto-generated colour based on the tag name.') }}</span>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var cb = document.getElementById('use_custom_colour');
|
||||
var picker = document.getElementById('tag_colour_picker');
|
||||
var hidden = document.getElementById('tag_colour_hidden');
|
||||
picker.addEventListener('input', function () { hidden.value = this.value; });
|
||||
cb.addEventListener('change', function () {
|
||||
picker.disabled = !this.checked;
|
||||
hidden.value = this.checked ? picker.value : '';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
{% from '_helpers.html' import render_simple_field, render_field %}
|
||||
<script src="{{url_for('static_content', group='js', filename='jquery-3.6.0.min.js')}}"></script>
|
||||
<script src="{{url_for('static_content', group='js', filename='modal.js')}}"></script>
|
||||
<style>
|
||||
{%- for uuid, tag in available_tags -%}
|
||||
{%- if tag and tag.title -%}
|
||||
{%- set class_name = tag.title|sanitize_tag_class -%}
|
||||
{%- if tag.get('tag_colour') -%}
|
||||
.watch-tag-list.tag-{{ class_name }} { background-color: {{ tag.tag_colour }}; color: {{ wcag_text_color(tag.tag_colour) }}; }
|
||||
{%- else -%}
|
||||
{%- set colors = generate_tag_colors(tag.title) -%}
|
||||
.watch-tag-list.tag-{{ class_name }} {
|
||||
background-color: {{ colors['light']['bg'] }};
|
||||
color: {{ colors['light']['color'] }};
|
||||
}
|
||||
html[data-darkmode="true"] .watch-tag-list.tag-{{ class_name }} {
|
||||
background-color: {{ colors['dark']['bg'] }};
|
||||
color: {{ colors['dark']['color'] }};
|
||||
}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
</style>
|
||||
|
||||
<div class="box">
|
||||
<form class="pure-form" action="{{ url_for('tags.form_tag_add') }}" method="POST" id="new-watch-form">
|
||||
@@ -45,10 +65,10 @@
|
||||
{% for uuid, tag in available_tags %}
|
||||
<tr id="{{ uuid }}" class="{{ loop.cycle('pure-table-odd', 'pure-table-even') }}">
|
||||
<td class="watch-controls">
|
||||
<a class="link-mute state-{{'on' if tag.notification_muted else 'off'}}" href="{{url_for('tags.mute', uuid=tag.uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="Mute notifications" title="Mute notifications" class="icon icon-mute" ></a>
|
||||
<a class="link-mute state-{{'on' if tag.notification_muted else 'off'}}" href="{{url_for('tags.mute', uuid=tag.uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="{{ _('Mute notifications') }}" title="{{ _('Mute notifications') }}" class="icon icon-mute" ></a>
|
||||
</td>
|
||||
<td>{{ "{:,}".format(tag_count[uuid]) if uuid in tag_count else 0 }}</td>
|
||||
<td class="title-col inline"> <a href="{{url_for('watchlist.index', tag=uuid) }}">{{ tag.title }}</a></td>
|
||||
<td class="title-col inline"> <a href="{{url_for('watchlist.index', tag=uuid) }}" class="watch-tag-list tag-{{ tag.title|sanitize_tag_class }}">{{ tag.title }}</a></td>
|
||||
<td>
|
||||
<a class="pure-button pure-button-primary" href="{{ url_for('tags.form_tag_edit', uuid=uuid) }}">{{ _('Edit') }}</a>
|
||||
<a href="{{ url_for('ui.form_watch_checknow', tag=uuid) }}" class="pure-button pure-button-primary" >{{ _('Recheck') }}</a>
|
||||
|
||||
@@ -142,7 +142,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
||||
for p in datastore.extra_browsers:
|
||||
form.fetch_backend.choices.append(p)
|
||||
|
||||
form.fetch_backend.choices.append(("system", 'System settings default'))
|
||||
form.fetch_backend.choices.append(("system", gettext('System settings default')))
|
||||
|
||||
# form.browser_steps[0] can be assumed that we 'goto url' first
|
||||
|
||||
@@ -150,7 +150,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
||||
# @todo - Couldn't get setattr() etc dynamic addition working, so remove it instead
|
||||
del form.proxy
|
||||
else:
|
||||
form.proxy.choices = [('', 'Default')]
|
||||
form.proxy.choices = [('', gettext('Default'))]
|
||||
for p in datastore.proxy_list:
|
||||
form.proxy.choices.append(tuple((p, datastore.proxy_list[p]['label'])))
|
||||
|
||||
@@ -301,7 +301,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
||||
'extra_classes': ' '.join(c),
|
||||
'extra_notification_token_placeholder_info': datastore.get_unique_notification_token_placeholders_available(),
|
||||
'extra_processor_config': form.extra_tab_content(),
|
||||
'extra_title': f" - Edit - {watch.label}",
|
||||
'extra_title': f" - {gettext('Edit')} - {watch.label}",
|
||||
'form': form,
|
||||
'has_default_notification_urls': True if len(datastore.data['settings']['application']['notification_urls']) else False,
|
||||
'has_extra_headers_file': len(datastore.get_all_headers_in_textfile_for_watch(uuid=uuid)) > 0,
|
||||
@@ -320,7 +320,12 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
||||
'using_global_webdriver_wait': not default['webdriver_delay'],
|
||||
'uuid': uuid,
|
||||
'watch': watch,
|
||||
'capabilities': capabilities
|
||||
'capabilities': capabilities,
|
||||
'auto_applied_tags': {
|
||||
tag_uuid: tag
|
||||
for tag_uuid, tag in datastore.data['settings']['application']['tags'].items()
|
||||
if tag_uuid not in watch.get('tags', []) and tag.matches_url(watch.get('url', ''))
|
||||
},
|
||||
}
|
||||
|
||||
included_content = None
|
||||
|
||||
@@ -107,7 +107,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
current_diff_url=watch['url'],
|
||||
current_version=timestamp,
|
||||
extra_stylesheets=extra_stylesheets,
|
||||
extra_title=f" - Diff - {watch.label} @ {timestamp}",
|
||||
extra_title=f" - {gettext('Diff')} - {watch.label} @ {timestamp}",
|
||||
highlight_ignored_line_numbers=ignored_line_numbers,
|
||||
highlight_triggered_line_numbers=triggered_line_numbers,
|
||||
highlight_blocked_line_numbers=blocked_line_numbers,
|
||||
|
||||
@@ -81,6 +81,14 @@
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.tags) }}
|
||||
<span class="pure-form-message-inline">{{ _('Organisational tag/group name used in the main listing page') }}</span>
|
||||
{% if auto_applied_tags %}
|
||||
<span class="pure-form-message-inline">
|
||||
{{ _('Also automatically applied by URL pattern:') }}
|
||||
{% for tag_uuid, tag in auto_applied_tags.items() %}
|
||||
<a href="{{ url_for('tags.form_tag_edit', uuid=tag_uuid) }}" class="watch-tag-list tag-{{ tag.title|sanitize_tag_class }}">{{ tag.title }}</a>
|
||||
{% endfor %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pure-control-group inline-radio">
|
||||
{{ render_field(form.processor) }}
|
||||
|
||||
@@ -92,6 +92,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe
|
||||
extra_classes='has-queue' if not update_q.empty() else '',
|
||||
form=form,
|
||||
generate_tag_colors=processors.generate_processor_badge_colors,
|
||||
wcag_text_color=processors.wcag_text_color,
|
||||
guid=datastore.data['app_guid'],
|
||||
has_proxies=proxy_list,
|
||||
hosted_sticky=os.getenv("SALTED_PASS", False) == False,
|
||||
|
||||
@@ -71,6 +71,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
{%- for uuid, tag in tags -%}
|
||||
{%- if tag and tag.title -%}
|
||||
{%- set class_name = tag.title|sanitize_tag_class -%}
|
||||
{%- if tag.get('tag_colour') -%}
|
||||
.button-tag.tag-{{ class_name }},
|
||||
.watch-tag-list.tag-{{ class_name }} {
|
||||
background-color: {{ tag.tag_colour }};
|
||||
color: {{ wcag_text_color(tag.tag_colour) }};
|
||||
}
|
||||
{%- else -%}
|
||||
{%- set colors = generate_tag_colors(tag.title) -%}
|
||||
.button-tag.tag-{{ class_name }} {
|
||||
background-color: {{ colors['light']['bg'] }};
|
||||
@@ -92,6 +99,7 @@ html[data-darkmode="true"] .watch-tag-list.tag-{{ class_name }} {
|
||||
color: {{ colors['dark']['color'] }};
|
||||
}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
</style>
|
||||
<div class="box" id="form-quick-watch-add">
|
||||
@@ -237,10 +245,10 @@ html[data-darkmode="true"] .watch-tag-list.tag-{{ class_name }} {
|
||||
<td class="inline checkbox-uuid" ><div><input name="uuids" type="checkbox" value="{{ watch.uuid}} " > <span class="counter-i">{{ loop.index+pagination.skip }}</span></div></td>
|
||||
<td class="inline watch-controls">
|
||||
<div>
|
||||
<a class="ajax-op state-off pause-toggle" data-op="pause" href="{{url_for('watchlist.index', op='pause', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='pause.svg')}}" alt="Pause checks" title="Pause checks" class="icon icon-pause" ></a>
|
||||
<a class="ajax-op state-on pause-toggle" data-op="pause" style="display: none" href="{{url_for('watchlist.index', op='pause', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='play.svg')}}" alt="UnPause checks" title="UnPause checks" class="icon icon-unpause" ></a>
|
||||
<a class="ajax-op state-off mute-toggle" data-op="mute" href="{{url_for('watchlist.index', op='mute', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="Mute notification" title="Mute notification" class="icon icon-mute" ></a>
|
||||
<a class="ajax-op state-on mute-toggle" data-op="mute" style="display: none" href="{{url_for('watchlist.index', op='mute', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="UnMute notification" title="UnMute notification" class="icon icon-mute" ></a>
|
||||
<a class="ajax-op state-off pause-toggle" data-op="pause" href="{{url_for('watchlist.index', op='pause', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='pause.svg')}}" alt="{{ _('Pause checks') }}" title="{{ _('Pause checks') }}" class="icon icon-pause" ></a>
|
||||
<a class="ajax-op state-on pause-toggle" data-op="pause" style="display: none" href="{{url_for('watchlist.index', op='pause', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='play.svg')}}" alt="{{ _('UnPause checks') }}" title="{{ _('UnPause checks') }}" class="icon icon-unpause" ></a>
|
||||
<a class="ajax-op state-off mute-toggle" data-op="mute" href="{{url_for('watchlist.index', op='mute', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="{{ _('Mute notification') }}" title="{{ _('Mute notification') }}" class="icon icon-mute" ></a>
|
||||
<a class="ajax-op state-on mute-toggle" data-op="mute" style="display: none" href="{{url_for('watchlist.index', op='mute', uuid=watch.uuid, tag=active_tag_uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="{{ _('UnMute notification') }}" title="{{ _('UnMute notification') }}" class="icon icon-mute" ></a>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -284,7 +292,7 @@ html[data-darkmode="true"] .watch-tag-list.tag-{{ class_name }} {
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
<div class="status-icons">
|
||||
<a class="link-spread" href="{{url_for('ui.form_share_put_watch', uuid=watch.uuid)}}"><img src="{{url_for('static_content', group='images', filename='spread.svg')}}" class="status-icon icon icon-spread" title="Create a link to share watch config with others" ></a>
|
||||
<a class="link-spread" href="{{url_for('ui.form_share_put_watch', uuid=watch.uuid)}}"><img src="{{url_for('static_content', group='images', filename='spread.svg')}}" class="status-icon icon icon-spread" title="{{ _('Create a link to share watch config with others') }}" ></a>
|
||||
{%- set effective_fetcher = watch.get_fetch_backend if watch.get_fetch_backend != "system" else system_default_fetcher -%}
|
||||
{%- if effective_fetcher and ("html_webdriver" in effective_fetcher or "html_" in effective_fetcher or "extra_browser_" in effective_fetcher) -%}
|
||||
{{ effective_fetcher|fetcher_status_icons }}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from json_logic.builtins import BUILTINS
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
from .exceptions import EmptyConditionRuleRowNotUsable
|
||||
from .pluggy_interface import plugin_manager # Import the pluggy plugin manager
|
||||
@@ -6,19 +7,19 @@ from . import default_plugin
|
||||
from loguru import logger
|
||||
# List of all supported JSON Logic operators
|
||||
operator_choices = [
|
||||
(None, "Choose one - Operator"),
|
||||
(">", "Greater Than"),
|
||||
("<", "Less Than"),
|
||||
(">=", "Greater Than or Equal To"),
|
||||
("<=", "Less Than or Equal To"),
|
||||
("==", "Equals"),
|
||||
("!=", "Not Equals"),
|
||||
("in", "Contains"),
|
||||
(None, _l("Choose one - Operator")),
|
||||
(">", _l("Greater Than")),
|
||||
("<", _l("Less Than")),
|
||||
(">=", _l("Greater Than or Equal To")),
|
||||
("<=", _l("Less Than or Equal To")),
|
||||
("==", _l("Equals")),
|
||||
("!=", _l("Not Equals")),
|
||||
("in", _l("Contains")),
|
||||
]
|
||||
|
||||
# Fields available in the rules
|
||||
field_choices = [
|
||||
(None, "Choose one - Field"),
|
||||
(None, _l("Choose one - Field")),
|
||||
]
|
||||
|
||||
# The data we will feed the JSON Rules to see if it passes the test/conditions or not
|
||||
|
||||
@@ -3,6 +3,7 @@ import re
|
||||
import pluggy
|
||||
from price_parser import Price
|
||||
from loguru import logger
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
hookimpl = pluggy.HookimplMarker("changedetectionio_conditions")
|
||||
|
||||
@@ -47,22 +48,22 @@ def register_operators():
|
||||
@hookimpl
|
||||
def register_operator_choices():
|
||||
return [
|
||||
("!in", "Does NOT Contain"),
|
||||
("starts_with", "Text Starts With"),
|
||||
("ends_with", "Text Ends With"),
|
||||
("length_min", "Length minimum"),
|
||||
("length_max", "Length maximum"),
|
||||
("contains_regex", "Text Matches Regex"),
|
||||
("!contains_regex", "Text Does NOT Match Regex"),
|
||||
("!in", _l("Does NOT Contain")),
|
||||
("starts_with", _l("Text Starts With")),
|
||||
("ends_with", _l("Text Ends With")),
|
||||
("length_min", _l("Length minimum")),
|
||||
("length_max", _l("Length maximum")),
|
||||
("contains_regex", _l("Text Matches Regex")),
|
||||
("!contains_regex", _l("Text Does NOT Match Regex")),
|
||||
]
|
||||
|
||||
@hookimpl
|
||||
def register_field_choices():
|
||||
return [
|
||||
("extracted_number", "Extracted number after 'Filters & Triggers'"),
|
||||
("extracted_number", _l("Extracted number after 'Filters & Triggers'")),
|
||||
# ("meta_description", "Meta Description"),
|
||||
# ("meta_keywords", "Meta Keywords"),
|
||||
("page_filtered_text", "Page text after 'Filters & Triggers'"),
|
||||
("page_filtered_text", _l("Page text after 'Filters & Triggers'")),
|
||||
#("page_title", "Page <title>"), # actual page title <title>
|
||||
]
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Condition Rule Form (for each rule row)
|
||||
from wtforms import Form, SelectField, StringField, validators
|
||||
from wtforms import validators
|
||||
from flask_babel import lazy_gettext as _l
|
||||
|
||||
class ConditionFormRow(Form):
|
||||
|
||||
@@ -8,18 +9,18 @@ class ConditionFormRow(Form):
|
||||
from changedetectionio.conditions import plugin_manager
|
||||
from changedetectionio.conditions import operator_choices, field_choices
|
||||
field = SelectField(
|
||||
"Field",
|
||||
_l("Field"),
|
||||
choices=field_choices,
|
||||
validators=[validators.Optional()]
|
||||
)
|
||||
|
||||
operator = SelectField(
|
||||
"Operator",
|
||||
_l("Operator"),
|
||||
choices=operator_choices,
|
||||
validators=[validators.Optional()]
|
||||
)
|
||||
|
||||
value = StringField("Value", validators=[validators.Optional()], render_kw={"placeholder": "A value"})
|
||||
value = StringField(_l("Value"), validators=[validators.Optional()], render_kw={"placeholder": _l("A value")})
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
# First, run the default validators
|
||||
@@ -30,15 +31,15 @@ class ConditionFormRow(Form):
|
||||
# If any of the operator/field/value is set, then they must be all set
|
||||
if any(value not in ("", False, "None", None) for value in [self.operator.data, self.field.data, self.value.data]):
|
||||
if not self.operator.data or self.operator.data == 'None':
|
||||
self.operator.errors.append("Operator is required.")
|
||||
self.operator.errors.append(_l("Operator is required."))
|
||||
return False
|
||||
|
||||
if not self.field.data or self.field.data == 'None':
|
||||
self.field.errors.append("Field is required.")
|
||||
self.field.errors.append(_l("Field is required."))
|
||||
return False
|
||||
|
||||
if not self.value.data:
|
||||
self.value.errors.append("Value is required.")
|
||||
self.value.errors.append(_l("Value is required."))
|
||||
return False
|
||||
|
||||
return True # Only return True if all conditions pass
|
||||
@@ -4,6 +4,7 @@ Provides metrics for measuring text similarity between snapshots.
|
||||
"""
|
||||
import pluggy
|
||||
from loguru import logger
|
||||
from flask_babel import gettext as _, lazy_gettext as _l
|
||||
|
||||
LEVENSHTEIN_MAX_LEN_FOR_EDIT_STATS=100000
|
||||
|
||||
@@ -53,8 +54,8 @@ def register_operator_choices():
|
||||
@conditions_hookimpl
|
||||
def register_field_choices():
|
||||
return [
|
||||
("levenshtein_ratio", "Levenshtein - Text similarity ratio"),
|
||||
("levenshtein_distance", "Levenshtein - Text change distance"),
|
||||
("levenshtein_ratio", _l("Levenshtein - Text similarity ratio")),
|
||||
("levenshtein_distance", _l("Levenshtein - Text change distance")),
|
||||
]
|
||||
|
||||
@conditions_hookimpl
|
||||
@@ -77,7 +78,7 @@ def ui_edit_stats_extras(watch):
|
||||
"""Add Levenshtein stats to the UI using the global plugin system"""
|
||||
"""Generate the HTML for Levenshtein stats - shared by both plugin systems"""
|
||||
if len(watch.history.keys()) < 2:
|
||||
return "<p>Not enough history to calculate Levenshtein metrics</p>"
|
||||
return f"<p>{_('Not enough history to calculate Levenshtein metrics')}</p>"
|
||||
|
||||
|
||||
# Protection against the algorithm getting stuck on huge documents
|
||||
@@ -87,37 +88,37 @@ def ui_edit_stats_extras(watch):
|
||||
for idx in (-1, -2)
|
||||
if len(k) >= abs(idx)
|
||||
):
|
||||
return "<p>Snapshot too large for edit statistics, skipping.</p>"
|
||||
return f"<p>{_('Snapshot too large for edit statistics, skipping.')}</p>"
|
||||
|
||||
try:
|
||||
lev_data = levenshtein_ratio_recent_history(watch)
|
||||
if not lev_data or not isinstance(lev_data, dict):
|
||||
return "<p>Unable to calculate Levenshtein metrics</p>"
|
||||
|
||||
return f"<p>{_('Unable to calculate Levenshtein metrics')}</p>"
|
||||
|
||||
html = f"""
|
||||
<div class="levenshtein-stats">
|
||||
<h4>Levenshtein Text Similarity Details</h4>
|
||||
<h4>{_('Levenshtein Text Similarity Details')}</h4>
|
||||
<table class="pure-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Raw distance (edits needed)</td>
|
||||
<td>{_('Raw distance (edits needed)')}</td>
|
||||
<td>{lev_data['distance']}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Similarity ratio</td>
|
||||
<td>{_('Similarity ratio')}</td>
|
||||
<td>{lev_data['ratio']:.4f}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Percent similar</td>
|
||||
<td>{_('Percent similar')}</td>
|
||||
<td>{lev_data['percent_similar']}%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="font-size: 80%;">Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one into the other.</p>
|
||||
<p style="font-size: 80%;">{_('Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one into the other.')}</p>
|
||||
</div>
|
||||
"""
|
||||
return html
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating Levenshtein UI extras: {str(e)}")
|
||||
return "<p>Error calculating Levenshtein metrics</p>"
|
||||
return f"<p>{_('Error calculating Levenshtein metrics')}</p>"
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ Provides word count metrics for snapshot content.
|
||||
"""
|
||||
import pluggy
|
||||
from loguru import logger
|
||||
from flask_babel import gettext as _, lazy_gettext as _l
|
||||
|
||||
# Support both plugin systems
|
||||
conditions_hookimpl = pluggy.HookimplMarker("changedetectionio_conditions")
|
||||
@@ -40,7 +41,7 @@ def register_operator_choices():
|
||||
def register_field_choices():
|
||||
# Add a field that will be available in conditions
|
||||
return [
|
||||
("word_count", "Word count of content"),
|
||||
("word_count", _l("Word count of content")),
|
||||
]
|
||||
|
||||
@conditions_hookimpl
|
||||
@@ -61,16 +62,16 @@ def _generate_stats_html(watch):
|
||||
|
||||
html = f"""
|
||||
<div class="word-count-stats">
|
||||
<h4>Content Analysis</h4>
|
||||
<h4>{_('Content Analysis')}</h4>
|
||||
<table class="pure-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Word count (latest snapshot)</td>
|
||||
<td>{_('Word count (latest snapshot)')}</td>
|
||||
<td>{word_count}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="font-size: 80%;">Word count is a simple measure of content length, calculated by splitting text on whitespace.</p>
|
||||
<p style="font-size: 80%;">{_('Word count is a simple measure of content length, calculated by splitting text on whitespace.')}</p>
|
||||
</div>
|
||||
"""
|
||||
return html
|
||||
|
||||
@@ -49,6 +49,9 @@ async def capture_full_page_async(page, screenshot_format='JPEG', watch_uuid=Non
|
||||
if page_height > page.viewport_size['height']:
|
||||
if page_height < step_size:
|
||||
step_size = page_height # Incase page is bigger than default viewport but smaller than proposed step size
|
||||
# Never set viewport taller than our max capture height - otherwise one screenshot chunk
|
||||
# captures the whole (e.g. 8098px) page even when SCREENSHOT_MAX_HEIGHT=1000
|
||||
step_size = min(step_size, SCREENSHOT_MAX_TOTAL_HEIGHT)
|
||||
viewport_start = time.time()
|
||||
logger.debug(f"{watch_info}Setting bigger viewport to step through large page width W{page.viewport_size['width']}xH{step_size} because page_height > viewport_size")
|
||||
# Set viewport to a larger size to capture more content at once
|
||||
|
||||
@@ -75,6 +75,9 @@ async def capture_full_page(page, screenshot_format='JPEG', watch_uuid=None, loc
|
||||
if page_height > page.viewport['height']:
|
||||
if page_height < step_size:
|
||||
step_size = page_height # Incase page is bigger than default viewport but smaller than proposed step size
|
||||
# Never set viewport taller than our max capture height - otherwise one screenshot chunk
|
||||
# captures the whole page even when SCREENSHOT_MAX_HEIGHT is set smaller
|
||||
step_size = min(step_size, SCREENSHOT_MAX_TOTAL_HEIGHT)
|
||||
viewport_start = time.time()
|
||||
await page.setViewport({'width': page.viewport['width'], 'height': step_size})
|
||||
viewport_time = time.time() - viewport_start
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from flask_babel import lazy_gettext as _l
|
||||
from loguru import logger
|
||||
from urllib.parse import urljoin, urlparse
|
||||
import hashlib
|
||||
@@ -13,7 +14,7 @@ from changedetectionio.validate_url import is_private_hostname
|
||||
|
||||
# "html_requests" is listed as the default fetcher in store.py!
|
||||
class fetcher(Fetcher):
|
||||
fetcher_description = "Basic fast Plaintext/HTTP Client"
|
||||
fetcher_description = _l("Basic fast Plaintext/HTTP Client")
|
||||
|
||||
def __init__(self, proxy_override=None, custom_browser_connection_url=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@@ -38,26 +38,39 @@
|
||||
if (a.size !== b.size) {
|
||||
return b.size - a.size;
|
||||
}
|
||||
|
||||
|
||||
// Second priority: apple-touch-icon over regular icon
|
||||
const isAppleA = /apple-touch-icon/.test(a.rel);
|
||||
const isAppleB = /apple-touch-icon/.test(b.rel);
|
||||
if (isAppleA && !isAppleB) return -1;
|
||||
if (!isAppleA && isAppleB) return 1;
|
||||
|
||||
|
||||
// Third priority: icons with no size attribute (fallback icons) last
|
||||
const hasNoSizeA = !a.hasSizes;
|
||||
const hasNoSizeB = !b.hasSizes;
|
||||
if (hasNoSizeA && !hasNoSizeB) return 1;
|
||||
if (!hasNoSizeA && hasNoSizeB) return -1;
|
||||
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
const timeoutMs = 2000;
|
||||
// 1 MB — matches the server-side limit in bump_favicon()
|
||||
const MAX_BYTES = 1 * 1024 * 1024;
|
||||
|
||||
for (const icon of icons) {
|
||||
try {
|
||||
// Inline data URI — no network fetch needed, data is already here
|
||||
if (icon.href.startsWith('data:')) {
|
||||
const match = icon.href.match(/^data:([^;]+);base64,([A-Za-z0-9+/=]+)$/);
|
||||
if (!match) continue;
|
||||
const mime_type = match[1];
|
||||
const base64 = match[2];
|
||||
// Rough size check: base64 is ~4/3 the binary size
|
||||
if (base64.length * 0.75 > MAX_BYTES) continue;
|
||||
return { url: icon.href, mime_type, base64 };
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||
|
||||
@@ -74,12 +87,15 @@
|
||||
|
||||
const blob = await resp.blob();
|
||||
|
||||
if (blob.size > MAX_BYTES) continue;
|
||||
|
||||
// Convert blob to base64
|
||||
const reader = new FileReader();
|
||||
return await new Promise(resolve => {
|
||||
reader.onloadend = () => {
|
||||
resolve({
|
||||
url: icon.href,
|
||||
mime_type: blob.type,
|
||||
base64: reader.result.split(",")[1]
|
||||
});
|
||||
};
|
||||
@@ -98,4 +114,3 @@
|
||||
// Auto-execute and return result for page.evaluate()
|
||||
return await window.getFaviconAsBlob();
|
||||
})();
|
||||
|
||||
|
||||
@@ -56,6 +56,10 @@ def stitch_images_worker_raw_bytes(pipe_conn, original_page_height, capture_heig
|
||||
im.close()
|
||||
del images
|
||||
|
||||
# Clip stitched image to capture_height (chunks may overshoot by up to step_size-1 px)
|
||||
if total_height > capture_height:
|
||||
stitched = stitched.crop((0, 0, max_width, capture_height))
|
||||
|
||||
# Draw caption only if page was trimmed
|
||||
if original_page_height > capture_height:
|
||||
draw = ImageDraw.Draw(stitched)
|
||||
|
||||
@@ -104,15 +104,17 @@ class fetcher(Fetcher):
|
||||
|
||||
from selenium.webdriver.remote.remote_connection import RemoteConnection
|
||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
||||
from selenium.webdriver.remote.client_config import ClientConfig
|
||||
from urllib3.util import Timeout
|
||||
driver = None
|
||||
try:
|
||||
# Create the RemoteConnection and set timeout (e.g., 30 seconds)
|
||||
remote_connection = RemoteConnection(
|
||||
self.browser_connection_url,
|
||||
connection_timeout = int(os.getenv("WEBDRIVER_CONNECTION_TIMEOUT", 90))
|
||||
client_config = ClientConfig(
|
||||
remote_server_addr=self.browser_connection_url,
|
||||
timeout=Timeout(connect=connection_timeout, total=connection_timeout)
|
||||
)
|
||||
remote_connection.set_timeout(30) # seconds
|
||||
remote_connection = RemoteConnection(client_config=client_config)
|
||||
|
||||
# Now create the driver with the RemoteConnection
|
||||
driver = RemoteWebDriver(
|
||||
command_executor=remote_connection,
|
||||
options=options
|
||||
|
||||
@@ -45,8 +45,38 @@ CHANGED_INTO_PLACEMARKER_CLOSED = '@changed_into_PLACEMARKER_CLOSED'
|
||||
# Compiled regex patterns for performance
|
||||
WHITESPACE_NORMALIZE_RE = re.compile(r'\s+')
|
||||
|
||||
# Regexes built from the constants above — no brittle hardcoded strings
|
||||
_EXTRACT_REMOVED_RE = re.compile(
|
||||
re.escape(REMOVED_PLACEMARKER_OPEN) + r'(.*?)' + re.escape(REMOVED_PLACEMARKER_CLOSED)
|
||||
+ r'|' +
|
||||
re.escape(CHANGED_PLACEMARKER_OPEN) + r'(.*?)' + re.escape(CHANGED_PLACEMARKER_CLOSED)
|
||||
)
|
||||
_EXTRACT_ADDED_RE = re.compile(
|
||||
re.escape(ADDED_PLACEMARKER_OPEN) + r'(.*?)' + re.escape(ADDED_PLACEMARKER_CLOSED)
|
||||
+ r'|' +
|
||||
re.escape(CHANGED_INTO_PLACEMARKER_OPEN) + r'(.*?)' + re.escape(CHANGED_INTO_PLACEMARKER_CLOSED)
|
||||
)
|
||||
|
||||
def render_inline_word_diff(before_line: str, after_line: str, ignore_junk: bool = False, markdown_style: str = None, tokenizer: str = 'words_and_html') -> tuple[str, bool]:
|
||||
|
||||
def extract_changed_from(raw_diff: str) -> str:
|
||||
"""Extract only the removed/changed-from fragments from a raw diff string.
|
||||
|
||||
Useful for {{diff_changed_from}} — gives just the old value (e.g. old price),
|
||||
not the full surrounding line. Multiple fragments joined with newlines.
|
||||
"""
|
||||
return '\n'.join(m.group(1) or m.group(2) for m in _EXTRACT_REMOVED_RE.finditer(raw_diff))
|
||||
|
||||
|
||||
def extract_changed_to(raw_diff: str) -> str:
|
||||
"""Extract only the added/changed-into fragments from a raw diff string.
|
||||
|
||||
Useful for {{diff_changed_to}} — gives just the new value (e.g. new price),
|
||||
not the full surrounding line. Multiple fragments joined with newlines.
|
||||
"""
|
||||
return '\n'.join(m.group(1) or m.group(2) for m in _EXTRACT_ADDED_RE.finditer(raw_diff))
|
||||
|
||||
|
||||
def render_inline_word_diff(before_line: str, after_line: str, ignore_junk: bool = False, markdown_style: str = None, tokenizer: str = 'words_and_html', include_change_type_prefix: bool = True) -> tuple[str, bool]:
|
||||
"""
|
||||
Render word-level differences between two lines inline using diff-match-patch library.
|
||||
|
||||
@@ -133,14 +163,20 @@ def render_inline_word_diff(before_line: str, after_line: str, ignore_junk: bool
|
||||
if removed_tokens:
|
||||
removed_full = ''.join(removed_tokens).rstrip()
|
||||
trailing_removed = ''.join(removed_tokens)[len(removed_full):] if len(''.join(removed_tokens)) > len(removed_full) else ''
|
||||
result_parts.append(f'{CHANGED_PLACEMARKER_OPEN}{removed_full}{CHANGED_PLACEMARKER_CLOSED}{trailing_removed}')
|
||||
if include_change_type_prefix:
|
||||
result_parts.append(f'{CHANGED_PLACEMARKER_OPEN}{removed_full}{CHANGED_PLACEMARKER_CLOSED}{trailing_removed}')
|
||||
else:
|
||||
result_parts.append(f'{removed_full}{trailing_removed}')
|
||||
|
||||
if added_tokens:
|
||||
if result_parts: # Add newline between removed and added
|
||||
result_parts.append('\n')
|
||||
added_full = ''.join(added_tokens).rstrip()
|
||||
trailing_added = ''.join(added_tokens)[len(added_full):] if len(''.join(added_tokens)) > len(added_full) else ''
|
||||
result_parts.append(f'{CHANGED_INTO_PLACEMARKER_OPEN}{added_full}{CHANGED_INTO_PLACEMARKER_CLOSED}{trailing_added}')
|
||||
if include_change_type_prefix:
|
||||
result_parts.append(f'{CHANGED_INTO_PLACEMARKER_OPEN}{added_full}{CHANGED_INTO_PLACEMARKER_CLOSED}{trailing_added}')
|
||||
else:
|
||||
result_parts.append(f'{added_full}{trailing_added}')
|
||||
|
||||
return ''.join(result_parts), has_changes
|
||||
else:
|
||||
@@ -150,21 +186,27 @@ def render_inline_word_diff(before_line: str, after_line: str, ignore_junk: bool
|
||||
if op == 0: # Equal
|
||||
result_parts.append(text)
|
||||
elif op == 1: # Insertion
|
||||
# Don't wrap empty content (e.g., whitespace-only tokens after rstrip)
|
||||
content = text.rstrip()
|
||||
trailing = text[len(content):] if len(text) > len(content) else ''
|
||||
if content:
|
||||
result_parts.append(f'{ADDED_PLACEMARKER_OPEN}{content}{ADDED_PLACEMARKER_CLOSED}{trailing}')
|
||||
if not include_change_type_prefix:
|
||||
result_parts.append(text)
|
||||
else:
|
||||
result_parts.append(trailing)
|
||||
# Don't wrap empty content (e.g., whitespace-only tokens after rstrip)
|
||||
content = text.rstrip()
|
||||
trailing = text[len(content):] if len(text) > len(content) else ''
|
||||
if content:
|
||||
result_parts.append(f'{ADDED_PLACEMARKER_OPEN}{content}{ADDED_PLACEMARKER_CLOSED}{trailing}')
|
||||
else:
|
||||
result_parts.append(trailing)
|
||||
elif op == -1: # Deletion
|
||||
# Don't wrap empty content (e.g., whitespace-only tokens after rstrip)
|
||||
content = text.rstrip()
|
||||
trailing = text[len(content):] if len(text) > len(content) else ''
|
||||
if content:
|
||||
result_parts.append(f'{REMOVED_PLACEMARKER_OPEN}{content}{REMOVED_PLACEMARKER_CLOSED}{trailing}')
|
||||
if not include_change_type_prefix:
|
||||
result_parts.append(text)
|
||||
else:
|
||||
result_parts.append(trailing)
|
||||
# Don't wrap empty content (e.g., whitespace-only tokens after rstrip)
|
||||
content = text.rstrip()
|
||||
trailing = text[len(content):] if len(text) > len(content) else ''
|
||||
if content:
|
||||
result_parts.append(f'{REMOVED_PLACEMARKER_OPEN}{content}{REMOVED_PLACEMARKER_CLOSED}{trailing}')
|
||||
else:
|
||||
result_parts.append(trailing)
|
||||
|
||||
return ''.join(result_parts), has_changes
|
||||
|
||||
@@ -360,7 +402,7 @@ def customSequenceMatcher(
|
||||
|
||||
# Use inline word-level diff for single line replacements when word_diff is enabled
|
||||
if word_diff and len(before_lines) == 1 and len(after_lines) == 1:
|
||||
inline_diff, has_changes = render_inline_word_diff(before_lines[0], after_lines[0], ignore_junk=ignore_junk, tokenizer=tokenizer)
|
||||
inline_diff, has_changes = render_inline_word_diff(before_lines[0], after_lines[0], ignore_junk=ignore_junk, tokenizer=tokenizer, include_change_type_prefix=include_change_type_prefix)
|
||||
# Check if there are any actual changes (not just whitespace when ignore_junk is enabled)
|
||||
if ignore_junk and not has_changes:
|
||||
# No real changes, skip this line
|
||||
|
||||
@@ -212,6 +212,11 @@ def _is_safe_valid_url(test_url):
|
||||
from .validate_url import is_safe_valid_url
|
||||
return is_safe_valid_url(test_url)
|
||||
|
||||
@app.template_global('get_html_head_extras')
|
||||
def _get_html_head_extras():
|
||||
from .pluggy_interface import collect_html_head_extras
|
||||
return collect_html_head_extras()
|
||||
|
||||
|
||||
@app.template_filter('format_number_locale')
|
||||
def _jinja2_filter_format_number_locale(value: float) -> str:
|
||||
|
||||
@@ -725,7 +725,7 @@ class ValidateStartsWithRegex(object):
|
||||
raise ValidationError(self.message or _l("Invalid value."))
|
||||
|
||||
class quickWatchForm(Form):
|
||||
url = fields.URLField(_l('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)
|
||||
@@ -771,16 +771,16 @@ class SingleBrowserStep(Form):
|
||||
operation = SelectField(_l('Operation'), [validators.Optional()], choices=browser_step_ui_config.keys())
|
||||
|
||||
# maybe better to set some <script>var..
|
||||
selector = StringField(_l('Selector'), [validators.Optional()], render_kw={"placeholder": "CSS or xPath selector"})
|
||||
optional_value = StringField(_l('value'), [validators.Optional()], render_kw={"placeholder": "Value"})
|
||||
selector = StringField(_l('Selector'), [validators.Optional()], render_kw={"placeholder": _l("CSS or xPath selector")})
|
||||
optional_value = StringField(_l('value'), [validators.Optional()], render_kw={"placeholder": _l("Value")})
|
||||
# @todo move to JS? ajax fetch new field?
|
||||
# remove_button = SubmitField(_l('-'), render_kw={"type": "button", "class": "pure-button pure-button-primary", 'title': 'Remove'})
|
||||
# add_button = SubmitField(_l('+'), render_kw={"type": "button", "class": "pure-button pure-button-primary", 'title': 'Add new step after'})
|
||||
|
||||
class processor_text_json_diff_form(commonSettingsForm):
|
||||
|
||||
url = fields.URLField('Web Page URL', validators=[validateURL()])
|
||||
tags = StringTagUUID('Group Tag', [validators.Optional()], default='')
|
||||
url = StringField(_l('Web Page URL'), validators=[validateURL()])
|
||||
tags = StringTagUUID(_l('Group Tag'), [validators.Optional()], default='')
|
||||
|
||||
time_between_check = EnhancedFormField(
|
||||
TimeBetweenCheckForm,
|
||||
@@ -798,6 +798,7 @@ class processor_text_json_diff_form(commonSettingsForm):
|
||||
|
||||
subtractive_selectors = StringListField(_l('Remove elements'), [ValidateCSSJSONXPATHInput(allow_json=False)])
|
||||
|
||||
extract_lines_containing = StringListField(_l('Extract lines containing'), [validators.Optional()])
|
||||
extract_text = StringListField(_l('Extract text'), [ValidateListRegex()])
|
||||
|
||||
title = StringField(_l('Title'), default='')
|
||||
@@ -917,7 +918,7 @@ class processor_text_json_diff_form(commonSettingsForm):
|
||||
|
||||
class SingleExtraProxy(Form):
|
||||
# maybe better to set some <script>var..
|
||||
proxy_name = StringField(_l('Name'), [validators.Optional()], render_kw={"placeholder": "Name"})
|
||||
proxy_name = StringField(_l('Name'), [validators.Optional()], render_kw={"placeholder": _l("Name")})
|
||||
proxy_url = StringField(_l('Proxy URL'), [
|
||||
validators.Optional(),
|
||||
ValidateStartsWithRegex(
|
||||
@@ -929,7 +930,7 @@ class SingleExtraProxy(Form):
|
||||
], render_kw={"placeholder": "socks5:// or regular proxy http://user:pass@...:3128", "size":50})
|
||||
|
||||
class SingleExtraBrowser(Form):
|
||||
browser_name = StringField(_l('Name'), [validators.Optional()], render_kw={"placeholder": "Name"})
|
||||
browser_name = StringField(_l('Name'), [validators.Optional()], render_kw={"placeholder": _l("Name")})
|
||||
browser_connection_url = StringField(_l('Browser connection URL'), [
|
||||
validators.Optional(),
|
||||
ValidateStartsWithRegex(
|
||||
@@ -998,7 +999,7 @@ class globalSettingsApplicationForm(commonSettingsForm):
|
||||
|
||||
# Screenshot comparison settings
|
||||
min_change_percentage = FloatField(
|
||||
'Screenshot: Minimum Change Percentage',
|
||||
_l('Screenshot: Minimum Change Percentage'),
|
||||
validators=[
|
||||
validators.Optional(),
|
||||
validators.NumberRange(min=0.0, max=100.0, message=_l('Must be between 0 and 100'))
|
||||
|
||||
@@ -28,18 +28,20 @@ def get_timeago_locale(flask_locale):
|
||||
str: timeago library locale code (e.g., 'en', 'zh_CN', 'pt_PT')
|
||||
"""
|
||||
locale_map = {
|
||||
'zh': 'zh_CN', # Chinese Simplified
|
||||
'zh': 'zh_CN', # Chinese Simplified
|
||||
# timeago library just hasn't been updated to use the more modern locale naming convention, before BCP 47 / RFC 5646.
|
||||
'zh_TW': 'zh_TW', # Chinese Traditional (timeago uses zh_TW)
|
||||
'zh_TW': 'zh_TW', # Chinese Traditional (timeago uses zh_TW)
|
||||
'zh_Hant_TW': 'zh_TW', # Flask-Babel normalizes zh_TW to zh_Hant_TW, map back to timeago's zh_TW
|
||||
'pt': 'pt_PT', # Portuguese (Portugal)
|
||||
'sv': 'sv_SE', # Swedish
|
||||
'no': 'nb_NO', # Norwegian Bokmål
|
||||
'hi': 'in_HI', # Hindi
|
||||
'cs': 'en', # Czech not supported by timeago, fallback to English
|
||||
'uk': 'uk', # Ukrainian
|
||||
'en_GB': 'en', # British English - timeago uses 'en'
|
||||
'en_US': 'en', # American English - timeago uses 'en'
|
||||
'pt': 'pt_PT', # Portuguese (Portugal)
|
||||
'pt_BR': 'pt_BR', # Portuguese (Brasil)
|
||||
'sv': 'sv_SE', # Swedish
|
||||
'no': 'nb_NO', # Norwegian Bokmål
|
||||
'hi': 'in_HI', # Hindi
|
||||
'cs': 'en', # Czech not supported by timeago, fallback to English
|
||||
'ja': 'ja', # Japanese
|
||||
'uk': 'uk', # Ukrainian
|
||||
'en_GB': 'en', # British English - timeago uses 'en'
|
||||
'en_US': 'en', # American English - timeago uses 'en'
|
||||
}
|
||||
return locale_map.get(flask_locale, flask_locale)
|
||||
|
||||
@@ -53,7 +55,8 @@ LANGUAGE_DATA = {
|
||||
'ko': {'flag': 'fi fi-kr fis', 'name': '한국어'},
|
||||
'cs': {'flag': 'fi fi-cz fis', 'name': 'Čeština'},
|
||||
'es': {'flag': 'fi fi-es fis', 'name': 'Español'},
|
||||
'pt': {'flag': 'fi fi-pt fis', 'name': 'Português'},
|
||||
'pt': {'flag': 'fi fi-pt fis', 'name': 'Português (Portugal)'},
|
||||
'pt_BR': {'flag': 'fi fi-br fis', 'name': 'Português (Brasil)'},
|
||||
'it': {'flag': 'fi fi-it fis', 'name': 'Italiano'},
|
||||
'ja': {'flag': 'fi fi-jp fis', 'name': '日本語'},
|
||||
'zh': {'flag': 'fi fi-cn fis', 'name': '中文 (简体)'},
|
||||
|
||||
@@ -46,11 +46,26 @@ class model(EntityPersistenceMixin, watch_base):
|
||||
super(model, self).__init__(*arg, **kw)
|
||||
|
||||
self['overrides_watch'] = kw.get('default', {}).get('overrides_watch')
|
||||
self['url_match_pattern'] = kw.get('default', {}).get('url_match_pattern', '')
|
||||
|
||||
if kw.get('default'):
|
||||
self.update(kw['default'])
|
||||
del kw['default']
|
||||
|
||||
def matches_url(self, url: str) -> bool:
|
||||
"""Return True if this tag should be auto-applied to the given watch URL.
|
||||
|
||||
Wildcard patterns (*,?,[ ) use fnmatch; anything else is a case-insensitive
|
||||
substring match. Returns False if no pattern is configured.
|
||||
"""
|
||||
import fnmatch
|
||||
pattern = self.get('url_match_pattern', '').strip()
|
||||
if not pattern or not url:
|
||||
return False
|
||||
if any(c in pattern for c in ('*', '?', '[')):
|
||||
return fnmatch.fnmatch(url.lower(), pattern.lower())
|
||||
return pattern.lower() in url.lower()
|
||||
|
||||
# _save_to_disk() method provided by EntityPersistenceMixin
|
||||
# commit() and _get_commit_data() methods inherited from watch_base
|
||||
# Tag uses default _get_commit_data() (includes all keys)
|
||||
|
||||
@@ -798,24 +798,50 @@ class model(EntityPersistenceMixin, watch_base):
|
||||
# Also in the case that the file didnt exist
|
||||
return True
|
||||
|
||||
def bump_favicon(self, url, favicon_base_64: str) -> None:
|
||||
def bump_favicon(self, url, favicon_base_64: str, mime_type: str = None) -> None:
|
||||
from urllib.parse import urlparse
|
||||
import base64
|
||||
import binascii
|
||||
decoded = None
|
||||
import re
|
||||
|
||||
if url:
|
||||
MAX_FAVICON_BYTES = 1 * 1024 * 1024 # 1 MB
|
||||
|
||||
MIME_TO_EXT = {
|
||||
'image/png': 'png',
|
||||
'image/x-icon': 'ico',
|
||||
'image/vnd.microsoft.icon': 'ico',
|
||||
'image/jpeg': 'jpg',
|
||||
'image/gif': 'gif',
|
||||
'image/svg+xml': 'svg',
|
||||
'image/webp': 'webp',
|
||||
'image/bmp': 'bmp',
|
||||
}
|
||||
|
||||
extension = None
|
||||
|
||||
# If the caller already resolved the MIME type (e.g. from blob.type or a data URI),
|
||||
# use that directly — it's more reliable than guessing from a URL path.
|
||||
if mime_type:
|
||||
extension = MIME_TO_EXT.get(mime_type.lower().split(';')[0].strip(), None)
|
||||
|
||||
# Fall back to extracting extension from URL path, unless it's a data URI.
|
||||
if not extension and url and not url.startswith('data:'):
|
||||
try:
|
||||
parsed = urlparse(url)
|
||||
filename = os.path.basename(parsed.path)
|
||||
(base, extension) = filename.lower().strip().rsplit('.', 1)
|
||||
(_base, ext) = filename.lower().strip().rsplit('.', 1)
|
||||
extension = ext
|
||||
except ValueError:
|
||||
logger.error(f"UUID: {self.get('uuid')} Cant work out file extension from '{url}'")
|
||||
return None
|
||||
else:
|
||||
# Assume favicon.ico
|
||||
base = "favicon"
|
||||
extension = "ico"
|
||||
logger.warning(f"UUID: {self.get('uuid')} Cant work out file extension from '{url}', defaulting to ico")
|
||||
|
||||
# Handle data URIs: extract MIME type from the URI itself when not already known
|
||||
if not extension and url and url.startswith('data:'):
|
||||
m = re.match(r'^data:([^;]+);base64,', url)
|
||||
if m:
|
||||
extension = MIME_TO_EXT.get(m.group(1).lower(), None)
|
||||
|
||||
if not extension:
|
||||
extension = 'ico'
|
||||
|
||||
fname = os.path.join(self.data_dir, f"favicon.{extension}")
|
||||
|
||||
@@ -824,22 +850,27 @@ class model(EntityPersistenceMixin, watch_base):
|
||||
decoded = base64.b64decode(favicon_base_64, validate=True)
|
||||
except (binascii.Error, ValueError) as e:
|
||||
logger.warning(f"UUID: {self.get('uuid')} FavIcon save data (Base64) corrupt? {str(e)}")
|
||||
else:
|
||||
if decoded:
|
||||
try:
|
||||
with open(fname, 'wb') as f:
|
||||
f.write(decoded)
|
||||
return None
|
||||
|
||||
# Invalidate module-level favicon filename cache for this watch
|
||||
_FAVICON_FILENAME_CACHE.pop(self.data_dir, None)
|
||||
if len(decoded) > MAX_FAVICON_BYTES:
|
||||
logger.warning(f"UUID: {self.get('uuid')} Favicon too large ({len(decoded)} bytes), skipping")
|
||||
return None
|
||||
|
||||
# A signal that could trigger the socket server to update the browser also
|
||||
watch_check_update = signal('watch_favicon_bump')
|
||||
if watch_check_update:
|
||||
watch_check_update.send(watch_uuid=self.get('uuid'))
|
||||
try:
|
||||
with open(fname, 'wb') as f:
|
||||
f.write(decoded)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"UUID: {self.get('uuid')} error saving FavIcon to {fname} - {str(e)}")
|
||||
# Invalidate module-level favicon filename cache for this watch
|
||||
_FAVICON_FILENAME_CACHE.pop(self.data_dir, None)
|
||||
|
||||
# A signal that could trigger the socket server to update the browser also
|
||||
watch_check_update = signal('watch_favicon_bump')
|
||||
if watch_check_update:
|
||||
watch_check_update.send(watch_uuid=self.get('uuid'))
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"UUID: {self.get('uuid')} error saving FavIcon to {fname} - {str(e)}")
|
||||
return None
|
||||
|
||||
# @todo - Store some checksum and only write when its different
|
||||
logger.debug(f"UUID: {self.get('uuid')} updated favicon to at {fname}")
|
||||
|
||||
@@ -186,6 +186,7 @@ class watch_base(dict):
|
||||
'consecutive_filter_failures': 0, # Every time the CSS/xPath filter cannot be located, reset when all is fine.
|
||||
'content-type': None,
|
||||
'date_created': None,
|
||||
'extract_lines_containing': [], # Keep only lines containing these substrings (plain text, case-insensitive)
|
||||
'extract_text': [], # Extract text by regex after filters
|
||||
'fetch_backend': 'system', # plaintext, playwright etc
|
||||
'fetch_time': 0.0,
|
||||
@@ -337,6 +338,7 @@ class watch_base(dict):
|
||||
# These are set by processors/workers and should not trigger edited flag
|
||||
additional_system_fields = {
|
||||
'last_check_status', # Set by processors
|
||||
'last_filter_config_hash', # Set by text_json_diff processor, internal skip-cache
|
||||
'restock', # Set by restock processor
|
||||
'last_viewed', # Set by mark_all_viewed endpoint
|
||||
}
|
||||
|
||||
@@ -259,9 +259,12 @@ def apply_service_tweaks(url, n_body, n_title, requested_output_format):
|
||||
elif (url.startswith('discord://') or url.startswith('https://discordapp.com/api/webhooks')
|
||||
or url.startswith('https://discord.com/api'))\
|
||||
and 'html' in requested_output_format:
|
||||
# Discord doesn't support HTML, replace <br> with newlines
|
||||
# Discord doesn't render HTML — convert markup to plain text equivalents.
|
||||
# is injected upstream to preserve double-spaces for HTML email clients;
|
||||
# Discord displays it as the literal string " " so strip it here.
|
||||
n_body = n_body.strip().replace('<br>', '\n')
|
||||
n_body = n_body.replace('</br>', '\n')
|
||||
n_body = n_body.replace(' ', ' ')
|
||||
n_body = newline_re.sub('\n', n_body)
|
||||
|
||||
# Don't replace placeholders or truncate here - let the custom Discord plugin handle it
|
||||
|
||||
@@ -88,6 +88,28 @@ class FormattableTimestamp(str):
|
||||
return self._dt.isoformat()
|
||||
|
||||
|
||||
class FormattableExtract(str):
|
||||
"""
|
||||
A str subclass that holds only the extracted changed fragments from a diff.
|
||||
Used for {{diff_changed_from}} and {{diff_changed_to}} tokens.
|
||||
|
||||
{{ diff_changed_from }} → old value(s) only, e.g. "$99.99"
|
||||
{{ diff_changed_to }} → new value(s) only, e.g. "$109.99"
|
||||
|
||||
Multiple changed fragments are joined with newlines.
|
||||
Being a str subclass means it is natively JSON serializable.
|
||||
"""
|
||||
def __new__(cls, prev_snapshot, current_snapshot, extract_fn):
|
||||
if prev_snapshot or current_snapshot:
|
||||
from changedetectionio import diff as diff_module
|
||||
raw = diff_module.render_diff(prev_snapshot, current_snapshot, word_diff=True)
|
||||
extracted = extract_fn(raw)
|
||||
else:
|
||||
extracted = ''
|
||||
instance = super().__new__(cls, extracted)
|
||||
return instance
|
||||
|
||||
|
||||
class FormattableDiff(str):
|
||||
"""
|
||||
A str subclass representing a rendered diff. As a plain string it renders
|
||||
@@ -161,6 +183,8 @@ class NotificationContextData(dict):
|
||||
'diff_patch': FormattableDiff('', '', patch_format=True),
|
||||
'diff_removed': FormattableDiff('', '', include_added=False),
|
||||
'diff_removed_clean': FormattableDiff('', '', include_added=False, include_change_type_prefix=False),
|
||||
'diff_changed_from': FormattableExtract('', '', extract_fn=lambda x: x),
|
||||
'diff_changed_to': FormattableExtract('', '', extract_fn=lambda x: x),
|
||||
'diff_url': None,
|
||||
'markup_text_links_to_html_links': False, # If automatic conversion of plaintext to HTML should happen
|
||||
'notification_timestamp': time.time(),
|
||||
@@ -244,16 +268,27 @@ def add_rendered_diff_to_notification_vars(notification_scan_text:str, prev_snap
|
||||
'diff_removed_clean': {'word_diff': word_diff, 'include_added': False, 'include_change_type_prefix': False},
|
||||
}
|
||||
|
||||
from changedetectionio.diff import extract_changed_from, extract_changed_to
|
||||
extract_specs = {
|
||||
'diff_changed_from': extract_changed_from,
|
||||
'diff_changed_to': extract_changed_to,
|
||||
}
|
||||
|
||||
ret = {}
|
||||
rendered_count = 0
|
||||
# Only create FormattableDiff objects for diff keys actually used in the notification text
|
||||
# Only create FormattableDiff/FormattableExtract objects for diff keys actually used in the notification text
|
||||
for key in NotificationContextData().keys():
|
||||
if key.startswith('diff') and key in diff_specs:
|
||||
# Check if this placeholder is actually used in the notification text
|
||||
pattern = rf"(?<![A-Za-z0-9_]){re.escape(key)}(?![A-Za-z0-9_])"
|
||||
if re.search(pattern, notification_scan_text, re.IGNORECASE):
|
||||
ret[key] = FormattableDiff(prev_snapshot, current_snapshot, **diff_specs[key])
|
||||
rendered_count += 1
|
||||
if not key.startswith('diff'):
|
||||
continue
|
||||
pattern = rf"(?<![A-Za-z0-9_]){re.escape(key)}(?![A-Za-z0-9_])"
|
||||
if not re.search(pattern, notification_scan_text, re.IGNORECASE):
|
||||
continue
|
||||
if key in diff_specs:
|
||||
ret[key] = FormattableDiff(prev_snapshot, current_snapshot, **diff_specs[key])
|
||||
rendered_count += 1
|
||||
elif key in extract_specs:
|
||||
ret[key] = FormattableExtract(prev_snapshot, current_snapshot, extract_fn=extract_specs[key])
|
||||
rendered_count += 1
|
||||
|
||||
if rendered_count:
|
||||
logger.trace(f"Rendered {rendered_count} diff placeholder(s) {sorted(ret.keys())} in {time.time() - now:.3f}s")
|
||||
@@ -461,7 +496,7 @@ Thanks - Your omniscient changedetection.io installation.
|
||||
n_object = NotificationContextData({
|
||||
'notification_title': f"Changedetection.io - Alert - Browser step at position {step} could not be run",
|
||||
'notification_body': body,
|
||||
'notification_format': self._check_cascading_vars('notification_format', watch),
|
||||
'notification_format': _check_cascading_vars(self.datastore, 'notification_format', watch),
|
||||
})
|
||||
n_object['markup_text_links_to_html_links'] = n_object.get('notification_format').startswith('html')
|
||||
|
||||
|
||||
@@ -174,6 +174,64 @@ class ChangeDetectionSpec:
|
||||
"""
|
||||
pass
|
||||
|
||||
@hookspec
|
||||
def get_html_head_extras():
|
||||
"""Return HTML to inject into the <head> of every page via base.html.
|
||||
|
||||
Plugins can use this to add <script>, <style>, or <link> tags that should
|
||||
be present on all pages. Return a raw HTML string or None.
|
||||
|
||||
IMPORTANT: Always use Flask's url_for() for any src/href URLs so that
|
||||
sub-path deployments (nginx reverse proxy with USE_X_SETTINGS / X-Forwarded-Prefix)
|
||||
work correctly. This hook is called inside a request context so url_for() is
|
||||
always available.
|
||||
|
||||
For small amounts of CSS/JS, return them inline — no file-serving needed::
|
||||
|
||||
from changedetectionio.pluggy_interface import hookimpl
|
||||
|
||||
@hookimpl
|
||||
def get_html_head_extras(self):
|
||||
return (
|
||||
'<style>.my-module-banner { color: red; }</style>\\n'
|
||||
'<script>console.log("my_module_content loaded");</script>'
|
||||
)
|
||||
|
||||
For larger assets, register your own lightweight Flask routes in the plugin
|
||||
module and point to them with url_for() so the sub-path prefix is handled
|
||||
automatically::
|
||||
|
||||
from flask import url_for, Response
|
||||
from changedetectionio.pluggy_interface import hookimpl
|
||||
from changedetectionio.flask_app import app as _app
|
||||
|
||||
MY_CSS = ".my-module-example { color: red; }"
|
||||
MY_JS = "console.log('my_module_content loaded');"
|
||||
|
||||
@_app.route('/my_module_content/css')
|
||||
def my_module_content_css():
|
||||
return Response(MY_CSS, mimetype='text/css',
|
||||
headers={'Cache-Control': 'max-age=3600'})
|
||||
|
||||
@_app.route('/my_module_content/js')
|
||||
def my_module_content_js():
|
||||
return Response(MY_JS, mimetype='application/javascript',
|
||||
headers={'Cache-Control': 'max-age=3600'})
|
||||
|
||||
@hookimpl
|
||||
def get_html_head_extras(self):
|
||||
css = url_for('my_module_content_css')
|
||||
js = url_for('my_module_content_js')
|
||||
return (
|
||||
f'<link rel="stylesheet" href="{css}">\\n'
|
||||
f'<script src="{js}" defer></script>'
|
||||
)
|
||||
|
||||
Returns:
|
||||
str or None: Raw HTML string to inject inside <head>, or None
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Set up Plugin Manager
|
||||
plugin_manager = pluggy.PluginManager(PLUGIN_NAMESPACE)
|
||||
@@ -606,4 +664,20 @@ def apply_update_finalize(update_handler, watch, datastore, processing_exception
|
||||
except Exception as e:
|
||||
# Don't let plugin errors crash the worker
|
||||
logger.error(f"Error in update_finalize hook: {e}")
|
||||
logger.exception(f"update_finalize hook exception details:")
|
||||
logger.exception(f"update_finalize hook exception details:")
|
||||
|
||||
|
||||
def collect_html_head_extras():
|
||||
"""Collect and combine HTML head extras from all plugins.
|
||||
|
||||
Called from a Flask template global so it always runs inside a request context.
|
||||
This means url_for() works correctly in plugin implementations, including when the
|
||||
app is deployed under a sub-path via USE_X_SETTINGS / X-Forwarded-Prefix (ProxyFix
|
||||
sets SCRIPT_NAME so url_for() automatically prepends the prefix).
|
||||
|
||||
Returns:
|
||||
str: Combined HTML string to inject inside <head>, or empty string
|
||||
"""
|
||||
results = plugin_manager.hook.get_html_head_extras()
|
||||
parts = [r for r in results if r]
|
||||
return "\n".join(parts) if parts else ""
|
||||
@@ -341,6 +341,18 @@ def get_processor_descriptions():
|
||||
return descriptions
|
||||
|
||||
|
||||
def wcag_text_color(hex_bg: str) -> str:
|
||||
"""Return #000000 or #ffffff for maximum WCAG contrast against hex_bg."""
|
||||
hex_bg = hex_bg.lstrip('#')
|
||||
if len(hex_bg) != 6:
|
||||
return '#000000'
|
||||
r, g, b = (int(hex_bg[i:i+2], 16) / 255 for i in (0, 2, 4))
|
||||
def lin(c):
|
||||
return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
|
||||
L = 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)
|
||||
return '#000000' if L > 0.179 else '#ffffff'
|
||||
|
||||
|
||||
def generate_processor_badge_colors(processor_name):
|
||||
"""
|
||||
Generate consistent colors for a processor badge based on its name.
|
||||
|
||||
@@ -97,7 +97,6 @@ class difference_detection_processor():
|
||||
logger.warning(f"Failed to read checksum file for {self.watch_uuid}: {e}")
|
||||
self.last_raw_content_checksum = None
|
||||
|
||||
|
||||
async def validate_iana_url(self):
|
||||
"""Pre-flight SSRF check — runs DNS lookup in executor to avoid blocking the event loop.
|
||||
Covers all fetchers (requests, playwright, puppeteer, plugins) since every fetch goes
|
||||
|
||||
@@ -61,7 +61,7 @@ def render_form(watch, datastore, request, url_for, render_template, flash, redi
|
||||
screenshot=screenshot_url,
|
||||
is_html_webdriver=is_html_webdriver,
|
||||
password_enabled_and_share_is_off=password_enabled_and_share_is_off,
|
||||
extra_title=f" - {watch.label} - Extract Data",
|
||||
extra_title=f" - {watch.label} - {gettext('Extract Data')}",
|
||||
extra_stylesheets=[url_for('static_content', group='styles', filename='diff.css')],
|
||||
pure_menu_fixed=False
|
||||
)
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
{% block content %}
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
{% if last_error_text %}<li class="tab" id="error-text-tab"><a href="{{ url_for('ui.ui_diff.diff_history_page', uuid=uuid)}}#error-text">Error Text</a></li> {% endif %}
|
||||
{% if last_error_screenshot %}<li class="tab" id="error-screenshot-tab"><a href="{{ url_for('ui.ui_diff.diff_history_page', uuid=uuid)}}#error-screenshot">Error Screenshot</a></li> {% endif %}
|
||||
<li class="tab" id=""><a href="{{ url_for('ui.ui_diff.diff_history_page', uuid=uuid)}}#text">Text</a></li>
|
||||
<li class="tab" id="screenshot-tab"><a href="{{ url_for('ui.ui_diff.diff_history_page', uuid=uuid)}}#screenshot">Screenshot</a></li>
|
||||
<li class="tab active" id="extract-tab"><a href="{{ url_for('ui.ui_diff.diff_history_page_extract_GET', uuid=uuid)}}">Extract Data</a></li>
|
||||
{% if last_error_text %}<li class="tab" id="error-text-tab"><a href="{{ url_for('ui.ui_diff.diff_history_page', uuid=uuid)}}#error-text">{{ _('Error Text') }}</a></li> {% endif %}
|
||||
{% if last_error_screenshot %}<li class="tab" id="error-screenshot-tab"><a href="{{ url_for('ui.ui_diff.diff_history_page', uuid=uuid)}}#error-screenshot">{{ _('Error Screenshot') }}</a></li> {% endif %}
|
||||
<li class="tab" id=""><a href="{{ url_for('ui.ui_diff.diff_history_page', uuid=uuid)}}#text">{{ _('Text') }}</a></li>
|
||||
<li class="tab" id="screenshot-tab"><a href="{{ url_for('ui.ui_diff.diff_history_page', uuid=uuid)}}#screenshot">{{ _('Screenshot') }}</a></li>
|
||||
<li class="tab active" id="extract-tab"><a href="{{ url_for('ui.ui_diff.diff_history_page_extract_GET', uuid=uuid)}}">{{ _('Extract Data') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -17,23 +17,23 @@
|
||||
<form id="extract-data-form" class="pure-form pure-form-stacked edit-form" action="{{ url_for('ui.ui_diff.diff_history_page_extract_POST', uuid=uuid) }}" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
<p>This tool will extract text data from all of the watch history.</p>
|
||||
<p>{{ _('This tool will extract text data from all of the watch history.') }}</p>
|
||||
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(extract_form.extract_regex) }}
|
||||
<span class="pure-form-message-inline">
|
||||
A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract.<br>
|
||||
{{ _('A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract.')|safe }}<br>
|
||||
|
||||
<p>
|
||||
For example, to extract only the numbers from text ‐<br>
|
||||
<strong>Raw text</strong>: <code>Temperature <span style="color: red">5.5</span>°C in Sydney</code><br>
|
||||
<strong>RegEx to extract:</strong> <code>Temperature <span style="color: red">([0-9\.]+)</span></code><br>
|
||||
{{ _('For example, to extract only the numbers from text') }} ‐<br>
|
||||
<strong>{{ _('Raw text') }}</strong>: <code>Temperature <span style="color: red">5.5</span>°C in Sydney</code><br>
|
||||
<strong>{{ _('RegEx to extract:') }}</strong> <code>Temperature <span style="color: red">([0-9\.]+)</span></code><br>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://RegExr.com/">Be sure to test your RegEx here.</a>
|
||||
<a href="https://RegExr.com/">{{ _('Be sure to test your RegEx here.') }}</a>
|
||||
</p>
|
||||
<p>
|
||||
Each RegEx group bracket <code>()</code> will be in its own column, the first column value is always the date.
|
||||
{{ _('Each RegEx group bracket') }} <code>()</code> {{ _('will be in its own column, the first column value is always the date.') }}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ a side-by-side or unified diff view with syntax highlighting and change markers.
|
||||
|
||||
import os
|
||||
import time
|
||||
from flask_babel import gettext
|
||||
from loguru import logger
|
||||
|
||||
from changedetectionio import diff, strtobool
|
||||
@@ -207,7 +208,7 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect,
|
||||
diff_prefs=diff_prefs,
|
||||
extra_classes='difference-page',
|
||||
extra_stylesheets=extra_stylesheets,
|
||||
extra_title=f" - {watch.label} - History",
|
||||
extra_title=f" - {watch.label} - {gettext('History')}",
|
||||
extract_form=extract_form,
|
||||
from_version=str(from_version),
|
||||
is_html_webdriver=is_html_webdriver,
|
||||
|
||||
@@ -85,6 +85,10 @@ class FilterConfig:
|
||||
self._subtractive_selectors_cache = [*tag_selectors, *watch_selectors, *global_selectors]
|
||||
return self._subtractive_selectors_cache
|
||||
|
||||
@property
|
||||
def extract_lines_containing(self):
|
||||
return self._get_merged_rules('extract_lines_containing')
|
||||
|
||||
@property
|
||||
def extract_text(self):
|
||||
return self._get_merged_rules('extract_text')
|
||||
@@ -101,6 +105,30 @@ class FilterConfig:
|
||||
def text_should_not_be_present(self):
|
||||
return self._get_merged_rules('text_should_not_be_present')
|
||||
|
||||
def get_filter_config_hash(self):
|
||||
"""
|
||||
Stable hash of the effective filter configuration.
|
||||
|
||||
Used by the skip-logic in run_changedetection() so that any change to
|
||||
global settings, tag overrides, or watch filters automatically invalidates
|
||||
the raw-content-unchanged shortcut — without needing scattered
|
||||
clear_all_last_checksums() calls at every settings mutation site.
|
||||
"""
|
||||
app = self.datastore.data['settings']['application']
|
||||
config = {
|
||||
'extract_lines_containing': sorted(self.extract_lines_containing),
|
||||
'extract_text': sorted(self.extract_text),
|
||||
'ignore_text': sorted(self.ignore_text),
|
||||
'include_filters': sorted(self.include_filters),
|
||||
'subtractive_selectors': sorted(self.subtractive_selectors),
|
||||
'text_should_not_be_present': sorted(self.text_should_not_be_present),
|
||||
'trigger_text': sorted(self.trigger_text),
|
||||
# Global processing flags not captured by the filter lists above
|
||||
'ignore_whitespace': app.get('ignore_whitespace', False),
|
||||
'strip_ignored_lines': app.get('strip_ignored_lines', False),
|
||||
}
|
||||
return hashlib.md5(json.dumps(config, sort_keys=True).encode()).hexdigest()
|
||||
|
||||
@property
|
||||
def has_include_filters(self):
|
||||
return bool(self.include_filters) and bool(self.include_filters[0].strip())
|
||||
@@ -135,6 +163,17 @@ class ContentTransformer:
|
||||
text = text.replace("\n\n", "\n")
|
||||
return '\n'.join(sorted(text.splitlines(), key=lambda x: x.lower()))
|
||||
|
||||
@staticmethod
|
||||
def extract_lines_containing(text, substrings):
|
||||
"""Keep only lines that contain at least one of the given substrings (case-insensitive)."""
|
||||
needles = [s.lower() for s in substrings if s.strip()]
|
||||
if not needles:
|
||||
return text
|
||||
return '\n'.join(
|
||||
line for line in text.splitlines()
|
||||
if any(needle in line.lower() for needle in needles)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def extract_by_regex(text, regex_patterns):
|
||||
"""Extract text matching regex patterns."""
|
||||
@@ -377,19 +416,26 @@ class perform_site_check(difference_detection_processor):
|
||||
raise Exception("Watch no longer exists.")
|
||||
|
||||
current_raw_document_checksum = self.get_raw_document_checksum()
|
||||
# Skip processing only if BOTH conditions are true:
|
||||
# 1. HTML content unchanged (checksum matches last saved checksum)
|
||||
# 2. Watch configuration was not edited (including trigger_text, filters, etc.)
|
||||
# The was_edited flag handles all watch configuration changes, so we don't need
|
||||
# separate checks for trigger_text or other processing rules.
|
||||
|
||||
# Build filter config up front so we can hash it for the skip check.
|
||||
filter_config = FilterConfig(watch, self.datastore)
|
||||
current_filter_config_hash = filter_config.get_filter_config_hash()
|
||||
|
||||
# Skip only when ALL of these hold:
|
||||
# 1. raw HTML is unchanged
|
||||
# 2. watch config was not edited (was_edited covers per-watch field changes)
|
||||
# 3. effective filter config is unchanged (covers global/tag setting changes that
|
||||
# bypass was_edited — e.g. global_ignore_text, global_subtractive_selectors)
|
||||
# last_filter_config_hash being False means first run or upgrade: don't skip.
|
||||
if (not force_reprocess and
|
||||
not watch.was_edited and
|
||||
self.last_raw_content_checksum and
|
||||
self.last_raw_content_checksum == current_raw_document_checksum):
|
||||
self.last_raw_content_checksum == current_raw_document_checksum and
|
||||
watch.get('last_filter_config_hash') and
|
||||
watch.get('last_filter_config_hash') == current_filter_config_hash):
|
||||
raise checksumFromPreviousCheckWasTheSame()
|
||||
|
||||
# Initialize components
|
||||
filter_config = FilterConfig(watch, self.datastore)
|
||||
# Initialize remaining components
|
||||
content_processor = ContentProcessor(self.fetcher, watch, filter_config, self.datastore)
|
||||
transformer = ContentTransformer()
|
||||
rule_engine = RuleEngine()
|
||||
@@ -410,6 +456,7 @@ class perform_site_check(difference_detection_processor):
|
||||
|
||||
# Save the raw content checksum to file (processor implementation detail, not watch config)
|
||||
self.update_last_raw_content_checksum(current_raw_document_checksum)
|
||||
update_obj['last_filter_config_hash'] = current_filter_config_hash
|
||||
|
||||
# === CONTENT PREPROCESSING ===
|
||||
# Avoid creating unnecessary intermediate string copies by reassigning only when needed
|
||||
@@ -503,6 +550,10 @@ class perform_site_check(difference_detection_processor):
|
||||
|
||||
update_obj["last_check_status"] = self.fetcher.get_last_status_code()
|
||||
|
||||
# === LINE FILTER (plain-text substring) ===
|
||||
if filter_config.extract_lines_containing:
|
||||
stripped_text = transformer.extract_lines_containing(stripped_text, filter_config.extract_lines_containing)
|
||||
|
||||
# === REGEX EXTRACTION ===
|
||||
if filter_config.extract_text:
|
||||
extracted = transformer.extract_by_regex(stripped_text, filter_config.extract_text)
|
||||
@@ -536,8 +587,8 @@ class perform_site_check(difference_detection_processor):
|
||||
# === BLOCKING RULES EVALUATION ===
|
||||
blocked = False
|
||||
|
||||
# Check trigger_text
|
||||
if rule_engine.evaluate_trigger_text(stripped_text, filter_config.trigger_text):
|
||||
# Check trigger_text - use text_for_checksuming so ignore_text can suppress trigger_text
|
||||
if rule_engine.evaluate_trigger_text(text_for_checksuming, filter_config.trigger_text):
|
||||
blocked = True
|
||||
|
||||
# Check text_should_not_be_present
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
function checkDiscordHtmlWarning() {
|
||||
var urls = $('textarea.notification-urls').val() || '';
|
||||
var format = $('select.notification-format').val() || '';
|
||||
var isDiscord = /discord:\/\/|https:\/\/discord(?:app)?\.com\/api/i.test(urls);
|
||||
var isHtml = format === 'html' || format === 'htmlcolor';
|
||||
if (isDiscord && isHtml) {
|
||||
$('#discord-html-format-warning').show();
|
||||
} else {
|
||||
$('#discord-html-format-warning').hide();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
$('textarea.notification-urls, select.notification-format').on('change input', checkDiscordHtmlWarning);
|
||||
checkDiscordHtmlWarning();
|
||||
|
||||
$('#add-email-helper').click(function (e) {
|
||||
e.preventDefault();
|
||||
email = prompt("Destination email");
|
||||
|
||||
@@ -980,12 +980,20 @@ class ChangeDetectionStore(DatastoreUpdatesMixin, FileSavingDataStore):
|
||||
def get_all_tags_for_watch(self, uuid):
|
||||
"""This should be in Watch model but Watch doesn't have access to datastore, not sure how to solve that yet"""
|
||||
watch = self.data['watching'].get(uuid)
|
||||
if not watch:
|
||||
return {}
|
||||
|
||||
# Should return a dict of full tag info linked by UUID
|
||||
if watch:
|
||||
return dictfilt(self.__data['settings']['application']['tags'], watch.get('tags', []))
|
||||
# Start with manually assigned tags
|
||||
result = dictfilt(self.__data['settings']['application']['tags'], watch.get('tags', []))
|
||||
|
||||
return {}
|
||||
# Additionally include any tag whose url_match_pattern matches this watch's URL
|
||||
watch_url = watch.get('url', '')
|
||||
if watch_url:
|
||||
for tag_uuid, tag in self.__data['settings']['application']['tags'].items():
|
||||
if tag_uuid not in result and tag.matches_url(watch_url):
|
||||
result[tag_uuid] = tag
|
||||
|
||||
return result
|
||||
|
||||
@property
|
||||
def extra_browsers(self):
|
||||
|
||||
@@ -98,6 +98,14 @@
|
||||
<td><code>{{ '{{diff_patch}}' }}</code></td>
|
||||
<td>{{ _('The diff output - patch in unified format') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ '{{diff_changed_from}}' }}</code></td>
|
||||
<td>{{ _('Only the changed words/values from the previous version — e.g. the old price. Best when a single value changes per line; multiple changed fragments are joined by newline.') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ '{{diff_changed_to}}' }}</code></td>
|
||||
<td>{{ _('Only the changed words/values from the new version — e.g. the new price. Best when a single value changes per line; multiple changed fragments are joined by newline.') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ '{{current_snapshot}}' }}</code></td>
|
||||
<td>{{ _('The current snapshot text contents value, useful when combined with JSON or CSS filters') }}
|
||||
@@ -187,6 +195,10 @@
|
||||
<div class="">
|
||||
{{ render_field(form.notification_format , class="notification-format") }}
|
||||
<span class="pure-form-message-inline">{{ _('Format for all notifications') }}</span>
|
||||
<div id="discord-html-format-warning" class="inline-warning" style="display: none; margin-top: 6px;">
|
||||
<img class="inline-warning-icon" src="{{url_for('static_content', group='images', filename='notice.svg')}}" alt="{{ _('Note') }}" title="{{ _('Note') }}">
|
||||
{{ _('Discord does not render HTML — switch to') }} <strong>{{ _('Plain Text') }}</strong> {{ _('format to avoid') }} <code>&nbsp;</code> {{ _('and other HTML entities appearing literally in your notifications.') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
<script src="{{url_for('static_content', group='js', filename='socket.io.min.js')}}"></script>
|
||||
<script src="{{url_for('static_content', group='js', filename='realtime.js')}}" defer></script>
|
||||
{% endif %}
|
||||
{%- set _html_head_extras = get_html_head_extras() -%}
|
||||
{%- if _html_head_extras %}
|
||||
{{ _html_head_extras | safe }}
|
||||
{%- endif %}
|
||||
</head>
|
||||
|
||||
<body class="{{extra_classes}}">
|
||||
@@ -65,7 +69,7 @@
|
||||
{% else %}
|
||||
{% if new_version_available and not(has_password and not current_user.is_authenticated) %}
|
||||
<span id="new-version-text" class="pure-menu-heading">
|
||||
<a href="https://changedetection.io">A new version is available</a>
|
||||
<a href="https://changedetection.io">{{ _('A new version is available') }}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -231,7 +235,7 @@
|
||||
{% if session['share-link'] %}
|
||||
<ul class="messages with-share-link">
|
||||
<li class="message">
|
||||
Share this link:
|
||||
{{ _('Share this link:') }}
|
||||
<span id="share-link">{{ session['share-link'] }}</span>
|
||||
<img style="height: 1em; display: inline-block" src="{{url_for('static_content', group='images', filename='copy.svg')}}" >
|
||||
</li>
|
||||
|
||||
@@ -10,7 +10,7 @@ xpath://body/div/span[contains(@class, 'example-class')]",
|
||||
<span class="pure-form-message-inline"><strong>Note!: //text() function does not work where the <element> contains <![CDATA[]]></strong></span><br>
|
||||
{% endif %}
|
||||
<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.<br>
|
||||
<span data-target="#advanced-help-selectors" class="toggle-show pure-button button-tag button-xsmall">Show advanced help and tips</span><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;">
|
||||
<li>CSS - Limit text to this CSS rule, only text matching this CSS rule is included.</li>
|
||||
<li>JSON - Limit text to this JSON rule, using either <a href="https://pypi.org/project/jsonpath-ng/" target="new">JSONPath</a> or <a href="https://stedolan.github.io/jq/" target="new">jq</a> (if installed).
|
||||
@@ -47,9 +47,9 @@ nav
|
||||
//*[contains(text(), 'Advertisement')]") }}
|
||||
<span class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li> Remove HTML element(s) by CSS and XPath selectors before text conversion. </li>
|
||||
<li> Don't paste HTML here, use only CSS and XPath selectors </li>
|
||||
<li> Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML. </li>
|
||||
<li> {{ _('Remove HTML element(s) by CSS and XPath selectors before text conversion.') }} </li>
|
||||
<li> {{ _("Don't paste HTML here, use only CSS and XPath selectors") }} </li>
|
||||
<li> {{ _('Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML.') }} </li>
|
||||
</ul>
|
||||
</span>
|
||||
</fieldset>
|
||||
|
||||
@@ -49,6 +49,21 @@ Unavailable") }}
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.extract_lines_containing, rows=5, placeholder="celsius
|
||||
temperature
|
||||
price") }}
|
||||
<span class="pure-form-message-inline">
|
||||
<ul>
|
||||
<li>{{ _('Keep only lines that contain any of these words or phrases (plain text, case-insensitive)') }}</li>
|
||||
<li>{{ _('One entry per line — any line in the page text that contains a match is kept') }}</li>
|
||||
<li>{{ _('Simpler alternative to regex — use this when you just want lines about a specific topic') }}</li>
|
||||
<li>{{ _('Example: enter') }} <code>celsius</code> {{ _('to keep only lines mentioning temperature readings') }}</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.extract_text, rows=5, placeholder="/.+?\d+ comments.+?/
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Test that plugins can inject HTML into base.html <head> via get_html_head_extras hookimpl."""
|
||||
import pytest
|
||||
from flask import url_for, Response
|
||||
|
||||
from changedetectionio.pluggy_interface import hookimpl, plugin_manager
|
||||
|
||||
_MY_JS = "console.log('my_module_content loaded');"
|
||||
_MY_CSS = ".my-module-example { color: red; }"
|
||||
|
||||
|
||||
class _HeadExtrasPlugin:
|
||||
"""Test plugin that injects tags pointing at its own Flask routes."""
|
||||
|
||||
@hookimpl
|
||||
def get_html_head_extras(self):
|
||||
css_url = url_for('test_plugin_my_module_content_css')
|
||||
js_url = url_for('test_plugin_my_module_content_js')
|
||||
return (
|
||||
f'<link rel="stylesheet" id="test-head-extra-css" href="{css_url}">\n'
|
||||
f'<script id="test-head-extra-js" src="{js_url}" defer></script>'
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def plugin_routes(live_server):
|
||||
"""Register plugin asset routes once per module (Flask routes can't be added twice)."""
|
||||
app = live_server.app
|
||||
|
||||
@app.route('/test-plugin/my_module_content/css')
|
||||
def test_plugin_my_module_content_css():
|
||||
return Response(_MY_CSS, mimetype='text/css',
|
||||
headers={'Cache-Control': 'max-age=3600'})
|
||||
|
||||
@app.route('/test-plugin/my_module_content/js')
|
||||
def test_plugin_my_module_content_js():
|
||||
return Response(_MY_JS, mimetype='application/javascript',
|
||||
headers={'Cache-Control': 'max-age=3600'})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def head_extras_plugin(plugin_routes):
|
||||
"""Register the hookimpl for one test then unregister it — function-scoped for clean isolation."""
|
||||
plugin = _HeadExtrasPlugin()
|
||||
plugin_manager.register(plugin, name="test_head_extras")
|
||||
yield plugin
|
||||
plugin_manager.unregister(name="test_head_extras")
|
||||
|
||||
|
||||
def test_plugin_html_injected_into_head(client, live_server, measure_memory_usage, datastore_path, head_extras_plugin):
|
||||
"""get_html_head_extras output must appear inside <head> in the rendered page."""
|
||||
res = client.get(url_for("watchlist.index"), follow_redirects=True)
|
||||
assert res.status_code == 200
|
||||
assert b'id="test-head-extra-css"' in res.data, "Plugin <link> tag missing from rendered page"
|
||||
assert b'id="test-head-extra-js"' in res.data, "Plugin <script> tag missing from rendered page"
|
||||
|
||||
head_end = res.data.find(b'</head>')
|
||||
assert head_end != -1
|
||||
for marker in (b'id="test-head-extra-css"', b'id="test-head-extra-js"'):
|
||||
pos = res.data.find(marker)
|
||||
assert pos != -1 and pos < head_end, f"{marker} must appear before </head>"
|
||||
|
||||
|
||||
def test_plugin_js_route_returns_correct_content(client, live_server, measure_memory_usage, datastore_path, plugin_routes):
|
||||
"""The plugin-registered JS route must return JS with the right Content-Type."""
|
||||
res = client.get(url_for('test_plugin_my_module_content_js'))
|
||||
assert res.status_code == 200
|
||||
assert 'javascript' in res.content_type
|
||||
assert _MY_JS.encode() in res.data
|
||||
|
||||
|
||||
def test_plugin_css_route_returns_correct_content(client, live_server, measure_memory_usage, datastore_path, plugin_routes):
|
||||
"""The plugin-registered CSS route must return CSS with the right Content-Type."""
|
||||
res = client.get(url_for('test_plugin_my_module_content_css'))
|
||||
assert res.status_code == 200
|
||||
assert 'css' in res.content_type
|
||||
assert _MY_CSS.encode() in res.data
|
||||
|
||||
|
||||
def test_no_extras_without_plugin(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""With no hookimpl registered the markers must not appear (isolation check)."""
|
||||
res = client.get(url_for("watchlist.index"), follow_redirects=True)
|
||||
assert b'id="test-head-extra-css"' not in res.data
|
||||
assert b'id="test-head-extra-js"' not in res.data
|
||||
@@ -11,10 +11,10 @@ from changedetectionio.tests.util import set_original_response, set_modified_res
|
||||
set_longer_modified_response, delete_all_watches
|
||||
|
||||
import logging
|
||||
|
||||
import os
|
||||
|
||||
# NOTE - RELIES ON mailserver as hostname running, see github build recipes
|
||||
smtp_test_server = 'mailserver'
|
||||
smtp_test_server = os.getenv('SMTP_TEST_MAILSERVER', 'mailserver')
|
||||
|
||||
ALL_MARKUP_TOKENS = ''.join(f"TOKEN: '{t}'\n{{{{{t}}}}}\n" for t in NotificationContextData().keys())
|
||||
|
||||
|
||||
@@ -374,6 +374,9 @@ def test_roundtrip_API(client, live_server, measure_memory_usage, datastore_path
|
||||
watch['last_changed'] = 454444444444
|
||||
watch['date_created'] = 454444444444
|
||||
|
||||
# Exercise the new extract_lines_containing field
|
||||
watch['extract_lines_containing'] = ['celsius', 'temperature']
|
||||
|
||||
# HTTP PUT ( UPDATE an existing watch )
|
||||
res = client.put(
|
||||
url_for("watch", uuid=uuid),
|
||||
@@ -397,6 +400,9 @@ def test_roundtrip_API(client, live_server, measure_memory_usage, datastore_path
|
||||
assert date_created != 454444444444
|
||||
assert date_created != "454444444444"
|
||||
|
||||
assert res.json.get('extract_lines_containing') == ['celsius', 'temperature'], \
|
||||
"extract_lines_containing should be persisted and returned via API"
|
||||
|
||||
|
||||
def test_access_denied(client, live_server, measure_memory_usage, datastore_path):
|
||||
# `config_api_token_enabled` Should be On by default
|
||||
|
||||
@@ -220,3 +220,342 @@ def test_regex_error_handling(client, live_server, measure_memory_usage, datasto
|
||||
assert b'is not a valid regular expression.' in res.data
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_extract_lines_containing(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""Test the 'extract_lines_containing' filter keeps only lines with matching substrings."""
|
||||
|
||||
test_return_data = """<html>
|
||||
<body>
|
||||
<p>Current temperature: 21 celsius</p>
|
||||
<p>Humidity: 55%</p>
|
||||
<p>Wind speed: 10 km/h</p>
|
||||
<p>Feels like: 19 celsius</p>
|
||||
<p>UV index: 3</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
with open(os.path.join(datastore_path, "endpoint-content.txt"), "w") as f:
|
||||
f.write(test_return_data)
|
||||
|
||||
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)
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_edit.edit_page", uuid=uuid),
|
||||
data={
|
||||
'extract_lines_containing': 'celsius',
|
||||
"url": test_url,
|
||||
"tags": "",
|
||||
"headers": "",
|
||||
'fetch_backend': "html_requests",
|
||||
"time_between_check_use_default": "y"
|
||||
},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Updated watch." in res.data
|
||||
wait_for_all_checks(client)
|
||||
|
||||
res = client.get(url_for("ui.ui_preview.preview_page", uuid=uuid), follow_redirects=True)
|
||||
|
||||
# Lines containing 'celsius' should be present
|
||||
assert b'celsius' in res.data
|
||||
# Lines without 'celsius' should be excluded
|
||||
assert b'Humidity' not in res.data
|
||||
assert b'Wind speed' not in res.data
|
||||
assert b'UV index' not in res.data
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_extract_lines_containing_case_insensitive(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""Test that extract_lines_containing is case-insensitive."""
|
||||
|
||||
test_return_data = """<html>
|
||||
<body>
|
||||
<p>PRICE: $99.99</p>
|
||||
<p>Price drops to $79.99</p>
|
||||
<p>Stock: Available</p>
|
||||
<p>price history shows decline</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
with open(os.path.join(datastore_path, "endpoint-content.txt"), "w") as f:
|
||||
f.write(test_return_data)
|
||||
|
||||
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)
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_edit.edit_page", uuid=uuid),
|
||||
data={
|
||||
'extract_lines_containing': 'price',
|
||||
"url": test_url,
|
||||
"tags": "",
|
||||
"headers": "",
|
||||
'fetch_backend': "html_requests",
|
||||
"time_between_check_use_default": "y"
|
||||
},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Updated watch." in res.data
|
||||
wait_for_all_checks(client)
|
||||
|
||||
res = client.get(url_for("ui.ui_preview.preview_page", uuid=uuid), follow_redirects=True)
|
||||
|
||||
# All three price lines (different cases) should match
|
||||
assert b'$99.99' in res.data
|
||||
assert b'$79.99' in res.data
|
||||
assert b'price history' in res.data
|
||||
# Non-price line should be excluded
|
||||
assert b'Stock' not in res.data
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_extract_lines_containing_multiple_terms(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""Test that multiple extract_lines_containing entries act as OR (keep line if any term matches)."""
|
||||
|
||||
test_return_data = """<html>
|
||||
<body>
|
||||
<p>Temperature: 21 celsius</p>
|
||||
<p>Humidity: 55%</p>
|
||||
<p>Wind speed: 10 km/h</p>
|
||||
<p>Rain chance: 20%</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
with open(os.path.join(datastore_path, "endpoint-content.txt"), "w") as f:
|
||||
f.write(test_return_data)
|
||||
|
||||
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)
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_edit.edit_page", uuid=uuid),
|
||||
data={
|
||||
'extract_lines_containing': 'celsius\r\nhumidity',
|
||||
"url": test_url,
|
||||
"tags": "",
|
||||
"headers": "",
|
||||
'fetch_backend': "html_requests",
|
||||
"time_between_check_use_default": "y"
|
||||
},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Updated watch." in res.data
|
||||
wait_for_all_checks(client)
|
||||
|
||||
res = client.get(url_for("ui.ui_preview.preview_page", uuid=uuid), follow_redirects=True)
|
||||
|
||||
assert b'celsius' in res.data
|
||||
assert b'Humidity' in res.data
|
||||
# Wind and Rain lines should be excluded
|
||||
assert b'Wind speed' not in res.data
|
||||
assert b'Rain chance' not in res.data
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_extract_lines_containing_with_ignore_text(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
extract_lines_containing narrows to matching lines; ignore_text then suppresses specific
|
||||
lines from triggering change detection (they remain visible but don't affect the checksum).
|
||||
|
||||
Filters are set BEFORE the first check so the filtered+ignored checksum is the baseline
|
||||
from the very start — no race between a forced-recheck and the next content write.
|
||||
"""
|
||||
initial_data = """<html><body>
|
||||
<p>Temperature: 21 celsius</p>
|
||||
<p>Feels like: 19 celsius</p>
|
||||
<p>Humidity: 55%</p>
|
||||
</body></html>"""
|
||||
|
||||
with open(os.path.join(datastore_path, "endpoint-content.txt"), "w") as f:
|
||||
f.write(initial_data)
|
||||
|
||||
test_url = url_for('test_endpoint', _external=True)
|
||||
uuid = client.application.config.get('DATASTORE').add_watch(url=test_url, extras={'paused': True})
|
||||
|
||||
# Set filters BEFORE the first check so the baseline is always filtered+ignored.
|
||||
# (Setting them after an initial unfiltered check creates a race: the forced recheck
|
||||
# that updates previous_md5 must complete before the next content write, which is
|
||||
# timing-sensitive and fails intermittently on slower systems / Python 3.14.)
|
||||
res = client.post(
|
||||
url_for("ui.ui_edit.edit_page", uuid=uuid, unpause_on_save=1),
|
||||
data={
|
||||
'extract_lines_containing': 'celsius',
|
||||
'ignore_text': 'Feels like',
|
||||
"url": test_url,
|
||||
"tags": "",
|
||||
"headers": "",
|
||||
'fetch_backend': "html_requests",
|
||||
"time_between_check_use_default": "y"
|
||||
},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"unpaused" in res.data
|
||||
|
||||
# First check — establishes filtered+ignored baseline. previous_md5 was False so
|
||||
# a change is always detected here; mark_all_viewed clears it before we assert.
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
|
||||
# Sanity: preview should only show celsius lines
|
||||
res = client.get(url_for("ui.ui_preview.preview_page", uuid=uuid), follow_redirects=True)
|
||||
assert b'celsius' in res.data
|
||||
assert b'Humidity' not in res.data
|
||||
|
||||
# Change ONLY the ignored "Feels like" line — should NOT trigger a change
|
||||
changed_data = """<html><body>
|
||||
<p>Temperature: 21 celsius</p>
|
||||
<p>Feels like: 17 celsius</p>
|
||||
<p>Humidity: 55%</p>
|
||||
</body></html>"""
|
||||
|
||||
with open(os.path.join(datastore_path, "endpoint-content.txt"), "w") as f:
|
||||
f.write(changed_data)
|
||||
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
|
||||
res = client.get(url_for("watchlist.index"))
|
||||
assert b'has-unread-changes' not in res.data, "Changing an ignored line should not trigger a change notification"
|
||||
|
||||
client.get(url_for("ui.mark_all_viewed"), follow_redirects=True)
|
||||
time.sleep(1)
|
||||
|
||||
# Change the non-ignored celsius line — SHOULD trigger
|
||||
triggered_data = """<html><body>
|
||||
<p>Temperature: 30 celsius</p>
|
||||
<p>Feels like: 17 celsius</p>
|
||||
<p>Humidity: 55%</p>
|
||||
</body></html>"""
|
||||
|
||||
with open(os.path.join(datastore_path, "endpoint-content.txt"), "w") as f:
|
||||
f.write(triggered_data)
|
||||
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
|
||||
res = client.get(url_for("watchlist.index"))
|
||||
assert b'has-unread-changes' in res.data, "Changing a non-ignored line should trigger a change notification"
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_extract_lines_containing_with_extract_text_regex(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
extract_lines_containing first narrows to relevant lines, then extract_text regex
|
||||
pulls specific tokens from those lines — verifying correct pipeline ordering.
|
||||
"""
|
||||
test_return_data = """<html><body>
|
||||
<p>Widget price: $49.99 each</p>
|
||||
<p>Gadget price: $129.00 each</p>
|
||||
<p>Latest news: price index up 2%</p>
|
||||
<p>Stock count: 150 units</p>
|
||||
<p>Shipping cost: $5.99</p>
|
||||
</body></html>"""
|
||||
|
||||
with open(os.path.join(datastore_path, "endpoint-content.txt"), "w") as f:
|
||||
f.write(test_return_data)
|
||||
|
||||
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)
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_edit.edit_page", uuid=uuid),
|
||||
data={
|
||||
# Step 1: keep lines containing "price" (excludes Stock count and Shipping cost)
|
||||
'extract_lines_containing': 'price',
|
||||
# Step 2: from those lines extract only dollar amounts
|
||||
'extract_text': r'/\$[\d.]+/',
|
||||
"url": test_url,
|
||||
"tags": "",
|
||||
"headers": "",
|
||||
'fetch_backend': "html_requests",
|
||||
"time_between_check_use_default": "y"
|
||||
},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Updated watch." in res.data
|
||||
wait_for_all_checks(client)
|
||||
|
||||
res = client.get(url_for("ui.ui_preview.preview_page", uuid=uuid), follow_redirects=True)
|
||||
|
||||
# Dollar amounts from price lines should be extracted
|
||||
assert b'$49.99' in res.data
|
||||
assert b'$129.00' in res.data
|
||||
# "price index up 2%" has no dollar amount — nothing extracted from that line
|
||||
# "Shipping cost" line was excluded by extract_lines_containing before regex ran
|
||||
assert b'$5.99' not in res.data
|
||||
# Raw line text should not appear — regex replaced it with just the match
|
||||
assert b'Widget' not in res.data
|
||||
assert b'Stock count' not in res.data
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_extract_lines_containing_with_include_filters_css(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
CSS include_filters narrows the HTML first; extract_lines_containing then filters
|
||||
within that already-reduced text — verifying correct pipeline ordering.
|
||||
"""
|
||||
test_return_data = """<html><body>
|
||||
<div class="weather">
|
||||
<p>Temperature: 21 celsius</p>
|
||||
<p>Humidity: 60%</p>
|
||||
<p>Wind: 15 km/h</p>
|
||||
</div>
|
||||
<div class="news">
|
||||
<p>Local forecast: warm celsius weather ahead</p>
|
||||
<p>Markets closed early</p>
|
||||
</div>
|
||||
</body></html>"""
|
||||
|
||||
with open(os.path.join(datastore_path, "endpoint-content.txt"), "w") as f:
|
||||
f.write(test_return_data)
|
||||
|
||||
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)
|
||||
|
||||
res = client.post(
|
||||
url_for("ui.ui_edit.edit_page", uuid=uuid),
|
||||
data={
|
||||
# CSS filter: only look inside the weather div
|
||||
'include_filters': 'div.weather',
|
||||
# Then keep only celsius lines from that section
|
||||
'extract_lines_containing': 'celsius',
|
||||
"url": test_url,
|
||||
"tags": "",
|
||||
"headers": "",
|
||||
'fetch_backend': "html_requests",
|
||||
"time_between_check_use_default": "y"
|
||||
},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Updated watch." in res.data
|
||||
wait_for_all_checks(client)
|
||||
|
||||
res = client.get(url_for("ui.ui_preview.preview_page", uuid=uuid), follow_redirects=True)
|
||||
|
||||
# Only the celsius line from the weather div should survive both filters
|
||||
assert b'celsius' in res.data
|
||||
# Other weather lines excluded by extract_lines_containing
|
||||
assert b'Humidity' not in res.data
|
||||
assert b'Wind' not in res.data
|
||||
# News div content excluded entirely by CSS filter (even though it contains "celsius")
|
||||
assert b'Markets' not in res.data
|
||||
assert b'forecast' not in res.data
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
@@ -214,3 +214,85 @@ def test_import_watchete_xlsx(client, live_server, measure_memory_usage, datasto
|
||||
assert watch.get('fetch_backend') == 'system' # uses default if blank
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_import_wachete_xlsx_row_counter(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""Row counter in Wachete XLSX import must advance even after a failed row.
|
||||
|
||||
Regression: row_id was only incremented in the try/else (on success), so
|
||||
after any failure the counter froze and all subsequent errors cited the
|
||||
stale number. With the enumerate() fix, row 5 must say "row 5", not "row 3".
|
||||
"""
|
||||
import openpyxl
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
# Header row (row 1)
|
||||
ws.append(['Name', 'Id', 'Url', 'Interval (min)', 'XPath', 'Dynamic Wachet', 'Portal Wachet', 'Folder'])
|
||||
# Row 2: valid
|
||||
ws.append(['Site A', '001', 'https://example.com/a', 60, None, None, None, None])
|
||||
# Row 3: bad URL — must report row 3
|
||||
ws.append(['Site B', '002', 'not-a-valid-url', 60, None, None, None, None])
|
||||
# Row 4: valid
|
||||
ws.append(['Site C', '003', 'https://example.com/c', 60, None, None, None, None])
|
||||
# Row 5: bad URL — must report row 5, not "row 3" (the pre-fix stale value)
|
||||
ws.append(['Site D', '004', 'also-not-valid', 60, None, None, None, None])
|
||||
|
||||
xlsx_bytes = io.BytesIO()
|
||||
wb.save(xlsx_bytes)
|
||||
xlsx_bytes.seek(0)
|
||||
|
||||
res = client.post(
|
||||
url_for("imports.import_page"),
|
||||
data={'file_mapping': 'wachete', 'xlsx_file': (xlsx_bytes, 'test.xlsx')},
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert b'2 imported from Wachete .xlsx' in res.data
|
||||
assert b'Error processing row number 3' in res.data
|
||||
assert b'Error processing row number 5' in res.data
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
|
||||
def test_import_custom_xlsx_row_counter(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""Row counter in custom XLSX import must reflect the actual row, not always row 1.
|
||||
|
||||
Regression: row_i was incremented in the else clause of the *outer* try/except
|
||||
(which only fired once, after the whole loop), so every URL-validation error
|
||||
inside the loop reported "row 1". With enumerate() the third row must say
|
||||
"row 3", not "row 1".
|
||||
"""
|
||||
import openpyxl
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
# Row 1: bad URL — must report row 1
|
||||
ws.append(['not-valid-url-row1'])
|
||||
# Row 2: valid
|
||||
ws.append(['https://example.com/b'])
|
||||
# Row 3: bad URL — must report row 3, not "row 1" (the pre-fix value)
|
||||
ws.append(['not-valid-url-row3'])
|
||||
# Row 4: valid
|
||||
ws.append(['https://example.com/d'])
|
||||
|
||||
xlsx_bytes = io.BytesIO()
|
||||
wb.save(xlsx_bytes)
|
||||
xlsx_bytes.seek(0)
|
||||
|
||||
res = client.post(
|
||||
url_for("imports.import_page"),
|
||||
data={
|
||||
'file_mapping': 'custom',
|
||||
'custom_xlsx[col_0]': '1',
|
||||
'custom_xlsx[col_type_0]': 'url',
|
||||
'xlsx_file': (xlsx_bytes, 'test.xlsx'),
|
||||
},
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert b'2 imported from custom .xlsx' in res.data
|
||||
assert b'Error processing row number 1' in res.data
|
||||
assert b'Error processing row number 3' in res.data
|
||||
|
||||
delete_all_watches(client)
|
||||
|
||||
@@ -50,6 +50,85 @@ def test_favicon(client, live_server, measure_memory_usage, datastore_path):
|
||||
res = client.get(url_for('static_content', group='js', filename='../styles/styles.css'))
|
||||
assert res.status_code != 200
|
||||
|
||||
def test_favicon_inline_data_uri(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
bump_favicon() must handle a data URI as the url parameter.
|
||||
Previously this logged "Cant work out file extension from 'data:image/png;base64,...'" and bailed.
|
||||
The mime_type from the data URI should be used to pick the correct extension.
|
||||
"""
|
||||
import base64
|
||||
import os
|
||||
|
||||
# 1x1 transparent PNG (minimal valid PNG bytes)
|
||||
PNG_BYTES = (
|
||||
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01'
|
||||
b'\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01'
|
||||
b'\x00\x00\x05\x00\x01\r\n-\xb4\x00\x00\x00\x00IEND\xaeB`\x82'
|
||||
)
|
||||
png_b64 = base64.b64encode(PNG_BYTES).decode()
|
||||
data_uri = f"data:image/png;base64,{png_b64}"
|
||||
|
||||
uuid = client.application.config.get('DATASTORE').add_watch(url='https://localhost')
|
||||
watch = live_server.app.config['DATASTORE'].data['watching'][uuid]
|
||||
|
||||
# Should NOT raise / bail — must save as favicon.png
|
||||
watch.bump_favicon(url=data_uri, favicon_base_64=png_b64, mime_type='image/png')
|
||||
|
||||
favicon_fname = watch.get_favicon_filename()
|
||||
assert favicon_fname is not None, "Favicon should have been saved"
|
||||
assert favicon_fname.endswith('.png'), f"Expected .png extension, got: {favicon_fname}"
|
||||
|
||||
full_path = os.path.join(watch.data_dir, favicon_fname)
|
||||
assert os.path.getsize(full_path) == len(PNG_BYTES)
|
||||
|
||||
# Also verify it's served correctly via the static route
|
||||
res = client.get(url_for('static_content', group='favicon', filename=uuid))
|
||||
assert res.status_code == 200
|
||||
assert res.data == PNG_BYTES
|
||||
|
||||
|
||||
def test_favicon_mime_type_overrides_url_extension(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
mime_type parameter takes precedence over the URL path extension.
|
||||
A URL ending in .ico but with mime_type='image/png' should save as .png.
|
||||
"""
|
||||
import base64
|
||||
import os
|
||||
|
||||
PNG_BYTES = (
|
||||
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01'
|
||||
b'\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01'
|
||||
b'\x00\x00\x05\x00\x01\r\n-\xb4\x00\x00\x00\x00IEND\xaeB`\x82'
|
||||
)
|
||||
png_b64 = base64.b64encode(PNG_BYTES).decode()
|
||||
|
||||
uuid = client.application.config.get('DATASTORE').add_watch(url='https://localhost')
|
||||
watch = live_server.app.config['DATASTORE'].data['watching'][uuid]
|
||||
|
||||
watch.bump_favicon(url='https://example.com/favicon.ico', favicon_base_64=png_b64, mime_type='image/png')
|
||||
|
||||
favicon_fname = watch.get_favicon_filename()
|
||||
assert favicon_fname is not None
|
||||
assert favicon_fname.endswith('.png'), f"mime_type should override URL extension, got: {favicon_fname}"
|
||||
|
||||
|
||||
def test_favicon_oversized_rejected(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""Favicons larger than 1 MB must be silently dropped."""
|
||||
import base64
|
||||
import os
|
||||
|
||||
oversized = b'\x00' * (1 * 1024 * 1024 + 1)
|
||||
oversized_b64 = base64.b64encode(oversized).decode()
|
||||
|
||||
uuid = client.application.config.get('DATASTORE').add_watch(url='https://localhost')
|
||||
watch = live_server.app.config['DATASTORE'].data['watching'][uuid]
|
||||
|
||||
result = watch.bump_favicon(url='https://example.com/big.png', favicon_base_64=oversized_b64, mime_type='image/png')
|
||||
|
||||
assert result is None, "bump_favicon should return None for oversized favicon"
|
||||
assert watch.get_favicon_filename() is None, "No favicon file should have been written"
|
||||
|
||||
|
||||
def test_bad_access(client, live_server, measure_memory_usage, datastore_path):
|
||||
|
||||
res = client.post(
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Integration tests for auto-applying tags to watches by URL pattern matching.
|
||||
|
||||
Verifies:
|
||||
- A tag with url_match_pattern shows on the watch overview list (via get_all_tags_for_watch)
|
||||
- The auto-applied tag appears on the watch edit page
|
||||
- A watch whose URL does NOT match the pattern does not get the tag
|
||||
"""
|
||||
|
||||
import json
|
||||
from flask import url_for
|
||||
from .util import set_original_response, live_server_setup
|
||||
|
||||
|
||||
def test_tag_url_pattern_shows_in_overview(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""Tag with a matching url_match_pattern must appear in the watch overview row."""
|
||||
set_original_response(datastore_path=datastore_path)
|
||||
|
||||
api_key = live_server.app.config['DATASTORE'].data['settings']['application'].get('api_access_token')
|
||||
|
||||
# Create a tag with a URL match pattern
|
||||
res = client.post(
|
||||
url_for("tag"),
|
||||
data=json.dumps({"title": "Auto GitHub", "url_match_pattern": "*github.com*"}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201, res.data
|
||||
tag_uuid = res.json['uuid']
|
||||
|
||||
# Add a watch that matches the pattern
|
||||
res = client.post(
|
||||
url_for("createwatch"),
|
||||
data=json.dumps({"url": "https://github.com/someuser/repo"}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201, res.data
|
||||
matching_watch_uuid = res.json['uuid']
|
||||
|
||||
# Add a watch that does NOT match
|
||||
res = client.post(
|
||||
url_for("createwatch"),
|
||||
data=json.dumps({"url": "https://example.com/page"}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201, res.data
|
||||
non_matching_watch_uuid = res.json['uuid']
|
||||
|
||||
# Watch overview — the tag label must appear in the matching watch's row
|
||||
res = client.get(url_for("watchlist.index"))
|
||||
assert res.status_code == 200
|
||||
html = res.get_data(as_text=True)
|
||||
|
||||
# The tag title should appear somewhere on the page (it's rendered per-watch via get_all_tags_for_watch)
|
||||
assert "Auto GitHub" in html, "Auto-matched tag title must appear in watch overview"
|
||||
|
||||
# Verify via the datastore directly that get_all_tags_for_watch returns the pattern-matched tag
|
||||
datastore = live_server.app.config['DATASTORE']
|
||||
|
||||
matching_tags = datastore.get_all_tags_for_watch(matching_watch_uuid)
|
||||
assert tag_uuid in matching_tags, "Pattern-matched tag must be returned for matching watch"
|
||||
|
||||
non_matching_tags = datastore.get_all_tags_for_watch(non_matching_watch_uuid)
|
||||
assert tag_uuid not in non_matching_tags, "Pattern-matched tag must NOT appear for non-matching watch"
|
||||
|
||||
|
||||
def test_auto_applied_tag_shows_on_watch_edit(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""The watch edit page must show auto-applied tags (from URL pattern) separately."""
|
||||
set_original_response(datastore_path=datastore_path)
|
||||
|
||||
api_key = live_server.app.config['DATASTORE'].data['settings']['application'].get('api_access_token')
|
||||
|
||||
res = client.post(
|
||||
url_for("tag"),
|
||||
data=json.dumps({"title": "Auto Docs", "url_match_pattern": "*docs.example.com*"}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201, res.data
|
||||
|
||||
res = client.post(
|
||||
url_for("createwatch"),
|
||||
data=json.dumps({"url": "https://docs.example.com/guide"}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201, res.data
|
||||
watch_uuid = res.json['uuid']
|
||||
|
||||
# Watch edit page must mention the auto-applied tag
|
||||
res = client.get(url_for("ui.ui_edit.edit_page", uuid=watch_uuid))
|
||||
assert res.status_code == 200
|
||||
html = res.get_data(as_text=True)
|
||||
|
||||
assert "Auto Docs" in html, "Auto-applied tag name must appear on watch edit page"
|
||||
assert "automatically applied" in html.lower() or "auto" in html.lower(), \
|
||||
"Watch edit page must indicate the tag is auto-applied by pattern"
|
||||
|
||||
|
||||
def test_multiple_pattern_tags_all_applied(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""A watch matching multiple tag patterns must receive all of them, not just the first."""
|
||||
set_original_response(datastore_path=datastore_path)
|
||||
|
||||
api_key = live_server.app.config['DATASTORE'].data['settings']['application'].get('api_access_token')
|
||||
|
||||
# Two tags with different patterns that both match the same URL
|
||||
res = client.post(
|
||||
url_for("tag"),
|
||||
data=json.dumps({"title": "Org Docs", "url_match_pattern": "*docs.*"}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201, res.data
|
||||
tag_docs_uuid = res.json['uuid']
|
||||
|
||||
res = client.post(
|
||||
url_for("tag"),
|
||||
data=json.dumps({"title": "Org Python", "url_match_pattern": "*python*"}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201, res.data
|
||||
tag_python_uuid = res.json['uuid']
|
||||
|
||||
# A third tag whose pattern does NOT match
|
||||
res = client.post(
|
||||
url_for("tag"),
|
||||
data=json.dumps({"title": "Org Rust", "url_match_pattern": "*rust-lang*"}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201, res.data
|
||||
tag_rust_uuid = res.json['uuid']
|
||||
|
||||
# Watch URL matches both "docs" and "python" patterns but not "rust"
|
||||
res = client.post(
|
||||
url_for("createwatch"),
|
||||
data=json.dumps({"url": "https://docs.python.org/3/library/fnmatch.html"}),
|
||||
headers={'content-type': 'application/json', 'x-api-key': api_key},
|
||||
)
|
||||
assert res.status_code == 201, res.data
|
||||
watch_uuid = res.json['uuid']
|
||||
|
||||
datastore = live_server.app.config['DATASTORE']
|
||||
resolved = datastore.get_all_tags_for_watch(watch_uuid)
|
||||
|
||||
assert tag_docs_uuid in resolved, "First matching tag must be included"
|
||||
assert tag_python_uuid in resolved, "Second matching tag must be included"
|
||||
assert tag_rust_uuid not in resolved, "Non-matching tag must NOT be included"
|
||||
@@ -70,6 +70,10 @@ def test_trigger_functionality(client, live_server, measure_memory_usage, datast
|
||||
uuid = client.application.config.get('DATASTORE').add_watch(url=test_url)
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
|
||||
|
||||
# And set the trigger text as 'ignore text', it should then not trigger
|
||||
live_server.app.config['DATASTORE'].data['settings']['application']['global_ignore_text'] = [trigger_text]
|
||||
|
||||
# Trigger a check
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
|
||||
@@ -122,6 +126,16 @@ def test_trigger_functionality(client, live_server, measure_memory_usage, datast
|
||||
# Now set the content which contains the trigger text
|
||||
set_modified_with_trigger_text_response(datastore_path=datastore_path)
|
||||
|
||||
# There is a "ignore text" set of the change that should be also the trigger, it should not trigger
|
||||
# because the ignore text should be stripped from the response, therefor, the trigger should not fire
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
res = client.get(url_for("watchlist.index"))
|
||||
assert b'has-unread-changes' not in res.data
|
||||
|
||||
|
||||
live_server.app.config['DATASTORE'].data['settings']['application']['global_ignore_text'] = []
|
||||
# check that the trigger fired once we stopped ignore it
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
res = client.get(url_for("watchlist.index"))
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Static analysis test: verify @login_optionally_required is always applied
|
||||
AFTER (inner to) @blueprint.route(), not before it.
|
||||
|
||||
In Flask, @route() must be the outermost decorator because it registers
|
||||
whatever function it receives. If @login_optionally_required is placed
|
||||
above @route(), the raw unprotected function gets registered and auth is
|
||||
silently bypassed (GHSA-jmrh-xmgh-x9j4).
|
||||
|
||||
Correct order (route outermost, auth inner):
|
||||
@blueprint.route('/path')
|
||||
@login_optionally_required
|
||||
def view(): ...
|
||||
|
||||
Wrong order (auth never called):
|
||||
@login_optionally_required ← registered by route, then discarded
|
||||
@blueprint.route('/path')
|
||||
def view(): ...
|
||||
"""
|
||||
|
||||
import ast
|
||||
import pathlib
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = pathlib.Path(__file__).parents[3] # …/changedetection.io/
|
||||
SOURCE_ROOT = REPO_ROOT / "changedetectionio"
|
||||
|
||||
|
||||
def _is_route_decorator(node: ast.expr) -> bool:
|
||||
"""Return True if the decorator looks like @something.route(...)."""
|
||||
return (
|
||||
isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == "route"
|
||||
)
|
||||
|
||||
|
||||
def _is_auth_decorator(node: ast.expr) -> bool:
|
||||
"""Return True if the decorator is @login_optionally_required."""
|
||||
return isinstance(node, ast.Name) and node.id == "login_optionally_required"
|
||||
|
||||
|
||||
def collect_violations() -> list[str]:
|
||||
violations = []
|
||||
|
||||
for path in SOURCE_ROOT.rglob("*.py"):
|
||||
try:
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||
except SyntaxError:
|
||||
continue
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
continue
|
||||
|
||||
decorators = node.decorator_list
|
||||
auth_indices = [i for i, d in enumerate(decorators) if _is_auth_decorator(d)]
|
||||
route_indices = [i for i, d in enumerate(decorators) if _is_route_decorator(d)]
|
||||
|
||||
# Bad order: auth decorator appears at a lower index (higher up) than a route decorator
|
||||
for auth_idx in auth_indices:
|
||||
for route_idx in route_indices:
|
||||
if auth_idx < route_idx:
|
||||
rel = path.relative_to(REPO_ROOT)
|
||||
violations.append(
|
||||
f"{rel}:{node.lineno} — `{node.name}`: "
|
||||
f"@login_optionally_required (line {decorators[auth_idx].lineno}) "
|
||||
f"is above @route (line {decorators[route_idx].lineno}); "
|
||||
f"auth wrapper will never be called"
|
||||
)
|
||||
|
||||
return violations
|
||||
|
||||
|
||||
def test_auth_decorator_order():
|
||||
violations = collect_violations()
|
||||
if violations:
|
||||
msg = (
|
||||
"\n\nFound routes where @login_optionally_required is placed ABOVE @blueprint.route().\n"
|
||||
"This silently disables authentication — @route() registers the raw function\n"
|
||||
"and the auth wrapper is never called.\n\n"
|
||||
"Fix: move @blueprint.route() to be the outermost (topmost) decorator.\n\n"
|
||||
+ "\n".join(f" • {v}" for v in violations)
|
||||
)
|
||||
pytest.fail(msg)
|
||||
@@ -64,7 +64,7 @@ class TestTriggerConditions(unittest.TestCase):
|
||||
"conditions": [
|
||||
{"operator": ">=", "field": "extracted_number", "value": "10"},
|
||||
{"operator": "<=", "field": "extracted_number", "value": "5000"},
|
||||
{"operator": "in", "field": "page_text", "value": "rock"},
|
||||
{"operator": "in", "field": "page_filtered_text", "value": "rock"},
|
||||
#{"operator": "starts_with", "field": "page_text", "value": "I saw"},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ from changedetectionio.diff import (
|
||||
CHANGED_PLACEMARKER_OPEN,
|
||||
CHANGED_PLACEMARKER_CLOSED,
|
||||
CHANGED_INTO_PLACEMARKER_OPEN,
|
||||
CHANGED_INTO_PLACEMARKER_CLOSED
|
||||
CHANGED_INTO_PLACEMARKER_CLOSED,
|
||||
extract_changed_from,
|
||||
extract_changed_to,
|
||||
)
|
||||
|
||||
|
||||
@@ -381,5 +383,140 @@ Line 3 with tabs and spaces"""
|
||||
self.assertNotIn('[-Line 2-]', output)
|
||||
self.assertNotIn('[+Line 2+]', output)
|
||||
|
||||
def test_diff_changed_from_to_word_level(self):
|
||||
"""Primary use case: extract just the old/new value from a changed line (e.g. price monitoring)"""
|
||||
before = "Widget costs $99.99 per month"
|
||||
after = "Widget costs $109.99 per month"
|
||||
|
||||
raw = diff.render_diff(before, after, word_diff=True)
|
||||
|
||||
self.assertEqual(extract_changed_from(raw), "$99.99")
|
||||
self.assertEqual(extract_changed_to(raw), "$109.99")
|
||||
|
||||
def test_diff_changed_from_to_multiple_changes(self):
|
||||
"""Multiple changed fragments on different lines are joined with newline.
|
||||
An unchanged line between the two changes ensures each is a 1-to-1 replace,
|
||||
so word_diff fires per line rather than falling back to multi-line block mode."""
|
||||
before = "Price $99\nunchanged\nTax $5"
|
||||
after = "Price $149\nunchanged\nTax $12"
|
||||
|
||||
raw = diff.render_diff(before, after, word_diff=True)
|
||||
|
||||
self.assertEqual(extract_changed_from(raw), "$99\n$5")
|
||||
self.assertEqual(extract_changed_to(raw), "$149\n$12")
|
||||
|
||||
def test_diff_changed_from_to_pure_insert_delete(self):
|
||||
"""Pure line additions/deletions (no inline word diff) are also captured"""
|
||||
before = "old line"
|
||||
after = "new line"
|
||||
|
||||
# word_diff=False forces line-level CHANGED markers
|
||||
raw = diff.render_diff(before, after, word_diff=False)
|
||||
|
||||
self.assertEqual(extract_changed_from(raw), "old line")
|
||||
self.assertEqual(extract_changed_to(raw), "new line")
|
||||
|
||||
def test_diff_changed_from_to_similar_numbers(self):
|
||||
"""$90.00 → $9.00 must not produce a partial match like '0.00'.
|
||||
The tokenizer splits on whitespace only, so '$90.00' and '$9.00' are
|
||||
each a single atomic token — diff never sees their internal characters."""
|
||||
before = "for sale $90.00"
|
||||
after = "for sale $9.00"
|
||||
|
||||
raw = diff.render_diff(before, after, word_diff=True)
|
||||
|
||||
self.assertEqual(extract_changed_from(raw), "$90.00")
|
||||
self.assertEqual(extract_changed_to(raw), "$9.00")
|
||||
|
||||
def test_diff_changed_from_to_whole_line_replaced(self):
|
||||
"""When every token on the line changed (no common tokens), render_inline_word_diff
|
||||
takes the whole_line_replaced path using CHANGED/CHANGED_INTO markers instead of
|
||||
REMOVED/ADDED. Extraction must still work via the alternation in the regex."""
|
||||
before = "$99"
|
||||
after = "$109"
|
||||
|
||||
raw = diff.render_diff(before, after, word_diff=True)
|
||||
|
||||
self.assertEqual(extract_changed_from(raw), "$99")
|
||||
self.assertEqual(extract_changed_to(raw), "$109")
|
||||
|
||||
def test_diff_changed_from_to_multiple_words_same_line(self):
|
||||
"""When multiple words change on the same line all fragments are joined with newline.
|
||||
'quick brown fox jumps' -> 'slow brown fox hops' gives 'quick\njumps' / 'slow\nhops'.
|
||||
These tokens work best when a single value changes per line."""
|
||||
before = "quick brown fox jumps"
|
||||
after = "slow brown fox hops"
|
||||
|
||||
raw = diff.render_diff(before, after, word_diff=True)
|
||||
|
||||
self.assertEqual(extract_changed_from(raw), "quick\njumps")
|
||||
self.assertEqual(extract_changed_to(raw), "slow\nhops")
|
||||
|
||||
def test_diff_changed_from_to_no_change(self):
|
||||
"""No changes → empty string"""
|
||||
content = "nothing changed here"
|
||||
|
||||
raw = diff.render_diff(content, content, word_diff=True)
|
||||
|
||||
self.assertEqual(extract_changed_from(raw), "")
|
||||
self.assertEqual(extract_changed_to(raw), "")
|
||||
|
||||
|
||||
def test_word_diff_no_prefix_whole_line_replaced(self):
|
||||
"""When include_change_type_prefix=False, word-level diffs for whole-line
|
||||
replacements must not include placemarkers (issue #3816)."""
|
||||
before = "73"
|
||||
after = "100"
|
||||
|
||||
raw = diff.render_diff(before, after, word_diff=True, include_change_type_prefix=False)
|
||||
|
||||
self.assertNotIn('PLACEMARKER', raw)
|
||||
# Should contain just the raw values separated by newline
|
||||
self.assertIn('73', raw)
|
||||
self.assertIn('100', raw)
|
||||
|
||||
def test_word_diff_no_prefix_inline_changes(self):
|
||||
"""When include_change_type_prefix=False, inline word-level diffs
|
||||
must not include placemarkers (issue #3816)."""
|
||||
before = "the price is 50 dollars"
|
||||
after = "the price is 75 dollars"
|
||||
|
||||
raw = diff.render_diff(before, after, word_diff=True, include_change_type_prefix=False)
|
||||
|
||||
self.assertNotIn('PLACEMARKER', raw)
|
||||
self.assertIn('50', raw)
|
||||
self.assertIn('75', raw)
|
||||
|
||||
def test_word_diff_with_prefix_still_wraps(self):
|
||||
"""Default include_change_type_prefix=True must still wrap tokens."""
|
||||
before = "73"
|
||||
after = "100"
|
||||
|
||||
raw = diff.render_diff(before, after, word_diff=True, include_change_type_prefix=True)
|
||||
|
||||
self.assertIn('PLACEMARKER', raw)
|
||||
|
||||
def test_word_diff_no_prefix_exact_output(self):
|
||||
"""Pin exact output for include_change_type_prefix=False to catch regressions.
|
||||
|
||||
Whole-line replacement: old and new values separated by newline, no markers.
|
||||
Inline partial replacement: equal tokens kept, changed tokens (both old and new)
|
||||
appended without markers — this means old+new are concatenated in place.
|
||||
"""
|
||||
# Whole-line replaced: both values on separate lines, clean
|
||||
raw = diff.render_diff('73', '100', word_diff=True, include_change_type_prefix=False)
|
||||
self.assertEqual(raw, '73\n100')
|
||||
|
||||
# Inline word replacement: equal context preserved, old+new token concatenated in-place
|
||||
raw = diff.render_diff('the price is 50 dollars', 'the price is 75 dollars',
|
||||
word_diff=True, include_change_type_prefix=False)
|
||||
self.assertEqual(raw, 'the price is 5075 dollars')
|
||||
|
||||
# Sanity: with prefix the whole-line case is fully wrapped
|
||||
raw = diff.render_diff('73', '100', word_diff=True, include_change_type_prefix=True)
|
||||
self.assertEqual(raw, '@changed_PLACEMARKER_OPEN73@changed_PLACEMARKER_CLOSED\n'
|
||||
'@changed_into_PLACEMARKER_OPEN100@changed_into_PLACEMARKER_CLOSED')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Unit test for send_step_failure_notification regression.
|
||||
|
||||
Before the fix, line 499 called self._check_cascading_vars('notification_format', watch)
|
||||
which raises AttributeError because _check_cascading_vars is a module-level function,
|
||||
not a method of NotificationService.
|
||||
"""
|
||||
|
||||
import queue
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
def _make_datastore(watch_uuid, notification_url):
|
||||
"""Minimal datastore mock that NotificationService and _check_cascading_vars need."""
|
||||
watch = MagicMock()
|
||||
watch.get = lambda key, default=None: {
|
||||
'uuid': watch_uuid,
|
||||
'url': 'https://example.com',
|
||||
'notification_urls': [notification_url],
|
||||
'notification_format': '',
|
||||
'notification_muted': False,
|
||||
}.get(key, default)
|
||||
watch.__getitem__ = lambda self, key: watch.get(key)
|
||||
|
||||
datastore = MagicMock()
|
||||
datastore.data = {
|
||||
'watching': {watch_uuid: watch},
|
||||
'settings': {
|
||||
'application': {
|
||||
'notification_urls': [],
|
||||
'notification_format': 'text',
|
||||
'filter_failure_notification_threshold_attempts': 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
datastore.get_all_tags_for_watch.return_value = {}
|
||||
return datastore, watch
|
||||
|
||||
|
||||
def test_send_step_failure_notification_does_not_raise():
|
||||
"""send_step_failure_notification must not raise AttributeError (wrong self. prefix on module-level function)."""
|
||||
from changedetectionio.notification_service import NotificationService
|
||||
|
||||
watch_uuid = 'test-uuid-1234'
|
||||
notification_q = queue.Queue()
|
||||
datastore, _ = _make_datastore(watch_uuid, 'post://localhost/test')
|
||||
service = NotificationService(datastore=datastore, notification_q=notification_q)
|
||||
|
||||
# Before the fix this raised:
|
||||
# AttributeError: 'NotificationService' object has no attribute '_check_cascading_vars'
|
||||
service.send_step_failure_notification(watch_uuid=watch_uuid, step_n=0)
|
||||
|
||||
|
||||
def test_send_step_failure_notification_queues_item():
|
||||
"""A notification object should be placed on the queue when URLs are configured."""
|
||||
from changedetectionio.notification_service import NotificationService
|
||||
|
||||
watch_uuid = 'test-uuid-5678'
|
||||
notification_q = queue.Queue()
|
||||
datastore, _ = _make_datastore(watch_uuid, 'post://localhost/test')
|
||||
service = NotificationService(datastore=datastore, notification_q=notification_q)
|
||||
|
||||
service.send_step_failure_notification(watch_uuid=watch_uuid, step_n=1)
|
||||
|
||||
assert not notification_q.empty(), "Expected a notification to be queued"
|
||||
item = notification_q.get_nowait()
|
||||
assert 'notification_title' in item
|
||||
assert 'position 2' in item['notification_title']
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# run from dir above changedetectionio/ dir
|
||||
# python3 -m unittest changedetectionio.tests.unit.test_tag_url_match
|
||||
|
||||
import unittest
|
||||
from changedetectionio.model.Tag import model as TagModel
|
||||
|
||||
|
||||
def make_tag(pattern):
|
||||
"""Minimal Tag instance for testing matches_url — skips datastore wiring."""
|
||||
tag = TagModel.__new__(TagModel)
|
||||
dict.__init__(tag)
|
||||
tag['url_match_pattern'] = pattern
|
||||
return tag
|
||||
|
||||
|
||||
class TestTagUrlMatch(unittest.TestCase):
|
||||
|
||||
def test_wildcard_matches(self):
|
||||
tag = make_tag('*example.com*')
|
||||
self.assertTrue(tag.matches_url('https://example.com/page'))
|
||||
self.assertTrue(tag.matches_url('https://www.example.com/shop/item'))
|
||||
self.assertFalse(tag.matches_url('https://other.com/page'))
|
||||
|
||||
def test_wildcard_case_insensitive(self):
|
||||
tag = make_tag('*EXAMPLE.COM*')
|
||||
self.assertTrue(tag.matches_url('https://example.com/page'))
|
||||
|
||||
def test_substring_match(self):
|
||||
tag = make_tag('github.com/myorg')
|
||||
self.assertTrue(tag.matches_url('https://github.com/myorg/repo'))
|
||||
self.assertFalse(tag.matches_url('https://github.com/otherorg/repo'))
|
||||
|
||||
def test_substring_case_insensitive(self):
|
||||
tag = make_tag('GitHub.com/MyOrg')
|
||||
self.assertTrue(tag.matches_url('https://github.com/myorg/repo'))
|
||||
|
||||
def test_empty_pattern_never_matches(self):
|
||||
tag = make_tag('')
|
||||
self.assertFalse(tag.matches_url('https://example.com'))
|
||||
|
||||
def test_empty_url_never_matches(self):
|
||||
tag = make_tag('*example.com*')
|
||||
self.assertFalse(tag.matches_url(''))
|
||||
|
||||
def test_question_mark_wildcard(self):
|
||||
tag = make_tag('https://example.com/item-?')
|
||||
self.assertTrue(tag.matches_url('https://example.com/item-1'))
|
||||
self.assertFalse(tag.matches_url('https://example.com/item-12'))
|
||||
|
||||
def test_substring_is_broad(self):
|
||||
"""Plain substring matching is intentionally broad — 'evil.com' matches anywhere
|
||||
in the URL string, including 'notevil.com'. Users who need precise domain matching
|
||||
should use a wildcard pattern like '*://evil.com/*' instead."""
|
||||
tag = make_tag('evil.com')
|
||||
self.assertTrue(tag.matches_url('https://evil.com/page'))
|
||||
self.assertTrue(tag.matches_url('https://notevil.com')) # substring match — expected
|
||||
|
||||
def test_precise_domain_match_with_wildcard(self):
|
||||
"""Use wildcard pattern for precise domain matching to avoid substring surprises."""
|
||||
tag = make_tag('*://evil.com/*')
|
||||
self.assertTrue(tag.matches_url('https://evil.com/page'))
|
||||
self.assertFalse(tag.matches_url('https://notevil.com/page'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -76,7 +76,9 @@ These commands read settings from `../../setup.cfg` automatically.
|
||||
- `en_US` - English (US)
|
||||
- `fr` - French (Français)
|
||||
- `it` - Italian (Italiano)
|
||||
- `ja` - Japanese (日本語)
|
||||
- `ko` - Korean (한국어)
|
||||
- `pt_BR` - Portuguese (Brasil)
|
||||
- `zh` - Chinese Simplified (中文简体)
|
||||
- `zh_Hant_TW` - Chinese Traditional (繁體中文)
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
|
||||
"POT-Creation-Date: 2026-04-15 03:04+0900\n"
|
||||
"PO-Revision-Date: 2026-01-14 03:57+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: de\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.16.0\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/backups/__init__.py
|
||||
msgid "A backup is already running, check back in a few minutes"
|
||||
@@ -74,6 +74,11 @@ msgstr ""
|
||||
msgid "File must be a .zip backup file"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
#, python-format
|
||||
msgid "Backup file is too large (max %(mb)s MB)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
msgid "Invalid or corrupted zip file"
|
||||
msgstr ""
|
||||
@@ -130,6 +135,11 @@ msgstr ""
|
||||
msgid "Note: This does not override the main application settings, only watches and groups."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
#, python-format
|
||||
msgid "Max upload size: %(upload)s MB, Max decompressed size: %(decomp)s MB"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
msgid "Include all groups found in backup?"
|
||||
msgstr ""
|
||||
@@ -204,6 +214,10 @@ msgstr "Distill.io"
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX & Wachete"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Backup Restore"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Restoring changedetection.io backups is in the"
|
||||
msgstr ""
|
||||
@@ -561,15 +575,15 @@ msgstr ""
|
||||
msgid "all of the ways that the browser is detected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Connect using Bright Data proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html changedetectionio/templates/_common_fields.html
|
||||
msgid "Tip:"
|
||||
msgstr "Tipp:"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr "Verbinden Sie sich über Bright Data und Oxylabs Proxies. Weitere Informationen finden Sie hier."
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Ignore whitespace, tabs and new-lines/line-feeds when considering if a change was detected."
|
||||
msgstr ""
|
||||
@@ -590,15 +604,15 @@ msgstr ""
|
||||
msgid "Changing this could affect the content of your existing watches, possibly trigger alerts etc."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Remove HTML element(s) by CSS and XPath selectors before text conversion."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Don't paste HTML here, use only CSS and XPath selectors"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML."
|
||||
msgstr ""
|
||||
|
||||
@@ -763,7 +777,7 @@ msgid "Tip"
|
||||
msgstr "Tipp"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successful than \"Data Center\" for blocked websites."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
@@ -829,10 +843,48 @@ msgstr "Tag nicht gefunden"
|
||||
msgid "Updated"
|
||||
msgstr "Aktualisiert"
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Activate for individual watches in this tag/group?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Auto-apply to watches with URLs matching"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "e.g. *://example.com/* or github.com/myorg"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Tag colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Tag name"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Filters & Triggers"
|
||||
msgstr "Filter und Trigger"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid ""
|
||||
"Automatically applies this tag to any watch whose URL matches. Supports wildcards: <code>*example.com*</code> or "
|
||||
"plain substring: <code>github.com/myorg</code>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Currently matching watches"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Custom colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are"
|
||||
msgstr "Diese Einstellungen sind"
|
||||
@@ -912,7 +964,11 @@ msgstr "Tag-/Labelname"
|
||||
msgid "No website organisational tags/groups configured"
|
||||
msgstr "Keine Gruppen/Labels konfiguriert"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "Benachrichtigungen stummschalten"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/edit.py
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Edit"
|
||||
msgstr "Bearbeiten"
|
||||
@@ -1031,6 +1087,10 @@ msgstr "Überwachung nicht gefunden"
|
||||
msgid "Cleared snapshot history for watch {}"
|
||||
msgstr "Snapshot-Verlauf für Beobachtung {} gelöscht"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr "löschen"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
msgid "History clearing started in background"
|
||||
msgstr ""
|
||||
@@ -1115,6 +1175,14 @@ msgstr ""
|
||||
msgid "Could not load '{}' processor, processor plugin might be missing."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "System settings default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Updated watch - unpaused!"
|
||||
msgstr "Aktualisierte Überwachung – fortgesetzt!"
|
||||
@@ -1127,6 +1195,10 @@ msgstr "Überwachung aktualisiert."
|
||||
msgid "Preview unavailable - No fetch/check completed or triggers not reached"
|
||||
msgstr "Vorschau nicht verfügbar – Kein Abruf/keine Überprüfung abgeschlossen oder Trigger nicht erreicht"
|
||||
|
||||
#: changedetectionio/blueprint/ui/preview.py
|
||||
msgid "Diff"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "This will remove version history (snapshots) for ALL watches, but keep your list of URLs!"
|
||||
msgstr ""
|
||||
@@ -1153,10 +1225,6 @@ msgstr "Bestätigungstext"
|
||||
msgid "Type in the word"
|
||||
msgstr "Geben Sie das Wort ein"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr "löschen"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "to confirm that you understand."
|
||||
msgstr "um zu bestätigen, dass Sie es verstanden haben."
|
||||
@@ -1242,14 +1310,17 @@ msgid "Jump"
|
||||
msgstr "Springen"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Text"
|
||||
msgstr "Fehlertext"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Screenshot"
|
||||
msgstr "Fehler-Screenshot"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Text"
|
||||
msgstr "Text"
|
||||
|
||||
@@ -1257,7 +1328,8 @@ msgstr "Text"
|
||||
msgid "Current screenshot"
|
||||
msgstr "Aktueller Screenshot"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/processors/extract.py
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Extract Data"
|
||||
msgstr "Daten extrahieren"
|
||||
|
||||
@@ -1349,6 +1421,10 @@ msgstr "Hilfe und Beispiele finden Sie hier"
|
||||
msgid "Organisational tag/group name used in the main listing page"
|
||||
msgstr "Gruppen-/Label-NameGruppen-/Label-NameOrganisations-Tag/Gruppenname, der auf der Haupteintragsseite verwendet wird"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Also automatically applied by URL pattern:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Automatically uses the page title if found, you can also use your own title/description here"
|
||||
msgstr ""
|
||||
@@ -1381,6 +1457,10 @@ msgstr ""
|
||||
"Die Methode erfordert eine Netzwerkverbindung zu einem laufenden WebDriver+Chrome-Server, der durch die "
|
||||
"Umgebungsvariable „WEBDRIVER_URL“ festgelegt wird."
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr "Verbinden Sie sich über Bright Data und Oxylabs Proxies. Weitere Informationen finden Sie hier."
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Check/Scan all"
|
||||
msgstr "Überprüfen Sie alles noch einmal"
|
||||
@@ -1617,7 +1697,7 @@ msgstr "Bereich zeichnen"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Clear selection"
|
||||
msgstr "Klare Auswahl"
|
||||
msgstr "Auswahl löschen"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "One moment, fetching screenshot and element information.."
|
||||
@@ -1863,6 +1943,26 @@ msgstr "Es sind keine Website-Überwachungen konfiguriert. Bitte fügen Sie im F
|
||||
msgid "import a list"
|
||||
msgstr "eine Liste importieren"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Pause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnPause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Mute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnMute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Create a link to share watch config with others"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Detecting restock and price"
|
||||
msgstr "Erkennen von Lagerbeständen und Preisen"
|
||||
@@ -1892,6 +1992,7 @@ msgid "Queued"
|
||||
msgstr "Wartend"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
#: changedetectionio/processors/text_json_diff/difference.py
|
||||
msgid "History"
|
||||
msgstr "Verlauf"
|
||||
|
||||
@@ -1925,6 +2026,168 @@ msgstr "Überprüfen Sie alles noch einmal"
|
||||
msgid "in '%(title)s'"
|
||||
msgstr "in '%(title)s'"
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Not Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Contains"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Does NOT Contain"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Ends With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length minimum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length maximum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Matches Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Does NOT Match Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Extracted number after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Page text after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "A value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Value is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text change distance"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Not enough history to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Snapshot too large for edit statistics, skipping."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Unable to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein Text Similarity Details"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Raw distance (edits needed)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Percent similar"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid ""
|
||||
"Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one "
|
||||
"into the other."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Error calculating Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count of content"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Content Analysis"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count (latest snapshot)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count is a simple measure of content length, calculated by splitting text on whitespace."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/content_fetchers/requests.py
|
||||
msgid "Basic fast Plaintext/HTTP Client"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/flask_app.py
|
||||
#: changedetectionio/realtime/socket_server.py
|
||||
msgid "Not yet"
|
||||
@@ -2209,10 +2472,26 @@ msgstr "UI-Optionen"
|
||||
msgid "Selector"
|
||||
msgstr "Auswahlmodus:"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "CSS or xPath selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "value"
|
||||
msgstr "Wert"
|
||||
|
||||
#: changedetectionio/conditions/form.py changedetectionio/forms.py
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Web Page URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group Tag"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Time Between Check"
|
||||
msgstr "Prüfintervall"
|
||||
@@ -2229,6 +2508,10 @@ msgstr "CSS/xPath-Filter"
|
||||
msgid "Remove elements"
|
||||
msgstr "Elemente entfernen"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract lines containing"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract text"
|
||||
msgstr "Daten extrahieren"
|
||||
@@ -2297,7 +2580,8 @@ msgstr "Blockieren Sie die Änderungserkennung, während der Text übereinstimmt
|
||||
msgid "Execute JavaScript before change detection"
|
||||
msgstr "Führen Sie JavaScript vor der Änderungserkennung aus"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Save"
|
||||
msgstr "Speichern"
|
||||
|
||||
@@ -2366,7 +2650,7 @@ msgstr "Ungültige Vorlagensyntax: %(error)s"
|
||||
msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr "Ungültige Vorlagensyntax im Header „%(header)s“: %(error)s"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
@@ -2462,6 +2746,10 @@ msgstr "Text ignorieren"
|
||||
msgid "Ignore whitespace"
|
||||
msgstr "Leerzeichen ignorieren"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Screenshot: Minimum Change Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py changedetectionio/processors/image_ssim_diff/forms.py
|
||||
msgid "Must be between 0 and 100"
|
||||
msgstr "Muss zwischen 0 und 100 liegen"
|
||||
@@ -2663,6 +2951,42 @@ msgstr "Wiederauffüllung und Preiserkennung für Seiten mit einem EINZELNEN Pro
|
||||
msgid "Detects if the product goes back to in-stock"
|
||||
msgstr "Erkennt, ob das Produkt wieder auf Lager ist"
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "This tool will extract text data from all of the watch history."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "For example, to extract only the numbers from text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Raw text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "RegEx to extract:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Be sure to test your RegEx here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Each RegEx group bracket"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "will be in its own column, the first column value is always the date."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/text_json_diff/processor.py
|
||||
msgid "Webpage Text/HTML, JSON and PDF changes"
|
||||
msgstr "Änderungen an Webseitentext/HTML, JSON und PDF"
|
||||
@@ -2729,6 +3053,11 @@ msgstr "Die Überwachungsgruppe / Tag"
|
||||
msgid "The URL of the preview page generated by changedetection.io."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
msgstr ""
|
||||
@@ -2737,6 +3066,14 @@ msgstr ""
|
||||
msgid "The diff output - only changes, additions, and removals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "All diff variants accept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "args, e.g."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The diff output - only changes, additions, and removals —"
|
||||
msgstr ""
|
||||
@@ -2773,6 +3110,18 @@ msgstr ""
|
||||
msgid "The diff output - patch in unified format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the previous version — e.g. the old price. Best when a single value changes per "
|
||||
"line; multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the new version — e.g. the new price. Best when a single value changes per line; "
|
||||
"multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The current snapshot text contents value, useful when combined with JSON or CSS filters"
|
||||
msgstr ""
|
||||
@@ -2817,7 +3166,7 @@ msgstr ""
|
||||
msgid "Use"
|
||||
msgstr "Verwenden"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#: changedetectionio/templates/_common_fields.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Show advanced help and tips"
|
||||
msgstr "Erweiterte Hilfe und Tipps anzeigen"
|
||||
|
||||
@@ -2921,6 +3270,26 @@ msgstr ""
|
||||
msgid "Format for all notifications"
|
||||
msgstr "Format für alle Benachrichtigungen"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Discord does not render HTML — switch to"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Plain Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "format to avoid"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "and other HTML entities appearing literally in your notifications."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
@@ -3037,10 +3406,18 @@ msgstr "Es wird keine Änderungserkennung stattfinden, da dieser Text existiert.
|
||||
msgid "Blocked text"
|
||||
msgstr "Blockierter Text"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "A new version is available"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr "Suchen oder Alt+S-Taste verwenden"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Share this link:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Real-time updates offline"
|
||||
msgstr "Echtzeit-Updates offline"
|
||||
@@ -3121,6 +3498,26 @@ msgstr ""
|
||||
msgid "All lines here must not exist (think of each line as \"OR\")"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Keep only lines that contain any of these words or phrases (plain text, case-insensitive)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "One entry per line — any line in the page text that contains a match is kept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Simpler alternative to regex — use this when you just want lines about a specific topic"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Example: enter"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "to keep only lines mentioning temperature readings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Extracts text in the final output (line by line) after other filters using regular expressions or string match:"
|
||||
msgstr ""
|
||||
@@ -3193,10 +3590,6 @@ msgstr ""
|
||||
msgid "Unmute notifications"
|
||||
msgstr "Benachrichtigungen entstummen"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "Benachrichtigungen stummschalten"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Notifications are muted - click to unmute"
|
||||
msgstr "Benachrichtigungen sind stummgeschaltet - klicken zum Entstummen"
|
||||
@@ -3385,3 +3778,6 @@ msgstr "Haupteinstellungen"
|
||||
#~ msgid "Marking watches as viewed in background..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
#~ msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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: 2026-02-23 03:54+0100\n"
|
||||
"POT-Creation-Date: 2026-04-15 03:04+0900\n"
|
||||
"PO-Revision-Date: 2026-01-12 16:33+0100\n"
|
||||
"Last-Translator: British English Translation Team\n"
|
||||
"Language: en_GB\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.16.0\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/backups/__init__.py
|
||||
msgid "A backup is already running, check back in a few minutes"
|
||||
@@ -74,6 +74,11 @@ msgstr ""
|
||||
msgid "File must be a .zip backup file"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
#, python-format
|
||||
msgid "Backup file is too large (max %(mb)s MB)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
msgid "Invalid or corrupted zip file"
|
||||
msgstr ""
|
||||
@@ -128,6 +133,11 @@ msgstr ""
|
||||
msgid "Note: This does not override the main application settings, only watches and groups."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
#, python-format
|
||||
msgid "Max upload size: %(upload)s MB, Max decompressed size: %(decomp)s MB"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
msgid "Include all groups found in backup?"
|
||||
msgstr ""
|
||||
@@ -202,6 +212,10 @@ msgstr ""
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Backup Restore"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Restoring changedetection.io backups is in the"
|
||||
msgstr ""
|
||||
@@ -547,15 +561,15 @@ msgstr ""
|
||||
msgid "all of the ways that the browser is detected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Connect using Bright Data proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html changedetectionio/templates/_common_fields.html
|
||||
msgid "Tip:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Ignore whitespace, tabs and new-lines/line-feeds when considering if a change was detected."
|
||||
msgstr ""
|
||||
@@ -576,15 +590,15 @@ msgstr ""
|
||||
msgid "Changing this could affect the content of your existing watches, possibly trigger alerts etc."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Remove HTML element(s) by CSS and XPath selectors before text conversion."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Don't paste HTML here, use only CSS and XPath selectors"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML."
|
||||
msgstr ""
|
||||
|
||||
@@ -749,7 +763,7 @@ msgid "Tip"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successful than \"Data Center\" for blocked websites."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
@@ -815,10 +829,48 @@ msgstr ""
|
||||
msgid "Updated"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Activate for individual watches in this tag/group?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Auto-apply to watches with URLs matching"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "e.g. *://example.com/* or github.com/myorg"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Tag colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Tag name"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Filters & Triggers"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid ""
|
||||
"Automatically applies this tag to any watch whose URL matches. Supports wildcards: <code>*example.com*</code> or "
|
||||
"plain substring: <code>github.com/myorg</code>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Currently matching watches"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Custom colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are"
|
||||
msgstr ""
|
||||
@@ -896,7 +948,11 @@ msgstr ""
|
||||
msgid "No website organisational tags/groups configured"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/edit.py
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
@@ -1011,6 +1067,10 @@ msgstr ""
|
||||
msgid "Cleared snapshot history for watch {}"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
msgid "History clearing started in background"
|
||||
msgstr ""
|
||||
@@ -1095,6 +1155,14 @@ msgstr ""
|
||||
msgid "Could not load '{}' processor, processor plugin might be missing."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "System settings default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Updated watch - unpaused!"
|
||||
msgstr ""
|
||||
@@ -1107,6 +1175,10 @@ msgstr ""
|
||||
msgid "Preview unavailable - No fetch/check completed or triggers not reached"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/preview.py
|
||||
msgid "Diff"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "This will remove version history (snapshots) for ALL watches, but keep your list of URLs!"
|
||||
msgstr ""
|
||||
@@ -1131,10 +1203,6 @@ msgstr ""
|
||||
msgid "Type in the word"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "to confirm that you understand."
|
||||
msgstr ""
|
||||
@@ -1220,14 +1288,17 @@ msgid "Jump"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Text"
|
||||
msgstr ""
|
||||
|
||||
@@ -1235,7 +1306,8 @@ msgstr ""
|
||||
msgid "Current screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/processors/extract.py
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Extract Data"
|
||||
msgstr ""
|
||||
|
||||
@@ -1327,6 +1399,10 @@ msgstr ""
|
||||
msgid "Organisational tag/group name used in the main listing page"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Also automatically applied by URL pattern:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Automatically uses the page title if found, you can also use your own title/description here"
|
||||
msgstr ""
|
||||
@@ -1353,6 +1429,10 @@ msgstr ""
|
||||
msgid "method requires a network connection to a running WebDriver+Chrome server, set by the ENV var 'WEBDRIVER_URL'."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Check/Scan all"
|
||||
msgstr ""
|
||||
@@ -1817,6 +1897,26 @@ msgstr ""
|
||||
msgid "import a list"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Pause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnPause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Mute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnMute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Create a link to share watch config with others"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Detecting restock and price"
|
||||
msgstr ""
|
||||
@@ -1846,6 +1946,7 @@ msgid "Queued"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
#: changedetectionio/processors/text_json_diff/difference.py
|
||||
msgid "History"
|
||||
msgstr ""
|
||||
|
||||
@@ -1879,6 +1980,168 @@ msgstr ""
|
||||
msgid "in '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Not Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Contains"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Does NOT Contain"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Ends With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length minimum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length maximum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Matches Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Does NOT Match Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Extracted number after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Page text after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "A value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Value is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text change distance"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Not enough history to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Snapshot too large for edit statistics, skipping."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Unable to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein Text Similarity Details"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Raw distance (edits needed)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Percent similar"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid ""
|
||||
"Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one "
|
||||
"into the other."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Error calculating Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count of content"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Content Analysis"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count (latest snapshot)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count is a simple measure of content length, calculated by splitting text on whitespace."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/content_fetchers/requests.py
|
||||
msgid "Basic fast Plaintext/HTTP Client"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/flask_app.py
|
||||
#: changedetectionio/realtime/socket_server.py
|
||||
msgid "Not yet"
|
||||
@@ -2161,10 +2424,26 @@ msgstr ""
|
||||
msgid "Selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "CSS or xPath selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py changedetectionio/forms.py
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Web Page URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group Tag"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Time Between Check"
|
||||
msgstr ""
|
||||
@@ -2181,6 +2460,10 @@ msgstr ""
|
||||
msgid "Remove elements"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract lines containing"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract text"
|
||||
msgstr ""
|
||||
@@ -2249,7 +2532,8 @@ msgstr ""
|
||||
msgid "Execute JavaScript before change detection"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
@@ -2317,7 +2601,7 @@ msgstr ""
|
||||
msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
@@ -2413,6 +2697,10 @@ msgstr ""
|
||||
msgid "Ignore whitespace"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Screenshot: Minimum Change Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py changedetectionio/processors/image_ssim_diff/forms.py
|
||||
msgid "Must be between 0 and 100"
|
||||
msgstr ""
|
||||
@@ -2612,6 +2900,42 @@ msgstr ""
|
||||
msgid "Detects if the product goes back to in-stock"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "This tool will extract text data from all of the watch history."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "For example, to extract only the numbers from text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Raw text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "RegEx to extract:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Be sure to test your RegEx here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Each RegEx group bracket"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "will be in its own column, the first column value is always the date."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/text_json_diff/processor.py
|
||||
msgid "Webpage Text/HTML, JSON and PDF changes"
|
||||
msgstr ""
|
||||
@@ -2678,6 +3002,11 @@ msgstr ""
|
||||
msgid "The URL of the preview page generated by changedetection.io."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
msgstr ""
|
||||
@@ -2686,6 +3015,14 @@ msgstr ""
|
||||
msgid "The diff output - only changes, additions, and removals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "All diff variants accept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "args, e.g."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The diff output - only changes, additions, and removals —"
|
||||
msgstr ""
|
||||
@@ -2722,6 +3059,18 @@ msgstr ""
|
||||
msgid "The diff output - patch in unified format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the previous version — e.g. the old price. Best when a single value changes per "
|
||||
"line; multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the new version — e.g. the new price. Best when a single value changes per line; "
|
||||
"multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The current snapshot text contents value, useful when combined with JSON or CSS filters"
|
||||
msgstr ""
|
||||
@@ -2766,7 +3115,7 @@ msgstr ""
|
||||
msgid "Use"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#: changedetectionio/templates/_common_fields.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Show advanced help and tips"
|
||||
msgstr ""
|
||||
|
||||
@@ -2870,6 +3219,26 @@ msgstr ""
|
||||
msgid "Format for all notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Discord does not render HTML — switch to"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Plain Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "format to avoid"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "and other HTML entities appearing literally in your notifications."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
@@ -2986,10 +3355,18 @@ msgstr ""
|
||||
msgid "Blocked text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "A new version is available"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Share this link:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Real-time updates offline"
|
||||
msgstr ""
|
||||
@@ -3068,6 +3445,26 @@ msgstr ""
|
||||
msgid "All lines here must not exist (think of each line as \"OR\")"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Keep only lines that contain any of these words or phrases (plain text, case-insensitive)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "One entry per line — any line in the page text that contains a match is kept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Simpler alternative to regex — use this when you just want lines about a specific topic"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Example: enter"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "to keep only lines mentioning temperature readings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Extracts text in the final output (line by line) after other filters using regular expressions or string match:"
|
||||
msgstr ""
|
||||
@@ -3140,10 +3537,6 @@ msgstr ""
|
||||
msgid "Unmute notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Notifications are muted - click to unmute"
|
||||
msgstr ""
|
||||
@@ -3215,3 +3608,6 @@ msgstr ""
|
||||
#~ msgid "Marking watches as viewed in background..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
#~ msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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: 2026-02-23 03:54+0100\n"
|
||||
"POT-Creation-Date: 2026-04-15 03:04+0900\n"
|
||||
"PO-Revision-Date: 2026-01-12 16:37+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en_US\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.16.0\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/backups/__init__.py
|
||||
msgid "A backup is already running, check back in a few minutes"
|
||||
@@ -74,6 +74,11 @@ msgstr ""
|
||||
msgid "File must be a .zip backup file"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
#, python-format
|
||||
msgid "Backup file is too large (max %(mb)s MB)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
msgid "Invalid or corrupted zip file"
|
||||
msgstr ""
|
||||
@@ -128,6 +133,11 @@ msgstr ""
|
||||
msgid "Note: This does not override the main application settings, only watches and groups."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
#, python-format
|
||||
msgid "Max upload size: %(upload)s MB, Max decompressed size: %(decomp)s MB"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
msgid "Include all groups found in backup?"
|
||||
msgstr ""
|
||||
@@ -202,6 +212,10 @@ msgstr ""
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Backup Restore"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Restoring changedetection.io backups is in the"
|
||||
msgstr ""
|
||||
@@ -547,15 +561,15 @@ msgstr ""
|
||||
msgid "all of the ways that the browser is detected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Connect using Bright Data proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html changedetectionio/templates/_common_fields.html
|
||||
msgid "Tip:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Ignore whitespace, tabs and new-lines/line-feeds when considering if a change was detected."
|
||||
msgstr ""
|
||||
@@ -576,15 +590,15 @@ msgstr ""
|
||||
msgid "Changing this could affect the content of your existing watches, possibly trigger alerts etc."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Remove HTML element(s) by CSS and XPath selectors before text conversion."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Don't paste HTML here, use only CSS and XPath selectors"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML."
|
||||
msgstr ""
|
||||
|
||||
@@ -749,7 +763,7 @@ msgid "Tip"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successful than \"Data Center\" for blocked websites."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
@@ -815,10 +829,48 @@ msgstr ""
|
||||
msgid "Updated"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Activate for individual watches in this tag/group?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Auto-apply to watches with URLs matching"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "e.g. *://example.com/* or github.com/myorg"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Tag colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Tag name"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Filters & Triggers"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid ""
|
||||
"Automatically applies this tag to any watch whose URL matches. Supports wildcards: <code>*example.com*</code> or "
|
||||
"plain substring: <code>github.com/myorg</code>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Currently matching watches"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Custom colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are"
|
||||
msgstr ""
|
||||
@@ -896,7 +948,11 @@ msgstr ""
|
||||
msgid "No website organisational tags/groups configured"
|
||||
msgstr "No website organizational tags/groups configured"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/edit.py
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
@@ -1011,6 +1067,10 @@ msgstr ""
|
||||
msgid "Cleared snapshot history for watch {}"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
msgid "History clearing started in background"
|
||||
msgstr ""
|
||||
@@ -1095,6 +1155,14 @@ msgstr ""
|
||||
msgid "Could not load '{}' processor, processor plugin might be missing."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "System settings default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Updated watch - unpaused!"
|
||||
msgstr ""
|
||||
@@ -1107,6 +1175,10 @@ msgstr ""
|
||||
msgid "Preview unavailable - No fetch/check completed or triggers not reached"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/preview.py
|
||||
msgid "Diff"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "This will remove version history (snapshots) for ALL watches, but keep your list of URLs!"
|
||||
msgstr ""
|
||||
@@ -1131,10 +1203,6 @@ msgstr ""
|
||||
msgid "Type in the word"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "to confirm that you understand."
|
||||
msgstr ""
|
||||
@@ -1220,14 +1288,17 @@ msgid "Jump"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Text"
|
||||
msgstr ""
|
||||
|
||||
@@ -1235,7 +1306,8 @@ msgstr ""
|
||||
msgid "Current screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/processors/extract.py
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Extract Data"
|
||||
msgstr ""
|
||||
|
||||
@@ -1327,6 +1399,10 @@ msgstr ""
|
||||
msgid "Organisational tag/group name used in the main listing page"
|
||||
msgstr "organizational tag/group name used in the main listing page"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Also automatically applied by URL pattern:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Automatically uses the page title if found, you can also use your own title/description here"
|
||||
msgstr ""
|
||||
@@ -1353,6 +1429,10 @@ msgstr ""
|
||||
msgid "method requires a network connection to a running WebDriver+Chrome server, set by the ENV var 'WEBDRIVER_URL'."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Check/Scan all"
|
||||
msgstr ""
|
||||
@@ -1817,6 +1897,26 @@ msgstr ""
|
||||
msgid "import a list"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Pause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnPause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Mute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnMute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Create a link to share watch config with others"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Detecting restock and price"
|
||||
msgstr ""
|
||||
@@ -1846,6 +1946,7 @@ msgid "Queued"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
#: changedetectionio/processors/text_json_diff/difference.py
|
||||
msgid "History"
|
||||
msgstr ""
|
||||
|
||||
@@ -1879,6 +1980,168 @@ msgstr ""
|
||||
msgid "in '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Not Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Contains"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Does NOT Contain"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Ends With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length minimum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length maximum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Matches Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Does NOT Match Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Extracted number after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Page text after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "A value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Value is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text change distance"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Not enough history to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Snapshot too large for edit statistics, skipping."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Unable to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein Text Similarity Details"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Raw distance (edits needed)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Percent similar"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid ""
|
||||
"Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one "
|
||||
"into the other."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Error calculating Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count of content"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Content Analysis"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count (latest snapshot)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count is a simple measure of content length, calculated by splitting text on whitespace."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/content_fetchers/requests.py
|
||||
msgid "Basic fast Plaintext/HTTP Client"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/flask_app.py
|
||||
#: changedetectionio/realtime/socket_server.py
|
||||
msgid "Not yet"
|
||||
@@ -2161,10 +2424,26 @@ msgstr ""
|
||||
msgid "Selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "CSS or xPath selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py changedetectionio/forms.py
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Web Page URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group Tag"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Time Between Check"
|
||||
msgstr ""
|
||||
@@ -2181,6 +2460,10 @@ msgstr ""
|
||||
msgid "Remove elements"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract lines containing"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract text"
|
||||
msgstr ""
|
||||
@@ -2249,7 +2532,8 @@ msgstr ""
|
||||
msgid "Execute JavaScript before change detection"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
@@ -2317,7 +2601,7 @@ msgstr ""
|
||||
msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
@@ -2413,6 +2697,10 @@ msgstr ""
|
||||
msgid "Ignore whitespace"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Screenshot: Minimum Change Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py changedetectionio/processors/image_ssim_diff/forms.py
|
||||
msgid "Must be between 0 and 100"
|
||||
msgstr ""
|
||||
@@ -2612,6 +2900,42 @@ msgstr ""
|
||||
msgid "Detects if the product goes back to in-stock"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "This tool will extract text data from all of the watch history."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "For example, to extract only the numbers from text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Raw text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "RegEx to extract:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Be sure to test your RegEx here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Each RegEx group bracket"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "will be in its own column, the first column value is always the date."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/text_json_diff/processor.py
|
||||
msgid "Webpage Text/HTML, JSON and PDF changes"
|
||||
msgstr ""
|
||||
@@ -2678,6 +3002,11 @@ msgstr ""
|
||||
msgid "The URL of the preview page generated by changedetection.io."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
msgstr ""
|
||||
@@ -2686,6 +3015,14 @@ msgstr ""
|
||||
msgid "The diff output - only changes, additions, and removals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "All diff variants accept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "args, e.g."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The diff output - only changes, additions, and removals —"
|
||||
msgstr ""
|
||||
@@ -2722,6 +3059,18 @@ msgstr ""
|
||||
msgid "The diff output - patch in unified format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the previous version — e.g. the old price. Best when a single value changes per "
|
||||
"line; multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the new version — e.g. the new price. Best when a single value changes per line; "
|
||||
"multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The current snapshot text contents value, useful when combined with JSON or CSS filters"
|
||||
msgstr ""
|
||||
@@ -2766,7 +3115,7 @@ msgstr ""
|
||||
msgid "Use"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#: changedetectionio/templates/_common_fields.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Show advanced help and tips"
|
||||
msgstr ""
|
||||
|
||||
@@ -2870,6 +3219,26 @@ msgstr ""
|
||||
msgid "Format for all notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Discord does not render HTML — switch to"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Plain Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "format to avoid"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "and other HTML entities appearing literally in your notifications."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
@@ -2986,10 +3355,18 @@ msgstr ""
|
||||
msgid "Blocked text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "A new version is available"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Share this link:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Real-time updates offline"
|
||||
msgstr ""
|
||||
@@ -3068,6 +3445,26 @@ msgstr ""
|
||||
msgid "All lines here must not exist (think of each line as \"OR\")"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Keep only lines that contain any of these words or phrases (plain text, case-insensitive)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "One entry per line — any line in the page text that contains a match is kept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Simpler alternative to regex — use this when you just want lines about a specific topic"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Example: enter"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "to keep only lines mentioning temperature readings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Extracts text in the final output (line by line) after other filters using regular expressions or string match:"
|
||||
msgstr ""
|
||||
@@ -3140,10 +3537,6 @@ msgstr ""
|
||||
msgid "Unmute notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Notifications are muted - click to unmute"
|
||||
msgstr ""
|
||||
@@ -3215,3 +3608,6 @@ msgstr ""
|
||||
#~ msgid "Marking watches as viewed in background..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
#~ msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
|
||||
"POT-Creation-Date: 2026-04-15 03:04+0900\n"
|
||||
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: fr\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.16.0\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/backups/__init__.py
|
||||
msgid "A backup is already running, check back in a few minutes"
|
||||
@@ -74,6 +74,11 @@ msgstr ""
|
||||
msgid "File must be a .zip backup file"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
#, python-format
|
||||
msgid "Backup file is too large (max %(mb)s MB)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
msgid "Invalid or corrupted zip file"
|
||||
msgstr ""
|
||||
@@ -128,6 +133,11 @@ msgstr ""
|
||||
msgid "Note: This does not override the main application settings, only watches and groups."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
#, python-format
|
||||
msgid "Max upload size: %(upload)s MB, Max decompressed size: %(decomp)s MB"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
msgid "Include all groups found in backup?"
|
||||
msgstr ""
|
||||
@@ -204,6 +214,10 @@ msgstr "Distill.io"
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX et Wachete"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Backup Restore"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Restoring changedetection.io backups is in the"
|
||||
msgstr ""
|
||||
@@ -551,15 +565,15 @@ msgstr ""
|
||||
msgid "all of the ways that the browser is detected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Connect using Bright Data proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html changedetectionio/templates/_common_fields.html
|
||||
msgid "Tip:"
|
||||
msgstr "Conseil:"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr "Connectez-vous à l'aide des proxys Bright Data et Oxylabs, découvrez-en plus ici."
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Ignore whitespace, tabs and new-lines/line-feeds when considering if a change was detected."
|
||||
msgstr ""
|
||||
@@ -580,15 +594,15 @@ msgstr ""
|
||||
msgid "Changing this could affect the content of your existing watches, possibly trigger alerts etc."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Remove HTML element(s) by CSS and XPath selectors before text conversion."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Don't paste HTML here, use only CSS and XPath selectors"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML."
|
||||
msgstr ""
|
||||
|
||||
@@ -753,7 +767,7 @@ msgid "Tip"
|
||||
msgstr "Astuce"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successful than \"Data Center\" for blocked websites."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
@@ -819,10 +833,48 @@ msgstr ""
|
||||
msgid "Updated"
|
||||
msgstr "Muet"
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Activate for individual watches in this tag/group?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Auto-apply to watches with URLs matching"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "e.g. *://example.com/* or github.com/myorg"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Tag colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Tag name"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Filters & Triggers"
|
||||
msgstr "Filtres et déclencheurs"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid ""
|
||||
"Automatically applies this tag to any watch whose URL matches. Supports wildcards: <code>*example.com*</code> or "
|
||||
"plain substring: <code>github.com/myorg</code>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Currently matching watches"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Custom colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are"
|
||||
msgstr "PARAMÈTRES"
|
||||
@@ -900,7 +952,11 @@ msgstr "Nom de l'étiquette/de l'étiquette"
|
||||
msgid "No website organisational tags/groups configured"
|
||||
msgstr "Aucun groupe/étiquette configuré"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "Désactiver les notifications"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/edit.py
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Edit"
|
||||
msgstr "Modifier"
|
||||
@@ -1015,6 +1071,10 @@ msgstr "Surveillance non trouvée"
|
||||
msgid "Cleared snapshot history for watch {}"
|
||||
msgstr "Historique effacé pour le moniteur {}"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr "clair"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
msgid "History clearing started in background"
|
||||
msgstr ""
|
||||
@@ -1099,6 +1159,14 @@ msgstr ""
|
||||
msgid "Could not load '{}' processor, processor plugin might be missing."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "System settings default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Updated watch - unpaused!"
|
||||
msgstr ""
|
||||
@@ -1111,6 +1179,10 @@ msgstr "Supprimer les montres ?"
|
||||
msgid "Preview unavailable - No fetch/check completed or triggers not reached"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/preview.py
|
||||
msgid "Diff"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "This will remove version history (snapshots) for ALL watches, but keep your list of URLs!"
|
||||
msgstr ""
|
||||
@@ -1135,10 +1207,6 @@ msgstr "Texte de confirmation"
|
||||
msgid "Type in the word"
|
||||
msgstr "Tapez le mot"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr "clair"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "to confirm that you understand."
|
||||
msgstr "pour confirmer que vous comprenez."
|
||||
@@ -1224,14 +1292,17 @@ msgid "Jump"
|
||||
msgstr "Saut"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Text"
|
||||
msgstr "Texte d'erreur"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Screenshot"
|
||||
msgstr "Capture d'écran d'erreur"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Text"
|
||||
msgstr "Texte"
|
||||
|
||||
@@ -1239,7 +1310,8 @@ msgstr "Texte"
|
||||
msgid "Current screenshot"
|
||||
msgstr "Capture d'écran actuelle"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/processors/extract.py
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Extract Data"
|
||||
msgstr "Extraire des données"
|
||||
|
||||
@@ -1333,6 +1405,10 @@ msgstr ""
|
||||
"Nom du groupe/étiquetteNom du groupe/étiquetteBalise organisationnelle/nom de groupe utilisé dans la page de liste "
|
||||
"principale"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Also automatically applied by URL pattern:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Automatically uses the page title if found, you can also use your own title/description here"
|
||||
msgstr ""
|
||||
@@ -1359,6 +1435,10 @@ msgstr ""
|
||||
msgid "method requires a network connection to a running WebDriver+Chrome server, set by the ENV var 'WEBDRIVER_URL'."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr "Connectez-vous à l'aide des proxys Bright Data et Oxylabs, découvrez-en plus ici."
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Check/Scan all"
|
||||
msgstr "Revérifiez tout"
|
||||
@@ -1823,6 +1903,26 @@ msgstr "Aucune surveillance de site Web configurée, veuillez ajouter une URL da
|
||||
msgid "import a list"
|
||||
msgstr "importer une liste"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Pause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnPause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Mute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnMute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Create a link to share watch config with others"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Detecting restock and price"
|
||||
msgstr "Détection du réapprovisionnement et du prix"
|
||||
@@ -1852,6 +1952,7 @@ msgid "Queued"
|
||||
msgstr "En file d'attente"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
#: changedetectionio/processors/text_json_diff/difference.py
|
||||
msgid "History"
|
||||
msgstr "Historique"
|
||||
|
||||
@@ -1885,6 +1986,168 @@ msgstr "Revérifiez tout"
|
||||
msgid "in '%(title)s'"
|
||||
msgstr "dans '%(title)s'"
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Not Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Contains"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Does NOT Contain"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Ends With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length minimum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length maximum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Matches Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Does NOT Match Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Extracted number after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Page text after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "A value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Value is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text change distance"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Not enough history to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Snapshot too large for edit statistics, skipping."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Unable to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein Text Similarity Details"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Raw distance (edits needed)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Percent similar"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid ""
|
||||
"Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one "
|
||||
"into the other."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Error calculating Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count of content"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Content Analysis"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count (latest snapshot)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count is a simple measure of content length, calculated by splitting text on whitespace."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/content_fetchers/requests.py
|
||||
msgid "Basic fast Plaintext/HTTP Client"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/flask_app.py
|
||||
#: changedetectionio/realtime/socket_server.py
|
||||
msgid "Not yet"
|
||||
@@ -2169,10 +2432,26 @@ msgstr "Options de l'interface utilisateur"
|
||||
msgid "Selector"
|
||||
msgstr "Mode de sélection :"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "CSS or xPath selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "value"
|
||||
msgstr "Pause"
|
||||
|
||||
#: changedetectionio/conditions/form.py changedetectionio/forms.py
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Web Page URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group Tag"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Time Between Check"
|
||||
msgstr "Intervalle de vérification"
|
||||
@@ -2189,6 +2468,10 @@ msgstr "Filtre CSS/JSONPath/JQ/XPath"
|
||||
msgid "Remove elements"
|
||||
msgstr "Supprimer par élément"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract lines containing"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract text"
|
||||
msgstr "Extraire des données"
|
||||
@@ -2257,7 +2540,8 @@ msgstr "Bloquer la détection des modifications lorsque le texte correspond"
|
||||
msgid "Execute JavaScript before change detection"
|
||||
msgstr "Exécuter JavaScript avant la détection des modifications"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Save"
|
||||
msgstr "Sauvegarder"
|
||||
|
||||
@@ -2325,7 +2609,7 @@ msgstr "Syntaxe de modèle non valide : %(error)s"
|
||||
msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
@@ -2421,6 +2705,10 @@ msgstr "Ignorer le texte"
|
||||
msgid "Ignore whitespace"
|
||||
msgstr "Ignorer les espaces"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Screenshot: Minimum Change Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py changedetectionio/processors/image_ssim_diff/forms.py
|
||||
msgid "Must be between 0 and 100"
|
||||
msgstr "Doit être compris entre 0 et 100"
|
||||
@@ -2620,6 +2908,42 @@ msgstr "Détection de réapprovisionnement et de prix pour les pages avec un SEU
|
||||
msgid "Detects if the product goes back to in-stock"
|
||||
msgstr "Détecte si le produit revient en stock"
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "This tool will extract text data from all of the watch history."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "For example, to extract only the numbers from text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Raw text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "RegEx to extract:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Be sure to test your RegEx here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Each RegEx group bracket"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "will be in its own column, the first column value is always the date."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/text_json_diff/processor.py
|
||||
msgid "Webpage Text/HTML, JSON and PDF changes"
|
||||
msgstr "Modifications du texte de la page Web/HTML, JSON et PDF"
|
||||
@@ -2686,6 +3010,11 @@ msgstr "Le groupe / tag du moniteur"
|
||||
msgid "The URL of the preview page generated by changedetection.io."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
msgstr ""
|
||||
@@ -2694,6 +3023,14 @@ msgstr ""
|
||||
msgid "The diff output - only changes, additions, and removals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "All diff variants accept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "args, e.g."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The diff output - only changes, additions, and removals —"
|
||||
msgstr ""
|
||||
@@ -2730,6 +3067,18 @@ msgstr ""
|
||||
msgid "The diff output - patch in unified format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the previous version — e.g. the old price. Best when a single value changes per "
|
||||
"line; multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the new version — e.g. the new price. Best when a single value changes per line; "
|
||||
"multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The current snapshot text contents value, useful when combined with JSON or CSS filters"
|
||||
msgstr ""
|
||||
@@ -2774,7 +3123,7 @@ msgstr ""
|
||||
msgid "Use"
|
||||
msgstr "Utiliser"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#: changedetectionio/templates/_common_fields.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Show advanced help and tips"
|
||||
msgstr "Afficher l'aide et astuces avancées"
|
||||
|
||||
@@ -2878,6 +3227,26 @@ msgstr ""
|
||||
msgid "Format for all notifications"
|
||||
msgstr "Format pour toutes les notifications"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Discord does not render HTML — switch to"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Plain Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "format to avoid"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "and other HTML entities appearing literally in your notifications."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
@@ -2994,10 +3363,18 @@ msgstr "Aucune détection de changement si ce texte existe."
|
||||
msgid "Blocked text"
|
||||
msgstr "Texte bloqué"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "A new version is available"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr "Recherchez ou utilisez la touche Alt+S"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Share this link:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Real-time updates offline"
|
||||
msgstr "Mises à jour en temps réel hors ligne"
|
||||
@@ -3078,6 +3455,26 @@ msgstr ""
|
||||
msgid "All lines here must not exist (think of each line as \"OR\")"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Keep only lines that contain any of these words or phrases (plain text, case-insensitive)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "One entry per line — any line in the page text that contains a match is kept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Simpler alternative to regex — use this when you just want lines about a specific topic"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Example: enter"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "to keep only lines mentioning temperature readings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Extracts text in the final output (line by line) after other filters using regular expressions or string match:"
|
||||
msgstr ""
|
||||
@@ -3150,10 +3547,6 @@ msgstr ""
|
||||
msgid "Unmute notifications"
|
||||
msgstr "Réactiver les notifications"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "Désactiver les notifications"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Notifications are muted - click to unmute"
|
||||
msgstr "Notifications désactivées - cliquez pour réactiver"
|
||||
@@ -3276,3 +3669,6 @@ msgstr "Paramètres principaux"
|
||||
#~ msgid "Marking watches as viewed in background..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
#~ msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
|
||||
"POT-Creation-Date: 2026-04-15 03:04+0900\n"
|
||||
"PO-Revision-Date: 2026-01-02 15:32+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: it\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.16.0\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/backups/__init__.py
|
||||
msgid "A backup is already running, check back in a few minutes"
|
||||
@@ -74,6 +74,11 @@ msgstr ""
|
||||
msgid "File must be a .zip backup file"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
#, python-format
|
||||
msgid "Backup file is too large (max %(mb)s MB)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
msgid "Invalid or corrupted zip file"
|
||||
msgstr ""
|
||||
@@ -128,6 +133,11 @@ msgstr ""
|
||||
msgid "Note: This does not override the main application settings, only watches and groups."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
#, python-format
|
||||
msgid "Max upload size: %(upload)s MB, Max decompressed size: %(decomp)s MB"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
msgid "Include all groups found in backup?"
|
||||
msgstr ""
|
||||
@@ -204,6 +214,10 @@ msgstr "Distill.io"
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX & Wachete"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Backup Restore"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Restoring changedetection.io backups is in the"
|
||||
msgstr ""
|
||||
@@ -549,15 +563,15 @@ msgstr ""
|
||||
msgid "all of the ways that the browser is detected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Connect using Bright Data proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html changedetectionio/templates/_common_fields.html
|
||||
msgid "Tip:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Ignore whitespace, tabs and new-lines/line-feeds when considering if a change was detected."
|
||||
msgstr ""
|
||||
@@ -578,15 +592,15 @@ msgstr ""
|
||||
msgid "Changing this could affect the content of your existing watches, possibly trigger alerts etc."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Remove HTML element(s) by CSS and XPath selectors before text conversion."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Don't paste HTML here, use only CSS and XPath selectors"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML."
|
||||
msgstr ""
|
||||
|
||||
@@ -751,7 +765,7 @@ msgid "Tip"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successful than \"Data Center\" for blocked websites."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
@@ -817,10 +831,48 @@ msgstr ""
|
||||
msgid "Updated"
|
||||
msgstr "Aggiornato"
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Activate for individual watches in this tag/group?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Auto-apply to watches with URLs matching"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "e.g. *://example.com/* or github.com/myorg"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Tag colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Tag name"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Filters & Triggers"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid ""
|
||||
"Automatically applies this tag to any watch whose URL matches. Supports wildcards: <code>*example.com*</code> or "
|
||||
"plain substring: <code>github.com/myorg</code>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Currently matching watches"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Custom colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are"
|
||||
msgstr ""
|
||||
@@ -898,7 +950,11 @@ msgstr ""
|
||||
msgid "No website organisational tags/groups configured"
|
||||
msgstr "Nessun gruppo/etichetta configurato"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "Disattiva notifiche"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/edit.py
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Edit"
|
||||
msgstr "Modifica"
|
||||
@@ -1013,6 +1069,10 @@ msgstr "Monitoraggio non trovato"
|
||||
msgid "Cleared snapshot history for watch {}"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
msgid "History clearing started in background"
|
||||
msgstr ""
|
||||
@@ -1097,6 +1157,14 @@ msgstr ""
|
||||
msgid "Could not load '{}' processor, processor plugin might be missing."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "System settings default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Updated watch - unpaused!"
|
||||
msgstr ""
|
||||
@@ -1109,6 +1177,10 @@ msgstr ""
|
||||
msgid "Preview unavailable - No fetch/check completed or triggers not reached"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/preview.py
|
||||
msgid "Diff"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "This will remove version history (snapshots) for ALL watches, but keep your list of URLs!"
|
||||
msgstr ""
|
||||
@@ -1133,10 +1205,6 @@ msgstr "Testo di conferma"
|
||||
msgid "Type in the word"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "to confirm that you understand."
|
||||
msgstr ""
|
||||
@@ -1222,14 +1290,17 @@ msgid "Jump"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Text"
|
||||
msgstr "Testo dell'errore"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Screenshot"
|
||||
msgstr "Screenshot dell'errore"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Text"
|
||||
msgstr "Testo"
|
||||
|
||||
@@ -1237,7 +1308,8 @@ msgstr "Testo"
|
||||
msgid "Current screenshot"
|
||||
msgstr "Screenshot corrente"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/processors/extract.py
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Extract Data"
|
||||
msgstr ""
|
||||
|
||||
@@ -1329,6 +1401,10 @@ msgstr ""
|
||||
msgid "Organisational tag/group name used in the main listing page"
|
||||
msgstr "Nome gruppo/etichetta"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Also automatically applied by URL pattern:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Automatically uses the page title if found, you can also use your own title/description here"
|
||||
msgstr ""
|
||||
@@ -1355,6 +1431,10 @@ msgstr ""
|
||||
msgid "method requires a network connection to a running WebDriver+Chrome server, set by the ENV var 'WEBDRIVER_URL'."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Check/Scan all"
|
||||
msgstr ""
|
||||
@@ -1819,6 +1899,26 @@ msgstr "Nessun monitoraggio configurato, aggiungi un URL nella casella sopra, op
|
||||
msgid "import a list"
|
||||
msgstr "importa una lista"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Pause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnPause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Mute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnMute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Create a link to share watch config with others"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Detecting restock and price"
|
||||
msgstr ""
|
||||
@@ -1848,6 +1948,7 @@ msgid "Queued"
|
||||
msgstr "In coda"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
#: changedetectionio/processors/text_json_diff/difference.py
|
||||
msgid "History"
|
||||
msgstr "Cronologia"
|
||||
|
||||
@@ -1881,6 +1982,168 @@ msgstr "Controlla tutti"
|
||||
msgid "in '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Not Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Contains"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Does NOT Contain"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Ends With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length minimum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length maximum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Matches Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Does NOT Match Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Extracted number after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Page text after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "A value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Value is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text change distance"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Not enough history to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Snapshot too large for edit statistics, skipping."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Unable to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein Text Similarity Details"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Raw distance (edits needed)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Percent similar"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid ""
|
||||
"Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one "
|
||||
"into the other."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Error calculating Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count of content"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Content Analysis"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count (latest snapshot)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count is a simple measure of content length, calculated by splitting text on whitespace."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/content_fetchers/requests.py
|
||||
msgid "Basic fast Plaintext/HTTP Client"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/flask_app.py
|
||||
#: changedetectionio/realtime/socket_server.py
|
||||
msgid "Not yet"
|
||||
@@ -2163,10 +2426,26 @@ msgstr "Operazione"
|
||||
msgid "Selector"
|
||||
msgstr "Selettore"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "CSS or xPath selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "value"
|
||||
msgstr "valore"
|
||||
|
||||
#: changedetectionio/conditions/form.py changedetectionio/forms.py
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Web Page URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group Tag"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Time Between Check"
|
||||
msgstr "Intervallo tra controlli"
|
||||
@@ -2183,6 +2462,10 @@ msgstr "Filtri CSS/JSONPath/JQ/XPath"
|
||||
msgid "Remove elements"
|
||||
msgstr "Rimuovi elementi"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract lines containing"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract text"
|
||||
msgstr "Estrai testo"
|
||||
@@ -2251,7 +2534,8 @@ msgstr "Blocca rilevamento modifiche quando il testo corrisponde"
|
||||
msgid "Execute JavaScript before change detection"
|
||||
msgstr "Esegui JavaScript prima del rilevamento"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Save"
|
||||
msgstr "Salva"
|
||||
|
||||
@@ -2319,7 +2603,7 @@ msgstr "Sintassi template non valida: %(error)s"
|
||||
msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
|
||||
msgid "Name"
|
||||
msgstr "Nome"
|
||||
|
||||
@@ -2415,6 +2699,10 @@ msgstr "Ignora testo"
|
||||
msgid "Ignore whitespace"
|
||||
msgstr "Ignora spazi"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Screenshot: Minimum Change Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py changedetectionio/processors/image_ssim_diff/forms.py
|
||||
msgid "Must be between 0 and 100"
|
||||
msgstr "Deve essere tra 0 e 100"
|
||||
@@ -2614,6 +2902,42 @@ msgstr "Rilevamento disponibilità e prezzi per pagine con UN SINGOLO prodotto"
|
||||
msgid "Detects if the product goes back to in-stock"
|
||||
msgstr "Rileva se il prodotto torna disponibile"
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "This tool will extract text data from all of the watch history."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "For example, to extract only the numbers from text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Raw text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "RegEx to extract:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Be sure to test your RegEx here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Each RegEx group bracket"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "will be in its own column, the first column value is always the date."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/text_json_diff/processor.py
|
||||
msgid "Webpage Text/HTML, JSON and PDF changes"
|
||||
msgstr "Modifiche testo/HTML, JSON e PDF"
|
||||
@@ -2680,6 +3004,11 @@ msgstr "Gruppo / Etichetta"
|
||||
msgid "The URL of the preview page generated by changedetection.io."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
msgstr ""
|
||||
@@ -2688,6 +3017,14 @@ msgstr ""
|
||||
msgid "The diff output - only changes, additions, and removals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "All diff variants accept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "args, e.g."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The diff output - only changes, additions, and removals —"
|
||||
msgstr ""
|
||||
@@ -2724,6 +3061,18 @@ msgstr ""
|
||||
msgid "The diff output - patch in unified format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the previous version — e.g. the old price. Best when a single value changes per "
|
||||
"line; multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the new version — e.g. the new price. Best when a single value changes per line; "
|
||||
"multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The current snapshot text contents value, useful when combined with JSON or CSS filters"
|
||||
msgstr ""
|
||||
@@ -2768,7 +3117,7 @@ msgstr ""
|
||||
msgid "Use"
|
||||
msgstr "Usa"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#: changedetectionio/templates/_common_fields.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Show advanced help and tips"
|
||||
msgstr ""
|
||||
|
||||
@@ -2872,6 +3221,26 @@ msgstr ""
|
||||
msgid "Format for all notifications"
|
||||
msgstr "Formato per tutte le notifiche"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Discord does not render HTML — switch to"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Plain Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "format to avoid"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "and other HTML entities appearing literally in your notifications."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
@@ -2988,10 +3357,18 @@ msgstr "Nessuna rilevazione se questo testo esiste."
|
||||
msgid "Blocked text"
|
||||
msgstr "Testo bloccato"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "A new version is available"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr "Cerca, o usa il tasto Alt+S"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Share this link:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Real-time updates offline"
|
||||
msgstr ""
|
||||
@@ -3070,6 +3447,26 @@ msgstr ""
|
||||
msgid "All lines here must not exist (think of each line as \"OR\")"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Keep only lines that contain any of these words or phrases (plain text, case-insensitive)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "One entry per line — any line in the page text that contains a match is kept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Simpler alternative to regex — use this when you just want lines about a specific topic"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Example: enter"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "to keep only lines mentioning temperature readings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Extracts text in the final output (line by line) after other filters using regular expressions or string match:"
|
||||
msgstr ""
|
||||
@@ -3142,10 +3539,6 @@ msgstr ""
|
||||
msgid "Unmute notifications"
|
||||
msgstr "Riattiva notifiche"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "Disattiva notifiche"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Notifications are muted - click to unmute"
|
||||
msgstr ""
|
||||
@@ -3250,3 +3643,6 @@ msgstr "Impostazioni principali"
|
||||
#~ msgid "Marking watches as viewed in background..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
#~ msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
|
||||
"POT-Creation-Date: 2026-04-15 03:04+0900\n"
|
||||
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: ko\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.16.0\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/backups/__init__.py
|
||||
msgid "A backup is already running, check back in a few minutes"
|
||||
@@ -74,6 +74,11 @@ msgstr ""
|
||||
msgid "File must be a .zip backup file"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
#, python-format
|
||||
msgid "Backup file is too large (max %(mb)s MB)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
msgid "Invalid or corrupted zip file"
|
||||
msgstr ""
|
||||
@@ -128,6 +133,11 @@ msgstr ""
|
||||
msgid "Note: This does not override the main application settings, only watches and groups."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
#, python-format
|
||||
msgid "Max upload size: %(upload)s MB, Max decompressed size: %(decomp)s MB"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
msgid "Include all groups found in backup?"
|
||||
msgstr ""
|
||||
@@ -202,6 +212,10 @@ msgstr "Distill.io"
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX 및 와체테"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Backup Restore"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Restoring changedetection.io backups is in the"
|
||||
msgstr ""
|
||||
@@ -547,15 +561,15 @@ msgstr ""
|
||||
msgid "all of the ways that the browser is detected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Connect using Bright Data proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html changedetectionio/templates/_common_fields.html
|
||||
msgid "Tip:"
|
||||
msgstr "팁:"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr "Bright Data 및 Oxylabs 프록시를 사용하여 연결하세요. 여기에서 자세한 내용을 알아보세요."
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Ignore whitespace, tabs and new-lines/line-feeds when considering if a change was detected."
|
||||
msgstr ""
|
||||
@@ -576,15 +590,15 @@ msgstr ""
|
||||
msgid "Changing this could affect the content of your existing watches, possibly trigger alerts etc."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Remove HTML element(s) by CSS and XPath selectors before text conversion."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Don't paste HTML here, use only CSS and XPath selectors"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML."
|
||||
msgstr ""
|
||||
|
||||
@@ -749,7 +763,7 @@ msgid "Tip"
|
||||
msgstr "팁"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successful than \"Data Center\" for blocked websites."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
@@ -815,10 +829,48 @@ msgstr ""
|
||||
msgid "Updated"
|
||||
msgstr "업데이트됨"
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Activate for individual watches in this tag/group?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Auto-apply to watches with URLs matching"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "e.g. *://example.com/* or github.com/myorg"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Tag colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Tag name"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Filters & Triggers"
|
||||
msgstr "필터 및 트리거"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid ""
|
||||
"Automatically applies this tag to any watch whose URL matches. Supports wildcards: <code>*example.com*</code> or "
|
||||
"plain substring: <code>github.com/myorg</code>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Currently matching watches"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Custom colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are"
|
||||
msgstr "설정"
|
||||
@@ -896,7 +948,11 @@ msgstr "태그/라벨 이름"
|
||||
msgid "No website organisational tags/groups configured"
|
||||
msgstr "구성된 그룹/태그 없음"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "알림 음소거"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/edit.py
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Edit"
|
||||
msgstr "편집하다"
|
||||
@@ -1011,6 +1067,10 @@ msgstr "모니터를 찾을 수 없음"
|
||||
msgid "Cleared snapshot history for watch {}"
|
||||
msgstr "모니터 {} 스냅샷 기록 삭제됨"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr "분명한"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
msgid "History clearing started in background"
|
||||
msgstr ""
|
||||
@@ -1095,6 +1155,14 @@ msgstr ""
|
||||
msgid "Could not load '{}' processor, processor plugin might be missing."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "System settings default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Updated watch - unpaused!"
|
||||
msgstr ""
|
||||
@@ -1107,6 +1175,10 @@ msgstr "모니터가 업데이트되었습니다."
|
||||
msgid "Preview unavailable - No fetch/check completed or triggers not reached"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/preview.py
|
||||
msgid "Diff"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "This will remove version history (snapshots) for ALL watches, but keep your list of URLs!"
|
||||
msgstr ""
|
||||
@@ -1131,10 +1203,6 @@ msgstr "확인 텍스트"
|
||||
msgid "Type in the word"
|
||||
msgstr "단어를 입력하세요"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr "분명한"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "to confirm that you understand."
|
||||
msgstr "당신이 이해했는지 확인하기 위해."
|
||||
@@ -1220,14 +1288,17 @@ msgid "Jump"
|
||||
msgstr "도약"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Text"
|
||||
msgstr "오류 텍스트"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Screenshot"
|
||||
msgstr "오류 스크린샷"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Text"
|
||||
msgstr "텍스트"
|
||||
|
||||
@@ -1235,7 +1306,8 @@ msgstr "텍스트"
|
||||
msgid "Current screenshot"
|
||||
msgstr "현재 스크린샷"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/processors/extract.py
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Extract Data"
|
||||
msgstr "데이터 추출"
|
||||
|
||||
@@ -1327,6 +1399,10 @@ msgstr "여기에 도움말과 예시가 있습니다"
|
||||
msgid "Organisational tag/group name used in the main listing page"
|
||||
msgstr "그룹/태그 이름"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Also automatically applied by URL pattern:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Automatically uses the page title if found, you can also use your own title/description here"
|
||||
msgstr ""
|
||||
@@ -1353,6 +1429,10 @@ msgstr ""
|
||||
msgid "method requires a network connection to a running WebDriver+Chrome server, set by the ENV var 'WEBDRIVER_URL'."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr "Bright Data 및 Oxylabs 프록시를 사용하여 연결하세요. 여기에서 자세한 내용을 알아보세요."
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Check/Scan all"
|
||||
msgstr "모두 다시 확인하세요"
|
||||
@@ -1817,6 +1897,26 @@ msgstr "구성된 웹사이트 시계가 없습니다. 위 상자에 URL을 추
|
||||
msgid "import a list"
|
||||
msgstr "목록 가져오기"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Pause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnPause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Mute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnMute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Create a link to share watch config with others"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Detecting restock and price"
|
||||
msgstr "재입고 및 가격 감지"
|
||||
@@ -1846,6 +1946,7 @@ msgid "Queued"
|
||||
msgstr "대기 중"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
#: changedetectionio/processors/text_json_diff/difference.py
|
||||
msgid "History"
|
||||
msgstr "기록"
|
||||
|
||||
@@ -1879,6 +1980,168 @@ msgstr "모두 다시 확인하세요"
|
||||
msgid "in '%(title)s'"
|
||||
msgstr "'%(title)s'에서"
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Not Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Contains"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Does NOT Contain"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Ends With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length minimum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length maximum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Matches Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Does NOT Match Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Extracted number after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Page text after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "A value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Value is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text change distance"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Not enough history to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Snapshot too large for edit statistics, skipping."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Unable to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein Text Similarity Details"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Raw distance (edits needed)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Percent similar"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid ""
|
||||
"Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one "
|
||||
"into the other."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Error calculating Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count of content"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Content Analysis"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count (latest snapshot)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count is a simple measure of content length, calculated by splitting text on whitespace."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/content_fetchers/requests.py
|
||||
msgid "Basic fast Plaintext/HTTP Client"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/flask_app.py
|
||||
#: changedetectionio/realtime/socket_server.py
|
||||
msgid "Not yet"
|
||||
@@ -2161,10 +2424,26 @@ msgstr "작업"
|
||||
msgid "Selector"
|
||||
msgstr "선택자"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "CSS or xPath selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "value"
|
||||
msgstr "값"
|
||||
|
||||
#: changedetectionio/conditions/form.py changedetectionio/forms.py
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Web Page URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group Tag"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Time Between Check"
|
||||
msgstr "확인 간격"
|
||||
@@ -2181,6 +2460,10 @@ msgstr "CSS/JSONPath/JQ/XPath 필터"
|
||||
msgid "Remove elements"
|
||||
msgstr "요소 제거"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract lines containing"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract text"
|
||||
msgstr "텍스트 추출"
|
||||
@@ -2249,7 +2532,8 @@ msgstr "텍스트가 일치하는 동안 변경 감지 차단"
|
||||
msgid "Execute JavaScript before change detection"
|
||||
msgstr "변경 감지 전에 JavaScript 실행"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Save"
|
||||
msgstr "구하다"
|
||||
|
||||
@@ -2317,7 +2601,7 @@ msgstr "잘못된 템플릿 구문: %(error)s"
|
||||
msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
|
||||
msgid "Name"
|
||||
msgstr "이름"
|
||||
|
||||
@@ -2413,6 +2697,10 @@ msgstr "텍스트 무시"
|
||||
msgid "Ignore whitespace"
|
||||
msgstr "공백 무시"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Screenshot: Minimum Change Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py changedetectionio/processors/image_ssim_diff/forms.py
|
||||
msgid "Must be between 0 and 100"
|
||||
msgstr "0에서 100 사이여야 합니다."
|
||||
@@ -2612,6 +2900,42 @@ msgstr "단일 제품이 포함된 페이지의 재입고 및 가격 감지"
|
||||
msgid "Detects if the product goes back to in-stock"
|
||||
msgstr "제품이 다시 재고로 돌아왔는지 감지합니다."
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "This tool will extract text data from all of the watch history."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "For example, to extract only the numbers from text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Raw text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "RegEx to extract:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Be sure to test your RegEx here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Each RegEx group bracket"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "will be in its own column, the first column value is always the date."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/text_json_diff/processor.py
|
||||
msgid "Webpage Text/HTML, JSON and PDF changes"
|
||||
msgstr "웹페이지 텍스트/HTML, JSON 및 PDF 변경"
|
||||
@@ -2678,6 +3002,11 @@ msgstr "모니터 그룹 / 태그"
|
||||
msgid "The URL of the preview page generated by changedetection.io."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
msgstr ""
|
||||
@@ -2686,6 +3015,14 @@ msgstr ""
|
||||
msgid "The diff output - only changes, additions, and removals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "All diff variants accept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "args, e.g."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The diff output - only changes, additions, and removals —"
|
||||
msgstr ""
|
||||
@@ -2722,6 +3059,18 @@ msgstr ""
|
||||
msgid "The diff output - patch in unified format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the previous version — e.g. the old price. Best when a single value changes per "
|
||||
"line; multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the new version — e.g. the new price. Best when a single value changes per line; "
|
||||
"multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The current snapshot text contents value, useful when combined with JSON or CSS filters"
|
||||
msgstr ""
|
||||
@@ -2766,7 +3115,7 @@ msgstr ""
|
||||
msgid "Use"
|
||||
msgstr "사용"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#: changedetectionio/templates/_common_fields.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Show advanced help and tips"
|
||||
msgstr "고급 도움말 표시"
|
||||
|
||||
@@ -2870,6 +3219,26 @@ msgstr ""
|
||||
msgid "Format for all notifications"
|
||||
msgstr "모든 알림 형식"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Discord does not render HTML — switch to"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Plain Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "format to avoid"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "and other HTML entities appearing literally in your notifications."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
@@ -2986,10 +3355,18 @@ msgstr "이 텍스트 존재 시 변경 감지 안 함."
|
||||
msgid "Blocked text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "A new version is available"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr "검색 또는 Alt+S 키 사용"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Share this link:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Real-time updates offline"
|
||||
msgstr "실시간 업데이트 오프라인"
|
||||
@@ -3068,6 +3445,26 @@ msgstr ""
|
||||
msgid "All lines here must not exist (think of each line as \"OR\")"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Keep only lines that contain any of these words or phrases (plain text, case-insensitive)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "One entry per line — any line in the page text that contains a match is kept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Simpler alternative to regex — use this when you just want lines about a specific topic"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Example: enter"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "to keep only lines mentioning temperature readings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Extracts text in the final output (line by line) after other filters using regular expressions or string match:"
|
||||
msgstr ""
|
||||
@@ -3140,10 +3537,6 @@ msgstr ""
|
||||
msgid "Unmute notifications"
|
||||
msgstr "알림 음소거 해제"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "알림 음소거"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Notifications are muted - click to unmute"
|
||||
msgstr "알림 음소거됨 - 클릭하여 해제"
|
||||
@@ -3371,3 +3764,6 @@ msgstr "기본 설정"
|
||||
#~ msgid "Marking watches as viewed in background..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
#~ msgstr ""
|
||||
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io 0.53.6\n"
|
||||
"Project-Id-Version: changedetection.io 0.54.9\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
|
||||
"POT-Creation-Date: 2026-04-15 03:04+0900\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"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.16.0\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/backups/__init__.py
|
||||
msgid "A backup is already running, check back in a few minutes"
|
||||
@@ -73,6 +73,11 @@ msgstr ""
|
||||
msgid "File must be a .zip backup file"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
#, python-format
|
||||
msgid "Backup file is too large (max %(mb)s MB)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
msgid "Invalid or corrupted zip file"
|
||||
msgstr ""
|
||||
@@ -127,6 +132,11 @@ msgstr ""
|
||||
msgid "Note: This does not override the main application settings, only watches and groups."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
#, python-format
|
||||
msgid "Max upload size: %(upload)s MB, Max decompressed size: %(decomp)s MB"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
msgid "Include all groups found in backup?"
|
||||
msgstr ""
|
||||
@@ -148,6 +158,7 @@ msgid "Importing 5,000 of the first URLs from your list, the rest can be importe
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from list in {:.2f}s, {} Skipped."
|
||||
msgstr ""
|
||||
|
||||
@@ -160,6 +171,7 @@ msgid "JSON structure looks invalid, was it broken?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} Imported from Distill.io in {:.2f}s, {} Skipped."
|
||||
msgstr ""
|
||||
|
||||
@@ -168,18 +180,22 @@ msgid "Unable to read export XLSX file, something wrong with the file?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "Error processing row number {}, URL value was incorrect, row was skipped."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "Error processing row number {}, check all cell data types are correct, row was skipped."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from Wachete .xlsx in {:.2f}s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/importer.py
|
||||
#, python-brace-format
|
||||
msgid "{} imported from custom .xlsx in {:.2f}s"
|
||||
msgstr ""
|
||||
|
||||
@@ -195,6 +211,10 @@ msgstr ""
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Backup Restore"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Restoring changedetection.io backups is in the"
|
||||
msgstr ""
|
||||
@@ -302,10 +322,12 @@ msgid "Password protection removed."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Warning: Worker count ({}) is close to or exceeds available CPU cores ({})"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Worker count adjusted: {}"
|
||||
msgstr ""
|
||||
|
||||
@@ -314,6 +336,7 @@ msgid "Dynamic worker adjustment not supported for sync workers"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Error adjusting workers: {}"
|
||||
msgstr ""
|
||||
|
||||
@@ -537,15 +560,15 @@ msgstr ""
|
||||
msgid "all of the ways that the browser is detected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Connect using Bright Data proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html changedetectionio/templates/_common_fields.html
|
||||
msgid "Tip:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Ignore whitespace, tabs and new-lines/line-feeds when considering if a change was detected."
|
||||
msgstr ""
|
||||
@@ -566,15 +589,15 @@ msgstr ""
|
||||
msgid "Changing this could affect the content of your existing watches, possibly trigger alerts etc."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Remove HTML element(s) by CSS and XPath selectors before text conversion."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Don't paste HTML here, use only CSS and XPath selectors"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML."
|
||||
msgstr ""
|
||||
|
||||
@@ -739,7 +762,7 @@ msgid "Tip"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successful than \"Data Center\" for blocked websites."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
@@ -777,6 +800,7 @@ msgid "Clear Snapshot History"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "The tag \"{}\" already exists"
|
||||
msgstr ""
|
||||
|
||||
@@ -804,10 +828,48 @@ msgstr ""
|
||||
msgid "Updated"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Activate for individual watches in this tag/group?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Auto-apply to watches with URLs matching"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "e.g. *://example.com/* or github.com/myorg"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Tag colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Tag name"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Filters & Triggers"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid ""
|
||||
"Automatically applies this tag to any watch whose URL matches. Supports wildcards: <code>*example.com*</code> or "
|
||||
"plain substring: <code>github.com/myorg</code>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Currently matching watches"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Custom colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are"
|
||||
msgstr ""
|
||||
@@ -885,7 +947,11 @@ msgstr ""
|
||||
msgid "No website organisational tags/groups configured"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/edit.py
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
@@ -937,46 +1003,57 @@ msgid "RSS Feed for this watch"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches deleted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches paused"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches unpaused"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches updated"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches muted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches un-muted"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches queued for rechecking"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches errors cleared"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches cleared/reset."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches set to use default notification settings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "{} watches were tagged"
|
||||
msgstr ""
|
||||
|
||||
@@ -985,9 +1062,14 @@ msgid "Watch not found"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Cleared snapshot history for watch {}"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
msgid "History clearing started in background"
|
||||
msgstr ""
|
||||
@@ -997,6 +1079,7 @@ msgid "Incorrect confirmation text."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "The watch by UUID {} does not exist."
|
||||
msgstr ""
|
||||
|
||||
@@ -1017,10 +1100,12 @@ msgid "Queued 1 watch for rechecking."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking."
|
||||
msgstr ""
|
||||
|
||||
@@ -1029,6 +1114,7 @@ msgid "Queueing watches for rechecking in background..."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Could not share, something went wrong while communicating with the share server - {}"
|
||||
msgstr ""
|
||||
|
||||
@@ -1049,21 +1135,33 @@ msgid "No watches to edit"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
#, python-brace-format
|
||||
msgid "No watch with the UUID {} found."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
#, python-brace-format
|
||||
msgid "Switched to mode - {}."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
#, python-brace-format
|
||||
msgid "Could not load '{}' processor, processor plugin might be missing. Please select a different processor."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
#, python-brace-format
|
||||
msgid "Could not load '{}' processor, processor plugin might be missing."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "System settings default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Updated watch - unpaused!"
|
||||
msgstr ""
|
||||
@@ -1076,6 +1174,10 @@ msgstr ""
|
||||
msgid "Preview unavailable - No fetch/check completed or triggers not reached"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/preview.py
|
||||
msgid "Diff"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "This will remove version history (snapshots) for ALL watches, but keep your list of URLs!"
|
||||
msgstr ""
|
||||
@@ -1100,10 +1202,6 @@ msgstr ""
|
||||
msgid "Type in the word"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "to confirm that you understand."
|
||||
msgstr ""
|
||||
@@ -1189,14 +1287,17 @@ msgid "Jump"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Text"
|
||||
msgstr ""
|
||||
|
||||
@@ -1204,7 +1305,8 @@ msgstr ""
|
||||
msgid "Current screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/processors/extract.py
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Extract Data"
|
||||
msgstr ""
|
||||
|
||||
@@ -1296,6 +1398,10 @@ msgstr ""
|
||||
msgid "Organisational tag/group name used in the main listing page"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Also automatically applied by URL pattern:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Automatically uses the page title if found, you can also use your own title/description here"
|
||||
msgstr ""
|
||||
@@ -1322,6 +1428,10 @@ msgstr ""
|
||||
msgid "method requires a network connection to a running WebDriver+Chrome server, set by the ENV var 'WEBDRIVER_URL'."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Check/Scan all"
|
||||
msgstr ""
|
||||
@@ -1645,6 +1755,7 @@ msgid "Screenshot requires a Content Fetcher ( Sockpuppetbrowser, selenium, etc
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/views.py
|
||||
#, python-brace-format
|
||||
msgid "Warning, URL {} already exists"
|
||||
msgstr ""
|
||||
|
||||
@@ -1657,6 +1768,7 @@ msgid "Watch added."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "displaying <b>{start} - {end}</b> {record_name} in total <b>{total}</b>"
|
||||
msgstr ""
|
||||
|
||||
@@ -1784,6 +1896,26 @@ msgstr ""
|
||||
msgid "import a list"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Pause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnPause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Mute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnMute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Create a link to share watch config with others"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Detecting restock and price"
|
||||
msgstr ""
|
||||
@@ -1813,6 +1945,7 @@ msgid "Queued"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
#: changedetectionio/processors/text_json_diff/difference.py
|
||||
msgid "History"
|
||||
msgstr ""
|
||||
|
||||
@@ -1846,6 +1979,168 @@ msgstr ""
|
||||
msgid "in '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Not Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Contains"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Does NOT Contain"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Ends With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length minimum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length maximum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Matches Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Does NOT Match Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Extracted number after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Page text after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "A value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Value is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text change distance"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Not enough history to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Snapshot too large for edit statistics, skipping."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Unable to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein Text Similarity Details"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Raw distance (edits needed)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Percent similar"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid ""
|
||||
"Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one "
|
||||
"into the other."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Error calculating Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count of content"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Content Analysis"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count (latest snapshot)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count is a simple measure of content length, calculated by splitting text on whitespace."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/content_fetchers/requests.py
|
||||
msgid "Basic fast Plaintext/HTTP Client"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/flask_app.py
|
||||
#: changedetectionio/realtime/socket_server.py
|
||||
msgid "Not yet"
|
||||
@@ -2128,10 +2423,26 @@ msgstr ""
|
||||
msgid "Selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "CSS or xPath selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py changedetectionio/forms.py
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Web Page URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group Tag"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Time Between Check"
|
||||
msgstr ""
|
||||
@@ -2148,6 +2459,10 @@ msgstr ""
|
||||
msgid "Remove elements"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract lines containing"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract text"
|
||||
msgstr ""
|
||||
@@ -2216,7 +2531,8 @@ msgstr ""
|
||||
msgid "Execute JavaScript before change detection"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
@@ -2284,7 +2600,7 @@ msgstr ""
|
||||
msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
@@ -2380,6 +2696,10 @@ msgstr ""
|
||||
msgid "Ignore whitespace"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Screenshot: Minimum Change Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py changedetectionio/processors/image_ssim_diff/forms.py
|
||||
msgid "Must be between 0 and 100"
|
||||
msgstr ""
|
||||
@@ -2457,10 +2777,12 @@ msgid "Not enough history to compare. Need at least 2 snapshots."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/image_ssim_diff/difference.py
|
||||
#, python-brace-format
|
||||
msgid "Failed to load screenshots: {}"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/image_ssim_diff/difference.py
|
||||
#, python-brace-format
|
||||
msgid "Failed to calculate diff: {}"
|
||||
msgstr ""
|
||||
|
||||
@@ -2577,6 +2899,42 @@ msgstr ""
|
||||
msgid "Detects if the product goes back to in-stock"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "This tool will extract text data from all of the watch history."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "For example, to extract only the numbers from text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Raw text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "RegEx to extract:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Be sure to test your RegEx here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Each RegEx group bracket"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "will be in its own column, the first column value is always the date."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/text_json_diff/processor.py
|
||||
msgid "Webpage Text/HTML, JSON and PDF changes"
|
||||
msgstr ""
|
||||
@@ -2586,6 +2944,7 @@ msgid "Detects all text changes where possible"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Error fetching metadata for {}"
|
||||
msgstr ""
|
||||
|
||||
@@ -2594,6 +2953,7 @@ msgid "Watch protocol is not permitted or invalid URL format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/store/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Watch limit reached ({}/{} watches). Cannot add more watches."
|
||||
msgstr ""
|
||||
|
||||
@@ -2641,6 +3001,11 @@ msgstr ""
|
||||
msgid "The URL of the preview page generated by changedetection.io."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
msgstr ""
|
||||
@@ -2649,6 +3014,14 @@ msgstr ""
|
||||
msgid "The diff output - only changes, additions, and removals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "All diff variants accept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "args, e.g."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The diff output - only changes, additions, and removals —"
|
||||
msgstr ""
|
||||
@@ -2685,6 +3058,18 @@ msgstr ""
|
||||
msgid "The diff output - patch in unified format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the previous version — e.g. the old price. Best when a single value changes per "
|
||||
"line; multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the new version — e.g. the new price. Best when a single value changes per line; "
|
||||
"multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The current snapshot text contents value, useful when combined with JSON or CSS filters"
|
||||
msgstr ""
|
||||
@@ -2729,7 +3114,7 @@ msgstr ""
|
||||
msgid "Use"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#: changedetectionio/templates/_common_fields.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Show advanced help and tips"
|
||||
msgstr ""
|
||||
|
||||
@@ -2833,6 +3218,26 @@ msgstr ""
|
||||
msgid "Format for all notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Discord does not render HTML — switch to"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Plain Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "format to avoid"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "and other HTML entities appearing literally in your notifications."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
@@ -2949,10 +3354,18 @@ msgstr ""
|
||||
msgid "Blocked text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "A new version is available"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Share this link:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Real-time updates offline"
|
||||
msgstr ""
|
||||
@@ -3031,6 +3444,26 @@ msgstr ""
|
||||
msgid "All lines here must not exist (think of each line as \"OR\")"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Keep only lines that contain any of these words or phrases (plain text, case-insensitive)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "One entry per line — any line in the page text that contains a match is kept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Simpler alternative to regex — use this when you just want lines about a specific topic"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Example: enter"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "to keep only lines mentioning temperature readings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Extracts text in the final output (line by line) after other filters using regular expressions or string match:"
|
||||
msgstr ""
|
||||
@@ -3103,10 +3536,6 @@ msgstr ""
|
||||
msgid "Unmute notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Notifications are muted - click to unmute"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
|
||||
"POT-Creation-Date: 2026-04-15 03:04+0900\n"
|
||||
"PO-Revision-Date: 2026-01-18 21:31+0800\n"
|
||||
"Last-Translator: 吾爱分享 <admin@wuaishare.cn>\n"
|
||||
"Language: zh\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.16.0\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/backups/__init__.py
|
||||
msgid "A backup is already running, check back in a few minutes"
|
||||
@@ -74,6 +74,11 @@ msgstr ""
|
||||
msgid "File must be a .zip backup file"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
#, python-format
|
||||
msgid "Backup file is too large (max %(mb)s MB)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
msgid "Invalid or corrupted zip file"
|
||||
msgstr ""
|
||||
@@ -128,6 +133,11 @@ msgstr ""
|
||||
msgid "Note: This does not override the main application settings, only watches and groups."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
#, python-format
|
||||
msgid "Max upload size: %(upload)s MB, Max decompressed size: %(decomp)s MB"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
msgid "Include all groups found in backup?"
|
||||
msgstr ""
|
||||
@@ -202,6 +212,10 @@ msgstr "Distill.io"
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX 与 Wachete"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Backup Restore"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Restoring changedetection.io backups is in the"
|
||||
msgstr ""
|
||||
@@ -547,15 +561,15 @@ msgstr "注意:仅更换 User-Agent 往往无法绕过反爬虫技术,务必
|
||||
msgid "all of the ways that the browser is detected"
|
||||
msgstr "浏览器被识别的各种方式"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Connect using Bright Data proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html changedetectionio/templates/_common_fields.html
|
||||
msgid "Tip:"
|
||||
msgstr "提示:"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr "使用 Bright Data 和 Oxylabs 代理连接,更多信息见此处。"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Ignore whitespace, tabs and new-lines/line-feeds when considering if a change was detected."
|
||||
msgstr "判断是否变更时忽略空格、制表符和换行。"
|
||||
@@ -576,15 +590,15 @@ msgstr "渲染 a 标签内容,默认关闭,开启后链接会呈现为"
|
||||
msgid "Changing this could affect the content of your existing watches, possibly trigger alerts etc."
|
||||
msgstr "更改此项可能影响现有监控项内容,可能触发警报等。"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Remove HTML element(s) by CSS and XPath selectors before text conversion."
|
||||
msgstr "在文本转换前通过 CSS 和 XPath 选择器移除 HTML 元素。"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Don't paste HTML here, use only CSS and XPath selectors"
|
||||
msgstr "不要在此粘贴 HTML,仅使用 CSS 和 XPath 选择器"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML."
|
||||
msgstr "每行添加多个元素、CSS 或 XPath 选择器,用于忽略 HTML 的多个部分。"
|
||||
|
||||
@@ -749,8 +763,8 @@ msgid "Tip"
|
||||
msgstr "提示"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
msgstr "对于被封锁的网站,“住宅”和“移动”代理类型可能比“数据中心”更有效。"
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successful than \"Data Center\" for blocked websites."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "\"Name\" will be used for selecting the proxy in the Watch Edit settings"
|
||||
@@ -815,10 +829,48 @@ msgstr "未找到标签"
|
||||
msgid "Updated"
|
||||
msgstr "已更新"
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Activate for individual watches in this tag/group?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Auto-apply to watches with URLs matching"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "e.g. *://example.com/* or github.com/myorg"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Tag colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Tag name"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Filters & Triggers"
|
||||
msgstr "过滤器与触发器"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid ""
|
||||
"Automatically applies this tag to any watch whose URL matches. Supports wildcards: <code>*example.com*</code> or "
|
||||
"plain substring: <code>github.com/myorg</code>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Currently matching watches"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Custom colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are"
|
||||
msgstr "这些设置会"
|
||||
@@ -896,7 +948,11 @@ msgstr "标签/名称"
|
||||
msgid "No website organisational tags/groups configured"
|
||||
msgstr "未配置分组/标签"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "静音通知"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/edit.py
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Edit"
|
||||
msgstr "编辑"
|
||||
@@ -1011,6 +1067,10 @@ msgstr "未找到监控项"
|
||||
msgid "Cleared snapshot history for watch {}"
|
||||
msgstr "已清除监控项 {} 的快照历史"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr "clear"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
msgid "History clearing started in background"
|
||||
msgstr "历史清理已在后台开始"
|
||||
@@ -1095,6 +1155,14 @@ msgstr ""
|
||||
msgid "Could not load '{}' processor, processor plugin might be missing."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "System settings default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Updated watch - unpaused!"
|
||||
msgstr "监控项已更新并取消暂停!"
|
||||
@@ -1107,6 +1175,10 @@ msgstr "监控项已更新。"
|
||||
msgid "Preview unavailable - No fetch/check completed or triggers not reached"
|
||||
msgstr "无法预览 - 尚未完成抓取/检查或未满足触发条件"
|
||||
|
||||
#: changedetectionio/blueprint/ui/preview.py
|
||||
msgid "Diff"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "This will remove version history (snapshots) for ALL watches, but keep your list of URLs!"
|
||||
msgstr "这将删除所有监控项的版本历史(快照),但会保留 URL 列表!"
|
||||
@@ -1131,10 +1203,6 @@ msgstr "确认文本"
|
||||
msgid "Type in the word"
|
||||
msgstr "请输入单词"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr "clear"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "to confirm that you understand."
|
||||
msgstr "以确认你已理解。"
|
||||
@@ -1220,14 +1288,17 @@ msgid "Jump"
|
||||
msgstr "跳转"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Text"
|
||||
msgstr "错误文本"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Screenshot"
|
||||
msgstr "错误截图"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Text"
|
||||
msgstr "文本"
|
||||
|
||||
@@ -1235,7 +1306,8 @@ msgstr "文本"
|
||||
msgid "Current screenshot"
|
||||
msgstr "当前截图"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/processors/extract.py
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Extract Data"
|
||||
msgstr "提取数据"
|
||||
|
||||
@@ -1327,6 +1399,10 @@ msgstr "帮助与示例在此"
|
||||
msgid "Organisational tag/group name used in the main listing page"
|
||||
msgstr "分组/标签名称"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Also automatically applied by URL pattern:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Automatically uses the page title if found, you can also use your own title/description here"
|
||||
msgstr "若检测到页面标题将自动使用,你也可以在此自定义标题/描述"
|
||||
@@ -1353,6 +1429,10 @@ msgstr "方式(默认),适用于无需 JavaScript 渲染的网站。"
|
||||
msgid "method requires a network connection to a running WebDriver+Chrome server, set by the ENV var 'WEBDRIVER_URL'."
|
||||
msgstr "方式需要连接正在运行的 WebDriver+Chrome 服务器,通过环境变量 'WEBDRIVER_URL' 设置。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr "使用 Bright Data 和 Oxylabs 代理连接,更多信息见此处。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Check/Scan all"
|
||||
msgstr "检查/扫描全部"
|
||||
@@ -1817,6 +1897,26 @@ msgstr "尚未配置网站监控项,请在上方输入 URL 或"
|
||||
msgid "import a list"
|
||||
msgstr "导入列表"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Pause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnPause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Mute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnMute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Create a link to share watch config with others"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Detecting restock and price"
|
||||
msgstr "检测补货与价格"
|
||||
@@ -1846,6 +1946,7 @@ msgid "Queued"
|
||||
msgstr "队列中"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
#: changedetectionio/processors/text_json_diff/difference.py
|
||||
msgid "History"
|
||||
msgstr "历史"
|
||||
|
||||
@@ -1879,6 +1980,168 @@ msgstr "重新检查全部"
|
||||
msgid "in '%(title)s'"
|
||||
msgstr "(“%(title)s”中)"
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Not Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Contains"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Does NOT Contain"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Ends With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length minimum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length maximum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Matches Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Does NOT Match Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Extracted number after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Page text after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "A value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Value is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text change distance"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Not enough history to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Snapshot too large for edit statistics, skipping."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Unable to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein Text Similarity Details"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Raw distance (edits needed)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Percent similar"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid ""
|
||||
"Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one "
|
||||
"into the other."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Error calculating Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count of content"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Content Analysis"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count (latest snapshot)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count is a simple measure of content length, calculated by splitting text on whitespace."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/content_fetchers/requests.py
|
||||
msgid "Basic fast Plaintext/HTTP Client"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/flask_app.py
|
||||
#: changedetectionio/realtime/socket_server.py
|
||||
msgid "Not yet"
|
||||
@@ -2161,10 +2424,26 @@ msgstr "操作"
|
||||
msgid "Selector"
|
||||
msgstr "选择器"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "CSS or xPath selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "value"
|
||||
msgstr "值"
|
||||
|
||||
#: changedetectionio/conditions/form.py changedetectionio/forms.py
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Web Page URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group Tag"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Time Between Check"
|
||||
msgstr "检查间隔"
|
||||
@@ -2181,6 +2460,10 @@ msgstr "CSS/JSONPath/JQ/XPath 过滤器"
|
||||
msgid "Remove elements"
|
||||
msgstr "移除元素"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract lines containing"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract text"
|
||||
msgstr "提取文本"
|
||||
@@ -2249,7 +2532,8 @@ msgstr "文本匹配时阻止变更检测"
|
||||
msgid "Execute JavaScript before change detection"
|
||||
msgstr "在变更检测前执行 JavaScript"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Save"
|
||||
msgstr "保存"
|
||||
|
||||
@@ -2317,7 +2601,7 @@ msgstr "模板语法无效:%(error)s"
|
||||
msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr "“%(header)s”请求头中的模板语法无效:%(error)s"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
|
||||
msgid "Name"
|
||||
msgstr "名称"
|
||||
|
||||
@@ -2413,6 +2697,10 @@ msgstr "忽略文本"
|
||||
msgid "Ignore whitespace"
|
||||
msgstr "忽略空白"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Screenshot: Minimum Change Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py changedetectionio/processors/image_ssim_diff/forms.py
|
||||
msgid "Must be between 0 and 100"
|
||||
msgstr "必须介于 0 到 100 之间"
|
||||
@@ -2612,6 +2900,42 @@ msgstr "适用于单一商品页面的补货与价格检测"
|
||||
msgid "Detects if the product goes back to in-stock"
|
||||
msgstr "检测商品是否恢复有库存"
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "This tool will extract text data from all of the watch history."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "For example, to extract only the numbers from text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Raw text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "RegEx to extract:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Be sure to test your RegEx here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Each RegEx group bracket"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "will be in its own column, the first column value is always the date."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/text_json_diff/processor.py
|
||||
msgid "Webpage Text/HTML, JSON and PDF changes"
|
||||
msgstr "网页文本/HTML、JSON 和 PDF 变更"
|
||||
@@ -2678,6 +3002,11 @@ msgstr "监视器组/标签"
|
||||
msgid "The URL of the preview page generated by changedetection.io."
|
||||
msgstr "changedetection.io 生成的预览页面 URL。"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
msgstr "该监控项的差异输出 URL。"
|
||||
@@ -2686,6 +3015,14 @@ msgstr "该监控项的差异输出 URL。"
|
||||
msgid "The diff output - only changes, additions, and removals"
|
||||
msgstr "差异输出 - 仅包含更改、新增与删除"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "All diff variants accept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "args, e.g."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The diff output - only changes, additions, and removals —"
|
||||
msgstr "差异输出 - 仅包含更改、新增与删除 —"
|
||||
@@ -2722,6 +3059,18 @@ msgstr "差异输出 - 完整差异内容 —"
|
||||
msgid "The diff output - patch in unified format"
|
||||
msgstr "差异输出 - 统一格式补丁"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the previous version — e.g. the old price. Best when a single value changes per "
|
||||
"line; multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the new version — e.g. the new price. Best when a single value changes per line; "
|
||||
"multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The current snapshot text contents value, useful when combined with JSON or CSS filters"
|
||||
msgstr "当前快照的文本内容值,与 JSON 或 CSS 过滤器结合使用时很有用"
|
||||
@@ -2766,7 +3115,7 @@ msgstr "请阅读通知服务 Wiki 以了解重要配置说明"
|
||||
msgid "Use"
|
||||
msgstr "使用"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#: changedetectionio/templates/_common_fields.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Show advanced help and tips"
|
||||
msgstr "显示高级帮助和提示"
|
||||
|
||||
@@ -2870,6 +3219,26 @@ msgstr "关于 Jinja2 内置过滤器的完整参考,请见"
|
||||
msgid "Format for all notifications"
|
||||
msgstr "所有通知的格式"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Discord does not render HTML — switch to"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Plain Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "format to avoid"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "and other HTML entities appearing literally in your notifications."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Entry"
|
||||
msgstr "条目"
|
||||
@@ -2986,10 +3355,18 @@ msgstr "此文本存在时将不会进行变更检测。"
|
||||
msgid "Blocked text"
|
||||
msgstr "阻止文本"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "A new version is available"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr "搜索,或使用 Alt+S 快捷键"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Share this link:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Real-time updates offline"
|
||||
msgstr "实时更新离线"
|
||||
@@ -3068,6 +3445,26 @@ msgstr "阻止文本来自该监控项的 CSS/JSON 过滤结果"
|
||||
msgid "All lines here must not exist (think of each line as \"OR\")"
|
||||
msgstr "此处所有行必须不存在(每行视为“或”)"
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Keep only lines that contain any of these words or phrases (plain text, case-insensitive)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "One entry per line — any line in the page text that contains a match is kept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Simpler alternative to regex — use this when you just want lines about a specific topic"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Example: enter"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "to keep only lines mentioning temperature readings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Extracts text in the final output (line by line) after other filters using regular expressions or string match:"
|
||||
msgstr "在其他过滤器之后,按行从最终输出中提取文本(使用正则或字符串匹配):"
|
||||
@@ -3140,10 +3537,6 @@ msgstr "调度已暂停 - 点击恢复"
|
||||
msgid "Unmute notifications"
|
||||
msgstr "取消静音通知"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "静音通知"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Notifications are muted - click to unmute"
|
||||
msgstr "通知已静音 - 点击取消静音"
|
||||
@@ -3200,3 +3593,6 @@ msgstr "主设置"
|
||||
#~ msgid "Marking watches as viewed in background..."
|
||||
#~ msgstr "正在后台将监控项标记为已读..."
|
||||
|
||||
#~ msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
#~ msgstr "对于被封锁的网站,“住宅”和“移动”代理类型可能比“数据中心”更有效。"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-02-23 03:54+0100\n"
|
||||
"POT-Creation-Date: 2026-04-15 03:04+0900\n"
|
||||
"PO-Revision-Date: 2026-01-15 12:00+0800\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh_Hant_TW\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.16.0\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
|
||||
#: changedetectionio/blueprint/backups/__init__.py
|
||||
msgid "A backup is already running, check back in a few minutes"
|
||||
@@ -74,6 +74,11 @@ msgstr ""
|
||||
msgid "File must be a .zip backup file"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
#, python-format
|
||||
msgid "Backup file is too large (max %(mb)s MB)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/restore.py
|
||||
msgid "Invalid or corrupted zip file"
|
||||
msgstr ""
|
||||
@@ -128,6 +133,11 @@ msgstr ""
|
||||
msgid "Note: This does not override the main application settings, only watches and groups."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
#, python-format
|
||||
msgid "Max upload size: %(upload)s MB, Max decompressed size: %(decomp)s MB"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/backups/templates/backup_restore.html
|
||||
msgid "Include all groups found in backup?"
|
||||
msgstr ""
|
||||
@@ -202,6 +212,10 @@ msgstr "Distill.io"
|
||||
msgid ".XLSX & Wachete"
|
||||
msgstr ".XLSX 和 Wachete"
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Backup Restore"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/imports/templates/import.html
|
||||
msgid "Restoring changedetection.io backups is in the"
|
||||
msgstr ""
|
||||
@@ -547,15 +561,15 @@ msgstr ""
|
||||
msgid "all of the ways that the browser is detected"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Connect using Bright Data proxies, find out more here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html changedetectionio/templates/_common_fields.html
|
||||
msgid "Tip:"
|
||||
msgstr "提示:"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr "使用 Bright Data 和 Oxylabs 代理連接,在此處了解更多資訊。"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "Ignore whitespace, tabs and new-lines/line-feeds when considering if a change was detected."
|
||||
msgstr ""
|
||||
@@ -576,15 +590,15 @@ msgstr ""
|
||||
msgid "Changing this could affect the content of your existing watches, possibly trigger alerts etc."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Remove HTML element(s) by CSS and XPath selectors before text conversion."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Don't paste HTML here, use only CSS and XPath selectors"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Add multiple elements, CSS or XPath selectors per line to ignore multiple parts of the HTML."
|
||||
msgstr ""
|
||||
|
||||
@@ -749,7 +763,7 @@ msgid "Tip"
|
||||
msgstr "提示"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
msgid "\"Residential\" and \"Mobile\" proxy type can be more successful than \"Data Center\" for blocked websites."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings.html
|
||||
@@ -815,10 +829,48 @@ msgstr "找不到標籤"
|
||||
msgid "Updated"
|
||||
msgstr "已更新"
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Activate for individual watches in this tag/group?"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Auto-apply to watches with URLs matching"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "e.g. *://example.com/* or github.com/myorg"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Tag colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/form.py
|
||||
msgid "Tag name"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Filters & Triggers"
|
||||
msgstr "過濾器與觸發器"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid ""
|
||||
"Automatically applies this tag to any watch whose URL matches. Supports wildcards: <code>*example.com*</code> or "
|
||||
"plain substring: <code>github.com/myorg</code>"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Currently matching watches"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Custom colour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "Leave unchecked to use the auto-generated colour based on the tag name."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/edit-tag.html
|
||||
msgid "These settings are"
|
||||
msgstr "這些設定會"
|
||||
@@ -896,7 +948,11 @@ msgstr "標籤 / 名稱"
|
||||
msgid "No website organisational tags/groups configured"
|
||||
msgstr "未設定群組/標籤"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "靜音通知"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/blueprint/ui/edit.py
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Edit"
|
||||
msgstr "編輯"
|
||||
@@ -1011,6 +1067,10 @@ msgstr "找不到監測任務"
|
||||
msgid "Cleared snapshot history for watch {}"
|
||||
msgstr "已清除監測任務 {} 的快照歷史記錄"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr "clear"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
msgid "History clearing started in background"
|
||||
msgstr ""
|
||||
@@ -1095,6 +1155,14 @@ msgstr ""
|
||||
msgid "Could not load '{}' processor, processor plugin might be missing."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "System settings default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/edit.py
|
||||
msgid "Updated watch - unpaused!"
|
||||
msgstr "已更新監測任務 - 已取消暫停!"
|
||||
@@ -1107,6 +1175,10 @@ msgstr "已更新監測任務。"
|
||||
msgid "Preview unavailable - No fetch/check completed or triggers not reached"
|
||||
msgstr "預覽無法使用 - 未完成抓取 / 檢查或未觸發"
|
||||
|
||||
#: changedetectionio/blueprint/ui/preview.py
|
||||
msgid "Diff"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "This will remove version history (snapshots) for ALL watches, but keep your list of URLs!"
|
||||
msgstr "這將移除「所有」監測任務的版本歷史記錄(快照),但保留您的 URL 列表!"
|
||||
@@ -1131,10 +1203,6 @@ msgstr "確認文字"
|
||||
msgid "Type in the word"
|
||||
msgstr "輸入單字"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "clear"
|
||||
msgstr "clear"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/clear_all_history.html
|
||||
msgid "to confirm that you understand."
|
||||
msgstr "以確認您已了解。"
|
||||
@@ -1220,14 +1288,17 @@ msgid "Jump"
|
||||
msgstr "跳轉"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Text"
|
||||
msgstr "錯誤文字"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Error Screenshot"
|
||||
msgstr "錯誤截圖"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/blueprint/ui/templates/preview.html
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Text"
|
||||
msgstr "文字"
|
||||
|
||||
@@ -1235,7 +1306,8 @@ msgstr "文字"
|
||||
msgid "Current screenshot"
|
||||
msgstr "目前截圖"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html
|
||||
#: changedetectionio/blueprint/ui/templates/diff.html changedetectionio/processors/extract.py
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Extract Data"
|
||||
msgstr "提取資料"
|
||||
|
||||
@@ -1327,6 +1399,10 @@ msgstr "幫助與範例請見此處"
|
||||
msgid "Organisational tag/group name used in the main listing page"
|
||||
msgstr "群組/標籤名稱"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Also automatically applied by URL pattern:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Automatically uses the page title if found, you can also use your own title/description here"
|
||||
msgstr "如果找到頁面標題將自動使用,您也可以在此使用您自己的標題 / 描述"
|
||||
@@ -1353,6 +1429,10 @@ msgstr "方法(預設),適用於您監測的網站不需要 Javascript 渲
|
||||
msgid "method requires a network connection to a running WebDriver+Chrome server, set by the ENV var 'WEBDRIVER_URL'."
|
||||
msgstr "方法需要連線到執行中的 WebDriver + Chrome 伺服器,由環境變數 'WEBDRIVER_URL' 設定。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Connect using Bright Data and Oxylabs Proxies, find out more here."
|
||||
msgstr "使用 Bright Data 和 Oxylabs 代理連接,在此處了解更多資訊。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/templates/edit.html
|
||||
msgid "Check/Scan all"
|
||||
msgstr "檢查 / 掃描全部"
|
||||
@@ -1817,6 +1897,26 @@ msgstr "未設定網站監測任務,請在上方欄位新增 URL,或"
|
||||
msgid "import a list"
|
||||
msgstr "匯入列表"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Pause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnPause checks"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Mute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "UnMute notification"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Create a link to share watch config with others"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Detecting restock and price"
|
||||
msgstr "檢測補貨與價格"
|
||||
@@ -1846,6 +1946,7 @@ msgid "Queued"
|
||||
msgstr "已排程"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
#: changedetectionio/processors/text_json_diff/difference.py
|
||||
msgid "History"
|
||||
msgstr "歷史記錄"
|
||||
|
||||
@@ -1879,6 +1980,168 @@ msgstr "複查全部"
|
||||
msgid "in '%(title)s'"
|
||||
msgstr "於 '%(title)s'"
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Greater Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Less Than or Equal To"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Not Equals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Contains"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/__init__.py
|
||||
msgid "Choose one - Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Does NOT Contain"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Ends With"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length minimum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Length maximum"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Matches Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Text Does NOT Match Regex"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Extracted number after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/default_plugin.py
|
||||
msgid "Page text after 'Filters & Triggers'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "A value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Operator is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Field is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/form.py
|
||||
msgid "Value is required."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein - Text change distance"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Not enough history to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Snapshot too large for edit statistics, skipping."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Unable to calculate Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Levenshtein Text Similarity Details"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Raw distance (edits needed)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Similarity ratio"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Percent similar"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid ""
|
||||
"Levenshtein metrics compare the last two snapshots, measuring how many character edits are needed to transform one "
|
||||
"into the other."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/levenshtein_plugin.py
|
||||
msgid "Error calculating Levenshtein metrics"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count of content"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Content Analysis"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count (latest snapshot)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/conditions/plugins/wordcount_plugin.py
|
||||
msgid "Word count is a simple measure of content length, calculated by splitting text on whitespace."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/content_fetchers/requests.py
|
||||
msgid "Basic fast Plaintext/HTTP Client"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/flask_app.py
|
||||
#: changedetectionio/realtime/socket_server.py
|
||||
msgid "Not yet"
|
||||
@@ -2161,10 +2424,26 @@ msgstr "操作"
|
||||
msgid "Selector"
|
||||
msgstr "選擇器"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "CSS or xPath selector"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "value"
|
||||
msgstr "值"
|
||||
|
||||
#: changedetectionio/conditions/form.py changedetectionio/forms.py
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Web Page URL"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Group Tag"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Time Between Check"
|
||||
msgstr "檢查間隔"
|
||||
@@ -2181,6 +2460,10 @@ msgstr "CSS / JSONPath / JQ / XPath 過濾器"
|
||||
msgid "Remove elements"
|
||||
msgstr "移除元素"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract lines containing"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Extract text"
|
||||
msgstr "提取文字"
|
||||
@@ -2249,7 +2532,8 @@ msgstr "當文字符合時,阻擋變更檢測"
|
||||
msgid "Execute JavaScript before change detection"
|
||||
msgstr "在變更檢測前執行 JavaScript"
|
||||
|
||||
#: changedetectionio/blueprint/tags/templates/groups-overview.html changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/blueprint/tags/templates/groups-overview.html
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Save"
|
||||
msgstr "儲存"
|
||||
|
||||
@@ -2317,7 +2601,7 @@ msgstr "無效的範本語法:%(error)s"
|
||||
msgid "Invalid template syntax in \"%(header)s\" header: %(error)s"
|
||||
msgstr "「%(header)s」標頭中的範本語法無效:%(error)s"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
#: changedetectionio/blueprint/tags/form.py changedetectionio/forms.py
|
||||
msgid "Name"
|
||||
msgstr "名稱"
|
||||
|
||||
@@ -2413,6 +2697,10 @@ msgstr "忽略文字"
|
||||
msgid "Ignore whitespace"
|
||||
msgstr "忽略空白"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Screenshot: Minimum Change Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py changedetectionio/processors/image_ssim_diff/forms.py
|
||||
msgid "Must be between 0 and 100"
|
||||
msgstr "必須介於 0 到 100 之間"
|
||||
@@ -2612,6 +2900,42 @@ msgstr "針對單一產品頁面的補貨與價格檢測"
|
||||
msgid "Detects if the product goes back to in-stock"
|
||||
msgstr "檢測產品是否恢復庫存"
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "This tool will extract text data from all of the watch history."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "A <strong>RegEx</strong> is a pattern that identifies exactly which part inside of the text that you want to extract."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "For example, to extract only the numbers from text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Raw text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "RegEx to extract:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Be sure to test your RegEx here."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "Each RegEx group bracket"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/templates/extract.html
|
||||
msgid "will be in its own column, the first column value is always the date."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/processors/text_json_diff/processor.py
|
||||
msgid "Webpage Text/HTML, JSON and PDF changes"
|
||||
msgstr "網頁文字 / HTML、JSON 和 PDF 變更"
|
||||
@@ -2678,6 +3002,11 @@ msgstr "群組 / 標籤"
|
||||
msgid "The URL of the preview page generated by changedetection.io."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#, python-format
|
||||
msgid "Date/time of the change, accepts format=, change_datetime(format='%A')', default is '%Y-%m-%d %H:%M:%S %Z'"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The URL of the diff output for the watch."
|
||||
msgstr ""
|
||||
@@ -2686,6 +3015,14 @@ msgstr ""
|
||||
msgid "The diff output - only changes, additions, and removals"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "All diff variants accept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "args, e.g."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The diff output - only changes, additions, and removals —"
|
||||
msgstr ""
|
||||
@@ -2722,6 +3059,18 @@ msgstr ""
|
||||
msgid "The diff output - patch in unified format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the previous version — e.g. the old price. Best when a single value changes per "
|
||||
"line; multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid ""
|
||||
"Only the changed words/values from the new version — e.g. the new price. Best when a single value changes per line; "
|
||||
"multiple changed fragments are joined by newline."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "The current snapshot text contents value, useful when combined with JSON or CSS filters"
|
||||
msgstr ""
|
||||
@@ -2766,7 +3115,7 @@ msgstr ""
|
||||
msgid "Use"
|
||||
msgstr "使用"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
#: changedetectionio/templates/_common_fields.html changedetectionio/templates/edit/include_subtract.html
|
||||
msgid "Show advanced help and tips"
|
||||
msgstr "顯示進階說明與提示"
|
||||
|
||||
@@ -2870,6 +3219,26 @@ msgstr ""
|
||||
msgid "Format for all notifications"
|
||||
msgstr "所有通知的格式"
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Discord does not render HTML — switch to"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "Plain Text"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "format to avoid"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_common_fields.html
|
||||
msgid "and other HTML entities appearing literally in your notifications."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
@@ -2986,10 +3355,18 @@ msgstr "當文字符合時,阻擋變更檢測"
|
||||
msgid "Blocked text"
|
||||
msgstr "阻擋文字"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "A new version is available"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Search, or Use Alt+S Key"
|
||||
msgstr "搜尋,或使用 Alt+S 鍵"
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Share this link:"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/base.html
|
||||
msgid "Real-time updates offline"
|
||||
msgstr "離線即時更新"
|
||||
@@ -3068,6 +3445,26 @@ msgstr ""
|
||||
msgid "All lines here must not exist (think of each line as \"OR\")"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Keep only lines that contain any of these words or phrases (plain text, case-insensitive)"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "One entry per line — any line in the page text that contains a match is kept"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Simpler alternative to regex — use this when you just want lines about a specific topic"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Example: enter"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "to keep only lines mentioning temperature readings"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/edit/text-options.html
|
||||
msgid "Extracts text in the final output (line by line) after other filters using regular expressions or string match:"
|
||||
msgstr ""
|
||||
@@ -3140,10 +3537,6 @@ msgstr ""
|
||||
msgid "Unmute notifications"
|
||||
msgstr "取消靜音通知"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Mute notifications"
|
||||
msgstr "靜音通知"
|
||||
|
||||
#: changedetectionio/templates/menu.html
|
||||
msgid "Notifications are muted - click to unmute"
|
||||
msgstr "通知已靜音 - 點擊以取消靜音"
|
||||
@@ -3329,3 +3722,6 @@ msgstr "主設定"
|
||||
#~ msgid "Marking watches as viewed in background..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "\"Residential\" and \"Mobile\" proxy type can be more successfull than \"Data Center\" for blocked websites."
|
||||
#~ msgstr ""
|
||||
|
||||
|
||||
@@ -484,7 +484,8 @@ async def async_update_worker(worker_id, q, notification_q, app, datastore, exec
|
||||
# Store favicon if necessary
|
||||
if update_handler.fetcher.favicon_blob and update_handler.fetcher.favicon_blob.get('base64'):
|
||||
watch.bump_favicon(url=update_handler.fetcher.favicon_blob.get('url'),
|
||||
favicon_base_64=update_handler.fetcher.favicon_blob.get('base64')
|
||||
favicon_base_64=update_handler.fetcher.favicon_blob.get('base64'),
|
||||
mime_type=update_handler.fetcher.favicon_blob.get('mime_type')
|
||||
)
|
||||
|
||||
datastore.update_watch(uuid=uuid, update_obj=final_updates)
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ services:
|
||||
# - PLAYWRIGHT_DRIVER_URL=ws://browser-sockpuppet-chrome:3000
|
||||
#
|
||||
#
|
||||
# Alternative WebDriver/selenium URL, do not use "'s or 's! (old, deprecated, does not support screenshots very well)
|
||||
# Alternative WebDriver/selenium URL, do not use "'s or 's! (old, deprecated, does not support screenshots very well, Can't handle custom headers etc)
|
||||
# - WEBDRIVER_URL=http://browser-selenium-chrome:4444/wd/hub
|
||||
#
|
||||
# WebDriver proxy settings webdriver_proxyType, webdriver_ftpProxy, webdriver_noProxy,
|
||||
|
||||
@@ -460,6 +460,13 @@ components:
|
||||
maxLength: 5000
|
||||
maxItems: 100
|
||||
description: Text that should NOT be present (triggers alert if found)
|
||||
extract_lines_containing:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxLength: 5000
|
||||
maxItems: 100
|
||||
description: Keep only lines containing these substrings (plain text, case-insensitive) — simpler alternative to regex
|
||||
extract_text:
|
||||
type: array
|
||||
items:
|
||||
@@ -725,6 +732,13 @@ components:
|
||||
- true: Tag settings override watch settings
|
||||
- false: Tag settings do not override (watches use their own settings)
|
||||
- null: Not decided yet / inherit default behavior
|
||||
url_match_pattern:
|
||||
type: string
|
||||
description: |
|
||||
Automatically apply this tag to any watch whose URL matches this pattern.
|
||||
Supports fnmatch wildcards (* and ?): e.g. *://example.com/* or github.com/myorg.
|
||||
Plain strings are matched as case-insensitive substrings.
|
||||
Leave empty to disable auto-matching.
|
||||
# Future: Aggregated statistics from all watches with this tag
|
||||
# check_count:
|
||||
# type: integer
|
||||
|
||||
+31
-7
File diff suppressed because one or more lines are too long
+2
-2
@@ -76,7 +76,7 @@ elementpath==5.1.1
|
||||
# - Pixelmatch is used as fallback when OpenCV is unavailable
|
||||
# - To install manually: pip install opencv-python-headless>=4.8.0.76
|
||||
|
||||
selenium~=4.31.0
|
||||
selenium~=4.43.0
|
||||
|
||||
# Templating, so far just in the URLs but in the future can be for the notifications also
|
||||
jinja2~=3.1
|
||||
@@ -98,7 +98,7 @@ pytest-flask ~=1.3
|
||||
pytest-mock ~=3.15
|
||||
|
||||
# OpenAPI validation support
|
||||
openapi-core[flask] ~= 0.22
|
||||
openapi-core[flask] ~= 0.23
|
||||
|
||||
loguru
|
||||
|
||||
|
||||
Reference in New Issue
Block a user