Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f99dff5238 | |||
| 1936ced1ed | |||
| cb695e653a | |||
| f57949c9a2 | |||
| 94fb3d6159 | |||
| 51560f191f | |||
| c0fcdf3b6e | |||
| 969efd2af7 | |||
| 87fccfee3a | |||
| e643effefa | |||
| c531be8780 | |||
| 6e3cecc57e | |||
| b3d385861f | |||
| cc5a8fa4b3 | |||
| 78ff727064 | |||
| 54dad707a4 | |||
| df8bc742fb | |||
| e342a4f143 | |||
| febb7b1748 | |||
| 537867a64b | |||
| 82a6fe64ae | |||
| 984a66b590 | |||
| bd79ad2a7d | |||
| b583de88f3 | |||
| baccb9e719 | |||
| 0fd11693f3 | |||
| 05f7a34d43 | |||
| 315e72c412 | |||
| f916373f55 | |||
| 5353211206 | |||
| 999ab0df2b | |||
| f8700c84b8 | |||
| ddd916161e | |||
| c3cd5e3109 | |||
| 051441d875 | |||
| 0cf5051cf8 | |||
| 12ca7aa085 | |||
| d53dc4cf28 | |||
| e7298b255a | |||
| ad02f21d04 | |||
| 46323b3fe7 | |||
| 86b3593fec | |||
| d7c504f48e | |||
| 6ad2001c7d | |||
| c5d4c762d8 | |||
| 8b66c2be1d | |||
| cfb22d7ec1 | |||
| f01ff067a5 | |||
| 55a3f9fcc9 | |||
| 996f6f68d2 | |||
| 3b8ac8b16a | |||
| 381f03b56a | |||
| 117d711d7d | |||
| 2f71804ff4 |
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: ''
|
||||
labels: bug, triage needed
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Feed Problems
|
||||
about: Problems with a feed or a website
|
||||
title: ''
|
||||
labels: feed problems, triage needed
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
---------------------------------
|
||||
|
||||
|
||||
@@ -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 ./...
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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...)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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/"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;`
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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": "繁體中文",
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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ä"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 d’accès",
|
||||
"page.settings.webauthn.register": "Enregister une nouvelle clé d’accès",
|
||||
"page.settings.webauthn.register.error": "Impossible d'enregistrer la clé d’accès",
|
||||
"page.shared_entries.title": "Articles partagés",
|
||||
"page.shared_entries_count": [
|
||||
"%d article partagé",
|
||||
"%d articles partagés"
|
||||
],
|
||||
"page.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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 के रूप में लॉग इन किया"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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ı"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "协议:",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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,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,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,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,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,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,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",
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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`)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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" }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user