Repository: miniflux/v2

Issue:
[Feature]: Could the /shares route be linked in the navbar?
### Feature Summary

Link the /shares route in, e.g., the main navbar next to starred, history, feeds, &c.

### What problem does this feature solve?

It doesn't look like this potentially useful route is linked anywhere - I found it only by looking at the source. (I was looking at the source to try to hack in some method of sharing that doesn't require authentication, and this would benefit from being able to see and manage all shares in a central location. The /shares route looks like a great place for that!)

### Proposed Solution

Put a link to /shares in the main navbar with "Unread", "Starred", &c.

### Alternatives Considered

Maybe, considering that this is less important and presumably less frequently accessed than the other links, it can be put in a less prominent place (though I don't know where that would be).

The other alternative, is of course, to do nothing. My only two concerns are that 1) Why does this route exist at all if we don't mean users to find it? and 2) It would be more difficult to expose future features like unauthenticated sharing to the UI.

### Additional Context

Thanks again for the hard work on miniflux; it's really solid software :)

### Checklist

- [x] I have searched existing issues to ensure this feature hasn't been requested before.
- [x] I understand that feature requests are not guaranteed to be implemented.

Hints from issue comments:
@atalii: It looks like I shared the wrong link and was therefore under the impression that shares weren't public. I would still appreciate a link to /shares, but the context isn't actually relevant. Sorry!@guest20: On [2.2.8](https://github.com/miniflux/v2/releases/tag/2.2.8)(And maybe earlier) you can find `/shares` linked under `History`

![Miniflux navigation under History, showing Flush history and Shared Entries as the nav items](https://github.com/user-attachments/assets/1442b63f-c490-450a-a66a-549af49d2a14)

Solution patch:
diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml
new file mode 100644
index 00000000..2837f835
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/documentation.yml
@@ -0,0 +1,81 @@
+name: "Documentation Issue"
+description: "Report issues or suggest improvements for the documentation"
+title: "[Docs]: "
+type: "Documentation"
+labels: ["triage needed"]
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for helping improve the Miniflux documentation! Clear and accurate documentation helps everyone.
+
+  - type: dropdown
+    id: issue_type
+    attributes:
+      label: "Documentation Issue Type"
+      description: "What kind of documentation issue are you reporting?"
+      options:
+        - "Missing Information"
+        - "Incorrect Information"
+        - "Outdated Information"
+        - "Unclear Explanation"
+        - "Formatting/Structural Issue"
+        - "Typo/Grammar Error"
+        - "Documentation Request"
+        - "Other"
+    validations:
+      required: true
+
+  - type: input
+    id: summary
+    attributes:
+      label: "Summary"
+      description: "Briefly describe the documentation issue."
+      placeholder: "e.g., The API authentication section is outdated"
+    validations:
+      required: true
+
+  - type: input
+    id: location
+    attributes:
+      label: "Location"
+      description: "Where is the documentation you're referring to? Provide URLs, file paths, or section names."
+      placeholder: "e.g., README.md, docs/api.md, Installation section of the website"
+    validations:
+      required: true
+
+  - type: textarea
+    id: description
+    attributes:
+      label: "Detailed Description"
+      description: "Provide a detailed description of the issue or improvement."
+      placeholder: "e.g., The API authentication section doesn't mention the new token-based authentication method introduced in version 2.0.5."
+    validations:
+      required: true
+
+  - type: textarea
+    id: current_content
+    attributes:
+      label: "Current Content (if applicable)"
+      description: "What does the current documentation say?"
+      placeholder: "Paste the current documentation text here."
+    validations:
+      required: false
+
+  - type: textarea
+    id: suggested_content
+    attributes:
+      label: "Suggested Changes"
+      description: "If you have specific suggestions for how to improve the documentation, please provide them here."
+      placeholder: "e.g., Add a new section about token-based authentication with these details..."
+    validations:
+      required: false
+
+  - type: input
+    id: version
+    attributes:
+      label: "Version"
+      description: "Which version of Miniflux does this documentation issue relate to?"
+      placeholder: "e.g., 2.2.6, or 'all versions'"
+    validations:
+      required: false
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index 1568b86f..c9e2ffce 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -39,19 +39,16 @@ jobs:
         run: gofmt -d -e .
 
   commitlint:
+    if: github.event_name == 'pull_request'
     name: Commit Linter
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0
-      - name: Setup Node.js
-        uses: actions/setup-node@v4
+      - name: Set up Python
+        uses: actions/setup-python@v5
         with:
-          node-version: "lts/*"
-      - name: Install commitlint
-        run: |
-          npm install --save-dev @commitlint/config-conventional @commitlint/cli
-          echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
+          python-version: '3.13'
       - name: Validate PR commits
-        run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
+        run: python3 .github/workflows/scripts/commit-checker.py --base ${{ github.event.pull_request.base.sha }} --head ${{ github.event.pull_request.head.sha }}
diff --git a/.github/workflows/scripts/commit-checker.py b/.github/workflows/scripts/commit-checker.py
new file mode 100644
index 00000000..de209548
--- /dev/null
+++ b/.github/workflows/scripts/commit-checker.py
@@ -0,0 +1,93 @@
+import subprocess
+import re
+import sys
+import argparse
+from typing import Match
+
+# Conventional commit pattern
+CONVENTIONAL_COMMIT_PATTERN: str = r"^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9-]+\))?!?: .{1,100}"
+
+
+def get_commit_message(commit_hash: str) -> str:
+    """Get the commit message for a given commit hash."""
+    try:
+        result: subprocess.CompletedProcess = subprocess.run(
+            ["git", "show", "-s", "--format=%B", commit_hash],
+            capture_output=True,
+            text=True,
+            check=True,
+        )
+        return result.stdout.strip()
+    except subprocess.CalledProcessError as e:
+        print(f"Error retrieving commit message: {e}")
+        sys.exit(1)
+
+
+def check_commit_message(
+    message: str, pattern: str = CONVENTIONAL_COMMIT_PATTERN
+) -> bool:
+    """Check if commit message follows conventional commit format."""
+    first_line: str = message.split("\n")[0]
+    match: Match[str] | None = re.match(pattern, first_line)
+    return bool(match)
+
+
+def check_commit_range(base_ref: str, head_ref: str) -> list[dict[str, str]]:
+    """Check all commits in a range for compliance."""
+    try:
+        result: subprocess.CompletedProcess = subprocess.run(
+            ["git", "log", "--format=%H", f"{base_ref}..{head_ref}"],
+            capture_output=True,
+            text=True,
+            check=True,
+        )
+        commit_hashes: list[str] = result.stdout.strip().split("\n")
+
+        # Filter out empty lines
+        commit_hashes = [hash for hash in commit_hashes if hash]
+
+        non_compliant: list[dict[str, str]] = []
+        for commit_hash in commit_hashes:
+            message: str = get_commit_message(commit_hash)
+            if not check_commit_message(message):
+                non_compliant.append(
+                    {"hash": commit_hash, "message": message.split("\n")[0]}
+                )
+
+        return non_compliant
+    except subprocess.CalledProcessError as e:
+        print(f"Error checking commit range: {e}")
+        sys.exit(1)
+
+
+def main() -> None:
+    parser: argparse.ArgumentParser = argparse.ArgumentParser(
+        description="Check conventional commit compliance"
+    )
+    parser.add_argument(
+        "--base", required=True, help="Base ref (starting commit, exclusive)"
+    )
+    parser.add_argument(
+        "--head", required=True, help="Head ref (ending commit, inclusive)"
+    )
+    args: argparse.Namespace = parser.parse_args()
+
+    non_compliant: list[dict[str, str]] = check_commit_range(args.base, args.head)
+
+    if non_compliant:
+        print("The following commits do not follow the conventional commit format:")
+        for commit in non_compliant:
+            print(f"- {commit['hash'][:8]}: {commit['message']}")
+        print("\nPlease ensure your commit messages follow the format:")
+        print("type(scope): subject")
+        print(
+            "\nWhere type is one of: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test"
+        )
+        sys.exit(1)
+    else:
+        print("All commits follow the conventional commit format!")
+        sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/ChangeLog b/ChangeLog
index 8a820bb3..8e891475 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,47 @@
+Version 2.2.7 (April 1, 2025)
+-----------------------------
+
+* test(api): update base URL after upgrading Hugo
+* refactor(rewrite): reorganize referer rules and remove obsolete mappings
+* refactor: combine feed icon handlers to use only `externalIconID`
+* fix(ui): update share feature to correctly select the title element and handle empty titles
+* fix(ui): update entry tags display logic to show links based on user authentication
+* fix(ui): remove touch-action style to prevent horizontal scrolling issues
+* fix(ui): log a warning for an empty client secret
+* fix(ui): change labels from "Read / Unread" to "Mark as Read"
+* fix(ui): avoid 500 errors and NaN when marking a deleted entry as read
+* fix(subscription): add `/rss/feed.xml` to the list of known feed URLs
+* fix(security): use a more restrictive CSP for untrusted content
+* fix(rewrite): remove obsolete rule for `webtoons.com`
+* fix(processor): add missing quotation marks to import comments
+* fix(googlereader): return enclosures in the `streamItemContentsHandler` response
+* fix: address minor issues detected by Go linters
+* feat(urlcleaner): add more Google Analytics parameters
+* feat(storage): reduce the number of SQL queries when fetching entry enclosures
+* feat(sanitizer): allow the `<u>` tag in feeds
+* feat(sanitizer): allow the `<b>` tag
+* feat(locale): update Polish translation
+* feat(locale): add Romanian translation
+* feat(integrations/ntfy): make ntfy topics configurable per feed
+* feat(googlereader): add a feed icon endpoint
+* feat: show database size on the about page
+* feat: add a `make add string` command to add new localized strings
+* docs: update README
+* docs: update client README to remove references to deprecated functions
+* ci: replace GitHub Issue Markdown templates with YAML forms
+* build(deps): bump `golangci/golangci-lint-action` from `6` to `7`
+* build(deps): bump `golang.org/x/term` from `0.29.0` to `0.30.0`
+* build(deps): bump `golang.org/x/oauth2` from `0.26.0` to` 0.28.0`
+* build(deps): bump `golang.org/x/net` from `0.35.0` to `0.38.0`
+* build(deps): bump `golang.org/x/image` from `0.24.0` to `0.25.0`
+* build(deps): bump `golang.org/x/crypto` from `0.33.0` to `0.36.0`
+* build(deps): bump `github.com/tdewolff/minify/v2` from `2.21.3` to `2.22.4`
+* build(deps): bump `github.com/prometheus/client_golang`
+* build(deps): bump `github.com/golang-jwt/jwt/v5` from `5.2.1` to `5.2.2`
+* build(deps): bump `github.com/go-webauthn/webauthn` from `0.11.2` to `0.12.2`
+* build(deps): bump `github.com/go-jose/go-jose/v4` from `4.0.2` to `4.0.5`
+* build(deps): bump `github.com/coreos/go-oidc/v3` from `3.12.0` to `3.13.0`
+
 Version 2.2.6 (February 22, 2025)
 ---------------------------------
 
diff --git a/client/model.go b/client/model.go
index 0a6cd4d1..8f257e6a 100644
--- a/client/model.go
+++ b/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.
diff --git a/go.mod b/go.mod
index a46e4abf..317bb1af 100644
--- a/go.mod
+++ b/go.mod
@@ -3,24 +3,24 @@ module miniflux.app/v2
 // +heroku goVersion go1.23
 
 require (
-	github.com/PuerkitoBio/goquery v1.10.2
+	github.com/PuerkitoBio/goquery v1.10.3
 	github.com/andybalholm/brotli v1.1.1
-	github.com/coreos/go-oidc/v3 v3.13.0
-	github.com/go-webauthn/webauthn v0.12.2
+	github.com/coreos/go-oidc/v3 v3.14.1
+	github.com/go-webauthn/webauthn v0.12.3
 	github.com/gorilla/mux v1.8.1
 	github.com/lib/pq v1.10.9
-	github.com/mattn/go-sqlite3 v1.14.24
-	github.com/prometheus/client_golang v1.21.1
-	github.com/tdewolff/minify/v2 v2.22.4
-	golang.org/x/crypto v0.36.0
-	golang.org/x/image v0.25.0
-	golang.org/x/net v0.38.0
-	golang.org/x/oauth2 v0.28.0
-	golang.org/x/term v0.30.0
+	github.com/mattn/go-sqlite3 v1.14.28
+	github.com/prometheus/client_golang v1.22.0
+	github.com/tdewolff/minify/v2 v2.23.1
+	golang.org/x/crypto v0.37.0
+	golang.org/x/image v0.26.0
+	golang.org/x/net v0.39.0
+	golang.org/x/oauth2 v0.29.0
+	golang.org/x/term v0.31.0
 )
 
 require (
-	github.com/go-webauthn/x v0.1.19 // indirect
+	github.com/go-webauthn/x v0.1.20 // indirect
 	github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
 	github.com/google/go-tpm v0.9.3 // indirect
 )
@@ -29,20 +29,19 @@ require (
 	github.com/andybalholm/cascadia v1.3.3 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
-	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
+	github.com/fxamacker/cbor/v2 v2.8.0 // indirect
 	github.com/go-jose/go-jose/v4 v4.0.5 // indirect
 	github.com/google/uuid v1.6.0 // indirect
-	github.com/klauspost/compress v1.17.11 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/prometheus/client_model v0.6.1 // indirect
 	github.com/prometheus/common v0.62.0 // indirect
 	github.com/prometheus/procfs v0.15.1 // indirect
-	github.com/tdewolff/parse/v2 v2.7.21 // indirect
+	github.com/tdewolff/parse/v2 v2.7.23 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
-	golang.org/x/sys v0.31.0 // indirect
-	golang.org/x/text v0.23.0 // indirect
-	google.golang.org/protobuf v1.36.1 // indirect
+	golang.org/x/sys v0.32.0 // indirect
+	golang.org/x/text v0.24.0 // indirect
+	google.golang.org/protobuf v1.36.5 // indirect
 )
 
 go 1.23.0
diff --git a/go.sum b/go.sum
index c311cf70..cb4ab9b5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
-github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
+github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
+github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
 github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
 github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
 github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
@@ -8,44 +8,45 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8=
-github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
+github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
+github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
-github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
+github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
+github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
 github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
 github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
-github.com/go-webauthn/webauthn v0.12.2 h1:yLaNPgBUEXDQtWnOjhsGhMMCEWbXwjg/aNkC8riJQI8=
-github.com/go-webauthn/webauthn v0.12.2/go.mod h1:Q8SZPPj4sZ469fNTcQXxRpzJOdb30jQrn/36FX8jilA=
-github.com/go-webauthn/x v0.1.19 h1:IUfdHiBRoTdujpBA/14qbrMXQ3LGzYe/PRGWdZcmudg=
-github.com/go-webauthn/x v0.1.19/go.mod h1:C5arLuTQ3pVHKPw89v7CDGnqAZSZJj+4Jnr40dsn7tk=
+github.com/go-webauthn/webauthn v0.12.3 h1:hHQl1xkUuabUU9uS+ISNCMLs9z50p9mDUZI/FmkayNE=
+github.com/go-webauthn/webauthn v0.12.3/go.mod h1:4JRe8Z3W7HIw8NGEWn2fnUwecoDzkkeach/NnvhkqGY=
+github.com/go-webauthn/x v0.1.20 h1:brEBDqfiPtNNCdS/peu8gARtq8fIPsHz0VzpPjGvgiw=
+github.com/go-webauthn/x v0.1.20/go.mod h1:n/gAc8ssZJGATM0qThE+W+vfgXiMedsWi3wf/C4lld0=
 github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
 github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
 github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
 github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
-github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
-github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
-github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
+github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
-github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
 github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
 github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
 github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
@@ -54,10 +55,10 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
 github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/tdewolff/minify/v2 v2.22.4 h1:0/8K2fheOuYr5B4e5oCE1hGBVX6DQHLP0EGzdsDlYeg=
-github.com/tdewolff/minify/v2 v2.22.4/go.mod h1:K/R8TT7aivpcU8QCNUU1UdR6etfnFPr7L11TO/X7shk=
-github.com/tdewolff/parse/v2 v2.7.21 h1:OCuPFtGr4mXdnfKikQlUb0n654ROJANhBqCk+wioJ/A=
-github.com/tdewolff/parse/v2 v2.7.21/go.mod h1:I7TXO37t3aSG9SlPUBefAhgIF8nt7yYUwVGgETIoBcA=
+github.com/tdewolff/minify/v2 v2.23.1 h1:r6sKQrumHzskWZRdhiRa+pZhn7CdBMojACNP9fuKpXQ=
+github.com/tdewolff/minify/v2 v2.23.1/go.mod h1:RkUGjklq6uIsBoOdzY3ll35HKKQ2aFqLQhnanBHhDyU=
+github.com/tdewolff/parse/v2 v2.7.23 h1:sCW2PNTCM1yVldh5YK/8wrpRI9rSbloUZWjAydlN2IA=
+github.com/tdewolff/parse/v2 v2.7.23/go.mod h1:I7TXO37t3aSG9SlPUBefAhgIF8nt7yYUwVGgETIoBcA=
 github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
 github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
@@ -71,10 +72,10 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
-golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
-golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
+golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
+golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
+golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
+golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -89,10 +90,10 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
-golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
-golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
+golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
+golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -111,8 +112,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
+golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -122,8 +123,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
-golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
-golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
+golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -133,8 +134,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
+golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -142,7 +143,7 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
-google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
+google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/api/entry.go b/internal/api/entry.go
index 508bbee4..dd4c7fbb 100644
--- a/internal/api/entry.go
+++ b/internal/api/entry.go
@@ -331,6 +331,18 @@ func (h *handler) fetchContent(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	shouldUpdateContent := request.QueryBoolParam(r, "update_content", false)
+	if shouldUpdateContent {
+		if err := h.store.UpdateEntryTitleAndContent(entry); err != nil {
+			json.ServerError(w, r, err)
+			return
+		}
+
+		json.OK(w, r, map[string]interface{}{"content": mediaproxy.RewriteDocumentWithRelativeProxyURL(h.router, entry.Content), "reading_time": entry.ReadingTime})
+
+		return
+	}
+
 	json.OK(w, r, map[string]string{"content": entry.Content})
 }
 
diff --git a/internal/api/subscription.go b/internal/api/subscription.go
index 37e68ac0..63eeba73 100644
--- a/internal/api/subscription.go
+++ b/internal/api/subscription.go
@@ -11,6 +11,7 @@ import (
 	"miniflux.app/v2/internal/http/request"
 	"miniflux.app/v2/internal/http/response/json"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 	"miniflux.app/v2/internal/reader/subscription"
 	"miniflux.app/v2/internal/validator"
@@ -36,11 +37,13 @@ func (h *handler) discoverSubscriptions(w http.ResponseWriter, r *http.Request)
 
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(subscriptionDiscoveryRequest.ProxyURL)
+	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
+	requestBuilder.UseCustomApplicationProxyURL(subscriptionDiscoveryRequest.FetchViaProxy)
 	requestBuilder.WithUserAgent(subscriptionDiscoveryRequest.UserAgent, config.Opts.HTTPClientUserAgent())
 	requestBuilder.WithCookie(subscriptionDiscoveryRequest.Cookie)
 	requestBuilder.WithUsernameAndPassword(subscriptionDiscoveryRequest.Username, subscriptionDiscoveryRequest.Password)
-	requestBuilder.UseProxy(subscriptionDiscoveryRequest.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(subscriptionDiscoveryRequest.AllowSelfSignedCertificates)
 	requestBuilder.DisableHTTP2(subscriptionDiscoveryRequest.DisableHTTP2)
 
diff --git a/internal/cli/cli.go b/internal/cli/cli.go
index fc074717..64d660d6 100644
--- a/internal/cli/cli.go
+++ b/internal/cli/cli.go
@@ -13,46 +13,49 @@ import (
 
 	"miniflux.app/v2/internal/config"
 	"miniflux.app/v2/internal/database"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/storage"
 	"miniflux.app/v2/internal/ui/static"
 	"miniflux.app/v2/internal/version"
 )
 
 const (
-	flagInfoHelp            = "Show build information"
-	flagVersionHelp         = "Show application version"
-	flagMigrateHelp         = "Run SQL migrations"
-	flagFlushSessionsHelp   = "Flush all sessions (disconnect users)"
-	flagCreateAdminHelp     = "Create an admin user from an interactive terminal"
-	flagResetPasswordHelp   = "Reset user password"
-	flagResetFeedErrorsHelp = "Clear all feed errors for all users"
-	flagDebugModeHelp       = "Show debug logs"
-	flagConfigFileHelp      = "Load configuration file"
-	flagConfigDumpHelp      = "Print parsed configuration values"
-	flagHealthCheckHelp     = `Perform a health check on the given endpoint (the value "auto" try to guess the health check endpoint).`
-	flagRefreshFeedsHelp    = "Refresh a batch of feeds and exit"
-	flagRunCleanupTasksHelp = "Run cleanup tasks (delete old sessions and archives old entries)"
-	flagExportUserFeedsHelp = "Export user feeds (provide the username as argument)"
+	flagInfoHelp             = "Show build information"
+	flagVersionHelp          = "Show application version"
+	flagMigrateHelp          = "Run SQL migrations"
+	flagFlushSessionsHelp    = "Flush all sessions (disconnect users)"
+	flagCreateAdminHelp      = "Create an admin user from an interactive terminal"
+	flagResetPasswordHelp    = "Reset user password"
+	flagResetFeedErrorsHelp  = "Clear all feed errors for all users"
+	flagDebugModeHelp        = "Show debug logs"
+	flagConfigFileHelp       = "Load configuration file"
+	flagConfigDumpHelp       = "Print parsed configuration values"
+	flagHealthCheckHelp      = `Perform a health check on the given endpoint (the value "auto" try to guess the health check endpoint).`
+	flagRefreshFeedsHelp     = "Refresh a batch of feeds and exit"
+	flagRunCleanupTasksHelp  = "Run cleanup tasks (delete old sessions and archives old entries)"
+	flagExportUserFeedsHelp  = "Export user feeds (provide the username as argument)"
+	flagResetNextCheckAtHelp = "Reset the next check time for all feeds"
 )
 
 // Parse parses command line arguments.
 func Parse() {
 	var (
-		err                 error
-		flagInfo            bool
-		flagVersion         bool
-		flagMigrate         bool
-		flagFlushSessions   bool
-		flagCreateAdmin     bool
-		flagResetPassword   bool
-		flagResetFeedErrors bool
-		flagDebugMode       bool
-		flagConfigFile      string
-		flagConfigDump      bool
-		flagHealthCheck     string
-		flagRefreshFeeds    bool
-		flagRunCleanupTasks bool
-		flagExportUserFeeds string
+		err                      error
+		flagInfo                 bool
+		flagVersion              bool
+		flagMigrate              bool
+		flagFlushSessions        bool
+		flagCreateAdmin          bool
+		flagResetPassword        bool
+		flagResetFeedErrors      bool
+		flagResetFeedNextCheckAt bool
+		flagDebugMode            bool
+		flagConfigFile           string
+		flagConfigDump           bool
+		flagHealthCheck          string
+		flagRefreshFeeds         bool
+		flagRunCleanupTasks      bool
+		flagExportUserFeeds      string
 	)
 
 	flag.BoolVar(&flagInfo, "info", false, flagInfoHelp)
@@ -64,6 +67,7 @@ func Parse() {
 	flag.BoolVar(&flagCreateAdmin, "create-admin", false, flagCreateAdminHelp)
 	flag.BoolVar(&flagResetPassword, "reset-password", false, flagResetPasswordHelp)
 	flag.BoolVar(&flagResetFeedErrors, "reset-feed-errors", false, flagResetFeedErrorsHelp)
+	flag.BoolVar(&flagResetFeedNextCheckAt, "reset-feed-next-check-at", false, flagResetNextCheckAtHelp)
 	flag.BoolVar(&flagDebugMode, "debug", false, flagDebugModeHelp)
 	flag.StringVar(&flagConfigFile, "config-file", "", flagConfigFileHelp)
 	flag.StringVar(&flagConfigFile, "c", "", flagConfigFileHelp)
@@ -189,7 +193,16 @@ func Parse() {
 	}
 
 	if flagResetFeedErrors {
-		store.ResetFeedErrors()
+		if err := store.ResetFeedErrors(); err != nil {
+			printErrorAndExit(err)
+		}
+		return
+	}
+
+	if flagResetFeedNextCheckAt {
+		if err := store.ResetNextCheckAt(); err != nil {
+			printErrorAndExit(err)
+		}
 		return
 	}
 
@@ -228,6 +241,14 @@ func Parse() {
 		createAdminUserFromEnvironmentVariables(store)
 	}
 
+	if config.Opts.HasHTTPClientProxiesConfigured() {
+		slog.Info("Initializing proxy rotation", slog.Int("proxies_count", len(config.Opts.HTTPClientProxies())))
+		proxyrotator.ProxyRotatorInstance, err = proxyrotator.NewProxyRotator(config.Opts.HTTPClientProxies())
+		if err != nil {
+			printErrorAndExit(fmt.Errorf("unable to initialize proxy rotator: %v", err))
+		}
+	}
+
 	if flagRefreshFeeds {
 		refreshFeeds(store)
 		return
diff --git a/internal/config/options.go b/internal/config/options.go
index 015a725e..9605dfe3 100644
--- a/internal/config/options.go
+++ b/internal/config/options.go
@@ -5,6 +5,7 @@ package config // import "miniflux.app/v2/internal/config"
 
 import (
 	"fmt"
+	"net/url"
 	"sort"
 	"strings"
 	"time"
@@ -36,6 +37,7 @@ const (
 	defaultSchedulerEntryFrequencyMaxInterval = 24 * 60
 	defaultSchedulerEntryFrequencyFactor      = 1
 	defaultSchedulerRoundRobinMinInterval     = 60
+	defaultSchedulerRoundRobinMaxInterval     = 1440
 	defaultPollingParsingErrorLimit           = 3
 	defaultRunMigrations                      = false
 	defaultDatabaseURL                        = "user=postgres password=postgres dbname=miniflux2 sslmode=disable"
@@ -136,6 +138,7 @@ type Options struct {
 	schedulerEntryFrequencyMaxInterval int
 	schedulerEntryFrequencyFactor      int
 	schedulerRoundRobinMinInterval     int
+	schedulerRoundRobinMaxInterval     int
 	pollingParsingErrorLimit           int
 	workerPoolSize                     int
 	createAdmin                        bool
@@ -163,7 +166,8 @@ type Options struct {
 	pocketConsumerKey                  string
 	httpClientTimeout                  int
 	httpClientMaxBodySize              int64
-	httpClientProxy                    string
+	httpClientProxyURL                 *url.URL
+	httpClientProxies                  []string
 	httpClientUserAgent                string
 	httpServerTimeout                  int
 	authProxyHeader                    string
@@ -218,6 +222,7 @@ func NewOptions() *Options {
 		schedulerEntryFrequencyMaxInterval: defaultSchedulerEntryFrequencyMaxInterval,
 		schedulerEntryFrequencyFactor:      defaultSchedulerEntryFrequencyFactor,
 		schedulerRoundRobinMinInterval:     defaultSchedulerRoundRobinMinInterval,
+		schedulerRoundRobinMaxInterval:     defaultSchedulerRoundRobinMaxInterval,
 		pollingParsingErrorLimit:           defaultPollingParsingErrorLimit,
 		workerPoolSize:                     defaultWorkerPoolSize,
 		createAdmin:                        defaultCreateAdmin,
@@ -243,7 +248,8 @@ func NewOptions() *Options {
 		pocketConsumerKey:                  defaultPocketConsumerKey,
 		httpClientTimeout:                  defaultHTTPClientTimeout,
 		httpClientMaxBodySize:              defaultHTTPClientMaxBodySize * 1024 * 1024,
-		httpClientProxy:                    defaultHTTPClientProxy,
+		httpClientProxyURL:                 nil,
+		httpClientProxies:                  []string{},
 		httpClientUserAgent:                defaultHTTPClientUserAgent,
 		httpServerTimeout:                  defaultHTTPServerTimeout,
 		authProxyHeader:                    defaultAuthProxyHeader,
@@ -430,6 +436,10 @@ func (o *Options) SchedulerRoundRobinMinInterval() int {
 	return o.schedulerRoundRobinMinInterval
 }
 
+func (o *Options) SchedulerRoundRobinMaxInterval() int {
+	return o.schedulerRoundRobinMaxInterval
+}
+
 // PollingParsingErrorLimit returns the limit of errors when to stop polling.
 func (o *Options) PollingParsingErrorLimit() int {
 	return o.pollingParsingErrorLimit
@@ -587,9 +597,24 @@ func (o *Options) HTTPClientMaxBodySize() int64 {
 	return o.httpClientMaxBodySize
 }
 
-// HTTPClientProxy returns the proxy URL for HTTP client.
-func (o *Options) HTTPClientProxy() string {
-	return o.httpClientProxy
+// HTTPClientProxyURL returns the client HTTP proxy URL if configured.
+func (o *Options) HTTPClientProxyURL() *url.URL {
+	return o.httpClientProxyURL
+}
+
+// HasHTTPClientProxyURLConfigured returns true if the client HTTP proxy URL if configured.
+func (o *Options) HasHTTPClientProxyURLConfigured() bool {
+	return o.httpClientProxyURL != nil
+}
+
+// HTTPClientProxies returns the list of proxies.
+func (o *Options) HTTPClientProxies() []string {
+	return o.httpClientProxies
+}
+
+// HTTPClientProxiesString returns true if the list of rotating proxies are configured.
+func (o *Options) HasHTTPClientProxiesConfigured() bool {
+	return len(o.httpClientProxies) > 0
 }
 
 // HTTPServerTimeout returns the time limit in seconds before the HTTP server cancel the request.
@@ -597,11 +622,6 @@ func (o *Options) HTTPServerTimeout() int {
 	return o.httpServerTimeout
 }
 
-// HasHTTPClientProxyConfigured returns true if the HTTP proxy is configured.
-func (o *Options) HasHTTPClientProxyConfigured() bool {
-	return o.httpClientProxy != ""
-}
-
 // AuthProxyHeader returns an HTTP header name that contains username for
 // authentication using auth proxy.
 func (o *Options) AuthProxyHeader() string {
@@ -664,6 +684,33 @@ func (o *Options) FilterEntryMaxAgeDays() int {
 
 // SortedOptions returns options as a list of key value pairs, sorted by keys.
 func (o *Options) SortedOptions(redactSecret bool) []*Option {
+	var clientProxyURLRedacted string
+	if o.httpClientProxyURL != nil {
+		if redactSecret {
+			clientProxyURLRedacted = o.httpClientProxyURL.Redacted()
+		} else {
+			clientProxyURLRedacted = o.httpClientProxyURL.String()
+		}
+	}
+
+	var clientProxyURLsRedacted string
+	if len(o.httpClientProxies) > 0 {
+		if redactSecret {
+			var proxyURLs []string
+			for range o.httpClientProxies {
+				proxyURLs = append(proxyURLs, "<redacted>")
+			}
+			clientProxyURLsRedacted = strings.Join(proxyURLs, ",")
+		} else {
+			clientProxyURLsRedacted = strings.Join(o.httpClientProxies, ",")
+		}
+	}
+
+	var mediaProxyPrivateKeyValue string
+	if len(o.mediaProxyPrivateKey) > 0 {
+		mediaProxyPrivateKeyValue = "<binary-data>"
+	}
+
 	var keyValues = map[string]interface{}{
 		"ADMIN_PASSWORD":                         redactSecretValue(o.adminPassword, redactSecret),
 		"ADMIN_USERNAME":                         o.adminUsername,
@@ -694,7 +741,8 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
 		"FETCH_BILIBILI_WATCH_TIME":              o.fetchBilibiliWatchTime,
 		"HTTPS":                                  o.HTTPS,
 		"HTTP_CLIENT_MAX_BODY_SIZE":              o.httpClientMaxBodySize,
-		"HTTP_CLIENT_PROXY":                      o.httpClientProxy,
+		"HTTP_CLIENT_PROXIES":                    clientProxyURLsRedacted,
+		"HTTP_CLIENT_PROXY":                      clientProxyURLRedacted,
 		"HTTP_CLIENT_TIMEOUT":                    o.httpClientTimeout,
 		"HTTP_CLIENT_USER_AGENT":                 o.httpClientUserAgent,
 		"HTTP_SERVER_TIMEOUT":                    o.httpServerTimeout,
@@ -729,7 +777,7 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
 		"MEDIA_PROXY_HTTP_CLIENT_TIMEOUT":        o.mediaProxyHTTPClientTimeout,
 		"MEDIA_PROXY_RESOURCE_TYPES":             o.mediaProxyResourceTypes,
 		"MEDIA_PROXY_MODE":                       o.mediaProxyMode,
-		"MEDIA_PROXY_PRIVATE_KEY":                redactSecretValue(string(o.mediaProxyPrivateKey), redactSecret),
+		"MEDIA_PROXY_PRIVATE_KEY":                mediaProxyPrivateKeyValue,
 		"MEDIA_PROXY_CUSTOM_URL":                 o.mediaProxyCustomURL,
 		"ROOT_URL":                               o.rootURL,
 		"RUN_MIGRATIONS":                         o.runMigrations,
@@ -737,6 +785,7 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
 		"SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL": o.schedulerEntryFrequencyMinInterval,
 		"SCHEDULER_ENTRY_FREQUENCY_FACTOR":       o.schedulerEntryFrequencyFactor,
 		"SCHEDULER_ROUND_ROBIN_MIN_INTERVAL":     o.schedulerRoundRobinMinInterval,
+		"SCHEDULER_ROUND_ROBIN_MAX_INTERVAL":     o.schedulerRoundRobinMaxInterval,
 		"SCHEDULER_SERVICE":                      o.schedulerService,
 		"SERVER_TIMING_HEADER":                   o.serverTimingHeader,
 		"WATCHDOG":                               o.watchdog,
diff --git a/internal/config/parser.go b/internal/config/parser.go
index a3b30190..29ef5018 100644
--- a/internal/config/parser.go
+++ b/internal/config/parser.go
@@ -160,6 +160,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
 			p.opts.schedulerEntryFrequencyFactor = parseInt(value, defaultSchedulerEntryFrequencyFactor)
 		case "SCHEDULER_ROUND_ROBIN_MIN_INTERVAL":
 			p.opts.schedulerRoundRobinMinInterval = parseInt(value, defaultSchedulerRoundRobinMinInterval)
+		case "SCHEDULER_ROUND_ROBIN_MAX_INTERVAL":
+			p.opts.schedulerRoundRobinMaxInterval = parseInt(value, defaultSchedulerRoundRobinMaxInterval)
 		case "POLLING_PARSING_ERROR_LIMIT":
 			p.opts.pollingParsingErrorLimit = parseInt(value, defaultPollingParsingErrorLimit)
 		case "PROXY_IMAGES":
@@ -240,7 +242,12 @@ func (p *Parser) parseLines(lines []string) (err error) {
 		case "HTTP_CLIENT_MAX_BODY_SIZE":
 			p.opts.httpClientMaxBodySize = int64(parseInt(value, defaultHTTPClientMaxBodySize) * 1024 * 1024)
 		case "HTTP_CLIENT_PROXY":
-			p.opts.httpClientProxy = parseString(value, defaultHTTPClientProxy)
+			p.opts.httpClientProxyURL, err = url.Parse(parseString(value, defaultHTTPClientProxy))
+			if err != nil {
+				return fmt.Errorf("config: invalid HTTP_CLIENT_PROXY value: %w", err)
+			}
+		case "HTTP_CLIENT_PROXIES":
+			p.opts.httpClientProxies = parseStringList(value, []string{})
 		case "HTTP_CLIENT_USER_AGENT":
 			p.opts.httpClientUserAgent = parseString(value, defaultHTTPClientUserAgent)
 		case "HTTP_SERVER_TIMEOUT":
diff --git a/internal/database/migrations.go b/internal/database/migrations.go
index 73da902c..4f553e9c 100644
--- a/internal/database/migrations.go
+++ b/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
+	},
 }
diff --git a/internal/googlereader/handler.go b/internal/googlereader/handler.go
index f4569380..c5e6e537 100644
--- a/internal/googlereader/handler.go
+++ b/internal/googlereader/handler.go
@@ -20,6 +20,7 @@ import (
 	"miniflux.app/v2/internal/integration"
 	"miniflux.app/v2/internal/mediaproxy"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 	mff "miniflux.app/v2/internal/reader/handler"
 	mfs "miniflux.app/v2/internal/reader/subscription"
@@ -683,7 +684,7 @@ func (h *handler) quickAddHandler(w http.ResponseWriter, r *http.Request) {
 
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
 
 	var rssBridgeURL string
 	if intg, err := h.store.Integration(userID); err == nil && intg != nil && intg.RSSBridgeEnabled {
diff --git a/internal/integration/telegrambot/telegrambot.go b/internal/integration/telegrambot/telegrambot.go
index e17e4b73..1c1c3c51 100644
--- a/internal/integration/telegrambot/telegrambot.go
+++ b/internal/integration/telegrambot/telegrambot.go
@@ -5,8 +5,12 @@ package telegrambot // import "miniflux.app/v2/internal/integration/telegrambot"
 
 import (
 	"fmt"
+	"log/slog"
+	"strconv"
 
+	"miniflux.app/v2/internal/config"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/urllib"
 )
 
 func PushEntry(feed *model.Feed, entry *model.Entry, botToken, chatID string, topicID *int64, disableWebPagePreview, disableNotification bool, disableButtons bool) error {
@@ -32,8 +36,16 @@ func PushEntry(feed *model.Feed, entry *model.Entry, botToken, chatID string, to
 	if !disableButtons {
 		var markupRow []*InlineKeyboardButton
 
-		websiteURLButton := InlineKeyboardButton{Text: "Go to website", URL: feed.SiteURL}
-		markupRow = append(markupRow, &websiteURLButton)
+		baseURL := config.Opts.BaseURL()
+		entryPath := "/unread/entry/" + strconv.FormatInt(entry.ID, 10)
+
+		minifluxEntryURL, err := urllib.JoinBaseURLAndPath(baseURL, entryPath)
+		if err != nil {
+			slog.Error("Unable to create Miniflux entry URL", slog.Any("error", err))
+		} else {
+			minifluxEntryURLButton := InlineKeyboardButton{Text: "Go to Miniflux", URL: minifluxEntryURL}
+			markupRow = append(markupRow, &minifluxEntryURLButton)
+		}
 
 		articleURLButton := InlineKeyboardButton{Text: "Go to article", URL: entry.URL}
 		markupRow = append(markupRow, &articleURLButton)
diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json
index 35b93443..214c987e 100644
--- a/internal/locale/translations/de_DE.json
+++ b/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,13 +189,14 @@
     "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.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",
-    "form.feed.label.pushover_low_priority": "Pushover low priority",
-    "form.feed.label.pushover_max_priority": "Pushover max priority",
-    "form.feed.label.pushover_min_priority": "Pushover min priority",
-    "form.feed.label.pushover_priority": "Pushover message priority",
+    "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",
+    "form.feed.label.pushover_low_priority": "Niedrige Pushoverpriorität",
+    "form.feed.label.pushover_max_priority": "Höchste Pushoverpriorität",
+    "form.feed.label.pushover_min_priority": "Niedrigste Pushoverpriorität",
+    "form.feed.label.pushover_priority": "Pushover-Nachrichtenpriorität",
     "form.feed.label.rewrite_rules": "Umschreiberegeln",
     "form.feed.label.scraper_rules": "Extraktionsregeln",
     "form.feed.label.site_url": "URL der Webseite",
@@ -272,11 +275,11 @@
     "form.integration.pocket_activate": "Einträge in Pocket speichern",
     "form.integration.pocket_connect_link": "Verbinden Sie Ihr Pocket-Konto",
     "form.integration.pocket_consumer_key": "Pocket-Verbraucher-Schlüssel",
-    "form.integration.pushover_activate": "Push entries to Pushover",
-    "form.integration.pushover_device": "Pushover device (optional)",
-    "form.integration.pushover_prefix": "Pushover URL prefix (optional)",
-    "form.integration.pushover_token": "Pushover application API token",
-    "form.integration.pushover_user": "Pushover user key",
+    "form.integration.pushover_activate": "Einträge an Pushover senden",
+    "form.integration.pushover_device": "Pushovergerät (optional)",
+    "form.integration.pushover_prefix": "Pushover-URL-Präfix (optional)",
+    "form.integration.pushover_token": "Pushover-Anwendungs-API-Token",
+    "form.integration.pushover_user": "Pushover-Benutzerschlüssel",
     "form.integration.raindrop_activate": "Einträge in Raindrop speichern",
     "form.integration.raindrop_collection_id": "Sammlungs-ID",
     "form.integration.raindrop_tags": "Tags (kommagetrennt)",
@@ -401,7 +404,7 @@
     "page.about.author": "Autor:",
     "page.about.build_date": "Datum der Kompilierung:",
     "page.about.credits": "Urheberrechte",
-    "page.about.db_usage": "Database size:",
+    "page.about.db_usage": "Datenbankgröße:",
     "page.about.global_config_options": "Globale Konfigurationsoptionen",
     "page.about.go_version": "Go-Version:",
     "page.about.license": "Lizenz:",
@@ -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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/el_EL.json b/internal/locale/translations/el_EL.json
index 0463d3b6..42d07e3a 100644
--- a/internal/locale/translations/el_EL.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/en_US.json b/internal/locale/translations/en_US.json
index cb516358..2b85ccfd 100644
--- a/internal/locale/translations/en_US.json
+++ b/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",
diff --git a/internal/locale/translations/es_ES.json b/internal/locale/translations/es_ES.json
index 8507b5ab..494e004c 100644
--- a/internal/locale/translations/es_ES.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/fi_FI.json b/internal/locale/translations/fi_FI.json
index 294b35b7..65dc3ace 100644
--- a/internal/locale/translations/fi_FI.json
+++ b/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ä"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/fr_FR.json b/internal/locale/translations/fr_FR.json
index 9293cbe0..60b39407 100644
--- a/internal/locale/translations/fr_FR.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/hi_IN.json b/internal/locale/translations/hi_IN.json
index dc323d11..cd90cd5e 100644
--- a/internal/locale/translations/hi_IN.json
+++ b/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 के रूप में लॉग इन किया"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/id_ID.json b/internal/locale/translations/id_ID.json
index e38c291f..998fc598 100644
--- a/internal/locale/translations/id_ID.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/it_IT.json b/internal/locale/translations/it_IT.json
index 479bf48b..2ea4a3e7 100644
--- a/internal/locale/translations/it_IT.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/ja_JP.json b/internal/locale/translations/ja_JP.json
index 78963d92..cbd4b47c 100644
--- a/internal/locale/translations/ja_JP.json
+++ b/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 としてログイン中"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/nan_Latn_pehoeji.json b/internal/locale/translations/nan_Latn_pehoeji.json
index d4f5046f..e70a0993 100644
--- a/internal/locale/translations/nan_Latn_pehoeji.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/nl_NL.json b/internal/locale/translations/nl_NL.json
index 5ddfea97..d2ca2322 100644
--- a/internal/locale/translations/nl_NL.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/pl_PL.json b/internal/locale/translations/pl_PL.json
index 6f01087d..dd2c0225 100644
--- a/internal/locale/translations/pl_PL.json
+++ b/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": "Nieprawidłowy adres URL serwera proxy.",
     "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": "Adres URL serwera proxy nie może być pusty.",
     "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 serwera 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": "Adres URL serwera proxy",
     "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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/pt_BR.json b/internal/locale/translations/pt_BR.json
index 40d9796b..196396ad 100644
--- a/internal/locale/translations/pt_BR.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/ro_RO.json b/internal/locale/translations/ro_RO.json
index 7f981ba5..5fb95831 100644
--- a/internal/locale/translations/ro_RO.json
+++ b/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",
diff --git a/internal/locale/translations/ru_RU.json b/internal/locale/translations/ru_RU.json
index eee84940..a95c6f68 100644
--- a/internal/locale/translations/ru_RU.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/tr_TR.json b/internal/locale/translations/tr_TR.json
index 067f8401..0132ef16 100644
--- a/internal/locale/translations/tr_TR.json
+++ b/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ı"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/uk_UA.json b/internal/locale/translations/uk_UA.json
index 822f653a..ef914630 100644
--- a/internal/locale/translations/uk_UA.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json
index fe89f22c..e7a79866 100644
--- a/internal/locale/translations/zh_CN.json
+++ b/internal/locale/translations/zh_CN.json
@@ -13,7 +13,7 @@
     "action.update": "更新",
     "alert.account_linked": "您的外部账号已关联！",
     "alert.account_unlinked": "您的外部帐户现已解除关联！",
-    "alert.background_feed_refresh": "所有的订阅源都在后台刷新中。您可以继续使用Miniflux，同时此过程正在运行。",
+    "alert.background_feed_refresh": "所有订阅源正在后台更新。此过程中您仍可继续使用 Miniflux。",
     "alert.feed_error": "该源存在问题",
     "alert.no_bookmark": "目前没有收藏",
     "alert.no_category": "目前没有分类",
@@ -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 密钥已存在。",
@@ -98,34 +98,36 @@
     "error.feed_title_not_empty": "订阅源的标题不能为空。",
     "error.feed_url_not_empty": "订阅源的网址不能为空。",
     "error.fields_mandatory": "必须填写全部信息",
-    "error.http_bad_gateway": "当前由于错误的网关导致该网站无法访问，问题不在Miniflux，请稍后重试。",
+    "error.http_bad_gateway": "当前由于错误的网关导致该网站无法访问，问题不在 Miniflux，请稍后重试。",
     "error.http_body_read": "无法读取HTTP主体: %v。",
     "error.http_client_error": "HTTP 客户端错误r: %v。",
-    "error.http_empty_response": "HTTP响应内容为空，该网站可能正在使用机器人保护机制。",
-    "error.http_empty_response_body": "HTTP响应主体为空。",
+    "error.http_empty_response": "HTTP 响应内容为空，该网站可能正在使用机器人保护机制。",
+    "error.http_empty_response_body": "HTTP 响应主体为空。",
     "error.http_forbidden": "该网站被禁止访问，网站可能有机器人保护机制？",
-    "error.http_gateway_timeout": "当前由于网关超时导致该网站无法访问，问题不在Miniflux，请稍后重试。",
-    "error.http_internal_server_error": "当前由于服务器错误导致该网站无法访问，问题不在Miniflux，请稍后重试。",
+    "error.http_gateway_timeout": "当前由于网关超时导致该网站无法访问，问题不在 Miniflux，请稍后重试。",
+    "error.http_internal_server_error": "当前由于服务器错误导致该网站无法访问，问题不在 Miniflux，请稍后重试。",
     "error.http_not_authorized": "该网站访问未授权，可能用户名和密码错误。",
-    "error.http_resource_not_found": "请求资源无法找到，请检查URL。",
-    "error.http_response_too_large": "HTTP响应内容过大，您可以在全局设置中增加HTTP响应大小限制（需要服务器重新启动）。",
-    "error.http_service_unavailable": "当前由于服务器内部错误导致该网站无法访问，问题不在Miniflux，请稍后重试。",
+    "error.http_resource_not_found": "请求资源无法找到，请检查 URL。",
+    "error.http_response_too_large": "HTTP 响应内容过大，您可以在全局设置中增加 HTTP 响应大小限制（需要服务器重新启动）。",
+    "error.http_service_unavailable": "当前由于服务器内部错误导致该网站无法访问，问题不在 Miniflux，请稍后重试。",
     "error.http_too_many_requests": "Miniflux 对该网站请求过多次数，请稍后重试或修改应用配置项。",
-    "error.http_unexpected_status_code": "当前由于意外的HTTP状态码：%d 导致该网站无法访问，问题不在Miniflux，请稍后重试。",
+    "error.http_unexpected_status_code": "当前由于意外的 HTTP 状态码：%d 导致该网站无法访问，问题不在 Miniflux，请稍后重试。",
     "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": "无效的语言。",
     "error.invalid_site_url": "源网站的网址无效。",
     "error.invalid_theme": "无效的主题。",
     "error.invalid_timezone": "无效的时区。",
-    "error.network_operation": "Miniflux无法访问该网站由于网络错误: %v。",
+    "error.network_operation": "Miniflux 无法访问该网站由于网络错误: %v。",
     "error.network_timeout": "该网站响应过慢，请求超时: %v",
     "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 的模式字符没有提供。",
@@ -141,11 +143,11 @@
     "error.site_url_not_empty": "源网站的网址不能为空。",
     "error.subscription_not_found": "找不到任何源",
     "error.title_required": "必须填写标题",
-    "error.tls_error": "TLS 错误: %q。如果您愿意的话可以在订阅源设置里关闭TLS验证。",
+    "error.tls_error": "TLS 错误: %q。如果您愿意的话可以在订阅源设置里关闭 TLS 验证。",
     "error.unable_to_create_api_key": "无法创建此 API 密钥。",
     "error.unable_to_create_category": "无法建立这个分类",
     "error.unable_to_create_user": "无法创建此用户",
-    "error.unable_to_detect_rssbridge": "无法使用RSS-Bridge去检测订阅源: %v。",
+    "error.unable_to_detect_rssbridge": "无法使用 RSS-Bridge 去检测订阅源: %v。",
     "error.unable_to_parse_feed": "无法解析该订阅源: %v。",
     "error.unable_to_update_category": "无法更新该分类",
     "error.unable_to_update_feed": "无法更新此源",
@@ -153,7 +155,7 @@
     "error.unlink_account_without_password": "您必须设置密码，否则您将无法再次登录。",
     "error.user_already_exists": "用户已存在",
     "error.user_mandatory_fields": "必须填写用户名",
-    "form.api_key.label.description": "API密钥标签",
+    "form.api_key.label.description": "API 密钥标签",
     "form.category.hide_globally": "隐藏全局未读列表中的文章",
     "form.category.label.title": "标题",
     "form.feed.fieldset.general": "通用",
@@ -172,40 +174,41 @@
     "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": "保留规则",
     "form.feed.label.no_media_player": "没有媒体播放器(音频/视频)",
     "form.feed.label.ntfy_activate": "推送条目到ntfy",
-    "form.feed.label.ntfy_default_priority": "Ntfy默认优先级",
-    "form.feed.label.ntfy_high_priority": "Ntfy高优先级",
-    "form.feed.label.ntfy_low_priority": "Ntfy低优先级",
-    "form.feed.label.ntfy_max_priority": "Ntfy最高优先级",
-    "form.feed.label.ntfy_min_priority": "Ntfy最低优先级",
-    "form.feed.label.ntfy_priority": "Ntfy优先级",
-    "form.feed.label.ntfy_topic": "Ntfy主题（可选）",
-    "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",
-    "form.feed.label.pushover_low_priority": "Pushover low priority",
-    "form.feed.label.pushover_max_priority": "Pushover max priority",
-    "form.feed.label.pushover_min_priority": "Pushover min priority",
-    "form.feed.label.pushover_priority": "Pushover message priority",
+    "form.feed.label.ntfy_default_priority": "Ntfy 默认优先级",
+    "form.feed.label.ntfy_high_priority": "Ntfy 高优先级",
+    "form.feed.label.ntfy_low_priority": "Ntfy 低优先级",
+    "form.feed.label.ntfy_max_priority": "Ntfy 最高优先级",
+    "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 高优先级",
+    "form.feed.label.pushover_low_priority": "Pushover 低优先级",
+    "form.feed.label.pushover_max_priority": "Pushover 最高优先级",
+    "form.feed.label.pushover_min_priority": "Pushover 最低优先级",
+    "form.feed.label.pushover_priority": "Pushover 消息优先级",
     "form.feed.label.rewrite_rules": "重写规则",
     "form.feed.label.scraper_rules": "抓取规则",
     "form.feed.label.site_url": "源网站 URL",
     "form.feed.label.title": "标题",
     "form.feed.label.urlrewrite_rules": "URL 重写规则",
     "form.feed.label.user_agent": "覆盖默认的用户代理",
-    "form.feed.label.webhook_url": "Override webhook url",
+    "form.feed.label.webhook_url": "覆盖 webhook url",
     "form.import.label.file": "OPML 文件",
     "form.import.label.url": "URL",
     "form.integration.apprise_activate": "将新文章推送到 Apprise",
     "form.integration.apprise_services_url": "使用逗号分隔的 Apprise 服务 URL 列表",
     "form.integration.apprise_url": "Apprise API URL",
     "form.integration.betula_activate": "保存文章到 Betula",
-    "form.integration.betula_token": "Betula 密钥",
+    "form.integration.betula_token": "Betula 令牌",
     "form.integration.betula_url": "Betula 服务地址",
     "form.integration.cubox_activate": "保存文章到 Cubox",
     "form.integration.cubox_api_link": "Cubox API 链接",
@@ -247,15 +250,15 @@
     "form.integration.matrix_bot_user": "Matrix Bot 用户名",
     "form.integration.notion_activate": "保存文章到 Notion",
     "form.integration.notion_page_id": "Notion 页面ID",
-    "form.integration.notion_token": "Notion 密钥",
-    "form.integration.ntfy_activate": "推送条目到ntfy",
+    "form.integration.notion_token": "Notion 令牌",
+    "form.integration.ntfy_activate": "推送条目到 ntfy",
     "form.integration.ntfy_api_token": "Ntfy API令牌（可选）",
-    "form.integration.ntfy_icon_url": "Ntfy图标URL（可选）",
-    "form.integration.ntfy_internal_links": "Use internal links on click (optional)",
-    "form.integration.ntfy_password": "Ntfy密码（可选）",
-    "form.integration.ntfy_topic": "Ntfy主题（預設，如果未在此源中定義）",
-    "form.integration.ntfy_url": "Ntfy URL（可选，默认为ntfy.sh）",
-    "form.integration.ntfy_username": "Ntfy用户名（可选）",
+    "form.integration.ntfy_icon_url": "Ntfy 图标 URL （可选）",
+    "form.integration.ntfy_internal_links": "点击时使用内部链接（可选）",
+    "form.integration.ntfy_password": "Ntfy 密码（可选）",
+    "form.integration.ntfy_topic": "Ntfy 主题（預設，如果未在此源中定義）",
+    "form.integration.ntfy_url": "Ntfy URL（可选，默认为 ntfy.sh）",
+    "form.integration.ntfy_username": "Ntfy 用户名（可选）",
     "form.integration.nunux_keeper_activate": "保存文章到 Nunux Keeper",
     "form.integration.nunux_keeper_api_key": "Nunux Keeper API 密钥",
     "form.integration.nunux_keeper_endpoint": "Nunux Keeper API 端点",
@@ -265,28 +268,28 @@
     "form.integration.pinboard_activate": "保存文章到 Pinboard",
     "form.integration.pinboard_bookmark": "标记为未读",
     "form.integration.pinboard_tags": "Pinboard 标签",
-    "form.integration.pinboard_token": "Pinboard API 密钥",
-    "form.integration.pocket_access_token": "Pocket 访问密钥",
+    "form.integration.pinboard_token": "Pinboard API 令牌",
+    "form.integration.pocket_access_token": "Pocket 访问令牌",
     "form.integration.pocket_activate": "将文章保存到 Pocket",
     "form.integration.pocket_connect_link": "连接您的 Pocket 帐户",
     "form.integration.pocket_consumer_key": "Pocket 用户密钥",
-    "form.integration.pushover_activate": "Push entries to Pushover",
-    "form.integration.pushover_device": "Pushover device (optional)",
-    "form.integration.pushover_prefix": "Pushover URL prefix (optional)",
-    "form.integration.pushover_token": "Pushover application API token",
-    "form.integration.pushover_user": "Pushover user key",
+    "form.integration.pushover_activate": "将条目推送至 Pushover",
+    "form.integration.pushover_device": "Pushover 装置（可选）",
+    "form.integration.pushover_prefix": "Pushover URL 前缀（可选）",
+    "form.integration.pushover_token": "Pushover 应用程序 API 令牌",
+    "form.integration.pushover_user": "Pushover 用户密钥",
     "form.integration.raindrop_activate": "保存文章到 Raindrop",
-    "form.integration.raindrop_collection_id": "Collection ID",
-    "form.integration.raindrop_tags": "Tags (comma-separated)",
-    "form.integration.raindrop_token": "(Test) 密钥",
+    "form.integration.raindrop_collection_id": "集合 ID",
+    "form.integration.raindrop_tags": "Tags （逗号分隔）",
+    "form.integration.raindrop_token": "(Test) 令牌",
     "form.integration.readeck_activate": "保存文章到 Readeck",
     "form.integration.readeck_api_key": "Readeck API 密钥",
     "form.integration.readeck_endpoint": "Readeck API 端点",
     "form.integration.readeck_labels": "Readeck 默认标签",
     "form.integration.readeck_only_url": "仅发送 URL（而不是完整内容）",
     "form.integration.readwise_activate": "保存文章到 Readwise Reader",
-    "form.integration.readwise_api_key": "Readwise Reader 密钥",
-    "form.integration.readwise_api_key_link": "获取你的 Readwise 密钥",
+    "form.integration.readwise_api_key": "Readwise Reader 访问令牌",
+    "form.integration.readwise_api_key_link": "获取你的 Readwise 访问令牌",
     "form.integration.rssbridge_activate": "添加订阅时检查 RSS-Bridge",
     "form.integration.rssbridge_url": "RSS-Bridge 服务器 URL",
     "form.integration.shaarli_activate": "保存文章到 Shaarli",
@@ -303,7 +306,7 @@
     "form.integration.telegram_bot_disable_notification": "禁用通知",
     "form.integration.telegram_bot_disable_web_page_preview": "禁用网页预览",
     "form.integration.telegram_bot_token": "机器人令牌",
-    "form.integration.telegram_chat_id": "聊天ID",
+    "form.integration.telegram_chat_id": "聊天 ID",
     "form.integration.telegram_topic_id": "Topic ID",
     "form.integration.wallabag_activate": "保存文章到 Wallabag",
     "form.integration.wallabag_client_id": "Wallabag 客户端 ID",
@@ -417,7 +420,7 @@
     "page.api_keys.table.created_at": "创建日期",
     "page.api_keys.table.description": "描述",
     "page.api_keys.table.last_used_at": "最后使用",
-    "page.api_keys.table.token": "密钥",
+    "page.api_keys.table.token": "令牌",
     "page.api_keys.title": "API 密钥",
     "page.categories_count": [
         "%d 分类"
diff --git a/internal/locale/translations/zh_TW.json b/internal/locale/translations/zh_TW.json
index 7b3e5c19..c009ea3b 100644
--- a/internal/locale/translations/zh_TW.json
+++ b/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"
-}
+}
\ No newline at end of file
diff --git a/internal/model/feed.go b/internal/model/feed.go
index 585365d3..6f289b47 100644
--- a/internal/model/feed.go
+++ b/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"`
@@ -116,9 +117,7 @@ func (f *Feed) CheckedNow() {
 }
 
 // ScheduleNextCheck set "next_check_at" of a feed based on the scheduler selected from the configuration.
-func (f *Feed) ScheduleNextCheck(weeklyCount int, refreshDelayInMinutes int) {
-	f.TTL = refreshDelayInMinutes
-
+func (f *Feed) ScheduleNextCheck(weeklyCount int, refreshDelayInMinutes int) int {
 	// Default to the global config Polling Frequency.
 	intervalMinutes := config.Opts.SchedulerRoundRobinMinInterval()
 
@@ -132,12 +131,21 @@ func (f *Feed) ScheduleNextCheck(weeklyCount int, refreshDelayInMinutes int) {
 		}
 	}
 
-	// If the feed has a TTL or a Retry-After defined, we use it to make sure we don't check it too often.
+	// Use the RSS TTL field, Retry-After, Cache-Control or Expires HTTP headers if defined.
 	if refreshDelayInMinutes > 0 && refreshDelayInMinutes > intervalMinutes {
 		intervalMinutes = refreshDelayInMinutes
 	}
 
+	// Limit the max interval value for misconfigured feeds.
+	switch config.Opts.PollingScheduler() {
+	case SchedulerRoundRobin:
+		intervalMinutes = min(intervalMinutes, config.Opts.SchedulerRoundRobinMaxInterval())
+	case SchedulerEntryFrequency:
+		intervalMinutes = min(intervalMinutes, config.Opts.SchedulerEntryFrequencyMaxInterval())
+	}
+
 	f.NextCheckAt = time.Now().Add(time.Minute * time.Duration(intervalMinutes))
+	return intervalMinutes
 }
 
 // FeedCreationRequest represents the request to create a feed.
@@ -161,6 +169,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 +204,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 +296,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
diff --git a/internal/model/subscription.go b/internal/model/subscription.go
index faed2b99..c8a45c71 100644
--- a/internal/model/subscription.go
+++ b/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"`
diff --git a/internal/proxyrotator/proxyrotator.go b/internal/proxyrotator/proxyrotator.go
new file mode 100644
index 00000000..03c791fc
--- /dev/null
+++ b/internal/proxyrotator/proxyrotator.go
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package proxyrotator // import "miniflux.app/v2/internal/proxyrotator"
+
+import (
+	"net/url"
+	"sync"
+)
+
+var ProxyRotatorInstance *ProxyRotator
+
+// ProxyRotator manages a list of proxies and rotates through them.
+type ProxyRotator struct {
+	proxies      []*url.URL
+	currentIndex int
+	mutex        sync.Mutex
+}
+
+// NewProxyRotator creates a new ProxyRotator with the given proxy URLs.
+func NewProxyRotator(proxyURLs []string) (*ProxyRotator, error) {
+	parsedProxies := make([]*url.URL, 0, len(proxyURLs))
+
+	for _, p := range proxyURLs {
+		proxyURL, err := url.Parse(p)
+		if err != nil {
+			return nil, err
+		}
+		parsedProxies = append(parsedProxies, proxyURL)
+	}
+
+	return &ProxyRotator{
+		proxies:      parsedProxies,
+		currentIndex: 0,
+		mutex:        sync.Mutex{},
+	}, nil
+}
+
+// GetNextProxy returns the next proxy in the rotation.
+func (pr *ProxyRotator) GetNextProxy() *url.URL {
+	pr.mutex.Lock()
+	defer pr.mutex.Unlock()
+
+	if len(pr.proxies) == 0 {
+		return nil
+	}
+
+	proxy := pr.proxies[pr.currentIndex]
+	pr.currentIndex = (pr.currentIndex + 1) % len(pr.proxies)
+
+	return proxy
+}
+
+// HasProxies checks if there are any proxies available in the rotator.
+func (pr *ProxyRotator) HasProxies() bool {
+	pr.mutex.Lock()
+	defer pr.mutex.Unlock()
+
+	return len(pr.proxies) > 0
+}
diff --git a/internal/reader/fetcher/request_builder.go b/internal/reader/fetcher/request_builder.go
index 5ed10a51..b46ed568 100644
--- a/internal/reader/fetcher/request_builder.go
+++ b/internal/reader/fetcher/request_builder.go
@@ -6,11 +6,14 @@ package fetcher // import "miniflux.app/v2/internal/reader/fetcher"
 import (
 	"crypto/tls"
 	"encoding/base64"
+	"fmt"
 	"log/slog"
 	"net"
 	"net/http"
 	"net/url"
 	"time"
+
+	"miniflux.app/v2/internal/proxyrotator"
 )
 
 const (
@@ -21,12 +24,14 @@ const (
 
 type RequestBuilder struct {
 	headers          http.Header
-	clientProxyURL   string
+	clientProxyURL   *url.URL
 	useClientProxy   bool
 	clientTimeout    int
 	withoutRedirects bool
 	ignoreTLSErrors  bool
 	disableHTTP2     bool
+	proxyRotator     *proxyrotator.ProxyRotator
+	feedProxyURL     string
 }
 
 func NewRequestBuilder() *RequestBuilder {
@@ -78,16 +83,26 @@ func (r *RequestBuilder) WithUsernameAndPassword(username, password string) *Req
 	return r
 }
 
-func (r *RequestBuilder) WithProxy(proxyURL string) *RequestBuilder {
+func (r *RequestBuilder) WithProxyRotator(proxyRotator *proxyrotator.ProxyRotator) *RequestBuilder {
+	r.proxyRotator = proxyRotator
+	return r
+}
+
+func (r *RequestBuilder) WithCustomApplicationProxyURL(proxyURL *url.URL) *RequestBuilder {
 	r.clientProxyURL = proxyURL
 	return r
 }
 
-func (r *RequestBuilder) UseProxy(value bool) *RequestBuilder {
+func (r *RequestBuilder) UseCustomApplicationProxyURL(value bool) *RequestBuilder {
 	r.useClientProxy = value
 	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
@@ -151,15 +166,25 @@ func (r *RequestBuilder) ExecuteRequest(requestURL string) (*http.Response, erro
 		transport.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
 	}
 
-	if r.useClientProxy && r.clientProxyURL != "" {
-		if proxyURL, err := url.Parse(r.clientProxyURL); err != nil {
-			slog.Warn("Unable to parse proxy URL",
-				slog.String("proxy_url", r.clientProxyURL),
-				slog.Any("error", err),
-			)
-		} else {
-			transport.Proxy = http.ProxyURL(proxyURL)
+	var clientProxyURL *url.URL
+
+	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
+	case r.proxyRotator != nil && r.proxyRotator.HasProxies():
+		clientProxyURL = r.proxyRotator.GetNextProxy()
+	}
+
+	var clientProxyURLRedacted string
+	if clientProxyURL != nil {
+		transport.Proxy = http.ProxyURL(clientProxyURL)
+		clientProxyURLRedacted = clientProxyURL.Redacted()
 	}
 
 	client := &http.Client{
@@ -189,8 +214,8 @@ func (r *RequestBuilder) ExecuteRequest(requestURL string) (*http.Response, erro
 		slog.String("url", req.URL.String()),
 		slog.Any("headers", req.Header),
 		slog.Bool("without_redirects", r.withoutRedirects),
-		slog.Bool("with_proxy", r.useClientProxy),
-		slog.String("proxy_url", r.clientProxyURL),
+		slog.Bool("use_app_client_proxy", r.useClientProxy),
+		slog.String("client_proxy_url", clientProxyURLRedacted),
 		slog.Bool("ignore_tls_errors", r.ignoreTLSErrors),
 		slog.Bool("disable_http2", r.disableHTTP2),
 	))
diff --git a/internal/reader/fetcher/response_handler.go b/internal/reader/fetcher/response_handler.go
index b71db54f..5c9778b5 100644
--- a/internal/reader/fetcher/response_handler.go
+++ b/internal/reader/fetcher/response_handler.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 	"io"
 	"log/slog"
+	"math"
 	"net"
 	"net/http"
 	"net/url"
@@ -53,6 +54,33 @@ func (r *ResponseHandler) ETag() string {
 	return r.httpResponse.Header.Get("ETag")
 }
 
+func (r *ResponseHandler) ExpiresInMinutes() int {
+	expiresHeaderValue := r.httpResponse.Header.Get("Expires")
+	if expiresHeaderValue != "" {
+		t, err := time.Parse(time.RFC1123, expiresHeaderValue)
+		if err == nil {
+			return int(math.Ceil(time.Until(t).Minutes()))
+		}
+	}
+	return 0
+}
+
+func (r *ResponseHandler) CacheControlMaxAgeInMinutes() int {
+	cacheControlHeaderValue := r.httpResponse.Header.Get("Cache-Control")
+	if cacheControlHeaderValue != "" {
+		for _, directive := range strings.Split(cacheControlHeaderValue, ",") {
+			directive = strings.TrimSpace(directive)
+			if strings.HasPrefix(directive, "max-age=") {
+				maxAge, err := strconv.Atoi(strings.TrimPrefix(directive, "max-age="))
+				if err == nil {
+					return int(math.Ceil(float64(maxAge) / 60))
+				}
+			}
+		}
+	}
+	return 0
+}
+
 func (r *ResponseHandler) ParseRetryDelay() int {
 	retryAfterHeaderValue := r.httpResponse.Header.Get("Retry-After")
 	if retryAfterHeaderValue != "" {
diff --git a/internal/reader/handler/handler.go b/internal/reader/handler/handler.go
index 937d7b78..43f6c764 100644
--- a/internal/reader/handler/handler.go
+++ b/internal/reader/handler/handler.go
@@ -12,6 +12,7 @@ import (
 	"miniflux.app/v2/internal/integration"
 	"miniflux.app/v2/internal/locale"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 	"miniflux.app/v2/internal/reader/icon"
 	"miniflux.app/v2/internal/reader/parser"
@@ -29,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) {
@@ -64,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)
@@ -83,8 +86,10 @@ func CreateFeedFromSubscriptionDiscovery(store *storage.Storage, userID int64, f
 	requestBuilder.WithUserAgent(feedCreationRequest.UserAgent, config.Opts.HTTPClientUserAgent())
 	requestBuilder.WithCookie(feedCreationRequest.Cookie)
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
-	requestBuilder.UseProxy(feedCreationRequest.FetchViaProxy)
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(feedCreationRequest.ProxyURL)
+	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
+	requestBuilder.UseCustomApplicationProxyURL(feedCreationRequest.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(feedCreationRequest.AllowSelfSignedCertificates)
 	requestBuilder.DisableHTTP2(feedCreationRequest.DisableHTTP2)
 
@@ -98,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) {
@@ -109,8 +115,10 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
 	requestBuilder.WithUserAgent(feedCreationRequest.UserAgent, config.Opts.HTTPClientUserAgent())
 	requestBuilder.WithCookie(feedCreationRequest.Cookie)
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
-	requestBuilder.UseProxy(feedCreationRequest.FetchViaProxy)
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(feedCreationRequest.ProxyURL)
+	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
+	requestBuilder.UseCustomApplicationProxyURL(feedCreationRequest.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(feedCreationRequest.AllowSelfSignedCertificates)
 	requestBuilder.DisableHTTP2(feedCreationRequest.DisableHTTP2)
 
@@ -157,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()
 
@@ -212,8 +221,10 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool
 	requestBuilder.WithUserAgent(originalFeed.UserAgent, config.Opts.HTTPClientUserAgent())
 	requestBuilder.WithCookie(originalFeed.Cookie)
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
-	requestBuilder.UseProxy(originalFeed.FetchViaProxy)
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(originalFeed.ProxyURL)
+	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
+	requestBuilder.UseCustomApplicationProxyURL(originalFeed.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(originalFeed.AllowSelfSignedCertificates)
 	requestBuilder.DisableHTTP2(originalFeed.DisableHTTP2)
 
@@ -229,18 +240,24 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool
 	if responseHandler.IsRateLimited() {
 		retryDelayInSeconds := responseHandler.ParseRetryDelay()
 		refreshDelayInMinutes = retryDelayInSeconds / 60
-		originalFeed.ScheduleNextCheck(weeklyEntryCount, refreshDelayInMinutes)
+		calculatedNextCheckIntervalInMinutes := originalFeed.ScheduleNextCheck(weeklyEntryCount, refreshDelayInMinutes)
 
 		slog.Warn("Feed is rate limited",
 			slog.String("feed_url", originalFeed.FeedURL),
 			slog.Int("retry_delay_in_seconds", retryDelayInSeconds),
 			slog.Int("refresh_delay_in_minutes", refreshDelayInMinutes),
+			slog.Int("calculated_next_check_interval_in_minutes", calculatedNextCheckIntervalInMinutes),
 			slog.Time("new_next_check_at", originalFeed.NextCheckAt),
 		)
 	}
 
 	if localizedError := responseHandler.LocalizedError(); localizedError != nil {
-		slog.Warn("Unable to fetch feed", slog.String("feed_url", originalFeed.FeedURL), slog.Any("error", localizedError.Error()))
+		slog.Warn("Unable to fetch feed",
+			slog.Int64("user_id", userID),
+			slog.Int64("feed_id", feedID),
+			slog.String("feed_url", originalFeed.FeedURL),
+			slog.Any("error", localizedError.Error()),
+		)
 		user, storeErr := store.UserByID(userID)
 		if storeErr != nil {
 			return locale.NewLocalizedErrorWrapper(storeErr, "error.database_error", storeErr)
@@ -292,16 +309,25 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool
 			return localizedError
 		}
 
-		// If the feed has a TTL defined, we use it to make sure we don't check it too often.
-		refreshDelayInMinutes = updatedFeed.TTL
+		// Use the RSS TTL value, or the Cache-Control or Expires HTTP headers if available.
+		// Otherwise, we use the default value from the configuration (min interval parameter).
+		feedTTLValue := updatedFeed.TTL
+		cacheControlMaxAgeValue := responseHandler.CacheControlMaxAgeInMinutes()
+		expiresValue := responseHandler.ExpiresInMinutes()
+		refreshDelayInMinutes = max(feedTTLValue, cacheControlMaxAgeValue, expiresValue)
 
 		// Set the next check at with updated arguments.
-		originalFeed.ScheduleNextCheck(weeklyEntryCount, refreshDelayInMinutes)
+		calculatedNextCheckIntervalInMinutes := originalFeed.ScheduleNextCheck(weeklyEntryCount, refreshDelayInMinutes)
 
 		slog.Debug("Updated next check date",
 			slog.Int64("user_id", userID),
 			slog.Int64("feed_id", feedID),
+			slog.String("feed_url", originalFeed.FeedURL),
+			slog.Int("feed_ttl_minutes", feedTTLValue),
+			slog.Int("cache_control_max_age_in_minutes", cacheControlMaxAgeValue),
+			slog.Int("expires_in_minutes", expiresValue),
 			slog.Int("refresh_delay_in_minutes", refreshDelayInMinutes),
+			slog.Int("calculated_next_check_interval_in_minutes", calculatedNextCheckIntervalInMinutes),
 			slog.Time("new_next_check_at", originalFeed.NextCheckAt),
 		)
 
diff --git a/internal/reader/icon/checker.go b/internal/reader/icon/checker.go
index a639844b..287a3d99 100644
--- a/internal/reader/icon/checker.go
+++ b/internal/reader/icon/checker.go
@@ -8,6 +8,7 @@ import (
 
 	"miniflux.app/v2/internal/config"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 	"miniflux.app/v2/internal/storage"
 )
@@ -29,8 +30,10 @@ func (c *IconChecker) fetchAndStoreIcon() {
 	requestBuilder.WithUserAgent(c.feed.UserAgent, config.Opts.HTTPClientUserAgent())
 	requestBuilder.WithCookie(c.feed.Cookie)
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
-	requestBuilder.UseProxy(c.feed.FetchViaProxy)
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(c.feed.ProxyURL)
+	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
+	requestBuilder.UseCustomApplicationProxyURL(c.feed.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(c.feed.AllowSelfSignedCertificates)
 	requestBuilder.DisableHTTP2(c.feed.DisableHTTP2)
 
diff --git a/internal/reader/processor/bilibili.go b/internal/reader/processor/bilibili.go
index 4450fcc3..315bba81 100644
--- a/internal/reader/processor/bilibili.go
+++ b/internal/reader/processor/bilibili.go
@@ -11,6 +11,7 @@ import (
 
 	"miniflux.app/v2/internal/config"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 )
 
@@ -45,7 +46,7 @@ func extractBilibiliVideoID(websiteURL string) (string, string, error) {
 func fetchBilibiliWatchTime(websiteURL string) (int, error) {
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
 
 	idType, videoID, extractErr := extractBilibiliVideoID(websiteURL)
 	if extractErr != nil {
diff --git a/internal/reader/processor/nebula.go b/internal/reader/processor/nebula.go
index afe7e302..0759f6fe 100644
--- a/internal/reader/processor/nebula.go
+++ b/internal/reader/processor/nebula.go
@@ -14,6 +14,7 @@ import (
 
 	"miniflux.app/v2/internal/config"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 )
 
@@ -33,7 +34,7 @@ func shouldFetchNebulaWatchTime(entry *model.Entry) bool {
 func fetchNebulaWatchTime(websiteURL string) (int, error) {
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(websiteURL))
 	defer responseHandler.Close()
diff --git a/internal/reader/processor/odysee.go b/internal/reader/processor/odysee.go
index 4748dc5c..132d2926 100644
--- a/internal/reader/processor/odysee.go
+++ b/internal/reader/processor/odysee.go
@@ -14,6 +14,7 @@ import (
 
 	"miniflux.app/v2/internal/config"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 )
 
@@ -33,7 +34,7 @@ func shouldFetchOdyseeWatchTime(entry *model.Entry) bool {
 func fetchOdyseeWatchTime(websiteURL string) (int, error) {
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(websiteURL))
 	defer responseHandler.Close()
diff --git a/internal/reader/processor/processor.go b/internal/reader/processor/processor.go
index d4485d91..7c5bb40f 100644
--- a/internal/reader/processor/processor.go
+++ b/internal/reader/processor/processor.go
@@ -14,6 +14,7 @@ import (
 	"miniflux.app/v2/internal/config"
 	"miniflux.app/v2/internal/metric"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 	"miniflux.app/v2/internal/reader/readingtime"
 	"miniflux.app/v2/internal/reader/rewrite"
@@ -78,8 +79,10 @@ func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, userID int64,
 			requestBuilder.WithUserAgent(feed.UserAgent, config.Opts.HTTPClientUserAgent())
 			requestBuilder.WithCookie(feed.Cookie)
 			requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-			requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
-			requestBuilder.UseProxy(feed.FetchViaProxy)
+			requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+			requestBuilder.WithCustomFeedProxyURL(feed.ProxyURL)
+			requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
+			requestBuilder.UseCustomApplicationProxyURL(feed.FetchViaProxy)
 			requestBuilder.IgnoreTLSErrors(feed.AllowSelfSignedCertificates)
 			requestBuilder.DisableHTTP2(feed.DisableHTTP2)
 
@@ -145,8 +148,10 @@ func ProcessEntryWebPage(feed *model.Feed, entry *model.Entry, user *model.User)
 	requestBuilder.WithUserAgent(feed.UserAgent, config.Opts.HTTPClientUserAgent())
 	requestBuilder.WithCookie(feed.Cookie)
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
-	requestBuilder.UseProxy(feed.FetchViaProxy)
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(feed.ProxyURL)
+	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
+	requestBuilder.UseCustomApplicationProxyURL(feed.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(feed.AllowSelfSignedCertificates)
 	requestBuilder.DisableHTTP2(feed.DisableHTTP2)
 
diff --git a/internal/reader/processor/youtube.go b/internal/reader/processor/youtube.go
index c67c02d9..72c9ce15 100644
--- a/internal/reader/processor/youtube.go
+++ b/internal/reader/processor/youtube.go
@@ -18,6 +18,7 @@ import (
 
 	"miniflux.app/v2/internal/config"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 )
 
@@ -52,7 +53,7 @@ func fetchYouTubeWatchTimeForSingleEntry(websiteURL string) (int, error) {
 
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(websiteURL))
 	defer responseHandler.Close()
@@ -132,7 +133,7 @@ func fetchYouTubeWatchTimeFromApiInBulk(videoIDs []string) (map[string]time.Dura
 
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(apiURL.String()))
 	defer responseHandler.Close()
diff --git a/internal/storage/feed.go b/internal/storage/feed.go
index 835bcd3f..4ccade16 100644
--- a/internal/storage/feed.go
+++ b/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,
 	)
@@ -481,3 +485,8 @@ func (s *Storage) ResetFeedErrors() error {
 	_, err := s.db.Exec(`UPDATE feeds SET parsing_error_count=0, parsing_error_msg=''`)
 	return err
 }
+
+func (s *Storage) ResetNextCheckAt() error {
+	_, err := s.db.Exec(`UPDATE feeds SET next_check_at=now()`)
+	return err
+}
diff --git a/internal/storage/feed_query_builder.go b/internal/storage/feed_query_builder.go
index b8a9eee0..82e6594f 100644
--- a/internal/storage/feed_query_builder.go
+++ b/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 {
diff --git a/internal/template/templates/views/add_subscription.html b/internal/template/templates/views/add_subscription.html
index 4619fd53..28ef4882 100644
--- a/internal/template/templates/views/add_subscription.html
+++ b/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">
 
diff --git a/internal/template/templates/views/choose_subscription.html b/internal/template/templates/views/choose_subscription.html
index 3b347f07..c8034a2b 100644
--- a/internal/template/templates/views/choose_subscription.html
+++ b/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 }}
diff --git a/internal/template/templates/views/edit_feed.html b/internal/template/templates/views/edit_feed.html
index 8daa9f3b..b55ad754 100644
--- a/internal/template/templates/views/edit_feed.html
+++ b/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">
 
diff --git a/internal/template/templates/views/entry.html b/internal/template/templates/views/entry.html
index fea2686b..68deae9a 100644
--- a/internal/template/templates/views/entry.html
+++ b/internal/template/templates/views/entry.html
@@ -67,11 +67,12 @@
                 </li>
                 {{ else }}
                 <li>
-                    <a href="{{ route "shareEntry" "entryID" .entry.ID }}"
-                        class="page-link"
-                        title="{{ t "entry.share.title" }}"
-                        data-share-status="share"
-                        target="_blank">{{ icon "share" }}<span class="icon-label">{{ t "entry.share.label" }}</span></a>
+                    <form method="post" action="{{route "shareEntry" "entryID" .entry.ID }}">
+                        <input type="hidden" name="csrf" value="{{ .csrf }}">
+                          <button type="submit" class="page-button">
+		              {{ icon "share" }}<span class="icon-label">{{ t "entry.share.label" }}</span>
+	                  </button>
+		    </form>
                 </li>
                 {{ end }}
                 <li>
diff --git a/internal/ui/feed_edit.go b/internal/ui/feed_edit.go
index c58d60a3..d1dcdc6f 100644
--- a/internal/ui/feed_edit.go
+++ b/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))
@@ -82,7 +83,7 @@ func (h *handler) showEditFeedPage(w http.ResponseWriter, r *http.Request) {
 	view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
 	view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
 	view.Set("defaultUserAgent", config.Opts.HTTPClientUserAgent())
-	view.Set("hasProxyConfigured", config.Opts.HasHTTPClientProxyConfigured())
+	view.Set("hasProxyConfigured", config.Opts.HasHTTPClientProxyURLConfigured())
 
 	html.OK(w, r, view.Render("edit_feed"))
 }
diff --git a/internal/ui/feed_update.go b/internal/ui/feed_update.go
index 5a6fd625..ea1e70ff 100644
--- a/internal/ui/feed_update.go
+++ b/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 {
diff --git a/internal/ui/form/feed.go b/internal/ui/form/feed.go
index e30d9318..fdb94b8e 100644
--- a/internal/ui/form/feed.go
+++ b/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"),
 	}
 }
diff --git a/internal/ui/form/subscription.go b/internal/ui/form/subscription.go
index ab46ce65..8ead956c 100644
--- a/internal/ui/form/subscription.go
+++ b/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"),
 	}
 }
diff --git a/internal/ui/opml_upload.go b/internal/ui/opml_upload.go
index ed05cc2f..c31b6e88 100644
--- a/internal/ui/opml_upload.go
+++ b/internal/ui/opml_upload.go
@@ -13,6 +13,7 @@ import (
 	"miniflux.app/v2/internal/http/response/html"
 	"miniflux.app/v2/internal/http/route"
 	"miniflux.app/v2/internal/locale"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 	"miniflux.app/v2/internal/reader/opml"
 	"miniflux.app/v2/internal/ui/session"
@@ -95,7 +96,7 @@ func (h *handler) fetchOPML(w http.ResponseWriter, r *http.Request) {
 
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(opmlFileURL))
 	defer responseHandler.Close()
diff --git a/internal/ui/static/js/app.js b/internal/ui/static/js/app.js
index cc818063..f8c4836a 100644
--- a/internal/ui/static/js/app.js
+++ b/internal/ui/static/js/app.js
@@ -719,11 +719,11 @@ function isPlayerPlaying(element) {
 /**
  * handle new share entires and already shared entries
  */
-function handleShare() {
+async function handleShare() {
     const link = document.querySelector(':is(a, button)[data-share-status]');
     const title = document.querySelector(".entry-header > h1 > a");
     if (link.dataset.shareStatus === "shared") {
-        checkShareAPI(title, link.href);
+        await checkShareAPI(title, link.href);
     }
     if (link.dataset.shareStatus === "share") {
         const request = new RequestBuilder(link.href);
@@ -738,14 +738,14 @@ function handleShare() {
 /**
 * wrapper for Web Share API
 */
-function checkShareAPI(title, url) {
+async function checkShareAPI(title, url) {
     if (!navigator.canShare) {
         console.error("Your browser doesn't support the Web Share API.");
         window.location = url;
         return;
     }
     try {
-        navigator.share({
+        await navigator.share({
             title: title ? title.textContent : url,
             url: url
         });
diff --git a/internal/ui/subscription_add.go b/internal/ui/subscription_add.go
index ca56a973..0347c91b 100644
--- a/internal/ui/subscription_add.go
+++ b/internal/ui/subscription_add.go
@@ -36,7 +36,7 @@ func (h *handler) showAddSubscriptionPage(w http.ResponseWriter, r *http.Request
 	view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
 	view.Set("defaultUserAgent", config.Opts.HTTPClientUserAgent())
 	view.Set("form", &form.SubscriptionForm{CategoryID: 0})
-	view.Set("hasProxyConfigured", config.Opts.HasHTTPClientProxyConfigured())
+	view.Set("hasProxyConfigured", config.Opts.HasHTTPClientProxyURLConfigured())
 
 	html.OK(w, r, view.Render("add_subscription"))
 }
diff --git a/internal/ui/subscription_bookmarklet.go b/internal/ui/subscription_bookmarklet.go
index dfc4dfd2..bc198439 100644
--- a/internal/ui/subscription_bookmarklet.go
+++ b/internal/ui/subscription_bookmarklet.go
@@ -53,7 +53,7 @@ func (h *handler) bookmarklet(w http.ResponseWriter, r *http.Request) {
 	view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
 	view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
 	view.Set("defaultUserAgent", config.Opts.HTTPClientUserAgent())
-	view.Set("hasProxyConfigured", config.Opts.HasHTTPClientProxyConfigured())
+	view.Set("hasProxyConfigured", config.Opts.HasHTTPClientProxyURLConfigured())
 
 	html.OK(w, r, view.Render("add_subscription"))
 }
diff --git a/internal/ui/subscription_choose.go b/internal/ui/subscription_choose.go
index af355d34..de6a4b5d 100644
--- a/internal/ui/subscription_choose.go
+++ b/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)
diff --git a/internal/ui/subscription_submit.go b/internal/ui/subscription_submit.go
index fd8e43ef..16a9c3f0 100644
--- a/internal/ui/subscription_submit.go
+++ b/internal/ui/subscription_submit.go
@@ -12,6 +12,7 @@ import (
 	"miniflux.app/v2/internal/http/route"
 	"miniflux.app/v2/internal/locale"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/proxyrotator"
 	"miniflux.app/v2/internal/reader/fetcher"
 	feedHandler "miniflux.app/v2/internal/reader/handler"
 	"miniflux.app/v2/internal/reader/subscription"
@@ -41,7 +42,7 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) {
 	v.Set("countUnread", h.store.CountUnreadEntries(user.ID))
 	v.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
 	v.Set("defaultUserAgent", config.Opts.HTTPClientUserAgent())
-	v.Set("hasProxyConfigured", config.Opts.HasHTTPClientProxyConfigured())
+	v.Set("hasProxyConfigured", config.Opts.HasHTTPClientProxyURLConfigured())
 
 	subscriptionForm := form.NewSubscriptionForm(r)
 	if validationErr := subscriptionForm.Validate(); validationErr != nil {
@@ -58,11 +59,13 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) {
 
 	requestBuilder := fetcher.NewRequestBuilder()
 	requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
-	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+	requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
+	requestBuilder.WithCustomFeedProxyURL(subscriptionForm.ProxyURL)
+	requestBuilder.WithCustomApplicationProxyURL(config.Opts.HTTPClientProxyURL())
+	requestBuilder.UseCustomApplicationProxyURL(subscriptionForm.FetchViaProxy)
 	requestBuilder.WithUserAgent(subscriptionForm.UserAgent, config.Opts.HTTPClientUserAgent())
 	requestBuilder.WithCookie(subscriptionForm.Cookie)
 	requestBuilder.WithUsernameAndPassword(subscriptionForm.Username, subscriptionForm.Password)
-	requestBuilder.UseProxy(subscriptionForm.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(subscriptionForm.AllowSelfSignedCertificates)
 	requestBuilder.DisableHTTP2(subscriptionForm.DisableHTTP2)
 
@@ -105,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 {
@@ -132,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)
@@ -149,7 +154,7 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) {
 		view.Set("user", user)
 		view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
 		view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
-		view.Set("hasProxyConfigured", config.Opts.HasHTTPClientProxyConfigured())
+		view.Set("hasProxyConfigured", config.Opts.HasHTTPClientProxyURLConfigured())
 
 		html.OK(w, r, view.Render("choose_subscription"))
 	}
diff --git a/internal/ui/ui.go b/internal/ui/ui.go
index a34656ec..f6a55c74 100644
--- a/internal/ui/ui.go
+++ b/internal/ui/ui.go
@@ -108,7 +108,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
 	uiRouter.HandleFunc("/entry/bookmark/{entryID}", handler.toggleBookmark).Name("toggleBookmark").Methods(http.MethodPost)
 
 	// Share pages.
-	uiRouter.HandleFunc("/entry/share/{entryID}", handler.createSharedEntry).Name("shareEntry").Methods(http.MethodGet)
+	uiRouter.HandleFunc("/entry/share/{entryID}", handler.createSharedEntry).Name("shareEntry").Methods(http.MethodPost)
 	uiRouter.HandleFunc("/entry/unshare/{entryID}", handler.unshareEntry).Name("unshareEntry").Methods(http.MethodPost)
 	uiRouter.HandleFunc("/share/{shareCode}", handler.sharedEntry).Name("sharedEntry").Methods(http.MethodGet)
 	uiRouter.HandleFunc("/shares", handler.sharedEntries).Name("sharedEntries").Methods(http.MethodGet)
diff --git a/internal/validator/feed.go b/internal/validator/feed.go
index 6a353892..b3227726 100644
--- a/internal/validator/feed.go
+++ b/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
 }
diff --git a/internal/validator/subscription.go b/internal/validator/subscription.go
index 516f679d..ff167e92 100644
--- a/internal/validator/subscription.go
+++ b/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
 }
diff --git a/internal/worker/worker.go b/internal/worker/worker.go
index 0ef0b01f..1fa77996 100644
--- a/internal/worker/worker.go
+++ b/internal/worker/worker.go
@@ -44,13 +44,5 @@ func (w *Worker) Run(c <-chan model.Job) {
 			}
 			metric.BackgroundFeedRefreshDuration.WithLabelValues(status).Observe(time.Since(startTime).Seconds())
 		}
-
-		if localizedError != nil {
-			slog.Warn("Unable to refresh a feed",
-				slog.Int64("user_id", job.UserID),
-				slog.Int64("feed_id", job.FeedID),
-				slog.Any("error", localizedError.Error()),
-			)
-		}
 	}
 }
diff --git a/miniflux.1 b/miniflux.1
index 05093890..0cba7ccf 100644
--- a/miniflux.1
+++ b/miniflux.1
@@ -1,12 +1,13 @@
 .\" Manpage for miniflux.
-.TH "MINIFLUX" "1" "December 7, 2024" "\ \&" "\ \&"
+.TH "MINIFLUX" "1" "April 11, 2025" "\ \&" "\ \&"
 
 .SH NAME
 miniflux \- Minimalist and opinionated feed reader
 
 .SH SYNOPSIS
-\fBminiflux\fR [-vic] [-config-dump] [-config-file] [-create-admin] [-debug] [-flush-sessions]
-    [-healthcheck] [-info] [-migrate] [-refresh-feeds] [-reset-feed-errors] [-reset-password]
+\fBminiflux\fR [-vic] [-config-dump] [-config-file] [-create-admin] [-debug]
+    [-flush-sessions] [-healthcheck] [-info] [-migrate] [-refresh-feeds]
+    [-reset-feed-errors] [-reset-feed-next-check-at] [-reset-password]
     [-run-cleanup-tasks] [-version]
 
 .SH DESCRIPTION
@@ -83,6 +84,11 @@ Refresh a batch of feeds and exit\&.
 Clear all feed errors for all users\&.
 .RE
 .PP
+.B \-reset-feed-next-check-at
+.RS 4
+Reset the next check time for all feeds\&.
+.RE
+.PP
 .B \-reset-password
 .RS 4
 Reset user password\&.
@@ -293,8 +299,13 @@ Maximum body size for HTTP requests in Mebibyte (MiB)\&.
 .br
 Default is 15 MiB\&.
 .TP
+.B HTTP_CLIENT_PROXIES
+Enable proxy rotation for outgoing requests by providing a comma-separated list of proxy URLs\&.
+.br
+Default is empty\&.
+.TP
 .B HTTP_CLIENT_PROXY
-Proxy URL for HTTP client\&.
+Proxy URL to use when the "Fetch via proxy" feed option is enabled\&.
 .br
 Default is empty\&.
 .TP
@@ -528,6 +539,11 @@ Minimum interval in minutes for the entry frequency scheduler\&.
 .br
 Default is 5 minutes\&.
 .TP
+.B SCHEDULER_ROUND_ROBIN_MAX_INTERVAL
+Maximum interval in minutes for the round robin scheduler\&.
+.br
+Default is 1440 minutes (24 hours)\&.
+.TP
 .B SCHEDULER_ROUND_ROBIN_MIN_INTERVAL
 Minimum interval in minutes for the round robin scheduler\&.
 .br



Test patch:
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index bfe40450..f925e2c9 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -1028,6 +1028,41 @@ func TestSchedulerRoundRobin(t *testing.T) {
 	}
 }
 
+func TestDefaultSchedulerRoundRobinMaxIntervalValue(t *testing.T) {
+	os.Clearenv()
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := defaultSchedulerRoundRobinMaxInterval
+	result := opts.SchedulerRoundRobinMaxInterval()
+
+	if result != expected {
+		t.Fatalf(`Unexpected SCHEDULER_ROUND_ROBIN_MAX_INTERVAL value, got %v instead of %v`, result, expected)
+	}
+}
+
+func TestSchedulerRoundRobinMaxInterval(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("SCHEDULER_ROUND_ROBIN_MAX_INTERVAL", "150")
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := 150
+	result := opts.SchedulerRoundRobinMaxInterval()
+
+	if result != expected {
+		t.Fatalf(`Unexpected SCHEDULER_ROUND_ROBIN_MAX_INTERVAL value, got %v instead of %v`, result, expected)
+	}
+}
+
 func TestPollingParsingErrorLimit(t *testing.T) {
 	os.Clearenv()
 	os.Setenv("POLLING_PARSING_ERROR_LIMIT", "100")
@@ -2186,3 +2221,71 @@ func TestParseConfigDumpOutput(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+func TestHTTPClientProxies(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("HTTP_CLIENT_PROXIES", "http://proxy1.example.com,http://proxy2.example.com")
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := []string{"http://proxy1.example.com", "http://proxy2.example.com"}
+	result := opts.HTTPClientProxies()
+
+	if len(expected) != len(result) {
+		t.Fatalf(`Unexpected HTTP_CLIENT_PROXIES value, got %v instead of %v`, result, expected)
+	}
+
+	for i, proxy := range expected {
+		if result[i] != proxy {
+			t.Fatalf(`Unexpected HTTP_CLIENT_PROXIES value at index %d, got %q instead of %q`, i, result[i], proxy)
+		}
+	}
+}
+
+func TestDefaultHTTPClientProxiesValue(t *testing.T) {
+	os.Clearenv()
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := []string{}
+	result := opts.HTTPClientProxies()
+
+	if len(expected) != len(result) {
+		t.Fatalf(`Unexpected default HTTP_CLIENT_PROXIES value, got %v instead of %v`, result, expected)
+	}
+}
+
+func TestHTTPClientProxy(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("HTTP_CLIENT_PROXY", "http://proxy.example.com")
+
+	parser := NewParser()
+	opts, err := parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	expected := "http://proxy.example.com"
+	if opts.HTTPClientProxyURL() == nil || opts.HTTPClientProxyURL().String() != expected {
+		t.Fatalf(`Unexpected HTTP_CLIENT_PROXY value, got %v instead of %v`, opts.HTTPClientProxyURL(), expected)
+	}
+}
+
+func TestInvalidHTTPClientProxy(t *testing.T) {
+	os.Clearenv()
+	os.Setenv("HTTP_CLIENT_PROXY", "sche|me://invalid-proxy-url")
+
+	parser := NewParser()
+	_, err := parser.ParseEnvironmentVariables()
+	if err == nil {
+		t.Fatalf(`Expected error for invalid HTTP_CLIENT_PROXY value, but got none`)
+	}
+}
diff --git a/internal/model/feed_test.go b/internal/model/feed_test.go
index 673b828e..d6887a93 100644
--- a/internal/model/feed_test.go
+++ b/internal/model/feed_test.go
@@ -144,6 +144,29 @@ func TestFeedScheduleNextCheckRoundRobinWithRefreshDelayBelowMinInterval(t *test
 	checkTargetInterval(t, feed, expectedInterval, timeBefore, "TestFeedScheduleNextCheckRoundRobinWithRefreshDelayBelowMinInterval")
 }
 
+func TestFeedScheduleNextCheckRoundRobinWithRefreshDelayAboveMaxInterval(t *testing.T) {
+	os.Clearenv()
+
+	var err error
+	parser := config.NewParser()
+	config.Opts, err = parser.ParseEnvironmentVariables()
+	if err != nil {
+		t.Fatalf(`Parsing failure: %v`, err)
+	}
+
+	timeBefore := time.Now()
+	feed := &Feed{}
+
+	feed.ScheduleNextCheck(0, config.Opts.SchedulerRoundRobinMaxInterval()+30)
+
+	if feed.NextCheckAt.IsZero() {
+		t.Error(`The next_check_at must be set`)
+	}
+
+	expectedInterval := config.Opts.SchedulerRoundRobinMaxInterval()
+	checkTargetInterval(t, feed, expectedInterval, timeBefore, "TestFeedScheduleNextCheckRoundRobinWithRefreshDelayAboveMaxInterval")
+}
+
 func TestFeedScheduleNextCheckRoundRobinMinInterval(t *testing.T) {
 	minInterval := 1
 	os.Clearenv()
diff --git a/internal/proxyrotator/proxyrotator_test.go b/internal/proxyrotator/proxyrotator_test.go
new file mode 100644
index 00000000..06afae4e
--- /dev/null
+++ b/internal/proxyrotator/proxyrotator_test.go
@@ -0,0 +1,71 @@
+// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package proxyrotator // import "miniflux.app/v2/internal/proxyrotator"
+
+import (
+	"testing"
+)
+
+func TestProxyRotator(t *testing.T) {
+	proxyURLs := []string{
+		"http://proxy1.example.com",
+		"http://proxy2.example.com",
+		"http://proxy3.example.com",
+	}
+
+	rotator, err := NewProxyRotator(proxyURLs)
+	if err != nil {
+		t.Fatalf("Failed to create ProxyRotator: %v", err)
+	}
+
+	if !rotator.HasProxies() {
+		t.Fatalf("Expected rotator to have proxies")
+	}
+
+	seenProxies := make(map[string]bool)
+	for range len(proxyURLs) * 2 {
+		proxy := rotator.GetNextProxy()
+		if proxy == nil {
+			t.Fatalf("Expected a proxy, got nil")
+		}
+
+		seenProxies[proxy.String()] = true
+	}
+
+	if len(seenProxies) != len(proxyURLs) {
+		t.Fatalf("Expected to see all proxies, but saw: %v", seenProxies)
+	}
+}
+
+func TestProxyRotatorEmpty(t *testing.T) {
+	rotator, err := NewProxyRotator([]string{})
+	if err != nil {
+		t.Fatalf("Failed to create ProxyRotator: %v", err)
+	}
+
+	if rotator.HasProxies() {
+		t.Fatalf("Expected rotator to have no proxies")
+	}
+
+	proxy := rotator.GetNextProxy()
+	if proxy != nil {
+		t.Fatalf("Expected no proxy, got: %v", proxy)
+	}
+}
+
+func TestProxyRotatorInvalidURL(t *testing.T) {
+	invalidProxyURLs := []string{
+		"http://validproxy.example.com",
+		"test|test://invalidproxy.example.com",
+	}
+
+	rotator, err := NewProxyRotator(invalidProxyURLs)
+	if err == nil {
+		t.Fatalf("Expected an error when creating ProxyRotator with invalid URLs, but got none")
+	}
+
+	if rotator != nil {
+		t.Fatalf("Expected rotator to be nil when initialization fails, but got: %v", rotator)
+	}
+}
diff --git a/internal/reader/fetcher/response_handler_test.go b/internal/reader/fetcher/response_handler_test.go
index da1f7856..cc675506 100644
--- a/internal/reader/fetcher/response_handler_test.go
+++ b/internal/reader/fetcher/response_handler_test.go
@@ -102,3 +102,75 @@ func TestRetryDelay(t *testing.T) {
 		})
 	}
 }
+
+func TestExpiresInMinutes(t *testing.T) {
+	var testCases = map[string]struct {
+		ExpiresHeader   string
+		ExpectedMinutes int
+	}{
+		"Empty header": {
+			ExpiresHeader:   "",
+			ExpectedMinutes: 0,
+		},
+		"Valid Expires header": {
+			ExpiresHeader:   time.Now().Add(10 * time.Minute).Format(time.RFC1123),
+			ExpectedMinutes: 10,
+		},
+		"Invalid Expires header": {
+			ExpiresHeader:   "invalid-date",
+			ExpectedMinutes: 0,
+		},
+	}
+	for name, tc := range testCases {
+		t.Run(name, func(tt *testing.T) {
+			header := http.Header{}
+			header.Add("Expires", tc.ExpiresHeader)
+			rh := ResponseHandler{
+				httpResponse: &http.Response{
+					Header: header,
+				},
+			}
+			if tc.ExpectedMinutes != rh.ExpiresInMinutes() {
+				t.Errorf("Expected %d, got %d for scenario %q", tc.ExpectedMinutes, rh.ExpiresInMinutes(), name)
+			}
+		})
+	}
+}
+
+func TestCacheControlMaxAgeInMinutes(t *testing.T) {
+	var testCases = map[string]struct {
+		CacheControlHeader string
+		ExpectedMinutes    int
+	}{
+		"Empty header": {
+			CacheControlHeader: "",
+			ExpectedMinutes:    0,
+		},
+		"Valid max-age": {
+			CacheControlHeader: "max-age=600",
+			ExpectedMinutes:    10,
+		},
+		"Invalid max-age": {
+			CacheControlHeader: "max-age=invalid",
+			ExpectedMinutes:    0,
+		},
+		"Multiple directives": {
+			CacheControlHeader: "no-cache, max-age=300",
+			ExpectedMinutes:    5,
+		},
+	}
+	for name, tc := range testCases {
+		t.Run(name, func(tt *testing.T) {
+			header := http.Header{}
+			header.Add("Cache-Control", tc.CacheControlHeader)
+			rh := ResponseHandler{
+				httpResponse: &http.Response{
+					Header: header,
+				},
+			}
+			if tc.ExpectedMinutes != rh.CacheControlMaxAgeInMinutes() {
+				t.Errorf("Expected %d, got %d for scenario %q", tc.ExpectedMinutes, rh.CacheControlMaxAgeInMinutes(), name)
+			}
+		})
+	}
+}