Compare commits

..

54 Commits

Author SHA1 Message Date
Frédéric Guillot f99dff5238 docs(changelog): update release notes for version 2.2.7 2025-04-01 17:18:32 -07:00
Frédéric Guillot 1936ced1ed docs: update README 2025-03-30 11:18:16 -07:00
Frédéric Guillot cb695e653a fix(security): use a more restrictive CSP for untrusted content 2025-03-29 19:49:41 -07:00
Matthaiks f57949c9a2 feat(locale): update Polish translation 2025-03-29 15:15:21 -07:00
Frédéric Guillot 94fb3d6159 fix: change labels from Read / Unread to "Mark as Read" 2025-03-28 17:34:44 -07:00
Frédéric Guillot 51560f191f fix(subscription): add /rss/feed.xml to the list of known feed URLs 2025-03-28 16:59:06 -07:00
Frédéric Guillot c0fcdf3b6e fix(ui): update entry tags display logic to show links based on user authentication 2025-03-28 15:53:08 -07:00
Frédéric Guillot 969efd2af7 fix(ui): update share feature to correctly select title element and handle empty title 2025-03-28 15:36:43 -07:00
Frédéric Guillot 87fccfee3a fix(ui): remove touch-action style because it could cause horizontal scrolling issue 2025-03-28 15:20:31 -07:00
Frédéric Guillot e643effefa feat: combine feed icon handlers to use only externalIconID 2025-03-28 14:29:19 -07:00
Frédéric Guillot c531be8780 fix: update Content-Security-Policy to use 'sandbox' directive 2025-03-28 13:06:59 -07:00
Frédéric Guillot 6e3cecc57e feat(locale): add Romanian translation
This contribution was sent outside GitHub. Credit to Laurentiu.
2025-03-28 11:11:20 -07:00
Tali Auster b3d385861f fix: log warning on an empty client secret
Because Miniflux runs as a confidential service, a missing client secret
is a mistake in configuration. An empty client secret appears to be
valid per RFC 6749 (and is in fact the default set by Miniflux!), so we
log a warning.
2025-03-27 20:00:09 -07:00
dependabot[bot] cc5a8fa4b3 build(deps): bump golang.org/x/net from 0.37.0 to 0.38.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.37.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.37.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-27 17:26:54 -07:00
Frédéric Guillot 78ff727064 fix: name "external_id": converting NULL to string is unsupported
Regresion introduced by commit df8bc742fb.
2025-03-26 20:51:57 -07:00
dependabot[bot] 54dad707a4 build(deps): bump github.com/tdewolff/minify/v2 from 2.22.3 to 2.22.4
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.22.3 to 2.22.4.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.22.3...v2.22.4)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 19:48:23 -07:00
Josiah Campbell df8bc742fb feat(googlereader): add feed icon field, endpoint
Adds an endpoint to the Google Reader integration to serve
feed icon URLs.
2025-03-25 20:55:16 -07:00
Frédéric Guillot e342a4f143 fix: address minor issues detected by Go linters 2025-03-24 20:48:46 -07:00
dependabot[bot] febb7b1748 build(deps): bump golangci/golangci-lint-action from 6 to 7
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6 to 7.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 20:02:03 -07:00
Frédéric Guillot 537867a64b ci: replace GitHub Issue Markdown templates with YAML forms 2025-03-24 17:27:31 -07:00
Frédéric Guillot 82a6fe64ae feat(storage): reduce the number of SQL queries when fetching entry enclosures 2025-03-23 14:13:33 -07:00
dependabot[bot] 984a66b590 build(deps): bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2
Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.1 to 5.2.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.1...v5.2.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 15:33:19 -07:00
dependabot[bot] bd79ad2a7d build(deps): bump github.com/tdewolff/minify/v2 from 2.22.2 to 2.22.3
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.22.2 to 2.22.3.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.22.2...v2.22.3)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-20 20:26:38 -07:00
dependabot[bot] b583de88f3 build(deps): bump github.com/tdewolff/minify/v2 from 2.21.3 to 2.22.2
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.21.3 to 2.22.2.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.21.3...v2.22.2)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-14 17:57:04 -07:00
dependabot[bot] baccb9e719 build(deps): bump github.com/coreos/go-oidc/v3 from 3.12.0 to 3.13.0
Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.12.0 to 3.13.0.
- [Release notes](https://github.com/coreos/go-oidc/releases)
- [Commits](https://github.com/coreos/go-oidc/compare/v3.12.0...v3.13.0)

---
updated-dependencies:
- dependency-name: github.com/coreos/go-oidc/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-13 19:30:26 -07:00
dependabot[bot] 0fd11693f3 build(deps): bump github.com/go-webauthn/webauthn from 0.12.1 to 0.12.2 (#3209)
Bumps [github.com/go-webauthn/webauthn](https://github.com/go-webauthn/webauthn) from 0.12.1 to 0.12.2.
- [Release notes](https://github.com/go-webauthn/webauthn/releases)
- [Commits](https://github.com/go-webauthn/webauthn/compare/v0.12.1...v0.12.2)

---
updated-dependencies:
- dependency-name: github.com/go-webauthn/webauthn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-10 18:02:27 -07:00
Matthaiks 05f7a34d43 feat(locale): update Polish translation 2025-03-07 11:36:25 -08:00
Frédéric Guillot 315e72c412 fix(rewrite): remove obsolete rule for webtoons.com 2025-03-06 20:11:03 -08:00
jvoisin f916373f55 fix: allow the <b> tag 2025-03-06 19:27:30 -08:00
jvoisin 5353211206 fix: allow the <u> tag in feeds 2025-03-06 19:26:26 -08:00
dependabot[bot] 999ab0df2b build(deps): bump golang.org/x/net from 0.36.0 to 0.37.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.36.0 to 0.37.0.
- [Commits](https://github.com/golang/net/compare/v0.36.0...v0.37.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 16:29:56 -08:00
dependabot[bot] f8700c84b8 build(deps): bump golang.org/x/image from 0.24.0 to 0.25.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/image/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 16:22:29 -08:00
dependabot[bot] ddd916161e build(deps): bump golang.org/x/crypto from 0.35.0 to 0.36.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/crypto/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 16:16:27 -08:00
dependabot[bot] c3cd5e3109 build(deps): bump golang.org/x/oauth2 from 0.27.0 to 0.28.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.27.0 to 0.28.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 16:11:59 -08:00
dependabot[bot] 051441d875 build(deps): bump golang.org/x/term from 0.29.0 to 0.30.0
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.29.0 to 0.30.0.
- [Commits](https://github.com/golang/term/compare/v0.29.0...v0.30.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 15:53:41 -08:00
Paul Stenius 0cf5051cf8 feat: show size of DB on the about page 2025-03-05 15:49:32 -08:00
Paul Stenius 12ca7aa085 feat: add a make add string command to add new localized strings 2025-03-04 21:22:33 -08:00
dependabot[bot] d53dc4cf28 build(deps): bump github.com/prometheus/client_golang
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.21.0 to 1.21.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.21.0...v1.21.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-04 16:47:52 -08:00
dependabot[bot] e7298b255a build(deps): bump golang.org/x/net from 0.35.0 to 0.36.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-04 16:45:39 -08:00
AiraNadih ad02f21d04 refactor(rewrite): reorganize referer rules and remove obsolete mapping 2025-03-02 19:40:52 -08:00
Frédéric Guillot 46323b3fe7 test(api): update base url after upgrading hugo 2025-03-02 19:32:09 -08:00
David Pedersen 86b3593fec docs: update client README to not reference deprecated function 2025-02-27 17:52:08 -08:00
Frédéric Guillot d7c504f48e fix(ui): avoid 500 error and NaN when marking as read a deleted entry
Steps to reproduce:

1. In /unread, open a feed's settings in a new tab. The feed must have unread entries in /unread.
2. In the new tab/window, delete the feed.
3. Without refreshing, mark an entry from the now-deleted feed as read.
4. Result: The total unread count in the UI header switches to NaN.
2025-02-26 16:39:38 -08:00
Matthaiks 6ad2001c7d feat(locale): update Polish translation 2025-02-26 16:33:18 -08:00
dependabot[bot] c5d4c762d8 build(deps): bump golang.org/x/crypto from 0.33.0 to 0.35.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.33.0 to 0.35.0.
- [Commits](https://github.com/golang/crypto/compare/v0.33.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 16:59:46 -08:00
dependabot[bot] 8b66c2be1d build(deps): bump golang.org/x/oauth2 from 0.26.0 to 0.27.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.26.0 to 0.27.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 16:58:57 -08:00
dependabot[bot] cfb22d7ec1 build(deps): bump github.com/go-webauthn/webauthn from 0.11.2 to 0.12.1
Bumps [github.com/go-webauthn/webauthn](https://github.com/go-webauthn/webauthn) from 0.11.2 to 0.12.1.
- [Release notes](https://github.com/go-webauthn/webauthn/releases)
- [Commits](https://github.com/go-webauthn/webauthn/compare/v0.11.2...v0.12.1)

---
updated-dependencies:
- dependency-name: github.com/go-webauthn/webauthn
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 16:54:07 -08:00
Maytham Alsudany f01ff067a5 fix(processor): add missing quotation marks to import comments 2025-02-24 16:34:26 -08:00
Uziskull 55a3f9fcc9 feat(integrations/ntfy): make ntfy topics configurable per feed, with default one as fallback 2025-02-24 16:29:08 -08:00
dependabot[bot] 996f6f68d2 build(deps): bump github.com/go-jose/go-jose/v4 from 4.0.2 to 4.0.5
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.2 to 4.0.5.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.0.2...v4.0.5)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 16:03:48 -08:00
Matthaiks 3b8ac8b16a feat(locale): update Polish translation 2025-02-23 11:46:28 -08:00
Frédéric Guillot 381f03b56a fix(greader): return enclosures in streamItemContentsHandler response 2025-02-22 20:11:41 -08:00
jvoisin 117d711d7d feat(urlcleaner): add more Google Analytics parameters 2025-02-22 17:07:59 -08:00
dependabot[bot] 2f71804ff4 build(deps): bump github.com/prometheus/client_golang
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.5 to 1.21.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.20.5...v1.21.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-22 17:06:47 -08:00
97 changed files with 1848 additions and 475 deletions
-10
View File
@@ -1,10 +0,0 @@
---
name: Bug report
about: Create a bug report
title: ''
labels: bug, triage needed
assignees: ''
---
+105
View File
@@ -0,0 +1,105 @@
name: "Bug Report"
description: "Report a bug or unexpected behavior"
title: "[Bug]: "
type: "Bug"
labels: ["triage needed"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting a bug! Please provide detailed information to help us reproduce and fix the issue.
- type: input
id: summary
attributes:
label: "Bug Summary"
description: "Briefly describe the bug."
placeholder: "e.g., Error when saving a new entry"
validations:
required: true
- type: textarea
id: description
attributes:
label: "Description"
description: "A clear and concise description of the bug."
placeholder: "e.g., When I click 'Save', I get a 500 error."
validations:
required: true
- type: textarea
id: steps_to_reproduce
attributes:
label: "Steps to Reproduce"
description: "Steps to reproduce the behavior."
placeholder: |
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
validations:
required: true
- type: textarea
id: expected_behavior
attributes:
label: "Expected Behavior"
description: "What should happen instead?"
placeholder: "e.g., The form should be saved successfully."
validations:
required: true
- type: textarea
id: actual_behavior
attributes:
label: "Actual Behavior"
description: "What actually happens?"
placeholder: "e.g., A 500 error is returned with no useful error message."
validations:
required: true
- type: input
id: version
attributes:
label: "Version"
description: "Which version of Miniflux are you using?"
placeholder: "e.g., 2.2.6"
validations:
required: true
- type: input
id: browser
attributes:
label: "Browser"
description: "If applicable, which browser are you using? Please provide the version."
placeholder: "e.g., Chrome, Firefox, Safari"
validations:
required: false
- type: textarea
id: logs
attributes:
label: "Relevant Logs or Error Output"
description: "Paste any relevant logs or error messages (if applicable)."
render: shell
placeholder: "e.g., Stack trace, log files, browser console logs, or console output"
validations:
required: false
- type: textarea
id: additional_context
attributes:
label: "Additional Context"
description: "Add any other context about the problem here."
placeholder: "e.g., Screenshots, video recordings, or related issues"
validations:
required: false
- type: checkboxes
id: agreement
attributes:
label: "Checklist"
description: "Please confirm the following:"
options:
- label: "I have searched existing issues to ensure this bug hasn't been reported before."
required: true
-10
View File
@@ -1,10 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: wishlist
assignees: ''
---
- [ ] I have read this document: https://miniflux.app/opinionated.html#feature-request
@@ -0,0 +1,65 @@
name: "Feature Request"
description: "Suggest an idea or improvement for the project"
title: "[Feature]: "
type: "Feature"
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to suggest a feature! Please provide detailed information to help us understand and evaluate your idea.
- type: input
id: summary
attributes:
label: "Feature Summary"
description: "Briefly describe the feature or enhancement."
placeholder: "e.g., Add dark mode support"
validations:
required: true
- type: textarea
id: problem
attributes:
label: "What problem does this feature solve?"
description: "Explain the problem or limitation this feature would address."
placeholder: "e.g., It's difficult to use the app in low-light environments."
validations:
required: true
- type: textarea
id: solution
attributes:
label: "Proposed Solution"
description: "Describe how you think this feature should work."
placeholder: "e.g., Add a toggle in settings to switch between light and dark mode."
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: "Alternatives Considered"
description: "Have you considered other solutions or workarounds?"
placeholder: "e.g., Using browser extensions to force dark mode."
validations:
required: false
- type: textarea
id: additional_context
attributes:
label: "Additional Context"
description: "Add any other context, screenshots, or examples to explain your request."
placeholder: "e.g., A screenshot of a similar feature in another project."
validations:
required: false
- type: checkboxes
id: agreement
attributes:
label: "Checklist"
description: "Please confirm the following:"
options:
- label: "I have searched existing issues to ensure this feature hasn't been requested before."
required: true
- label: "I understand that feature requests are not guaranteed to be implemented."
required: true
-10
View File
@@ -1,10 +0,0 @@
---
name: Feed Problems
about: Problems with a feed or a website
title: ''
labels: feed problems, triage needed
assignees: ''
---
+84
View File
@@ -0,0 +1,84 @@
name: "Feed/Website Issue"
description: "Report problems with a specific feed or website"
title: "[Feed Issue]: "
type: "Feed Issue"
labels: ["triage needed"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting an issue with a feed or website! Please provide detailed information to help us diagnose and resolve the problem.
- type: input
id: feed_url
attributes:
label: "Feed URL"
description: "Provide the URL of the feed that is not working correctly."
placeholder: "e.g., https://example.com/feed.xml"
validations:
required: true
- type: input
id: website_url
attributes:
label: "Website URL"
description: "Provide the URL of the website."
placeholder: "e.g., https://example.com"
validations:
required: true
- type: textarea
id: problem_description
attributes:
label: "Problem Description"
description: "Describe the issue you are experiencing with this feed."
placeholder: |
e.g.,
- The feed URL returns a 403 error.
- The content is malformed.
- Images are not loading in the web ui.
validations:
required: true
- type: textarea
id: expected_behavior
attributes:
label: "Expected Behavior"
description: "Describe what you expect to happen."
placeholder: "e.g., The feed should show the images correctly."
validations:
required: true
- type: textarea
id: error_logs
attributes:
label: "Relevant Logs or Error Output"
description: "Paste any relevant logs or error messages, if available."
render: shell
placeholder: "e.g., HTTP error codes, invalid XML warnings, etc."
validations:
required: false
- type: textarea
id: additional_context
attributes:
label: "Additional Context"
description: "Add any other context, screenshots, or related information to help us troubleshoot."
placeholder: "e.g., Is this a recurring problem? Did the feed work before?"
validations:
required: false
- type: checkboxes
id: troubleshooting
attributes:
label: "Troubleshooting Steps"
description: "Please confirm that you have tried the following:"
options:
- label: "I have checked if the feed URL is correct and accessible in a web browser."
required: true
- label: "I have checked if the feed URL is correct and accessible with `curl`."
required: true
- label: "I have verified that the feed is valid using an RSS/Atom validator."
required: false
- label: "I have searched for existing issues to avoid duplicates."
required: true
+86
View File
@@ -0,0 +1,86 @@
name: "Proposal / RFC"
description: "Propose a significant change, or architectural decision"
title: "[Proposal]: "
type: "Proposal"
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to submit a proposal! Please provide detailed information to ensure a productive discussion.
- type: input
id: summary
attributes:
label: "Proposal Summary"
description: "A brief summary of the proposed change or idea."
placeholder: "e.g., Refactor database schema for performance optimization"
validations:
required: true
- type: textarea
id: motivation
attributes:
label: "Motivation and Context"
description: "Explain the problem this proposal addresses. Why is it necessary? What are the current limitations or pain points?"
placeholder: |
e.g.,
- The current database schema causes performance bottlenecks when querying large datasets.
- Adding this feature will improve scalability and reliability for large-scale use cases.
validations:
required: true
- type: textarea
id: proposed_solution
attributes:
label: "Proposed Solution"
description: "Describe the proposed solution or approach. Include technical details, diagrams, and examples where possible."
placeholder: |
e.g.,
- Redesign the schema to normalize tables and introduce indexing.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: "Alternatives Considered"
description: "List any alternative approaches that were considered and explain why they were rejected."
placeholder: |
e.g.,
- Use Redis for caching, but it adds operational complexity.
- Stick with the current schema and optimize queries, but this has limited impact on performance.
validations:
required: false
- type: textarea
id: impact
attributes:
label: "Impact and Risks"
description: "Describe the potential impact of this change. Highlight possible risks and backward compatibility concerns."
placeholder: |
e.g.,
- May require data migration with downtime.
- Could introduce breaking changes in API responses.
- Affects core functionality, requiring extensive testing.
validations:
required: true
- type: textarea
id: additional_context
attributes:
label: "Additional Context or References"
description: "Add any relevant context, links to related discussions, RFCs, or design documents."
placeholder: "e.g., Links to research, GitHub issues, or similar projects"
validations:
required: false
- type: checkboxes
id: agreement
attributes:
label: "Checklist"
description: "Please confirm the following:"
options:
- label: "I have reviewed existing proposals to ensure this change hasn't been proposed before."
required: true
- label: "I agree to provide follow-up updates and maintain discussion on this proposal."
required: true
+4 -7
View File
@@ -29,17 +29,14 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version: "1.24.x"
- uses: golangci/golangci-lint-action@v6
- uses: golangci/golangci-lint-action@v7
with:
args: >
--timeout 10m
--exclude-dirs=tests
--disable errcheck
--enable sqlclosecheck,misspell,gofmt,goimports,whitespace,gocritic
- uses: dominikh/staticcheck-action@v1.3.1
with:
version: "latest"
install-go: false
--enable sqlclosecheck,misspell,whitespace,gocritic
- name: Run gofmt linter
run: gofmt -d -e .
commitlint:
name: Commit Linter
+44
View File
@@ -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)
---------------------------------
+10
View File
@@ -116,6 +116,16 @@ run:
clean:
@ rm -f $(APP)-* $(APP) $(APP)*.rpm $(APP)*.deb $(APP)*.exe $(APP)*.sha256
.PHONY: add-string
add-string:
cd internal/locale/translations && \
for file in *.json; do \
jq --indent 4 --arg key "$(KEY)" --arg val "$(VAL)" \
'. + {($$key): $$val} | to_entries | sort_by(.key) | from_entries' "$$file" > tmp && \
mv tmp "$$file"; \
done
test:
go test -cover -race -count=1 ./...
+101 -11
View File
@@ -1,20 +1,110 @@
Miniflux 2
==========
Miniflux is a minimalist and opinionated feed reader:
- Written in Go (Golang)
- Works only with Postgresql
- Doesn't use any ORM
- Doesn't use any complicated framework
- Use only modern vanilla Javascript (ES6 and Fetch API)
- Single binary compiled statically without dependency
- The number of features is voluntarily limited
Miniflux is a minimalist and opinionated feed reader.
It's simple, fast, lightweight and super easy to install.
Official website: <https://miniflux.app>
Features
--------
### Feed Reader
- Supported feed formats: Atom 0.3/1.0, RSS 1.0/2.0, and JSON Feed 1.0/1.1.
- [OPML](https://en.wikipedia.org/wiki/OPML) file import/export and URL import.
- Supports multiple attachments (podcasts, videos, music, and images enclosures).
- Plays videos from YouTube directly inside Miniflux.
- Organizes articles using categories and bookmarks.
- Share individual articles publicly.
- Fetches website icons (favicons).
- Saves articles to third-party services.
- Provides full-text search (powered by Postgres).
- Available in 20 languages: Portuguese (Brazilian), Chinese (Simplified and Traditional), Dutch, English (US), Finnish, French, German, Greek, Hindi, Indonesian, Italian, Japanese, Polish, Romanian, Russian, Taiwanese POJ, Ukrainian, Spanish, and Turkish.
### Privacy
- Removes pixel trackers.
- Strips tracking parameters from URLs (e.g., `utm_source`, `utm_medium`, `utm_campaign`, `fbclid`, etc.).
- Retrieves original links when feeds are sourced from FeedBurner.
- Opens external links with attributes `rel="noopener noreferrer" referrerpolicy="no-referrer"` for improved security.
- Implements the HTTP header `Referrer-Policy: no-referrer` to prevent referrer leakage.
- Provides a media proxy to avoid tracking and resolve mixed content warnings when using HTTPS.
- Plays YouTube videos via the privacy-focused domain `youtube-nocookie.com`.
- Supports alternative YouTube video players such as [Invidious](https://invidio.us).
- Blocks external JavaScript to prevent tracking and enhance security.
### Bot Protection Bypass Mechanisms
- Optionally disable HTTP/2 to mitigate fingerprinting.
- Allows configuration of a custom user agent.
- Supports adding custom cookies for specific use cases.
- Enables the use of proxies for enhanced privacy or bypassing restrictions.
### Content Manipulation
- Fetches the original article and extracts only the relevant content using a local Readability parser.
- Allows custom scraper rules based on <abbr title="Cascading Style Sheets">CSS</abbr> selectors.
- Supports custom rewriting rules for content manipulation.
- Provides a regex filter to include or exclude articles based on specific patterns.
- Optionally permits self-signed or invalid certificates (disabled by default).
- Scrapes YouTube's website to retrieve video duration as read time or uses the YouTube API (disabled by default).
### User Interface
- Optimized stylesheet for readability.
- Responsive design that adapts seamlessly to desktop, tablet, and mobile devices.
- Minimalistic and distraction-free user interface.
- No requirement to download an app from Apple App Store or Google Play Store.
- Can be added directly to the home screen for quick access.
- Supports a wide range of keyboard shortcuts for efficient navigation.
- Optional touch gesture support for navigation on mobile devices.
- Custom stylesheets and JavaScript to personalize the user interface to your preferences.
- Themes:
- Light (Sans-Serif)
- Light (Serif)
- Dark (Sans-Serif)
- Dark (Serif)
- System (Sans-Serif) Automatically switches between Dark and Light themes based on system preferences.
- System (Serif)
### Integrations
- 25+ integrations with third-party services: [Apprise](https://github.com/caronc/apprise), [Betula](https://sr.ht/~bouncepaw/betula/), [Cubox](https://cubox.cc/), [Discord](https://discord.com/), [Espial](https://github.com/jonschoning/espial), [Instapaper](https://www.instapaper.com/), [LinkAce](https://www.linkace.org/), [Linkding](https://github.com/sissbruecker/linkding), [LinkWarden](https://linkwarden.app/), [Matrix](https://matrix.org), [Notion](https://www.notion.com/), [Ntfy](https://ntfy.sh/), [Nunux Keeper](https://keeper.nunux.org/), [Pinboard](https://pinboard.in/), [Pocket](https://getpocket.com/), [Pushover](https://pushover.net), [RainDrop](https://raindrop.io/), [Readeck](https://readeck.org/en/), [Readwise Reader](https://readwise.io/read), [RssBridge](https://rss-bridge.org/), [Shaarli](https://github.com/shaarli/Shaarli), [Shiori](https://github.com/go-shiori/shiori), [Slack](https://slack.com/), [Telegram](https://telegram.org), [Wallabag](https://www.wallabag.org/), etc.
- Bookmarklet for subscribing to websites directly from any web browser.
- Webhooks for real-time notifications or custom integrations.
- Compatibility with existing mobile applications using the Fever or Google Reader API.
- REST API with client libraries available in [Go](https://github.com/miniflux/v2/tree/main/client) and [Python](https://github.com/miniflux/python-client).
### Authentication
- Local username and password.
- Passkeys ([WebAuthn](https://en.wikipedia.org/wiki/WebAuthn)).
- Google (OAuth2).
- Generic OpenID Connect.
- Reverse-Proxy authentication.
### Technical Stuff
- Written in [Go (Golang)](https://golang.org/).
- Single binary compiled statically without dependency.
- Works only with [PostgreSQL](https://www.postgresql.org/).
- Does not use any ORM or any complicated frameworks.
- Uses modern vanilla JavaScript only when necessary.
- All static files are bundled into the application binary using the Go `embed` package.
- Supports the Systemd `sd_notify` protocol for process monitoring.
- Configures HTTPS automatically with Let's Encrypt.
- Allows the use of custom <abbr title="Secure Sockets Layer">SSL</abbr> certificates.
- Supports [HTTP/2](https://en.wikipedia.org/wiki/HTTP/2) when TLS is enabled.
- Updates feeds in the background using an internal scheduler or a traditional cron job.
- Sanitizes external content before rendering it.
- Enforces a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) that permits only application JavaScript and blocks inline scripts and styles.
- Uses native lazy loading for images and iframes.
- Compatible only with modern browsers.
- Adheres to the [Twelve-Factor App](https://12factor.net/) methodology.
- Provides official Debian/RPM packages and pre-built binaries.
- Publishes a Docker image to Docker Hub, GitHub Registry, and Quay.io Registry, with ARM architecture support.
Documentation
-------------
@@ -29,7 +119,7 @@ The Miniflux documentation is available here: <https://miniflux.app/docs/> ([Man
- [Command Line Usage](https://miniflux.app/docs/cli.html)
- [User Interface Usage](https://miniflux.app/docs/ui.html)
- [Keyboard Shortcuts](https://miniflux.app/docs/keyboard_shortcuts.html)
- [Integration with External Services](https://miniflux.app/docs/services.html)
- [Integration with External Services](https://miniflux.app/docs/#integrations)
- [Rewrite and Scraper Rules](https://miniflux.app/docs/rules.html)
- [API Reference](https://miniflux.app/docs/api.html)
- [Development](https://miniflux.app/docs/development.html)
+2 -2
View File
@@ -27,10 +27,10 @@ import (
func main() {
// Authentication with username/password:
client := miniflux.New("https://api.example.org", "admin", "secret")
client := miniflux.NewClient("https://api.example.org", "admin", "secret")
// Authentication with an API Key:
client := miniflux.New("https://api.example.org", "my-secret-token")
client := miniflux.NewClient("https://api.example.org", "my-secret-token")
// Fetch all feeds.
feeds, err := client.Feeds()
+1
View File
@@ -18,6 +18,7 @@ type Client struct {
}
// New returns a new Miniflux client.
//
// Deprecated: use NewClient instead.
func New(endpoint string, credentials ...string) *Client {
return NewClient(endpoint, credentials...)
+22 -20
View File
@@ -5,24 +5,24 @@ module miniflux.app/v2
require (
github.com/PuerkitoBio/goquery v1.10.2
github.com/andybalholm/brotli v1.1.1
github.com/coreos/go-oidc/v3 v3.12.0
github.com/go-webauthn/webauthn v0.11.2
github.com/coreos/go-oidc/v3 v3.13.0
github.com/go-webauthn/webauthn v0.12.2
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.20.5
github.com/tdewolff/minify/v2 v2.21.3
golang.org/x/crypto v0.33.0
golang.org/x/image v0.24.0
golang.org/x/net v0.35.0
golang.org/x/oauth2 v0.26.0
golang.org/x/term v0.29.0
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
)
require (
github.com/go-webauthn/x v0.1.14 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/google/go-tpm v0.9.1 // indirect
github.com/go-webauthn/x v0.1.19 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/go-tpm v0.9.3 // indirect
)
require (
@@ -30,19 +30,21 @@ require (
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/go-jose/go-jose/v4 v4.0.2 // 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.9 // 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.55.0 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/tdewolff/parse/v2 v2.7.19 // indirect
github.com/tdewolff/parse/v2 v2.7.21 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/protobuf v1.34.2 // 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
)
go 1.23
go 1.23.0
toolchain go1.24.1
+42 -43
View File
@@ -8,30 +8,30 @@ 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.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
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/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/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0=
github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
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/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-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
github.com/google/go-tpm v0.9.1/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
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.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
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=
@@ -44,23 +44,22 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
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.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
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_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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tdewolff/minify/v2 v2.21.3 h1:KmhKNGrN/dGcvb2WDdB5yA49bo37s+hcD8RiF+lioV8=
github.com/tdewolff/minify/v2 v2.21.3/go.mod h1:iGxHaGiONAnsYuo8CRyf8iPUcqRJVB/RhtEcTpqS7xw=
github.com/tdewolff/parse/v2 v2.7.19 h1:7Ljh26yj+gdLFEq/7q9LT4SYyKtwQX4ocNrj45UCePg=
github.com/tdewolff/parse/v2 v2.7.19/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
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/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=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
@@ -72,10 +71,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.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
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/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=
@@ -90,10 +89,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.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
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/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=
@@ -112,8 +111,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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.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/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=
@@ -123,8 +122,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.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
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/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=
@@ -134,8 +133,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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
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/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=
@@ -143,7 +142,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.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+1 -1
View File
@@ -48,7 +48,7 @@ func newIntegrationTestConfig() *integrationTestConfig {
testFeedURL: getDefaultEnvValues("TEST_MINIFLUX_FEED_URL", "https://miniflux.app/feed.xml"),
testFeedTitle: getDefaultEnvValues("TEST_MINIFLUX_FEED_TITLE", "Miniflux"),
testSubscriptionTitle: getDefaultEnvValues("TEST_MINIFLUX_SUBSCRIPTION_TITLE", "Miniflux Releases"),
testWebsiteURL: getDefaultEnvValues("TEST_MINIFLUX_WEBSITE_URL", "https://miniflux.app"),
testWebsiteURL: getDefaultEnvValues("TEST_MINIFLUX_WEBSITE_URL", "https://miniflux.app/"),
}
}
+6 -2
View File
@@ -189,11 +189,15 @@ func (p *Parser) parseLines(lines []string) (err error) {
case "PROXY_PRIVATE_KEY":
slog.Warn("The PROXY_PRIVATE_KEY environment variable is deprecated, use MEDIA_PROXY_PRIVATE_KEY instead")
randomKey := make([]byte, 16)
rand.Read(randomKey)
if _, err := rand.Read(randomKey); err != nil {
return fmt.Errorf("config: unable to generate random key: %w", err)
}
p.opts.mediaProxyPrivateKey = parseBytes(value, randomKey)
case "MEDIA_PROXY_PRIVATE_KEY":
randomKey := make([]byte, 16)
rand.Read(randomKey)
if _, err := rand.Read(randomKey); err != nil {
return fmt.Errorf("config: unable to generate random key: %w", err)
}
p.opts.mediaProxyPrivateKey = parseBytes(value, randomKey)
case "MEDIA_PROXY_CUSTOM_URL":
p.opts.mediaProxyCustomURL = parseString(value, defaultMediaProxyURL)
+54
View File
@@ -5,6 +5,8 @@ package database // import "miniflux.app/v2/internal/database"
import (
"database/sql"
"miniflux.app/v2/internal/crypto"
)
var schemaVersion = len(migrations)
@@ -1008,4 +1010,56 @@ var migrations = []func(tx *sql.Tx, driver string) error{
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx, _ string) (err error) {
sql := `
ALTER TABLE feeds ADD COLUMN ntfy_topic text default '';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx, _ string) (err error) {
sql := `
ALTER TABLE icons ADD COLUMN external_id text default '';
CREATE UNIQUE INDEX icons_external_id_idx ON icons USING btree(external_id) WHERE external_id <> '';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx, _ string) (err error) {
_, err = tx.Exec(`
DECLARE id_cursor CURSOR FOR
SELECT
id
FROM icons
WHERE external_id = ''
FOR UPDATE`)
if err != nil {
return err
}
defer tx.Exec("CLOSE id_cursor")
for {
var id int64
if err := tx.QueryRow(`FETCH NEXT FROM id_cursor`).Scan(&id); err != nil {
if err == sql.ErrNoRows {
break
}
return err
}
_, err = tx.Exec(
`
UPDATE icons SET external_id = $1 WHERE id = $2
`,
crypto.GenerateRandomStringHex(20), id)
if err != nil {
return err
}
}
return nil
},
}
+11 -1
View File
@@ -827,6 +827,14 @@ func move(stream Stream, destination Stream, store *storage.Storage, userID int6
return store.UpdateFeed(feed)
}
func (h *handler) feedIconURL(f *model.Feed) string {
if f.Icon != nil && f.Icon.ExternalIconID != "" {
return config.Opts.RootURL() + route.Path(h.router, "feedIcon", "externalIconID", f.Icon.ExternalIconID)
} else {
return ""
}
}
func (h *handler) editSubscriptionHandler(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
clientIP := request.ClientIP(r)
@@ -950,6 +958,7 @@ func (h *handler) streamItemContentsHandler(w http.ResponseWriter, r *http.Reque
)
builder := h.store.NewEntryQueryBuilder(userID)
builder.WithEnclosures()
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithEntryIDs(itemIDs)
builder.WithSorting(model.DefaultSortingOrder, requestModifiers.SortDirection)
@@ -1207,6 +1216,7 @@ func (h *handler) subscriptionListHandler(w http.ResponseWriter, r *http.Request
json.ServerError(w, r, err)
return
}
result.Subscriptions = make([]subscription, 0)
for _, feed := range feeds {
result.Subscriptions = append(result.Subscriptions, subscription{
@@ -1215,7 +1225,7 @@ func (h *handler) subscriptionListHandler(w http.ResponseWriter, r *http.Request
URL: feed.FeedURL,
Categories: []subscriptionCategory{{fmt.Sprintf(UserLabelPrefix, userID) + feed.Category.Title, feed.Category.Title, "folder"}},
HTMLURL: feed.SiteURL,
IconURL: "", // TODO: Icons are base64 encoded in the DB.
IconURL: h.feedIconURL(feed),
})
}
json.OK(w, r, result)
+2 -2
View File
@@ -37,7 +37,7 @@ func ServerError(w http.ResponseWriter, r *http.Request, err error) {
builder := response.New(w, r)
builder.WithStatus(http.StatusInternalServerError)
builder.WithHeader("Content-Security-Policy", `default-src 'self'`)
builder.WithHeader("Content-Security-Policy", response.ContentSecurityPolicyForUntrustedContent)
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithBody(err)
@@ -61,7 +61,7 @@ func BadRequest(w http.ResponseWriter, r *http.Request, err error) {
builder := response.New(w, r)
builder.WithStatus(http.StatusBadRequest)
builder.WithHeader("Content-Security-Policy", `default-src 'self'`)
builder.WithHeader("Content-Security-Policy", response.ContentSecurityPolicyForUntrustedContent)
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithBody(err)
+14
View File
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package response // import "miniflux.app/v2/internal/http/response"
// ContentSecurityPolicyForUntrustedContent is the default CSP for untrusted content.
// default-src 'none' disables all content sources
// form-action 'none' disables all form submissions
// sandbox enables a sandbox for the requested resource
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/form-action
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src
const ContentSecurityPolicyForUntrustedContent = `default-src 'none'; form-action 'none'; sandbox;`
+6 -1
View File
@@ -502,15 +502,20 @@ func PushEntries(feed *model.Feed, entries model.Entries, userIntegrations *mode
}
if userIntegrations.NtfyEnabled && feed.NtfyEnabled {
ntfyTopic := feed.NtfyTopic
if ntfyTopic == "" {
ntfyTopic = userIntegrations.NtfyTopic
}
slog.Debug("Sending new entries to Ntfy",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int("nb_entries", len(entries)),
slog.Int64("feed_id", feed.ID),
slog.String("topic", ntfyTopic),
)
client := ntfy.NewClient(
userIntegrations.NtfyURL,
userIntegrations.NtfyTopic,
ntfyTopic,
userIntegrations.NtfyAPIToken,
userIntegrations.NtfyUsername,
userIntegrations.NtfyPassword,
+14 -13
View File
@@ -83,7 +83,7 @@ func TestMissingTranslations(t *testing.T) {
for key := range references {
if _, found := messages[key]; !found {
t.Fatalf(`Translation key %q not found in language %q`, key, language)
t.Errorf(`Translation key %q not found in language %q`, key, language)
}
}
}
@@ -91,25 +91,26 @@ func TestMissingTranslations(t *testing.T) {
func TestTranslationFilePluralForms(t *testing.T) {
var numberOfPluralFormsPerLanguage = map[string]int{
"de_DE": 2,
"el_EL": 2,
"en_US": 2,
"es_ES": 2,
"fi_FI": 2,
"fr_FR": 2,
"de_DE": 2,
"pl_PL": 3,
"pt_BR": 2,
"zh_CN": 1,
"zh_TW": 1,
"nan_Latn_pehoeji": 1,
"nl_NL": 2,
"ru_RU": 3,
"hi_IN": 2,
"id_ID": 1,
"it_IT": 2,
"ja_JP": 1,
"nan_Latn_pehoeji": 1,
"nl_NL": 2,
"pl_PL": 3,
"pt_BR": 2,
"ro_RO": 3,
"ru_RU": 3,
"tr_TR": 2,
"el_EL": 2,
"fi_FI": 2,
"hi_IN": 2,
"uk_UA": 3,
"id_ID": 1,
"zh_CN": 1,
"zh_TW": 1,
}
for language := range AvailableLanguages {
messages, err := loadTranslationFile(language)
+13 -12
View File
@@ -5,23 +5,24 @@ package locale // import "miniflux.app/v2/internal/locale"
// AvailableLanguages is the list of available languages.
var AvailableLanguages = map[string]string{
"de_DE": "Deutsch",
"el_EL": "Ελληνικά",
"en_US": "English",
"es_ES": "Español",
"fi_FI": "Suomi",
"fr_FR": "Français",
"de_DE": "Deutsch",
"pl_PL": "Polski",
"pt_BR": "Português Brasileiro",
"zh_CN": "简体中文",
"zh_TW": "繁體中文",
"nan_Latn_pehoeji": "Pe̍h-ōe-jī",
"nl_NL": "Nederlands",
"ru_RU": "Русский",
"hi_IN": "हिन्दी",
"id_ID": "Bahasa Indonesia",
"it_IT": "Italiano",
"ja_JP": "日本語",
"nan_Latn_pehoeji": "Pe̍h-ōe-jī",
"nl_NL": "Nederlands",
"pl_PL": "Polski",
"pt_BR": "Português Brasileiro",
"ro_RO": "Română",
"ru_RU": "Русский",
"tr_TR": "Türkçe",
"el_EL": "Ελληνικά",
"fi_FI": "Suomi",
"hi_IN": "हिन्दी",
"uk_UA": "Українська",
"id_ID": "Bahasa Indonesia",
"zh_CN": "简体中文",
"zh_TW": "繁體中文",
}
+10
View File
@@ -71,6 +71,16 @@ var pluralForms = map[string]func(n int) int{
}
return 0
},
// nplurals=3; plural=(n==1 ? 0 : n==0 || (n%100 > 0 && n%100 < 20) ? 1 : 2);
"ro_RO": func(n int) int {
switch {
case n == 1:
return 0
case n == 0 || (n%100 > 0 && n%100 < 20):
return 1
}
return 2
},
"ru_RU": pluralFormRuSrUa,
// nplurals=2; plural=(n > 1);
"tr_TR": func(n int) int {
+5
View File
@@ -49,6 +49,11 @@ func TestPluralRules(t *testing.T) {
2: 1,
5: 1,
},
"ro_RO": {
1: 0,
2: 1,
5: 1,
},
"ru_RU": {
1: 0,
2: 1,
+13 -11
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "Öffnen Sie den öffentlichen Link",
"entry.state.loading": "Lade...",
"entry.state.saving": "Speichern...",
"entry.status.read": "Gelesen",
"entry.status.mark_as_read": "Als gelesen markieren",
"entry.status.title": "Status des Artikels ändern",
"entry.status.toast.read": "Als gelesen markiert",
"entry.status.toast.unread": "Als ungelesen markiert",
"entry.status.unread": "Ungelesen",
"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.",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Höchste Ntfy-Priorität",
"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",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "Ntfy-Symbol-URL (optional)",
"form.integration.ntfy_internal_links": "Interne Links beim Klicken verwenden (optional)",
"form.integration.ntfy_password": "Ntfy-Passwort (optional)",
"form.integration.ntfy_topic": "Ntfy-Thema",
"form.integration.ntfy_topic": "Ntfy-Thema (Standard, wenn nicht im Feed eingestellt)",
"form.integration.ntfy_url": "Ntfy-URL (optional, Standard ist ntfy.sh)",
"form.integration.ntfy_username": "Ntfy-Benutzername (optional)",
"form.integration.nunux_keeper_activate": "Artikel in Nunux Keeper speichern",
@@ -400,6 +401,7 @@
"page.about.author": "Autor:",
"page.about.build_date": "Datum der Kompilierung:",
"page.about.credits": "Urheberrechte",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Globale Konfigurationsoptionen",
"page.about.go_version": "Go-Version:",
"page.about.license": "Lizenz:",
@@ -419,10 +421,6 @@
"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.",
@@ -431,6 +429,10 @@
"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:",
@@ -536,25 +538,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.shared_entries.title": "Geteilte Artikel",
"page.starred.title": "Lesezeichen",
"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",
@@ -601,4 +603,4 @@
"time_elapsed.yesterday": "gestern",
"tooltip.keyboard_shortcuts": "Tastenkürzel: %s",
"tooltip.logged_user": "Angemeldet als %s"
}
}
+13 -11
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "Ανοίξτε τον δημόσιο σύνδεσμο",
"entry.state.loading": "Φόρτωση...",
"entry.state.saving": "Aποθήκευση...",
"entry.status.read": "Αναγνωσμένο",
"entry.status.mark_as_read": "Επισήμανση ως αναγνωσμένο",
"entry.status.title": "Αλλαγή κατάστασης καταχώρησης",
"entry.status.toast.read": "Επισήμανση ως αναγνωσμένο",
"entry.status.toast.unread": "Επισήμανση ως μη αναγνωσμένο",
"entry.status.unread": "Μη αναγνωσμένο",
"entry.status.mark_as_unread": "Επισήμανση ως μη αναγνωσμένο",
"entry.tags.label": "Ετικέτες:",
"entry.unshare.label": "Aναίρεση Διαμοιρασμού",
"error.api_key_already_exists": "Αυτό το κλειδί API υπάρχει ήδη.",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.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",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default used if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "Αποθήκευση άρθρων στο Nunux Keeper",
@@ -400,6 +401,7 @@
"page.about.author": "Συγγραφέας:",
"page.about.build_date": "Ημερομηνία Κατασκευής:",
"page.about.credits": "Συνεισφέροντες",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Γενικές ρυθμίσεις",
"page.about.go_version": "Έκδοση Go:",
"page.about.license": "Άδεια:",
@@ -419,10 +421,6 @@
"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 ροή.",
@@ -431,6 +429,10 @@
"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:",
@@ -536,25 +538,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.shared_entries.title": "Κοινόχρηστες Καταχωρήσεις",
"page.starred.title": "Αγαπημένo",
"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": "Ναι.",
@@ -601,4 +603,4 @@
"time_elapsed.yesterday": "χθες",
"tooltip.keyboard_shortcuts": "Συντόμευση πληκτρολογίου: % s",
"tooltip.logged_user": "Συνδεδεμένος/η ως %s"
}
}
+5 -3
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "Open the public link",
"entry.state.loading": "Loading…",
"entry.state.saving": "Saving…",
"entry.status.read": "Read",
"entry.status.mark_as_read": "Mark as read",
"entry.status.title": "Change entry status",
"entry.status.toast.read": "Marked as read",
"entry.status.toast.unread": "Marked as unread",
"entry.status.unread": "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.",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.pushover_activate": "Push entries to Pushover",
"form.feed.label.pushover_default_priority": "Default priority",
"form.feed.label.pushover_high_priority": "High priority",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default used if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "Save entries to Nunux Keeper",
@@ -400,6 +401,7 @@
"page.about.author": "Author:",
"page.about.build_date": "Build Date:",
"page.about.credits": "Credits",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Global configuration options",
"page.about.go_version": "Go version:",
"page.about.license": "License:",
+13 -11
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "Abrir el enlace público",
"entry.state.loading": "Cargando...",
"entry.state.saving": "Guardando...",
"entry.status.read": "Leído",
"entry.status.mark_as_read": "Marcar como 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.unread": "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.",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Prioridad máxima a Ntfy",
"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.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",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "URL del icono de Ntfy (opcional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Contraseña de Ntfy (opcional)",
"form.integration.ntfy_topic": "Tema Ntfy",
"form.integration.ntfy_topic": "Tema Ntfy (por defecto, si no se establece en el feed)",
"form.integration.ntfy_url": "URL de Ntfy (opcional, la predeterminada es ntfy.sh)",
"form.integration.ntfy_username": "Nombre de usuario de Ntfy (opcional)",
"form.integration.nunux_keeper_activate": "Enviar artículos a Nunux Keeper",
@@ -400,6 +401,7 @@
"page.about.author": "Autor:",
"page.about.build_date": "Fecha de compilación:",
"page.about.credits": "Créditos",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Opciones de configuración global",
"page.about.go_version": "Go versión:",
"page.about.license": "Licencia:",
@@ -419,10 +421,6 @@
"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.",
@@ -431,6 +429,10 @@
"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:",
@@ -536,25 +538,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.shared_entries.title": "Artículos compartidos",
"page.starred.title": "Marcadores",
"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í",
@@ -601,4 +603,4 @@
"time_elapsed.yesterday": "ayer",
"tooltip.keyboard_shortcuts": "Atajo de teclado: %s",
"tooltip.logged_user": "Registrado como %s"
}
}
+13 -11
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "Avaa julkinen linkki",
"entry.state.loading": "Ladataan...",
"entry.state.saving": "Tallennetaan...",
"entry.status.read": "Luettu",
"entry.status.mark_as_read": "Merkitse luetuksi",
"entry.status.title": "Vaihda artikkelin tilaa",
"entry.status.toast.read": "Merkitty luetuksi",
"entry.status.toast.unread": "Merkitty lukemattomaksi",
"entry.status.unread": "Lukematon",
"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.",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.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",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default used if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "Tallenna artikkelit Nunux Keeperiin",
@@ -400,6 +401,7 @@
"page.about.author": "Tekijä:",
"page.about.build_date": "Valmistuspäivä:",
"page.about.credits": "Kiitokset",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Yleiset asetukset",
"page.about.go_version": "Go-versio:",
"page.about.license": "Lisenssi:",
@@ -419,10 +421,6 @@
"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.",
@@ -431,6 +429,10 @@
"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:",
@@ -536,25 +538,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.shared_entries.title": "Jaetut artikkelit",
"page.starred.title": "Suosikit",
"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ä",
@@ -601,4 +603,4 @@
"time_elapsed.yesterday": "eilen",
"tooltip.keyboard_shortcuts": "Pikanäppäin: %s",
"tooltip.logged_user": "Kirjautunut %s-käyttäjänä"
}
}
+13 -11
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "Ouvrir le lien public",
"entry.state.loading": "Chargement...",
"entry.state.saving": "Sauvegarde en cours...",
"entry.status.read": "Lu",
"entry.status.mark_as_read": "Marquer comme 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.unread": "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à.",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Priorité maximale de notification",
"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.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",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "URL de l'icône Ntfy (facultatif)",
"form.integration.ntfy_internal_links": "Utiliser les liens internes vers Miniflux (facultatif)",
"form.integration.ntfy_password": "Mot de passe Ntfy (facultatif)",
"form.integration.ntfy_topic": "Sujet Ntfy",
"form.integration.ntfy_topic": "Sujet Ntfy (défaut s'il n'est pas défini dans le flux)",
"form.integration.ntfy_url": "URL de Ntfy (optionnel, ntfy.sh par défaut)",
"form.integration.ntfy_username": "Nom d'utilisateur Ntfy (optionnel)",
"form.integration.nunux_keeper_activate": "Sauvegarder les articles vers Nunux Keeper",
@@ -400,6 +401,7 @@
"page.about.author": "Auteur :",
"page.about.build_date": "Date de la compilation :",
"page.about.credits": "Crédits",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Options de configuration globales",
"page.about.go_version": "Version de Go :",
"page.about.license": "Licence :",
@@ -419,10 +421,6 @@
"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.",
@@ -431,6 +429,10 @@
"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 :",
@@ -536,25 +538,25 @@
"page.settings.webauthn.passkeys": "Clés daccès",
"page.settings.webauthn.register": "Enregister une nouvelle clé daccès",
"page.settings.webauthn.register.error": "Impossible d'enregistrer la clé daccès",
"page.shared_entries.title": "Articles partagés",
"page.shared_entries_count": [
"%d article partagé",
"%d articles partagés"
],
"page.shared_entries.title": "Articles partagés",
"page.starred.title": "Favoris",
"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",
@@ -601,4 +603,4 @@
"time_elapsed.yesterday": "hier",
"tooltip.keyboard_shortcuts": "Raccourci clavier : %s",
"tooltip.logged_user": "Connecté en tant que %s"
}
}
+13 -11
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "सार्वजनिक लिंक खोले",
"entry.state.loading": "लोड हो रहा है...",
"entry.state.saving": "सहेजा जा रहा है...",
"entry.status.read": "पढ़े",
"entry.status.mark_as_read": "पढ़े हुए का चिह्न",
"entry.status.title": "प्रविष्टि स्थिति बदलें",
"entry.status.toast.read": "पढ़ा हुआ चिह्नित करे",
"entry.status.toast.unread": "अपठित के रूप में चिह्नित",
"entry.status.unread": "अपठित",
"entry.status.mark_as_unread": "अपठित के रूप में चिह्नित करें",
"entry.tags.label": "टैग:",
"entry.unshare.label": "न साझा कारें",
"error.api_key_already_exists": "यह एपीआई कुंजी पहले से मौजूद है।",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.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",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default used if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "विषय-वस्तु को ननक्स कीपर में सहेजें",
@@ -400,6 +401,7 @@
"page.about.author": "रचयिता:",
"page.about.build_date": "बनाने की तिथि:",
"page.about.credits": "आभार सूची",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "वैश्विक विन्यास विकल्प",
"page.about.go_version": "गो संस्करण:",
"page.about.license": "अनुज्ञा:",
@@ -419,10 +421,6 @@
"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 फ़ीड बाकी है।",
@@ -431,6 +429,10 @@
"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": "ईटाग हैडर:",
@@ -536,25 +538,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.shared_entries.title": "साझा किया हुआ प्रविष्टि",
"page.starred.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": "हां",
@@ -601,4 +603,4 @@
"time_elapsed.yesterday": "कल",
"tooltip.keyboard_shortcuts": "कुंजीपटल संक्षिप्त रीति: %s",
"tooltip.logged_user": "%s के रूप में लॉग इन किया"
}
}
+12 -10
View File
@@ -69,11 +69,11 @@
"entry.shared_entry.title": "Buka tautan publik",
"entry.state.loading": "Memuat...",
"entry.state.saving": "Menyimpan...",
"entry.status.read": "Telah dibaca",
"entry.status.mark_as_read": "Telah 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.unread": "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.",
@@ -184,6 +184,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.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",
@@ -252,7 +253,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default used if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "Simpan artikel ke Nunux Keeper",
@@ -398,6 +399,7 @@
"page.about.author": "Pengembang:",
"page.about.build_date": "Tanggal Penyusunan:",
"page.about.credits": "Pengembang",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Pengaturan Konfigurasi Global",
"page.about.go_version": "Versi Go:",
"page.about.license": "Lisensi:",
@@ -417,9 +419,6 @@
"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."
@@ -427,6 +426,9 @@
"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:",
@@ -529,21 +531,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.shared_entries.title": "Entri yang Dibagikan",
"page.starred.title": "Markah",
"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",
@@ -584,4 +586,4 @@
"time_elapsed.yesterday": "kemarin",
"tooltip.keyboard_shortcuts": "Pintasan Papan Tik: %s",
"tooltip.logged_user": "Masuk sebagai %s"
}
}
+13 -11
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "Apri il link pubblico",
"entry.state.loading": "Caricamento in corso...",
"entry.state.saving": "Salvataggio in corso...",
"entry.status.read": "Letto",
"entry.status.mark_as_read": "Segna come 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.unread": "Da leggere",
"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à.",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.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",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default used if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "Salva gli articoli su Nunux Keeper",
@@ -400,6 +401,7 @@
"page.about.author": "Autore:",
"page.about.build_date": "Data della build:",
"page.about.credits": "Crediti",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Opzioni di configurazione globali",
"page.about.go_version": "Go versione:",
"page.about.license": "Licenza:",
@@ -419,10 +421,6 @@
"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.",
@@ -431,6 +429,10 @@
"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:",
@@ -536,25 +538,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.shared_entries.title": "Voci condivise",
"page.starred.title": "Preferiti",
"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ì",
@@ -601,4 +603,4 @@
"time_elapsed.yesterday": "ieri",
"tooltip.keyboard_shortcuts": "Scorciatoia da tastiera: %s",
"tooltip.logged_user": "Autenticato come %s"
}
}
+12 -10
View File
@@ -69,11 +69,11 @@
"entry.shared_entry.title": "公開リンクを開く",
"entry.state.loading": "読み込み中…",
"entry.state.saving": "保存中…",
"entry.status.read": "既読にする",
"entry.status.mark_as_read": "既読にする",
"entry.status.title": "記事の状態を変更",
"entry.status.toast.read": "既読にしました",
"entry.status.toast.unread": "未読にしました",
"entry.status.unread": "未読にす",
"entry.status.mark_as_unread": "未読にす",
"entry.tags.label": "タグ:",
"entry.unshare.label": "共有を解除",
"error.api_key_already_exists": "この API キーは既に存在します。",
@@ -184,6 +184,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.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",
@@ -252,7 +253,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default used if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "Nunux Keeper に記事を保存する",
@@ -398,6 +399,7 @@
"page.about.author": "作者:",
"page.about.build_date": "ビルド日時:",
"page.about.credits": "著作権表示",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "グローバル構成オプション",
"page.about.go_version": "Go バージョン:",
"page.about.license": "ライセンス:",
@@ -417,9 +419,6 @@
"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 件のフィードがあります。"
@@ -427,6 +426,9 @@
"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 ヘッダー:",
@@ -529,21 +531,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.shared_entries.title": "共有エントリ",
"page.starred.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": "管理者",
@@ -584,4 +586,4 @@
"time_elapsed.yesterday": "昨日",
"tooltip.keyboard_shortcuts": "キーボードショートカット: %s",
"tooltip.logged_user": "%s としてログイン中"
}
}
@@ -69,11 +69,11 @@
"entry.shared_entry.title": "Phah khui kong-khai ê liân-kiat",
"entry.state.loading": "Tng leh chip-hêng…",
"entry.state.saving": "Tng leh pó-chûn…",
"entry.status.read": "Chù chòe tha̍k--kè",
"entry.status.mark_as_read": "Chù chòe tha̍k 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.unread": "Chù chòe ah-bōe tha̍k",
"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",
@@ -184,6 +184,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy siōng koân iu-sian sūn-sū",
"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.pushover_activate": "Push entries to Pushover",
"form.feed.label.pushover_default_priority": "Default priority",
"form.feed.label.pushover_high_priority": "High priority",
@@ -252,7 +253,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon bāng-chí (soán thiⁿ)",
"form.integration.ntfy_internal_links": "Tiám ê sî-chūn iōng lāi-pō͘ liân-kiat (soán thiⁿ)",
"form.integration.ntfy_password": "Ntfy bi̍t-bé (soán thiⁿ)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (chhī-liāu nā bô siat-tēng, tiō iōng ī-siat-ti̍t)",
"form.integration.ntfy_url": "Ntfy bāng-chí (soán thiⁿ, ū-siat sī ntfy.sh)",
"form.integration.ntfy_username": "Ntfy kháu-chō miâ (soán thiⁿ)",
"form.integration.nunux_keeper_activate": "Pó-chûn siau-sit kàu Nunux Keeper",
@@ -398,6 +399,7 @@
"page.about.author": "Chok-chiá: ",
"page.about.build_date": "Kiàn-tì li̍t-kî:",
"page.about.credits": "Pán-koân",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Choân-he̍k siat-tēng soán-hāng",
"page.about.go_version": "Go pán-pún:",
"page.about.license": "Pàng-koân:",
@@ -417,9 +419,6 @@
"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"
@@ -427,6 +426,9 @@
"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:",
@@ -529,21 +531,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.shared_entries.title": "Hun-hióng kè ê siau-sit",
"page.starred.title": "Siu-chông",
"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ī",
@@ -584,4 +586,4 @@
"time_elapsed.yesterday": "cha-hng",
"tooltip.keyboard_shortcuts": "Khoài-sok khí:%s",
"tooltip.logged_user": "Chit-má teng-lo̍k--ê: %s"
}
}
+13 -11
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "Open de openbare link",
"entry.state.loading": "Laden...",
"entry.state.saving": "Opslaan...",
"entry.status.read": "Gelezen",
"entry.status.mark_as_read": "Markeren als gelezen",
"entry.status.title": "Verander artikelstatus",
"entry.status.toast.read": "Gemarkeerd als gelezen",
"entry.status.toast.unread": "Gemarkeerd als ongelezen",
"entry.status.unread": "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.",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy maximale prioriteit",
"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.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",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optioneel)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy wachtwoord (optioneel)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (standaard gebruikt als deze niet is ingesteld in feed)",
"form.integration.ntfy_url": "Ntfy URL (optioneel, standaard is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy gebruikersnaam (optioneel)",
"form.integration.nunux_keeper_activate": "Artikelen opslaan in Nunux Keeper",
@@ -400,6 +401,7 @@
"page.about.author": "Auteur:",
"page.about.build_date": "Compilatiedatum:",
"page.about.credits": "Credits",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Globale Configuratie Opties",
"page.about.go_version": "Go versie:",
"page.about.license": "Licentie:",
@@ -419,10 +421,6 @@
"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.",
@@ -431,6 +429,10 @@
"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:",
@@ -536,25 +538,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.shared_entries.title": "Gedeelde artikelen",
"page.starred.title": "Favorieten",
"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",
@@ -601,4 +603,4 @@
"time_elapsed.yesterday": "gisteren",
"tooltip.keyboard_shortcuts": "Sneltoets: %s",
"tooltip.logged_user": "Ingelogd als %s"
}
}
+26 -24
View File
@@ -73,11 +73,11 @@
"entry.shared_entry.title": "Otwórz publiczne łącze",
"entry.state.loading": "Ładowanie…",
"entry.state.saving": "Zapisywanie…",
"entry.status.read": "Przeczytany",
"entry.status.mark_as_read": "Oznacz jako przeczytany",
"entry.status.title": "Zmień status wpisu",
"entry.status.toast.read": "Oznaczono jako przeczytany",
"entry.status.toast.unread": "Oznaczono jako nieprzeczytany",
"entry.status.unread": "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.",
@@ -188,13 +188,14 @@
"form.feed.label.ntfy_max_priority": "Maksymalny priorytet ntfy",
"form.feed.label.ntfy_min_priority": "Minimalny priorytet ntfy",
"form.feed.label.ntfy_priority": "Priorytet 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_topic": "Temat ntfy (opcjonalny)",
"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",
"form.feed.label.pushover_low_priority": "Niski priorytet Pushover",
"form.feed.label.pushover_max_priority": "Maksymalny priorytet Pushover",
"form.feed.label.pushover_min_priority": "Minimalny priorytet Pushover",
"form.feed.label.pushover_priority": "Priorytet wiadomości Pushover",
"form.feed.label.rewrite_rules": "Reguły zapisu",
"form.feed.label.scraper_rules": "Reguły ekstrakcji",
"form.feed.label.site_url": "Adres URL strony",
@@ -256,7 +257,7 @@
"form.integration.ntfy_icon_url": "Adres URL ikony ntfy (opcjonalny)",
"form.integration.ntfy_internal_links": "Używaj łączy wewnętrznych po kliknięciu (opcjonalnie)",
"form.integration.ntfy_password": "Hasło do ntfy (opcjonalne)",
"form.integration.ntfy_topic": "Temay ntfy",
"form.integration.ntfy_topic": "Temat ntfy (domyślny, jeśli nie został ustawiony w kanale)",
"form.integration.ntfy_url": "Adres URL ntfy (opcjonalny, domyślny to ntfy.sh)",
"form.integration.ntfy_username": "Login do ntfy (opcjonalny)",
"form.integration.nunux_keeper_activate": "Zapisuj wpisy w Nunux Keeper",
@@ -273,11 +274,11 @@
"form.integration.pocket_activate": "Zapisuj wpisy w Pocket",
"form.integration.pocket_connect_link": "Połącz swoje konto Pocket",
"form.integration.pocket_consumer_key": "Klucz klienta do 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": "Prześlij wpisy do Pushover",
"form.integration.pushover_device": "Urządzenie Pushover (opcjonalne)",
"form.integration.pushover_prefix": "Prefiks adresu URL Pushover (opcjonalny)",
"form.integration.pushover_token": "Token API aplikacji Pushover",
"form.integration.pushover_user": "Klucz użytkownika Pushover",
"form.integration.raindrop_activate": "Zapisuj wpisy do Raindrop",
"form.integration.raindrop_collection_id": "Identyfikator kolekcji",
"form.integration.raindrop_tags": "Znaczniki (oddzielone przecinkami)",
@@ -402,6 +403,7 @@
"page.about.author": "Autor:",
"page.about.build_date": "Data opracowania:",
"page.about.credits": "Prawa autorskie",
"page.about.db_usage": "Rozmiar bazy danych:",
"page.about.global_config_options": "Globalne opcje konfiguracji",
"page.about.go_version": "Wersja Go:",
"page.about.license": "Licencja:",
@@ -421,11 +423,6 @@
"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ł.",
@@ -435,6 +432,11 @@
"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:",
@@ -543,29 +545,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.shared_entries.title": "Udostępnione wpisy",
"page.starred.title": "Ulubione",
"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",
@@ -618,4 +620,4 @@
"time_elapsed.yesterday": "wczoraj",
"tooltip.keyboard_shortcuts": "Skróty klawiszowe: %s",
"tooltip.logged_user": "Zalogowany jako %s"
}
}
+13 -11
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "Abrir link público",
"entry.state.loading": "Carregando...",
"entry.state.saving": "Salvando...",
"entry.status.read": "Lido",
"entry.status.mark_as_read": "Marcar como 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.unread": "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.",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.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",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "Salvar itens no Nunux Keeper",
@@ -400,6 +401,7 @@
"page.about.author": "Autor:",
"page.about.build_date": "Compilado em:",
"page.about.credits": "Créditos",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "opções de configuração global",
"page.about.go_version": "Go versão:",
"page.about.license": "Licença:",
@@ -419,10 +421,6 @@
"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.",
@@ -431,6 +429,10 @@
"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':",
@@ -536,25 +538,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.shared_entries.title": "Itens compartilhados",
"page.starred.title": "Favoritos",
"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",
@@ -601,4 +603,4 @@
"time_elapsed.yesterday": "ontem",
"tooltip.keyboard_shortcuts": "Atalho do teclado: %s",
"tooltip.logged_user": "Autenticado como %s"
}
}
+623
View File
@@ -0,0 +1,623 @@
{
"action.cancel": "abandon",
"action.download": "Descărcare",
"action.edit": "Editare",
"action.home_screen": "Adaugă pe ecranul principal",
"action.import": "Importă",
"action.login": "Autentificare",
"action.or": "sau",
"action.remove": "Elimină",
"action.remove_feed": "Elimină acest flux",
"action.save": "Salvează",
"action.subscribe": "Abonează-te",
"action.update": "Actualizare",
"alert.account_linked": "Contul dvs. extern este atașat!",
"alert.account_unlinked": "Am decuplat contul dvs. extern!",
"alert.background_feed_refresh": "Toate fluxurile sunt actualizate în fundal. Puteți să continuați utilizarea Miniflux în timp ce procesul rulează.",
"alert.feed_error": "Este o problemă cu acest flux",
"alert.no_bookmark": "Nu sunt înregistrări marcate.",
"alert.no_category": "Nu sunt categorii.",
"alert.no_category_entry": "Nu sunt înregistrări în această categorie.",
"alert.no_feed": "Nu aveți fluxuri.",
"alert.no_feed_entry": "Nu sunt înregistrări pentru acest flux.",
"alert.no_feed_in_category": "Nu sunt fluxuri pentru această categorie.",
"alert.no_history": "Nu există istoric în acest moment.",
"alert.no_search_result": "Nu există înregistrări pentru această căutare.",
"alert.no_shared_entry": "Nu sunt înregistrări partajate.",
"alert.no_tag_entry": "Nu sunt înregistrări pentru această etichetă.",
"alert.no_unread_entry": "Nu sunt intrări necitite.",
"alert.no_user": "Sunteți singurul utilizator.",
"alert.pocket_linked": "Contul dvs. Pocket este atașat!",
"alert.prefs_saved": "Preferințe salvate!",
"alert.too_many_feeds_refresh": [
"Ați activat actualizarea a prea multe fluxuri de informații. Vă rog să așteptați %d minut înainte de a reîncerca.",
"Ați activat actualizarea a prea multe fluxuri de informații. Vă rog să așteptați %d minute înainte de a reîncerca.",
"Ați activat actualizarea a prea multe fluxuri de informații. Vă rog să așteptați %d minute înainte de a reîncerca."
],
"confirm.loading": "În progres…",
"confirm.no": "nu",
"confirm.question": "Suneți sigur?",
"confirm.question.refresh": "Sunteți sigur că vreți să forțați reîmprospătarea?",
"confirm.yes": "da",
"enclosure_media_controls.seek": "Caută:",
"enclosure_media_controls.seek.title": "Caută %s secunde",
"enclosure_media_controls.speed": "Viteză:",
"enclosure_media_controls.speed.faster": "Mai rapid",
"enclosure_media_controls.speed.faster.title": "Mai rapid cu %sx",
"enclosure_media_controls.speed.reset": "Resetare",
"enclosure_media_controls.speed.reset.title": "Resetare viteză la 1x",
"enclosure_media_controls.speed.slower": "Mai încet",
"enclosure_media_controls.speed.slower.title": "Mai încet cu %sx",
"entry.bookmark.toast.off": "Fără stea",
"entry.bookmark.toast.on": "Cu stea",
"entry.bookmark.toggle.off": "Fără stea",
"entry.bookmark.toggle.on": "Stea",
"entry.comments.label": "Comentarii",
"entry.comments.title": "Vizualizare Comentarii",
"entry.estimated_reading_time": [
"%d minut de lectură",
"%d minute de lectură",
"%d minut de lectură"
],
"entry.external_link.label": "Legătură externă",
"entry.save.completed": "Gata!",
"entry.save.label": "Salvare",
"entry.save.title": "Salvez această înregistrare",
"entry.save.toast.completed": "Înregistrare salvată",
"entry.scraper.completed": "Gata!",
"entry.scraper.label": "Descărcare",
"entry.scraper.title": "Descarcă conținutul original",
"entry.share.label": "Partajare",
"entry.share.title": "Partajează această înregistrare",
"entry.shared_entry.label": "Partajare",
"entry.shared_entry.title": "Deschide legătura publică",
"entry.state.loading": "Încarc…",
"entry.state.saving": "Salvez…",
"entry.status.mark_as_read": "Marcați ca citit",
"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.",
"error.bad_credentials": "Utilizator sau parolă invalide.",
"error.category_already_exists": "Această categorie există deja.",
"error.category_not_found": "Această categorie nu există sau nu aparține acestui utilizator.",
"error.database_error": "Eroare bază de date: %v.",
"error.different_passwords": "Parolele nu sunt identice.",
"error.duplicate_fever_username": "Este deja cineva cu același cont de Fever!",
"error.duplicate_googlereader_username": "Este deja cineva cu același nume de utilizator Google Reader!",
"error.duplicate_linked_account": "Este deja cineva asociat cu acest furnizor!",
"error.duplicated_feed": "Acest flux există deja.",
"error.empty_file": "Acest fișier este gol.",
"error.entries_per_page_invalid": "Numărul de înregistrări de pe pagină nu este valid.",
"error.feed_already_exists": "Acest flux există deja.",
"error.feed_category_not_found": "Această categorie nu există sau nu aparține utilizatorului.",
"error.feed_format_not_detected": "Nu pot detecta formatul fluxului: %v.",
"error.feed_invalid_blocklist_rule": "Blocul listei de reguli este invalid.",
"error.feed_invalid_keeplist_rule": "Lista de reguli keep este invalidă.",
"error.feed_mandatory_fields": "Adresa URL și categoria sunt obligatorii.",
"error.feed_not_found": "Acest flux nu există sau un aparține acestui utilizator.",
"error.feed_title_not_empty": "Titlul fluxului nu poate fi gol.",
"error.feed_url_not_empty": "Adresa URL a fluxului nu poate fi goală.",
"error.fields_mandatory": "Toate câmpurile sunt obligatorii.",
"error.http_bad_gateway": "Acest site web nu este disponibil momentan din cauza unei erori generată de gateway. Problema nu este de la Miniflux. Vă rugăm să reîncercați mai târziu.",
"error.http_body_read": "Nu pot citi corpul HTTP: %v.",
"error.http_client_error": "Eroare client HTTP: %v.",
"error.http_empty_response": "Răspunsul HTTP este gol. Poate acest site web utilizează un mecanism împotriva boților?",
"error.http_empty_response_body": "Corpul răspunsului HTTP este gol.",
"error.http_forbidden": "Accesul la acest site web este interzis. Poate acesta utilizează un mecanism împotriva boților?",
"error.http_gateway_timeout": "Acest site web nu este disponibil momentan din cauza unei erori generată de gateway. Problema nu este de la Miniflux. Vă rugăm să reîncercați mai târziu.",
"error.http_internal_server_error": "Acest site web nu este disponibil momentan din cauza unei erori generată de server. Problema nu este de la Miniflux. Vă rugăm să reîncercați mai târziu.",
"error.http_not_authorized": "Accesul la acest site nu este autorizat. Poate fi din cauza parolei sau a userului greșite.",
"error.http_resource_not_found": "Resursa solicitată nu este găsită. Vă rog să verificați URL-ul.",
"error.http_response_too_large": "Răspunsul HTTP este prea mare. Puteți crește dimensiunea acestuia în setările globale (necesită repornirea server-ului).",
"error.http_service_unavailable": "Acest site web nu este disponibil momentan din cauza unei erori generată de server. Problema nu este de la Miniflux. Vă rugăm să reîncercați mai târziu.",
"error.http_too_many_requests": "Miniflux a generat prea multe solicitări pe acest site web. Vă rog, încercați mai tîrziu sau modificați configurațiile aplicației.",
"error.http_unexpected_status_code": "Acest site web nu este disponibil momentan din cauza unei erori HTTP: %d. Problema nu este de la Miniflux. Vă rugăm să reîncercați mai târziu.",
"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_url": "Adresa URL a fluxului este invalidă.",
"error.invalid_gesture_nav": "Gest de navigare invalid.",
"error.invalid_language": "Limbă invalidă.",
"error.invalid_site_url": "Adresa URL a site-ului este invalidă.",
"error.invalid_theme": "Temă invalidă.",
"error.invalid_timezone": "Dată/oră invalide.",
"error.network_operation": "Miniflux nu poate ajunge la acest site din cauza unei erori de rețea: %v.",
"error.network_timeout": "Acest site web este prea lent și conexiunea nu s-a realizat: %v",
"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.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",
"error.settings_block_rule_separator_required": "Regulă de bloc invalidă: modelul regulii #%d's trebuie separat de '='",
"error.settings_invalid_domain_list": "Lista domeniilor invalidă. Vă rugăm să ne furnizați o listă separată de virgulă a domeniilor.",
"error.settings_keep_rule_fieldname_invalid": "Regulă Keep invalidă: regulii #%d îi lipsește un nume valid (Opțiuni: %s)",
"error.settings_keep_rule_invalid_regex": "Regulă Keep invalidă: modelul regulii #%d's nu este regex valid",
"error.settings_keep_rule_regex_required": "Regulă Keep invalidă: modelul regulii #%d nu este furnizat",
"error.settings_keep_rule_separator_required": "Regulă Keep invalidă: modelul regulii #%d's trebuie separat de'='",
"error.settings_mandatory_fields": "Numele utilizatorului, tema, limba și fusul orar sunt obligatorii.",
"error.settings_media_playback_rate_range": "Viteza de rulare nu este validă",
"error.settings_reading_speed_is_positive": "Vitezele de citire trebuie să fie numere întregi pozitive.",
"error.site_url_not_empty": "Adresa URL a site-ului nu poate fi goală.",
"error.subscription_not_found": "Nu se poate găsi nici un flux.",
"error.title_required": "Titlul este obligatoriu.",
"error.tls_error": "Eroare TLS: %q. Puteți dezactiva verificarea TLS în setările fluxurilor dacă doriți.",
"error.unable_to_create_api_key": "Nu pot crea această cheie API.",
"error.unable_to_create_category": "Nu se poate crea această categorie.",
"error.unable_to_create_user": "Nu se poate crea utilizatorul.",
"error.unable_to_detect_rssbridge": "Nu pot detecta fluxul când utilizez RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Nu pot procesa acest flux: %v.",
"error.unable_to_update_category": "Nu se poate actualiza această categorie.",
"error.unable_to_update_feed": "Nu se poate actualiza acest flux.",
"error.unable_to_update_user": "Nu se poate actualiza utilizatorul.",
"error.unlink_account_without_password": "Trebuie să definiți o parolă, altfel nu vă veți mai putea conecta.",
"error.user_already_exists": "Acest utilizator există deja.",
"error.user_mandatory_fields": "Numele utilizatorului este obligatoriu.",
"form.api_key.label.description": "Etichetă Cheie API",
"form.category.hide_globally": "Ascunde intrările în lista globală de articole necitite",
"form.category.label.title": "Titlu",
"form.feed.fieldset.general": "General",
"form.feed.fieldset.integration": "Servicii Terțe",
"form.feed.fieldset.network_settings": "Setări Rețea",
"form.feed.fieldset.rules": "Reguli",
"form.feed.label.allow_self_signed_certificates": "Permite certificatele auto-semnate sau invalide",
"form.feed.label.apprise_service_urls": "Lista de URL-uri ale serviciilor Apprise separate prin virgule",
"form.feed.label.blocklist_rules": "Reguli de Blocare",
"form.feed.label.category": "Categorie",
"form.feed.label.cookie": "Setare Cookie-uri",
"form.feed.label.crawler": "Aduce conținutul original",
"form.feed.label.description": "Descriere",
"form.feed.label.disable_http2": "Dezactivează HTTP/2 pentru a preveni amprentarea",
"form.feed.label.disabled": "Nu actualiza acest flux",
"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.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",
"form.feed.label.no_media_player": "Nu există player media (audio/video)",
"form.feed.label.ntfy_activate": "Împinge intrările la ntfy",
"form.feed.label.ntfy_default_priority": "Prioritate predefinită Ntfy",
"form.feed.label.ntfy_high_priority": "Prioritate ridicată Ntfy",
"form.feed.label.ntfy_low_priority": "Prioritate redusă Ntfy",
"form.feed.label.ntfy_max_priority": "Prioritate maximă Ntfy",
"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.pushover_activate": "Activează Pushover",
"form.feed.label.pushover_default_priority": "Prioritate implicită Pushover",
"form.feed.label.pushover_high_priority": "Prioritate ridicată Pushover",
"form.feed.label.pushover_low_priority": "Prioritate redusă Pushover",
"form.feed.label.pushover_max_priority": "Prioritate maximă Pushover",
"form.feed.label.pushover_min_priority": "Prioritate minimă Pushover",
"form.feed.label.pushover_priority": "Prioritate Pushover",
"form.feed.label.rewrite_rules": "Rescrie Regulile",
"form.feed.label.scraper_rules": "Reguli de Eliminare",
"form.feed.label.site_url": "Adresă URL",
"form.feed.label.title": "Titlu",
"form.feed.label.urlrewrite_rules": "URL Reguli de Rescriere",
"form.feed.label.user_agent": "Suprascrie User Agent Predefinit",
"form.feed.label.webhook_url": "URL Webhook (pentru a primi notificări despre evenimentele de intrare)",
"form.import.label.file": "Fișier OPML",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Trimite înregistrările pe Apprise",
"form.integration.apprise_services_url": "URL-uri separate de virgulă cu servicii Apprise",
"form.integration.apprise_url": "URL API Apprise",
"form.integration.betula_activate": "Salvează înregistrările în Betula",
"form.integration.betula_token": "Token Betula",
"form.integration.betula_url": "Adresă server Betula",
"form.integration.cubox_activate": "Salvează intrările în Cubox",
"form.integration.cubox_api_link": "Link APi Cubox",
"form.integration.discord_activate": "Împinge intrările pe Discord",
"form.integration.discord_webhook_link": "Link Webhook Discord",
"form.integration.espial_activate": "Save entries to Espial",
"form.integration.espial_api_key": "Cheie API Espial",
"form.integration.espial_endpoint": "Punct acces API Espial",
"form.integration.espial_tags": "Etichete Espial",
"form.integration.fever_activate": "Activează API Fever",
"form.integration.fever_endpoint": "Punct access API Fever:",
"form.integration.fever_password": "Parolă Fever",
"form.integration.fever_username": "Utilizator Fever",
"form.integration.googlereader_activate": "Activează API Google Reader",
"form.integration.googlereader_endpoint": "Punct acces API Google Reader:",
"form.integration.googlereader_password": "Parolă Google Reader",
"form.integration.googlereader_username": "Utilizator Google Reader",
"form.integration.instapaper_activate": "Salvează înregistrările pe Instapaper",
"form.integration.instapaper_password": "Parolă Instapaper",
"form.integration.instapaper_username": "Utilizator Instapaper",
"form.integration.linkace_activate": "Salvează intrările în LinkAce",
"form.integration.linkace_api_key": "Cheie API LinkAce",
"form.integration.linkace_check_disabled": "Dezactivează verificarea link-urilor",
"form.integration.linkace_endpoint": "Endpoint API LinkAce",
"form.integration.linkace_is_private": "Marchează link-urile ca private",
"form.integration.linkace_tags": "Tag-uri LinkAce",
"form.integration.linkding_activate": "Salvează intrările în Linkding",
"form.integration.linkding_api_key": "Cheie API Linkding",
"form.integration.linkding_bookmark": "Marchează semnele de carte ca necitite",
"form.integration.linkding_endpoint": "Endpoint API Linkding",
"form.integration.linkding_tags": "TAG-uri Linkding",
"form.integration.linkwarden_activate": "Salvează intrările în Linkwarden",
"form.integration.linkwarden_api_key": "Cheie API Linkwarden",
"form.integration.linkwarden_endpoint": "Endpoint API Linkwarden",
"form.integration.matrix_bot_activate": "Împinge intrările noi pe Matrix",
"form.integration.matrix_bot_chat_id": "ID-ul Camerei Matrix",
"form.integration.matrix_bot_password": "Parola utilizatorului Matrix",
"form.integration.matrix_bot_url": "Server URL Matrix",
"form.integration.matrix_bot_user": "Utilizator Matrix",
"form.integration.notion_activate": "Salvează înregistrările în Notion",
"form.integration.notion_page_id": "ID Pagină Notion",
"form.integration.notion_token": "Token Secret Notion",
"form.integration.ntfy_activate": "Împinge intrările pe ntfy",
"form.integration.ntfy_api_token": "Token API Ntfy (opțional)",
"form.integration.ntfy_icon_url": "Icon URL Ntfy (opțional)",
"form.integration.ntfy_internal_links": "Utilizează legături interne la clic (opțional)",
"form.integration.ntfy_password": "Parolă Ntfy (opțional)",
"form.integration.ntfy_topic": "Topic Ntfy",
"form.integration.ntfy_url": "URL Ntfy (opțional, predefinit este ntfy.sh)",
"form.integration.ntfy_username": "Utilizator Ntfy (opțional)",
"form.integration.nunux_keeper_activate": "Salvează înregistrările în Nunux Keeper",
"form.integration.nunux_keeper_api_key": "Cheie API Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Punct de acces API Keeper",
"form.integration.omnivore_activate": "Salvare înregistrări în Omnivore",
"form.integration.omnivore_api_key": "Cheie API Omnivore",
"form.integration.omnivore_url": "Punct acces API Omnivore",
"form.integration.pinboard_activate": "Salvează intrările în Pinboard",
"form.integration.pinboard_bookmark": "Marchează bookmark ca necitit",
"form.integration.pinboard_tags": "Etichete Pinboard",
"form.integration.pinboard_token": "Token API Pinboard",
"form.integration.pocket_access_token": "Token Acces Pocket",
"form.integration.pocket_activate": "Salvează înregistrările în Pocket",
"form.integration.pocket_connect_link": "Conectează contul Pocket personal",
"form.integration.pocket_consumer_key": "Cheie Consumator Pocket",
"form.integration.pushover_activate": "Activează Pushover",
"form.integration.pushover_device": "Dispozitiv Pushover (opțional)",
"form.integration.pushover_prefix": "Prefix Pushover (opțional)",
"form.integration.pushover_token": "Token Pushover",
"form.integration.pushover_user": "Utilizator Pushover",
"form.integration.raindrop_activate": "Salvează intrările în Raindrop",
"form.integration.raindrop_collection_id": "ID Colecție",
"form.integration.raindrop_tags": "Tag-uri (separate de virgulă)",
"form.integration.raindrop_token": "(Test) Token",
"form.integration.readeck_activate": "Salvează intrările în readeck",
"form.integration.readeck_api_key": "Cheie API Readeck",
"form.integration.readeck_endpoint": "URL Readeck",
"form.integration.readeck_labels": "Etichete Readeck",
"form.integration.readeck_only_url": "Trimite numai URL (în loc de tot conținutul)",
"form.integration.readwise_activate": "Salvare înregistrări în Readwise Reader",
"form.integration.readwise_api_key": "Token Acces Readwise Reader",
"form.integration.readwise_api_key_link": "Obțineți Token-ul de Acess pe Readwise",
"form.integration.rssbridge_activate": "Verifică RSS-Bridge la adăugarea de abonamente",
"form.integration.rssbridge_url": "URL server RSS-Bridge",
"form.integration.shaarli_activate": "Salvează articolele în Shaarli",
"form.integration.shaarli_api_secret": "Secret API Shaarli",
"form.integration.shaarli_endpoint": "URL Shaarli",
"form.integration.shiori_activate": "Salvează articolele în Shiori",
"form.integration.shiori_endpoint": "Endpoint API Shiori",
"form.integration.shiori_password": "Parolă Shiori",
"form.integration.shiori_username": "Utilizator Shiori",
"form.integration.slack_activate": "Împinge intrările pe Slack",
"form.integration.slack_webhook_link": "Link Webhook Slack",
"form.integration.telegram_bot_activate": "Împingeți înregistrările noi pe chat-ul Telegram",
"form.integration.telegram_bot_disable_buttons": "Dezactivează butoanele",
"form.integration.telegram_bot_disable_notification": "Dezactivează notificările",
"form.integration.telegram_bot_disable_web_page_preview": "Dezactivează previzualizarea paginii web",
"form.integration.telegram_bot_token": "Token Bot",
"form.integration.telegram_chat_id": "ID Chat",
"form.integration.telegram_topic_id": "ID Topic",
"form.integration.wallabag_activate": "Salvează înregistrările în Wallabag",
"form.integration.wallabag_client_id": "ID Client Wallabag",
"form.integration.wallabag_client_secret": "Secret Client Wallabag",
"form.integration.wallabag_endpoint": "URL Wallabag",
"form.integration.wallabag_only_url": "Trimite numai URL-ul (fără conținut complet)",
"form.integration.wallabag_password": "Parolă Wallabag",
"form.integration.wallabag_username": "Utilizator Wallabag",
"form.integration.webhook_activate": "Activează Webhook",
"form.integration.webhook_secret": "Secret Webhook",
"form.integration.webhook_url": "URL Webhook",
"form.prefs.fieldset.application_settings": "Setări Aplicație",
"form.prefs.fieldset.authentication_settings": "Setări Autentificare",
"form.prefs.fieldset.global_feed_settings": "Setări Globale pt. Flux",
"form.prefs.fieldset.reader_settings": "Setări Citire",
"form.prefs.help.external_font_hosts": "Lista fonturilor de pe gazdă separate de virgulă care poate fi utilizate. De exemplu: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.categories_sorting_order": "Sortare categorii",
"form.prefs.label.cjk_reading_speed": "Viteză de citire pentru Chineză, Coreană și Japoneză (caractere pe minut)",
"form.prefs.label.custom_css": "CSS personalizat",
"form.prefs.label.custom_js": "JavaScript personalizat",
"form.prefs.label.default_home_page": "Pagina pornire predefinită",
"form.prefs.label.default_reading_speed": "Viteză de citire pentru alte limbi (cuvinte pe minut)",
"form.prefs.label.display_mode": "Mod afișare Aplicație Web Progresivă (PWA)",
"form.prefs.label.entries_per_page": "Intrări pe pagină",
"form.prefs.label.entry_order": "Coloană de sortare",
"form.prefs.label.entry_sorting": "Sortare intrări",
"form.prefs.label.entry_swipe": "Activare glisare pentru ecranele tactile",
"form.prefs.label.external_font_hosts": "Fonturi externe gazdă",
"form.prefs.label.gesture_nav": "Gesturi pentru navigare între înregistrări",
"form.prefs.label.keyboard_shortcuts": "Activare scurtături tastatură",
"form.prefs.label.language": "Limbă",
"form.prefs.label.mark_read_manually": "Marchează manual intrările ca citite",
"form.prefs.label.mark_read_on_media_completion": "Marchează ca citit numai când redarea de conținut audio/video atinge 90%%",
"form.prefs.label.mark_read_on_view": "Marchează intrările ca citite la vizualizare",
"form.prefs.label.mark_read_on_view_or_media_completion": "Marchează intrările ca citite la vizualizare. Pentru audio/video, marchează ca citit la redarea a 90%% de conținut",
"form.prefs.label.media_playback_rate": "Viteza de rulare audio/video",
"form.prefs.label.show_reading_time": "Afișare timp estimat de citire pentru înregistrări",
"form.prefs.label.theme": "Temă",
"form.prefs.label.timezone": "Fus orar",
"form.prefs.select.alphabetical": "Alfabetic",
"form.prefs.select.browser": "Browser",
"form.prefs.select.created_time": "Dată creare înregistrare",
"form.prefs.select.fullscreen": "Ecran complet",
"form.prefs.select.minimal_ui": "Minim",
"form.prefs.select.none": "Nimic",
"form.prefs.select.older_first": "Intrările mai vechi la început",
"form.prefs.select.publish_time": "Data publicare înregistrare",
"form.prefs.select.recent_first": "Intrările mai noi la început",
"form.prefs.select.standalone": "Independent",
"form.prefs.select.swipe": "Glisare",
"form.prefs.select.tap": "Apăsare dublă",
"form.prefs.select.unread_count": "Contor necitite",
"form.submit.loading": "Încarc…",
"form.submit.saving": "Salvez…",
"form.user.label.admin": "Administrator",
"form.user.label.confirmation": "Confirmare Parolă",
"form.user.label.password": "Parolă",
"form.user.label.username": "Nume utilizator",
"menu.about": "Despre",
"menu.add_feed": "Adaugă flux",
"menu.add_user": "Adaugă utilizator",
"menu.api_keys": "Chei API",
"menu.categories": "Categorii",
"menu.create_api_key": "Crează o nouă cheie API",
"menu.create_category": "Crează o categorie",
"menu.edit_category": "Editare",
"menu.edit_feed": "Editare",
"menu.export": "Exportă",
"menu.feed_entries": "Intrări",
"menu.feeds": "Fluxuri",
"menu.flush_history": "Elimină istoricul",
"menu.history": "Istoric",
"menu.home_page": "Pagina principală",
"menu.import": "Importă",
"menu.integrations": "Integrări",
"menu.logout": "Deconectare",
"menu.mark_all_as_read": "Marchează tot ca citit",
"menu.mark_page_as_read": "Marchează această pagină ca citită",
"menu.preferences": "Preferințe",
"menu.refresh_all_feeds": "Reînnoiește toate fluxurile în fundal",
"menu.refresh_feed": "Reînnoire",
"menu.search": "Caută",
"menu.sessions": "Sesiuni",
"menu.settings": "Setări",
"menu.shared_entries": "Intrări partajate",
"menu.show_all_entries": "Afișează toate intrările",
"menu.show_only_starred_entries": "Afișează numai intrările marcate",
"menu.show_only_unread_entries": "Afișează numai intrările necitite",
"menu.starred": "Marcat",
"menu.title": "Meniu",
"menu.unread": "Necitit",
"menu.users": "Utilizatori",
"page.about.author": "Autor:",
"page.about.build_date": "Dată Build:",
"page.about.credits": "Credit",
"page.about.db_usage": "Utilizare Bază de Date",
"page.about.global_config_options": "Opțiuni globale de configurare",
"page.about.go_version": "Versiune Go:",
"page.about.license": "Licență:",
"page.about.postgres_version": "Versiune Postgres:",
"page.about.title": "Despre",
"page.about.version": "Versiune:",
"page.add_feed.choose_feed": "Alegeți un flux",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Opțiuni Avansate",
"page.add_feed.no_category": "Nu există categorii. Trebuie să aveți măcar o categorie.",
"page.add_feed.submit": "Găsește un flux",
"page.add_feed.title": "Flux nou",
"page.api_keys.never_used": "Niciodată Utilizată",
"page.api_keys.table.actions": "Acțiuni",
"page.api_keys.table.created_at": "Dată Creare",
"page.api_keys.table.description": "Descriere",
"page.api_keys.table.last_used_at": "Utilizat ultima dată",
"page.api_keys.table.token": "Token",
"page.api_keys.title": "Chei API",
"page.categories_count": [
"%d categorie",
"%d categorii",
"%d categorie găsită"
],
"page.categories.entries": "Intrări",
"page.categories.feed_count": [
"Este %d flux.",
"Sunt %d fluxuri.",
"Sunt %d fluxuri găsite."
],
"page.categories.feeds": "Fluxuri",
"page.categories.no_feed": "Nici un flux.",
"page.categories.title": "Categorii",
"page.category_label": "Categorie: %s",
"page.edit_category.title": "Editare Categorie: %s",
"page.edit_feed.etag_header": "Antet ETag:",
"page.edit_feed.last_check": "Ultima verificare:",
"page.edit_feed.last_modified_header": "UltimaModificare antet:",
"page.edit_feed.last_parsing_error": "Ultima Eroare la Analiză",
"page.edit_feed.no_header": "Nimic",
"page.edit_feed.title": "Editare Flux: %s",
"page.edit_user.title": "Editare Utilizator: %s",
"page.entry.attachments": "Atașamente",
"page.feeds.error_count": [
"%d eroare",
"%d erori",
"%d erori găsite"
],
"page.feeds.last_check": "Ultima verificare:",
"page.feeds.next_check": "Următoarea verificare:",
"page.feeds.read_counter": "Numărul de intrări citite",
"page.feeds.title": "Fluxuri",
"page.history.title": "Istoric",
"page.import.title": "Import",
"page.integration.bookmarklet": "Marcaje",
"page.integration.bookmarklet.help": "Această legătură specială permite să vă abonați direct pe un site prin utilizarea unui marcaj în browser-ul web.",
"page.integration.bookmarklet.instructions": "Trageți legătura în favorite.",
"page.integration.bookmarklet.name": "Adaugă în Miniflux",
"page.integration.miniflux_api": "API Miniflux",
"page.integration.miniflux_api_endpoint": "Punct de acces API",
"page.integration.miniflux_api_password": "Parolă",
"page.integration.miniflux_api_password_value": "Parola contului",
"page.integration.miniflux_api_username": "Utilizator",
"page.integrations.title": "Integrări",
"page.keyboard_shortcuts.close_modal": "Închide fereastra de dialog",
"page.keyboard_shortcuts.download_content": "Descarcă conținutul original",
"page.keyboard_shortcuts.go_to_bottom_item": "Du-te la ultimul obiect",
"page.keyboard_shortcuts.go_to_categories": "Du-te la categorii",
"page.keyboard_shortcuts.go_to_feed": "Du-te la flux",
"page.keyboard_shortcuts.go_to_feeds": "Du-te la fluxuri",
"page.keyboard_shortcuts.go_to_history": "Du-te la istoric",
"page.keyboard_shortcuts.go_to_next_item": "Du-te la obiectul următor",
"page.keyboard_shortcuts.go_to_next_page": "Du-te la pagina următoare",
"page.keyboard_shortcuts.go_to_previous_item": "Du-te la obiectul anterior",
"page.keyboard_shortcuts.go_to_previous_page": "Du-te la pagina anterioară",
"page.keyboard_shortcuts.go_to_search": "Focusul pe formularul de căutare",
"page.keyboard_shortcuts.go_to_settings": "Du-te la setări",
"page.keyboard_shortcuts.go_to_starred": "Du-te la marcat",
"page.keyboard_shortcuts.go_to_top_item": "Du-te la primul obiect",
"page.keyboard_shortcuts.go_to_unread": "Du-te la necitit",
"page.keyboard_shortcuts.mark_page_as_read": "Marchează pagina curentă ca citită",
"page.keyboard_shortcuts.open_comments": "Deschide comentariile link-ului",
"page.keyboard_shortcuts.open_comments_same_window": "Deschide comentariile link-ului în tab-ul curent",
"page.keyboard_shortcuts.open_item": "Deschide obiectul selectat",
"page.keyboard_shortcuts.open_original": "Deschide link-ul original",
"page.keyboard_shortcuts.open_original_same_window": "Deschide link-ul original în tab-ul curent",
"page.keyboard_shortcuts.refresh_all_feeds": "Reîncarcă toate fluxurile în fundal",
"page.keyboard_shortcuts.remove_feed": "Elimină acest flux",
"page.keyboard_shortcuts.save_article": "Salvare înregistrare",
"page.keyboard_shortcuts.scroll_item_to_top": "Derulează obiectul la început",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Afișează scurtăturile tastaturii",
"page.keyboard_shortcuts.subtitle.actions": "Acțiuni",
"page.keyboard_shortcuts.subtitle.items": "Navigare Obiecte",
"page.keyboard_shortcuts.subtitle.pages": "Navigare Pagini",
"page.keyboard_shortcuts.subtitle.sections": "Navigare Secțiuni",
"page.keyboard_shortcuts.title": "Scurtături Tastatură",
"page.keyboard_shortcuts.toggle_bookmark_status": "Comută marcate",
"page.keyboard_shortcuts.toggle_entry_attachments": "Comută deschis/închis pe atașamentele înregistrării",
"page.keyboard_shortcuts.toggle_read_status_next": "Comută citit/necitit focus următor",
"page.keyboard_shortcuts.toggle_read_status_prev": "Comută citit/necitit, focus anterior",
"page.login.google_signin": "Conectare cu Google",
"page.login.oidc_signin": "Conectare cu %s",
"page.login.title": "Conectare",
"page.login.webauthn_login": "Conectare cu cheia de acces",
"page.login.webauthn_login.error": "Eroare la conectarea cu cheia de acces",
"page.login.webauthn_login.help": "Vă rog să introduceți numele utilizatorului dacă utilizați o cheie. Nu este necesară dacă utilizați o cheie de acces (credențiale descoperibile).",
"page.new_api_key.title": "Cheie API Nouă",
"page.new_category.title": "Categorie Nouă",
"page.new_user.title": "Utilizator Nou",
"page.offline.message": "Sunteți offline",
"page.offline.refresh_page": "Încercați să reîmprospătați pagina",
"page.offline.title": "Mod Offline",
"page.read_entry_count": [
"%d înregistrare citită",
"%d înregistrări citite",
"%d înregistrări citite"
],
"page.search.title": "Rezultate Căutare",
"page.sessions.table.actions": "Acțiuni",
"page.sessions.table.current_session": "Sesiunea Curentă",
"page.sessions.table.date": "Dată",
"page.sessions.table.ip": "Adresă IP",
"page.sessions.table.user_agent": "Agent Utilizator",
"page.sessions.title": "Sesiuni",
"page.settings.link_google_account": "Atașează contul personal Google",
"page.settings.link_oidc_account": "Atașează contul meu %s",
"page.settings.title": "Setări",
"page.settings.unlink_google_account": "Decuplează contul personal Google",
"page.settings.unlink_oidc_account": "Decuplează contul meu %s",
"page.settings.webauthn.actions": "Acțiuni",
"page.settings.webauthn.added_on": "Adăugată în",
"page.settings.webauthn.delete": [
"Elimină %d cheie de acces",
"Elimină %d chei de acces",
"Elimină %d chei de acces"
],
"page.settings.webauthn.last_seen_on": "Utilizat ultima dată",
"page.settings.webauthn.passkey_name": "Nume cheie acces",
"page.settings.webauthn.passkeys": "Chei Acces",
"page.settings.webauthn.register": "Înregistrare cheie acces",
"page.settings.webauthn.register.error": "Eroare la înregistrarea cheii de acces",
"page.shared_entries_count": [
"%d înregistrare partajată",
"%d înregistrări partajate",
"%d înregistrări partajate"
],
"page.shared_entries.title": "Înregistrări partajate",
"page.starred_entry_count": [
"%d înregistrare marcată",
"%d Înregistrări marcate",
"%d Înregistrări marcate"
],
"page.starred.title": "Marcate",
"page.total_entry_count": [
"%d intrare în total",
"%d intrări în total",
"%d intrări în total"
],
"page.unread_entry_count": [
"%d înregistrare necitită",
"%d înregistrări necitite",
"%d înregistrări necitite"
],
"page.unread.title": "Necitite",
"page.users.actions": "Acțiuni",
"page.users.admin.no": "Nu",
"page.users.admin.yes": "Da",
"page.users.is_admin": "Administrator",
"page.users.last_login": "Ultima Conectare",
"page.users.never_logged": "Niciodată",
"page.users.title": "Utilizatori",
"page.users.username": "Nume",
"page.webauthn_rename.title": "Redenumire Cheie Acces",
"pagination.first": "Prima",
"pagination.last": "Ultima",
"pagination.next": "Următor",
"pagination.previous": "Anterior",
"search.label": "Caută",
"search.placeholder": "Caută…",
"search.submit": "Caută",
"skip_to_content": "Sari la conținut",
"time_elapsed.days": [
"%d zi în urmă",
"%d zile în urmă",
"%d zile în urmă"
],
"time_elapsed.hours": [
"%d oră în urmă",
"%d ore în urmă",
"%d ore în urmă"
],
"time_elapsed.minutes": [
"%d minut în urmă",
"%d minute în urmă",
"%d minute în urmă"
],
"time_elapsed.months": [
"%d lună în urmă",
"%d luni în urmă",
"%d luni în urmă"
],
"time_elapsed.not_yet": "încă nu",
"time_elapsed.now": "chiar acum",
"time_elapsed.weeks": [
"%d săptămână în urmă",
"%d săptămâni în urmă",
"%d săptămâni în urmă"
],
"time_elapsed.years": [
"%d an în urmă",
"%d ani în urmă",
"%d ani în urmă"
],
"time_elapsed.yesterday": "ieri",
"tooltip.keyboard_shortcuts": "Scurtături Tastatură: %s",
"tooltip.logged_user": "Atentificat ca %s"
}
+14 -12
View File
@@ -73,11 +73,11 @@
"entry.shared_entry.title": "Открыть публичную ссылку",
"entry.state.loading": "Загрузка…",
"entry.state.saving": "Сохранение…",
"entry.status.read": "Прочитано",
"entry.status.mark_as_read": "Отметить как прочитанное",
"entry.status.title": "Изменить статус записи",
"entry.status.toast.read": "Помечено как прочитанное",
"entry.status.toast.unread": "Помечено как непрочитанное",
"entry.status.unread": "Не прочитано",
"entry.status.mark_as_unread": "Пометить как непрочитанное",
"entry.tags.label": "Теги:",
"entry.unshare.label": "Удалить из общедоступных",
"error.api_key_already_exists": "Этот API-ключ уже существует.",
@@ -188,6 +188,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.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",
@@ -256,7 +257,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "Сохранять статьи в Nunux Keeper",
@@ -402,6 +403,7 @@
"page.about.author": "Автор:",
"page.about.build_date": "Дата сборки:",
"page.about.credits": "Авторы",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Глобальные параметры конфигурации",
"page.about.go_version": "Версия Go:",
"page.about.license": "Лицензия:",
@@ -421,11 +423,6 @@
"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 подписка.",
@@ -435,6 +432,11 @@
"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:",
@@ -543,29 +545,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.shared_entries.title": "Общедоступные статьи",
"page.starred.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": "Да",
@@ -618,4 +620,4 @@
"time_elapsed.yesterday": "вчера",
"tooltip.keyboard_shortcuts": "Сочетания клавиш: %s",
"tooltip.logged_user": "Авторизован как %s"
}
}
+15 -13
View File
@@ -71,11 +71,11 @@
"entry.shared_entry.title": "Herkese açık bağlantıyı aç",
"entry.state.loading": "Yükleniyor...",
"entry.state.saving": "Kaydediliyor...",
"entry.status.read": "Okundu",
"entry.status.mark_as_read": "Okundu olarak işaretle",
"entry.status.title": "Makele okundu durumunu değiştir",
"entry.status.toast.read": "Okundu olarak işaretle",
"entry.status.toast.unread": "Okunmadı olarak işaretle",
"entry.status.unread": "Okunmadı",
"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.",
@@ -186,6 +186,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.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",
@@ -254,7 +255,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "Makaleleri Nunux Keeper'a kaydet",
@@ -400,6 +401,7 @@
"page.about.author": "Yazar:",
"page.about.build_date": "Oluşturulma Tarihi:",
"page.about.credits": "Katkıda Bulunanlar",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Global yapılandırma seçenekleri",
"page.about.go_version": "Go sürümü:",
"page.about.license": "Lisans:",
@@ -419,10 +421,6 @@
"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.",
@@ -431,6 +429,10 @@
"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ığı:",
@@ -536,25 +538,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.shared_entries.title": "Paylaşılan makaleler",
"page.starred.title": "Yıldızlı",
"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",
@@ -601,4 +603,4 @@
"time_elapsed.yesterday": "dün",
"tooltip.keyboard_shortcuts": "Klavye Kısayolu: %s",
"tooltip.logged_user": "%s olarak giriş yapıldı"
}
}
+14 -12
View File
@@ -73,11 +73,11 @@
"entry.shared_entry.title": "Відкрити публічне посилання",
"entry.state.loading": "Завантаження...",
"entry.state.saving": "Зберігаю...",
"entry.status.read": "Прочитане",
"entry.status.mark_as_read": означити як прочитане",
"entry.status.title": "Змінити стан запису",
"entry.status.toast.read": "Відмічено прочитаним",
"entry.status.toast.unread": "Відмічено непрочитаним",
"entry.status.unread": "Непрочитане",
"entry.status.mark_as_unread": "Позначити як непрочитане",
"entry.tags.label": "Теги:",
"entry.unshare.label": "Не ділитися",
"error.api_key_already_exists": "Такий ключ API вже існує.",
@@ -188,6 +188,7 @@
"form.feed.label.ntfy_max_priority": "Ntfy max priority",
"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.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",
@@ -256,7 +257,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Use internal links on click (optional)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (default if not set in feed)",
"form.integration.ntfy_url": "Ntfy URL (optional, default is ntfy.sh)",
"form.integration.ntfy_username": "Ntfy Username (optional)",
"form.integration.nunux_keeper_activate": "Зберігати статті до Nunux Keeper",
@@ -402,6 +403,7 @@
"page.about.author": "Автор:",
"page.about.build_date": "Дата побудови:",
"page.about.credits": "Титри",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "Параметри глобальної конфігурації",
"page.about.go_version": "Версія Go:",
"page.about.license": "Ліцензія:",
@@ -421,11 +423,6 @@
"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 стрічку.",
@@ -435,6 +432,11 @@
"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:",
@@ -543,29 +545,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.shared_entries.title": "Спільні записи",
"page.starred.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": "Так",
@@ -618,4 +620,4 @@
"time_elapsed.yesterday": "вчора",
"tooltip.keyboard_shortcuts": "Комбінація клавіш: %s",
"tooltip.logged_user": "Здійснено вхід як %s"
}
}
+5 -3
View File
@@ -69,11 +69,11 @@
"entry.shared_entry.title": "打开公共链接",
"entry.state.loading": "载入中…",
"entry.state.saving": "保存中…",
"entry.status.read": "标为已读",
"entry.status.mark_as_read": "标为已读",
"entry.status.title": "更改状态",
"entry.status.toast.read": "已标为已读",
"entry.status.toast.unread": "已标为未读",
"entry.status.unread": "标为未读",
"entry.status.mark_as_unread": "标为未读",
"entry.tags.label": "标签:",
"entry.unshare.label": "取消分享",
"error.api_key_already_exists": "此 API 密钥已存在。",
@@ -184,6 +184,7 @@
"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",
@@ -252,7 +253,7 @@
"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_topic": "Ntfy主题(預設,如果未在此源中定義)",
"form.integration.ntfy_url": "Ntfy URL(可选,默认为ntfy.sh",
"form.integration.ntfy_username": "Ntfy用户名(可选)",
"form.integration.nunux_keeper_activate": "保存文章到 Nunux Keeper",
@@ -398,6 +399,7 @@
"page.about.author": "作者:",
"page.about.build_date": "构建日期:",
"page.about.credits": "版权",
"page.about.db_usage": "数据库容量",
"page.about.global_config_options": "全局配置选项",
"page.about.go_version": "Go 版本号:",
"page.about.license": "协议:",
+12 -10
View File
@@ -69,11 +69,11 @@
"entry.shared_entry.title": "開啟公共連結",
"entry.state.loading": "載入中…",
"entry.state.saving": "儲存中…",
"entry.status.read": "標記為已讀",
"entry.status.mark_as_read": "標記為已讀",
"entry.status.title": "更改狀態",
"entry.status.toast.read": "已標記為已讀",
"entry.status.toast.unread": "已標記為未讀",
"entry.status.unread": "標記為未讀",
"entry.status.mark_as_unread": "標記為未讀",
"entry.tags.label": "標籤:",
"entry.unshare.label": "取消分享",
"error.api_key_already_exists": "此 API 金鑰已存在。",
@@ -184,6 +184,7 @@
"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 topic (選填)",
"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",
@@ -252,7 +253,7 @@
"form.integration.ntfy_icon_url": "Ntfy Icon 網址 (選填)",
"form.integration.ntfy_internal_links": "點選時使用內部連結 (選填)",
"form.integration.ntfy_password": "Ntfy 密碼 (選填)",
"form.integration.ntfy_topic": "Ntfy topic",
"form.integration.ntfy_topic": "Ntfy topic (飼料若無設定,則使用預設值)",
"form.integration.ntfy_url": "Ntfy 網址 (選填,預設為 ntfy.sh)",
"form.integration.ntfy_username": "Ntfy 使用者名稱 (選填)",
"form.integration.nunux_keeper_activate": "儲存文章到 Nunux Keeper",
@@ -398,6 +399,7 @@
"page.about.author": "作者:",
"page.about.build_date": "建構日期:",
"page.about.credits": "版權",
"page.about.db_usage": "Database size:",
"page.about.global_config_options": "全域設定選項",
"page.about.go_version": "Go 版本:",
"page.about.license": "授權:",
@@ -417,9 +419,6 @@
"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"
@@ -427,6 +426,9 @@
"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 標頭:",
@@ -529,21 +531,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.shared_entries.title": "已分享的文章",
"page.starred.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": "是",
@@ -584,4 +586,4 @@
"time_elapsed.yesterday": "昨天",
"tooltip.keyboard_shortcuts": "快捷鍵:%s",
"tooltip.logged_user": "目前登入 %s"
}
}
+1
View File
@@ -56,6 +56,7 @@ type Feed struct {
WebhookURL string `json:"webhook_url"`
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"`
+8 -6
View File
@@ -10,10 +10,11 @@ import (
// Icon represents a website icon (favicon)
type Icon struct {
ID int64 `json:"id"`
Hash string `json:"hash"`
MimeType string `json:"mime_type"`
Content []byte `json:"-"`
ID int64 `json:"id"`
Hash string `json:"hash"`
MimeType string `json:"mime_type"`
Content []byte `json:"-"`
ExternalID string `json:"external_id"`
}
// DataURL returns the data URL of the icon.
@@ -26,6 +27,7 @@ type Icons []*Icon
// FeedIcon is a junction table between feeds and icons.
type FeedIcon struct {
FeedID int64 `json:"feed_id"`
IconID int64 `json:"icon_id"`
FeedID int64 `json:"feed_id"`
IconID int64 `json:"icon_id"`
ExternalIconID string `json:"external_icon_id"`
}
+1 -1
View File
@@ -36,7 +36,7 @@ func (s WebAuthnSession) String() string {
if s.SessionData == nil {
return "{}"
}
return fmt.Sprintf("{Challenge: %s, UserID: %x}", s.SessionData.Challenge, s.SessionData.UserID)
return fmt.Sprintf("{Challenge: %s, UserID: %x}", s.Challenge, s.UserID)
}
type WebAuthnCredential struct {
+4
View File
@@ -39,5 +39,9 @@ func NewManager(ctx context.Context, clientID, clientSecret, redirectURL, oidcDi
}
}
if clientSecret == "" {
slog.Warn("OIDC client secret is empty or missing.")
}
return m
}
+4 -4
View File
@@ -116,12 +116,12 @@ type Atom03Content struct {
func (a *Atom03Content) Content() string {
content := ""
switch {
case a.Mode == "xml":
switch a.Mode {
case "xml":
content = a.InnerXML
case a.Mode == "escaped":
case "escaped":
content = a.CharData
case a.Mode == "base64":
case "base64":
b, err := base64.StdEncoding.DecodeString(a.CharData)
if err == nil {
content = string(b)
+3 -1
View File
@@ -77,7 +77,9 @@ func (h *Handler) Import(userID int64, data io.Reader) error {
Category: category,
}
h.store.CreateFeed(feed)
if err := h.store.CreateFeed(feed); err != nil {
return fmt.Errorf(`opml: unable to create this feed: %q`, subscription.FeedURL)
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package processor // import "miniflux.app/v2/internal/reader/processor
package processor // import "miniflux.app/v2/internal/reader/processor"
import (
"encoding/json"
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package processor // import "miniflux.app/v2/internal/reader/processor
package processor // import "miniflux.app/v2/internal/reader/processor"
import (
"log/slog"
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package processor // import "miniflux.app/v2/internal/reader/processor
package processor // import "miniflux.app/v2/internal/reader/processor"
import (
"errors"
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package processor // import "miniflux.app/v2/internal/reader/processor
package processor // import "miniflux.app/v2/internal/reader/processor"
import (
"errors"
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package processor // import "miniflux.app/v2/internal/reader/processor
package processor // import "miniflux.app/v2/internal/reader/processor"
import (
"log/slog"
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package processor // import "miniflux.app/v2/internal/reader/processor
package processor // import "miniflux.app/v2/internal/reader/processor"
import (
"log/slog"
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package processor // import "miniflux.app/v2/internal/reader/processor
package processor // import "miniflux.app/v2/internal/reader/processor"
import (
"encoding/json"
@@ -33,11 +33,6 @@ func TestGetRefererForURL(t *testing.T) {
url: "https://scontent-sjc3-1.cdninstagram.com/example.jpg",
expected: "https://www.instagram.com",
},
{
name: "Piokok URL",
url: "https://sp1.piokok.com/example.jpg",
expected: "https://sp1.piokok.com",
},
{
name: "Weibo Video URL",
url: "https://f.video.weibocdn.com/example.mp4",
+10 -13
View File
@@ -36,7 +36,6 @@ var predefinedRules = map[string]string{
"thedoghousediaries.com": "add_image_title",
"theverge.com": `add_dynamic_image, remove("div.duet--recirculation--related-list, .hidden")`,
"treelobsters.com": "add_image_title",
"webtoons.com": `add_dynamic_image,replace("webtoon"|"swebtoon")`,
"www.qwantz.com": "add_image_title,add_mailto_subject",
"xkcd.com": "add_image_title",
"youtube.com": "add_youtube_video",
@@ -50,31 +49,29 @@ func GetRefererForURL(u string) string {
}
switch parsedUrl.Hostname() {
case "moyu.im":
return "https://i.jandan.net"
case "i.pximg.net":
return "https://www.pixiv.net"
case "sp1.piokok.com":
return "https://sp1.piokok.com"
case "appinn.com":
return "https://appinn.com"
case "bjp.org.cn":
return "https://bjp.org.cn"
case "cdnfile.sspai.com":
return "https://sspai.com"
case "f.video.weibocdn.com":
return "https://weibo.com"
case "i.pximg.net":
return "https://www.pixiv.net"
case "img.hellogithub.com":
return "https://hellogithub.com"
case "bjp.org.cn":
return "https://bjp.org.cn"
case "appinn.com":
return "https://appinn.com"
case "moyu.im":
return "https://i.jandan.net"
}
switch {
case strings.HasSuffix(parsedUrl.Hostname(), ".sinaimg.cn"):
return "https://weibo.com"
case strings.HasSuffix(parsedUrl.Hostname(), ".cdninstagram.com"):
return "https://www.instagram.com"
case strings.HasSuffix(parsedUrl.Hostname(), ".moyu.im"):
return "https://i.jandan.net"
case strings.HasSuffix(parsedUrl.Hostname(), ".sinaimg.cn"):
return "https://weibo.com"
}
return ""
+4 -4
View File
@@ -39,7 +39,7 @@ func (r *RSSAdapter) BuildFeed(baseURL string) *model.Feed {
}
// Try to find the feed URL from the Atom links.
for _, atomLink := range r.rss.Channel.AtomLinks.Links {
for _, atomLink := range r.rss.Channel.Links {
atomLinkHref := strings.TrimSpace(atomLink.Href)
if atomLinkHref != "" && atomLink.Rel == "self" {
if absoluteFeedURL, err := urllib.AbsoluteURL(feed.FeedURL, atomLinkHref); err == nil {
@@ -189,7 +189,7 @@ func findEntryURL(rssItem *RSSItem) string {
}
}
for _, atomLink := range rssItem.AtomLinks.Links {
for _, atomLink := range rssItem.Links {
if atomLink.Href != "" && (strings.EqualFold(atomLink.Rel, "alternate") || atomLink.Rel == "") {
return strings.TrimSpace(atomLink.Href)
}
@@ -253,8 +253,8 @@ func findEntryAuthor(rssItem *RSSItem) string {
author = rssItem.ItunesAuthor
case rssItem.DublinCoreCreator != "":
author = rssItem.DublinCoreCreator
case rssItem.AtomAuthor.PersonName() != "":
author = rssItem.AtomAuthor.PersonName()
case rssItem.PersonName() != "":
author = rssItem.PersonName()
case strings.Contains(rssItem.Author.Inner, "<![CDATA["):
author = rssItem.Author.Data
default:
+5 -2
View File
@@ -25,6 +25,7 @@ var (
"aside": {},
"audio": {"src"},
"blockquote": {},
"b": {},
"br": {},
"caption": {},
"cite": {},
@@ -71,6 +72,7 @@ var (
"thead": {},
"time": {"datetime"},
"tr": {},
"u": {},
"ul": {"id"},
"var": {},
"video": {"poster", "height", "width", "src"},
@@ -281,9 +283,10 @@ func isPixelTracker(tagName string, attributes []html.Attribute) bool {
for _, attribute := range attributes {
if attribute.Val == "1" {
if attribute.Key == "height" {
switch attribute.Key {
case "height":
hasHeight = true
} else if attribute.Key == "width" {
case "width":
hasWidth = true
}
}
+2 -2
View File
@@ -198,7 +198,7 @@ func TestProtocolRelativeURL(t *testing.T) {
}
func TestInvalidTag(t *testing.T) {
input := `<p>My invalid <b>tag</b>.</p>`
input := `<p>My invalid <z>tag</z>.</p>`
expected := `<p>My invalid tag.</p>`
output := Sanitize("http://example.org/", input)
@@ -238,7 +238,7 @@ func TestUnknownTag(t *testing.T) {
}
func TestInvalidNestedTag(t *testing.T) {
input := `<p>My invalid <b>tag with some <em>valid</em> tag</b>.</p>`
input := `<p>My invalid <z>tag with some <em>valid</em> tag</z>.</p>`
expected := `<p>My invalid tag with some <em>valid</em> tag.</p>`
output := Sanitize("http://example.org/", input)
+3 -3
View File
@@ -49,10 +49,10 @@ func parseImageCandidate(input string) (*ImageCandidate, error) {
parts := strings.Split(strings.TrimSpace(input), " ")
nbParts := len(parts)
switch {
case nbParts == 1:
switch nbParts {
case 1:
return &ImageCandidate{ImageURL: parts[0]}, nil
case nbParts == 2:
case 2:
if !isValidWidthOrDensityDescriptor(parts[1]) {
return nil, fmt.Errorf(`srcset: invalid descriptor`)
}
+9 -8
View File
@@ -189,14 +189,15 @@ func (f *SubscriptionFinder) FindSubscriptionsFromWebPage(websiteURL, contentTyp
func (f *SubscriptionFinder) FindSubscriptionsFromWellKnownURLs(websiteURL string) (Subscriptions, *locale.LocalizedErrorWrapper) {
knownURLs := map[string]string{
"atom.xml": parser.FormatAtom,
"feed.xml": parser.FormatAtom,
"feed/": parser.FormatAtom,
"rss.xml": parser.FormatRSS,
"rss/": parser.FormatRSS,
"index.rss": parser.FormatRSS,
"index.xml": parser.FormatRSS,
"feed.atom": parser.FormatAtom,
"atom.xml": parser.FormatAtom,
"feed.atom": parser.FormatAtom,
"feed.xml": parser.FormatAtom,
"feed/": parser.FormatAtom,
"index.rss": parser.FormatRSS,
"index.xml": parser.FormatRSS,
"rss.xml": parser.FormatRSS,
"rss/": parser.FormatRSS,
"rss/feed.xml": parser.FormatRSS,
}
websiteURLRoot := urllib.RootURL(websiteURL)
+9
View File
@@ -14,6 +14,7 @@ import (
// https://firefox.settings.services.mozilla.com/v1/buckets/main/collections/query-stripping/records
// https://github.com/Smile4ever/Neat-URL/blob/master/data/default-params-by-category.json
// https://github.com/brave/brave-core/blob/master/components/query_filter/utils.cc
// https://developers.google.com/analytics/devguides/collection/ga4/reference/config
var trackingParams = map[string]bool{
// Facebook Click Identifiers
"fbclid": true,
@@ -31,6 +32,14 @@ var trackingParams = map[string]bool{
"wbraid": true,
"gclsrc": true,
// Google Analytics
"campaign_id": true,
"campaign_medium": true,
"campaign_name": true,
"campaign_source": true,
"campaign_term": true,
"campaign_content": true,
// Yandex Click Identifiers
"yclid": true,
"ysclid": true,
+45
View File
@@ -60,6 +60,51 @@ func (s *Storage) GetEnclosures(entryID int64) (model.EnclosureList, error) {
return enclosures, nil
}
func (s *Storage) GetEnclosuresForEntries(entryIDs []int64) (map[int64]model.EnclosureList, error) {
query := `
SELECT
id,
user_id,
entry_id,
url,
size,
mime_type,
media_progression
FROM
enclosures
WHERE
entry_id = ANY($1)
ORDER BY id ASC
`
rows, err := s.db.Query(query, pq.Array(entryIDs))
if err != nil {
return nil, fmt.Errorf("store: unable to fetch enclosures: %w", err)
}
defer rows.Close()
enclosuresMap := make(map[int64]model.EnclosureList)
for rows.Next() {
var enclosure model.Enclosure
err := rows.Scan(
&enclosure.ID,
&enclosure.UserID,
&enclosure.EntryID,
&enclosure.URL,
&enclosure.Size,
&enclosure.MimeType,
&enclosure.MediaProgression,
)
if err != nil {
return nil, fmt.Errorf("store: unable to scan enclosure row: %w", err)
}
enclosuresMap[enclosure.EntryID] = append(enclosuresMap[enclosure.EntryID], &enclosure)
}
return enclosuresMap, nil
}
func (s *Storage) GetEnclosure(enclosureID int64) (*model.Enclosure, error) {
query := `
SELECT
+1 -11
View File
@@ -369,20 +369,10 @@ func (s *Storage) ArchiveEntries(status string, days, limit int) (int64, error)
// SetEntriesStatus update the status of the given list of entries.
func (s *Storage) SetEntriesStatus(userID int64, entryIDs []int64, status string) error {
query := `UPDATE entries SET status=$1, changed_at=now() WHERE user_id=$2 AND id=ANY($3)`
result, err := s.db.Exec(query, status, userID, pq.Array(entryIDs))
if err != nil {
if _, err := s.db.Exec(query, status, userID, pq.Array(entryIDs)); err != nil {
return fmt.Errorf(`store: unable to update entries statuses %v: %v`, entryIDs, err)
}
count, err := result.RowsAffected()
if err != nil {
return fmt.Errorf(`store: unable to update these entries %v: %v`, entryIDs, err)
}
if count == 0 {
return errors.New(`store: nothing has been updated`)
}
return nil
}
+28 -12
View File
@@ -277,7 +277,6 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
e.created_at,
e.changed_at,
e.tags,
(SELECT true FROM enclosures WHERE entry_id=e.id LIMIT 1) as has_enclosure,
f.title as feed_title,
f.feed_url,
f.site_url,
@@ -294,6 +293,7 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
f.hide_globally,
f.no_media_player,
fi.icon_id,
i.external_id AS icon_external_id,
u.timezone
FROM
entries e
@@ -303,6 +303,8 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
categories c ON c.id=f.category_id
LEFT JOIN
feed_icons fi ON fi.feed_id=f.id
LEFT JOIN
icons i ON i.id=fi.icon_id
LEFT JOIN
users u ON u.id=e.user_id
WHERE %s %s
@@ -319,10 +321,13 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
defer rows.Close()
entries := make(model.Entries, 0)
entryMap := make(map[int64]*model.Entry)
var entryIDs []int64
for rows.Next() {
var iconID sql.NullInt64
var externalIconID sql.NullString
var tz string
var hasEnclosure sql.NullBool
entry := model.NewEntry()
@@ -344,7 +349,6 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
&entry.CreatedAt,
&entry.ChangedAt,
pq.Array(&entry.Tags),
&hasEnclosure,
&entry.Feed.Title,
&entry.Feed.FeedURL,
&entry.Feed.SiteURL,
@@ -361,6 +365,7 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
&entry.Feed.HideGlobally,
&entry.Feed.NoMediaPlayer,
&iconID,
&externalIconID,
&tz,
)
@@ -368,20 +373,15 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
return nil, fmt.Errorf("store: unable to fetch entry row: %v", err)
}
if hasEnclosure.Valid && hasEnclosure.Bool && e.fetchEnclosures {
entry.Enclosures, err = e.store.GetEnclosures(entry.ID)
if err != nil {
return nil, fmt.Errorf("store: unable to fetch enclosures for entry #%d: %w", entry.ID, err)
}
}
if iconID.Valid {
if iconID.Valid && externalIconID.Valid && externalIconID.String != "" {
entry.Feed.Icon.FeedID = entry.FeedID
entry.Feed.Icon.IconID = iconID.Int64
entry.Feed.Icon.ExternalIconID = externalIconID.String
} else {
entry.Feed.Icon.IconID = 0
}
// Make sure that timestamp fields contains timezone information (API)
// Make sure that timestamp fields contain timezone information (API)
entry.Date = timezone.Convert(tz, entry.Date)
entry.CreatedAt = timezone.Convert(tz, entry.CreatedAt)
entry.ChangedAt = timezone.Convert(tz, entry.ChangedAt)
@@ -391,7 +391,23 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
entry.Feed.UserID = entry.UserID
entry.Feed.Icon.FeedID = entry.FeedID
entry.Feed.Category.UserID = entry.UserID
entries = append(entries, entry)
entryMap[entry.ID] = entry
entryIDs = append(entryIDs, entry.ID)
}
if e.fetchEnclosures && len(entryIDs) > 0 {
enclosures, err := e.store.GetEnclosuresForEntries(entryIDs)
if err != nil {
return nil, fmt.Errorf("store: unable to fetch enclosures: %w", err)
}
for entryID, entryEnclosures := range enclosures {
if entry, exists := entryMap[entryID]; exists {
entry.Enclosures = entryEnclosures
}
}
}
return entries, nil
+5 -3
View File
@@ -361,10 +361,11 @@ func (s *Storage) UpdateFeed(feed *model.Feed) (err error) {
description=$30,
ntfy_enabled=$31,
ntfy_priority=$32,
pushover_enabled=$33,
pushover_priority=$34
ntfy_topic=$33,
pushover_enabled=$34,
pushover_priority=$35
WHERE
id=$35 AND user_id=$36
id=$36 AND user_id=$37
`
_, err = s.db.Exec(query,
feed.FeedURL,
@@ -399,6 +400,7 @@ func (s *Storage) UpdateFeed(feed *model.Feed) (err error) {
feed.Description,
feed.NtfyEnabled,
feed.NtfyPriority,
feed.NtfyTopic,
feed.PushoverEnabled,
feed.PushoverPriority,
feed.ID,
+13 -5
View File
@@ -163,12 +163,14 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
c.title as category_title,
c.hide_globally as category_hidden,
fi.icon_id,
i.external_id,
u.timezone,
f.apprise_service_urls,
f.webhook_url,
f.disable_http2,
f.ntfy_enabled,
f.ntfy_priority,
f.ntfy_topic,
f.pushover_enabled,
f.pushover_priority
FROM
@@ -177,6 +179,8 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
categories c ON c.id=f.category_id
LEFT JOIN
feed_icons fi ON fi.feed_id=f.id
LEFT JOIN
icons i ON i.id=fi.icon_id
LEFT JOIN
users u ON u.id=f.user_id
WHERE %s
@@ -200,6 +204,7 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
for rows.Next() {
var feed model.Feed
var iconID sql.NullInt64
var externalIconID sql.NullString
var tz string
feed.Category = &model.Category{}
@@ -236,12 +241,14 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
&feed.Category.Title,
&feed.Category.HideGlobally,
&iconID,
&externalIconID,
&tz,
&feed.AppriseServiceURLs,
&feed.WebhookURL,
&feed.DisableHTTP2,
&feed.NtfyEnabled,
&feed.NtfyPriority,
&feed.NtfyTopic,
&feed.PushoverEnabled,
&feed.PushoverPriority,
)
@@ -250,10 +257,10 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
return nil, fmt.Errorf(`store: unable to fetch feeds row: %w`, err)
}
if iconID.Valid {
feed.Icon = &model.FeedIcon{FeedID: feed.ID, IconID: iconID.Int64}
if iconID.Valid && externalIconID.Valid {
feed.Icon = &model.FeedIcon{FeedID: feed.ID, IconID: iconID.Int64, ExternalIconID: externalIconID.String}
} else {
feed.Icon = &model.FeedIcon{FeedID: feed.ID, IconID: 0}
feed.Icon = &model.FeedIcon{FeedID: feed.ID, IconID: 0, ExternalIconID: ""}
}
if readCounters != nil {
@@ -316,9 +323,10 @@ func (f *FeedQueryBuilder) fetchFeedCounter() (unreadCounters map[int64]int, rea
return nil, nil, fmt.Errorf(`store: unable to fetch feed counter row: %w`, err)
}
if status == model.EntryStatusRead {
switch status {
case model.EntryStatusRead:
readCounters[feedID] = count
} else if status == model.EntryStatusUnread {
case model.EntryStatusUnread:
unreadCounters[feedID] = count
}
}
+43 -8
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"strings"
"miniflux.app/v2/internal/crypto"
"miniflux.app/v2/internal/model"
)
@@ -22,8 +23,16 @@ func (s *Storage) HasFeedIcon(feedID int64) bool {
// IconByID returns an icon by the ID.
func (s *Storage) IconByID(iconID int64) (*model.Icon, error) {
var icon model.Icon
query := `SELECT id, hash, mime_type, content FROM icons WHERE id=$1`
err := s.db.QueryRow(query, iconID).Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content)
query := `
SELECT
id,
hash,
mime_type,
content,
external_id
FROM icons
WHERE id=$1`
err := s.db.QueryRow(query, iconID).Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content, &icon.ExternalID)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
@@ -33,6 +42,29 @@ func (s *Storage) IconByID(iconID int64) (*model.Icon, error) {
return &icon, nil
}
// IconByExternalID returns an icon by the External Icon ID.
func (s *Storage) IconByExternalID(externalIconID string) (*model.Icon, error) {
var icon model.Icon
query := `
SELECT
id,
hash,
mime_type,
content,
external_id
FROM icons
WHERE external_id=$1
`
err := s.db.QueryRow(query, externalIconID).Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content, &icon.ExternalID)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("store: unable to fetch icon %s: %w", externalIconID, err)
}
return &icon, nil
}
// IconByFeedID returns a feed icon.
func (s *Storage) IconByFeedID(userID, feedID int64) (*model.Icon, error) {
query := `
@@ -40,7 +72,8 @@ func (s *Storage) IconByFeedID(userID, feedID int64) (*model.Icon, error) {
icons.id,
icons.hash,
icons.mime_type,
icons.content
icons.content,
icons.external_id
FROM icons
LEFT JOIN feed_icons ON feed_icons.icon_id=icons.id
LEFT JOIN feeds ON feeds.id=feed_icons.feed_id
@@ -49,7 +82,7 @@ func (s *Storage) IconByFeedID(userID, feedID int64) (*model.Icon, error) {
LIMIT 1
`
var icon model.Icon
err := s.db.QueryRow(query, userID, feedID).Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content)
err := s.db.QueryRow(query, userID, feedID).Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content, &icon.ExternalID)
if err != nil {
return nil, fmt.Errorf(`store: unable to fetch icon: %v`, err)
}
@@ -67,9 +100,9 @@ func (s *Storage) StoreFeedIcon(feedID int64, icon *model.Icon) error {
if err := tx.QueryRow(`SELECT id FROM icons WHERE hash=$1`, icon.Hash).Scan(&icon.ID); err == sql.ErrNoRows {
query := `
INSERT INTO icons
(hash, mime_type, content)
(hash, mime_type, content, external_id)
VALUES
($1, $2, $3)
($1, $2, $3, $4)
RETURNING
id
`
@@ -78,6 +111,7 @@ func (s *Storage) StoreFeedIcon(feedID int64, icon *model.Icon) error {
icon.Hash,
normalizeMimeType(icon.MimeType),
icon.Content,
crypto.GenerateRandomStringHex(20),
).Scan(&icon.ID)
if err != nil {
@@ -113,7 +147,8 @@ func (s *Storage) Icons(userID int64) (model.Icons, error) {
icons.id,
icons.hash,
icons.mime_type,
icons.content
icons.content,
icons.external_id
FROM icons
LEFT JOIN feed_icons ON feed_icons.icon_id=icons.id
LEFT JOIN feeds ON feeds.id=feed_icons.feed_id
@@ -129,7 +164,7 @@ func (s *Storage) Icons(userID int64) (model.Icons, error) {
var icons model.Icons
for rows.Next() {
var icon model.Icon
err := rows.Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content)
err := rows.Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content, &icon.ExternalID)
if err != nil {
return nil, fmt.Errorf(`store: unable to fetch icons row: %v`, err)
}
+12
View File
@@ -42,3 +42,15 @@ func (s *Storage) Ping() error {
func (s *Storage) DBStats() sql.DBStats {
return s.db.Stats()
}
// DBSize returns how much size the database is using in a pretty way.
func (s *Storage) DBSize() (string, error) {
var size string
err := s.db.QueryRow("SELECT pg_size_pretty(pg_database_size(current_database()))").Scan(&size)
if err != nil {
return "", err
}
return size, nil
}
@@ -10,7 +10,7 @@
<h2 id="feed-title-{{ .ID }}" class="item-title">
<a href="{{ route "feedEntries" "feedID" .ID }}">
{{ if and (.Icon) (gt .Icon.IconID 0) }}
<img src="{{ route "icon" "iconID" .Icon.IconID }}" width="16" height="16" loading="lazy" alt="">
<img src="{{ route "feedIcon" "externalIconID" .Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="">
{{ end }}
{{ if .Disabled }} 🚫 {{ end }}
{{ .Title }}
@@ -22,10 +22,10 @@
title="{{ t "entry.status.title" }}"
data-toggle-status="true"
data-label-loading="{{ t "entry.state.saving" }}"
data-label-read="{{ t "entry.status.read" }}"
data-label-unread="{{ t "entry.status.unread" }}"
data-label-read="{{ t "entry.status.mark_as_read" }}"
data-label-unread="{{ t "entry.status.mark_as_unread" }}"
data-value="{{ if eq .entry.Status "read" }}read{{ else }}unread{{ end }}"
>{{ if eq .entry.Status "read" }}{{ icon "unread" }}{{ else }}{{ icon "read" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "read" }}{{ t "entry.status.unread" }}{{ else }}{{ t "entry.status.read" }}{{ end }}</span></button>
>{{ if eq .entry.Status "read" }}{{ icon "unread" }}{{ else }}{{ icon "read" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "read" }}{{ t "entry.status.mark_as_unread" }}{{ else }}{{ t "entry.status.mark_as_read" }}{{ end }}</span></button>
</li>
<li class="item-meta-icons-star">
<button
@@ -18,6 +18,7 @@
<li><strong>{{ t "page.about.build_date" }}</strong> {{ .build_date }}</li>
{{ if .user.IsAdmin }}<li><strong>{{ t "page.about.postgres_version" }}</strong> {{ .postgres_version }}</li>{{ end }}
<li><strong>{{t "page.about.go_version" }}</strong> {{ .go_version }}</li>
{{ if .user.IsAdmin }}<li><strong>{{t "page.about.db_usage" }}</strong> {{ .db_usage }}</li>{{ end }}
</ul>
</div>
@@ -29,7 +29,7 @@
<h2 id="entry-title-{{ .ID }}" class="item-title">
<a href="{{ route "starredEntry" "entryID" .ID }}">
{{ if ne .Feed.Icon.IconID 0 }}
<img src="{{ route "icon" "iconID" .Feed.Icon.IconID }}" width="16" height="16" loading="lazy" alt="">
<img src="{{ route "feedIcon" "externalIconID" .Feed.Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="">
{{ end }}
{{ .Title }}
</a>
@@ -102,7 +102,7 @@
{{ end }}
>
{{ if ne .Feed.Icon.IconID 0 }}
<img src="{{ route "icon" "iconID" .Feed.Icon.IconID }}" width="16" height="16" loading="lazy" alt="">
<img src="{{ route "feedIcon" "externalIconID" .Feed.Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="">
{{ end }}
{{ .Title }}
</a>
@@ -191,6 +191,12 @@
<details {{ if .form.NtfyEnabled }}open{{ end }}>
<summary>Ntfy</summary>
<label><input type="checkbox" name="ntfy_enabled" value="1" {{ if .form.NtfyEnabled }}checked{{ end }}> {{ t "form.feed.label.ntfy_activate" }}</label>
<div class="form-label-row">
<label for="form-ntfy-topic">
{{ t "form.feed.label.ntfy_topic" }}
</label>
</div>
<input type="text" name="ntfy_topic" id="form-ntfy-topic" value="{{ .form.NtfyTopic }}" spellcheck="false" autocomplete="off">
<div class="form-label-row">
<label for="form-ntfy-priority">
{{ t "form.feed.label.ntfy_priority" }}
+14 -7
View File
@@ -15,12 +15,12 @@
title="{{ t "entry.status.title" }}"
data-toggle-status="true"
data-label-loading="{{ t "entry.state.saving" }}"
data-label-unread="{{ t "entry.status.unread" }}"
data-label-read="{{ t "entry.status.read" }}"
data-label-unread="{{ t "entry.status.mark_as_unread" }}"
data-label-read="{{ t "entry.status.mark_as_read" }}"
data-toast-unread="{{ t "entry.status.toast.unread" }}"
data-toast-read="{{ t "entry.status.toast.read" }}"
data-value="{{ if eq .entry.Status "read" }}read{{ else }}unread{{ end }}"
>{{ if eq .entry.Status "unread" }}{{ icon "read" }}{{ else }}{{ icon "unread" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "unread" }}{{ t "entry.status.read" }}{{ else }}{{ t "entry.status.unread" }}{{ end }}</span></button>
>{{ if eq .entry.Status "unread" }}{{ icon "read" }}{{ else }}{{ icon "unread" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "unread" }}{{ t "entry.status.mark_as_read" }}{{ else }}{{ t "entry.status.mark_as_unread" }}{{ end }}</span></button>
</li>
<li>
<button
@@ -108,8 +108,8 @@
{{ end }}
<div class="entry-meta" dir="auto">
<span class="entry-website">
{{ if and .user (ne .entry.Feed.Icon.IconID 0) }}
<img src="{{ route "icon" "iconID" .entry.Feed.Icon.IconID }}" width="16" height="16" loading="lazy" alt="{{ .entry.Feed.Title }}">
{{ if ne .entry.Feed.Icon.IconID 0 }}
<img src="{{ route "feedIcon" "externalIconID" .entry.Feed.Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="{{ .entry.Feed.Title }}">
{{ end }}
{{ if .user }}
<a href="{{ route "feedEntries" "feedID" .entry.Feed.ID }}">{{ .entry.Feed.Title }}</a>
@@ -135,7 +135,14 @@
{{ if .entry.Tags }}
<div class="entry-tags">
{{ t "entry.tags.label" }}
{{range $i, $e := .entry.Tags}}{{if $i}}, {{end}}<a href="{{ route "tagEntriesAll" "tagName" (urlEncode $e) }}"><strong>{{ $e }}</strong></a>{{end}}
{{ range $i, $e := .entry.Tags }}
{{if $i}}, {{end}}
{{ if $.user }}
<a href="{{ route "tagEntriesAll" "tagName" (urlEncode $e) }}"><strong>{{ $e }}</strong></a>
{{ else }}
<strong>{{ $e }}</strong>
{{ end }}
{{ end }}
</div>
{{ end }}
<div class="entry-date">
@@ -164,7 +171,7 @@
</div>
{{ end }}
{{ end }}
<article class="entry-content gesture-nav-{{ $.user.GestureNav }}" dir="auto">
<article class="entry-content {{ if ne $.user.GestureNav "none" }}gesture-nav-{{ $.user.GestureNav }}{{ end }}" dir="auto">
{{ if not .entry.Feed.NoMediaPlayer }}
{{ $mediaPlayerEnclosure := .entry.Enclosures.FindMediaPlayerEnclosure }}
@@ -113,7 +113,7 @@
{{ end }}
>
{{ if ne .Feed.Icon.IconID 0 }}
<img src="{{ route "icon" "iconID" .Feed.Icon.IconID }}" width="16" height="16" loading="lazy" alt="">
<img src="{{ route "feedIcon" "externalIconID" .Feed.Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="">
{{ end }}
{{ .Title }}
</a>
@@ -48,7 +48,7 @@
<h2 id="entry-title-{{ .ID }}" class="item-title">
<a href="{{ route "readEntry" "entryID" .ID }}">
{{ if ne .Feed.Icon.IconID 0 }}
<img src="{{ route "icon" "iconID" .Feed.Icon.IconID }}" width="16" height="16" loading="lazy" alt="">
<img src="{{ route "feedIcon" "externalIconID" .Feed.Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="">
{{ end }}
{{ .Title }}
</a>
@@ -32,7 +32,7 @@
<h2 id="entry-title-{{ .ID }}" class="item-title">
<a href="{{ route "searchEntry" "entryID" .ID }}?q={{ $.searchQuery }}">
{{ if ne .Feed.Icon.IconID 0 }}
<img src="{{ route "icon" "iconID" .Feed.Icon.IconID }}" width="16" height="16" loading="lazy" alt="{{ .Feed.Title }}">
<img src="{{ route "feedIcon" "externalIconID" .Feed.Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="{{ .Feed.Title }}">
{{ else }}
<span class="sr-only">{{ .Feed.Title }}</span>
{{ end }}
@@ -48,7 +48,7 @@
<h2 id="entry-title-{{ .ID }}" class="item-title">
<a href="{{ route "readEntry" "entryID" .ID }}">
{{ if ne .Feed.Icon.IconID 0 }}
<img src="{{ route "icon" "iconID" .Feed.Icon.IconID }}" width="16" height="16" loading="lazy" alt="">
<img src="{{ route "feedIcon" "externalIconID" .Feed.Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="">
{{ end }}
{{ .Title }}
</a>
@@ -29,7 +29,7 @@
<h2 id="entry-title-{{ .ID }}" class="item-title">
<a href="{{ route "tagEntry" "entryID" .ID "tagName" (urlEncode $.tagName) }}">
{{ if ne .Feed.Icon.IconID 0 }}
<img src="{{ route "icon" "iconID" .Feed.Icon.IconID }}" width="16" height="16" loading="lazy" alt="">
<img src="{{ route "feedIcon" "externalIconID" .Feed.Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="">
{{ end }}
{{ .Title }}
</a>
@@ -56,7 +56,7 @@
<h2 id="entry-title-{{ .ID }}" class="item-title">
<a href="{{ route "unreadEntry" "entryID" .ID }}">
{{ if ne .Feed.Icon.IconID 0 }}
<img src="{{ route "icon" "iconID" .Feed.Icon.IconID }}" width="16" height="16" loading="lazy" alt="">
<img src="{{ route "feedIcon" "externalIconID" .Feed.Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="">
{{ end }}
{{ .Title }}
</a>
+8
View File
@@ -22,6 +22,8 @@ func (h *handler) showAboutPage(w http.ResponseWriter, r *http.Request) {
return
}
dbSize, dbErr := h.store.DBSize()
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("version", version.Version)
@@ -35,5 +37,11 @@ func (h *handler) showAboutPage(w http.ResponseWriter, r *http.Request) {
view.Set("postgres_version", h.store.DatabaseVersion())
view.Set("go_version", runtime.Version())
if dbErr != nil {
view.Set("db_usage", dbErr)
} else {
view.Set("db_usage", dbSize)
}
html.OK(w, r, view.Render("about"))
}
+1
View File
@@ -67,6 +67,7 @@ func (h *handler) showEditFeedPage(w http.ResponseWriter, r *http.Request) {
DisableHTTP2: feed.DisableHTTP2,
NtfyEnabled: feed.NtfyEnabled,
NtfyPriority: feed.NtfyPriority,
NtfyTopic: feed.NtfyTopic,
PushoverEnabled: feed.PushoverEnabled,
PushoverPriority: feed.PushoverPriority,
}
+4 -4
View File
@@ -12,9 +12,9 @@ import (
"miniflux.app/v2/internal/http/response/html"
)
func (h *handler) showIcon(w http.ResponseWriter, r *http.Request) {
iconID := request.RouteInt64Param(r, "iconID")
icon, err := h.store.IconByID(iconID)
func (h *handler) showFeedIcon(w http.ResponseWriter, r *http.Request) {
externalIconID := request.RouteStringParam(r, "externalIconID")
icon, err := h.store.IconByExternalID(externalIconID)
if err != nil {
html.ServerError(w, r, err)
return
@@ -26,7 +26,7 @@ func (h *handler) showIcon(w http.ResponseWriter, r *http.Request) {
}
response.New(w, r).WithCaching(icon.Hash, 72*time.Hour, func(b *response.Builder) {
b.WithHeader("Content-Security-Policy", `default-src 'self'`)
b.WithHeader("Content-Security-Policy", response.ContentSecurityPolicyForUntrustedContent)
b.WithHeader("Content-Type", icon.MimeType)
b.WithBody(icon.Content)
if icon.MimeType != "image/svg+xml" {
+3
View File
@@ -39,6 +39,7 @@ type FeedForm struct {
DisableHTTP2 bool
NtfyEnabled bool
NtfyPriority int
NtfyTopic string
PushoverEnabled bool
PushoverPriority int
}
@@ -73,6 +74,7 @@ func (f FeedForm) Merge(feed *model.Feed) *model.Feed {
feed.DisableHTTP2 = f.DisableHTTP2
feed.NtfyEnabled = f.NtfyEnabled
feed.NtfyPriority = f.NtfyPriority
feed.NtfyTopic = f.NtfyTopic
feed.PushoverEnabled = f.PushoverEnabled
feed.PushoverPriority = f.PushoverPriority
return feed
@@ -121,6 +123,7 @@ func NewFeedForm(r *http.Request) *FeedForm {
DisableHTTP2: r.FormValue("disable_http2") == "1",
NtfyEnabled: r.FormValue("ntfy_enabled") == "1",
NtfyPriority: ntfyPriority,
NtfyTopic: r.FormValue("ntfy_topic"),
PushoverEnabled: r.FormValue("pushover_enabled") == "1",
PushoverPriority: pushoverPriority,
}
+8
View File
@@ -63,6 +63,13 @@ func (m *middleware) handleUserSession(next http.Handler) http.Handler {
func (m *middleware) handleAppSession(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if mux.CurrentRoute(r).GetName() == "feedIcon" {
// Skip app session handling for the feed icon route to avoid unnecessary session creation
// when fetching feed icons.
next.ServeHTTP(w, r)
return
}
var err error
session := m.getAppSessionValueFromCookie(r)
@@ -154,6 +161,7 @@ func (m *middleware) isPublicRoute(r *http.Request) bool {
"oauth2Redirect",
"oauth2Callback",
"appIcon",
"feedIcon",
"favicon",
"webManifest",
"robots",
+1 -1
View File
@@ -144,7 +144,7 @@ func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
response.New(w, r).WithCaching(etag, 72*time.Hour, func(b *response.Builder) {
b.WithStatus(resp.StatusCode)
b.WithHeader("Content-Security-Policy", `default-src 'self'`)
b.WithHeader("Content-Security-Policy", response.ContentSecurityPolicyForUntrustedContent)
b.WithHeader("Content-Type", resp.Header.Get("Content-Type"))
if filename := path.Base(parsedMediaURL.Path); filename != "" {
-1
View File
@@ -1023,7 +1023,6 @@ article.category-has-unread {
color: var(--entry-content-color);
line-height: 1.4em;
overflow-wrap: break-word;
touch-action: pan-y pinch-zoom;
}
.entry-content h1, h2, h3, h4, h5, h6 {
+2 -2
View File
@@ -721,7 +721,7 @@ function isPlayerPlaying(element) {
*/
function handleShare() {
const link = document.querySelector(':is(a, button)[data-share-status]');
const title = document.querySelector("body > main > section > header > h1 > a");
const title = document.querySelector(".entry-header > h1 > a");
if (link.dataset.shareStatus === "shared") {
checkShareAPI(title, link.href);
}
@@ -746,7 +746,7 @@ function checkShareAPI(title, url) {
}
try {
navigator.share({
title: title,
title: title ? title.textContent : url,
url: url
});
} catch (err) {
+1 -1
View File
@@ -74,7 +74,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
uiRouter.HandleFunc("/feed/{feedID}/entries/all", handler.showFeedEntriesAllPage).Name("feedEntriesAll").Methods(http.MethodGet)
uiRouter.HandleFunc("/feed/{feedID}/entry/{entryID}", handler.showFeedEntryPage).Name("feedEntry").Methods(http.MethodGet)
uiRouter.HandleFunc("/unread/feed/{feedID}/entry/{entryID}", handler.showUnreadFeedEntryPage).Name("unreadFeedEntry").Methods(http.MethodGet)
uiRouter.HandleFunc("/feed/icon/{iconID}", handler.showIcon).Name("icon").Methods(http.MethodGet)
uiRouter.HandleFunc("/feed/icon/{externalIconID}", handler.showFeedIcon).Name("feedIcon").Methods(http.MethodGet)
uiRouter.HandleFunc("/feed/{feedID}/mark-all-as-read", handler.markFeedAsRead).Name("markFeedAsRead").Methods(http.MethodPost)
// Category pages.
+1 -1
View File
@@ -235,7 +235,7 @@ func (h *handler) finishLogin(w http.ResponseWriter, r *http.Request) {
return
}
sessionData.SessionData.UserID = parsedResponse.Response.UserHandle
sessionData.UserID = parsedResponse.Response.UserHandle
webAuthUser := WebAuthnUser{user, parsedResponse.Response.UserHandle, storedCredentials}
// Since go-webauthn v0.11.0, the backup eligibility flag is strictly validated, but Miniflux does not store this flag.