Browse Source

feat: implement proxy URL per feed

Frédéric Guillot 1 year ago
parent
commit
ef22e95f8b
48 changed files with 333 additions and 192 deletions
  1. 3 0
      client/model.go
  2. 1 0
      internal/api/subscription.go
  3. 4 0
      internal/database/migrations.go
  4. 0 1
      internal/googlereader/handler.go
  5. 13 10
      internal/locale/translations/de_DE.json
  6. 13 10
      internal/locale/translations/el_EL.json
  7. 5 2
      internal/locale/translations/en_US.json
  8. 13 10
      internal/locale/translations/es_ES.json
  9. 13 10
      internal/locale/translations/fi_FI.json
  10. 13 10
      internal/locale/translations/fr_FR.json
  11. 13 10
      internal/locale/translations/hi_IN.json
  12. 12 9
      internal/locale/translations/id_ID.json
  13. 13 10
      internal/locale/translations/it_IT.json
  14. 12 9
      internal/locale/translations/ja_JP.json
  15. 12 9
      internal/locale/translations/nan_Latn_pehoeji.json
  16. 13 10
      internal/locale/translations/nl_NL.json
  17. 14 11
      internal/locale/translations/pl_PL.json
  18. 13 10
      internal/locale/translations/pt_BR.json
  19. 5 2
      internal/locale/translations/ro_RO.json
  20. 14 11
      internal/locale/translations/ru_RU.json
  21. 13 10
      internal/locale/translations/tr_TR.json
  22. 14 11
      internal/locale/translations/uk_UA.json
  23. 6 3
      internal/locale/translations/zh_CN.json
  24. 12 9
      internal/locale/translations/zh_TW.json
  25. 9 2
      internal/model/feed.go
  26. 1 0
      internal/model/subscription.go
  27. 17 2
      internal/reader/fetcher/request_builder.go
  28. 7 0
      internal/reader/handler/handler.go
  29. 1 0
      internal/reader/icon/checker.go
  30. 0 1
      internal/reader/processor/bilibili.go
  31. 0 1
      internal/reader/processor/nebula.go
  32. 0 1
      internal/reader/processor/odysee.go
  33. 2 0
      internal/reader/processor/processor.go
  34. 0 2
      internal/reader/processor/youtube.go
  35. 8 4
      internal/storage/feed.go
  36. 3 1
      internal/storage/feed_query_builder.go
  37. 3 0
      internal/template/templates/views/add_subscription.html
  38. 1 0
      internal/template/templates/views/choose_subscription.html
  39. 3 0
      internal/template/templates/views/edit_feed.html
  40. 1 0
      internal/ui/feed_edit.go
  41. 1 0
      internal/ui/feed_update.go
  42. 4 0
      internal/ui/form/feed.go
  43. 6 0
      internal/ui/form/subscription.go
  44. 0 1
      internal/ui/opml_upload.go
  45. 1 0
      internal/ui/subscription_choose.go
  46. 3 0
      internal/ui/subscription_submit.go
  47. 14 0
      internal/validator/feed.go
  48. 4 0
      internal/validator/subscription.go

+ 3 - 0
client/model.go

@@ -151,6 +151,7 @@ type Feed struct {
 	Category                    *Category `json:"category,omitempty"`
 	HideGlobally                bool      `json:"hide_globally"`
 	DisableHTTP2                bool      `json:"disable_http2"`
+	ProxyURL                    string    `json:"proxy_url"`
 }
 
 // FeedCreationRequest represents the request to create a feed.
@@ -172,6 +173,7 @@ type FeedCreationRequest struct {
 	KeeplistRules               string `json:"keeplist_rules"`
 	HideGlobally                bool   `json:"hide_globally"`
 	DisableHTTP2                bool   `json:"disable_http2"`
+	ProxyURL                    string `json:"proxy_url"`
 }
 
 // FeedModificationRequest represents the request to update a feed.
@@ -195,6 +197,7 @@ type FeedModificationRequest struct {
 	FetchViaProxy               *bool   `json:"fetch_via_proxy"`
 	HideGlobally                *bool   `json:"hide_globally"`
 	DisableHTTP2                *bool   `json:"disable_http2"`
+	ProxyURL                    *string `json:"proxy_url"`
 }
 
 // FeedIcon represents the feed icon.

+ 1 - 0
internal/api/subscription.go

@@ -38,6 +38,7 @@ func (h *handler) discoverSubscriptions(w http.ResponseWriter, r *http.Request)
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(subscriptionDiscoveryRequest.ProxyURL)
 	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 	requestBuilder.UseCustomApplicationProxyURL(subscriptionDiscoveryRequest.FetchViaProxy)
 	requestBuilder.WithUserAgent(subscriptionDiscoveryRequest.UserAgent, config.Opts.HTTPClientUserAgent())

+ 4 - 0
internal/database/migrations.go

@@ -1062,4 +1062,8 @@ var migrations = []func(tx *sql.Tx, driver string) error{
 
 		return nil
 	},
+	func(tx *sql.Tx, _ string) (err error) {
+		_, err = tx.Exec(`ALTER TABLE feeds ADD COLUMN proxy_url text default ''`)
+		return err
+	},
 }

+ 0 - 1
internal/googlereader/handler.go

@@ -684,7 +684,6 @@ func (h *handler) quickAddHandler(w http.ResponseWriter, r *http.Request) {
 
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
 
 	var rssBridgeURL string

+ 13 - 10
internal/locale/translations/de_DE.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "Lade...",
     "entry.state.saving": "Speichern...",
     "entry.status.mark_as_read": "Als gelesen markieren",
+    "entry.status.mark_as_unread": "Als ungelesen markieren",
     "entry.status.title": "Status des Artikels ändern",
     "entry.status.toast.read": "Als gelesen markiert",
     "entry.status.toast.unread": "Als ungelesen markiert",
-    "entry.status.mark_as_unread": "Als ungelesen markieren",
     "entry.tags.label": "Stichworte:",
     "entry.unshare.label": "Nicht teilen",
     "error.api_key_already_exists": "Dieser API-Schlüssel ist bereits vorhanden.",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "Ungültige Standard-Startseite!",
     "error.invalid_display_mode": "Progressive-Web-App- (PWA-)Anzeigemodus",
     "error.invalid_entry_direction": "Ungültige Sortierreihenfolge.",
+    "error.invalid_feed_proxy_url": "Ungültige Proxy-URL.",
     "error.invalid_feed_url": "Ungültiger Feed-URL.",
     "error.invalid_gesture_nav": "Ungültige Gestennavigation.",
     "error.invalid_language": "Ungültige Sprache.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "Wenigstens 6 Zeichen müssen genutzt werden.",
     "error.pocket_access_token": "Zugriffstoken konnte nicht von Pocket abgerufen werden!",
     "error.pocket_request_token": "Anfrage-Token konnte nicht von Pocket abgerufen werden!",
+    "error.proxy_url_not_empty": "Die Proxy-URL darf nicht leer sein.",
     "error.settings_block_rule_fieldname_invalid": "Ungültige Blockierregel: Regel #%d hat keinen gültigen Feldnamen (Optionen: %s)",
     "error.settings_block_rule_invalid_regex": "Ungültige Blockierregel: Das Muster für Regel #%d ist kein zulässiger regulärer Ausdruck",
     "error.settings_block_rule_regex_required": "Ungültige Blockierregel: Regel #%d hat kein Muster",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "Passwort des Abonnements",
     "form.feed.label.feed_url": "URL des Abonnements",
     "form.feed.label.feed_username": "Benutzername des Abonnements",
-    "form.feed.label.fetch_via_proxy": "Über Proxy abrufen",
+    "form.feed.label.fetch_via_proxy": "Den auf Anwendungsebene konfigurierten Proxy verwenden",
     "form.feed.label.hide_globally": "Einträge in der globalen Ungelesen-Liste ausblenden",
     "form.feed.label.ignore_http_cache": "Ignoriere HTTP-Cache",
     "form.feed.label.keeplist_rules": "Erlaubnisregeln",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Niedrigste Ntfy-Priorität",
     "form.feed.label.ntfy_priority": "Ntfy-Priorität",
     "form.feed.label.ntfy_topic": "Ntfy-Thema (optional)",
+    "form.feed.label.proxy_url": "Proxy-URL",
     "form.feed.label.pushover_activate": "Einträge an pushover.net senden",
     "form.feed.label.pushover_default_priority": "Pushover-Standardpriorität",
     "form.feed.label.pushover_high_priority": "Hohe Pushoverpriorität",
@@ -421,6 +424,10 @@
     "page.api_keys.table.last_used_at": "Zuletzt verwendeten",
     "page.api_keys.table.token": "Zeichen",
     "page.api_keys.title": "API-Schlüssel",
+    "page.categories_count": [
+        "%d Kategorie",
+        "%d Kategorien"
+    ],
     "page.categories.entries": "Artikel",
     "page.categories.feed_count": [
         "Es gibt %d Abonnement.",
@@ -429,10 +436,6 @@
     "page.categories.feeds": "Abonnements",
     "page.categories.no_feed": "Kein Abonnement.",
     "page.categories.title": "Kategorien",
-    "page.categories_count": [
-        "%d Kategorie",
-        "%d Kategorien"
-    ],
     "page.category_label": "Kategorie: %s",
     "page.edit_category.title": "Kategorie bearbeiten: %s",
     "page.edit_feed.etag_header": "ETag-Kopfzeile:",
@@ -538,25 +541,25 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "Hauptschlüssel registrieren",
     "page.settings.webauthn.register.error": "Hauptschlüssel kann nicht registriert werden",
-    "page.shared_entries.title": "Geteilte Artikel",
     "page.shared_entries_count": [
         "%d geteilter Artikel",
         "%d geteilte Artikel"
     ],
-    "page.starred.title": "Lesezeichen",
+    "page.shared_entries.title": "Geteilte Artikel",
     "page.starred_entry_count": [
         "%d Lesezeichen",
         "%d Lesezeichen"
     ],
+    "page.starred.title": "Lesezeichen",
     "page.total_entry_count": [
         "%d Artikel insgesamt",
         "%d Artikel insgesamt"
     ],
-    "page.unread.title": "Ungelesen",
     "page.unread_entry_count": [
         "%d ungelesener Artikel",
         "%d ungelesene Artikel"
     ],
+    "page.unread.title": "Ungelesen",
     "page.users.actions": "Aktionen",
     "page.users.admin.no": "Nein",
     "page.users.admin.yes": "Ja",
@@ -603,4 +606,4 @@
     "time_elapsed.yesterday": "gestern",
     "tooltip.keyboard_shortcuts": "Tastenkürzel: %s",
     "tooltip.logged_user": "Angemeldet als %s"
-}
+}

+ 13 - 10
internal/locale/translations/el_EL.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "Φόρτωση...",
     "entry.state.saving": "Aποθήκευση...",
     "entry.status.mark_as_read": "Επισήμανση ως αναγνωσμένο",
+    "entry.status.mark_as_unread": "Επισήμανση ως μη αναγνωσμένο",
     "entry.status.title": "Αλλαγή κατάστασης καταχώρησης",
     "entry.status.toast.read": "Επισήμανση ως αναγνωσμένο",
     "entry.status.toast.unread": "Επισήμανση ως μη αναγνωσμένο",
-    "entry.status.mark_as_unread": "Επισήμανση ως μη αναγνωσμένο",
     "entry.tags.label": "Ετικέτες:",
     "entry.unshare.label": "Aναίρεση Διαμοιρασμού",
     "error.api_key_already_exists": "Αυτό το κλειδί API υπάρχει ήδη.",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "Μη έγκυρη προεπιλεγμένη αρχική σελίδα!",
     "error.invalid_display_mode": "Μη έγκυρη λειτουργία εμφάνισης εφαρμογών ιστού.",
     "error.invalid_entry_direction": "Μη έγκυρη κατεύθυνση ταξινόμησης άρθρων.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "Μη έγκυρη διεύθυνση URL ροής.",
     "error.invalid_gesture_nav": "Μη έγκυρη πλοήγηση με χειρονομίες.",
     "error.invalid_language": "Μη έγκυρη γλώσσα.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 6 χαρακτήρες.",
     "error.pocket_access_token": "Δεν είναι δυνατή η λήψη του access token από το Pocket!",
     "error.pocket_request_token": "Δεν είναι δυνατή η λήψη του request token από το Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
     "error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
     "error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "Κωδικός Πρόσβασης ροής",
     "form.feed.label.feed_url": "Διεύθυνση URL ροής",
     "form.feed.label.feed_username": "Όνομα Χρήστη ροής",
-    "form.feed.label.fetch_via_proxy": "Λήψη μέσω διακομιστή μεσολάβησης",
+    "form.feed.label.fetch_via_proxy": "Χρησιμοποιήστε τον διακομιστή μεσολάβησης που έχει ρυθμιστεί σε επίπεδο εφαρμογής",
     "form.feed.label.hide_globally": "Απόκρυψη καταχωρήσεων σε γενική λίστα μη αναγνωσμένων",
     "form.feed.label.ignore_http_cache": "Αγνοήστε την προσωρινή μνήμη HTTP",
     "form.feed.label.keeplist_rules": "Κρατήστε Κανόνες",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -421,6 +424,10 @@
     "page.api_keys.table.last_used_at": "Τελευταία Χρήση",
     "page.api_keys.table.token": "Token",
     "page.api_keys.title": "Κλειδιά API",
+    "page.categories_count": [
+        "%d category",
+        "%d categories"
+    ],
     "page.categories.entries": "Άρθρα",
     "page.categories.feed_count": [
         "Υπάρχει μία %d ροή.",
@@ -429,10 +436,6 @@
     "page.categories.feeds": "Συνδρομές",
     "page.categories.no_feed": "Καμία ροή.",
     "page.categories.title": "Κατηγορίες",
-    "page.categories_count": [
-        "%d category",
-        "%d categories"
-    ],
     "page.category_label": "Category: %s",
     "page.edit_category.title": "Επεξεργασία κατηγορίας: % s",
     "page.edit_feed.etag_header": "Κεφαλίδα ETag:",
@@ -538,25 +541,25 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "Εγγραφή κωδικού πρόσβασης",
     "page.settings.webauthn.register.error": "Δεν είναι δυνατή η εγγραφή του κωδικού πρόσβασης",
-    "page.shared_entries.title": "Κοινόχρηστες Καταχωρήσεις",
     "page.shared_entries_count": [
         "%d shared entry",
         "%d shared entries"
     ],
-    "page.starred.title": "Αγαπημένo",
+    "page.shared_entries.title": "Κοινόχρηστες Καταχωρήσεις",
     "page.starred_entry_count": [
         "%d starred entry",
         "%d starred entries"
     ],
+    "page.starred.title": "Αγαπημένo",
     "page.total_entry_count": [
         "%d entry in total",
         "%d entries in total"
     ],
-    "page.unread.title": "Μη αναγνωσμένα",
     "page.unread_entry_count": [
         "%d unread entry",
         "%d unread entries"
     ],
+    "page.unread.title": "Μη αναγνωσμένα",
     "page.users.actions": "Eνέργειες",
     "page.users.admin.no": "Όχι",
     "page.users.admin.yes": "Ναι.",
@@ -603,4 +606,4 @@
     "time_elapsed.yesterday": "χθες",
     "tooltip.keyboard_shortcuts": "Συντόμευση πληκτρολογίου: % s",
     "tooltip.logged_user": "Συνδεδεμένος/η ως %s"
-}
+}

+ 5 - 2
internal/locale/translations/en_US.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "Loading…",
     "entry.state.saving": "Saving…",
     "entry.status.mark_as_read": "Mark as read",
+    "entry.status.mark_as_unread": "Mark as unread",
     "entry.status.title": "Change entry status",
     "entry.status.toast.read": "Marked as read",
     "entry.status.toast.unread": "Marked as unread",
-    "entry.status.mark_as_unread": "Mark as unread",
     "entry.tags.label": "Tags:",
     "entry.unshare.label": "Unshare",
     "error.api_key_already_exists": "This API Key already exists.",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "Invalid default homepage!",
     "error.invalid_display_mode": "Invalid web app display mode.",
     "error.invalid_entry_direction": "Invalid entry direction.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "Invalid feed URL.",
     "error.invalid_gesture_nav": "Invalid gesture navigation.",
     "error.invalid_language": "Invalid language.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "The password must have at least 6 characters.",
     "error.pocket_access_token": "Unable to fetch access token from Pocket!",
     "error.pocket_request_token": "Unable to fetch request token from Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
     "error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
     "error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "Feed Password",
     "form.feed.label.feed_url": "Feed URL",
     "form.feed.label.feed_username": "Feed Username",
-    "form.feed.label.fetch_via_proxy": "Fetch via proxy",
+    "form.feed.label.fetch_via_proxy": "Use the proxy configured at the application level",
     "form.feed.label.hide_globally": "Hide entries in global unread list",
     "form.feed.label.ignore_http_cache": "Ignore HTTP cache",
     "form.feed.label.keeplist_rules": "Keep Rules",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to Pushover",
     "form.feed.label.pushover_default_priority": "Default priority",
     "form.feed.label.pushover_high_priority": "High priority",

+ 13 - 10
internal/locale/translations/es_ES.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "Cargando...",
     "entry.state.saving": "Guardando...",
     "entry.status.mark_as_read": "Marcar como leído",
+    "entry.status.mark_as_unread": "Marcar como no leído",
     "entry.status.title": "Cambiar estado del artículo",
     "entry.status.toast.read": "Marcado como leído",
     "entry.status.toast.unread": "Marcado como no leído",
-    "entry.status.mark_as_unread": "Marcar como no leído",
     "entry.tags.label": "Etiquetas:",
     "entry.unshare.label": "No compartir",
     "error.api_key_already_exists": "Esta clave API ya existe.",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "¡Página de inicio por defecto no válida!",
     "error.invalid_display_mode": "Modo de visualización de la aplicación web no válido.",
     "error.invalid_entry_direction": "Dirección de artículo no válida.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "URL de feed no válida.",
     "error.invalid_gesture_nav": "Navegación por gestos no válida.",
     "error.invalid_language": "Idioma no válido.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "La contraseña debería tener al menos 6 caracteres.",
     "error.pocket_access_token": "Incapaz de obtener un token de acceso de Pocket!",
     "error.pocket_request_token": "Incapaz de obtener un token de solicitud de Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Regla de bloqueo no válida: a la regla #%d le falta un nombre de campo válido (Opciones: %s)",
     "error.settings_block_rule_invalid_regex": "Regla de bloqueo no válida: el patrón de la regla #%d no es una expresión regular válida",
     "error.settings_block_rule_regex_required": "Regla de bloqueo no válida: no se ha proporcionado el patrón de la regla #%d",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "Contraseña de la fuente",
     "form.feed.label.feed_url": "URL de la fuente",
     "form.feed.label.feed_username": "Nombre de usuario de la fuente",
-    "form.feed.label.fetch_via_proxy": "Buscar a través de proxy",
+    "form.feed.label.fetch_via_proxy": "Usar el proxy configurado a nivel de la aplicación",
     "form.feed.label.hide_globally": "Ocultar artículos en la lista global de no leídos",
     "form.feed.label.ignore_http_cache": "Ignorar caché HTTP",
     "form.feed.label.keeplist_rules": "Reglas de Filtrado (Permitir)",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Prioridad mínima a Ntfy",
     "form.feed.label.ntfy_priority": "Prioridad Ntfy",
     "form.feed.label.ntfy_topic": "Tema Ntfy (opcional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -421,6 +424,10 @@
     "page.api_keys.table.last_used_at": "Último utilizado",
     "page.api_keys.table.token": "simbólico",
     "page.api_keys.title": "Claves API",
+    "page.categories_count": [
+        "%d categoría",
+        "%d categorías"
+    ],
     "page.categories.entries": "Artículos",
     "page.categories.feed_count": [
         "Hay %d fuente.",
@@ -429,10 +436,6 @@
     "page.categories.feeds": "Fuentes",
     "page.categories.no_feed": "Sin fuente.",
     "page.categories.title": "Categorías",
-    "page.categories_count": [
-        "%d categoría",
-        "%d categorías"
-    ],
     "page.category_label": "Categoría: %s",
     "page.edit_category.title": "Editar categoría: %s",
     "page.edit_feed.etag_header": "Cabecera de ETag:",
@@ -538,25 +541,25 @@
     "page.settings.webauthn.passkeys": "Claves de acceso",
     "page.settings.webauthn.register": "Registrar clave de acceso",
     "page.settings.webauthn.register.error": "No se puede registrar la clave de acceso",
-    "page.shared_entries.title": "Artículos compartidos",
     "page.shared_entries_count": [
         "%d artículo compartido",
         "%d artículos compartidos"
     ],
-    "page.starred.title": "Marcadores",
+    "page.shared_entries.title": "Artículos compartidos",
     "page.starred_entry_count": [
         "%d artículo marcado",
         "%d artículos marcados"
     ],
+    "page.starred.title": "Marcadores",
     "page.total_entry_count": [
         "%d artículo en total",
         "%d artículos en total"
     ],
-    "page.unread.title": "No leídos",
     "page.unread_entry_count": [
         "%d artículo no leído",
         "%d artículos no leídos"
     ],
+    "page.unread.title": "No leídos",
     "page.users.actions": "Acciones",
     "page.users.admin.no": "No",
     "page.users.admin.yes": "Sí",
@@ -603,4 +606,4 @@
     "time_elapsed.yesterday": "ayer",
     "tooltip.keyboard_shortcuts": "Atajo de teclado: %s",
     "tooltip.logged_user": "Registrado como %s"
-}
+}

+ 13 - 10
internal/locale/translations/fi_FI.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "Ladataan...",
     "entry.state.saving": "Tallennetaan...",
     "entry.status.mark_as_read": "Merkitse luetuksi",
+    "entry.status.mark_as_unread": "Merkitse lukemattomaksi",
     "entry.status.title": "Vaihda artikkelin tilaa",
     "entry.status.toast.read": "Merkitty luetuksi",
     "entry.status.toast.unread": "Merkitty lukemattomaksi",
-    "entry.status.mark_as_unread": "Merkitse lukemattomaksi",
     "entry.tags.label": "Tags:",
     "entry.unshare.label": "Poista jako",
     "error.api_key_already_exists": "API-avain on jo olemassa.",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "Väärä oletusarvoinen kotisivu!",
     "error.invalid_display_mode": "Virheellinen verkkosovelluksen näyttötila.",
     "error.invalid_entry_direction": "Invalid entry direction.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "Virheellinen syötteen URL-osoite.",
     "error.invalid_gesture_nav": "Virheellinen ele-navigointi.",
     "error.invalid_language": "Virheellinen kieli.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "Salasanassa on oltava vähintään 6 merkkiä.",
     "error.pocket_access_token": "Unable to fetch access token from Pocket!",
     "error.pocket_request_token": "Unable to fetch request token from Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
     "error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
     "error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "Syötteen salasana",
     "form.feed.label.feed_url": "Syötteen URL-osoite",
     "form.feed.label.feed_username": "Syötteen käyttäjätunnus",
-    "form.feed.label.fetch_via_proxy": "Nouda välityspalvelimen kautta",
+    "form.feed.label.fetch_via_proxy": "Käytä sovellustasolla määritettyä välityspalvelinta",
     "form.feed.label.hide_globally": "Piilota artikkelit lukemattomien listassa",
     "form.feed.label.ignore_http_cache": "Ohita HTTP-välimuisti",
     "form.feed.label.keeplist_rules": "Keep-säännöt",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -421,6 +424,10 @@
     "page.api_keys.table.last_used_at": "Viimeksi käytetty",
     "page.api_keys.table.token": "Tunnus",
     "page.api_keys.title": "API-avaimet",
+    "page.categories_count": [
+        "%d category",
+        "%d categories"
+    ],
     "page.categories.entries": "Artikkelit",
     "page.categories.feed_count": [
         "On %d syöte.",
@@ -429,10 +436,6 @@
     "page.categories.feeds": "Tilaukset",
     "page.categories.no_feed": "Ei syötettä.",
     "page.categories.title": "Kategoriat",
-    "page.categories_count": [
-        "%d category",
-        "%d categories"
-    ],
     "page.category_label": "Category: %s",
     "page.edit_category.title": "Muokkaa kategoria: %s",
     "page.edit_feed.etag_header": "ETag-otsikko:",
@@ -538,25 +541,25 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "Rekisteröi salasana",
     "page.settings.webauthn.register.error": "Salasanaa ei voi rekisteröidä",
-    "page.shared_entries.title": "Jaetut artikkelit",
     "page.shared_entries_count": [
         "%d shared entry",
         "%d shared entries"
     ],
-    "page.starred.title": "Suosikit",
+    "page.shared_entries.title": "Jaetut artikkelit",
     "page.starred_entry_count": [
         "%d starred entry",
         "%d starred entries"
     ],
+    "page.starred.title": "Suosikit",
     "page.total_entry_count": [
         "%d entry in total",
         "%d entries in total"
     ],
-    "page.unread.title": "Lukemattomat",
     "page.unread_entry_count": [
         "%d unread entry",
         "%d unread entries"
     ],
+    "page.unread.title": "Lukemattomat",
     "page.users.actions": "Toiminnot",
     "page.users.admin.no": "Ei",
     "page.users.admin.yes": "Kyllä",
@@ -603,4 +606,4 @@
     "time_elapsed.yesterday": "eilen",
     "tooltip.keyboard_shortcuts": "Pikanäppäin: %s",
     "tooltip.logged_user": "Kirjautunut %s-käyttäjänä"
-}
+}

+ 13 - 10
internal/locale/translations/fr_FR.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "Chargement...",
     "entry.state.saving": "Sauvegarde en cours...",
     "entry.status.mark_as_read": "Marquer comme lu",
+    "entry.status.mark_as_unread": "Marquer comme non lu",
     "entry.status.title": "Changer le statut de l'entrée",
     "entry.status.toast.read": "Marqué comme lu",
     "entry.status.toast.unread": "Marqué comme non lu",
-    "entry.status.mark_as_unread": "Marquer comme non lu",
     "entry.tags.label": "Libellés :",
     "entry.unshare.label": "Enlever le partage",
     "error.api_key_already_exists": "Cette clé d'API existe déjà.",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "Page d'accueil par défaut invalide !",
     "error.invalid_display_mode": "Mode d'affichage de l'application web non valide.",
     "error.invalid_entry_direction": "Ordre de trie non valide.",
+    "error.invalid_feed_proxy_url": "L'URL du proxy n'est pas valide.",
     "error.invalid_feed_url": "URL de flux non valide.",
     "error.invalid_gesture_nav": "Navigation gestuelle non valide.",
     "error.invalid_language": "Langue non valide.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "Vous devez utiliser au moins 6 caractères pour le mot de passe.",
     "error.pocket_access_token": "Impossible de récupérer le jeton d'accès depuis Pocket !",
     "error.pocket_request_token": "Impossible de récupérer le jeton d'accès depuis Pocket !",
+    "error.proxy_url_not_empty": "L'URL du proxy ne peut pas être vide.",
     "error.settings_block_rule_fieldname_invalid": "Règle de blocage invalide : la règle n°%d ne contient pas un nom de champ valide (Options : %s)",
     "error.settings_block_rule_invalid_regex": "Règle de blocage invalide : le motif de la règle n°%d n'est pas une expression régulière valide",
     "error.settings_block_rule_regex_required": "Règle de blocage invalide : le motif de la règle n°%d n'est pas fourni",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "Mot de passe du flux",
     "form.feed.label.feed_url": "URL du flux",
     "form.feed.label.feed_username": "Nom d'utilisateur du flux",
-    "form.feed.label.fetch_via_proxy": "Récupérer via proxy",
+    "form.feed.label.fetch_via_proxy": "Utiliser le proxy configuré au niveau de l'application",
     "form.feed.label.hide_globally": "Masquer les entrées dans la liste globale non lue",
     "form.feed.label.ignore_http_cache": "Ignorer le cache HTTP",
     "form.feed.label.keeplist_rules": "Règles d'autorisation",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Priorité minimale de notification",
     "form.feed.label.ntfy_priority": "Priorité de notification",
     "form.feed.label.ntfy_topic": "Sujet Ntfy (facultatif)",
+    "form.feed.label.proxy_url": "URL du proxy",
     "form.feed.label.pushover_activate": "Activer les notifications vers Pushover",
     "form.feed.label.pushover_default_priority": "Priorité par défaut",
     "form.feed.label.pushover_high_priority": "Priorité élevée",
@@ -421,6 +424,10 @@
     "page.api_keys.table.last_used_at": "Dernière utilisation",
     "page.api_keys.table.token": "Jeton",
     "page.api_keys.title": "Clés d'API",
+    "page.categories_count": [
+        "%d catégorie",
+        "%d catégories"
+    ],
     "page.categories.entries": "Articles",
     "page.categories.feed_count": [
         "Il y a %d abonnement.",
@@ -429,10 +436,6 @@
     "page.categories.feeds": "Abonnements",
     "page.categories.no_feed": "Aucun abonnement.",
     "page.categories.title": "Catégories",
-    "page.categories_count": [
-        "%d catégorie",
-        "%d catégories"
-    ],
     "page.category_label": "Catégorie : %s",
     "page.edit_category.title": "Modification de la catégorie : %s",
     "page.edit_feed.etag_header": "En-tête ETag :",
@@ -538,25 +541,25 @@
     "page.settings.webauthn.passkeys": "Clés d’accès",
     "page.settings.webauthn.register": "Enregister une nouvelle clé d’accès",
     "page.settings.webauthn.register.error": "Impossible d'enregistrer la clé d’accès",
-    "page.shared_entries.title": "Articles partagés",
     "page.shared_entries_count": [
         "%d article partagé",
         "%d articles partagés"
     ],
-    "page.starred.title": "Favoris",
+    "page.shared_entries.title": "Articles partagés",
     "page.starred_entry_count": [
         "%d favori",
         "%d favoris"
     ],
+    "page.starred.title": "Favoris",
     "page.total_entry_count": [
         "%d article au total",
         "%d articles au total"
     ],
-    "page.unread.title": "Non lus",
     "page.unread_entry_count": [
         "%d article non lu",
         "%d articles non lus"
     ],
+    "page.unread.title": "Non lus",
     "page.users.actions": "Actions",
     "page.users.admin.no": "Non",
     "page.users.admin.yes": "Oui",
@@ -603,4 +606,4 @@
     "time_elapsed.yesterday": "hier",
     "tooltip.keyboard_shortcuts": "Raccourci clavier : %s",
     "tooltip.logged_user": "Connecté en tant que %s"
-}
+}

+ 13 - 10
internal/locale/translations/hi_IN.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "लोड हो रहा है...",
     "entry.state.saving": "सहेजा जा रहा है...",
     "entry.status.mark_as_read": "पढ़े हुए का चिह्न",
+    "entry.status.mark_as_unread": "अपठित के रूप में चिह्नित करें",
     "entry.status.title": "प्रविष्टि स्थिति बदलें",
     "entry.status.toast.read": "पढ़ा हुआ चिह्नित करे",
     "entry.status.toast.unread": "अपठित के रूप में चिह्नित",
-    "entry.status.mark_as_unread": "अपठित के रूप में चिह्नित करें",
     "entry.tags.label": "टैग:",
     "entry.unshare.label": "न साझा कारें",
     "error.api_key_already_exists": "यह एपीआई कुंजी पहले से मौजूद है।",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "अमान्य डिफ़ॉल्ट मुखपृष्ठ!",
     "error.invalid_display_mode": "अमान्य वेब ऐप्लिकेशन प्रदर्शन मोड.",
     "error.invalid_entry_direction": "अमान्य प्रवेश दिशा।",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "दृष्टिकोण यूआरएल.",
     "error.invalid_gesture_nav": "अमान्य इशारा नेविगेशन।",
     "error.invalid_language": "अमान्य भाषा.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "पासवर्ड में कम से कम 6 अक्षर होने चाहिए।",
     "error.pocket_access_token": "पॉकेट से एक्सेस टोकन प्राप्त करने में असमर्थ!",
     "error.pocket_request_token": "पॉकेट से अनुरोध टोकन लाने में असमर्थ!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
     "error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
     "error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "फ़ीड पासवर्ड",
     "form.feed.label.feed_url": "फ़ीड यूआरएल",
     "form.feed.label.feed_username": "फ़ीड उपयोगकर्ता नाम",
-    "form.feed.label.fetch_via_proxy": "प्रॉक्सी के माध्यम से प्राप्त करें",
+    "form.feed.label.fetch_via_proxy": "एप्लिकेशन स्तर पर कॉन्फ़िगर किए गए प्रॉक्सी का उपयोग करें",
     "form.feed.label.hide_globally": "वैश्विक अपठित सूची में प्रविष्टियां छिपाएं",
     "form.feed.label.ignore_http_cache": "एचटीटीपी कैश पर ध्यान न दें",
     "form.feed.label.keeplist_rules": "नियम बनाए रखें",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -421,6 +424,10 @@
     "page.api_keys.table.last_used_at": "आखरी इस्त्तमाल किया गया",
     "page.api_keys.table.token": "टोकन",
     "page.api_keys.title": "एपीआई कुंजी",
+    "page.categories_count": [
+        "%d category",
+        "%d categories"
+    ],
     "page.categories.entries": "विषयवस्तुया",
     "page.categories.feed_count": [
         "%d फ़ीड बाकी है।",
@@ -429,10 +436,6 @@
     "page.categories.feeds": "सदस्यता ले",
     "page.categories.no_feed": "कोई फ़ीड नहीं है।",
     "page.categories.title": "श्रेणियाँ",
-    "page.categories_count": [
-        "%d category",
-        "%d categories"
-    ],
     "page.category_label": "Category: %s",
     "page.edit_category.title": "%s श्रेणी संपाद करे",
     "page.edit_feed.etag_header": "ईटाग हैडर:",
@@ -538,25 +541,25 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "रजिस्टर पासकी",
     "page.settings.webauthn.register.error": "पासकी पंजीकृत करने में असमर्थ",
-    "page.shared_entries.title": "साझा किया हुआ प्रविष्टि",
     "page.shared_entries_count": [
         "%d shared entry",
         "%d shared entries"
     ],
-    "page.starred.title": "तारांकित",
+    "page.shared_entries.title": "साझा किया हुआ प्रविष्टि",
     "page.starred_entry_count": [
         "%d starred entry",
         "%d starred entries"
     ],
+    "page.starred.title": "तारांकित",
     "page.total_entry_count": [
         "%d entry in total",
         "%d entries in total"
     ],
-    "page.unread.title": "अपठित",
     "page.unread_entry_count": [
         "%d unread entry",
         "%d unread entries"
     ],
+    "page.unread.title": "अपठित",
     "page.users.actions": "कार्रवाई",
     "page.users.admin.no": "नहीं",
     "page.users.admin.yes": "हां",
@@ -603,4 +606,4 @@
     "time_elapsed.yesterday": "कल",
     "tooltip.keyboard_shortcuts": "कुंजीपटल संक्षिप्त रीति: %s",
     "tooltip.logged_user": "%s के रूप में लॉग इन किया"
-}
+}

+ 12 - 9
internal/locale/translations/id_ID.json

@@ -70,10 +70,10 @@
     "entry.state.loading": "Memuat...",
     "entry.state.saving": "Menyimpan...",
     "entry.status.mark_as_read": "Telah dibaca",
+    "entry.status.mark_as_unread": "Belum dibaca",
     "entry.status.title": "Ubah status entri",
     "entry.status.toast.read": "Ditandai sebagai telah dibaca",
     "entry.status.toast.unread": "Ditandai sebagai belum dibaca",
-    "entry.status.mark_as_unread": "Belum dibaca",
     "entry.tags.label": "Tanda:",
     "entry.unshare.label": "Batal bagikan",
     "error.api_key_already_exists": "Kunci API ini sudah ada.",
@@ -115,6 +115,7 @@
     "error.invalid_default_home_page": "Beranda baku tidak valid!",
     "error.invalid_display_mode": "Mode tampilan aplikasi web tidak valid.",
     "error.invalid_entry_direction": "Urutan entri tidak valid.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "URL umpan tidak valid.",
     "error.invalid_gesture_nav": "Navigasi gestur tidak valid.",
     "error.invalid_language": "Bahasa tidak valid.",
@@ -126,6 +127,7 @@
     "error.password_min_length": "Kata sandi harus memiliki setidaknya 6 karakter.",
     "error.pocket_access_token": "Tidak bisa mendapatkan token akses dari Pocket!",
     "error.pocket_request_token": "Tidak bisa mendapatkan token permintaan dari Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
     "error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
     "error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
@@ -172,7 +174,7 @@
     "form.feed.label.feed_password": "Kata Sandi Umpan",
     "form.feed.label.feed_url": "URL Umpan",
     "form.feed.label.feed_username": "Nama Pengguna Umpan",
-    "form.feed.label.fetch_via_proxy": "Ambil via Proksi",
+    "form.feed.label.fetch_via_proxy": "Gunakan proxy yang dikonfigurasi di tingkat aplikasi",
     "form.feed.label.hide_globally": "Sembunyikan entri di daftar belum dibaca global",
     "form.feed.label.ignore_http_cache": "Abaikan Tembolok HTTP",
     "form.feed.label.keeplist_rules": "Aturan Simpan",
@@ -185,6 +187,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -419,6 +422,9 @@
     "page.api_keys.table.last_used_at": "Terakhir Digunakan",
     "page.api_keys.table.token": "Token",
     "page.api_keys.title": "Kunci API",
+    "page.categories_count": [
+        "%d category"
+    ],
     "page.categories.entries": "Artikel",
     "page.categories.feed_count": [
         "Ada %d umpan."
@@ -426,9 +432,6 @@
     "page.categories.feeds": "Langganan",
     "page.categories.no_feed": "Tidak ada umpan.",
     "page.categories.title": "Kategori",
-    "page.categories_count": [
-        "%d category"
-    ],
     "page.category_label": "Category: %s",
     "page.edit_category.title": "Sunting Kategori: %s",
     "page.edit_feed.etag_header": "Tajuk ETag:",
@@ -531,21 +534,21 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "Register passkey",
     "page.settings.webauthn.register.error": "Unable to register passkey",
-    "page.shared_entries.title": "Entri yang Dibagikan",
     "page.shared_entries_count": [
         "%d shared entry"
     ],
-    "page.starred.title": "Markah",
+    "page.shared_entries.title": "Entri yang Dibagikan",
     "page.starred_entry_count": [
         "%d starred entry"
     ],
+    "page.starred.title": "Markah",
     "page.total_entry_count": [
         "%d entry in total"
     ],
-    "page.unread.title": "Belum Dibaca",
     "page.unread_entry_count": [
         "%d unread entry"
     ],
+    "page.unread.title": "Belum Dibaca",
     "page.users.actions": "Tindakan",
     "page.users.admin.no": "Tidak",
     "page.users.admin.yes": "Ya",
@@ -586,4 +589,4 @@
     "time_elapsed.yesterday": "kemarin",
     "tooltip.keyboard_shortcuts": "Pintasan Papan Tik: %s",
     "tooltip.logged_user": "Masuk sebagai %s"
-}
+}

+ 13 - 10
internal/locale/translations/it_IT.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "Caricamento in corso...",
     "entry.state.saving": "Salvataggio in corso...",
     "entry.status.mark_as_read": "Segna come letto",
+    "entry.status.mark_as_unread": "Segna come non letto",
     "entry.status.title": "Cambia lo stato dell'articolo",
     "entry.status.toast.read": "Contrassegnato come letto",
     "entry.status.toast.unread": "Contrassegnato come non letto",
-    "entry.status.mark_as_unread": "Segna come non letto",
     "entry.tags.label": "Tag:",
     "entry.unshare.label": "Unshare",
     "error.api_key_already_exists": "Questa chiave API esiste già.",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "Pagina iniziale predefinita non valida!",
     "error.invalid_display_mode": "Modalità di visualizzazione web app non valida.",
     "error.invalid_entry_direction": "Ordinamento non valido.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "URL del feed non valido.",
     "error.invalid_gesture_nav": "Navigazione gestuale non valida.",
     "error.invalid_language": "Lingua non valida.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "La password deve contenere almeno 6 caratteri.",
     "error.pocket_access_token": "Non sono riuscito ad ottenere l'access token da Pocket!",
     "error.pocket_request_token": "Non sono riuscito ad ottenere il request token da Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
     "error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
     "error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "Password del feed",
     "form.feed.label.feed_url": "URL del feed",
     "form.feed.label.feed_username": "Nome utente del feed",
-    "form.feed.label.fetch_via_proxy": "Recuperare tramite proxy",
+    "form.feed.label.fetch_via_proxy": "Usa il proxy configurato a livello di applicazione",
     "form.feed.label.hide_globally": "Nascondere le voci nella lista globale dei non letti",
     "form.feed.label.ignore_http_cache": "Ignora cache HTTP",
     "form.feed.label.keeplist_rules": "Regole di autorizzazione",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -421,6 +424,10 @@
     "page.api_keys.table.last_used_at": "Ultimo uso",
     "page.api_keys.table.token": "Gettone",
     "page.api_keys.title": "Chiavi API",
+    "page.categories_count": [
+        "%d category",
+        "%d categories"
+    ],
     "page.categories.entries": "Articoli",
     "page.categories.feed_count": [
         "C'è %d feed.",
@@ -429,10 +436,6 @@
     "page.categories.feeds": "Abbonamenti",
     "page.categories.no_feed": "Nessun feed.",
     "page.categories.title": "Categorie",
-    "page.categories_count": [
-        "%d category",
-        "%d categories"
-    ],
     "page.category_label": "Category: %s",
     "page.edit_category.title": "Modifica categoria: %s",
     "page.edit_feed.etag_header": "Header ETag:",
@@ -538,25 +541,25 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "Registra la chiave di accesso",
     "page.settings.webauthn.register.error": "Impossibile registrare la passkey",
-    "page.shared_entries.title": "Voci condivise",
     "page.shared_entries_count": [
         "%d shared entry",
         "%d shared entries"
     ],
-    "page.starred.title": "Preferiti",
+    "page.shared_entries.title": "Voci condivise",
     "page.starred_entry_count": [
         "%d starred entry",
         "%d starred entries"
     ],
+    "page.starred.title": "Preferiti",
     "page.total_entry_count": [
         "%d entry in total",
         "%d entries in total"
     ],
-    "page.unread.title": "Da leggere",
     "page.unread_entry_count": [
         "%d unread entry",
         "%d unread entries"
     ],
+    "page.unread.title": "Da leggere",
     "page.users.actions": "Azioni",
     "page.users.admin.no": "No",
     "page.users.admin.yes": "Sì",
@@ -603,4 +606,4 @@
     "time_elapsed.yesterday": "ieri",
     "tooltip.keyboard_shortcuts": "Scorciatoia da tastiera: %s",
     "tooltip.logged_user": "Autenticato come %s"
-}
+}

+ 12 - 9
internal/locale/translations/ja_JP.json

@@ -70,10 +70,10 @@
     "entry.state.loading": "読み込み中…",
     "entry.state.saving": "保存中…",
     "entry.status.mark_as_read": "既読にする",
+    "entry.status.mark_as_unread": "未読に戻す",
     "entry.status.title": "記事の状態を変更",
     "entry.status.toast.read": "既読にしました",
     "entry.status.toast.unread": "未読にしました",
-    "entry.status.mark_as_unread": "未読に戻す",
     "entry.tags.label": "タグ:",
     "entry.unshare.label": "共有を解除",
     "error.api_key_already_exists": "この API キーは既に存在します。",
@@ -115,6 +115,7 @@
     "error.invalid_default_home_page": "デフォルトのトップページが無効です",
     "error.invalid_display_mode": "Web アプリの表示モードが無効です。",
     "error.invalid_entry_direction": "記事の表示順が無効です。",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "フィード URL が無効です。",
     "error.invalid_gesture_nav": "ジェスチャー ナビゲーションが無効です。",
     "error.invalid_language": "言語が無効です。",
@@ -126,6 +127,7 @@
     "error.password_min_length": "パスワードは6文字以上である必要があります。",
     "error.pocket_access_token": "Pocket の access token が取得できません!",
     "error.pocket_request_token": "Pocket の request token が取得できません!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
     "error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
     "error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
@@ -172,7 +174,7 @@
     "form.feed.label.feed_password": "フィードのパスワード",
     "form.feed.label.feed_url": "フィード URL",
     "form.feed.label.feed_username": "フィードのユーザー名",
-    "form.feed.label.fetch_via_proxy": "プロキシ経由で取得",
+    "form.feed.label.fetch_via_proxy": "アプリケーションレベルで設定されたプロキシを使用する",
     "form.feed.label.hide_globally": "未読一覧に記事を表示しない",
     "form.feed.label.ignore_http_cache": "HTTPキャッシュを無視",
     "form.feed.label.keeplist_rules": "Keep ルール",
@@ -185,6 +187,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -419,6 +422,9 @@
     "page.api_keys.table.last_used_at": "最終使用",
     "page.api_keys.table.token": "トークン",
     "page.api_keys.title": "API キー",
+    "page.categories_count": [
+        "%d category"
+    ],
     "page.categories.entries": "記事一覧",
     "page.categories.feed_count": [
         "%d 件のフィードがあります。"
@@ -426,9 +432,6 @@
     "page.categories.feeds": "フィード一覧",
     "page.categories.no_feed": "フィードはありません。",
     "page.categories.title": "カテゴリ",
-    "page.categories_count": [
-        "%d category"
-    ],
     "page.category_label": "Category: %s",
     "page.edit_category.title": "カテゴリを編集: %s",
     "page.edit_feed.etag_header": "ETag ヘッダー:",
@@ -531,21 +534,21 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "パスキーを登録する",
     "page.settings.webauthn.register.error": "パスキーを登録できません",
-    "page.shared_entries.title": "共有エントリ",
     "page.shared_entries_count": [
         "%d shared entry"
     ],
-    "page.starred.title": "星付き",
+    "page.shared_entries.title": "共有エントリ",
     "page.starred_entry_count": [
         "%d starred entry"
     ],
+    "page.starred.title": "星付き",
     "page.total_entry_count": [
         "%d entry in total"
     ],
-    "page.unread.title": "未読",
     "page.unread_entry_count": [
         "%d unread entry"
     ],
+    "page.unread.title": "未読",
     "page.users.actions": "アクション",
     "page.users.admin.no": "非管理者",
     "page.users.admin.yes": "管理者",
@@ -586,4 +589,4 @@
     "time_elapsed.yesterday": "昨日",
     "tooltip.keyboard_shortcuts": "キーボードショートカット: %s",
     "tooltip.logged_user": "%s としてログイン中"
-}
+}

+ 12 - 9
internal/locale/translations/nan_Latn_pehoeji.json

@@ -70,10 +70,10 @@
     "entry.state.loading": "Tng leh chip-hêng…",
     "entry.state.saving": "Tng leh pó-chûn…",
     "entry.status.mark_as_read": "Chù chòe tha̍k kè",
+    "entry.status.mark_as_unread": "Chù chòe ah-bōe tha̍k",
     "entry.status.title": "Kái chōng-thài",
     "entry.status.toast.read": "Chù chòe tha̍k kè chòe soah",
     "entry.status.toast.unread": "Chù chòe ah-bōe tha̍k chòe soah",
-    "entry.status.mark_as_unread": "Chù chòe ah-bōe tha̍k",
     "entry.tags.label": "Khan-á:",
     "entry.unshare.label": "Chhú-siau hun-hióng",
     "error.api_key_already_exists": "Chit ê API só-sî í-keng chûn-chāi",
@@ -115,6 +115,7 @@
     "error.invalid_default_home_page": "Ū-siat chú-ia̍h ū būn-tôe!",
     "error.invalid_display_mode": "Ū būn-tôe ê su-li̍p bô͘-sek.",
     "error.invalid_entry_direction": "Ū būn-tôe ê su-li̍p hong-hiòng.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "Beh tēng ê siau-sit lâi-goân ê bāng-chí ū būn-tôe.",
     "error.invalid_gesture_nav": "Chhiú-sè tō-lám ū būn-tôe.",
     "error.invalid_language": "Ū būn-tôe ê gú-giân.",
@@ -126,6 +127,7 @@
     "error.password_min_length": "Chhiáⁿ chì-chió ài su-li̍p la̍k ê lī goân.",
     "error.pocket_access_token": "Bô-hoat-tō͘ ùi Pocket thê tio̍h access token",
     "error.pocket_request_token": "Bô-hoat-tō͘ ùi Pocket thê tio̍h request token",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Bô-hāu ê hong-só kui-chek: kui-chek #%d khiàm ū-hāu ê lân-ūi miâ (e-sai ê soán-hāng: %s)",
     "error.settings_block_rule_invalid_regex": "Bô-hāu ê hong-só kui-chek: kui-chek #%d ê bô͘-sek m̄ sī ha̍p-hoat ê chiàⁿ-kui piáu-ta̍t sek",
     "error.settings_block_rule_regex_required": "Bô-hāu ê hong-só kui-chek: kui-chek #%d bô thê-kiong chiàⁿ-kui piáu-ta̍t sek",
@@ -172,7 +174,7 @@
     "form.feed.label.feed_password": "Siau-sit lâi-goân bi̍t-bé",
     "form.feed.label.feed_url": "Siau-sit lâi-goân bāng-chí",
     "form.feed.label.feed_username": "Siau-sit lâi-goân kháu-chō miâ",
-    "form.feed.label.fetch_via_proxy": "Thàu-kè tāi-lí lia̍h",
+    "form.feed.label.fetch_via_proxy": "Iōng tī su-hāu-khì siat-tēng ê proxy",
     "form.feed.label.hide_globally": "Tī choân-he̍k ah-bōe tha̍k--ê lia̍t-pió am-khàm siau-sit",
     "form.feed.label.ignore_http_cache": "Pàng-ba̍k HTTP cache",
     "form.feed.label.keeplist_rules": "Pó-liû kui-chek",
@@ -185,6 +187,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy siōng kē iu-sian sūn-sū",
     "form.feed.label.ntfy_priority": "Ntfy iu-sian sūn-sū",
     "form.feed.label.ntfy_topic": "Ntfy topic (soán thiⁿ)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to Pushover",
     "form.feed.label.pushover_default_priority": "Default priority",
     "form.feed.label.pushover_high_priority": "High priority",
@@ -419,6 +422,9 @@
     "page.api_keys.table.last_used_at": "Siōng-bóe pái sú-iōng",
     "page.api_keys.table.token": "Só-sî",
     "page.api_keys.title": "API só-sî",
+    "page.categories_count": [
+        "%d ê lūi-pia̍t"
+    ],
     "page.categories.entries": "Siau-sit",
     "page.categories.feed_count": [
         "Ū %d ê Siau-sit lâi-goân"
@@ -426,9 +432,6 @@
     "page.categories.feeds": "Siau-sit lâi-goân",
     "page.categories.no_feed": "Ah-bô siau-sit lâi-goân",
     "page.categories.title": "Lūi-pia̍t",
-    "page.categories_count": [
-        "%d ê lūi-pia̍t"
-    ],
     "page.category_label": "Lūi-pia̍t: %s",
     "page.edit_category.title": "Pian-chi̍p lūi-pia̍t: %s",
     "page.edit_feed.etag_header": "ETag piau-thâu:",
@@ -531,21 +534,21 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "Chù-chheh Passkey",
     "page.settings.webauthn.register.error": "Bô-hoat-tō͘ chù-chheh Passkey",
-    "page.shared_entries.title": "Hun-hióng kè ê siau-sit",
     "page.shared_entries_count": [
         "Í-keng hun-hióng %d ê siau-sit"
     ],
-    "page.starred.title": "Siu-chông",
+    "page.shared_entries.title": "Hun-hióng kè ê siau-sit",
     "page.starred_entry_count": [
         "%d ê siu-chông ê siau-sit"
     ],
+    "page.starred.title": "Siu-chông",
     "page.total_entry_count": [
         "Lóng-chóng %d ê siau-sit"
     ],
-    "page.unread.title": "Ah-bōe tha̍k",
     "page.unread_entry_count": [
         "%d ê siau-sit ah-bōe tha̍k"
     ],
+    "page.unread.title": "Ah-bōe tha̍k",
     "page.users.actions": "chhau-chok",
     "page.users.admin.no": "Hóⁿ",
     "page.users.admin.yes": "Sī",
@@ -586,4 +589,4 @@
     "time_elapsed.yesterday": "cha-hng",
     "tooltip.keyboard_shortcuts": "Khoài-sok khí:%s",
     "tooltip.logged_user": "Chit-má teng-lo̍k--ê:  %s"
-}
+}

+ 13 - 10
internal/locale/translations/nl_NL.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "Laden...",
     "entry.state.saving": "Opslaan...",
     "entry.status.mark_as_read": "Markeren als gelezen",
+    "entry.status.mark_as_unread": "Markeren als ongelezen",
     "entry.status.title": "Verander artikelstatus",
     "entry.status.toast.read": "Gemarkeerd als gelezen",
     "entry.status.toast.unread": "Gemarkeerd als ongelezen",
-    "entry.status.mark_as_unread": "Markeren als ongelezen",
     "entry.tags.label": "Tags:",
     "entry.unshare.label": "Delen ongedaan maken",
     "error.api_key_already_exists": "Deze API-sleutel bestaat al.",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "Ongeldige startpagina!",
     "error.invalid_display_mode": "Ongeldige weergavemodus voor de webapp.",
     "error.invalid_entry_direction": "Ongeldige sorteervolgorde.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "Ongeldige feed URL.",
     "error.invalid_gesture_nav": "Ongeldige gebarennavigatie.",
     "error.invalid_language": "Ongeldige taal.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "Minimaal 6 tekens gebruiken.",
     "error.pocket_access_token": "Kon geen toegangstoken ophalen van Pocket!",
     "error.pocket_request_token": "Kon geen aanvraagtoken ophalen van Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Ongeldige blokkeerregel: regel #%d mist een geldige veldnaam (Opties: %s)",
     "error.settings_block_rule_invalid_regex": "Ongeldige blokkeerregel: het patroon van regel #%d is geen geldige regex",
     "error.settings_block_rule_regex_required": "Ongeldige blokkeerregel:  het patroon van regel #%d is niet opgegeven",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "Feed wachtwoord",
     "form.feed.label.feed_url": "Feed URL",
     "form.feed.label.feed_username": "Feed gebruikersnaam",
-    "form.feed.label.fetch_via_proxy": "Ophalen via proxy",
+    "form.feed.label.fetch_via_proxy": "Gebruik de proxy die op applicatieniveau is geconfigureerd",
     "form.feed.label.hide_globally": "Verberg artikelen in de globale ongelezen lijst",
     "form.feed.label.ignore_http_cache": "Negeer HTTP-cache",
     "form.feed.label.keeplist_rules": "Bewaarregels",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy minimale prioriteit",
     "form.feed.label.ntfy_priority": "Ntfy prioriteit",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -421,6 +424,10 @@
     "page.api_keys.table.last_used_at": "Laatst gebruikt",
     "page.api_keys.table.token": "Token",
     "page.api_keys.title": "API-sleutels",
+    "page.categories_count": [
+        "%d categorie",
+        "%d categorieën"
+    ],
     "page.categories.entries": "Artikelen",
     "page.categories.feed_count": [
         "Er is %d feed.",
@@ -429,10 +436,6 @@
     "page.categories.feeds": "Feeds",
     "page.categories.no_feed": "Geen feed.",
     "page.categories.title": "Categorieën",
-    "page.categories_count": [
-        "%d categorie",
-        "%d categorieën"
-    ],
     "page.category_label": "Categorie: %s",
     "page.edit_category.title": "Bewerk categorie: %s",
     "page.edit_feed.etag_header": "ETAG header:",
@@ -538,25 +541,25 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "Passkey registreren",
     "page.settings.webauthn.register.error": "Kan passkey niet registreren",
-    "page.shared_entries.title": "Gedeelde artikelen",
     "page.shared_entries_count": [
         "%d gedeeld artikel",
         "%d gedeelde artikelen"
     ],
-    "page.starred.title": "Favorieten",
+    "page.shared_entries.title": "Gedeelde artikelen",
     "page.starred_entry_count": [
         "%d favoriet artikel",
         "%d favoriete artikelen"
     ],
+    "page.starred.title": "Favorieten",
     "page.total_entry_count": [
         "%d artikel totaal",
         "%d artikelen totaal"
     ],
-    "page.unread.title": "Ongelezen",
     "page.unread_entry_count": [
         "%d ongelezen artikel",
         "%d ongelezen artikelen"
     ],
+    "page.unread.title": "Ongelezen",
     "page.users.actions": "Acties",
     "page.users.admin.no": "Nee",
     "page.users.admin.yes": "Ja",
@@ -603,4 +606,4 @@
     "time_elapsed.yesterday": "gisteren",
     "tooltip.keyboard_shortcuts": "Sneltoets: %s",
     "tooltip.logged_user": "Ingelogd als %s"
-}
+}

+ 14 - 11
internal/locale/translations/pl_PL.json

@@ -74,10 +74,10 @@
     "entry.state.loading": "Ładowanie…",
     "entry.state.saving": "Zapisywanie…",
     "entry.status.mark_as_read": "Oznacz jako przeczytany",
+    "entry.status.mark_as_unread": "Oznacz jako nieprzeczytany",
     "entry.status.title": "Zmień status wpisu",
     "entry.status.toast.read": "Oznaczono jako przeczytany",
     "entry.status.toast.unread": "Oznaczono jako nieprzeczytany",
-    "entry.status.mark_as_unread": "Oznacz jako nieprzeczytany",
     "entry.tags.label": "Znaczniki:",
     "entry.unshare.label": "Cofnij udostępnianie",
     "error.api_key_already_exists": "Ten klucz API już istnieje.",
@@ -119,6 +119,7 @@
     "error.invalid_default_home_page": "Nieprawidłowa domyślna strona główna!",
     "error.invalid_display_mode": "Nieprawidłowy tryb wyświetlania aplikacji sieciowej.",
     "error.invalid_entry_direction": "Nieprawidłowa kolejność sortowania.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "Nieprawidłowy adres URL kanału.",
     "error.invalid_gesture_nav": "Nieprawidłowa nawigacja gestami.",
     "error.invalid_language": "Nieprawidłowy język.",
@@ -130,6 +131,7 @@
     "error.password_min_length": "Musisz użyć co najmniej 6 znaków.",
     "error.pocket_access_token": "Nie można pobrać tokena dostępu z Pocket!",
     "error.pocket_request_token": "Nie można pobrać tokena żądania z Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Nieprawidłowa reguła blokowania: w regule #%d brakuje prawidłowej nazwy pola (opcje: %s)",
     "error.settings_block_rule_invalid_regex": "Nieprawidłowa reguła blokowania: wzór reguły #%d nie jest prawidłowym wyrażeniem regularnym",
     "error.settings_block_rule_regex_required": "Nieprawidłowa reguła blokowania: nie podano wzorca reguły #%d",
@@ -176,7 +178,7 @@
     "form.feed.label.feed_password": "Hasło do subskrypcji",
     "form.feed.label.feed_url": "Adres URL kanału",
     "form.feed.label.feed_username": "Nazwa użytkownika subskrypcji",
-    "form.feed.label.fetch_via_proxy": "Pobierz przez proxy",
+    "form.feed.label.fetch_via_proxy": "Użyj proxy skonfigurowanego na poziomie aplikacji",
     "form.feed.label.hide_globally": "Ukryj wpisy na globalnej liście nieprzeczytanych",
     "form.feed.label.ignore_http_cache": "Zignoruj pamięć podręczną HTTP",
     "form.feed.label.keeplist_rules": "Reguły utrzymywania",
@@ -189,6 +191,7 @@
     "form.feed.label.ntfy_min_priority": "Minimalny priorytet ntfy",
     "form.feed.label.ntfy_priority": "Priorytet ntfy",
     "form.feed.label.ntfy_topic": "Temat ntfy (opcjonalny)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Prześlij wpisy do pushover.net",
     "form.feed.label.pushover_default_priority": "Domyślny priorytet Pushover",
     "form.feed.label.pushover_high_priority": "Wysoki priorytet Pushover",
@@ -423,6 +426,11 @@
     "page.api_keys.table.last_used_at": "Ostatnio używane",
     "page.api_keys.table.token": "Token",
     "page.api_keys.title": "Klucze API",
+    "page.categories_count": [
+        "%d kategoria",
+        "%d kategorie",
+        "%d kategorii"
+    ],
     "page.categories.entries": "Wpisy",
     "page.categories.feed_count": [
         "Jest %d kanał.",
@@ -432,11 +440,6 @@
     "page.categories.feeds": "Kanały",
     "page.categories.no_feed": "Brak kanałów.",
     "page.categories.title": "Kategorie",
-    "page.categories_count": [
-        "%d kategoria",
-        "%d kategorie",
-        "%d kategorii"
-    ],
     "page.category_label": "Kategoria: %s",
     "page.edit_category.title": "Edytuj kategorię: %s",
     "page.edit_feed.etag_header": "Nagłówek ETag:",
@@ -545,29 +548,29 @@
     "page.settings.webauthn.passkeys": "Klucze dostępu",
     "page.settings.webauthn.register": "Zarejestruj klucz dostępu",
     "page.settings.webauthn.register.error": "Nie można zarejestrować klucza dostępu",
-    "page.shared_entries.title": "Udostępnione wpisy",
     "page.shared_entries_count": [
         "%d udostępniony wpis",
         "%d udostępnione wpisy",
         "%d udostępnionych wpisów"
     ],
-    "page.starred.title": "Ulubione",
+    "page.shared_entries.title": "Udostępnione wpisy",
     "page.starred_entry_count": [
         "%d ulubiony wpis",
         "%d ulubione wpisy",
         "%d ulubionych wpisów"
     ],
+    "page.starred.title": "Ulubione",
     "page.total_entry_count": [
         "%d wpis łącznie",
         "%d wpisy łącznie",
         "%d wpisów łącznie"
     ],
-    "page.unread.title": "Nieprzeczytane",
     "page.unread_entry_count": [
         "%d nieprzeczytany wpis",
         "%d nieprzeczytane wpisy",
         "%d nieprzeczytanych wpisów"
     ],
+    "page.unread.title": "Nieprzeczytane",
     "page.users.actions": "Działania",
     "page.users.admin.no": "Nie",
     "page.users.admin.yes": "Tak",
@@ -620,4 +623,4 @@
     "time_elapsed.yesterday": "wczoraj",
     "tooltip.keyboard_shortcuts": "Skróty klawiszowe: %s",
     "tooltip.logged_user": "Zalogowany jako %s"
-}
+}

+ 13 - 10
internal/locale/translations/pt_BR.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "Carregando...",
     "entry.state.saving": "Salvando...",
     "entry.status.mark_as_read": "Marcar como lido",
+    "entry.status.mark_as_unread": "Marcar como não lido",
     "entry.status.title": "Modificar estado deste item",
     "entry.status.toast.read": "Marcado como lido",
     "entry.status.toast.unread": "Marcado como não lido",
-    "entry.status.mark_as_unread": "Marcar como não lido",
     "entry.tags.label": "Etiquetas:",
     "entry.unshare.label": "Descompartilhar",
     "error.api_key_already_exists": "Essa chave de API já existe.",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "Página inicial por defeito inválida!",
     "error.invalid_display_mode": "Modo de exibição de aplicativo inválido da web.",
     "error.invalid_entry_direction": "Direção de entrada inválida.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "URL de feed inválido.",
     "error.invalid_gesture_nav": "Navegação por gestos inválida.",
     "error.invalid_language": "Idioma inválido.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "A senha deve ter no mínimo 6 caracteres.",
     "error.pocket_access_token": "Não foi possível obter um token de acesso no Pocket!",
     "error.pocket_request_token": "Não foi possível obter um pedido de token no Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
     "error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
     "error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "Senha da fonte",
     "form.feed.label.feed_url": "URL da fonte",
     "form.feed.label.feed_username": "Nome de usuário da fonte",
-    "form.feed.label.fetch_via_proxy": "Buscar via proxy",
+    "form.feed.label.fetch_via_proxy": "Usar o proxy configurado no nível da aplicação",
     "form.feed.label.hide_globally": "Ocultar entradas na lista global não lida",
     "form.feed.label.ignore_http_cache": "Ignorar cache HTTP",
     "form.feed.label.keeplist_rules": "Regras de permissão",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -421,6 +424,10 @@
     "page.api_keys.table.last_used_at": "Ultima utilização",
     "page.api_keys.table.token": "Token",
     "page.api_keys.title": "Chaves de API",
+    "page.categories_count": [
+        "%d category",
+        "%d categories"
+    ],
     "page.categories.entries": "Itens",
     "page.categories.feed_count": [
         "Existe %d fonte.",
@@ -429,10 +436,6 @@
     "page.categories.feeds": "Inscrições",
     "page.categories.no_feed": "Sem fonte.",
     "page.categories.title": "Categorias",
-    "page.categories_count": [
-        "%d category",
-        "%d categories"
-    ],
     "page.category_label": "Category: %s",
     "page.edit_category.title": "Editar categoria: %s",
     "page.edit_feed.etag_header": "Cabeçalho 'ETag':",
@@ -538,25 +541,25 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "Registrar senha",
     "page.settings.webauthn.register.error": "Não foi possível registrar a senha",
-    "page.shared_entries.title": "Itens compartilhados",
     "page.shared_entries_count": [
         "%d shared entry",
         "%d shared entries"
     ],
-    "page.starred.title": "Favoritos",
+    "page.shared_entries.title": "Itens compartilhados",
     "page.starred_entry_count": [
         "%d starred entry",
         "%d starred entries"
     ],
+    "page.starred.title": "Favoritos",
     "page.total_entry_count": [
         "%d entry in total",
         "%d entries in total"
     ],
-    "page.unread.title": "Não lidos",
     "page.unread_entry_count": [
         "%d unread entry",
         "%d unread entries"
     ],
+    "page.unread.title": "Não lidos",
     "page.users.actions": "Ações",
     "page.users.admin.no": "Não",
     "page.users.admin.yes": "Sim",
@@ -603,4 +606,4 @@
     "time_elapsed.yesterday": "ontem",
     "tooltip.keyboard_shortcuts": "Atalho do teclado: %s",
     "tooltip.logged_user": "Autenticado como %s"
-}
+}

+ 5 - 2
internal/locale/translations/ro_RO.json

@@ -74,10 +74,10 @@
     "entry.state.loading": "Încarc…",
     "entry.state.saving": "Salvez…",
     "entry.status.mark_as_read": "Marcați ca citit",
+    "entry.status.mark_as_unread": "Marcați ca necitit",
     "entry.status.title": "Modifică starea intrării",
     "entry.status.toast.read": "Marcat ca citit",
     "entry.status.toast.unread": "Marcat ca necitit",
-    "entry.status.mark_as_unread": "Marcați ca necitit",
     "entry.tags.label": "Etichete:",
     "entry.unshare.label": "Elimină partajarea",
     "error.api_key_already_exists": "Această cheie API există deja.",
@@ -119,6 +119,7 @@
     "error.invalid_default_home_page": "Pagină de start invalidă!",
     "error.invalid_display_mode": "Mod invalid de afișare în aplicația web.",
     "error.invalid_entry_direction": "Direcție invalidă ăn intrare.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "Adresa URL a fluxului este invalidă.",
     "error.invalid_gesture_nav": "Gest de navigare invalid.",
     "error.invalid_language": "Limbă invalidă.",
@@ -130,6 +131,7 @@
     "error.password_min_length": "Parola trebuie să aibă cel puțin 6 caractere.",
     "error.pocket_access_token": "Nu poate obține token-ul de acces de la Pocket!",
     "error.pocket_request_token": "Nu poate obține token-ul solicitat de la Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Regulă de bloc invalidă: regulii #%d îi lipsește un nume valid de câmp (Opțiuni: %s)",
     "error.settings_block_rule_invalid_regex": "Regulă de bloc invalidă: modelul regulii #%d's nu este regex valid",
     "error.settings_block_rule_regex_required": "Regulă de bloc invalidă: modelul regulii #%d's nu este furnizat",
@@ -176,7 +178,7 @@
     "form.feed.label.feed_password": "Parolă Flux",
     "form.feed.label.feed_url": "Flux URL",
     "form.feed.label.feed_username": "Nume user Flux",
-    "form.feed.label.fetch_via_proxy": "Actualizare prin proxy",
+    "form.feed.label.fetch_via_proxy": "Utilizați proxy-ul configurat la nivelul aplicației",
     "form.feed.label.hide_globally": "Ascunde intrările în lista globală de articole necitite",
     "form.feed.label.ignore_http_cache": "Ignoră cache HTTP",
     "form.feed.label.keeplist_rules": "Reguli de Păstrare",
@@ -189,6 +191,7 @@
     "form.feed.label.ntfy_min_priority": "Prioritate minimă Ntfy",
     "form.feed.label.ntfy_priority": "Prioritate Ntfy",
     "form.feed.label.ntfy_topic": "Subiect Ntfy (opțional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Activează Pushover",
     "form.feed.label.pushover_default_priority": "Prioritate implicită Pushover",
     "form.feed.label.pushover_high_priority": "Prioritate ridicată Pushover",

+ 14 - 11
internal/locale/translations/ru_RU.json

@@ -74,10 +74,10 @@
     "entry.state.loading": "Загрузка…",
     "entry.state.saving": "Сохранение…",
     "entry.status.mark_as_read": "Отметить как прочитанное",
+    "entry.status.mark_as_unread": "Пометить как непрочитанное",
     "entry.status.title": "Изменить статус записи",
     "entry.status.toast.read": "Помечено как прочитанное",
     "entry.status.toast.unread": "Помечено как непрочитанное",
-    "entry.status.mark_as_unread": "Пометить как непрочитанное",
     "entry.tags.label": "Теги:",
     "entry.unshare.label": "Удалить из общедоступных",
     "error.api_key_already_exists": "Этот API-ключ уже существует.",
@@ -119,6 +119,7 @@
     "error.invalid_default_home_page": "Недопустимая домашняя страница по умолчанию!",
     "error.invalid_display_mode": "Недопустимый режим отображения веб-приложения.",
     "error.invalid_entry_direction": "Недопустимая сортировка записей.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "Недействительная ссылка подписки.",
     "error.invalid_gesture_nav": "Недопустимая навигация жестами.",
     "error.invalid_language": "Недопустимый язык.",
@@ -130,6 +131,7 @@
     "error.password_min_length": "Вы должны использовать минимум 6 символов.",
     "error.pocket_access_token": "Не удалось получить ключ доступа от Pocket!",
     "error.pocket_request_token": "Не удалось получить request token от Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
     "error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
     "error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
@@ -176,7 +178,7 @@
     "form.feed.label.feed_password": "Пароль подписки",
     "form.feed.label.feed_url": "Адрес подписки",
     "form.feed.label.feed_username": "Имя пользователя подписки",
-    "form.feed.label.fetch_via_proxy": "Использовать прокси",
+    "form.feed.label.fetch_via_proxy": "Использовать прокси, настроенный на уровне приложения",
     "form.feed.label.hide_globally": "Скрыть записи в глобальном списке непрочитанных",
     "form.feed.label.ignore_http_cache": "Игнорировать HTTP кеш",
     "form.feed.label.keeplist_rules": "Правила белого списка",
@@ -189,6 +191,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -423,6 +426,11 @@
     "page.api_keys.table.last_used_at": "Последнее использование",
     "page.api_keys.table.token": "Токен",
     "page.api_keys.title": "API-ключи",
+    "page.categories_count": [
+        "%d category",
+        "%d categories",
+        "%d categories"
+    ],
     "page.categories.entries": "Cтатьи",
     "page.categories.feed_count": [
         "Есть %d подписка.",
@@ -432,11 +440,6 @@
     "page.categories.feeds": "Подписки",
     "page.categories.no_feed": "Нет подписок.",
     "page.categories.title": "Категории",
-    "page.categories_count": [
-        "%d category",
-        "%d categories",
-        "%d categories"
-    ],
     "page.category_label": "Category: %s",
     "page.edit_category.title": "Изменить категорию: %s",
     "page.edit_feed.etag_header": "Заголовок ETag:",
@@ -545,29 +548,29 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "Зарегистрировать пароль",
     "page.settings.webauthn.register.error": "Не удается зарегистрировать пароль",
-    "page.shared_entries.title": "Общедоступные статьи",
     "page.shared_entries_count": [
         "%d shared entry",
         "%d shared entries",
         "%d shared entries"
     ],
-    "page.starred.title": "Избранное",
+    "page.shared_entries.title": "Общедоступные статьи",
     "page.starred_entry_count": [
         "%d starred entry",
         "%d starred entries",
         "%d starred entries"
     ],
+    "page.starred.title": "Избранное",
     "page.total_entry_count": [
         "%d entry in total",
         "%d entries in total",
         "%d entries in total"
     ],
-    "page.unread.title": "Непрочитанное",
     "page.unread_entry_count": [
         "%d unread entry",
         "%d unread entries",
         "%d unread entries"
     ],
+    "page.unread.title": "Непрочитанное",
     "page.users.actions": "Действия",
     "page.users.admin.no": "Нет",
     "page.users.admin.yes": "Да",
@@ -620,4 +623,4 @@
     "time_elapsed.yesterday": "вчера",
     "tooltip.keyboard_shortcuts": "Сочетания клавиш: %s",
     "tooltip.logged_user": "Авторизован как %s"
-}
+}

+ 13 - 10
internal/locale/translations/tr_TR.json

@@ -72,10 +72,10 @@
     "entry.state.loading": "Yükleniyor...",
     "entry.state.saving": "Kaydediliyor...",
     "entry.status.mark_as_read": "Okundu olarak işaretle",
+    "entry.status.mark_as_unread": "Okunmadı olarak işaretle",
     "entry.status.title": "Makele okundu durumunu değiştir",
     "entry.status.toast.read": "Okundu olarak işaretlendi",
     "entry.status.toast.unread": "Okunmamış olarak işaretlendi",
-    "entry.status.mark_as_unread": "Okunmadı olarak işaretle",
     "entry.tags.label": "Etiketler:",
     "entry.unshare.label": "Paylaşma",
     "error.api_key_already_exists": "Bu API anahtarı zaten mevcut.",
@@ -117,6 +117,7 @@
     "error.invalid_default_home_page": "Geçersiz varsayılan ana sayfa!",
     "error.invalid_display_mode": "Geçersiz web uygulaması görüntüleme modu.",
     "error.invalid_entry_direction": "Geçersiz makele sıralaması.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "Geçersiz besleme URL'si.",
     "error.invalid_gesture_nav": "Hareketle gezinme geçersiz.",
     "error.invalid_language": "Geçersiz dil.",
@@ -128,6 +129,7 @@
     "error.password_min_length": "Parola en az 6 karakter içermeli.",
     "error.pocket_access_token": "Pocket'tan access tokeni alınamıyor!",
     "error.pocket_request_token": "Pocket'tan request tokeni alınamıyor!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Geçersiz Engelleme kuralı: #%d kuralında geçerli bir alan adı eksik (Seçenekler: %s)",
     "error.settings_block_rule_invalid_regex": "Geçersiz Engelleme kuralı: #%d kuralı modeli geçerli bir düzenli ifade değil",
     "error.settings_block_rule_regex_required": "Geçersiz Engelleme kuralı: #%d kuralı modeli sağlanmadı",
@@ -174,7 +176,7 @@
     "form.feed.label.feed_password": "Besleme Parolası",
     "form.feed.label.feed_url": "Besleme URL'si",
     "form.feed.label.feed_username": "Besleme Kullanıcı Adı",
-    "form.feed.label.fetch_via_proxy": "Proxy ile çek",
+    "form.feed.label.fetch_via_proxy": "Uygulama düzeyinde yapılandırılmış proxy'yi kullan",
     "form.feed.label.hide_globally": "Genel okunmamış listesindeki girişleri gizle",
     "form.feed.label.ignore_http_cache": "HTTP önbelleğini yoksay",
     "form.feed.label.keeplist_rules": "Saklama Kuralları",
@@ -187,6 +189,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -421,6 +424,10 @@
     "page.api_keys.table.last_used_at": "Son Kullanılma",
     "page.api_keys.table.token": "Token",
     "page.api_keys.title": "API Anahtarları",
+    "page.categories_count": [
+        "%d kategori",
+        "%d kategori"
+    ],
     "page.categories.entries": "Makaleler",
     "page.categories.feed_count": [
         "%d besleme var.",
@@ -429,10 +436,6 @@
     "page.categories.feeds": "Beslemeler",
     "page.categories.no_feed": "Besleme yok.",
     "page.categories.title": "Kategoriler",
-    "page.categories_count": [
-        "%d kategori",
-        "%d kategori"
-    ],
     "page.category_label": "Kategori: %s",
     "page.edit_category.title": "Kategoriyi Düzenle: %s",
     "page.edit_feed.etag_header": "ETag başlığı:",
@@ -538,25 +541,25 @@
     "page.settings.webauthn.passkeys": "Passkeyler",
     "page.settings.webauthn.register": "Passkey'i kaydet",
     "page.settings.webauthn.register.error": "Passkey kaydedilemiyor",
-    "page.shared_entries.title": "Paylaşılan makaleler",
     "page.shared_entries_count": [
         "%d paylaşılan makaleler",
         "%d paylaşılan makaleler"
     ],
-    "page.starred.title": "Yıldızlı",
+    "page.shared_entries.title": "Paylaşılan makaleler",
     "page.starred_entry_count": [
         "%d yıldızlanmış makale",
         "%d yıldızlanmış makale"
     ],
+    "page.starred.title": "Yıldızlı",
     "page.total_entry_count": [
         "Toplamda %d makale",
         "Toplamda %d makale"
     ],
-    "page.unread.title": "Okunmadı",
     "page.unread_entry_count": [
         "Toplamda %d okunmamış makale",
         "Toplamda %d okunmamış makale"
     ],
+    "page.unread.title": "Okunmadı",
     "page.users.actions": "Eylemler",
     "page.users.admin.no": "Hayır",
     "page.users.admin.yes": "Evet",
@@ -603,4 +606,4 @@
     "time_elapsed.yesterday": "dün",
     "tooltip.keyboard_shortcuts": "Klavye Kısayolu: %s",
     "tooltip.logged_user": "%s olarak giriş yapıldı"
-}
+}

+ 14 - 11
internal/locale/translations/uk_UA.json

@@ -74,10 +74,10 @@
     "entry.state.loading": "Завантаження...",
     "entry.state.saving": "Зберігаю...",
     "entry.status.mark_as_read": "Позначити як прочитане",
+    "entry.status.mark_as_unread": "Позначити як непрочитане",
     "entry.status.title": "Змінити стан запису",
     "entry.status.toast.read": "Відмічено прочитаним",
     "entry.status.toast.unread": "Відмічено непрочитаним",
-    "entry.status.mark_as_unread": "Позначити як непрочитане",
     "entry.tags.label": "Теги:",
     "entry.unshare.label": "Не ділитися",
     "error.api_key_already_exists": "Такий ключ API вже існує.",
@@ -119,6 +119,7 @@
     "error.invalid_default_home_page": "Недійсна домашня сторінка за замовчуванням!",
     "error.invalid_display_mode": "Недійсний режим відображення.",
     "error.invalid_entry_direction": "Недійсний напрямок запису.",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "Недійсна URL-адреса стрічки.",
     "error.invalid_gesture_nav": "Недійсна навігація жестами.",
     "error.invalid_language": "Недійсна мова.",
@@ -130,6 +131,7 @@
     "error.password_min_length": "Пароль має складати щонайменше 6 символів.",
     "error.pocket_access_token": "Не вдалося отримати токен доступу з Pocket!",
     "error.pocket_request_token": "Не вдалося отримати токен доступу з Pocket!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
     "error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
     "error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
@@ -176,7 +178,7 @@
     "form.feed.label.feed_password": "Пароль для завантаження",
     "form.feed.label.feed_url": "URL-адреса стрічки",
     "form.feed.label.feed_username": "Ім’я користувача для завантаження",
-    "form.feed.label.fetch_via_proxy": "Використати проксі-сервер",
+    "form.feed.label.fetch_via_proxy": "Використовувати проксі, налаштований на рівні програми",
     "form.feed.label.hide_globally": "Приховати записи в глобальному списку непрочитаного",
     "form.feed.label.ignore_http_cache": "Ігнорувати кеш HTTP",
     "form.feed.label.keeplist_rules": "Правила дозволення",
@@ -189,6 +191,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy min priority",
     "form.feed.label.ntfy_priority": "Ntfy priority",
     "form.feed.label.ntfy_topic": "Ntfy topic (optional)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -423,6 +426,11 @@
     "page.api_keys.table.last_used_at": "Дата останнього використання",
     "page.api_keys.table.token": "Токен",
     "page.api_keys.title": "Ключі API",
+    "page.categories_count": [
+        "%d category",
+        "%d categories",
+        "%d categories"
+    ],
     "page.categories.entries": "Статті",
     "page.categories.feed_count": [
         "Містить %d стрічку.",
@@ -432,11 +440,6 @@
     "page.categories.feeds": "Підписки",
     "page.categories.no_feed": "Немає стрічки.",
     "page.categories.title": "Категорії",
-    "page.categories_count": [
-        "%d category",
-        "%d categories",
-        "%d categories"
-    ],
     "page.category_label": "Категорія: %s",
     "page.edit_category.title": "Редагування категорії: %s",
     "page.edit_feed.etag_header": "Заголовок ETag:",
@@ -545,29 +548,29 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "Зареєструвати пароль",
     "page.settings.webauthn.register.error": "Не вдалося зареєструвати ключ доступу",
-    "page.shared_entries.title": "Спільні записи",
     "page.shared_entries_count": [
         "%d shared entry",
         "%d shared entries",
         "%d shared entries"
     ],
-    "page.starred.title": "З зірочкою",
+    "page.shared_entries.title": "Спільні записи",
     "page.starred_entry_count": [
         "%d starred entry",
         "%d starred entries",
         "%d starred entries"
     ],
+    "page.starred.title": "З зірочкою",
     "page.total_entry_count": [
         "%d entry in total",
         "%d entries in total",
         "%d entries in total"
     ],
-    "page.unread.title": "Непрочитане",
     "page.unread_entry_count": [
         "%d unread entry",
         "%d unread entries",
         "%d unread entries"
     ],
+    "page.unread.title": "Непрочитане",
     "page.users.actions": "Дії",
     "page.users.admin.no": "Ні",
     "page.users.admin.yes": "Так",
@@ -620,4 +623,4 @@
     "time_elapsed.yesterday": "вчора",
     "tooltip.keyboard_shortcuts": "Комбінація клавіш: %s",
     "tooltip.logged_user": "Здійснено вхід як %s"
-}
+}

+ 6 - 3
internal/locale/translations/zh_CN.json

@@ -70,10 +70,10 @@
     "entry.state.loading": "载入中…",
     "entry.state.saving": "保存中…",
     "entry.status.mark_as_read": "标为已读",
+    "entry.status.mark_as_unread": "标为未读",
     "entry.status.title": "更改状态",
     "entry.status.toast.read": "已标为已读",
     "entry.status.toast.unread": "已标为未读",
-    "entry.status.mark_as_unread": "标为未读",
     "entry.tags.label": "标签:",
     "entry.unshare.label": "取消分享",
     "error.api_key_already_exists": "此 API 密钥已存在。",
@@ -115,6 +115,7 @@
     "error.invalid_default_home_page": "无效的默认主页!",
     "error.invalid_display_mode": "无效的网页应用显示模式。",
     "error.invalid_entry_direction": "无效的输入方向。",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "订阅源的网址无效。",
     "error.invalid_gesture_nav": "手势导航无效。",
     "error.invalid_language": "无效的语言。",
@@ -126,6 +127,7 @@
     "error.password_min_length": "请至少输入 6 个字符",
     "error.pocket_access_token": "无法从 Pocket 获取访问令牌!",
     "error.pocket_request_token": "无法从 Pocket 获取请求令牌!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "无效的阻止规则: 规则 #%d 缺少合法的字段名 (可选: %s)",
     "error.settings_block_rule_invalid_regex": "无效的阻止规则: 规则 #%d 的模式字符不是合法的正则表达式。",
     "error.settings_block_rule_regex_required": "无效的阻止规则: 规则 #%d 的模式字符没有提供。",
@@ -172,7 +174,7 @@
     "form.feed.label.feed_password": "源密码",
     "form.feed.label.feed_url": "订阅源 URL",
     "form.feed.label.feed_username": "源用户名",
-    "form.feed.label.fetch_via_proxy": "通过代理获取",
+    "form.feed.label.fetch_via_proxy": "使用在应用程序级别配置的代理",
     "form.feed.label.hide_globally": "隐藏全局未读列表中的文章",
     "form.feed.label.ignore_http_cache": "忽略 HTTP 缓存",
     "form.feed.label.keeplist_rules": "保留规则",
@@ -185,6 +187,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy 最低优先级",
     "form.feed.label.ntfy_priority": "Ntfy 优先级",
     "form.feed.label.ntfy_topic": "Ntfy 主题(可选)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "将条目推送至 pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover 默认优先级",
     "form.feed.label.pushover_high_priority": "Pushover 高优先级",
@@ -586,4 +589,4 @@
     "time_elapsed.yesterday": "昨天",
     "tooltip.keyboard_shortcuts": "快捷键: %s",
     "tooltip.logged_user": "当前登录 %s"
-}
+}

+ 12 - 9
internal/locale/translations/zh_TW.json

@@ -70,10 +70,10 @@
     "entry.state.loading": "載入中…",
     "entry.state.saving": "儲存中…",
     "entry.status.mark_as_read": "標記為已讀",
+    "entry.status.mark_as_unread": "標記為未讀",
     "entry.status.title": "更改狀態",
     "entry.status.toast.read": "已標記為已讀",
     "entry.status.toast.unread": "已標記為未讀",
-    "entry.status.mark_as_unread": "標記為未讀",
     "entry.tags.label": "標籤:",
     "entry.unshare.label": "取消分享",
     "error.api_key_already_exists": "此 API 金鑰已存在。",
@@ -115,6 +115,7 @@
     "error.invalid_default_home_page": "預設主頁無效!",
     "error.invalid_display_mode": "無效的顯示模式。",
     "error.invalid_entry_direction": "無效的輸入方向。",
+    "error.invalid_feed_proxy_url": "Invalid proxy URL.",
     "error.invalid_feed_url": "訂閱網址無效。",
     "error.invalid_gesture_nav": "手勢導覽無效。",
     "error.invalid_language": "無效的語言。",
@@ -126,6 +127,7 @@
     "error.password_min_length": "請至少輸入 6 個字元",
     "error.pocket_access_token": "無法從 Pocket 取得存取金鑰!",
     "error.pocket_request_token": "無法從 Pocket 取得請求金鑰!",
+    "error.proxy_url_not_empty": "The proxy URL cannot be empty.",
     "error.settings_block_rule_fieldname_invalid": "無效的封鎖規則:規則 #%d 缺少有效的欄位名稱 (可用選項:%s)",
     "error.settings_block_rule_invalid_regex": "無效的封鎖規則:規則 #%d 的模式不是合法的正規表示式",
     "error.settings_block_rule_regex_required": "無效的封鎖規則:規則 #%d 沒有提供正規表示式",
@@ -172,7 +174,7 @@
     "form.feed.label.feed_password": "Feed 密碼",
     "form.feed.label.feed_url": "Feed 網址",
     "form.feed.label.feed_username": "Feed 使用者名稱",
-    "form.feed.label.fetch_via_proxy": "透過代理取得",
+    "form.feed.label.fetch_via_proxy": "使用應用程式層級設定的代理",
     "form.feed.label.hide_globally": "在全域未讀列表中隱藏文章",
     "form.feed.label.ignore_http_cache": "忽略 HTTP 快取",
     "form.feed.label.keeplist_rules": "保留規則",
@@ -185,6 +187,7 @@
     "form.feed.label.ntfy_min_priority": "Ntfy 最低優先順序",
     "form.feed.label.ntfy_priority": "Ntfy 優先順序",
     "form.feed.label.ntfy_topic": "Ntfy topic (選填)",
+    "form.feed.label.proxy_url": "Proxy URL",
     "form.feed.label.pushover_activate": "Push entries to pushover.net",
     "form.feed.label.pushover_default_priority": "Pushover default priority",
     "form.feed.label.pushover_high_priority": "Pushover high priority",
@@ -419,6 +422,9 @@
     "page.api_keys.table.last_used_at": "最後使用",
     "page.api_keys.table.token": "金鑰",
     "page.api_keys.title": "API 金鑰",
+    "page.categories_count": [
+        "%d 個分類"
+    ],
     "page.categories.entries": "檢視內容",
     "page.categories.feed_count": [
         "有 %d 個 Feed"
@@ -426,9 +432,6 @@
     "page.categories.feeds": "檢視 Feeds",
     "page.categories.no_feed": "沒有 Feed",
     "page.categories.title": "分類",
-    "page.categories_count": [
-        "%d 個分類"
-    ],
     "page.category_label": "分類:%s",
     "page.edit_category.title": "編輯分類 : %s",
     "page.edit_feed.etag_header": "ETag 標頭:",
@@ -531,21 +534,21 @@
     "page.settings.webauthn.passkeys": "Passkeys",
     "page.settings.webauthn.register": "註冊 Passkey",
     "page.settings.webauthn.register.error": "無法註冊 Passkey",
-    "page.shared_entries.title": "已分享的文章",
     "page.shared_entries_count": [
         "已分享 %d 篇文章"
     ],
-    "page.starred.title": "收藏",
+    "page.shared_entries.title": "已分享的文章",
     "page.starred_entry_count": [
         "%d 篇收藏文章"
     ],
+    "page.starred.title": "收藏",
     "page.total_entry_count": [
         "總共 %d 篇文章"
     ],
-    "page.unread.title": "未讀",
     "page.unread_entry_count": [
         "%d 篇未讀文章"
     ],
+    "page.unread.title": "未讀",
     "page.users.actions": "操作",
     "page.users.admin.no": "否",
     "page.users.admin.yes": "是",
@@ -586,4 +589,4 @@
     "time_elapsed.yesterday": "昨天",
     "tooltip.keyboard_shortcuts": "快捷鍵:%s",
     "tooltip.logged_user": "目前登入 %s"
-}
+}

+ 9 - 2
internal/model/feed.go

@@ -57,8 +57,9 @@ type Feed struct {
 	NtfyEnabled                 bool      `json:"ntfy_enabled"`
 	NtfyPriority                int       `json:"ntfy_priority"`
 	NtfyTopic                   string    `json:"ntfy_topic"`
-	PushoverEnabled             bool      `json:"pushover_enabled,omitempty"`
-	PushoverPriority            int       `json:"pushover_priority,omitempty"`
+	PushoverEnabled             bool      `json:"pushover_enabled"`
+	PushoverPriority            int       `json:"pushover_priority"`
+	ProxyURL                    string    `json:"proxy_url"`
 
 	// Non-persisted attributes
 	Category *Category `json:"category,omitempty"`
@@ -161,6 +162,7 @@ type FeedCreationRequest struct {
 	HideGlobally                bool   `json:"hide_globally"`
 	UrlRewriteRules             string `json:"urlrewrite_rules"`
 	DisableHTTP2                bool   `json:"disable_http2"`
+	ProxyURL                    string `json:"proxy_url"`
 }
 
 type FeedCreationRequestFromSubscriptionDiscovery struct {
@@ -195,6 +197,7 @@ type FeedModificationRequest struct {
 	FetchViaProxy               *bool   `json:"fetch_via_proxy"`
 	HideGlobally                *bool   `json:"hide_globally"`
 	DisableHTTP2                *bool   `json:"disable_http2"`
+	ProxyURL                    *string `json:"proxy_url"`
 }
 
 // Patch updates a feed with modified values.
@@ -286,6 +289,10 @@ func (f *FeedModificationRequest) Patch(feed *Feed) {
 	if f.DisableHTTP2 != nil {
 		feed.DisableHTTP2 = *f.DisableHTTP2
 	}
+
+	if f.ProxyURL != nil {
+		feed.ProxyURL = *f.ProxyURL
+	}
 }
 
 // Feeds is a list of feed

+ 1 - 0
internal/model/subscription.go

@@ -10,6 +10,7 @@ type SubscriptionDiscoveryRequest struct {
 	Cookie                      string `json:"cookie"`
 	Username                    string `json:"username"`
 	Password                    string `json:"password"`
+	ProxyURL                    string `json:"proxy_url"`
 	FetchViaProxy               bool   `json:"fetch_via_proxy"`
 	AllowSelfSignedCertificates bool   `json:"allow_self_signed_certificates"`
 	DisableHTTP2                bool   `json:"disable_http2"`

+ 17 - 2
internal/reader/fetcher/request_builder.go

@@ -6,6 +6,7 @@ package fetcher // import "miniflux.app/v2/internal/reader/fetcher"
 import (
 	"crypto/tls"
 	"encoding/base64"
+	"fmt"
 	"log/slog"
 	"net"
 	"net/http"
@@ -30,6 +31,7 @@ type RequestBuilder struct {
 	ignoreTLSErrors  bool
 	disableHTTP2     bool
 	proxyRotator     *proxyrotator.ProxyRotator
+	feedProxyURL     string
 }
 
 func NewRequestBuilder() *RequestBuilder {
@@ -96,6 +98,11 @@ func (r *RequestBuilder) UseCustomApplicationProxyURL(value bool) *RequestBuilde
 	return r
 }
 
+func (r *RequestBuilder) WithCustomFeedProxyURL(proxyURL string) *RequestBuilder {
+	r.feedProxyURL = proxyURL
+	return r
+}
+
 func (r *RequestBuilder) WithTimeout(timeout int) *RequestBuilder {
 	r.clientTimeout = timeout
 	return r
@@ -160,9 +167,17 @@ func (r *RequestBuilder) ExecuteRequest(requestURL string) (*http.Response, erro
 	}
 
 	var clientProxyURL *url.URL
-	if r.useClientProxy && r.clientProxyURL != nil {
+
+	switch {
+	case r.feedProxyURL != "":
+		var err error
+		clientProxyURL, err = url.Parse(r.feedProxyURL)
+		if err != nil {
+			return nil, fmt.Errorf(`fetcher: invalid feed proxy URL %q: %w`, r.feedProxyURL, err)
+		}
+	case r.useClientProxy && r.clientProxyURL != nil:
 		clientProxyURL = r.clientProxyURL
-	} else if r.proxyRotator != nil && r.proxyRotator.HasProxies() {
+	case r.proxyRotator != nil && r.proxyRotator.HasProxies():
 		clientProxyURL = r.proxyRotator.GetNextProxy()
 	}
 

+ 7 - 0
internal/reader/handler/handler.go

@@ -30,6 +30,7 @@ func CreateFeedFromSubscriptionDiscovery(store *storage.Storage, userID int64, f
 	slog.Debug("Begin feed creation process from subscription discovery",
 		slog.Int64("user_id", userID),
 		slog.String("feed_url", feedCreationRequest.FeedURL),
+		slog.String("proxy_url", feedCreationRequest.ProxyURL),
 	)
 
 	if !store.CategoryIDExists(userID, feedCreationRequest.CategoryID) {
@@ -65,6 +66,7 @@ func CreateFeedFromSubscriptionDiscovery(store *storage.Storage, userID int64, f
 	subscription.FeedURL = feedCreationRequest.FeedURL
 	subscription.DisableHTTP2 = feedCreationRequest.DisableHTTP2
 	subscription.WithCategoryID(feedCreationRequest.CategoryID)
+	subscription.ProxyURL = feedCreationRequest.ProxyURL
 	subscription.CheckedNow()
 
 	processor.ProcessFeedEntries(store, subscription, userID, true)
@@ -85,6 +87,7 @@ func CreateFeedFromSubscriptionDiscovery(store *storage.Storage, userID int64, f
 	requestBuilder.WithCookie(feedCreationRequest.Cookie)
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(feedCreationRequest.ProxyURL)
 	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 	requestBuilder.UseCustomApplicationProxyURL(feedCreationRequest.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(feedCreationRequest.AllowSelfSignedCertificates)
@@ -100,6 +103,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
 	slog.Debug("Begin feed creation process",
 		slog.Int64("user_id", userID),
 		slog.String("feed_url", feedCreationRequest.FeedURL),
+		slog.String("proxy_url", feedCreationRequest.ProxyURL),
 	)
 
 	if !store.CategoryIDExists(userID, feedCreationRequest.CategoryID) {
@@ -112,6 +116,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
 	requestBuilder.WithCookie(feedCreationRequest.Cookie)
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(feedCreationRequest.ProxyURL)
 	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 	requestBuilder.UseCustomApplicationProxyURL(feedCreationRequest.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(feedCreationRequest.AllowSelfSignedCertificates)
@@ -160,6 +165,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
 	subscription.EtagHeader = responseHandler.ETag()
 	subscription.LastModifiedHeader = responseHandler.LastModified()
 	subscription.FeedURL = responseHandler.EffectiveURL()
+	subscription.ProxyURL = feedCreationRequest.ProxyURL
 	subscription.WithCategoryID(feedCreationRequest.CategoryID)
 	subscription.CheckedNow()
 
@@ -216,6 +222,7 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool
 	requestBuilder.WithCookie(originalFeed.Cookie)
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(originalFeed.ProxyURL)
 	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 	requestBuilder.UseCustomApplicationProxyURL(originalFeed.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(originalFeed.AllowSelfSignedCertificates)

+ 1 - 0
internal/reader/icon/checker.go

@@ -31,6 +31,7 @@ func (c *IconChecker) fetchAndStoreIcon() {
 	requestBuilder.WithCookie(c.feed.Cookie)
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(c.feed.ProxyURL)
 	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 	requestBuilder.UseCustomApplicationProxyURL(c.feed.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(c.feed.AllowSelfSignedCertificates)

+ 0 - 1
internal/reader/processor/bilibili.go

@@ -47,7 +47,6 @@ func fetchBilibiliWatchTime(websiteURL string) (int, error) {
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
-	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 
 	idType, videoID, extractErr := extractBilibiliVideoID(websiteURL)
 	if extractErr != nil {

+ 0 - 1
internal/reader/processor/nebula.go

@@ -35,7 +35,6 @@ func fetchNebulaWatchTime(websiteURL string) (int, error) {
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
-	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(websiteURL))
 	defer responseHandler.Close()

+ 0 - 1
internal/reader/processor/odysee.go

@@ -35,7 +35,6 @@ func fetchOdyseeWatchTime(websiteURL string) (int, error) {
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
-	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(websiteURL))
 	defer responseHandler.Close()

+ 2 - 0
internal/reader/processor/processor.go

@@ -80,6 +80,7 @@ func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, userID int64,
 			requestBuilder.WithCookie(feed.Cookie)
 			requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 			requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+			requestBuilder.WithCustomFeedProxyURL(feed.ProxyURL)
 			requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 			requestBuilder.UseCustomApplicationProxyURL(feed.FetchViaProxy)
 			requestBuilder.IgnoreTLSErrors(feed.AllowSelfSignedCertificates)
@@ -148,6 +149,7 @@ func ProcessEntryWebPage(feed *model.Feed, entry *model.Entry, user *model.User)
 	requestBuilder.WithCookie(feed.Cookie)
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(feed.ProxyURL)
 	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 	requestBuilder.UseCustomApplicationProxyURL(feed.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(feed.AllowSelfSignedCertificates)

+ 0 - 2
internal/reader/processor/youtube.go

@@ -54,7 +54,6 @@ func fetchYouTubeWatchTimeForSingleEntry(websiteURL string) (int, error) {
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
-	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(websiteURL))
 	defer responseHandler.Close()
@@ -135,7 +134,6 @@ func fetchYouTubeWatchTimeFromApiInBulk(videoIDs []string) (map[string]time.Dura
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
-	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(apiURL.String()))
 	defer responseHandler.Close()

+ 8 - 4
internal/storage/feed.go

@@ -248,10 +248,11 @@ func (s *Storage) CreateFeed(feed *model.Feed) error {
 			apprise_service_urls,
 			webhook_url,
 			disable_http2,
-			description
+			description,
+			proxy_url
 		)
 		VALUES
-			($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27)
+			($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)
 		RETURNING
 			id
 	`
@@ -284,6 +285,7 @@ func (s *Storage) CreateFeed(feed *model.Feed) error {
 		feed.WebhookURL,
 		feed.DisableHTTP2,
 		feed.Description,
+		feed.ProxyURL,
 	).Scan(&feed.ID)
 	if err != nil {
 		return fmt.Errorf(`store: unable to create feed %q: %v`, feed.FeedURL, err)
@@ -363,9 +365,10 @@ func (s *Storage) UpdateFeed(feed *model.Feed) (err error) {
 			ntfy_priority=$32,
 			ntfy_topic=$33,
 			pushover_enabled=$34,
-			pushover_priority=$35
+			pushover_priority=$35,
+			proxy_url=$36
 		WHERE
-			id=$36 AND user_id=$37
+			id=$37 AND user_id=$38
 	`
 	_, err = s.db.Exec(query,
 		feed.FeedURL,
@@ -403,6 +406,7 @@ func (s *Storage) UpdateFeed(feed *model.Feed) (err error) {
 		feed.NtfyTopic,
 		feed.PushoverEnabled,
 		feed.PushoverPriority,
+		feed.ProxyURL,
 		feed.ID,
 		feed.UserID,
 	)

+ 3 - 1
internal/storage/feed_query_builder.go

@@ -172,7 +172,8 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
 			f.ntfy_priority,
 			f.ntfy_topic,
 			f.pushover_enabled,
-			f.pushover_priority
+			f.pushover_priority,
+			f.proxy_url
 		FROM
 			feeds f
 		LEFT JOIN
@@ -251,6 +252,7 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
 			&feed.NtfyTopic,
 			&feed.PushoverEnabled,
 			&feed.PushoverPriority,
+			&feed.ProxyURL,
 		)
 
 		if err != nil {

+ 3 - 0
internal/template/templates/views/add_subscription.html

@@ -39,6 +39,9 @@
                 <label><input type="checkbox" name="fetch_via_proxy" value="1" {{ if .form.FetchViaProxy }}checked{{ end }}> {{ t "form.feed.label.fetch_via_proxy" }}</label>
                 {{ end }}
 
+                <label for="form-proxy-url">{{ t "form.feed.label.proxy_url" }}</label>
+                <input type="url" name="proxy_url" id="form-proxy-url" value="{{ .form.ProxyURL }}" spellcheck="false">
+
                 <label for="form-user-agent">{{ t "form.feed.label.user_agent" }}</label>
                 <input type="text" name="user_agent" id="form-user-agent" placeholder="{{ .defaultUserAgent }}" value="{{ .form.UserAgent }}"  spellcheck="false" autocomplete="off">
 

+ 1 - 0
internal/template/templates/views/choose_subscription.html

@@ -20,6 +20,7 @@
     <input type="hidden" name="blocklist_rules" value="{{ .form.BlocklistRules }}">
     <input type="hidden" name="keeplist_rules" value="{{ .form.KeeplistRules }}">
     <input type="hidden" name="urlrewrite_rules" value="{{ .form.UrlRewriteRules }}">
+    <input type="hidden" name="proxy_url" value="{{ .form.ProxyURL }}">
     {{ if .form.FetchViaProxy }}
     <input type="hidden" name="fetch_via_proxy" value="1">
     {{ end }}

+ 3 - 0
internal/template/templates/views/edit_feed.html

@@ -97,6 +97,9 @@
             <label for="form-user-agent">{{ t "form.feed.label.user_agent" }}</label>
             <input type="text" name="user_agent" id="form-user-agent" placeholder="{{ .defaultUserAgent }}" value="{{ .form.UserAgent }}" spellcheck="false">
 
+            <label for="form-proxy-url">{{ t "form.feed.label.proxy_url" }}</label>
+            <input type="url" name="proxy_url" id="form-proxy-url" value="{{ .form.ProxyURL }}" spellcheck="false">
+
             <label for="form-cookie">{{ t "form.feed.label.cookie" }}</label>
             <input type="text" name="cookie" id="form-cookie" value="{{ .form.Cookie }}" spellcheck="false">
 

+ 1 - 0
internal/ui/feed_edit.go

@@ -70,6 +70,7 @@ func (h *handler) showEditFeedPage(w http.ResponseWriter, r *http.Request) {
 		NtfyTopic:                   feed.NtfyTopic,
 		PushoverEnabled:             feed.PushoverEnabled,
 		PushoverPriority:            feed.PushoverPriority,
+		ProxyURL:                    feed.ProxyURL,
 	}
 
 	sess := session.New(h.store, request.SessionID(r))

+ 1 - 0
internal/ui/feed_update.go

@@ -64,6 +64,7 @@ func (h *handler) updateFeed(w http.ResponseWriter, r *http.Request) {
 		BlocklistRules:  model.OptionalString(feedForm.BlocklistRules),
 		KeeplistRules:   model.OptionalString(feedForm.KeeplistRules),
 		UrlRewriteRules: model.OptionalString(feedForm.UrlRewriteRules),
+		ProxyURL:        model.OptionalString(feedForm.ProxyURL),
 	}
 
 	if validationErr := validator.ValidateFeedModification(h.store, loggedUser.ID, feed.ID, feedModificationRequest); validationErr != nil {

+ 4 - 0
internal/ui/form/feed.go

@@ -42,6 +42,7 @@ type FeedForm struct {
 	NtfyTopic                   string
 	PushoverEnabled             bool
 	PushoverPriority            int
+	ProxyURL                    string
 }
 
 // Merge updates the fields of the given feed.
@@ -77,6 +78,7 @@ func (f FeedForm) Merge(feed *model.Feed) *model.Feed {
 	feed.NtfyTopic = f.NtfyTopic
 	feed.PushoverEnabled = f.PushoverEnabled
 	feed.PushoverPriority = f.PushoverPriority
+	feed.ProxyURL = f.ProxyURL
 	return feed
 }
 
@@ -86,6 +88,7 @@ func NewFeedForm(r *http.Request) *FeedForm {
 	if err != nil {
 		categoryID = 0
 	}
+
 	ntfyPriority, err := strconv.Atoi(r.FormValue("ntfy_priority"))
 	if err != nil {
 		ntfyPriority = 0
@@ -126,5 +129,6 @@ func NewFeedForm(r *http.Request) *FeedForm {
 		NtfyTopic:                   r.FormValue("ntfy_topic"),
 		PushoverEnabled:             r.FormValue("pushover_enabled") == "1",
 		PushoverPriority:            pushoverPriority,
+		ProxyURL:                    r.FormValue("proxy_url"),
 	}
 }

+ 6 - 0
internal/ui/form/subscription.go

@@ -28,6 +28,7 @@ type SubscriptionForm struct {
 	KeeplistRules               string
 	UrlRewriteRules             string
 	DisableHTTP2                bool
+	ProxyURL                    string
 }
 
 // Validate makes sure the form values locale.are valid.
@@ -52,6 +53,10 @@ func (s *SubscriptionForm) Validate() *locale.LocalizedError {
 		return locale.NewLocalizedError("error.feed_invalid_urlrewrite_rule")
 	}
 
+	if s.ProxyURL != "" && !validator.IsValidURL(s.ProxyURL) {
+		return locale.NewLocalizedError("error.invalid_feed_proxy_url")
+	}
+
 	return nil
 }
 
@@ -78,5 +83,6 @@ func NewSubscriptionForm(r *http.Request) *SubscriptionForm {
 		KeeplistRules:               r.FormValue("keeplist_rules"),
 		UrlRewriteRules:             r.FormValue("urlrewrite_rules"),
 		DisableHTTP2:                r.FormValue("disable_http2") == "1",
+		ProxyURL:                    r.FormValue("proxy_url"),
 	}
 }

+ 0 - 1
internal/ui/opml_upload.go

@@ -97,7 +97,6 @@ func (h *handler) fetchOPML(w http.ResponseWriter, r *http.Request) {
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
-	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(opmlFileURL))
 	defer responseHandler.Close()

+ 1 - 0
internal/ui/subscription_choose.go

@@ -63,6 +63,7 @@ func (h *handler) showChooseSubscriptionPage(w http.ResponseWriter, r *http.Requ
 		UrlRewriteRules:             subscriptionForm.UrlRewriteRules,
 		FetchViaProxy:               subscriptionForm.FetchViaProxy,
 		DisableHTTP2:                subscriptionForm.DisableHTTP2,
+		ProxyURL:                    subscriptionForm.ProxyURL,
 	})
 	if localizedError != nil {
 		view.Set("form", subscriptionForm)

+ 3 - 0
internal/ui/subscription_submit.go

@@ -60,6 +60,7 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) {
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
 	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(subscriptionForm.ProxyURL)
 	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
 	requestBuilder.UseCustomApplicationProxyURL(subscriptionForm.FetchViaProxy)
 	requestBuilder.WithUserAgent(subscriptionForm.UserAgent, config.Opts.HTTPClientUserAgent())
@@ -107,6 +108,7 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) {
 				UrlRewriteRules:             subscriptionForm.UrlRewriteRules,
 				FetchViaProxy:               subscriptionForm.FetchViaProxy,
 				DisableHTTP2:                subscriptionForm.DisableHTTP2,
+				ProxyURL:                    subscriptionForm.ProxyURL,
 			},
 		})
 		if localizedError != nil {
@@ -134,6 +136,7 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) {
 			UrlRewriteRules:             subscriptionForm.UrlRewriteRules,
 			FetchViaProxy:               subscriptionForm.FetchViaProxy,
 			DisableHTTP2:                subscriptionForm.DisableHTTP2,
+			ProxyURL:                    subscriptionForm.ProxyURL,
 		})
 		if localizedError != nil {
 			v.Set("form", subscriptionForm)

+ 14 - 0
internal/validator/feed.go

@@ -35,6 +35,10 @@ func ValidateFeedCreation(store *storage.Storage, userID int64, request *model.F
 		return locale.NewLocalizedError("error.feed_invalid_keeplist_rule")
 	}
 
+	if request.ProxyURL != "" && !IsValidURL(request.ProxyURL) {
+		return locale.NewLocalizedError("error.invalid_feed_proxy_url")
+	}
+
 	return nil
 }
 
@@ -88,5 +92,15 @@ func ValidateFeedModification(store *storage.Storage, userID, feedID int64, requ
 		}
 	}
 
+	if request.ProxyURL != nil {
+		if *request.ProxyURL == "" {
+			return locale.NewLocalizedError("error.proxy_url_not_empty")
+		}
+
+		if !IsValidURL(*request.ProxyURL) {
+			return locale.NewLocalizedError("error.invalid_feed_proxy_url")
+		}
+	}
+
 	return nil
 }

+ 4 - 0
internal/validator/subscription.go

@@ -14,5 +14,9 @@ func ValidateSubscriptionDiscovery(request *model.SubscriptionDiscoveryRequest)
 		return locale.NewLocalizedError("error.invalid_site_url")
 	}
 
+	if request.ProxyURL != "" && !IsValidURL(request.ProxyURL) {
+		return locale.NewLocalizedError("error.invalid_proxy_url")
+	}
+
 	return nil
 }