mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-05-11 20:21:18 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eae9521924 | |||
| 1dbbbd6819 | |||
| d07b57b816 | |||
| 22ef98d58e | |||
| 9f6e4ea0ad | |||
| 7d2803e179 | |||
| f93dc7746d | |||
| d427dbc2b2 | |||
| 52b189fc7c | |||
| 866b442576 | |||
| ba20f66cee | |||
| e064bcea13 | |||
| 74a7eb1b11 | |||
| 79d75f7926 |
@@ -44,10 +44,60 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
lint-template-i18n:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Check for fragmented gettext calls in templates
|
||||
run: |
|
||||
python3 << 'PYEOF'
|
||||
import re, sys
|
||||
from pathlib import Path
|
||||
|
||||
# Detects adjacent {{ _(...) }} calls on the same line separated only by HTML
|
||||
# tags, whitespace, or non-translating Jinja2 variables — the anti-pattern of
|
||||
# splitting a single sentence across multiple msgids.
|
||||
# See https://github.com/dgtlmoon/changedetection.io/issues/4074 for background.
|
||||
#
|
||||
# The correct fix is to consolidate fragments into one entire-sentence msgid,
|
||||
# injecting dynamic values via %(name)s kwargs — per the GNU gettext manual
|
||||
# sections "Entire sentences" and "No string concatenation". See PR #4076 for
|
||||
# worked examples of each consolidation pattern.
|
||||
#
|
||||
# BASELINE: this limit reflects pre-existing violations present when this check
|
||||
# was introduced. It must only ever go DOWN. Each time you fix a template, lower
|
||||
# the limit by the number of lines fixed so the improvement is locked in.
|
||||
# When the count reaches 0, replace the baseline check with a hard sys.exit(1).
|
||||
BASELINE_LIMIT = 44
|
||||
|
||||
FRAGMENT_RE = re.compile(
|
||||
r'\{\{[^{}]*\b_\s*\([^)]*\)[^{}]*\}\}'
|
||||
r'(?:\s*(?:<[^>]+>|\{\{(?![^}]*\b_\s*\()[^}]*\}\})\s*)+'
|
||||
r'\{\{[^{}]*\b_\s*\([^)]*\)[^{}]*\}\}'
|
||||
)
|
||||
|
||||
violations = []
|
||||
for f in sorted(Path('changedetectionio').rglob('*.html')):
|
||||
for lineno, line in enumerate(f.read_text().splitlines(), 1):
|
||||
if FRAGMENT_RE.search(line):
|
||||
violations.append(f"{f}:{lineno}: {line.strip()[:120]}")
|
||||
|
||||
count = len(violations)
|
||||
print(f"Fragmented i18n calls found: {count} (limit: {BASELINE_LIMIT})")
|
||||
for v in violations:
|
||||
print(v)
|
||||
|
||||
if count > BASELINE_LIMIT:
|
||||
print(f"\nERROR: {count} fragmented gettext calls exceed the baseline of {BASELINE_LIMIT}.")
|
||||
print("Consolidate adjacent _() calls into a single entire-sentence msgid.")
|
||||
print("See https://github.com/dgtlmoon/changedetection.io/issues/4074 for patterns.")
|
||||
sys.exit(1)
|
||||
PYEOF
|
||||
|
||||
test-application-3-10:
|
||||
# Only run on push to master (including PR merges)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
needs: [lint-code, lint-translations]
|
||||
needs: [lint-code, lint-translations, lint-template-i18n]
|
||||
uses: ./.github/workflows/test-stack-reusable-workflow.yml
|
||||
with:
|
||||
python-version: '3.10'
|
||||
@@ -55,7 +105,7 @@ jobs:
|
||||
|
||||
test-application-3-11:
|
||||
# Always run
|
||||
needs: [lint-code, lint-translations]
|
||||
needs: [lint-code, lint-translations, lint-template-i18n]
|
||||
uses: ./.github/workflows/test-stack-reusable-workflow.yml
|
||||
with:
|
||||
python-version: '3.11'
|
||||
@@ -63,7 +113,7 @@ jobs:
|
||||
test-application-3-12:
|
||||
# Only run on push to master (including PR merges)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
needs: [lint-code, lint-translations]
|
||||
needs: [lint-code, lint-translations, lint-template-i18n]
|
||||
uses: ./.github/workflows/test-stack-reusable-workflow.yml
|
||||
with:
|
||||
python-version: '3.12'
|
||||
@@ -72,7 +122,7 @@ jobs:
|
||||
test-application-3-13:
|
||||
# Only run on push to master (including PR merges)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
needs: [lint-code, lint-translations]
|
||||
needs: [lint-code, lint-translations, lint-template-i18n]
|
||||
uses: ./.github/workflows/test-stack-reusable-workflow.yml
|
||||
with:
|
||||
python-version: '3.13'
|
||||
@@ -81,7 +131,7 @@ jobs:
|
||||
|
||||
test-application-3-14:
|
||||
#if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
needs: [lint-code, lint-translations]
|
||||
needs: [lint-code, lint-translations, lint-template-i18n]
|
||||
uses: ./.github/workflows/test-stack-reusable-workflow.yml
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
@@ -352,4 +352,6 @@ changedetectionio.html_tools.elementpath_tostring: Copyright (c), 2018-2021, SIS
|
||||
|
||||
Recognition of fantastic contributors to the project
|
||||
|
||||
<sub>Developer note: see [translation guide](changedetectionio/translations/README.md) for i18n template patterns and workflow.</sub>
|
||||
|
||||
- Constantin Hong https://github.com/Constantin1489
|
||||
|
||||
@@ -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.10'
|
||||
__version__ = '0.55.2'
|
||||
|
||||
from changedetectionio.strtobool import strtobool
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
<div class="tab-pane-inner" id="ai">
|
||||
<script src="{{ url_for('static_content', group='js', filename='sub-tabs.js') }}"></script>
|
||||
|
||||
{# TRANSLATORS: 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide #}
|
||||
{% set _usage_label = pgettext('AI usage stats', 'Usage') %}
|
||||
{% call stab_shell('ai-settings', [
|
||||
{'id': 'overview', 'label': _('Overview'), 'icon': '✦'},
|
||||
{'id': 'provider', 'label': _('Provider'), 'icon': '⚙'},
|
||||
{'id': 'prompts', 'label': _('Prompts'), 'icon': '≡'},
|
||||
{'id': 'behaviour', 'label': _('Behaviour'), 'icon': '⚑'},
|
||||
{'id': 'usage', 'label': _('Usage'), 'icon': '$'},
|
||||
{'id': 'usage', 'label': _usage_label, 'icon': '$'},
|
||||
]) %}
|
||||
|
||||
{# ── Overview ──────────────────────────────────────────────────────────── #}
|
||||
@@ -375,7 +377,7 @@
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const LIVE_PROVIDERS = ['openai', 'anthropic', 'gemini', 'ollama'];
|
||||
const LIVE_PROVIDERS = ['openai', 'anthropic', 'gemini', 'ollama', 'openrouter'];
|
||||
const BASE_DEFAULTS = { ollama: 'http://localhost:11434' };
|
||||
const KEY_HINTS = {
|
||||
openai: '{{ _("platform.openai.com → API keys") }}',
|
||||
@@ -399,7 +401,7 @@
|
||||
|
||||
fetchGroup.style.display = LIVE_PROVIDERS.includes(provider) ? '' : 'none';
|
||||
|
||||
const needsBase = provider === 'ollama' || provider === 'openrouter';
|
||||
const needsBase = provider === 'ollama';
|
||||
baseGroup.style.display = needsBase ? '' : 'none';
|
||||
if (BASE_DEFAULTS[provider] !== undefined) {
|
||||
if (!baseField.value) baseField.value = BASE_DEFAULTS[provider];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
{% from '_helpers.html' import render_field, render_checkbox_field, render_button, render_time_schedule_form, playwright_warning, only_playwright_type_watches_warning, highlight_trigger_ignored_explainer, render_conditions_fieldlist_of_formfields_as_table, render_ternary_field %}
|
||||
{% from '_helpers.html' import render_field, render_checkbox_field, render_button, render_time_schedule_form, only_playwright_type_watches_warning, highlight_trigger_ignored_explainer, render_conditions_fieldlist_of_formfields_as_table, render_ternary_field %}
|
||||
{% from '_common_fields.html' import render_common_settings_form %}
|
||||
<script src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
||||
<script src="{{url_for('static_content', group='js', filename='vis.js')}}" defer></script>
|
||||
|
||||
@@ -223,8 +223,8 @@ window.watchOverviewI18n = {
|
||||
{%- if any_has_restock_price_processor -%}
|
||||
<th>{{ _('Restock & Price') }}</th>
|
||||
{%- endif -%}
|
||||
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_checked' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_checked', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last') }}</span> {{ _('Checked') }} <span class='arrow {{link_order}}'></span></a></th>
|
||||
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_changed' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_changed', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last') }}</span> {{ _('Changed') }} <span class='arrow {{link_order}}'></span></a></th>
|
||||
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_checked' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_checked', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last Checked') }}</span><span class="hide-on-desktop">{{ _('Checked') }}</span> <span class='arrow {{link_order}}'></span></a></th>
|
||||
<th><a class="{{ 'active '+link_order if sort_attribute == 'last_changed' else 'inactive' }}" href="{{url_for('watchlist.index', sort='last_changed', order=link_order, tag=active_tag_uuid)}}"><span class="hide-on-mobile">{{ _('Last Changed') }}</span><span class="hide-on-desktop">{{ _('Changed') }}</span> <span class='arrow {{link_order}}'></span></a></th>
|
||||
<th class="empty-cell"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -981,6 +981,11 @@ def changedetection_app(config=None, datastore_o=None):
|
||||
"queued_data": all_queued
|
||||
})
|
||||
|
||||
if strtobool(os.getenv('HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR', 'False')):
|
||||
logger.warning("SECURITY WARNING: HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR is enabled — "
|
||||
"snapshot reads are NOT confined to the watch data directory. "
|
||||
"This disables protection against path traversal via restored backups (GHSA-8757-69j2-hx56).")
|
||||
|
||||
# Start the async workers during app initialization
|
||||
# Can be overridden by ENV or use the default settings
|
||||
n_workers = int(os.getenv("FETCH_WORKERS", datastore.data['settings']['requests']['workers']))
|
||||
|
||||
@@ -282,7 +282,7 @@ def xpath_filter(xpath_filter, html_content, append_pretty_line_formatting=False
|
||||
try:
|
||||
if is_xml:
|
||||
# So that we can keep CDATA for cdata_in_document_to_text() to process
|
||||
parser = etree.XMLParser(strip_cdata=False)
|
||||
parser = etree.XMLParser(strip_cdata=False, resolve_entities=False, no_network=True)
|
||||
# For XML/RSS content, use etree.fromstring to properly handle XML declarations
|
||||
tree = etree.fromstring(html_content.encode('utf-8') if isinstance(html_content, str) else html_content, parser=parser)
|
||||
else:
|
||||
@@ -346,7 +346,7 @@ def xpath1_filter(xpath_filter, html_content, append_pretty_line_formatting=Fals
|
||||
try:
|
||||
if is_xml:
|
||||
# So that we can keep CDATA for cdata_in_document_to_text() to process
|
||||
parser = etree.XMLParser(strip_cdata=False)
|
||||
parser = etree.XMLParser(strip_cdata=False, resolve_entities=False, no_network=True)
|
||||
# For XML/RSS content, use etree.fromstring to properly handle XML declarations
|
||||
tree = etree.fromstring(html_content.encode('utf-8') if isinstance(html_content, str) else html_content, parser=parser)
|
||||
else:
|
||||
|
||||
@@ -465,22 +465,21 @@ class model(EntityPersistenceMixin, watch_base):
|
||||
if ',' in i:
|
||||
k, v = i.strip().split(',', 2)
|
||||
|
||||
# The index history could contain a relative path, so we need to make the fullpath
|
||||
# so that python can read it
|
||||
# Cross-platform: check for any path separator (works on Windows and Unix)
|
||||
if os.sep not in v and '/' not in v and '\\' not in v:
|
||||
# Relative filename only, no path separators
|
||||
v = os.path.join(self.data_dir, v)
|
||||
else:
|
||||
# It's possible that they moved the datadir on older versions
|
||||
# So the snapshot exists but is in a different path
|
||||
# Cross-platform: use os.path.basename instead of split('/')
|
||||
snapshot_fname = os.path.basename(v)
|
||||
proposed_new_path = os.path.join(self.data_dir, snapshot_fname)
|
||||
if not os.path.exists(v) and os.path.exists(proposed_new_path):
|
||||
v = proposed_new_path
|
||||
# Always resolve history entries to within the watch's own data directory.
|
||||
# Entries restored from backup could contain absolute or traversal paths —
|
||||
# never trust them. Use realpath to also block symlink-based escapes.
|
||||
safe_data_dir = os.path.realpath(self.data_dir)
|
||||
snapshot_fname = os.path.basename(v.strip())
|
||||
resolved_path = os.path.realpath(os.path.join(self.data_dir, snapshot_fname))
|
||||
|
||||
tmp_history[k] = v
|
||||
if not resolved_path.startswith(safe_data_dir + os.sep) and resolved_path != safe_data_dir:
|
||||
logger.warning(f"Skipping unsafe history entry for {self.get('uuid')}: {v!r}")
|
||||
continue
|
||||
|
||||
if not os.path.exists(resolved_path):
|
||||
continue
|
||||
|
||||
tmp_history[k] = resolved_path
|
||||
|
||||
if len(tmp_history):
|
||||
self.__newest_history_key = list(tmp_history.keys())[-1]
|
||||
@@ -563,6 +562,15 @@ class model(EntityPersistenceMixin, watch_base):
|
||||
if not filepath:
|
||||
filepath = self.history[timestamp]
|
||||
|
||||
# Confine every read to the watch's own data directory — defence in depth
|
||||
# against any path that bypasses the history parser (e.g. direct filepath= callers).
|
||||
# Set HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR=true to disable (not recommended).
|
||||
if self.data_dir and not strtobool(os.getenv('HISTORY_SNAPSHOT_FILE_ALLOW_OUTSIDE_WATCH_DATADIR', 'False')):
|
||||
safe_data_dir = os.path.realpath(self.data_dir)
|
||||
resolved = os.path.realpath(filepath)
|
||||
if not (resolved.startswith(safe_data_dir + os.sep) or resolved == safe_data_dir):
|
||||
raise PermissionError(f"Snapshot path {filepath!r} is outside the watch data directory")
|
||||
|
||||
# Check if binary file (image, PDF, etc.)
|
||||
# Binary files are NEVER saved with .br compression, only text files are
|
||||
binary_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.webp', '.pdf', '.bin', '.jfif')
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
* Provides accessible, animated confirmation dialogs
|
||||
*/
|
||||
|
||||
// Escapes a string for safe insertion via innerHTML
|
||||
function _modalEscapeHTML(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
const ModalDialog = {
|
||||
/**
|
||||
* Show a confirmation dialog
|
||||
@@ -125,9 +132,10 @@ const ModalDialog = {
|
||||
* @param {Function} onConfirm - Callback when confirmed
|
||||
*/
|
||||
confirmDelete: function(itemName, onConfirm) {
|
||||
const safeName = _modalEscapeHTML(itemName);
|
||||
return this.confirm({
|
||||
title: 'Delete ' + itemName + '?',
|
||||
message: `<p>Are you sure you want to delete <strong>${itemName}</strong>?</p><p>This action cannot be undone.</p>`,
|
||||
title: 'Delete ' + safeName + '?',
|
||||
message: `<p>Are you sure you want to delete <strong>${safeName}</strong>?</p><p>This action cannot be undone.</p>`,
|
||||
type: 'danger',
|
||||
confirmText: 'Delete',
|
||||
cancelText: 'Cancel',
|
||||
@@ -141,9 +149,10 @@ const ModalDialog = {
|
||||
* @param {Function} onConfirm - Callback when confirmed
|
||||
*/
|
||||
confirmUnlink: function(itemName, onConfirm) {
|
||||
const safeName = _modalEscapeHTML(itemName);
|
||||
return this.confirm({
|
||||
title: 'Unlink ' + itemName + '?',
|
||||
message: `<p>Are you sure you want to unlink all watches from <strong>${itemName}</strong>?</p><p>The tag will be kept but watches will be removed from it.</p>`,
|
||||
title: 'Unlink ' + safeName + '?',
|
||||
message: `<p>Are you sure you want to unlink all watches from <strong>${safeName}</strong>?</p><p>The tag will be kept but watches will be removed from it.</p>`,
|
||||
type: 'warning',
|
||||
confirmText: 'Unlink',
|
||||
cancelText: 'Cancel',
|
||||
@@ -172,11 +181,11 @@ $(document).ready(function() {
|
||||
const url = $element.attr('href');
|
||||
|
||||
const config = {
|
||||
type: $element.data('confirm-type') || 'danger',
|
||||
title: $element.data('confirm-title') || 'Confirm Action',
|
||||
message: $element.data('confirm-message') || '<p>Are you sure you want to proceed?</p>',
|
||||
confirmText: $element.data('confirm-button') || 'Confirm',
|
||||
cancelText: $element.data('cancel-button') || 'Cancel',
|
||||
type: $element.attr('data-confirm-type') || 'danger',
|
||||
title: $element.attr('data-confirm-title') || 'Confirm Action',
|
||||
message: $element.attr('data-confirm-message') || '<p>Are you sure you want to proceed?</p>',
|
||||
confirmText: $element.attr('data-confirm-button') || 'Confirm',
|
||||
cancelText: $element.attr('data-cancel-button') || 'Cancel',
|
||||
onConfirm: function() {
|
||||
// If it's a link, navigate to the URL
|
||||
if ($element.is('a')) {
|
||||
|
||||
@@ -180,4 +180,10 @@ $grid-gap: 0.5rem;
|
||||
.pure-table td {
|
||||
padding: 3px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.watch-table thead tr th .hide-on-desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -52,10 +52,6 @@
|
||||
<td><code>{{ '{{diff_url}}' }}</code></td>
|
||||
<td>{{ _('The URL of the diff output for the watch.') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ '{{diff_url}}' }}</code></td>
|
||||
<td>{{ _('The URL of the diff output for the watch.') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ '{{diff}}' }}</code></td>
|
||||
<td>{{ _('The diff output - only changes, additions, and removals') }}<br>
|
||||
|
||||
@@ -179,15 +179,6 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro playwright_warning() %}
|
||||
<p><strong>{{ _('Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled.') }}</strong> {{ _('Alternatively try our') }} <a href="https://changedetection.io">{{ _('very affordable subscription based service which has all this setup for you') }}</a>.</p>
|
||||
<p>{{ _('You may need to <a href="%(url_pinned)s">Enable playwright environment variable</a> and uncomment the <strong>sockpuppetbrowser</strong> in the <a href="%(url_master)s">docker-compose.yml</a> file.',
|
||||
url_pinned='https://github.com/dgtlmoon/changedetection.io/blob/09ebc6ec6338545bdd694dc6eee57f2e9d2b8075/docker-compose.yml#L31',
|
||||
url_master='https://github.com/dgtlmoon/changedetection.io/blob/master/docker-compose.yml')|safe }}</p>
|
||||
<br>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_time_schedule_form(form, available_timezones, timezone_default_config) %}
|
||||
<style>
|
||||
.day-schedule *, .day-schedule select {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import pytest
|
||||
|
||||
from flask import url_for
|
||||
from zipfile import ZipFile, ZIP_DEFLATED
|
||||
|
||||
from changedetectionio.tests.util import set_modified_response
|
||||
from .util import live_server_setup, wait_for_all_checks, delete_all_watches
|
||||
@@ -823,3 +827,75 @@ def test_unresolvable_hostname_is_allowed(client, live_server, monkeypatch):
|
||||
res = client.get(url_for('watchlist.index'))
|
||||
assert b'this-host-does-not-exist-xyz987.invalid' in res.data, \
|
||||
"Unresolvable hostname watch should appear in the watch overview list"
|
||||
|
||||
|
||||
def test_ghsa_8757_69j2_hx56_backup_restore_history_path_traversal(client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
GHSA-8757-69j2-hx56: Crafted backup ZIP with absolute path in history.txt must not
|
||||
expose arbitrary local files through the preview or API endpoints.
|
||||
|
||||
Attack chain:
|
||||
1. Attacker creates a backup ZIP with a malicious history.txt containing an absolute
|
||||
path (e.g. /etc/passwd) as a snapshot reference.
|
||||
2. Victim restores the backup.
|
||||
3. Attacker reads the targeted file via the Preview page.
|
||||
|
||||
The fix ensures history entries are always resolved to os.path.basename() joined with
|
||||
the watch's data_dir, and rejects entries that escape that directory.
|
||||
"""
|
||||
set_original_response(datastore_path=datastore_path)
|
||||
|
||||
datastore = live_server.app.config['DATASTORE']
|
||||
watch_url = url_for('test_endpoint', _external=True)
|
||||
|
||||
# Create a real watch and trigger a check so we have a valid backup structure
|
||||
uuid = datastore.add_watch(url=watch_url)
|
||||
client.get(url_for("ui.form_watch_checknow"), follow_redirects=True)
|
||||
wait_for_all_checks(client)
|
||||
|
||||
# Download a legitimate backup to use as a template
|
||||
client.get(url_for("backups.request_backup"), follow_redirects=True)
|
||||
time.sleep(4)
|
||||
res = client.get(url_for("backups.download_backup", filename="latest"), follow_redirects=True)
|
||||
assert res.content_type == "application/zip"
|
||||
|
||||
# Tamper: replace the history.txt inside the backup with a malicious entry
|
||||
# that points at /etc/passwd (a file that exists on any Unix system)
|
||||
original_zip = ZipFile(io.BytesIO(res.data))
|
||||
tampered_buf = io.BytesIO()
|
||||
with ZipFile(tampered_buf, 'w', ZIP_DEFLATED) as new_zip:
|
||||
for item in original_zip.infolist():
|
||||
data = original_zip.read(item.filename)
|
||||
# Replace the watch's history.txt with a malicious absolute path entry
|
||||
if item.filename.endswith('history.txt') and uuid in item.filename:
|
||||
data = b'1776969105,/etc/passwd\n'
|
||||
new_zip.writestr(item, data)
|
||||
|
||||
tampered_buf.seek(0)
|
||||
tampered_zip_data = tampered_buf.read()
|
||||
|
||||
# Restore the tampered backup
|
||||
res = client.post(
|
||||
url_for("backups.restore.backups_restore_start"),
|
||||
data={
|
||||
'zip_file': (io.BytesIO(tampered_zip_data), 'malicious_backup.zip'),
|
||||
'include_watches': 'y',
|
||||
'include_watches_replace_existing': 'y',
|
||||
},
|
||||
content_type='multipart/form-data',
|
||||
follow_redirects=True
|
||||
)
|
||||
assert res.status_code == 200
|
||||
time.sleep(2)
|
||||
|
||||
# Now try to read the /etc/passwd contents via the Preview page using the injected timestamp
|
||||
res = client.get(
|
||||
url_for("ui.ui_preview.preview_page", uuid=uuid) + "?timestamp=1776969105",
|
||||
follow_redirects=True
|
||||
)
|
||||
|
||||
# The preview must NOT contain typical /etc/passwd content
|
||||
assert b'root:' not in res.data, \
|
||||
"Preview must not expose /etc/passwd — history path traversal not blocked"
|
||||
assert b'/bin/' not in res.data or b'No history' in res.data or res.status_code in [404, 500], \
|
||||
"Preview must not serve arbitrary local files from a malicious history entry"
|
||||
|
||||
@@ -311,5 +311,72 @@ class TestLLMDiffSummaryCache(unittest.TestCase):
|
||||
assert watch.get_llm_diff_summary('1000', '2000', prompt=self.PROMPT) == 'Updated summary'
|
||||
|
||||
|
||||
class TestHistoryPathTraversal(unittest.TestCase):
|
||||
"""GHSA-8757-69j2-hx56: history.txt must not allow reads outside the watch data dir."""
|
||||
|
||||
def _make_watch(self):
|
||||
mock_datastore = {'settings': {'application': {}}, 'watching': {}}
|
||||
watch = Watch.model(datastore_path='/tmp', __datastore=mock_datastore, default={})
|
||||
watch.ensure_data_dir_exists()
|
||||
return watch
|
||||
|
||||
def _write_history_txt(self, watch, lines):
|
||||
"""Directly write raw lines to history.txt to simulate a restored backup."""
|
||||
fname = os.path.join(watch.data_dir, watch.history_index_filename)
|
||||
with open(fname, 'w', encoding='utf-8') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
def test_absolute_path_in_history_is_rejected(self):
|
||||
"""An absolute path like /etc/passwd must not appear in history."""
|
||||
watch = self._make_watch()
|
||||
self._write_history_txt(watch, ['1000000000,/etc/passwd\n'])
|
||||
history = watch.history
|
||||
self.assertEqual(history, {}, "Absolute path entry must be rejected")
|
||||
|
||||
def test_traversal_path_in_history_is_rejected(self):
|
||||
"""A relative traversal path like ../../etc/passwd must not appear in history."""
|
||||
watch = self._make_watch()
|
||||
self._write_history_txt(watch, ['1000000000,../../etc/passwd\n'])
|
||||
history = watch.history
|
||||
self.assertEqual(history, {}, "Path traversal entry must be rejected")
|
||||
|
||||
def test_normal_snapshot_entry_is_accepted(self):
|
||||
"""A bare filename written by save_history_blob must still load correctly."""
|
||||
import uuid as uuid_builder
|
||||
watch = self._make_watch()
|
||||
watch.save_history_blob(contents="hello world", timestamp=1000000000, snapshot_id=str(uuid_builder.uuid4()))
|
||||
history = watch.history
|
||||
self.assertEqual(len(history), 1, "Normal snapshot entry must be accepted")
|
||||
self.assertTrue(
|
||||
list(history.values())[0].startswith(watch.data_dir),
|
||||
"Resolved path must be inside the watch data directory"
|
||||
)
|
||||
|
||||
def test_get_history_snapshot_blocks_outside_path_directly(self):
|
||||
"""get_history_snapshot(filepath=...) must raise if the path escapes data_dir."""
|
||||
watch = self._make_watch()
|
||||
with self.assertRaises(PermissionError):
|
||||
watch.get_history_snapshot(filepath='/etc/passwd')
|
||||
|
||||
def test_get_history_snapshot_blocks_traversal_directly(self):
|
||||
"""get_history_snapshot(filepath=...) must raise on ../../ traversal paths."""
|
||||
watch = self._make_watch()
|
||||
with self.assertRaises(PermissionError):
|
||||
watch.get_history_snapshot(filepath=os.path.join(watch.data_dir, '../../etc/passwd'))
|
||||
|
||||
def test_resolved_path_stays_inside_data_dir(self):
|
||||
"""All resolved history paths must reside within the watch's data_dir."""
|
||||
import uuid as uuid_builder
|
||||
watch = self._make_watch()
|
||||
for ts in [1000000001, 1000000002, 1000000003]:
|
||||
watch.save_history_blob(contents=f"content {ts}", timestamp=ts, snapshot_id=str(uuid_builder.uuid4()))
|
||||
safe_dir = os.path.realpath(watch.data_dir)
|
||||
for path in watch.history.values():
|
||||
self.assertTrue(
|
||||
os.path.realpath(path).startswith(safe_dir),
|
||||
f"Path {path!r} escapes the watch data directory"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# run from dir above changedetectionio/ dir
|
||||
# python3 -m pytest changedetectionio/tests/unit/test_xml_security.py
|
||||
|
||||
import pytest
|
||||
from changedetectionio import html_tools
|
||||
|
||||
|
||||
def _xxe_payload(file_path: str) -> str:
|
||||
return f"""<?xml version="1.0"?>
|
||||
<!DOCTYPE root [
|
||||
<!ENTITY xxe SYSTEM "file://{file_path}">
|
||||
]>
|
||||
<root><item>&xxe;</item></root>"""
|
||||
|
||||
|
||||
def test_xxe_not_expanded_xpath_filter(tmp_path):
|
||||
"""xpath_filter must not expand external entities (CVE-2026-41895)."""
|
||||
sentinel_file = tmp_path / "sentinel.txt"
|
||||
sentinel = "xxe_sentinel_should_never_appear_in_output"
|
||||
sentinel_file.write_text(sentinel)
|
||||
|
||||
result = html_tools.xpath_filter("//item", _xxe_payload(sentinel_file), is_xml=True)
|
||||
assert sentinel not in result
|
||||
|
||||
|
||||
def test_xxe_not_expanded_xpath1_filter(tmp_path):
|
||||
"""xpath1_filter must not expand external entities (CVE-2026-41895)."""
|
||||
sentinel_file = tmp_path / "sentinel.txt"
|
||||
sentinel = "xxe_sentinel_should_never_appear_in_output"
|
||||
sentinel_file.write_text(sentinel)
|
||||
|
||||
result = html_tools.xpath1_filter("//item", _xxe_payload(sentinel_file), is_xml=True)
|
||||
assert sentinel not in result
|
||||
@@ -1,103 +1,234 @@
|
||||
# Translation Guide
|
||||
# Translators Guide
|
||||
|
||||
## Updating Translations
|
||||
This document is for contributors who write templates (HTML) and for translators who maintain `.po` files.
|
||||
It exists because fragmented `msgid`s — splitting a single sentence across multiple `_()` calls — cause
|
||||
systematic translation breakage across many languages. Follow the patterns here to prevent that.
|
||||
|
||||
To maintain consistency and minimize unnecessary changes in translation files, run these commands:
|
||||
---
|
||||
|
||||
```bash
|
||||
python setup.py extract_messages # Extract translatable strings
|
||||
python setup.py update_catalog # Update all language files
|
||||
python setup.py compile_catalog # Compile to binary .mo files
|
||||
## Terminology
|
||||
|
||||
- **Always use "monitor" or "watcher"** for the concept of watching a URL — never the bare word "watch",
|
||||
which translates to "clock" (e.g. `hodinky` in Czech, `시계` in Korean, `時計` in Japanese).
|
||||
- Use the **shortest suitable wording** for each language. If a language naturally uses the English
|
||||
derivative, prefer that.
|
||||
|
||||
---
|
||||
|
||||
## Template rules: do not fragment `msgid`s
|
||||
|
||||
### Why fragments break translation
|
||||
|
||||
The GNU gettext manual is explicit on this:
|
||||
|
||||
> **[Entire sentences](https://www.gnu.org/software/gettext/manual/html_node/Entire-sentences.html)**:
|
||||
> Translatable strings should be entire sentences. Because gender/number declension depends on other
|
||||
> parts of the sentence, half-sentence *"dumb string concatenation"* breaks in many languages other than English.
|
||||
|
||||
> **[No string concatenation](https://www.gnu.org/software/gettext/manual/html_node/No-string-concatenation.html)**:
|
||||
> Placing adjacent `_()` calls is semantically equivalent to runtime `strcat` concatenation, so the same
|
||||
> guideline applies. The manual also notes that "in some languages the translator might want to swap the
|
||||
> order" of components.
|
||||
|
||||
> **[No embedded URLs](https://www.gnu.org/software/gettext/manual/html_node/No-embedded-URLs.html)**:
|
||||
> URLs should not be written directly inside `msgid`s; they should be injected via `%(name)s` placeholders
|
||||
> and values passed as kwargs.
|
||||
|
||||
> **[No unusual markup](https://www.gnu.org/software/gettext/manual/html_node/No-unusual-markup.html)**:
|
||||
> "HTML markup, however, is common enough that it's probably ok to use in translatable strings."
|
||||
|
||||
Fragments break differently depending on language family:
|
||||
|
||||
| Language family | How fragmentation breaks it |
|
||||
|---|---|
|
||||
| SOV (Japanese, Korean, Turkish) | Verb-final word order can't be achieved when verb and subject are in separate fragments |
|
||||
| Germanic (German) | Gender/case agreement between article and noun is lost across fragment boundaries |
|
||||
| Romance (French, Spanish, Italian, Portuguese) | Adjective placement, subjunctive mood, verb agreement can't be maintained |
|
||||
| Slavic (Czech, Ukrainian) | Case (driven by preposition/verb relationships) is easy to get wrong |
|
||||
| CJK (Chinese, Japanese, Korean) | Modifier position and SVO-vs-topic-prominent differences can't be applied at fragment level |
|
||||
|
||||
A past workaround was redistributing translations across adjacent fragments and using `msgstr " "` (a
|
||||
single space) to suppress unused fragments. This is fragile: as soon as the same short `msgid` is reused
|
||||
in a different template, the redistributed translation is applied verbatim and breaks the new context.
|
||||
|
||||
---
|
||||
|
||||
## The four correct patterns
|
||||
|
||||
### Pattern 1 — Inline HTML embedding
|
||||
|
||||
Keep markup **inside** the `msgid`. Render with `| safe`. This also lets CJK translators decide how to
|
||||
handle `<i>` (see CJK section below).
|
||||
|
||||
```jinja
|
||||
{# BAD: three fragments; CJK translators can't see the <i> at all #}
|
||||
{{ _('Helps reduce changes detected caused by sites shuffling lines around, combine with') }}
|
||||
<i>{{ _('check unique lines') }}</i>
|
||||
{{ _('below.') }}
|
||||
|
||||
{# GOOD: one msgid, rendered with |safe #}
|
||||
{{ _('Helps reduce changes detected caused by sites shuffling lines around, combine with <i>check unique lines</i> below.') | safe }}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
### Pattern 2 — URL as kwarg
|
||||
|
||||
All translation settings are configured in **`../../setup.cfg`** (single source of truth).
|
||||
Pass URLs via `%(name)s` so translators can freely reorder them.
|
||||
|
||||
The configuration below is shown for reference - **edit `setup.cfg` to change settings**:
|
||||
```jinja
|
||||
{# BAD: URL hardcoded between three fragments #}
|
||||
{{ _('Use') }}
|
||||
<a target="newwindow" href="https://github.com/caronc/apprise">{{ _('AppRise Notification URLs') }}</a>
|
||||
{{ _('for notification to just about any service!') }}
|
||||
|
||||
{# GOOD: URL passed as kwarg, <a> embedded in the msgid #}
|
||||
{{ _('Use <a target="newwindow" href="%(url)s">AppRise Notification URLs</a> for notification to just about any service!',
|
||||
url='https://github.com/caronc/apprise') | safe }}
|
||||
```
|
||||
|
||||
### Pattern 3 — Literal `{{}}` escape as kwarg
|
||||
|
||||
Jinja2 would double-interpolate `{{token}}` inside a `_()` call. Pass it as a kwarg instead.
|
||||
|
||||
```jinja
|
||||
{# BAD: literal {{token}} in the middle forces splitting #}
|
||||
{{ _('Accepts the') }} <code>{{ '{{token}}' }}</code> {{ _('placeholders listed below') }}
|
||||
|
||||
{# GOOD: literal passed as kwarg; msgid stays as an entire sentence #}
|
||||
{{ _('Accepts the <code>%(token)s</code> placeholders listed below', token='{{token}}') | safe }}
|
||||
```
|
||||
|
||||
### Pattern 4 — `{% if %}` outside the `msgid`
|
||||
|
||||
Move conditional branches outside `_()` so each branch is a complete sentence, not a fragment.
|
||||
|
||||
```jinja
|
||||
{# BAD: three fragments; SOV languages can't reorder %(title)s relative to "URL or Title" #}
|
||||
{{ _('URL or Title') }}{% if active_tag_uuid %} {{ _('in') }} '{{ active_tag.title }}'{% endif %}
|
||||
|
||||
{# GOOD: branch between two complete msgids; each language can freely reorder %(title)s #}
|
||||
{% if active_tag_uuid %}
|
||||
{{ _("URL or Title in '%(title)s'", title=active_tag.title) }}
|
||||
{% else %}
|
||||
{{ _('URL or Title') }}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CJK italic policy
|
||||
|
||||
CJK fonts typically have no true italic cut — `<i>` falls back to a mechanical slant that reduces
|
||||
legibility. Now that `<i>` is inside `msgid`s, CJK translators can handle it per-locale. Apply this policy
|
||||
for `ja` / `zh` / `zh_Hant_TW`:
|
||||
|
||||
| Context | Action |
|
||||
|---|---|
|
||||
| `<i>` used for general emphasis | Replace with `<strong>`, or drop if the emphasis is self-evident |
|
||||
| `<strong><i>...</i></strong>` | Collapse to `<strong>...</strong>` |
|
||||
| `<i>` wrapping a UI term (e.g. "check unique lines") | Wrap in locale-conventional quotation marks: 「」 for `ja`/`zh_Hant_TW`, `""` for `zh` |
|
||||
|
||||
---
|
||||
|
||||
## Translation workflow
|
||||
|
||||
**Always use these commands** — they read consistent settings from `setup.cfg` and produce minimal diffs:
|
||||
|
||||
```bash
|
||||
python setup.py extract_messages # Extract translatable strings from source
|
||||
python setup.py update_catalog # Propagate new msgids to all .po files
|
||||
python setup.py compile_catalog # Compile .po files to binary .mo files
|
||||
```
|
||||
|
||||
Running `pybabel` directly without the project options causes reordering, rewrapping, and line-number
|
||||
churn that makes diffs hard to review.
|
||||
|
||||
### Configuration
|
||||
|
||||
All translation settings are in `setup.cfg` (single source of truth):
|
||||
|
||||
```ini
|
||||
[extract_messages]
|
||||
# Extract translatable strings from source code
|
||||
mapping_file = babel.cfg
|
||||
output_file = changedetectionio/translations/messages.pot
|
||||
input_paths = changedetectionio
|
||||
keywords = _ _l gettext
|
||||
# Options to reduce unnecessary changes in .pot files
|
||||
sort_by_file = true # Keeps entries ordered by file path
|
||||
width = 120 # Consistent line width (prevents rewrapping)
|
||||
add_location = file # Show file path only (not line numbers)
|
||||
|
||||
[update_catalog]
|
||||
# Update existing .po files with new strings from .pot
|
||||
# Note: 'locale' is omitted - Babel auto-discovers all catalogs in output_dir
|
||||
input_file = changedetectionio/translations/messages.pot
|
||||
output_dir = changedetectionio/translations
|
||||
domain = messages
|
||||
# Options for consistent formatting
|
||||
width = 120 # Consistent line width
|
||||
width = 120
|
||||
no_fuzzy_matching = true # Avoids incorrect automatic matches
|
||||
|
||||
[compile_catalog]
|
||||
# Compile .po files to .mo binary format
|
||||
directory = changedetectionio/translations
|
||||
domain = messages
|
||||
```
|
||||
|
||||
**Key formatting options:**
|
||||
- `sort_by_file = true` - Orders entries by file path (consistent ordering)
|
||||
- `width = 120` - Fixed line width prevents text rewrapping
|
||||
- `add_location = file` - Shows file path only, not line numbers (reduces git churn)
|
||||
- `no_fuzzy_matching = true` - Prevents incorrect automatic fuzzy matches
|
||||
---
|
||||
|
||||
## Why Use These Commands?
|
||||
## Multi-language fix process
|
||||
|
||||
Running pybabel commands directly without consistent options causes:
|
||||
- ❌ Entries get reordered differently each time
|
||||
- ❌ Text gets rewrapped at different widths
|
||||
- ❌ Line numbers change every edit (if not configured)
|
||||
- ❌ Large diffs that make code review difficult
|
||||
When you find a translation error in **any** language, you must check all others for the same `msgid`:
|
||||
|
||||
Using `python setup.py` commands ensures:
|
||||
- ✅ Consistent ordering (by file path, not alphabetically)
|
||||
- ✅ Consistent line width (120 characters, no rewrapping)
|
||||
- ✅ File-only locations (no line number churn)
|
||||
- ✅ No fuzzy matching (prevents incorrect auto-translations)
|
||||
- ✅ Minimal diffs (only actual changes show up)
|
||||
- ✅ Easier code review and git history
|
||||
```bash
|
||||
for lang in cs de en_GB en_US es fr it ja ko pt_BR tr uk zh zh_Hant_TW; do
|
||||
echo "=== $lang ===" && grep -A1 'msgid "YourString"' changedetectionio/translations/$lang/LC_MESSAGES/messages.po
|
||||
done
|
||||
```
|
||||
|
||||
These commands read settings from `../../setup.cfg` automatically.
|
||||
1. Identify every language with the same problem
|
||||
2. Fix all affected `.po` files in the same session
|
||||
3. Recompile: `python setup.py compile_catalog`
|
||||
|
||||
## Supported Languages
|
||||
Never fix one language and move on.
|
||||
|
||||
- `cs` - Czech (Čeština)
|
||||
- `de` - German (Deutsch)
|
||||
- `en_GB` - English (UK)
|
||||
- `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 (繁體中文)
|
||||
---
|
||||
|
||||
## Adding a New Language
|
||||
## Supported languages
|
||||
|
||||
1. Initialize the new language catalog:
|
||||
```bash
|
||||
pybabel init -i changedetectionio/translations/messages.pot -d changedetectionio/translations -l NEW_LANG_CODE
|
||||
```
|
||||
2. Compile it:
|
||||
```bash
|
||||
python setup.py compile_catalog
|
||||
```
|
||||
| Code | Language |
|
||||
|---|---|
|
||||
| `cs` | Czech (Čeština) |
|
||||
| `de` | German (Deutsch) |
|
||||
| `en_GB` | English (UK) |
|
||||
| `en_US` | English (US) |
|
||||
| `es` | Spanish (Español) |
|
||||
| `fr` | French (Français) |
|
||||
| `it` | Italian (Italiano) |
|
||||
| `ja` | Japanese (日本語) |
|
||||
| `ko` | Korean (한국어) |
|
||||
| `pt_BR` | Portuguese (Brasil) |
|
||||
| `tr` | Turkish (Türkçe) |
|
||||
| `uk` | Ukrainian (Українська) |
|
||||
| `zh` | Chinese Simplified (中文简体) |
|
||||
| `zh_Hant_TW` | Chinese Traditional (繁體中文) |
|
||||
|
||||
Babel will auto-discover the new language on subsequent translation updates.
|
||||
## Adding a new language
|
||||
|
||||
## Translation Notes
|
||||
```bash
|
||||
pybabel init -i changedetectionio/translations/messages.pot \
|
||||
-d changedetectionio/translations \
|
||||
-l NEW_LANG_CODE
|
||||
# Reset POT-Creation-Date to the sentinel so it matches the other catalogs
|
||||
sed -i 's|^"POT-Creation-Date: .*\\n"$|"POT-Creation-Date: 1970-01-01 00:00+0000\\n"|' \
|
||||
changedetectionio/translations/NEW_LANG_CODE/LC_MESSAGES/messages.po
|
||||
python setup.py compile_catalog
|
||||
```
|
||||
|
||||
From CLAUDE.md:
|
||||
- Always use "monitor" or "watcher" terminology (not "clock")
|
||||
- Use the most brief wording suitable
|
||||
- When finding issues in one language, check ALL languages for the same issue
|
||||
Babel auto-discovers the new language on subsequent runs.
|
||||
|
||||
---
|
||||
|
||||
## CI linter
|
||||
|
||||
A GitHub Actions job (`lint-template-i18n`) checks for adjacent `{{ _(...) }}` calls on the same line
|
||||
separated only by HTML — the primary symptom of fragmented `msgid`s. It enforces a declining baseline:
|
||||
the count of existing violations may only go down, never up. When you fix a template, lower the
|
||||
`BASELINE_LIMIT` in `.github/workflows/test-only.yml` by the number of lines you fixed.
|
||||
|
||||
See [issue #4074](https://github.com/dgtlmoon/changedetection.io/issues/4074) for full background and
|
||||
[PR #4076](https://github.com/dgtlmoon/changedetection.io/pull/4076) for worked consolidation examples.
|
||||
|
||||
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-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: cs\n"
|
||||
@@ -769,6 +769,12 @@ msgstr "Zpět"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "Vymazat historii snímků"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "Využití"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -785,10 +791,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2215,13 +2217,17 @@ msgid "Checked"
|
||||
msgstr "Zkontrolováno"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "Poslední"
|
||||
msgid "Last Checked"
|
||||
msgstr "Poslední Zkontrolováno"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "Změněno"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "Poslední Změněno"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "Nejsou nakonfigurována žádná sledování webových stránek, do výše uvedeného pole přidejte adresu URL nebo"
|
||||
@@ -2270,18 +2276,10 @@ msgstr "Cena"
|
||||
msgid "No information"
|
||||
msgstr "Žádné informace"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "Probíhá kontrola"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "Ve frontě"
|
||||
@@ -3695,25 +3693,6 @@ msgstr ""
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
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-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-01-14 03:57+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: de\n"
|
||||
@@ -785,6 +785,12 @@ msgstr "Zurück"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "Snapshot-Verlauf löschen"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "Nutzung"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -801,10 +807,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2266,13 +2268,17 @@ msgid "Checked"
|
||||
msgstr "Geprüft"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "Zuletzt"
|
||||
msgid "Last Checked"
|
||||
msgstr "Zuletzt Geprüft"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "Geändert"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "Zuletzt Geändert"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "Es sind keine Website-Überwachungen konfiguriert. Bitte fügen Sie im Feld oben eine URL hinzu, oder"
|
||||
@@ -2321,18 +2327,10 @@ msgstr "Preis"
|
||||
msgid "No information"
|
||||
msgstr "Keine Informationen"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "Jetzt prüfen"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "Wartend"
|
||||
@@ -3749,29 +3747,6 @@ msgstr "Diese Zeile/Regel entfernen"
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr "Überprüfen Sie diese Regel anhand des aktuellen Snapshots."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
"Fehler – Diese Überwachung benötigt Chrome (mit Playwright/Sockpuppetbrowser), aber das Abrufen über Chrome ist nicht"
|
||||
" aktiviert."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr "Alternativ können Sie auch unser"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr "ein sehr günstiges Abonnement-basiertes Angebot, das all diese Einstellungen für Sie übernimmt"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
"Sie müssen möglicherweise <a href=\"%(url_pinned)s\">Aktivieren der Umgebungsvariable „Playwright“</a> und entferne "
|
||||
"den Kommentar von <strong>sockpuppetbrowser</strong> in der <a href=\"%(url_master)s\">docker-compose.yml</a> Datei."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr "Legen Sie einen Stunden-/Wochenplan fest."
|
||||
|
||||
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-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-01-12 16:33+0100\n"
|
||||
"Last-Translator: British English Translation Team\n"
|
||||
"Language: en_GB\n"
|
||||
@@ -767,6 +767,12 @@ msgstr ""
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr ""
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -783,10 +789,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2211,13 +2213,17 @@ msgid "Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr ""
|
||||
@@ -2266,18 +2272,10 @@ msgstr ""
|
||||
msgid "No information"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr ""
|
||||
@@ -3689,25 +3687,6 @@ msgstr ""
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
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-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-01-12 16:37+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en_US\n"
|
||||
@@ -767,6 +767,12 @@ msgstr ""
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr ""
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -783,10 +789,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2211,13 +2213,17 @@ msgid "Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr ""
|
||||
@@ -2266,18 +2272,10 @@ msgstr ""
|
||||
msgid "No information"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr ""
|
||||
@@ -3689,25 +3687,6 @@ msgstr ""
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -3,7 +3,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io 0.53.6\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-03-20 18:13+0100\n"
|
||||
"Last-Translator: Adrian Gonzalez <adrian@example.com>\n"
|
||||
"Language: es\n"
|
||||
@@ -803,6 +803,12 @@ msgstr "Atrás"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "Borrar historial de instantáneas"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "Uso"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -819,10 +825,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2280,13 +2282,17 @@ msgid "Checked"
|
||||
msgstr "Comprobado"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "Último"
|
||||
msgid "Last Checked"
|
||||
msgstr "Último Comprobado"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "Cambiadp"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "Último Cambiadp"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "No hay monitores de detección de cambios de página web configuradas; agregue una URL en el cuadro de arriba, o"
|
||||
@@ -2335,18 +2341,10 @@ msgstr "Precio"
|
||||
msgid "No information"
|
||||
msgstr "Sin información"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "Comprobando ahora"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "En cola"
|
||||
@@ -3764,29 +3762,6 @@ msgstr "Eliminar esta fila/regla"
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr "Verifique esta regla con la instantánea actual"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
"Error: este monitor necesita Chrome (con playwright/sockpuppetbrowser), pero la obtención basada en Chrome no está "
|
||||
"habilitada."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr "Alternativamente prueba nuestro"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr "servicio basado en suscripción muy asequible que tiene toda esta configuración para usted."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
"Es posible que necesites <a href=\"%(url_pinned)s\">Habilitar la variable de entorno de playwright</a> y descomentar "
|
||||
"el <strong>sockpuppetbrowser</strong> en el fichero <a href=\"%(url_master)s\">docker-compose.yml</a>."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr "Establecer un horario por horas/días de la semana"
|
||||
|
||||
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-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-01-02 11:40+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: fr\n"
|
||||
@@ -773,6 +773,12 @@ msgstr "Retour"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "Effacer/réinitialiser l'historique"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "Utilisation"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -789,10 +795,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2222,13 +2224,17 @@ msgid "Checked"
|
||||
msgstr "Vérification"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "Dernier"
|
||||
msgid "Last Checked"
|
||||
msgstr "Dernier Vérification"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "Modifié"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "Dernier Modifié"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "Aucune surveillance de site Web configurée, veuillez ajouter une URL dans la case ci-dessus, ou"
|
||||
@@ -2277,18 +2283,10 @@ msgstr "Prix"
|
||||
msgid "No information"
|
||||
msgstr "Aucune information"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "Vérifier maintenant"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "En file d'attente"
|
||||
@@ -3702,25 +3700,6 @@ msgstr ""
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
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-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-01-02 15:32+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: it\n"
|
||||
@@ -769,6 +769,12 @@ msgstr "Indietro"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "Cancella cronologia snapshot"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "Utilizzo"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -785,10 +791,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2213,13 +2215,17 @@ msgid "Checked"
|
||||
msgstr "Controllo"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "Ultimo"
|
||||
msgid "Last Checked"
|
||||
msgstr "Ultimo Controllo"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "Modifica"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "Ultimo Modifica"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "Nessun monitoraggio configurato, aggiungi un URL nella casella sopra, oppure"
|
||||
@@ -2268,18 +2274,10 @@ msgstr "Prezzo"
|
||||
msgid "No information"
|
||||
msgstr "Nessuna informazione"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "Controllo in corso"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "In coda"
|
||||
@@ -3691,25 +3689,6 @@ msgstr ""
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io 0.53.6\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-03-31 23:52+0900\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: ja\n"
|
||||
@@ -774,6 +774,12 @@ msgstr "戻る"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "スナップショット履歴をクリア"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "使用量"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -790,10 +796,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2230,13 +2232,17 @@ msgid "Checked"
|
||||
msgstr "チェック済み"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "最終"
|
||||
msgid "Last Checked"
|
||||
msgstr "前回チェック"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "変更済み"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "前回更新"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "ウェブページ変更検知ウォッチが設定されていません。上のボックスにURLを追加するか、"
|
||||
@@ -2285,18 +2291,10 @@ msgstr "価格"
|
||||
msgid "No information"
|
||||
msgstr "情報なし"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr "前回チェック"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "今すぐチェック"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "前回更新"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "キュー済み"
|
||||
@@ -3722,27 +3720,6 @@ msgstr "この行/ルールを削除"
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr "現在のスナップショットに対してこのルールを検証"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr "エラー - このウォッチは Chrome(playwright/sockpuppetbrowser付き)が必要ですが、Chromeベースの取得が有効になっていません。"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr "または以下をお試しください:"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr "すべての設定が完了した手頃な価格のサブスクリプションサービス"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
"<a href=\"%(url_pinned)s\">playwright 環境変数の有効化</a>と <a href=\"%(url_master)s\">docker-compose.yml</a> ファイル内の "
|
||||
"<strong>sockpuppetbrowser</strong> のコメント解除が必要な場合があります。"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr "時間/曜日スケジュールを設定"
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,9 @@
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io 0.54.10\n"
|
||||
"Project-Id-Version: changedetection.io 0.55.2\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 2026-04-28 15:22+1000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -766,6 +766,12 @@ msgstr ""
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr ""
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -782,10 +788,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2210,13 +2212,17 @@ msgid "Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr ""
|
||||
@@ -2265,18 +2271,10 @@ msgstr ""
|
||||
msgid "No information"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr ""
|
||||
@@ -3688,25 +3686,6 @@ msgstr ""
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io 0.54.8\n"
|
||||
"Report-Msgid-Bugs-To: mstrey@gmail.com\n"
|
||||
"POT-Creation-Date: 2026-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-04-07 22:00-0300\n"
|
||||
"Last-Translator: Gemini AI\n"
|
||||
"Language: pt_BR\n"
|
||||
@@ -792,6 +792,12 @@ msgstr "Voltar"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "Limpar Histórico de Instantâneos"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "Uso"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -808,10 +814,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2259,13 +2261,17 @@ msgid "Checked"
|
||||
msgstr "Verificado"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "Último"
|
||||
msgid "Last Checked"
|
||||
msgstr "Último Verificado"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "Alterado"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "Último Alterado"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "Nenhum monitoramento configurado, adicione uma URL na caixa acima ou"
|
||||
@@ -2314,18 +2320,10 @@ msgstr "Preço"
|
||||
msgid "No information"
|
||||
msgstr "Sem informações"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "Verificando agora"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "Enfileirado"
|
||||
@@ -3739,29 +3737,6 @@ msgstr "Remover esta linha/regra"
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr "Verificar esta regra contra o instantâneo atual"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
"Erro - Este monitoramento precisa do Chrome (com playwright/sockpuppetbrowser), mas a busca baseada em Chrome não "
|
||||
"está ativada."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr "Alternativamente, tente nosso"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr "serviço por assinatura muito acessível que já tem toda essa configuração pronta para você"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
"Você pode precisar <a href=\"%(url_pinned)s\">Ativar a variável de ambiente do Playwright</a> e descomentar o "
|
||||
"<strong>sockpuppetbrowser</strong> no arquivo <a href=\"%(url_master)s\">docker-compose.yml</a>."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr "Definir um agendamento por hora/dia da semana"
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io 0.53.6\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-04-10 20:38+0300\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: tr\n"
|
||||
@@ -802,6 +802,12 @@ msgstr "Geri"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "Anlık Görüntü Geçmişini Temizle"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "Kullanım"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -818,10 +824,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2264,13 +2266,17 @@ msgid "Checked"
|
||||
msgstr "Kontrol Edildi"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "Son"
|
||||
msgid "Last Checked"
|
||||
msgstr "Son Kontrol Edildi"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "Değiştirildi"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "Son Değiştirildi"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "Yapılandırılmış web sayfası değişiklik tespiti izleyicisi yok, lütfen yukarıdaki kutuya bir URL ekleyin veya"
|
||||
@@ -2319,18 +2325,10 @@ msgstr "Fiyat"
|
||||
msgid "No information"
|
||||
msgstr "Bilgi yok"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "Şimdi kontrol ediliyor"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "Sırada"
|
||||
@@ -3744,29 +3742,6 @@ msgstr "Bu satırı/kuralı kaldır"
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr "Bu kuralı mevcut anlık görüntüye karşı doğrula"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr ""
|
||||
"Hata - Bu izleyicinin Chrome'a (playwright/sockpuppetbrowser ile) ihtiyacı var, ancak Chrome tabanlı getirme etkin "
|
||||
"değil."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr "Alternatif olarak şunu deneyin"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr "sizin için tüm bu kuruluma sahip çok uygun fiyatlı abonelik tabanlı hizmetimiz"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
"<a href=\"%(url_pinned)s\">Playwright ortam değişkenini etkinleştirmeniz</a> ve <a href=\"%(url_master)s\">docker-"
|
||||
"compose.yml</a> dosyası içindeki <strong>sockpuppetbrowser</strong> satırının yorumunu kaldırmanız gerekebilir."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr "Saatlik/hafta içi bir zamanlama ayarla"
|
||||
|
||||
Binary file not shown.
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/dgtlmoon/changedetection.io\n"
|
||||
"POT-Creation-Date: 2026-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-02-19 12:30+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: uk\n"
|
||||
@@ -780,6 +780,12 @@ msgstr "Назад"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "Очистити історію знімків"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "Використання"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -796,10 +802,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2241,13 +2243,17 @@ msgid "Checked"
|
||||
msgstr "Перевірено"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "Останній"
|
||||
msgid "Last Checked"
|
||||
msgstr "Останній Перевірено"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "Змінено"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "Останній Змінено"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "Немає налаштованих завдань для відстеження змін, будь ласка, додайте URL у поле вище або"
|
||||
@@ -2296,18 +2302,10 @@ msgstr "Ціна"
|
||||
msgid "No information"
|
||||
msgstr "Немає інформації"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "Перевірка..."
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "В черзі"
|
||||
@@ -3721,27 +3719,6 @@ msgstr "Видалити цей рядок/правило"
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr "Перевірити це правило на поточному знімку"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr "Помилка - Це завдання потребує Chrome (playwright/sockpuppetbrowser), але завантаження через Chrome не увімкнено."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr "Альтернативно спробуйте наш"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr "дуже доступний сервіс за передплатою, де все це вже налаштовано для вас"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
"Вам може знадобитися <a href=\"%(url_pinned)s\">Увімкнути змінну оточення playwright</a> та розкоментувати "
|
||||
"<strong>sockpuppetbrowser</strong> у файлі <a href=\"%(url_master)s\">docker-compose.yml</a>."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
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-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-01-18 21:31+0800\n"
|
||||
"Last-Translator: 吾爱分享 <admin@wuaishare.cn>\n"
|
||||
"Language: zh\n"
|
||||
@@ -771,6 +771,12 @@ msgstr "返回"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "清除快照历史"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "使用量"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -787,10 +793,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -2216,13 +2218,17 @@ msgid "Checked"
|
||||
msgstr "检查"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "最近"
|
||||
msgid "Last Checked"
|
||||
msgstr "最近检查"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "变更"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "最近变更"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "尚未配置网站监控项,请在上方输入 URL 或"
|
||||
@@ -2271,18 +2277,10 @@ msgstr "价格"
|
||||
msgid "No information"
|
||||
msgstr "暂无信息"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "正在检查"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "队列中"
|
||||
@@ -3695,27 +3693,6 @@ msgstr "移除此行/规则"
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr "使用当前快照验证此规则"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr "错误 - 该监控项需要 Chrome(playwright/sockpuppetbrowser),但未启用基于 Chrome 的抓取。"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr "也可以试试我们的"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr "价格实惠的订阅服务,已为你完成全部配置"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
"您可能需要<a href=\"%(url_pinned)s\">启用 Playwright 环境变量</a>,并在 <a href=\"%(url_master)s\">docker-compose.yml</a> 文件中取消注释 "
|
||||
"<strong>sockpuppetbrowser</strong>。"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
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-04-25 01:52+0900\n"
|
||||
"POT-Creation-Date: 1970-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-01-15 12:00+0800\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: zh_Hant_TW\n"
|
||||
@@ -770,6 +770,12 @@ msgstr "返回"
|
||||
msgid "Clear Snapshot History"
|
||||
msgstr "清除快照歷史記錄"
|
||||
|
||||
#. 'Usage' here means token consumption/cost stats for the AI provider, not a how-to guide
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgctxt "AI usage stats"
|
||||
msgid "Usage"
|
||||
msgstr "使用量"
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
@@ -786,10 +792,6 @@ msgstr ""
|
||||
msgid "Behaviour"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/settings/templates/settings_llm_tab.html
|
||||
msgid "AI-powered change monitoring"
|
||||
msgstr ""
|
||||
@@ -1427,7 +1429,7 @@ msgstr "已將 1 個監測任務排入複查佇列。"
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
msgid "Queued {} watches for rechecking ({} already queued or running)."
|
||||
msgstr "已將 {} 個監測任務排入複查佇列。"
|
||||
msgstr "已將 {} 個監測任務排入複查佇列({} 個已在佇列中或正在執行)。"
|
||||
|
||||
#: changedetectionio/blueprint/ui/__init__.py
|
||||
#, python-brace-format
|
||||
@@ -2215,13 +2217,17 @@ msgid "Checked"
|
||||
msgstr "檢查"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last"
|
||||
msgstr "上次"
|
||||
msgid "Last Checked"
|
||||
msgstr "上次檢查"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Changed"
|
||||
msgstr "變更"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr "上次變更"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "No web page change detection watches configured, please add a URL in the box above, or"
|
||||
msgstr "未設定網站監測任務,請在上方欄位新增 URL,或"
|
||||
@@ -2270,18 +2276,10 @@ msgstr "價格"
|
||||
msgid "No information"
|
||||
msgstr "無資訊"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Checked"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html changedetectionio/templates/base.html
|
||||
msgid "Checking now"
|
||||
msgstr "正在檢查"
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Last Changed"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
|
||||
msgid "Queued"
|
||||
msgstr "已排程"
|
||||
@@ -3693,27 +3691,6 @@ msgstr "移除此行 / 規則"
|
||||
msgid "Verify this rule against current snapshot"
|
||||
msgstr "針對目前快照驗證此規則"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Error - This watch needs Chrome (with playwright/sockpuppetbrowser), but Chrome based fetching is not enabled."
|
||||
msgstr "錯誤 - 此監測任務需要 Chrome(搭配 playwright / sockpuppetbrowser),但未啟用基於 Chrome 的抓取功能。"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Alternatively try our"
|
||||
msgstr "或者嘗試我們"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "very affordable subscription based service which has all this setup for you"
|
||||
msgstr "非常實惠的訂閱服務,為您準備好所有設定"
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You may need to <a href=\"%(url_pinned)s\">Enable playwright environment variable</a> and uncomment the "
|
||||
"<strong>sockpuppetbrowser</strong> in the <a href=\"%(url_master)s\">docker-compose.yml</a> file."
|
||||
msgstr ""
|
||||
"您可能需要 <a href=\"%(url_pinned)s\">啟用 playwright 環境變數</a> 並取消註解 <strong>sockpuppetbrowser</strong> 在 <a "
|
||||
"href=\"%(url_master)s\">docker-compose.yml</a> 檔案."
|
||||
|
||||
#: changedetectionio/templates/_helpers.html
|
||||
msgid "Set a hourly/week day schedule"
|
||||
msgstr "設定每小時 / 平日排程"
|
||||
|
||||
Reference in New Issue
Block a user