pax_global_header 0000666 0000000 0000000 00000000064 15062123773 0014520 g ustar 00root root 0000000 0000000 52 comment=10b2b36895ddbd3b29687a459fa7cb3ad04c5028
v2-2.2.13/ 0000775 0000000 0000000 00000000000 15062123773 0012134 5 ustar 00root root 0000000 0000000 v2-2.2.13/.devcontainer/ 0000775 0000000 0000000 00000000000 15062123773 0014673 5 ustar 00root root 0000000 0000000 v2-2.2.13/.devcontainer/devcontainer.json 0000664 0000000 0000000 00000001345 15062123773 0020252 0 ustar 00root root 0000000 0000000 {
"name": "Miniflux",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"remoteUser": "vscode",
"forwardPorts": [
8080
],
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
},
"customizations": {
"vscode": {
"settings": {
"go.toolsManagement.checkForUpdates": "local",
"go.useLanguageServer": true,
"go.gopath": "/go"
},
"extensions": [
"ms-azuretools.vscode-docker",
"golang.go",
"rangav.vscode-thunder-client",
"GitHub.codespaces",
"GitHub.copilot",
"GitHub.copilot-chat"
]
}
}
} v2-2.2.13/.devcontainer/docker-compose.yml 0000664 0000000 0000000 00000001322 15062123773 0020326 0 ustar 00root root 0000000 0000000 version: '3.8'
services:
app:
image: mcr.microsoft.com/devcontainers/go:1.23
volumes:
- ..:/workspace:cached
command: sleep infinity
network_mode: service:db
environment:
- CREATE_ADMIN=1
- ADMIN_USERNAME=admin
- ADMIN_PASSWORD=test123
db:
image: postgres:15
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
hostname: postgres
environment:
POSTGRES_DB: miniflux2
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
apprise:
image: caronc/apprise:1.0
restart: unless-stopped
hostname: apprise
volumes:
postgres-data: null
v2-2.2.13/.github/ 0000775 0000000 0000000 00000000000 15062123773 0013474 5 ustar 00root root 0000000 0000000 v2-2.2.13/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 15062123773 0015657 5 ustar 00root root 0000000 0000000 v2-2.2.13/.github/ISSUE_TEMPLATE/bug_report.yml 0000664 0000000 0000000 00000005540 15062123773 0020556 0 ustar 00root root 0000000 0000000 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
v2-2.2.13/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000034 15062123773 0017644 0 ustar 00root root 0000000 0000000 blank_issues_enabled: false
v2-2.2.13/.github/ISSUE_TEMPLATE/documentation.yml 0000664 0000000 0000000 00000005046 15062123773 0021260 0 ustar 00root root 0000000 0000000 name: "Documentation Issue"
description: "Report issues or suggest improvements for the documentation"
title: "[Docs]: "
type: "Documentation"
labels: ["triage needed"]
body:
- type: markdown
attributes:
value: |
Thanks for helping improve the Miniflux documentation! Clear and accurate documentation helps everyone.
- type: dropdown
id: issue_type
attributes:
label: "Documentation Issue Type"
description: "What kind of documentation issue are you reporting?"
options:
- "Missing Information"
- "Incorrect Information"
- "Outdated Information"
- "Unclear Explanation"
- "Formatting/Structural Issue"
- "Typo/Grammar Error"
- "Documentation Request"
- "Other"
validations:
required: true
- type: input
id: summary
attributes:
label: "Summary"
description: "Briefly describe the documentation issue."
placeholder: "e.g., The API authentication section is outdated"
validations:
required: true
- type: input
id: location
attributes:
label: "Location"
description: "Where is the documentation you're referring to? Provide URLs, file paths, or section names."
placeholder: "e.g., README.md, docs/api.md, Installation section of the website"
validations:
required: true
- type: textarea
id: description
attributes:
label: "Detailed Description"
description: "Provide a detailed description of the issue or improvement."
placeholder: "e.g., The API authentication section doesn't mention the new token-based authentication method introduced in version 2.0.5."
validations:
required: true
- type: textarea
id: current_content
attributes:
label: "Current Content (if applicable)"
description: "What does the current documentation say?"
placeholder: "Paste the current documentation text here."
validations:
required: false
- type: textarea
id: suggested_content
attributes:
label: "Suggested Changes"
description: "If you have specific suggestions for how to improve the documentation, please provide them here."
placeholder: "e.g., Add a new section about token-based authentication with these details..."
validations:
required: false
- type: input
id: version
attributes:
label: "Version"
description: "Which version of Miniflux does this documentation issue relate to?"
placeholder: "e.g., 2.2.6, or 'all versions'"
validations:
required: false
v2-2.2.13/.github/ISSUE_TEMPLATE/feature_request.yml 0000664 0000000 0000000 00000004267 15062123773 0021616 0 ustar 00root root 0000000 0000000 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
- label: "I agree to follow the project's contribution guidelines."
required: true
v2-2.2.13/.github/ISSUE_TEMPLATE/feed_issue.yml 0000664 0000000 0000000 00000005245 15062123773 0020523 0 ustar 00root root 0000000 0000000 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
v2-2.2.13/.github/ISSUE_TEMPLATE/proposal.yml 0000664 0000000 0000000 00000006216 15062123773 0020246 0 ustar 00root root 0000000 0000000 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
- label: "I agree to follow the project's contribution guidelines."
required: true
v2-2.2.13/.github/dependabot.yml 0000664 0000000 0000000 00000001210 15062123773 0016316 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/packaging/docker/alpine"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directory: "/packaging/docker/distroless"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directory: "packaging/debian"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directory: "packaging/rpm"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
v2-2.2.13/.github/pull_request_template.md 0000664 0000000 0000000 00000000640 15062123773 0020435 0 ustar 00root root 0000000 0000000 Have you followed these guidelines?
- [ ] I have tested my changes
- [ ] There are no breaking changes
- [ ] I have thoroughly tested my changes and verified there are no regressions
- [ ] My commit messages follow the [Conventional Commits specification](https://www.conventionalcommits.org/)
- [ ] I have read and understood the [contribution guidelines](https://github.com/miniflux/v2/blob/main/CONTRIBUTING.md)
v2-2.2.13/.github/workflows/ 0000775 0000000 0000000 00000000000 15062123773 0015531 5 ustar 00root root 0000000 0000000 v2-2.2.13/.github/workflows/build_binaries.yml 0000664 0000000 0000000 00000001210 15062123773 0021221 0 ustar 00root root 0000000 0000000 name: Build Binaries
on:
workflow_dispatch:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Golang
uses: actions/setup-go@v6
with:
go-version: stable
check-latest: true
- name: Compile binaries
env:
CGO_ENABLED: 0
run: make build
- name: Upload binaries
uses: actions/upload-artifact@v4
with:
name: binaries
path: miniflux-*
if-no-files-found: error
retention-days: 5
v2-2.2.13/.github/workflows/codeql-analysis.yml 0000664 0000000 0000000 00000001607 15062123773 0021350 0 ustar 00root root 0000000 0000000 name: "CodeQL"
permissions: read-all
on:
push:
branches: [ main ]
paths:
- '**.js'
- '**.go'
- '!**_test.go'
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
paths:
- '**.js'
- '**.go'
- '!**_test.go'
schedule:
- cron: '45 22 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: stable
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
v2-2.2.13/.github/workflows/debian_packages.yml 0000664 0000000 0000000 00000004724 15062123773 0021343 0 ustar 00root root 0000000 0000000 name: Debian Packages
permissions: read-all
on:
workflow_dispatch:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
schedule:
- cron: '0 0 * * 1,4' # Runs at 00:00 UTC on Monday and Thursday
pull_request:
branches: [ main ]
paths:
- 'packaging/debian/**' # Only run on changes to the debian packaging files
jobs:
test-packages:
if: github.event_name == 'schedule' || github.event_name == 'pull_request'
name: Test Packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
id: buildx
with:
install: true
- name: Available Docker Platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Build Debian Packages
run: make debian-packages
- name: List generated files
run: ls -l *.deb
build-packages-manually:
if: github.event_name == 'workflow_dispatch'
name: Build Packages Manually
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
id: buildx
with:
install: true
- name: Available Docker Platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Build Debian Packages
run: make debian-packages
- name: Upload package
uses: actions/upload-artifact@v4
with:
name: packages
path: "*.deb"
if-no-files-found: error
retention-days: 3
publish-packages:
if: github.event_name == 'push'
name: Publish Packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
id: buildx
with:
install: true
- name: Available Docker Platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Build Debian Packages
run: make debian-packages
- name: List generated files
run: ls -l *.deb
- name: Upload packages to repository
env:
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
run: for f in *.deb; do curl -F package=@$f https://$FURY_TOKEN@push.fury.io/miniflux/; done
v2-2.2.13/.github/workflows/docker.yml 0000664 0000000 0000000 00000006341 15062123773 0017527 0 ustar 00root root 0000000 0000000 name: Docker
on:
schedule:
- cron: '0 1 * * *'
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
pull_request:
branches: [ main ]
paths:
- 'packaging/docker/**'
jobs:
docker-images:
name: Docker Images
permissions:
packages: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Generate Alpine Docker tags
id: docker_alpine_tags
uses: docker/metadata-action@v5
with:
images: |
docker.io/${{ github.repository_owner }}/miniflux
ghcr.io/${{ github.repository_owner }}/miniflux
quay.io/${{ github.repository_owner }}/miniflux
tags: |
type=ref,event=pr
type=schedule,pattern=nightly
type=semver,pattern={{raw}}
- name: Generate Distroless Docker tags
id: docker_distroless_tags
uses: docker/metadata-action@v5
with:
images: |
docker.io/${{ github.repository_owner }}/miniflux
ghcr.io/${{ github.repository_owner }}/miniflux
quay.io/${{ github.repository_owner }}/miniflux
tags: |
type=ref,event=pr
type=schedule,pattern=nightly
type=semver,pattern={{raw}}
flavor: |
suffix=-distroless,onlatest=true
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: ${{ github.event_name != 'pull_request' && vars.PUBLISH_DOCKER_IMAGES == 'true' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
if: ${{ github.event_name != 'pull_request' && vars.PUBLISH_DOCKER_IMAGES == 'true' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Quay Container Registry
if: ${{ github.event_name != 'pull_request' && vars.PUBLISH_DOCKER_IMAGES == 'true' }}
uses: docker/login-action@v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_TOKEN }}
- name: Build and Push Alpine images
uses: docker/build-push-action@v6
if: ${{ vars.PUBLISH_DOCKER_IMAGES == 'true' }}
with:
context: .
file: ./packaging/docker/alpine/Dockerfile
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_alpine_tags.outputs.tags }}
- name: Build and Push Distroless images
uses: docker/build-push-action@v6
if: ${{ vars.PUBLISH_DOCKER_IMAGES == 'true' }}
with:
context: .
file: ./packaging/docker/distroless/Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_distroless_tags.outputs.tags }}
v2-2.2.13/.github/workflows/linters.yml 0000664 0000000 0000000 00000002570 15062123773 0017740 0 ustar 00root root 0000000 0000000 name: Linters
permissions: read-all
on:
pull_request:
branches:
- main
workflow_dispatch:
jobs:
jshint:
name: Javascript Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install linters
run: |
sudo npm install -g jshint@2.13.6 eslint@8.57.0
- name: Run jshint
run: jshint internal/ui/static/js/*.js
- name: Run ESLint
run: eslint internal/ui/static/js/*.js
golangci:
name: Golang Linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: stable
- uses: golangci/golangci-lint-action@v8
with:
args: >
--timeout 10m
--disable errcheck
--enable sqlclosecheck,misspell,whitespace,gocritic
- name: Run gofmt linter
run: gofmt -d -e .
commitlint:
if: github.event_name == 'pull_request'
name: Commit Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Validate PR commits
run: python3 .github/workflows/scripts/commit-checker.py --base ${{ github.event.pull_request.base.sha }} --head ${{ github.event.pull_request.head.sha }}
v2-2.2.13/.github/workflows/rpm_packages.yml 0000664 0000000 0000000 00000003100 15062123773 0020702 0 ustar 00root root 0000000 0000000 name: RPM Packages
permissions: read-all
on:
workflow_dispatch:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
schedule:
- cron: '0 0 * * 1,4' # Runs at 00:00 UTC on Monday and Thursday
pull_request:
branches: [ main ]
paths:
- 'packaging/rpm/**' # Only run on changes to the rpm packaging files
jobs:
test-package:
if: github.event_name == 'schedule' || github.event_name == 'pull_request'
name: Test Packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Build RPM Package
run: make rpm
- name: List generated files
run: ls -l *.rpm
build-package-manually:
if: github.event_name == 'workflow_dispatch'
name: Build Packages Manually
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Build RPM Package
run: make rpm
- name: Upload package
uses: actions/upload-artifact@v4
with:
name: packages
path: "*.rpm"
if-no-files-found: error
retention-days: 3
publish-package:
if: github.event_name == 'push'
name: Publish Packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Build RPM Package
run: make rpm
- name: List generated files
run: ls -l *.rpm
- name: Upload package to repository
env:
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
run: for f in *.rpm; do curl -F package=@$f https://$FURY_TOKEN@push.fury.io/miniflux/; done
v2-2.2.13/.github/workflows/scripts/ 0000775 0000000 0000000 00000000000 15062123773 0017220 5 ustar 00root root 0000000 0000000 v2-2.2.13/.github/workflows/scripts/commit-checker.py 0000664 0000000 0000000 00000006044 15062123773 0022470 0 ustar 00root root 0000000 0000000 import subprocess
import re
import sys
import argparse
from typing import Match
# Conventional commit pattern (including Git revert messages)
CONVENTIONAL_COMMIT_PATTERN: str = (
r"^((build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9-]+\))?!?: .{1,100}|Revert .+)"
)
def get_commit_message(commit_hash: str) -> str:
"""Get the commit message for a given commit hash."""
try:
result: subprocess.CompletedProcess = subprocess.run(
["git", "show", "-s", "--format=%B", commit_hash],
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Error retrieving commit message: {e}")
sys.exit(1)
def check_commit_message(message: str, pattern: str = CONVENTIONAL_COMMIT_PATTERN) -> bool:
"""Check if commit message follows conventional commit format."""
first_line: str = message.split("\n")[0]
match: Match[str] | None = re.match(pattern, first_line)
return bool(match)
def check_commit_range(base_ref: str, head_ref: str) -> list[dict[str, str]]:
"""Check all commits in a range for compliance."""
try:
result: subprocess.CompletedProcess = subprocess.run(
["git", "log", "--format=%H", f"{base_ref}..{head_ref}"],
capture_output=True,
text=True,
check=True,
)
commit_hashes: list[str] = result.stdout.strip().split("\n")
# Filter out empty lines
commit_hashes = [hash for hash in commit_hashes if hash]
non_compliant: list[dict[str, str]] = []
for commit_hash in commit_hashes:
message: str = get_commit_message(commit_hash)
if not check_commit_message(message):
non_compliant.append({"hash": commit_hash, "message": message.split("\n")[0]})
return non_compliant
except subprocess.CalledProcessError as e:
print(f"Error checking commit range: {e}")
sys.exit(1)
def main() -> None:
parser: argparse.ArgumentParser = argparse.ArgumentParser(description="Check conventional commit compliance")
parser.add_argument("--base", required=True, help="Base ref (starting commit, exclusive)")
parser.add_argument("--head", required=True, help="Head ref (ending commit, inclusive)")
args: argparse.Namespace = parser.parse_args()
non_compliant: list[dict[str, str]] = check_commit_range(args.base, args.head)
if non_compliant:
print("The following commits do not follow the conventional commit format:")
for commit in non_compliant:
print(f"- {commit['hash'][:8]}: {commit['message']}")
print("\nPlease ensure your commit messages follow the format:")
print("type(scope): subject")
print("\nWhere type is one of: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test")
sys.exit(1)
else:
print("All commits follow the conventional commit format!")
sys.exit(0)
if __name__ == "__main__":
main()
v2-2.2.13/.github/workflows/tests.yml 0000664 0000000 0000000 00000002720 15062123773 0017417 0 ustar 00root root 0000000 0000000 name: Tests
permissions: read-all
on:
pull_request:
branches:
- main
workflow_dispatch:
jobs:
unit-tests:
name: Unit Tests
runs-on: ${{ matrix.os }}
strategy:
max-parallel: 4
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: stable
- name: Run unit tests with coverage and race conditions checking
if: matrix.os == 'ubuntu-latest'
run: make test
- name: Run unit tests without coverage and race conditions checking
if: matrix.os != 'ubuntu-latest'
run: go test ./...
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:9.5
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: stable
- name: Install Postgres client
run: sudo apt update && sudo apt install -y postgresql-client
- name: Run integration tests
run: make integration-test
env:
PGHOST: 127.0.0.1
PGPASSWORD: postgres
v2-2.2.13/.gitignore 0000664 0000000 0000000 00000000072 15062123773 0014123 0 ustar 00root root 0000000 0000000 ./*.sha256
/miniflux
.idea
.vscode
*.deb
*.rpm
miniflux-*
v2-2.2.13/CONTRIBUTING.md 0000664 0000000 0000000 00000012174 15062123773 0014372 0 ustar 00root root 0000000 0000000 # Contributing to Miniflux
This document outlines how to contribute effectively to Miniflux.
## Philosophy
Miniflux follows a **minimalist philosophy**. The feature set is intentionally kept limited to avoid bloatware. Before contributing, please understand that:
- **Improving existing features takes priority over adding new ones**
- **Quality over quantity** - well-implemented, focused features are preferred
- **Simplicity is key** - complex solutions are discouraged in favor of simple, maintainable code
## Before You Start
### Feature Requests
Before implementing a new feature:
- Check if it aligns with Miniflux's philosophy
- Consider if the feature could be implemented differently to maintain simplicity
- Remember that developing software takes significant time, and this is a volunteer-driven project
- If you need a specific feature, the best approach is to contribute it yourself
### Bug Reports
When reporting bugs:
- Search existing issues first to avoid duplicates
- Provide clear reproduction steps
- Include relevant system information (OS, browser, Miniflux version)
- Include error messages, screenshots, and logs when applicable
## Development Setup
### Requirements
- **Git**
- **Go >= 1.24**
- **PostgreSQL**
### Getting Started
1. **Fork the repository** on GitHub
2. **Clone your fork locally:**
```bash
git clone https://github.com/YOUR_USERNAME/miniflux.git
cd miniflux
```
3. **Build the application binary:**
```bash
make miniflux
```
4. **Run locally in debug mode:**
```bash
make run
```
### Database Setup
For development and testing, you can run a local PostgreSQL database with Docker:
```bash
# Start PostgreSQL container
docker run --rm --name miniflux2-db -p 5432:5432 \
-e POSTGRES_DB=miniflux2 \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
postgres
```
You can also use an existing PostgreSQL instance. Make sure to set the `DATABASE_URL` environment variable accordingly.
## Development Workflow
### Code Quality
1. **Run the linter:**
```bash
make lint
```
Requires `staticcheck` and `golangci-lint` to be installed.
2. **Run unit tests:**
```bash
make test
```
3. **Run integration tests:**
```bash
make integration-test
make clean-integration-test
```
### Building
- **Current platform:** `make miniflux`
- **All platforms:** `make build`
- **Specific platforms:** `make linux-amd64`, `make darwin-arm64`, etc.
- **Docker image:** `make docker-image`
### Cross-Platform Support
Miniflux supports multiple architectures. When making changes, ensure compatibility across:
- Linux (amd64, arm64, armv7, armv6, armv5)
- macOS (amd64, arm64)
- FreeBSD, OpenBSD, Windows (amd64)
## Pull Request Guidelines
### What Is Preferred
✅ **Good Pull Requests:**
- Focus on a single issue or feature
- Include tests for new functionality
- Maintain or improve performance
- Follow existing code style and patterns
- The commit messages follow the [conventional commit format](https://www.conventionalcommits.org/) (e.g., `feat: add new feature`, `fix: resolve bug`)
- Update documentation when necessary
### What to Avoid
❌ **Pull Requests That Cannot Be Accepted:**
- **Too many changes** - makes review difficult
- **Breaking changes** - disrupts existing functionality
- **New bugs or regressions** - reduces software quality
- **Unnecessary dependencies** - conflicts with minimalist approach
- **Performance degradation** - slows down the software
- **Poor-quality code** - hard to maintain
- **Dependent PRs** - creates review complexity
- **Radical UI changes** - disrupts user experience
- **Conflicts with philosophy** - doesn't align with minimalist approach
### Pull Request Template
When creating a pull request, please include:
- **Description:** What does this PR do?
- **Motivation:** Why is this change needed?
- **Testing:** How was this tested?
- **Breaking Changes:** Are there any breaking changes?
- **Related Issues:** Link to any related issues
## Code Style
- Follow Go conventions and best practices
- Use `gofmt` to format your Go code, and `jshint` for JavaScript
- Write clear, descriptive variable and function names
- Include comments for complex logic
- Keep functions small and focused
## Testing
### Unit Tests
- Write unit tests for new functions and methods
- Ensure tests are fast and don't require external dependencies
- Aim for good test coverage
### Integration Tests
- Add integration tests for new API endpoints
- Tests run against a real PostgreSQL database
- Ensure tests clean up after themselves
## Communication
- **Discussions:** Use GitHub Discussions for general questions and community interaction
- **Issues:** Use GitHub issues for bug reports and feature requests
- **Pull Requests:** Use PR comments for code-specific discussions
- **Philosophy Questions:** Refer to the FAQ for common questions about project direction
## Questions?
- Check the [FAQ](https://miniflux.app/faq.html) for common questions
- Review the [development documentation](https://miniflux.app/docs/development.html) and [internationalization guide](https://miniflux.app/docs/i18n.html)
- Look at existing issues and pull requests for examples
v2-2.2.13/LICENSE 0000664 0000000 0000000 00000023676 15062123773 0013157 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
v2-2.2.13/Makefile 0000664 0000000 0000000 00000011314 15062123773 0013574 0 ustar 00root root 0000000 0000000 APP := miniflux
DOCKER_IMAGE := miniflux/miniflux
VERSION := $(shell git describe --tags --exact-match 2>/dev/null)
LD_FLAGS := "-s -w -X 'miniflux.app/v2/internal/version.Version=$(VERSION)'"
PKG_LIST := $(shell go list ./... | grep -v /vendor/)
DB_URL := postgres://postgres:postgres@localhost/miniflux_test?sslmode=disable
DOCKER_PLATFORM := amd64
export PGPASSWORD := postgres
.PHONY: \
miniflux \
miniflux-no-pie \
linux-amd64 \
linux-arm64 \
linux-armv7 \
linux-armv6 \
linux-armv5 \
linux-x86 \
darwin-amd64 \
darwin-arm64 \
freebsd-amd64 \
openbsd-amd64 \
netbsd-amd64 \
build \
run \
clean \
add-string \
test \
lint \
integration-test \
clean-integration-test \
docker-image \
docker-image-distroless \
docker-images \
rpm \
debian \
debian-packages
miniflux:
@ go build -buildmode=pie -ldflags=$(LD_FLAGS) -o $(APP)
miniflux-no-pie:
@ go build -ldflags=$(LD_FLAGS) -o $(APP)
linux-amd64:
@ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@
@ sha256sum $(APP)-$@ > $(APP)-$@.sha256
linux-arm64:
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@
@ sha256sum $(APP)-$@ > $(APP)-$@.sha256
linux-armv7:
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@
@ sha256sum $(APP)-$@ > $(APP)-$@.sha256
linux-armv6:
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@
@ sha256sum $(APP)-$@ > $(APP)-$@.sha256
linux-armv5:
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@
@ sha256sum $(APP)-$@ > $(APP)-$@.sha256
darwin-amd64:
@ GOOS=darwin GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@
@ sha256sum $(APP)-$@ > $(APP)-$@.sha256
darwin-arm64:
@ GOOS=darwin GOARCH=arm64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@
@ sha256sum $(APP)-$@ > $(APP)-$@.sha256
freebsd-amd64:
@ CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@
@ sha256sum $(APP)-$@ > $(APP)-$@.sha256
openbsd-amd64:
@ GOOS=openbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@
@ sha256sum $(APP)-$@ > $(APP)-$@.sha256
build: linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-armv5 darwin-amd64 darwin-arm64 freebsd-amd64 openbsd-amd64
run:
@ LOG_DATE_TIME=1 LOG_LEVEL=debug RUN_MIGRATIONS=1 CREATE_ADMIN=1 ADMIN_USERNAME=admin ADMIN_PASSWORD=test123 go run main.go
clean:
@ rm -f $(APP)-* $(APP) $(APP)*.rpm $(APP)*.deb $(APP)*.exe $(APP)*.sha256
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 ./...
lint:
go vet ./...
staticcheck ./...
golangci-lint run --disable errcheck --enable sqlclosecheck --enable misspell --enable gofmt --enable goimports --enable whitespace
integration-test:
psql -U postgres -c 'drop database if exists miniflux_test;'
psql -U postgres -c 'create database miniflux_test;'
DATABASE_URL=$(DB_URL) \
ADMIN_USERNAME=admin \
ADMIN_PASSWORD=test123 \
CREATE_ADMIN=1 \
RUN_MIGRATIONS=1 \
LOG_LEVEL=debug \
go run main.go >/tmp/miniflux.log 2>&1 & echo "$$!" > "/tmp/miniflux.pid"
while ! nc -z localhost 8080; do sleep 1; done
TEST_MINIFLUX_BASE_URL=http://127.0.0.1:8080 \
TEST_MINIFLUX_ADMIN_USERNAME=admin \
TEST_MINIFLUX_ADMIN_PASSWORD=test123 \
go test -v -count=1 ./internal/api
clean-integration-test:
@ kill -9 `cat /tmp/miniflux.pid`
@ rm -f /tmp/miniflux.pid /tmp/miniflux.log
@ psql -U postgres -c 'drop database if exists miniflux_test;'
docker-image:
docker build --pull -t $(DOCKER_IMAGE):$(VERSION) -f packaging/docker/alpine/Dockerfile .
docker-image-distroless:
docker build -t $(DOCKER_IMAGE):$(VERSION) -f packaging/docker/distroless/Dockerfile .
docker-images:
docker buildx build \
--platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 \
--file packaging/docker/alpine/Dockerfile \
--tag $(DOCKER_IMAGE):$(VERSION) \
--push .
rpm: clean
@ docker build \
-t miniflux-rpm-builder \
-f packaging/rpm/Dockerfile \
.
@ docker run --rm \
-v ${PWD}:/root/rpmbuild/RPMS/x86_64 miniflux-rpm-builder \
rpmbuild -bb --define "_miniflux_version $(VERSION)" /root/rpmbuild/SPECS/miniflux.spec
debian:
@ docker buildx build --load \
--platform linux/$(DOCKER_PLATFORM) \
-t miniflux-deb-builder \
-f packaging/debian/Dockerfile \
.
@ docker run --rm --platform linux/$(DOCKER_PLATFORM) \
-v ${PWD}:/pkg miniflux-deb-builder
debian-packages: clean
$(MAKE) debian DOCKER_PLATFORM=amd64
$(MAKE) debian DOCKER_PLATFORM=arm64
$(MAKE) debian DOCKER_PLATFORM=arm/v7
v2-2.2.13/Procfile 0000664 0000000 0000000 00000000022 15062123773 0013614 0 ustar 00root root 0000000 0000000 web: miniflux.app
v2-2.2.13/README.md 0000664 0000000 0000000 00000020134 15062123773 0013413 0 ustar 00root root 0000000 0000000 Miniflux 2
==========
Miniflux is a minimalist and opinionated feed reader.
It's simple, fast, lightweight and super easy to install.
Official website: ';
*/
func (h *handler) handleFavicons(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
slog.Debug("[Fever] Fetching favicons",
slog.Int64("user_id", userID),
)
icons, err := h.store.Icons(userID)
if err != nil {
json.ServerError(w, r, err)
return
}
var result faviconsResponse
for _, i := range icons {
result.Favicons = append(result.Favicons, favicon{
ID: i.ID,
Data: i.DataURL(),
})
}
result.SetCommonValues()
json.OK(w, r, result)
}
/*
A request with the items argument will return two additional members:
items contains an array of item objects
total_items contains the total number of items stored in the database (added in API version 2)
An item object has the following members:
id (positive integer)
feed_id (positive integer)
title (utf-8 string)
author (utf-8 string)
html (utf-8 string)
url (utf-8 string)
is_saved (boolean integer)
is_read (boolean integer)
created_on_time (Unix timestamp/integer)
Most servers won’t have enough memory allocated to PHP to dump all items at once.
Three optional arguments control determine the items included in the response.
Use the since_id argument with the highest id of locally cached items to request 50 additional items.
Repeat until the items array in the response is empty.
Use the max_id argument with the lowest id of locally cached items (or 0 initially) to request 50 previous items.
Repeat until the items array in the response is empty. (added in API version 2)
Use the with_ids argument with a comma-separated list of item ids to request (a maximum of 50) specific items.
(added in API version 2)
*/
func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
var result itemsResponse
userID := request.UserID(r)
builder := h.store.NewEntryQueryBuilder(userID)
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithLimit(50)
switch {
case request.HasQueryParam(r, "since_id"):
sinceID := request.QueryInt64Param(r, "since_id", 0)
if sinceID > 0 {
slog.Debug("[Fever] Fetching items since a given date",
slog.Int64("user_id", userID),
slog.Int64("since_id", sinceID),
)
builder.AfterEntryID(sinceID)
builder.WithSorting("id", "ASC")
}
case request.HasQueryParam(r, "max_id"):
maxID := request.QueryInt64Param(r, "max_id", 0)
if maxID == 0 {
slog.Debug("[Fever] Fetching most recent items",
slog.Int64("user_id", userID),
)
builder.WithSorting("id", "DESC")
} else if maxID > 0 {
slog.Debug("[Fever] Fetching items before a given item ID",
slog.Int64("user_id", userID),
slog.Int64("max_id", maxID),
)
builder.BeforeEntryID(maxID)
builder.WithSorting("id", "DESC")
}
case request.HasQueryParam(r, "with_ids"):
csvItemIDs := request.QueryStringParam(r, "with_ids", "")
if csvItemIDs != "" {
var itemIDs []int64
for _, strItemID := range strings.Split(csvItemIDs, ",") {
strItemID = strings.TrimSpace(strItemID)
itemID, _ := strconv.ParseInt(strItemID, 10, 64)
itemIDs = append(itemIDs, itemID)
}
builder.WithEntryIDs(itemIDs)
}
default:
slog.Debug("[Fever] Fetching oldest items",
slog.Int64("user_id", userID),
)
}
entries, err := builder.GetEntries()
if err != nil {
json.ServerError(w, r, err)
return
}
builder = h.store.NewEntryQueryBuilder(userID)
builder.WithoutStatus(model.EntryStatusRemoved)
result.Total, err = builder.CountEntries()
if err != nil {
json.ServerError(w, r, err)
return
}
result.Items = make([]item, 0)
for _, entry := range entries {
isRead := 0
if entry.Status == model.EntryStatusRead {
isRead = 1
}
isSaved := 0
if entry.Starred {
isSaved = 1
}
result.Items = append(result.Items, item{
ID: entry.ID,
FeedID: entry.FeedID,
Title: entry.Title,
Author: entry.Author,
HTML: mediaproxy.RewriteDocumentWithAbsoluteProxyURL(h.router, entry.Content),
URL: entry.URL,
IsSaved: isSaved,
IsRead: isRead,
CreatedAt: entry.Date.Unix(),
})
}
result.SetCommonValues()
json.OK(w, r, result)
}
/*
The unread_item_ids and saved_item_ids arguments can be used to keep your local cache synced
with the remote Fever installation.
A request with the unread_item_ids argument will return one additional member:
unread_item_ids (string/comma-separated list of positive integers)
*/
func (h *handler) handleUnreadItems(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
slog.Debug("[Fever] Fetching unread items",
slog.Int64("user_id", userID),
)
builder := h.store.NewEntryQueryBuilder(userID)
builder.WithStatus(model.EntryStatusUnread)
rawEntryIDs, err := builder.GetEntryIDs()
if err != nil {
json.ServerError(w, r, err)
return
}
var itemIDs []string
for _, entryID := range rawEntryIDs {
itemIDs = append(itemIDs, strconv.FormatInt(entryID, 10))
}
var result unreadResponse
result.ItemIDs = strings.Join(itemIDs, ",")
result.SetCommonValues()
json.OK(w, r, result)
}
/*
The unread_item_ids and saved_item_ids arguments can be used to keep your local cache synced
with the remote Fever installation.
A request with the saved_item_ids argument will return one additional member:
saved_item_ids (string/comma-separated list of positive integers)
*/
func (h *handler) handleSavedItems(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
slog.Debug("[Fever] Fetching saved items",
slog.Int64("user_id", userID),
)
builder := h.store.NewEntryQueryBuilder(userID)
builder.WithStarred(true)
entryIDs, err := builder.GetEntryIDs()
if err != nil {
json.ServerError(w, r, err)
return
}
var itemsIDs []string
for _, entryID := range entryIDs {
itemsIDs = append(itemsIDs, strconv.FormatInt(entryID, 10))
}
result := &savedResponse{ItemIDs: strings.Join(itemsIDs, ",")}
result.SetCommonValues()
json.OK(w, r, result)
}
/*
mark=item
as=? where ? is replaced with read, saved or unsaved
id=? where ? is replaced with the id of the item to modify
*/
func (h *handler) handleWriteItems(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
slog.Debug("[Fever] Receiving mark=item call",
slog.Int64("user_id", userID),
)
entryID := request.FormInt64Value(r, "id")
if entryID <= 0 {
return
}
builder := h.store.NewEntryQueryBuilder(userID)
builder.WithEntryID(entryID)
builder.WithoutStatus(model.EntryStatusRemoved)
entry, err := builder.GetEntry()
if err != nil {
json.ServerError(w, r, err)
return
}
if entry == nil {
slog.Debug("[Fever] Entry not found",
slog.Int64("user_id", userID),
slog.Int64("entry_id", entryID),
)
json.OK(w, r, newBaseResponse())
return
}
switch r.FormValue("as") {
case "read":
slog.Debug("[Fever] Mark entry as read",
slog.Int64("user_id", userID),
slog.Int64("entry_id", entryID),
)
h.store.SetEntriesStatus(userID, []int64{entryID}, model.EntryStatusRead)
case "unread":
slog.Debug("[Fever] Mark entry as unread",
slog.Int64("user_id", userID),
slog.Int64("entry_id", entryID),
)
h.store.SetEntriesStatus(userID, []int64{entryID}, model.EntryStatusUnread)
case "saved":
slog.Debug("[Fever] Mark entry as saved",
slog.Int64("user_id", userID),
slog.Int64("entry_id", entryID),
)
if err := h.store.ToggleStarred(userID, entryID); err != nil {
json.ServerError(w, r, err)
return
}
settings, err := h.store.Integration(userID)
if err != nil {
json.ServerError(w, r, err)
return
}
go func() {
integration.SendEntry(entry, settings)
}()
case "unsaved":
slog.Debug("[Fever] Mark entry as unsaved",
slog.Int64("user_id", userID),
slog.Int64("entry_id", entryID),
)
if err := h.store.ToggleStarred(userID, entryID); err != nil {
json.ServerError(w, r, err)
return
}
}
json.OK(w, r, newBaseResponse())
}
/*
mark=feed
as=read
id=? where ? is replaced with the id of the feed or group to modify
before=? where ? is replaced with the Unix timestamp of the the local client’s most recent items API request
*/
func (h *handler) handleWriteFeeds(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
feedID := request.FormInt64Value(r, "id")
before := time.Unix(request.FormInt64Value(r, "before"), 0)
slog.Debug("[Fever] Mark feed as read before a given date",
slog.Int64("user_id", userID),
slog.Int64("feed_id", feedID),
slog.Time("before_ts", before),
)
if feedID <= 0 {
return
}
go func() {
if err := h.store.MarkFeedAsRead(userID, feedID, before); err != nil {
slog.Error("[Fever] Unable to mark feed as read",
slog.Int64("user_id", userID),
slog.Int64("feed_id", feedID),
slog.Time("before_ts", before),
slog.Any("error", err),
)
}
}()
json.OK(w, r, newBaseResponse())
}
/*
mark=group
as=read
id=? where ? is replaced with the id of the feed or group to modify
before=? where ? is replaced with the Unix timestamp of the the local client’s most recent items API request
*/
func (h *handler) handleWriteGroups(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
groupID := request.FormInt64Value(r, "id")
before := time.Unix(request.FormInt64Value(r, "before"), 0)
slog.Debug("[Fever] Mark group as read before a given date",
slog.Int64("user_id", userID),
slog.Int64("group_id", groupID),
slog.Time("before_ts", before),
)
if groupID < 0 {
return
}
go func() {
var err error
if groupID == 0 {
err = h.store.MarkAllAsRead(userID)
} else {
err = h.store.MarkCategoryAsRead(userID, groupID, before)
}
if err != nil {
slog.Error("[Fever] Unable to mark group as read",
slog.Int64("user_id", userID),
slog.Int64("group_id", groupID),
slog.Time("before_ts", before),
slog.Any("error", err),
)
}
}()
json.OK(w, r, newBaseResponse())
}
/*
A feeds_group object has the following members:
group_id (positive integer)
feed_ids (string/comma-separated list of positive integers)
*/
func (h *handler) buildFeedGroups(feeds model.Feeds) []feedsGroups {
feedsGroupedByCategory := make(map[int64][]string)
for _, feed := range feeds {
feedsGroupedByCategory[feed.Category.ID] = append(feedsGroupedByCategory[feed.Category.ID], strconv.FormatInt(feed.ID, 10))
}
result := make([]feedsGroups, 0)
for categoryID, feedIDs := range feedsGroupedByCategory {
result = append(result, feedsGroups{
GroupID: categoryID,
FeedIDs: strings.Join(feedIDs, ","),
})
}
return result
}
v2-2.2.13/internal/fever/middleware.go 0000664 0000000 0000000 00000004266 15062123773 0017533 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package fever // import "miniflux.app/v2/internal/fever"
import (
"context"
"log/slog"
"net/http"
"miniflux.app/v2/internal/http/request"
"miniflux.app/v2/internal/http/response/json"
"miniflux.app/v2/internal/storage"
)
type middleware struct {
store *storage.Storage
}
func newMiddleware(s *storage.Storage) *middleware {
return &middleware{s}
}
func (m *middleware) serve(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
apiKey := r.FormValue("api_key")
if apiKey == "" {
slog.Warn("[Fever] No API key provided",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
json.OK(w, r, newAuthFailureResponse())
return
}
user, err := m.store.UserByFeverToken(apiKey)
if err != nil {
slog.Error("[Fever] Unable to fetch user by API key",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Any("error", err),
)
json.OK(w, r, newAuthFailureResponse())
return
}
if user == nil {
slog.Warn("[Fever] No user found with the API key provided",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
json.OK(w, r, newAuthFailureResponse())
return
}
slog.Info("[Fever] User authenticated successfully",
slog.Bool("authentication_successful", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", user.ID),
slog.String("username", user.Username),
)
m.store.SetLastLogin(user.ID)
ctx := r.Context()
ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
v2-2.2.13/internal/fever/response.go 0000664 0000000 0000000 00000005404 15062123773 0017247 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package fever // import "miniflux.app/v2/internal/fever"
import (
"time"
)
type baseResponse struct {
Version int `json:"api_version"`
Authenticated int `json:"auth"`
LastRefresh int64 `json:"last_refreshed_on_time"`
}
func (b *baseResponse) SetCommonValues() {
b.Version = 3
b.Authenticated = 1
b.LastRefresh = time.Now().Unix()
}
/*
The default response is a JSON object containing two members:
api_version contains the version of the API responding (positive integer)
auth whether the request was successfully authenticated (boolean integer)
The API can also return XML by passing xml as the optional value of the api argument like so:
http://yourdomain.com/fever/?api=xml
The top level XML element is named response.
The response to each successfully authenticated request will have auth set to 1 and include
at least one additional member:
last_refreshed_on_time contains the time of the most recently refreshed (not updated)
feed (Unix timestamp/integer)
*/
func newBaseResponse() baseResponse {
r := baseResponse{}
r.SetCommonValues()
return r
}
func newAuthFailureResponse() baseResponse {
return baseResponse{Version: 3, Authenticated: 0}
}
type groupsResponse struct {
baseResponse
Groups []group `json:"groups"`
FeedsGroups []feedsGroups `json:"feeds_groups"`
}
type feedsResponse struct {
baseResponse
Feeds []feed `json:"feeds"`
FeedsGroups []feedsGroups `json:"feeds_groups"`
}
type faviconsResponse struct {
baseResponse
Favicons []favicon `json:"favicons"`
}
type itemsResponse struct {
baseResponse
Items []item `json:"items"`
Total int `json:"total_items"`
}
type unreadResponse struct {
baseResponse
ItemIDs string `json:"unread_item_ids"`
}
type savedResponse struct {
baseResponse
ItemIDs string `json:"saved_item_ids"`
}
type group struct {
ID int64 `json:"id"`
Title string `json:"title"`
}
type feedsGroups struct {
GroupID int64 `json:"group_id"`
FeedIDs string `json:"feed_ids"`
}
type feed struct {
ID int64 `json:"id"`
FaviconID int64 `json:"favicon_id"`
Title string `json:"title"`
URL string `json:"url"`
SiteURL string `json:"site_url"`
IsSpark int `json:"is_spark"`
LastUpdated int64 `json:"last_updated_on_time"`
}
type item struct {
ID int64 `json:"id"`
FeedID int64 `json:"feed_id"`
Title string `json:"title"`
Author string `json:"author"`
HTML string `json:"html"`
URL string `json:"url"`
IsSaved int `json:"is_saved"`
IsRead int `json:"is_read"`
CreatedAt int64 `json:"created_on_time"`
}
type favicon struct {
ID int64 `json:"id"`
Data string `json:"data"`
}
v2-2.2.13/internal/googlereader/ 0000775 0000000 0000000 00000000000 15062123773 0016407 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/googlereader/handler.go 0000664 0000000 0000000 00000111033 15062123773 0020352 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/v2/internal/googlereader"
import (
"errors"
"fmt"
"log/slog"
"net/http"
"strconv"
"time"
"miniflux.app/v2/internal/config"
"miniflux.app/v2/internal/http/request"
"miniflux.app/v2/internal/http/response"
"miniflux.app/v2/internal/http/response/json"
"miniflux.app/v2/internal/http/route"
"miniflux.app/v2/internal/integration"
"miniflux.app/v2/internal/mediaproxy"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/proxyrotator"
"miniflux.app/v2/internal/reader/fetcher"
mff "miniflux.app/v2/internal/reader/handler"
mfs "miniflux.app/v2/internal/reader/subscription"
"miniflux.app/v2/internal/storage"
"miniflux.app/v2/internal/validator"
"github.com/gorilla/mux"
)
type handler struct {
store *storage.Storage
router *mux.Router
}
var (
errEmptyFeedTitle = errors.New("googlereader: empty feed title")
errFeedNotFound = errors.New("googlereader: feed not found")
errCategoryNotFound = errors.New("googlereader: category not found")
)
// Serve handles Google Reader API calls.
func Serve(router *mux.Router, store *storage.Storage) {
handler := &handler{store, router}
router.HandleFunc("/accounts/ClientLogin", handler.clientLoginHandler).Methods(http.MethodPost).Name("ClientLogin")
middleware := newMiddleware(store)
sr := router.PathPrefix("/reader/api/0").Subrouter()
sr.Use(middleware.handleCORS)
sr.Use(middleware.apiKeyAuth)
sr.Methods(http.MethodOptions)
sr.HandleFunc("/token", handler.tokenHandler).Methods(http.MethodGet).Name("Token")
sr.HandleFunc("/edit-tag", handler.editTagHandler).Methods(http.MethodPost).Name("EditTag")
sr.HandleFunc("/rename-tag", handler.renameTagHandler).Methods(http.MethodPost).Name("Rename Tag")
sr.HandleFunc("/disable-tag", handler.disableTagHandler).Methods(http.MethodPost).Name("Disable Tag")
sr.HandleFunc("/tag/list", handler.tagListHandler).Methods(http.MethodGet).Name("TagList")
sr.HandleFunc("/user-info", handler.userInfoHandler).Methods(http.MethodGet).Name("UserInfo")
sr.HandleFunc("/subscription/list", handler.subscriptionListHandler).Methods(http.MethodGet).Name("SubscriptonList")
sr.HandleFunc("/subscription/edit", handler.editSubscriptionHandler).Methods(http.MethodPost).Name("SubscriptionEdit")
sr.HandleFunc("/subscription/quickadd", handler.quickAddHandler).Methods(http.MethodPost).Name("QuickAdd")
sr.HandleFunc("/stream/items/ids", handler.streamItemIDsHandler).Methods(http.MethodGet).Name("StreamItemIDs")
sr.HandleFunc("/stream/items/contents", handler.streamItemContentsHandler).Methods(http.MethodPost).Name("StreamItemsContents")
sr.HandleFunc("/mark-all-as-read", handler.markAllAsReadHandler).Methods(http.MethodPost).Name("MarkAllAsRead")
sr.PathPrefix("/").HandlerFunc(handler.serveHandler).Methods(http.MethodPost, http.MethodGet).Name("GoogleReaderApiEndpoint")
}
func checkAndSimplifyTags(addTags []Stream, removeTags []Stream) (map[StreamType]bool, error) {
tags := make(map[StreamType]bool)
for _, s := range addTags {
switch s.Type {
case ReadStream:
if _, ok := tags[KeptUnreadStream]; ok {
return nil, fmt.Errorf("googlereader: %s and %s should not be supplied simultaneously", keptUnreadStreamSuffix, readStreamSuffix)
}
tags[ReadStream] = true
case KeptUnreadStream:
if _, ok := tags[ReadStream]; ok {
return nil, fmt.Errorf("googlereader: %s and %s should not be supplied simultaneously", keptUnreadStreamSuffix, readStreamSuffix)
}
tags[ReadStream] = false
case StarredStream:
tags[StarredStream] = true
case BroadcastStream, LikeStream:
slog.Debug("Broadcast & Like tags are not implemented!")
default:
return nil, fmt.Errorf("googlereader: unsupported tag type: %s", s.Type)
}
}
for _, s := range removeTags {
switch s.Type {
case ReadStream:
if _, ok := tags[ReadStream]; ok {
return nil, fmt.Errorf("googlereader: %s and %s should not be supplied simultaneously", keptUnreadStreamSuffix, readStreamSuffix)
}
tags[ReadStream] = false
case KeptUnreadStream:
if _, ok := tags[ReadStream]; ok {
return nil, fmt.Errorf("googlereader: %s and %s should not be supplied simultaneously", keptUnreadStreamSuffix, readStreamSuffix)
}
tags[ReadStream] = true
case StarredStream:
if _, ok := tags[StarredStream]; ok {
return nil, fmt.Errorf("googlereader: %s should not be supplied for add and remove simultaneously", starredStreamSuffix)
}
tags[StarredStream] = false
case BroadcastStream, LikeStream:
slog.Debug("Broadcast & Like tags are not implemented!")
default:
return nil, fmt.Errorf("googlereader: unsupported tag type: %s", s.Type)
}
}
return tags, nil
}
func checkOutputFormat(r *http.Request) error {
var output string
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
return err
}
output = r.Form.Get("output")
} else {
output = request.QueryStringParam(r, "output", "")
}
if output != "json" {
err := fmt.Errorf("googlereader: only json output is supported")
return err
}
return nil
}
func (h *handler) clientLoginHandler(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /accounts/ClientLogin",
slog.String("handler", "clientLoginHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
if err := r.ParseForm(); err != nil {
slog.Warn("[GoogleReader] Could not parse request form data",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Any("error", err),
)
json.Unauthorized(w, r)
return
}
username := r.Form.Get("Email")
password := r.Form.Get("Passwd")
output := r.Form.Get("output")
if username == "" || password == "" {
slog.Warn("[GoogleReader] Empty username or password",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
json.Unauthorized(w, r)
return
}
if err := h.store.GoogleReaderUserCheckPassword(username, password); err != nil {
slog.Warn("[GoogleReader] Invalid username or password",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.String("username", username),
slog.Any("error", err),
)
json.Unauthorized(w, r)
return
}
slog.Info("[GoogleReader] User authenticated successfully",
slog.Bool("authentication_successful", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.String("username", username),
)
integration, err := h.store.GoogleReaderUserGetIntegration(username)
if err != nil {
json.ServerError(w, r, err)
return
}
h.store.SetLastLogin(integration.UserID)
token := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
slog.Debug("[GoogleReader] Created token",
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.String("username", username),
)
result := loginResponse{SID: token, LSID: token, Auth: token}
if output == "json" {
json.OK(w, r, result)
return
}
builder := response.New(w, r)
builder.WithHeader("Content-Type", "text/plain; charset=UTF-8")
builder.WithBody(result.String())
builder.Write()
}
func (h *handler) tokenHandler(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /token",
slog.String("handler", "tokenHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
if !request.IsAuthenticated(r) {
slog.Warn("[GoogleReader] User is not authenticated",
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
json.Unauthorized(w, r)
return
}
token := request.GoogleReaderToken(r)
if token == "" {
slog.Warn("[GoogleReader] User does not have token",
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", request.UserID(r)),
)
json.Unauthorized(w, r)
return
}
slog.Debug("[GoogleReader] Token handler",
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", request.UserID(r)),
slog.String("token", token),
)
w.Header().Add("Content-Type", "text/plain; charset=UTF-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(token))
}
func (h *handler) editTagHandler(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /edit-tag",
slog.String("handler", "editTagHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", userID),
)
if err := r.ParseForm(); err != nil {
json.ServerError(w, r, err)
return
}
addTags, err := getStreams(r.PostForm[paramTagsAdd], userID)
if err != nil {
json.ServerError(w, r, err)
return
}
removeTags, err := getStreams(r.PostForm[paramTagsRemove], userID)
if err != nil {
json.ServerError(w, r, err)
return
}
if len(addTags) == 0 && len(removeTags) == 0 {
err = fmt.Errorf("googlreader: add or/and remove tags should be supplied")
json.ServerError(w, r, err)
return
}
tags, err := checkAndSimplifyTags(addTags, removeTags)
if err != nil {
json.ServerError(w, r, err)
return
}
itemIDs, err := parseItemIDsFromRequest(r)
if err != nil {
json.BadRequest(w, r, err)
return
}
slog.Debug("[GoogleReader] Edited tags",
slog.String("handler", "editTagHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", userID),
slog.Any("item_ids", itemIDs),
slog.Any("tags", tags),
)
builder := h.store.NewEntryQueryBuilder(userID)
builder.WithEntryIDs(itemIDs)
builder.WithoutStatus(model.EntryStatusRemoved)
entries, err := builder.GetEntries()
if err != nil {
json.ServerError(w, r, err)
return
}
n := 0
readEntryIDs := make([]int64, 0)
unreadEntryIDs := make([]int64, 0)
starredEntryIDs := make([]int64, 0)
unstarredEntryIDs := make([]int64, 0)
for _, entry := range entries {
if read, exists := tags[ReadStream]; exists {
if read && entry.Status == model.EntryStatusUnread {
readEntryIDs = append(readEntryIDs, entry.ID)
} else if entry.Status == model.EntryStatusRead {
unreadEntryIDs = append(unreadEntryIDs, entry.ID)
}
}
if starred, exists := tags[StarredStream]; exists {
if starred && !entry.Starred {
starredEntryIDs = append(starredEntryIDs, entry.ID)
// filter the original array
entries[n] = entry
n++
} else if entry.Starred {
unstarredEntryIDs = append(unstarredEntryIDs, entry.ID)
}
}
}
entries = entries[:n]
if len(readEntryIDs) > 0 {
err = h.store.SetEntriesStatus(userID, readEntryIDs, model.EntryStatusRead)
if err != nil {
json.ServerError(w, r, err)
return
}
}
if len(unreadEntryIDs) > 0 {
err = h.store.SetEntriesStatus(userID, unreadEntryIDs, model.EntryStatusUnread)
if err != nil {
json.ServerError(w, r, err)
return
}
}
if len(unstarredEntryIDs) > 0 {
err = h.store.SetEntriesStarredState(userID, unstarredEntryIDs, false)
if err != nil {
json.ServerError(w, r, err)
return
}
}
if len(starredEntryIDs) > 0 {
err = h.store.SetEntriesStarredState(userID, starredEntryIDs, true)
if err != nil {
json.ServerError(w, r, err)
return
}
}
if len(entries) > 0 {
settings, err := h.store.Integration(userID)
if err != nil {
json.ServerError(w, r, err)
return
}
for _, entry := range entries {
e := entry
go func() {
integration.SendEntry(e, settings)
}()
}
}
sendOkayResponse(w)
}
func (h *handler) quickAddHandler(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /subscription/quickadd",
slog.String("handler", "quickAddHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", userID),
)
err := r.ParseForm()
if err != nil {
json.BadRequest(w, r, err)
return
}
feedURL := r.Form.Get(paramQuickAdd)
if !validator.IsValidURL(feedURL) {
json.BadRequest(w, r, fmt.Errorf("googlereader: invalid URL: %s", feedURL))
return
}
requestBuilder := fetcher.NewRequestBuilder()
requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
var rssBridgeURL string
var rssBridgeToken string
if intg, err := h.store.Integration(userID); err == nil && intg != nil && intg.RSSBridgeEnabled {
rssBridgeURL = intg.RSSBridgeURL
rssBridgeToken = intg.RSSBridgeToken
}
subscriptions, localizedError := mfs.NewSubscriptionFinder(requestBuilder).FindSubscriptions(feedURL, rssBridgeURL, rssBridgeToken)
if localizedError != nil {
json.ServerError(w, r, localizedError.Error())
return
}
if len(subscriptions) == 0 {
json.OK(w, r, quickAddResponse{
NumResults: 0,
})
return
}
toSubscribe := Stream{FeedStream, subscriptions[0].URL}
category := Stream{NoStream, ""}
newFeed, err := subscribe(toSubscribe, category, "", h.store, userID)
if err != nil {
json.ServerError(w, r, err)
return
}
slog.Debug("[GoogleReader] Added a new feed",
slog.String("handler", "quickAddHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", userID),
slog.String("feed_url", newFeed.FeedURL),
)
json.OK(w, r, quickAddResponse{
NumResults: 1,
Query: newFeed.FeedURL,
StreamID: fmt.Sprintf(feedPrefix+"%d", newFeed.ID),
StreamName: newFeed.Title,
})
}
func getFeed(stream Stream, store *storage.Storage, userID int64) (*model.Feed, error) {
feedID, err := strconv.ParseInt(stream.ID, 10, 64)
if err != nil {
return nil, err
}
return store.FeedByID(userID, feedID)
}
func getOrCreateCategory(streamCategory Stream, store *storage.Storage, userID int64) (*model.Category, error) {
switch {
case streamCategory.ID == "":
return store.FirstCategory(userID)
case store.CategoryTitleExists(userID, streamCategory.ID):
return store.CategoryByTitle(userID, streamCategory.ID)
default:
return store.CreateCategory(userID, &model.CategoryCreationRequest{
Title: streamCategory.ID,
})
}
}
func subscribe(newFeed Stream, category Stream, title string, store *storage.Storage, userID int64) (*model.Feed, error) {
destCategory, err := getOrCreateCategory(category, store, userID)
if err != nil {
return nil, err
}
feedRequest := model.FeedCreationRequest{
FeedURL: newFeed.ID,
CategoryID: destCategory.ID,
}
verr := validator.ValidateFeedCreation(store, userID, &feedRequest)
if verr != nil {
return nil, verr.Error()
}
created, localizedError := mff.CreateFeed(store, userID, &feedRequest)
if localizedError != nil {
return nil, localizedError.Error()
}
if title != "" {
feedModification := model.FeedModificationRequest{
Title: &title,
}
feedModification.Patch(created)
if err := store.UpdateFeed(created); err != nil {
return nil, err
}
}
return created, nil
}
func unsubscribe(streams []Stream, store *storage.Storage, userID int64) error {
for _, stream := range streams {
feedID, err := strconv.ParseInt(stream.ID, 10, 64)
if err != nil {
return err
}
err = store.RemoveFeed(userID, feedID)
if err != nil {
return err
}
}
return nil
}
func rename(feedStream Stream, title string, store *storage.Storage, userID int64) error {
slog.Debug("[GoogleReader] Renaming feed",
slog.Int64("user_id", userID),
slog.Any("feed_stream", feedStream),
slog.String("new_title", title),
)
if title == "" {
return errEmptyFeedTitle
}
feed, err := getFeed(feedStream, store, userID)
if err != nil {
return err
}
if feed == nil {
return errFeedNotFound
}
feedModification := model.FeedModificationRequest{
Title: &title,
}
feedModification.Patch(feed)
return store.UpdateFeed(feed)
}
func move(feedStream Stream, labelStream Stream, store *storage.Storage, userID int64) error {
slog.Debug("[GoogleReader] Moving feed",
slog.Int64("user_id", userID),
slog.Any("feed_stream", feedStream),
slog.Any("label_stream", labelStream),
)
feed, err := getFeed(feedStream, store, userID)
if err != nil {
return err
}
if feed == nil {
return errFeedNotFound
}
category, err := getOrCreateCategory(labelStream, store, userID)
if err != nil {
return err
}
if category == nil {
return errCategoryNotFound
}
feedModification := model.FeedModificationRequest{
CategoryID: &category.ID,
}
feedModification.Patch(feed)
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)
slog.Debug("[GoogleReader] Handle /subscription/edit",
slog.String("handler", "editSubscriptionHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", userID),
)
if err := r.ParseForm(); err != nil {
json.BadRequest(w, r, err)
return
}
streamIds, err := getStreams(r.Form[paramStreamID], userID)
if err != nil || len(streamIds) == 0 {
json.BadRequest(w, r, errors.New("googlereader: no valid stream IDs provided"))
return
}
newLabel, err := getStream(r.Form.Get(paramTagsAdd), userID)
if err != nil {
json.BadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", paramTagsAdd))
return
}
title := r.Form.Get(paramTitle)
action := r.Form.Get(paramSubscribeAction)
switch action {
case "subscribe":
_, err := subscribe(streamIds[0], newLabel, title, h.store, userID)
if err != nil {
json.ServerError(w, r, err)
return
}
case "unsubscribe":
err := unsubscribe(streamIds, h.store, userID)
if err != nil {
json.ServerError(w, r, err)
return
}
case "edit":
if title != "" {
if err := rename(streamIds[0], title, h.store, userID); err != nil {
if errors.Is(err, errFeedNotFound) || errors.Is(err, errEmptyFeedTitle) {
json.BadRequest(w, r, err)
} else {
json.ServerError(w, r, err)
}
return
}
}
if r.Form.Has(paramTagsAdd) {
if newLabel.Type != LabelStream {
json.BadRequest(w, r, errors.New("destination must be a label"))
return
}
if err := move(streamIds[0], newLabel, h.store, userID); err != nil {
if errors.Is(err, errFeedNotFound) || errors.Is(err, errCategoryNotFound) {
json.BadRequest(w, r, err)
} else {
json.ServerError(w, r, err)
}
return
}
}
default:
json.BadRequest(w, r, fmt.Errorf("googlereader: unrecognized action %s", action))
return
}
sendOkayResponse(w)
}
func (h *handler) streamItemContentsHandler(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
userName := request.UserName(r)
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /stream/items/contents",
slog.String("handler", "streamItemContentsHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", userID),
)
if err := checkOutputFormat(r); err != nil {
json.BadRequest(w, r, err)
return
}
err := r.ParseForm()
if err != nil {
json.ServerError(w, r, err)
return
}
requestModifiers, err := parseStreamFilterFromRequest(r)
if err != nil {
json.ServerError(w, r, err)
return
}
userReadingList := fmt.Sprintf(userStreamPrefix, userID) + readingListStreamSuffix
userRead := fmt.Sprintf(userStreamPrefix, userID) + readStreamSuffix
userStarred := fmt.Sprintf(userStreamPrefix, userID) + starredStreamSuffix
itemIDs, err := parseItemIDsFromRequest(r)
if err != nil {
json.BadRequest(w, r, err)
return
}
slog.Debug("[GoogleReader] Fetching item contents",
slog.String("handler", "streamItemContentsHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", userID),
slog.Any("item_ids", itemIDs),
)
builder := h.store.NewEntryQueryBuilder(userID)
builder.WithEnclosures()
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithEntryIDs(itemIDs)
builder.WithSorting(model.DefaultSortingOrder, requestModifiers.SortDirection)
entries, err := builder.GetEntries()
if err != nil {
json.ServerError(w, r, err)
return
}
result := streamContentItemsResponse{
Direction: "ltr",
ID: "user/-/state/com.google/reading-list",
Title: "Reading List",
Updated: time.Now().Unix(),
Self: []contentHREF{{
HREF: config.Opts.RootURL() + route.Path(h.router, "StreamItemsContents"),
}},
Author: userName,
Items: make([]contentItem, len(entries)),
}
for i, entry := range entries {
enclosures := make([]contentItemEnclosure, 0, len(entry.Enclosures))
for _, enclosure := range entry.Enclosures {
enclosures = append(enclosures, contentItemEnclosure{URL: enclosure.URL, Type: enclosure.MimeType})
}
categories := make([]string, 0)
categories = append(categories, userReadingList)
if entry.Feed.Category.Title != "" {
categories = append(categories, fmt.Sprintf(userLabelPrefix, userID)+entry.Feed.Category.Title)
}
if entry.Status == model.EntryStatusRead {
categories = append(categories, userRead)
}
if entry.Starred {
categories = append(categories, userStarred)
}
entry.Content = mediaproxy.RewriteDocumentWithAbsoluteProxyURL(h.router, entry.Content)
entry.Enclosures.ProxifyEnclosureURL(h.router, config.Opts.MediaProxyMode(), config.Opts.MediaProxyResourceTypes())
result.Items[i] = contentItem{
ID: convertEntryIDToLongFormItemID(entry.ID),
Title: entry.Title,
Author: entry.Author,
TimestampUsec: strconv.FormatInt(entry.Date.UnixMicro(), 10),
CrawlTimeMsec: strconv.FormatInt(entry.CreatedAt.UnixMilli(), 10),
Published: entry.Date.Unix(),
Updated: entry.ChangedAt.Unix(),
Categories: categories,
Canonical: []contentHREF{
{
HREF: entry.URL,
},
},
Alternate: []contentHREFType{
{
HREF: entry.URL,
Type: "text/html",
},
},
Content: contentItemContent{
Direction: "ltr",
Content: entry.Content,
},
Summary: contentItemContent{
Direction: "ltr",
Content: entry.Content,
},
Origin: contentItemOrigin{
StreamID: fmt.Sprintf(feedPrefix+"%d", entry.FeedID),
Title: entry.Feed.Title,
HTMLUrl: entry.Feed.SiteURL,
},
Enclosure: enclosures,
}
}
json.OK(w, r, result)
}
func (h *handler) disableTagHandler(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /disable-tags",
slog.String("handler", "disableTagHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", userID),
)
err := r.ParseForm()
if err != nil {
json.BadRequest(w, r, err)
return
}
streams, err := getStreams(r.Form[paramStreamID], userID)
if err != nil {
json.BadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", paramStreamID))
return
}
titles := make([]string, len(streams))
for i, stream := range streams {
if stream.Type != LabelStream {
json.BadRequest(w, r, errors.New("googlereader: only labels are supported"))
return
}
titles[i] = stream.ID
}
err = h.store.RemoveAndReplaceCategoriesByName(userID, titles)
if err != nil {
json.ServerError(w, r, err)
return
}
sendOkayResponse(w)
}
func (h *handler) renameTagHandler(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /rename-tag",
slog.String("handler", "renameTagHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
err := r.ParseForm()
if err != nil {
json.BadRequest(w, r, err)
return
}
source, err := getStream(r.Form.Get(paramStreamID), userID)
if err != nil {
json.BadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", paramStreamID))
return
}
destination, err := getStream(r.Form.Get(paramDestination), userID)
if err != nil {
json.BadRequest(w, r, fmt.Errorf("googlereader: invalid data in %s", paramDestination))
return
}
if source.Type != LabelStream || destination.Type != LabelStream {
json.BadRequest(w, r, errors.New("googlereader: only labels supported"))
return
}
if destination.ID == "" {
json.BadRequest(w, r, errors.New("googlereader: empty destination name"))
return
}
category, err := h.store.CategoryByTitle(userID, source.ID)
if err != nil {
json.ServerError(w, r, err)
return
}
if category == nil {
json.NotFound(w, r)
return
}
categoryModificationRequest := model.CategoryModificationRequest{
Title: model.SetOptionalField(destination.ID),
}
if validationError := validator.ValidateCategoryModification(h.store, userID, category.ID, &categoryModificationRequest); validationError != nil {
json.BadRequest(w, r, validationError.Error())
return
}
categoryModificationRequest.Patch(category)
if err := h.store.UpdateCategory(category); err != nil {
json.ServerError(w, r, err)
return
}
sendOkayResponse(w)
}
func (h *handler) tagListHandler(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /tags/list",
slog.String("handler", "tagListHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
if err := checkOutputFormat(r); err != nil {
json.BadRequest(w, r, err)
return
}
var result tagsResponse
categories, err := h.store.Categories(userID)
if err != nil {
json.ServerError(w, r, err)
return
}
result.Tags = make([]subscriptionCategoryResponse, 0)
result.Tags = append(result.Tags, subscriptionCategoryResponse{
ID: fmt.Sprintf(userStreamPrefix, userID) + starredStreamSuffix,
})
for _, category := range categories {
result.Tags = append(result.Tags, subscriptionCategoryResponse{
ID: fmt.Sprintf(userLabelPrefix, userID) + category.Title,
Label: category.Title,
Type: "folder",
})
}
json.OK(w, r, result)
}
func (h *handler) subscriptionListHandler(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /subscription/list",
slog.String("handler", "subscriptionListHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
if err := checkOutputFormat(r); err != nil {
json.BadRequest(w, r, err)
return
}
var result subscriptionsResponse
feeds, err := h.store.Feeds(userID)
if err != nil {
json.ServerError(w, r, err)
return
}
result.Subscriptions = make([]subscriptionResponse, 0)
for _, feed := range feeds {
result.Subscriptions = append(result.Subscriptions, subscriptionResponse{
ID: fmt.Sprintf(feedPrefix+"%d", feed.ID),
Title: feed.Title,
URL: feed.FeedURL,
Categories: []subscriptionCategoryResponse{{fmt.Sprintf(userLabelPrefix, userID) + feed.Category.Title, feed.Category.Title, "folder"}},
HTMLURL: feed.SiteURL,
IconURL: h.feedIconURL(feed),
})
}
json.OK(w, r, result)
}
func (h *handler) serveHandler(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] API endpoint not implemented yet",
slog.Any("url", r.RequestURI),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
json.OK(w, r, []string{})
}
func (h *handler) userInfoHandler(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /user-info",
slog.String("handler", "userInfoHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
if err := checkOutputFormat(r); err != nil {
json.BadRequest(w, r, err)
return
}
user, err := h.store.UserByID(request.UserID(r))
if err != nil {
json.ServerError(w, r, err)
return
}
userInfo := userInfoResponse{UserID: fmt.Sprint(user.ID), UserName: user.Username, UserProfileID: fmt.Sprint(user.ID), UserEmail: user.Username}
json.OK(w, r, userInfo)
}
func (h *handler) streamItemIDsHandler(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /stream/items/ids",
slog.String("handler", "streamItemIDsHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int64("user_id", userID),
)
if err := checkOutputFormat(r); err != nil {
json.BadRequest(w, r, err)
return
}
rm, err := parseStreamFilterFromRequest(r)
if err != nil {
json.ServerError(w, r, err)
return
}
slog.Debug("[GoogleReader] Request Modifiers",
slog.String("handler", "streamItemIDsHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Any("modifiers", rm),
)
if len(rm.Streams) != 1 {
json.ServerError(w, r, fmt.Errorf("googlereader: only one stream type expected"))
return
}
switch rm.Streams[0].Type {
case ReadingListStream:
h.handleReadingListStreamHandler(w, r, rm)
case StarredStream:
h.handleStarredStreamHandler(w, r, rm)
case ReadStream:
h.handleReadStreamHandler(w, r, rm)
case FeedStream:
h.handleFeedStreamHandler(w, r, rm)
default:
slog.Warn("[GoogleReader] Unknown Stream",
slog.String("handler", "streamItemIDsHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Any("stream_type", rm.Streams[0].Type),
)
json.ServerError(w, r, fmt.Errorf("googlereader: unknown stream type %s", rm.Streams[0].Type))
}
}
func (h *handler) handleReadingListStreamHandler(w http.ResponseWriter, r *http.Request, rm RequestModifiers) {
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle ReadingListStream",
slog.String("handler", "handleReadingListStreamHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
builder := h.store.NewEntryQueryBuilder(rm.UserID)
for _, s := range rm.ExcludeTargets {
switch s.Type {
case ReadStream:
builder.WithStatus(model.EntryStatusUnread)
default:
slog.Warn("[GoogleReader] Unknown ExcludeTargets filter type",
slog.String("handler", "handleReadingListStreamHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Int("filter_type", int(s.Type)),
)
}
}
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithLimit(rm.Count)
builder.WithOffset(rm.Offset)
builder.WithSorting(model.DefaultSortingOrder, rm.SortDirection)
if rm.StartTime > 0 {
builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
}
if rm.StopTime > 0 {
builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
}
rawEntryIDs, err := builder.GetEntryIDs()
if err != nil {
json.ServerError(w, r, err)
return
}
var itemRefs = make([]itemRef, 0)
for _, entryID := range rawEntryIDs {
formattedID := strconv.FormatInt(entryID, 10)
itemRefs = append(itemRefs, itemRef{ID: formattedID})
}
totalEntries, err := builder.CountEntries()
if err != nil {
json.ServerError(w, r, err)
return
}
continuation := 0
if len(itemRefs)+rm.Offset < totalEntries {
continuation = len(itemRefs) + rm.Offset
}
json.OK(w, r, streamIDResponse{itemRefs, continuation})
}
func (h *handler) handleStarredStreamHandler(w http.ResponseWriter, r *http.Request, rm RequestModifiers) {
builder := h.store.NewEntryQueryBuilder(rm.UserID)
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithStarred(true)
builder.WithLimit(rm.Count)
builder.WithOffset(rm.Offset)
builder.WithSorting(model.DefaultSortingOrder, rm.SortDirection)
if rm.StartTime > 0 {
builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
}
if rm.StopTime > 0 {
builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
}
rawEntryIDs, err := builder.GetEntryIDs()
if err != nil {
json.ServerError(w, r, err)
return
}
var itemRefs = make([]itemRef, 0)
for _, entryID := range rawEntryIDs {
formattedID := strconv.FormatInt(entryID, 10)
itemRefs = append(itemRefs, itemRef{ID: formattedID})
}
totalEntries, err := builder.CountEntries()
if err != nil {
json.ServerError(w, r, err)
return
}
continuation := 0
if len(itemRefs)+rm.Offset < totalEntries {
continuation = len(itemRefs) + rm.Offset
}
json.OK(w, r, streamIDResponse{itemRefs, continuation})
}
func (h *handler) handleReadStreamHandler(w http.ResponseWriter, r *http.Request, rm RequestModifiers) {
builder := h.store.NewEntryQueryBuilder(rm.UserID)
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithStatus(model.EntryStatusRead)
builder.WithLimit(rm.Count)
builder.WithOffset(rm.Offset)
builder.WithSorting(model.DefaultSortingOrder, rm.SortDirection)
if rm.StartTime > 0 {
builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
}
if rm.StopTime > 0 {
builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
}
rawEntryIDs, err := builder.GetEntryIDs()
if err != nil {
json.ServerError(w, r, err)
return
}
var itemRefs = make([]itemRef, 0)
for _, entryID := range rawEntryIDs {
formattedID := strconv.FormatInt(entryID, 10)
itemRefs = append(itemRefs, itemRef{ID: formattedID})
}
totalEntries, err := builder.CountEntries()
if err != nil {
json.ServerError(w, r, err)
return
}
continuation := 0
if len(itemRefs)+rm.Offset < totalEntries {
continuation = len(itemRefs) + rm.Offset
}
json.OK(w, r, streamIDResponse{itemRefs, continuation})
}
func (h *handler) handleFeedStreamHandler(w http.ResponseWriter, r *http.Request, rm RequestModifiers) {
feedID, err := strconv.ParseInt(rm.Streams[0].ID, 10, 64)
if err != nil {
json.ServerError(w, r, err)
return
}
builder := h.store.NewEntryQueryBuilder(rm.UserID)
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithFeedID(feedID)
builder.WithLimit(rm.Count)
builder.WithOffset(rm.Offset)
builder.WithSorting(model.DefaultSortingOrder, rm.SortDirection)
if rm.StartTime > 0 {
builder.AfterPublishedDate(time.Unix(rm.StartTime, 0))
}
if rm.StopTime > 0 {
builder.BeforePublishedDate(time.Unix(rm.StopTime, 0))
}
if len(rm.ExcludeTargets) > 0 {
for _, s := range rm.ExcludeTargets {
if s.Type == ReadStream {
builder.WithoutStatus(model.EntryStatusRead)
}
}
}
rawEntryIDs, err := builder.GetEntryIDs()
if err != nil {
json.ServerError(w, r, err)
return
}
var itemRefs = make([]itemRef, 0)
for _, entryID := range rawEntryIDs {
formattedID := strconv.FormatInt(entryID, 10)
itemRefs = append(itemRefs, itemRef{ID: formattedID})
}
totalEntries, err := builder.CountEntries()
if err != nil {
json.ServerError(w, r, err)
return
}
continuation := 0
if len(itemRefs)+rm.Offset < totalEntries {
continuation = len(itemRefs) + rm.Offset
}
json.OK(w, r, streamIDResponse{itemRefs, continuation})
}
func (h *handler) markAllAsReadHandler(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
clientIP := request.ClientIP(r)
slog.Debug("[GoogleReader] Handle /mark-all-as-read",
slog.String("handler", "markAllAsReadHandler"),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
if err := r.ParseForm(); err != nil {
json.BadRequest(w, r, err)
return
}
stream, err := getStream(r.Form.Get(paramStreamID), userID)
if err != nil {
json.BadRequest(w, r, err)
return
}
var before time.Time
if timestampParamValue := r.Form.Get(paramTimestamp); timestampParamValue != "" {
timestampParsedValue, err := strconv.ParseInt(timestampParamValue, 10, 64)
if err != nil {
json.BadRequest(w, r, err)
return
}
if timestampParsedValue > 0 {
// It's unclear if the timestamp is in seconds or microseconds, so we try both using a naive approach.
if len(timestampParamValue) >= 16 {
before = time.UnixMicro(timestampParsedValue)
} else {
before = time.Unix(timestampParsedValue, 0)
}
}
}
if before.IsZero() {
before = time.Now()
}
switch stream.Type {
case FeedStream:
feedID, err := strconv.ParseInt(stream.ID, 10, 64)
if err != nil {
json.BadRequest(w, r, err)
return
}
err = h.store.MarkFeedAsRead(userID, feedID, before)
if err != nil {
json.ServerError(w, r, err)
return
}
case LabelStream:
category, err := h.store.CategoryByTitle(userID, stream.ID)
if err != nil {
json.ServerError(w, r, err)
return
}
if category == nil {
json.NotFound(w, r)
return
}
if err := h.store.MarkCategoryAsRead(userID, category.ID, before); err != nil {
json.ServerError(w, r, err)
return
}
case ReadingListStream:
if err = h.store.MarkAllAsReadBeforeDate(userID, before); err != nil {
json.ServerError(w, r, err)
return
}
}
sendOkayResponse(w)
}
v2-2.2.13/internal/googlereader/item.go 0000664 0000000 0000000 00000004454 15062123773 0017703 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/v2/internal/googlereader"
import (
"fmt"
"net/http"
"strconv"
"strings"
)
const (
ItemIDPrefix = "tag:google.com,2005:reader/item/"
ItemIDFormat = "tag:google.com,2005:reader/item/%016x"
)
func convertEntryIDToLongFormItemID(entryID int64) string {
// The entry ID is a 64-bit integer, so we need to format it as a 16-character hexadecimal string.
return fmt.Sprintf(ItemIDFormat, entryID)
}
// Expected format: "tag:google.com,2005:reader/item/00000000148b9369" (hexadecimal string with prefix and padding)
// NetNewsWire uses this format: "tag:google.com,2005:reader/item/2f2" (hexadecimal string with prefix and no padding)
// Reeder uses this format: "000000000000048c" (hexadecimal string without prefix and padding)
// Liferea uses this format: "12345" (decimal string)
// It returns the parsed ID as a int64 and an error if parsing fails.
func parseItemID(itemIDValue string) (int64, error) {
var itemID int64
if strings.HasPrefix(itemIDValue, ItemIDPrefix) {
n, err := fmt.Sscanf(itemIDValue, ItemIDFormat, &itemID)
if err != nil {
return 0, fmt.Errorf("failed to parse hexadecimal item ID %s: %w", itemIDValue, err)
}
if n != 1 {
return 0, fmt.Errorf("failed to parse hexadecimal item ID %s: expected 1 value, got %d", itemIDValue, n)
}
if itemID == 0 {
return 0, fmt.Errorf("failed to parse hexadecimal item ID %s: item ID is zero", itemIDValue)
}
return itemID, nil
}
if len(itemIDValue) == 16 {
if n, err := fmt.Sscanf(itemIDValue, "%016x", &itemID); err == nil && n == 1 {
return itemID, nil
}
}
itemID, err := strconv.ParseInt(itemIDValue, 10, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse decimal item ID %s: %w", itemIDValue, err)
}
return itemID, nil
}
func parseItemIDsFromRequest(r *http.Request) ([]int64, error) {
items := r.Form[paramItemIDs]
if len(items) == 0 {
return nil, fmt.Errorf("googlereader: no items requested")
}
itemIDs := make([]int64, len(items))
for i, item := range items {
itemID, err := parseItemID(item)
if err != nil {
return nil, fmt.Errorf("googlereader: failed to parse item ID %s: %w", item, err)
}
itemIDs[i] = itemID
}
return itemIDs, nil
}
v2-2.2.13/internal/googlereader/item_test.go 0000664 0000000 0000000 00000005056 15062123773 0020741 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/v2/internal/googlereader"
import (
"net/http"
"net/url"
"reflect"
"testing"
)
func TestConvertEntryIDToLongFormItemID(t *testing.T) {
entryID := int64(344691561)
expected := "tag:google.com,2005:reader/item/00000000148b9369"
result := convertEntryIDToLongFormItemID(entryID)
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
}
func TestParseItemIDsFromRequest(t *testing.T) {
formValues := url.Values{}
formValues.Add("i", "12345")
formValues.Add("i", "tag:google.com,2005:reader/item/00000000148b9369")
formValues.Add("i", "tag:google.com,2005:reader/item/2f2")
formValues.Add("i", "000000000000046f")
formValues.Add("i", "tag:google.com,2005:reader/item/272")
request := &http.Request{
Form: formValues,
}
result, err := parseItemIDsFromRequest(request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var expected = []int64{12345, 344691561, 754, 1135, 626}
if !reflect.DeepEqual(result, expected) {
t.Errorf("expected %v, got %v", expected, result)
}
// Test with no item IDs
formValues = url.Values{}
request = &http.Request{
Form: formValues,
}
_, err = parseItemIDsFromRequest(request)
if err == nil {
t.Fatalf("expected error, got nil")
}
}
func TestParseItemID(t *testing.T) {
// Test with long form ID and hex ID
result, err := parseItemID("tag:google.com,2005:reader/item/0000000000000001")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := int64(1)
if result != expected {
t.Errorf("expected %d, got %d", expected, result)
}
// Test with hexadecimal long form ID
result, err = parseItemID("0000000000000468")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected = int64(1128)
if result != expected {
t.Errorf("expected %d, got %d", expected, result)
}
// Test with short form ID
result, err = parseItemID("12345")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected = int64(12345)
if result != expected {
t.Errorf("expected %d, got %d", expected, result)
}
// Test with invalid long form ID
_, err = parseItemID("tag:google.com,2005:reader/item/000000000000000g")
if err == nil {
t.Fatalf("expected error, got nil")
}
// Test with invalid short form ID
_, err = parseItemID("invalid_id")
if err == nil {
t.Fatalf("expected error, got nil")
}
// Test with empty ID
_, err = parseItemID("")
if err == nil {
t.Fatalf("expected error, got nil")
}
}
v2-2.2.13/internal/googlereader/middleware.go 0000664 0000000 0000000 00000013752 15062123773 0021063 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/v2/internal/googlereader"
import (
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"log/slog"
"net/http"
"strings"
"miniflux.app/v2/internal/http/request"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/storage"
)
type middleware struct {
store *storage.Storage
}
func newMiddleware(s *storage.Storage) *middleware {
return &middleware{s}
}
func (m *middleware) handleCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Authorization")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func (m *middleware) apiKeyAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
var token string
if r.Method == http.MethodPost {
if err := r.ParseForm(); err != nil {
slog.Warn("[GoogleReader] Could not parse request form data",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Any("error", err),
)
sendUnauthorizedResponse(w)
return
}
token = r.Form.Get("T")
if token == "" {
slog.Warn("[GoogleReader] Post-Form T field is empty",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
sendUnauthorizedResponse(w)
return
}
} else {
authorization := r.Header.Get("Authorization")
if authorization == "" {
slog.Warn("[GoogleReader] No token provided",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
sendUnauthorizedResponse(w)
return
}
fields := strings.Fields(authorization)
if len(fields) != 2 {
slog.Warn("[GoogleReader] Authorization header does not have the expected GoogleLogin format auth=xxxxxx",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
sendUnauthorizedResponse(w)
return
}
if fields[0] != "GoogleLogin" {
slog.Warn("[GoogleReader] Authorization header does not begin with GoogleLogin",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
sendUnauthorizedResponse(w)
return
}
auths := strings.Split(fields[1], "=")
if len(auths) != 2 {
slog.Warn("[GoogleReader] Authorization header does not have the expected GoogleLogin format auth=xxxxxx",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
sendUnauthorizedResponse(w)
return
}
if auths[0] != "auth" {
slog.Warn("[GoogleReader] Authorization header does not have the expected GoogleLogin format auth=xxxxxx",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
sendUnauthorizedResponse(w)
return
}
token = auths[1]
}
parts := strings.Split(token, "/")
if len(parts) != 2 {
slog.Warn("[GoogleReader] Auth token does not have the expected structure username/hash",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.String("token", token),
)
sendUnauthorizedResponse(w)
return
}
var integration *model.Integration
var user *model.User
var err error
if integration, err = m.store.GoogleReaderUserGetIntegration(parts[0]); err != nil {
slog.Warn("[GoogleReader] No user found with the given Google Reader username",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Any("error", err),
)
sendUnauthorizedResponse(w)
return
}
expectedToken := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
if expectedToken != token {
slog.Warn("[GoogleReader] Token does not match",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
sendUnauthorizedResponse(w)
return
}
if user, err = m.store.UserByID(integration.UserID); err != nil {
slog.Error("[GoogleReader] Unable to fetch user from database",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
slog.Any("error", err),
)
sendUnauthorizedResponse(w)
return
}
if user == nil {
slog.Warn("[GoogleReader] No user found with the given Google Reader credentials",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("user_agent", r.UserAgent()),
)
sendUnauthorizedResponse(w)
return
}
m.store.SetLastLogin(integration.UserID)
ctx := r.Context()
ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
ctx = context.WithValue(ctx, request.UserNameContextKey, user.Username)
ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
ctx = context.WithValue(ctx, request.GoogleReaderTokenKey, token)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func getAuthToken(username, password string) string {
token := hex.EncodeToString(hmac.New(sha1.New, []byte(username+password)).Sum(nil))
token = username + "/" + token
return token
}
v2-2.2.13/internal/googlereader/parameters.go 0000664 0000000 0000000 00000003617 15062123773 0021110 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/v2/internal/googlereader"
const (
// paramItemIDs - name of the parameter with the item ids
paramItemIDs = "i"
// paramStreamID - name of the parameter containing the stream to be included
paramStreamID = "s"
// paramStreamExcludes - name of the parameter containing streams to be excluded
paramStreamExcludes = "xt"
// paramStreamFilters - name of the parameter containing streams to be included
paramStreamFilters = "it"
// paramStreamMaxItems - name of the parameter containing number of items per page/max items returned
paramStreamMaxItems = "n"
// paramStreamOrder - name of the parameter containing the sort criteria
paramStreamOrder = "r"
// paramStreamStartTime - name of the parameter containing epoch timestamp, filtering items older than
paramStreamStartTime = "ot"
// paramStreamStopTime - name of the parameter containing epoch timestamp, filtering items newer than
paramStreamStopTime = "nt"
// paramTagsRemove - name of the parameter containing tags (streams) to be removed
paramTagsRemove = "r"
// paramTagsAdd - name of the parameter containing tags (streams) to be added
paramTagsAdd = "a"
// paramSubscribeAction - name of the parameter indicating the action to take for subscription/edit
paramSubscribeAction = "ac"
// paramTitle - name of the parameter for the title of the subscription
paramTitle = "t"
// paramQuickAdd - name of the parameter for a URL being quick subscribed to
paramQuickAdd = "quickadd"
// paramDestination - name of the parameter for the new name of a tag
paramDestination = "dest"
// paramContinuation - name of the parameter for callers to pass to receive the next page of results
paramContinuation = "c"
// paramTimestamp - name of the parameter for unix timestamp
paramTimestamp = "ts"
)
v2-2.2.13/internal/googlereader/prefix_suffix.go 0000664 0000000 0000000 00000002610 15062123773 0021616 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/v2/internal/googlereader"
const (
// streamPrefix is the prefix for streams (read/starred/reading list and so on)
streamPrefix = "user/-/state/com.google/"
// userStreamPrefix is the user specific prefix for streams (read/starred/reading list and so on)
userStreamPrefix = "user/%d/state/com.google/"
// labelPrefix is the prefix for a label stream
labelPrefix = "user/-/label/"
// userLabelPrefix is the user specific prefix prefix for a label stream
userLabelPrefix = "user/%d/label/"
// feedPrefix is the prefix for a feed stream
feedPrefix = "feed/"
// readStreamSuffix is the suffix for read stream
readStreamSuffix = "read"
// starredStreamSuffix is the suffix for starred stream
starredStreamSuffix = "starred"
// readingListStreamSuffix is the suffix for reading list stream
readingListStreamSuffix = "reading-list"
// keptUnreadStreamSuffix is the suffix for kept unread stream
keptUnreadStreamSuffix = "kept-unread"
// broadcastStreamSuffix is the suffix for broadcast stream
broadcastStreamSuffix = "broadcast"
// broadcastFriendsStreamSuffix is the suffix for broadcast friends stream
broadcastFriendsStreamSuffix = "broadcast-friends"
// likeStreamSuffix is the suffix for like stream
likeStreamSuffix = "like"
)
v2-2.2.13/internal/googlereader/request_modifier.go 0000664 0000000 0000000 00000005442 15062123773 0022311 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/v2/internal/googlereader"
import (
"fmt"
"net/http"
"strings"
"miniflux.app/v2/internal/http/request"
)
type RequestModifiers struct {
ExcludeTargets []Stream
FilterTargets []Stream
Streams []Stream
Count int
Offset int
SortDirection string
StartTime int64
StopTime int64
ContinuationToken string
UserID int64
}
func (r RequestModifiers) String() string {
var results []string
results = append(results, fmt.Sprintf("UserID: %d", r.UserID))
var streamStr []string
for _, s := range r.Streams {
streamStr = append(streamStr, s.String())
}
results = append(results, fmt.Sprintf("Streams: [%s]", strings.Join(streamStr, ", ")))
var exclusions []string
for _, s := range r.ExcludeTargets {
exclusions = append(exclusions, s.String())
}
results = append(results, fmt.Sprintf("Exclusions: [%s]", strings.Join(exclusions, ", ")))
var filters []string
for _, s := range r.FilterTargets {
filters = append(filters, s.String())
}
results = append(results, fmt.Sprintf("Filters: [%s]", strings.Join(filters, ", ")))
results = append(results, fmt.Sprintf("Count: %d", r.Count))
results = append(results, fmt.Sprintf("Offset: %d", r.Offset))
results = append(results, fmt.Sprintf("Sort Direction: %s", r.SortDirection))
results = append(results, fmt.Sprintf("Continuation Token: %s", r.ContinuationToken))
results = append(results, fmt.Sprintf("Start Time: %d", r.StartTime))
results = append(results, fmt.Sprintf("Stop Time: %d", r.StopTime))
return strings.Join(results, "; ")
}
func parseStreamFilterFromRequest(r *http.Request) (RequestModifiers, error) {
userID := request.UserID(r)
result := RequestModifiers{
SortDirection: "desc",
UserID: userID,
}
streamOrder := request.QueryStringParam(r, paramStreamOrder, "d")
if streamOrder == "o" {
result.SortDirection = "asc"
}
var err error
result.Streams, err = getStreams(request.QueryStringParamList(r, paramStreamID), userID)
if err != nil {
return RequestModifiers{}, err
}
result.ExcludeTargets, err = getStreams(request.QueryStringParamList(r, paramStreamExcludes), userID)
if err != nil {
return RequestModifiers{}, err
}
result.FilterTargets, err = getStreams(request.QueryStringParamList(r, paramStreamFilters), userID)
if err != nil {
return RequestModifiers{}, err
}
result.Count = request.QueryIntParam(r, paramStreamMaxItems, 0)
result.Offset = request.QueryIntParam(r, paramContinuation, 0)
result.StartTime = request.QueryInt64Param(r, paramStreamStartTime, int64(0))
result.StopTime = request.QueryInt64Param(r, paramStreamStopTime, int64(0))
return result, nil
}
v2-2.2.13/internal/googlereader/response.go 0000664 0000000 0000000 00000007523 15062123773 0020603 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/v2/internal/googlereader"
import (
"fmt"
"net/http"
)
type loginResponse struct {
SID string `json:"SID,omitempty"`
LSID string `json:"LSID,omitempty"`
Auth string `json:"Auth,omitempty"`
}
func (l loginResponse) String() string {
return fmt.Sprintf("SID=%s\nLSID=%s\nAuth=%s\n", l.SID, l.LSID, l.Auth)
}
type userInfoResponse struct {
UserID string `json:"userId"`
UserName string `json:"userName"`
UserProfileID string `json:"userProfileId"`
UserEmail string `json:"userEmail"`
}
type subscriptionResponse struct {
ID string `json:"id"`
Title string `json:"title"`
Categories []subscriptionCategoryResponse `json:"categories"`
URL string `json:"url"`
HTMLURL string `json:"htmlUrl"`
IconURL string `json:"iconUrl"`
}
type subscriptionsResponse struct {
Subscriptions []subscriptionResponse `json:"subscriptions"`
}
type quickAddResponse struct {
NumResults int64 `json:"numResults"`
Query string `json:"query,omitempty"`
StreamID string `json:"streamId,omitempty"`
StreamName string `json:"streamName,omitempty"`
}
type subscriptionCategoryResponse struct {
ID string `json:"id"`
Label string `json:"label,omitempty"`
Type string `json:"type,omitempty"`
}
type itemRef struct {
ID string `json:"id"`
DirectStreamIDs string `json:"directStreamIds,omitempty"`
TimestampUsec string `json:"timestampUsec,omitempty"`
}
type streamIDResponse struct {
ItemRefs []itemRef `json:"itemRefs"`
Continuation int `json:"continuation,omitempty,string"`
}
type tagsResponse struct {
Tags []subscriptionCategoryResponse `json:"tags"`
}
type streamContentItemsResponse struct {
Direction string `json:"direction"`
ID string `json:"id"`
Title string `json:"title"`
Self []contentHREF `json:"self"`
Updated int64 `json:"updated"`
Items []contentItem `json:"items"`
Author string `json:"author"`
}
type contentItem struct {
ID string `json:"id"`
Categories []string `json:"categories"`
Title string `json:"title"`
CrawlTimeMsec string `json:"crawlTimeMsec"`
TimestampUsec string `json:"timestampUsec"`
Published int64 `json:"published"`
Updated int64 `json:"updated"`
Author string `json:"author"`
Alternate []contentHREFType `json:"alternate"`
Summary contentItemContent `json:"summary"`
Content contentItemContent `json:"content"`
Origin contentItemOrigin `json:"origin"`
Enclosure []contentItemEnclosure `json:"enclosure"`
Canonical []contentHREF `json:"canonical"`
}
type contentHREFType struct {
HREF string `json:"href"`
Type string `json:"type"`
}
type contentHREF struct {
HREF string `json:"href"`
}
type contentItemEnclosure struct {
URL string `json:"url"`
Type string `json:"type"`
}
type contentItemContent struct {
Direction string `json:"direction"`
Content string `json:"content"`
}
type contentItemOrigin struct {
StreamID string `json:"streamId"`
Title string `json:"title"`
HTMLUrl string `json:"htmlUrl"`
}
func sendUnauthorizedResponse(w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("X-Reader-Google-Bad-Token", "true")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorized"))
}
func sendOkayResponse(w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
v2-2.2.13/internal/googlereader/stream.go 0000664 0000000 0000000 00000006466 15062123773 0020245 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/v2/internal/googlereader"
import (
"fmt"
"strings"
)
type StreamType int
const (
// NoStream - no stream type
NoStream StreamType = iota
// ReadStream - read stream type
ReadStream
// StarredStream - starred stream type
StarredStream
// ReadingListStream - reading list stream type
ReadingListStream
// KeptUnreadStream - kept unread stream type
KeptUnreadStream
// BroadcastStream - broadcast stream type
BroadcastStream
// BroadcastFriendsStream - broadcast friends stream type
BroadcastFriendsStream
// LabelStream - label stream type
LabelStream
// FeedStream - feed stream type
FeedStream
// LikeStream - like stream type
LikeStream
)
// Stream defines a stream type and its ID.
type Stream struct {
Type StreamType
ID string
}
func (s Stream) String() string {
return fmt.Sprintf("%v - '%s'", s.Type, s.ID)
}
func (st StreamType) String() string {
switch st {
case NoStream:
return "NoStream"
case ReadStream:
return "ReadStream"
case StarredStream:
return "StarredStream"
case ReadingListStream:
return "ReadingListStream"
case KeptUnreadStream:
return "KeptUnreadStream"
case BroadcastStream:
return "BroadcastStream"
case BroadcastFriendsStream:
return "BroadcastFriendsStream"
case LabelStream:
return "LabelStream"
case FeedStream:
return "FeedStream"
case LikeStream:
return "LikeStream"
default:
return st.String()
}
}
func getStream(streamID string, userID int64) (Stream, error) {
switch {
case strings.HasPrefix(streamID, feedPrefix):
return Stream{Type: FeedStream, ID: strings.TrimPrefix(streamID, feedPrefix)}, nil
case strings.HasPrefix(streamID, fmt.Sprintf(userStreamPrefix, userID)), strings.HasPrefix(streamID, streamPrefix):
id := strings.TrimPrefix(streamID, fmt.Sprintf(userStreamPrefix, userID))
id = strings.TrimPrefix(id, streamPrefix)
switch id {
case readStreamSuffix:
return Stream{ReadStream, ""}, nil
case starredStreamSuffix:
return Stream{StarredStream, ""}, nil
case readingListStreamSuffix:
return Stream{ReadingListStream, ""}, nil
case keptUnreadStreamSuffix:
return Stream{KeptUnreadStream, ""}, nil
case broadcastStreamSuffix:
return Stream{BroadcastStream, ""}, nil
case broadcastFriendsStreamSuffix:
return Stream{BroadcastFriendsStream, ""}, nil
case likeStreamSuffix:
return Stream{LikeStream, ""}, nil
default:
return Stream{NoStream, ""}, fmt.Errorf("googlereader: unknown stream with id: %s", id)
}
case strings.HasPrefix(streamID, fmt.Sprintf(userLabelPrefix, userID)), strings.HasPrefix(streamID, labelPrefix):
id := strings.TrimPrefix(streamID, fmt.Sprintf(userLabelPrefix, userID))
id = strings.TrimPrefix(id, labelPrefix)
return Stream{LabelStream, id}, nil
case streamID == "":
return Stream{NoStream, ""}, nil
default:
return Stream{NoStream, ""}, fmt.Errorf("googlereader: unknown stream type: %s", streamID)
}
}
func getStreams(streamIDs []string, userID int64) ([]Stream, error) {
streams := make([]Stream, 0, len(streamIDs))
for _, streamID := range streamIDs {
stream, err := getStream(streamID, userID)
if err != nil {
return []Stream{}, err
}
streams = append(streams, stream)
}
return streams, nil
}
v2-2.2.13/internal/http/ 0000775 0000000 0000000 00000000000 15062123773 0014727 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/http/cookie/ 0000775 0000000 0000000 00000000000 15062123773 0016200 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/http/cookie/cookie.go 0000664 0000000 0000000 00000002163 15062123773 0020002 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cookie // import "miniflux.app/v2/internal/http/cookie"
import (
"net/http"
"time"
"miniflux.app/v2/internal/config"
)
// Cookie names.
const (
CookieAppSessionID = "MinifluxAppSessionID"
CookieUserSessionID = "MinifluxUserSessionID"
)
// New creates a new cookie.
func New(name, value string, isHTTPS bool, path string) *http.Cookie {
return &http.Cookie{
Name: name,
Value: value,
Path: basePath(path),
Secure: isHTTPS,
HttpOnly: true,
Expires: time.Now().Add(config.Opts.CleanupRemoveSessionsInterval()),
SameSite: http.SameSiteLaxMode,
}
}
// Expired returns an expired cookie.
func Expired(name string, isHTTPS bool, path string) *http.Cookie {
return &http.Cookie{
Name: name,
Value: "",
Path: basePath(path),
Secure: isHTTPS,
HttpOnly: true,
MaxAge: -1,
Expires: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
SameSite: http.SameSiteLaxMode,
}
}
func basePath(path string) string {
if path == "" {
return "/"
}
return path
}
v2-2.2.13/internal/http/request/ 0000775 0000000 0000000 00000000000 15062123773 0016417 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/http/request/client_ip.go 0000664 0000000 0000000 00000002224 15062123773 0020714 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/v2/internal/http/request"
import (
"net"
"net/http"
"strings"
)
// FindClientIP returns the client real IP address based on trusted Reverse-Proxy HTTP headers.
func FindClientIP(r *http.Request) string {
headers := [...]string{"X-Forwarded-For", "X-Real-Ip"}
for _, header := range headers {
value := r.Header.Get(header)
if value != "" {
addresses := strings.Split(value, ",")
address := strings.TrimSpace(addresses[0])
address = dropIPv6zone(address)
if net.ParseIP(address) != nil {
return address
}
}
}
// Fallback to TCP/IP source IP address.
return FindRemoteIP(r)
}
// FindRemoteIP returns remote client IP address without considering HTTP headers.
func FindRemoteIP(r *http.Request) string {
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
remoteIP = r.RemoteAddr
}
return dropIPv6zone(remoteIP)
}
func dropIPv6zone(address string) string {
i := strings.IndexByte(address, '%')
if i != -1 {
address = address[:i]
}
return address
}
v2-2.2.13/internal/http/request/client_ip_test.go 0000664 0000000 0000000 00000007444 15062123773 0021764 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/v2/internal/http/request"
import (
"net/http"
"testing"
)
func TestFindClientIPWithoutHeaders(t *testing.T) {
r := &http.Request{RemoteAddr: "192.168.0.1:4242"}
if ip := FindClientIP(r); ip != "192.168.0.1" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
r = &http.Request{RemoteAddr: "192.168.0.1"}
if ip := FindClientIP(r); ip != "192.168.0.1" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
r = &http.Request{RemoteAddr: "fe80::14c2:f039:edc7:edc7"}
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
r = &http.Request{RemoteAddr: "fe80::14c2:f039:edc7:edc7%eth0"}
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
r = &http.Request{RemoteAddr: "[fe80::14c2:f039:edc7:edc7%eth0]:4242"}
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
}
func TestFindClientIPWithXFFHeader(t *testing.T) {
// Test with multiple IPv4 addresses.
headers := http.Header{}
headers.Set("X-Forwarded-For", "203.0.113.195, 70.41.3.18, 150.172.238.178")
r := &http.Request{RemoteAddr: "192.168.0.1:4242", Header: headers}
if ip := FindClientIP(r); ip != "203.0.113.195" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
// Test with single IPv6 address.
headers = http.Header{}
headers.Set("X-Forwarded-For", "2001:db8:85a3:8d3:1319:8a2e:370:7348")
r = &http.Request{RemoteAddr: "192.168.0.1:4242", Header: headers}
if ip := FindClientIP(r); ip != "2001:db8:85a3:8d3:1319:8a2e:370:7348" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
// Test with single IPv6 address with zone
headers = http.Header{}
headers.Set("X-Forwarded-For", "fe80::14c2:f039:edc7:edc7%eth0")
r = &http.Request{RemoteAddr: "192.168.0.1:4242", Header: headers}
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
// Test with single IPv4 address.
headers = http.Header{}
headers.Set("X-Forwarded-For", "70.41.3.18")
r = &http.Request{RemoteAddr: "192.168.0.1:4242", Header: headers}
if ip := FindClientIP(r); ip != "70.41.3.18" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
// Test with invalid IP address.
headers = http.Header{}
headers.Set("X-Forwarded-For", "fake IP")
r = &http.Request{RemoteAddr: "192.168.0.1:4242", Header: headers}
if ip := FindClientIP(r); ip != "192.168.0.1" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
}
func TestClientIPWithXRealIPHeader(t *testing.T) {
headers := http.Header{}
headers.Set("X-Real-Ip", "192.168.122.1")
r := &http.Request{RemoteAddr: "192.168.0.1:4242", Header: headers}
if ip := FindClientIP(r); ip != "192.168.122.1" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
}
func TestClientIPWithBothHeaders(t *testing.T) {
headers := http.Header{}
headers.Set("X-Forwarded-For", "203.0.113.195, 70.41.3.18, 150.172.238.178")
headers.Set("X-Real-Ip", "192.168.122.1")
r := &http.Request{RemoteAddr: "192.168.0.1:4242", Header: headers}
if ip := FindClientIP(r); ip != "203.0.113.195" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
}
func TestClientIPWithUnixSocketRemoteAddress(t *testing.T) {
r := &http.Request{RemoteAddr: "@"}
if ip := FindClientIP(r); ip != "@" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
}
func TestClientIPWithUnixSocketRemoteAddrAndBothHeaders(t *testing.T) {
headers := http.Header{}
headers.Set("X-Forwarded-For", "203.0.113.195, 70.41.3.18, 150.172.238.178")
headers.Set("X-Real-Ip", "192.168.122.1")
r := &http.Request{RemoteAddr: "@", Header: headers}
if ip := FindClientIP(r); ip != "203.0.113.195" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
}
v2-2.2.13/internal/http/request/context.go 0000664 0000000 0000000 00000011042 15062123773 0020430 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/v2/internal/http/request"
import (
"net/http"
"strconv"
"time"
"miniflux.app/v2/internal/model"
)
// ContextKey represents a context key.
type ContextKey int
// List of context keys.
const (
UserIDContextKey ContextKey = iota
UserNameContextKey
UserTimezoneContextKey
IsAdminUserContextKey
IsAuthenticatedContextKey
UserSessionTokenContextKey
UserLanguageContextKey
UserThemeContextKey
SessionIDContextKey
CSRFContextKey
OAuth2StateContextKey
OAuth2CodeVerifierContextKey
FlashMessageContextKey
FlashErrorMessageContextKey
LastForceRefreshContextKey
ClientIPContextKey
GoogleReaderTokenKey
WebAuthnDataContextKey
)
func WebAuthnSessionData(r *http.Request) *model.WebAuthnSession {
if v := r.Context().Value(WebAuthnDataContextKey); v != nil {
if value, valid := v.(model.WebAuthnSession); valid {
return &value
}
}
return nil
}
// GoogleReaderToken returns the google reader token if it exists.
func GoogleReaderToken(r *http.Request) string {
return getContextStringValue(r, GoogleReaderTokenKey)
}
// IsAdminUser checks if the logged user is administrator.
func IsAdminUser(r *http.Request) bool {
return getContextBoolValue(r, IsAdminUserContextKey)
}
// IsAuthenticated returns a boolean if the user is authenticated.
func IsAuthenticated(r *http.Request) bool {
return getContextBoolValue(r, IsAuthenticatedContextKey)
}
// UserID returns the UserID of the logged user.
func UserID(r *http.Request) int64 {
return getContextInt64Value(r, UserIDContextKey)
}
// UserName returns the username of the logged user.
func UserName(r *http.Request) string {
value := getContextStringValue(r, UserNameContextKey)
if value == "" {
value = "unknown"
}
return value
}
// UserTimezone returns the timezone used by the logged user.
func UserTimezone(r *http.Request) string {
value := getContextStringValue(r, UserTimezoneContextKey)
if value == "" {
value = "UTC"
}
return value
}
// UserLanguage get the locale used by the current logged user.
func UserLanguage(r *http.Request) string {
language := getContextStringValue(r, UserLanguageContextKey)
if language == "" {
language = "en_US"
}
return language
}
// UserTheme get the theme used by the current logged user.
func UserTheme(r *http.Request) string {
theme := getContextStringValue(r, UserThemeContextKey)
if theme == "" {
theme = "system_serif"
}
return theme
}
// CSRF returns the current CSRF token.
func CSRF(r *http.Request) string {
return getContextStringValue(r, CSRFContextKey)
}
// SessionID returns the current session ID.
func SessionID(r *http.Request) string {
return getContextStringValue(r, SessionIDContextKey)
}
// UserSessionToken returns the current user session token.
func UserSessionToken(r *http.Request) string {
return getContextStringValue(r, UserSessionTokenContextKey)
}
// OAuth2State returns the current OAuth2 state.
func OAuth2State(r *http.Request) string {
return getContextStringValue(r, OAuth2StateContextKey)
}
func OAuth2CodeVerifier(r *http.Request) string {
return getContextStringValue(r, OAuth2CodeVerifierContextKey)
}
// FlashMessage returns the message message if any.
func FlashMessage(r *http.Request) string {
return getContextStringValue(r, FlashMessageContextKey)
}
// FlashErrorMessage returns the message error message if any.
func FlashErrorMessage(r *http.Request) string {
return getContextStringValue(r, FlashErrorMessageContextKey)
}
// LastForceRefresh returns the last force refresh timestamp.
func LastForceRefresh(r *http.Request) time.Time {
jsonStringValue := getContextStringValue(r, LastForceRefreshContextKey)
timestamp, err := strconv.ParseInt(jsonStringValue, 10, 64)
if err != nil {
return time.Time{}
}
return time.Unix(timestamp, 0)
}
// ClientIP returns the client IP address stored in the context.
func ClientIP(r *http.Request) string {
return getContextStringValue(r, ClientIPContextKey)
}
func getContextStringValue(r *http.Request, key ContextKey) string {
if v := r.Context().Value(key); v != nil {
if value, valid := v.(string); valid {
return value
}
}
return ""
}
func getContextBoolValue(r *http.Request, key ContextKey) bool {
if v := r.Context().Value(key); v != nil {
if value, valid := v.(bool); valid {
return value
}
}
return false
}
func getContextInt64Value(r *http.Request, key ContextKey) int64 {
if v := r.Context().Value(key); v != nil {
if value, valid := v.(int64); valid {
return value
}
}
return 0
}
v2-2.2.13/internal/http/request/context_test.go 0000664 0000000 0000000 00000023764 15062123773 0021505 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/v2/internal/http/request"
import (
"context"
"net/http"
"testing"
)
func TestContextStringValue(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
ctx := r.Context()
ctx = context.WithValue(ctx, ClientIPContextKey, "IP")
r = r.WithContext(ctx)
result := getContextStringValue(r, ClientIPContextKey)
expected := "IP"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestContextStringValueWithInvalidType(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
ctx := r.Context()
ctx = context.WithValue(ctx, ClientIPContextKey, 0)
r = r.WithContext(ctx)
result := getContextStringValue(r, ClientIPContextKey)
expected := ""
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestContextStringValueWhenUnset(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := getContextStringValue(r, ClientIPContextKey)
expected := ""
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestContextBoolValue(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
ctx := r.Context()
ctx = context.WithValue(ctx, IsAdminUserContextKey, true)
r = r.WithContext(ctx)
result := getContextBoolValue(r, IsAdminUserContextKey)
expected := true
if result != expected {
t.Errorf(`Unexpected context value, got %v instead of %v`, result, expected)
}
}
func TestContextBoolValueWithInvalidType(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
ctx := r.Context()
ctx = context.WithValue(ctx, IsAdminUserContextKey, "invalid")
r = r.WithContext(ctx)
result := getContextBoolValue(r, IsAdminUserContextKey)
expected := false
if result != expected {
t.Errorf(`Unexpected context value, got %v instead of %v`, result, expected)
}
}
func TestContextBoolValueWhenUnset(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := getContextBoolValue(r, IsAdminUserContextKey)
expected := false
if result != expected {
t.Errorf(`Unexpected context value, got %v instead of %v`, result, expected)
}
}
func TestContextInt64Value(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
ctx := r.Context()
ctx = context.WithValue(ctx, UserIDContextKey, int64(1234))
r = r.WithContext(ctx)
result := getContextInt64Value(r, UserIDContextKey)
expected := int64(1234)
if result != expected {
t.Errorf(`Unexpected context value, got %d instead of %d`, result, expected)
}
}
func TestContextInt64ValueWithInvalidType(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
ctx := r.Context()
ctx = context.WithValue(ctx, UserIDContextKey, "invalid")
r = r.WithContext(ctx)
result := getContextInt64Value(r, UserIDContextKey)
expected := int64(0)
if result != expected {
t.Errorf(`Unexpected context value, got %d instead of %d`, result, expected)
}
}
func TestContextInt64ValueWhenUnset(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := getContextInt64Value(r, UserIDContextKey)
expected := int64(0)
if result != expected {
t.Errorf(`Unexpected context value, got %d instead of %d`, result, expected)
}
}
func TestIsAdmin(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := IsAdminUser(r)
expected := false
if result != expected {
t.Errorf(`Unexpected context value, got %v instead of %v`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, IsAdminUserContextKey, true)
r = r.WithContext(ctx)
result = IsAdminUser(r)
expected = true
if result != expected {
t.Errorf(`Unexpected context value, got %v instead of %v`, result, expected)
}
}
func TestIsAuthenticated(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := IsAuthenticated(r)
expected := false
if result != expected {
t.Errorf(`Unexpected context value, got %v instead of %v`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, IsAuthenticatedContextKey, true)
r = r.WithContext(ctx)
result = IsAuthenticated(r)
expected = true
if result != expected {
t.Errorf(`Unexpected context value, got %v instead of %v`, result, expected)
}
}
func TestUserID(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := UserID(r)
expected := int64(0)
if result != expected {
t.Errorf(`Unexpected context value, got %v instead of %v`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, UserIDContextKey, int64(123))
r = r.WithContext(ctx)
result = UserID(r)
expected = int64(123)
if result != expected {
t.Errorf(`Unexpected context value, got %v instead of %v`, result, expected)
}
}
func TestUserTimezone(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := UserTimezone(r)
expected := "UTC"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, UserTimezoneContextKey, "Europe/Paris")
r = r.WithContext(ctx)
result = UserTimezone(r)
expected = "Europe/Paris"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestUserLanguage(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := UserLanguage(r)
expected := "en_US"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, UserLanguageContextKey, "fr_FR")
r = r.WithContext(ctx)
result = UserLanguage(r)
expected = "fr_FR"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestUserTheme(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := UserTheme(r)
expected := "system_serif"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, UserThemeContextKey, "dark_serif")
r = r.WithContext(ctx)
result = UserTheme(r)
expected = "dark_serif"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestCSRF(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := CSRF(r)
expected := ""
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, CSRFContextKey, "secret")
r = r.WithContext(ctx)
result = CSRF(r)
expected = "secret"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestSessionID(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := SessionID(r)
expected := ""
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, SessionIDContextKey, "id")
r = r.WithContext(ctx)
result = SessionID(r)
expected = "id"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestUserSessionToken(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := UserSessionToken(r)
expected := ""
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, UserSessionTokenContextKey, "token")
r = r.WithContext(ctx)
result = UserSessionToken(r)
expected = "token"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestOAuth2State(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := OAuth2State(r)
expected := ""
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, OAuth2StateContextKey, "state")
r = r.WithContext(ctx)
result = OAuth2State(r)
expected = "state"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestFlashMessage(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := FlashMessage(r)
expected := ""
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, FlashMessageContextKey, "message")
r = r.WithContext(ctx)
result = FlashMessage(r)
expected = "message"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestFlashErrorMessage(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := FlashErrorMessage(r)
expected := ""
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, FlashErrorMessageContextKey, "error message")
r = r.WithContext(ctx)
result = FlashErrorMessage(r)
expected = "error message"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
func TestClientIP(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := ClientIP(r)
expected := ""
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
ctx := r.Context()
ctx = context.WithValue(ctx, ClientIPContextKey, "127.0.0.1")
r = r.WithContext(ctx)
result = ClientIP(r)
expected = "127.0.0.1"
if result != expected {
t.Errorf(`Unexpected context value, got %q instead of %q`, result, expected)
}
}
v2-2.2.13/internal/http/request/cookie.go 0000664 0000000 0000000 00000000606 15062123773 0020221 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/v2/internal/http/request"
import "net/http"
// CookieValue returns the cookie value.
func CookieValue(r *http.Request, name string) string {
cookie, err := r.Cookie(name)
if err != nil {
return ""
}
return cookie.Value
}
v2-2.2.13/internal/http/request/cookie_test.go 0000664 0000000 0000000 00000001511 15062123773 0021254 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/v2/internal/http/request"
import (
"net/http"
"testing"
)
func TestGetCookieValue(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
r.AddCookie(&http.Cookie{Value: "cookie_value", Name: "my_cookie"})
result := CookieValue(r, "my_cookie")
expected := "cookie_value"
if result != expected {
t.Errorf(`Unexpected cookie value, got %q instead of %q`, result, expected)
}
}
func TestGetCookieValueWhenUnset(t *testing.T) {
r, _ := http.NewRequest("GET", "http://example.org", nil)
result := CookieValue(r, "my_cookie")
expected := ""
if result != expected {
t.Errorf(`Unexpected cookie value, got %q instead of %q`, result, expected)
}
}
v2-2.2.13/internal/http/request/params.go 0000664 0000000 0000000 00000005360 15062123773 0020235 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/v2/internal/http/request"
import (
"net/http"
"strconv"
"strings"
"github.com/gorilla/mux"
)
// FormInt64Value returns a form value as integer.
func FormInt64Value(r *http.Request, param string) int64 {
value := r.FormValue(param)
integer, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0
}
return integer
}
// RouteInt64Param returns an URL route parameter as int64.
func RouteInt64Param(r *http.Request, param string) int64 {
vars := mux.Vars(r)
value, err := strconv.ParseInt(vars[param], 10, 64)
if err != nil {
return 0
}
if value < 0 {
return 0
}
return value
}
// RouteStringParam returns a URL route parameter as string.
func RouteStringParam(r *http.Request, param string) string {
vars := mux.Vars(r)
return vars[param]
}
// QueryStringParam returns a query string parameter as string.
func QueryStringParam(r *http.Request, param, defaultValue string) string {
value := r.URL.Query().Get(param)
if value == "" {
value = defaultValue
}
return value
}
// QueryStringParamList returns all values associated to the parameter.
func QueryStringParamList(r *http.Request, param string) []string {
var results []string
values := r.URL.Query()
if _, found := values[param]; found {
for _, value := range values[param] {
value = strings.TrimSpace(value)
if value != "" {
results = append(results, value)
}
}
}
return results
}
// QueryIntParam returns a query string parameter as integer.
func QueryIntParam(r *http.Request, param string, defaultValue int) int {
value := r.URL.Query().Get(param)
if value == "" {
return defaultValue
}
val, err := strconv.ParseInt(value, 10, 0)
if err != nil {
return defaultValue
}
if val < 0 {
return defaultValue
}
return int(val)
}
// QueryInt64Param returns a query string parameter as int64.
func QueryInt64Param(r *http.Request, param string, defaultValue int64) int64 {
value := r.URL.Query().Get(param)
if value == "" {
return defaultValue
}
val, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return defaultValue
}
if val < 0 {
return defaultValue
}
return val
}
// QueryBoolParam returns a query string parameter as bool.
func QueryBoolParam(r *http.Request, param string, defaultValue bool) bool {
value := r.URL.Query().Get(param)
if value == "" {
return defaultValue
}
val, err := strconv.ParseBool(value)
if err != nil {
return defaultValue
}
return val
}
// HasQueryParam checks if the query string contains the given parameter.
func HasQueryParam(r *http.Request, param string) bool {
values := r.URL.Query()
_, ok := values[param]
return ok
}
v2-2.2.13/internal/http/request/params_test.go 0000664 0000000 0000000 00000012064 15062123773 0021273 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/v2/internal/http/request"
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/gorilla/mux"
)
func TestFormInt64Value(t *testing.T) {
f := url.Values{}
f.Set("integer value", "42")
f.Set("invalid value", "invalid integer")
r := &http.Request{Form: f}
result := FormInt64Value(r, "integer value")
expected := int64(42)
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = FormInt64Value(r, "invalid value")
expected = int64(0)
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = FormInt64Value(r, "missing value")
expected = int64(0)
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
}
func TestRouteStringParam(t *testing.T) {
router := mux.NewRouter()
router.HandleFunc("/route/{variable}/index", func(w http.ResponseWriter, r *http.Request) {
result := RouteStringParam(r, "variable")
expected := "value"
if result != expected {
t.Errorf(`Unexpected result, got %q instead of %q`, result, expected)
}
result = RouteStringParam(r, "missing variable")
expected = ""
if result != expected {
t.Errorf(`Unexpected result, got %q instead of %q`, result, expected)
}
})
r, err := http.NewRequest("GET", "/route/value/index", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
router.ServeHTTP(w, r)
}
func TestRouteInt64Param(t *testing.T) {
router := mux.NewRouter()
router.HandleFunc("/a/{variable1}/b/{variable2}/c/{variable3}", func(w http.ResponseWriter, r *http.Request) {
result := RouteInt64Param(r, "variable1")
expected := int64(42)
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = RouteInt64Param(r, "missing variable")
expected = 0
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = RouteInt64Param(r, "variable2")
expected = 0
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = RouteInt64Param(r, "variable3")
expected = 0
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
})
r, err := http.NewRequest("GET", "/a/42/b/not-int/c/-10", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
router.ServeHTTP(w, r)
}
func TestQueryStringParam(t *testing.T) {
u, _ := url.Parse("http://example.org/?key=value")
r := &http.Request{URL: u}
result := QueryStringParam(r, "key", "fallback")
expected := "value"
if result != expected {
t.Errorf(`Unexpected result, got %q instead of %q`, result, expected)
}
result = QueryStringParam(r, "missing key", "fallback")
expected = "fallback"
if result != expected {
t.Errorf(`Unexpected result, got %q instead of %q`, result, expected)
}
}
func TestQueryIntParam(t *testing.T) {
u, _ := url.Parse("http://example.org/?key=42&invalid=value&negative=-5")
r := &http.Request{URL: u}
result := QueryIntParam(r, "key", 84)
expected := 42
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = QueryIntParam(r, "missing key", 84)
expected = 84
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = QueryIntParam(r, "negative", 69)
expected = 69
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = QueryIntParam(r, "invalid", 99)
expected = 99
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
}
func TestQueryInt64Param(t *testing.T) {
u, _ := url.Parse("http://example.org/?key=42&invalid=value&negative=-5")
r := &http.Request{URL: u}
result := QueryInt64Param(r, "key", int64(84))
expected := int64(42)
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = QueryInt64Param(r, "missing key", int64(84))
expected = int64(84)
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = QueryInt64Param(r, "invalid", int64(69))
expected = int64(69)
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
result = QueryInt64Param(r, "invalid", int64(99))
expected = int64(99)
if result != expected {
t.Errorf(`Unexpected result, got %d instead of %d`, result, expected)
}
}
func TestHasQueryParam(t *testing.T) {
u, _ := url.Parse("http://example.org/?key=42")
r := &http.Request{URL: u}
result := HasQueryParam(r, "key")
expected := true
if result != expected {
t.Errorf(`Unexpected result, got %v instead of %v`, result, expected)
}
result = HasQueryParam(r, "missing key")
expected = false
if result != expected {
t.Errorf(`Unexpected result, got %v instead of %v`, result, expected)
}
}
v2-2.2.13/internal/http/response/ 0000775 0000000 0000000 00000000000 15062123773 0016565 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/http/response/builder.go 0000664 0000000 0000000 00000007066 15062123773 0020553 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package response // import "miniflux.app/v2/internal/http/response"
import (
"compress/flate"
"compress/gzip"
"io"
"log/slog"
"net/http"
"strings"
"time"
"github.com/andybalholm/brotli"
)
const compressionThreshold = 1024
// Builder generates HTTP responses.
type Builder struct {
w http.ResponseWriter
r *http.Request
statusCode int
headers map[string]string
enableCompression bool
body any
}
// WithStatus uses the given status code to build the response.
func (b *Builder) WithStatus(statusCode int) *Builder {
b.statusCode = statusCode
return b
}
// WithHeader adds the given HTTP header to the response.
func (b *Builder) WithHeader(key, value string) *Builder {
b.headers[key] = value
return b
}
// WithBody uses the given body to build the response.
func (b *Builder) WithBody(body any) *Builder {
b.body = body
return b
}
// WithAttachment forces the document to be downloaded by the web browser.
func (b *Builder) WithAttachment(filename string) *Builder {
b.headers["Content-Disposition"] = "attachment; filename=" + filename
return b
}
// WithoutCompression disables HTTP compression.
func (b *Builder) WithoutCompression() *Builder {
b.enableCompression = false
return b
}
// WithCaching adds caching headers to the response.
func (b *Builder) WithCaching(etag string, duration time.Duration, callback func(*Builder)) {
b.headers["ETag"] = etag
b.headers["Cache-Control"] = "public"
b.headers["Expires"] = time.Now().Add(duration).UTC().Format(http.TimeFormat)
if etag == b.r.Header.Get("If-None-Match") {
b.statusCode = http.StatusNotModified
b.body = nil
b.Write()
} else {
callback(b)
}
}
// Write generates the HTTP response.
func (b *Builder) Write() {
if b.body == nil {
b.writeHeaders()
return
}
switch v := b.body.(type) {
case []byte:
b.compress(v)
case string:
b.compress([]byte(v))
case io.Reader:
// Compression not implemented in this case
b.writeHeaders()
_, err := io.Copy(b.w, v)
if err != nil {
slog.Error("Unable to write response body", slog.Any("error", err))
}
}
}
func (b *Builder) writeHeaders() {
b.headers["X-Content-Type-Options"] = "nosniff"
b.headers["X-Frame-Options"] = "DENY"
b.headers["Referrer-Policy"] = "no-referrer"
for key, value := range b.headers {
b.w.Header().Set(key, value)
}
b.w.WriteHeader(b.statusCode)
}
func (b *Builder) compress(data []byte) {
if b.enableCompression && len(data) > compressionThreshold {
acceptEncoding := b.r.Header.Get("Accept-Encoding")
switch {
case strings.Contains(acceptEncoding, "br"):
b.headers["Content-Encoding"] = "br"
b.writeHeaders()
brotliWriter := brotli.NewWriterV2(b.w, brotli.DefaultCompression)
brotliWriter.Write(data)
brotliWriter.Close()
return
case strings.Contains(acceptEncoding, "gzip"):
b.headers["Content-Encoding"] = "gzip"
b.writeHeaders()
gzipWriter := gzip.NewWriter(b.w)
gzipWriter.Write(data)
gzipWriter.Close()
return
case strings.Contains(acceptEncoding, "deflate"):
b.headers["Content-Encoding"] = "deflate"
b.writeHeaders()
flateWriter, _ := flate.NewWriter(b.w, -1)
flateWriter.Write(data)
flateWriter.Close()
return
}
}
b.writeHeaders()
b.w.Write(data)
}
// New creates a new response builder.
func New(w http.ResponseWriter, r *http.Request) *Builder {
return &Builder{w: w, r: r, statusCode: http.StatusOK, headers: make(map[string]string), enableCompression: true}
}
v2-2.2.13/internal/http/response/builder_test.go 0000664 0000000 0000000 00000021125 15062123773 0021602 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package response // import "miniflux.app/v2/internal/http/response"
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
func TestResponseHasCommonHeaders(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).Write()
})
handler.ServeHTTP(w, r)
resp := w.Result()
headers := map[string]string{
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
}
for header, expected := range headers {
actual := resp.Header.Get(header)
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
}
func TestBuildResponseWithCustomStatusCode(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithStatus(http.StatusNotAcceptable).Write()
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusNotAcceptable
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
}
func TestBuildResponseWithCustomHeader(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithHeader("X-My-Header", "Value").Write()
})
handler.ServeHTTP(w, r)
resp := w.Result()
expected := "Value"
actual := resp.Header.Get("X-My-Header")
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
func TestBuildResponseWithAttachment(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithAttachment("my_file.pdf").Write()
})
handler.ServeHTTP(w, r)
resp := w.Result()
expected := "attachment; filename=my_file.pdf"
actual := resp.Header.Get("Content-Disposition")
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
func TestBuildResponseWithByteBody(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithBody([]byte("body")).Write()
})
handler.ServeHTTP(w, r)
expectedBody := `body`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
}
func TestBuildResponseWithCachingEnabled(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithCaching("etag", 1*time.Minute, func(b *Builder) {
b.WithBody("cached body")
b.Write()
})
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusOK
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `cached body`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedHeader := "public"
actualHeader := resp.Header.Get("Cache-Control")
if actualHeader != expectedHeader {
t.Fatalf(`Unexpected cache control header, got %q instead of %q`, actualHeader, expectedHeader)
}
if resp.Header.Get("Expires") == "" {
t.Fatalf(`Expires header should not be empty`)
}
}
func TestBuildResponseWithCachingAndEtag(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
r.Header.Set("If-None-Match", "etag")
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithCaching("etag", 1*time.Minute, func(b *Builder) {
b.WithBody("cached body")
b.Write()
})
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusNotModified
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := ``
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedHeader := "public"
actualHeader := resp.Header.Get("Cache-Control")
if actualHeader != expectedHeader {
t.Fatalf(`Unexpected cache control header, got %q instead of %q`, actualHeader, expectedHeader)
}
if resp.Header.Get("Expires") == "" {
t.Fatalf(`Expires header should not be empty`)
}
}
func TestBuildResponseWithBrotliCompression(t *testing.T) {
body := strings.Repeat("a", compressionThreshold+1)
r, err := http.NewRequest("GET", "/", nil)
r.Header.Set("Accept-Encoding", "gzip, deflate, br")
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithBody(body).Write()
})
handler.ServeHTTP(w, r)
resp := w.Result()
expected := "br"
actual := resp.Header.Get("Content-Encoding")
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
func TestBuildResponseWithGzipCompression(t *testing.T) {
body := strings.Repeat("a", compressionThreshold+1)
r, err := http.NewRequest("GET", "/", nil)
r.Header.Set("Accept-Encoding", "gzip, deflate")
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithBody(body).Write()
})
handler.ServeHTTP(w, r)
resp := w.Result()
expected := "gzip"
actual := resp.Header.Get("Content-Encoding")
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
func TestBuildResponseWithDeflateCompression(t *testing.T) {
body := strings.Repeat("a", compressionThreshold+1)
r, err := http.NewRequest("GET", "/", nil)
r.Header.Set("Accept-Encoding", "deflate")
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithBody(body).Write()
})
handler.ServeHTTP(w, r)
resp := w.Result()
expected := "deflate"
actual := resp.Header.Get("Content-Encoding")
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
func TestBuildResponseWithCompressionDisabled(t *testing.T) {
body := strings.Repeat("a", compressionThreshold+1)
r, err := http.NewRequest("GET", "/", nil)
r.Header.Set("Accept-Encoding", "deflate")
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithBody(body).WithoutCompression().Write()
})
handler.ServeHTTP(w, r)
resp := w.Result()
expected := ""
actual := resp.Header.Get("Content-Encoding")
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
func TestBuildResponseWithDeflateCompressionAndSmallPayload(t *testing.T) {
body := strings.Repeat("a", compressionThreshold)
r, err := http.NewRequest("GET", "/", nil)
r.Header.Set("Accept-Encoding", "deflate")
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithBody(body).Write()
})
handler.ServeHTTP(w, r)
resp := w.Result()
expected := ""
actual := resp.Header.Get("Content-Encoding")
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
func TestBuildResponseWithoutCompressionHeader(t *testing.T) {
body := strings.Repeat("a", compressionThreshold+1)
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
New(w, r).WithBody(body).Write()
})
handler.ServeHTTP(w, r)
resp := w.Result()
expected := ""
actual := resp.Header.Get("Content-Encoding")
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
v2-2.2.13/internal/http/response/html/ 0000775 0000000 0000000 00000000000 15062123773 0017531 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/http/response/html/html.go 0000664 0000000 0000000 00000011552 15062123773 0021030 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package html // import "miniflux.app/v2/internal/http/response/html"
import (
"html"
"log/slog"
"net/http"
"miniflux.app/v2/internal/http/request"
"miniflux.app/v2/internal/http/response"
)
// OK creates a new HTML response with a 200 status code.
func OK[T []byte | string](w http.ResponseWriter, r *http.Request, body T) {
builder := response.New(w, r)
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithBody(body)
builder.Write()
}
// ServerError sends an internal error to the client.
func ServerError(w http.ResponseWriter, r *http.Request, err error) {
slog.Error(http.StatusText(http.StatusInternalServerError),
slog.Any("error", err),
slog.String("client_ip", request.ClientIP(r)),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("user_agent", r.UserAgent()),
),
slog.Group("response",
slog.Int("status_code", http.StatusInternalServerError),
),
)
builder := response.New(w, r)
builder.WithStatus(http.StatusInternalServerError)
builder.WithHeader("Content-Security-Policy", response.ContentSecurityPolicyForUntrustedContent)
builder.WithHeader("Content-Type", "text/plain; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithBody(html.EscapeString(err.Error()))
builder.Write()
}
// BadRequest sends a bad request error to the client.
func BadRequest(w http.ResponseWriter, r *http.Request, err error) {
slog.Warn(http.StatusText(http.StatusBadRequest),
slog.Any("error", err),
slog.String("client_ip", request.ClientIP(r)),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("user_agent", r.UserAgent()),
),
slog.Group("response",
slog.Int("status_code", http.StatusBadRequest),
),
)
builder := response.New(w, r)
builder.WithStatus(http.StatusBadRequest)
builder.WithHeader("Content-Security-Policy", response.ContentSecurityPolicyForUntrustedContent)
builder.WithHeader("Content-Type", "text/plain; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithBody(html.EscapeString(err.Error()))
builder.Write()
}
// Forbidden sends a forbidden error to the client.
func Forbidden(w http.ResponseWriter, r *http.Request) {
slog.Warn(http.StatusText(http.StatusForbidden),
slog.String("client_ip", request.ClientIP(r)),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("user_agent", r.UserAgent()),
),
slog.Group("response",
slog.Int("status_code", http.StatusForbidden),
),
)
builder := response.New(w, r)
builder.WithStatus(http.StatusForbidden)
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithBody("Access Forbidden")
builder.Write()
}
// NotFound sends a page not found error to the client.
func NotFound(w http.ResponseWriter, r *http.Request) {
slog.Warn(http.StatusText(http.StatusNotFound),
slog.String("client_ip", request.ClientIP(r)),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("user_agent", r.UserAgent()),
),
slog.Group("response",
slog.Int("status_code", http.StatusNotFound),
),
)
builder := response.New(w, r)
builder.WithStatus(http.StatusNotFound)
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithBody("Page Not Found")
builder.Write()
}
// Redirect redirects the user to another location.
func Redirect(w http.ResponseWriter, r *http.Request, uri string) {
http.Redirect(w, r, uri, http.StatusFound)
}
// RequestedRangeNotSatisfiable sends a range not satisfiable error to the client.
func RequestedRangeNotSatisfiable(w http.ResponseWriter, r *http.Request, contentRange string) {
slog.Warn(http.StatusText(http.StatusRequestedRangeNotSatisfiable),
slog.String("client_ip", request.ClientIP(r)),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("user_agent", r.UserAgent()),
),
slog.Group("response",
slog.Int("status_code", http.StatusRequestedRangeNotSatisfiable),
),
)
builder := response.New(w, r)
builder.WithStatus(http.StatusRequestedRangeNotSatisfiable)
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithHeader("Content-Range", contentRange)
builder.WithBody("Range Not Satisfiable")
builder.Write()
}
v2-2.2.13/internal/http/response/html/html_test.go 0000664 0000000 0000000 00000015276 15062123773 0022076 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package html // import "miniflux.app/v2/internal/http/response/html"
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
)
func TestOKResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
OK(w, r, "Some HTML")
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusOK
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `Some HTML`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
headers := map[string]string{
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": "no-cache, max-age=0, must-revalidate, no-store",
}
for header, expected := range headers {
actual := resp.Header.Get(header)
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
}
func TestServerErrorResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ServerError(w, r, errors.New("Some error with injected HTML "))
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusInternalServerError
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `Some error with injected HTML <script>alert('XSS')</script>`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := "text/plain; charset=utf-8"
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestBadRequestResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
BadRequest(w, r, errors.New("Some error with injected HTML "))
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusBadRequest
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `Some error with injected HTML <script>alert('XSS')</script>`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := "text/plain; charset=utf-8"
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestForbiddenResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Forbidden(w, r)
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusForbidden
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `Access Forbidden`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := "text/html; charset=utf-8"
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestNotFoundResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
NotFound(w, r)
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusNotFound
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `Page Not Found`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := "text/html; charset=utf-8"
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestRedirectResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Redirect(w, r, "/path")
})
handler.ServeHTTP(w, r)
resp := w.Result()
defer resp.Body.Close()
expectedStatusCode := http.StatusFound
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedResult := "/path"
actualResult := resp.Header.Get("Location")
if actualResult != expectedResult {
t.Fatalf(`Unexpected redirect location, got %q instead of %q`, actualResult, expectedResult)
}
}
func TestRequestedRangeNotSatisfiable(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
RequestedRangeNotSatisfiable(w, r, "bytes */12777")
})
handler.ServeHTTP(w, r)
resp := w.Result()
defer resp.Body.Close()
expectedStatusCode := http.StatusRequestedRangeNotSatisfiable
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedContentRangeHeader := "bytes */12777"
actualContentRangeHeader := resp.Header.Get("Content-Range")
if actualContentRangeHeader != expectedContentRangeHeader {
t.Fatalf(`Unexpected content range header, got %q instead of %q`, actualContentRangeHeader, expectedContentRangeHeader)
}
}
v2-2.2.13/internal/http/response/json/ 0000775 0000000 0000000 00000000000 15062123773 0017536 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/http/response/json/json.go 0000664 0000000 0000000 00000014446 15062123773 0021047 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package json // import "miniflux.app/v2/internal/http/response/json"
import (
"encoding/json"
"errors"
"log/slog"
"net/http"
"miniflux.app/v2/internal/http/request"
"miniflux.app/v2/internal/http/response"
)
const contentTypeHeader = `application/json`
// OK creates a new JSON response with a 200 status code.
func OK(w http.ResponseWriter, r *http.Request, body any) {
responseBody, err := json.Marshal(body)
if err != nil {
ServerError(w, r, err)
return
}
builder := response.New(w, r)
builder.WithHeader("Content-Type", contentTypeHeader)
builder.WithBody(responseBody)
builder.Write()
}
// Created sends a created response to the client.
func Created(w http.ResponseWriter, r *http.Request, body any) {
responseBody, err := json.Marshal(body)
if err != nil {
ServerError(w, r, err)
return
}
builder := response.New(w, r)
builder.WithStatus(http.StatusCreated)
builder.WithHeader("Content-Type", contentTypeHeader)
builder.WithBody(responseBody)
builder.Write()
}
// NoContent sends a no content response to the client.
func NoContent(w http.ResponseWriter, r *http.Request) {
builder := response.New(w, r)
builder.WithStatus(http.StatusNoContent)
builder.WithHeader("Content-Type", contentTypeHeader)
builder.Write()
}
func Accepted(w http.ResponseWriter, r *http.Request) {
builder := response.New(w, r)
builder.WithStatus(http.StatusAccepted)
builder.WithHeader("Content-Type", contentTypeHeader)
builder.Write()
}
// ServerError sends an internal error to the client.
func ServerError(w http.ResponseWriter, r *http.Request, err error) {
slog.Error(http.StatusText(http.StatusInternalServerError),
slog.Any("error", err),
slog.String("client_ip", request.ClientIP(r)),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("user_agent", r.UserAgent()),
),
slog.Group("response",
slog.Int("status_code", http.StatusInternalServerError),
),
)
responseBody, jsonErr := generateJSONError(err)
if jsonErr != nil {
slog.Error("Unable to generate JSON error", slog.Any("error", jsonErr))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
builder := response.New(w, r)
builder.WithStatus(http.StatusInternalServerError)
builder.WithHeader("Content-Type", contentTypeHeader)
builder.WithBody(responseBody)
builder.Write()
}
// BadRequest sends a bad request error to the client.
func BadRequest(w http.ResponseWriter, r *http.Request, err error) {
slog.Warn(http.StatusText(http.StatusBadRequest),
slog.Any("error", err),
slog.String("client_ip", request.ClientIP(r)),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("user_agent", r.UserAgent()),
),
slog.Group("response",
slog.Int("status_code", http.StatusBadRequest),
),
)
responseBody, jsonErr := generateJSONError(err)
if jsonErr != nil {
slog.Error("Unable to generate JSON error", slog.Any("error", jsonErr))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
builder := response.New(w, r)
builder.WithStatus(http.StatusBadRequest)
builder.WithHeader("Content-Type", contentTypeHeader)
builder.WithBody(responseBody)
builder.Write()
}
// Unauthorized sends a not authorized error to the client.
func Unauthorized(w http.ResponseWriter, r *http.Request) {
slog.Warn(http.StatusText(http.StatusUnauthorized),
slog.String("client_ip", request.ClientIP(r)),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("user_agent", r.UserAgent()),
),
slog.Group("response",
slog.Int("status_code", http.StatusUnauthorized),
),
)
responseBody, jsonErr := generateJSONError(errors.New("access unauthorized"))
if jsonErr != nil {
slog.Error("Unable to generate JSON error", slog.Any("error", jsonErr))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
builder := response.New(w, r)
builder.WithStatus(http.StatusUnauthorized)
builder.WithHeader("Content-Type", contentTypeHeader)
builder.WithBody(responseBody)
builder.Write()
}
// Forbidden sends a forbidden error to the client.
func Forbidden(w http.ResponseWriter, r *http.Request) {
slog.Warn(http.StatusText(http.StatusForbidden),
slog.String("client_ip", request.ClientIP(r)),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("user_agent", r.UserAgent()),
),
slog.Group("response",
slog.Int("status_code", http.StatusForbidden),
),
)
responseBody, jsonErr := generateJSONError(errors.New("access forbidden"))
if jsonErr != nil {
slog.Error("Unable to generate JSON error", slog.Any("error", jsonErr))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
builder := response.New(w, r)
builder.WithStatus(http.StatusForbidden)
builder.WithHeader("Content-Type", contentTypeHeader)
builder.WithBody(responseBody)
builder.Write()
}
// NotFound sends a page not found error to the client.
func NotFound(w http.ResponseWriter, r *http.Request) {
slog.Warn(http.StatusText(http.StatusNotFound),
slog.String("client_ip", request.ClientIP(r)),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("user_agent", r.UserAgent()),
),
slog.Group("response",
slog.Int("status_code", http.StatusNotFound),
),
)
responseBody, jsonErr := generateJSONError(errors.New("resource not found"))
if jsonErr != nil {
slog.Error("Unable to generate JSON error", slog.Any("error", jsonErr))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
builder := response.New(w, r)
builder.WithStatus(http.StatusNotFound)
builder.WithHeader("Content-Type", contentTypeHeader)
builder.WithBody(responseBody)
builder.Write()
}
func generateJSONError(err error) ([]byte, error) {
type errorMsg struct {
ErrorMessage string `json:"error_message"`
}
encodedBody, err := json.Marshal(errorMsg{ErrorMessage: err.Error()})
if err != nil {
return nil, err
}
return encodedBody, nil
}
v2-2.2.13/internal/http/response/json/json_test.go 0000664 0000000 0000000 00000021157 15062123773 0022103 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package json // import "miniflux.app/v2/internal/http/response/json"
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
)
func TestOKResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
OK(w, r, map[string]string{"key": "value"})
})
handler.ServeHTTP(w, r)
resp := w.Result()
defer resp.Body.Close()
expectedStatusCode := http.StatusOK
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `{"key":"value"}`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %q instead of %q`, actualBody, expectedBody)
}
expectedContentType := contentTypeHeader
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestCreatedResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Created(w, r, map[string]string{"key": "value"})
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusCreated
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `{"key":"value"}`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := contentTypeHeader
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestNoContentResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
NoContent(w, r)
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusNoContent
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := ``
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := contentTypeHeader
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestServerErrorResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ServerError(w, r, errors.New("some error"))
})
handler.ServeHTTP(w, r)
resp := w.Result()
defer resp.Body.Close()
expectedStatusCode := http.StatusInternalServerError
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `{"error_message":"some error"}`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %q instead of %q`, actualBody, expectedBody)
}
expectedContentType := contentTypeHeader
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestBadRequestResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
BadRequest(w, r, errors.New("Some Error"))
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusBadRequest
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `{"error_message":"Some Error"}`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := contentTypeHeader
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestUnauthorizedResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Unauthorized(w, r)
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusUnauthorized
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `{"error_message":"access unauthorized"}`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := contentTypeHeader
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestForbiddenResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Forbidden(w, r)
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusForbidden
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `{"error_message":"access forbidden"}`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := contentTypeHeader
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestNotFoundResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
NotFound(w, r)
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusNotFound
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `{"error_message":"resource not found"}`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := contentTypeHeader
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestBuildInvalidJSONResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
OK(w, r, make(chan int))
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusInternalServerError
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `{"error_message":"json: unsupported type: chan int"}`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := contentTypeHeader
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
v2-2.2.13/internal/http/response/response.go 0000664 0000000 0000000 00000001612 15062123773 0020752 0 ustar 00root root 0000000 0000000 // 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;`
v2-2.2.13/internal/http/response/xml/ 0000775 0000000 0000000 00000000000 15062123773 0017365 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/http/response/xml/xml.go 0000664 0000000 0000000 00000001555 15062123773 0020522 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package xml // import "miniflux.app/v2/internal/http/response/xml"
import (
"net/http"
"miniflux.app/v2/internal/http/response"
)
// OK writes a standard XML response with a status 200 OK.
func OK[T []byte | string](w http.ResponseWriter, r *http.Request, body T) {
builder := response.New(w, r)
builder.WithHeader("Content-Type", "text/xml; charset=utf-8")
builder.WithBody(body)
builder.Write()
}
// Attachment forces the XML document to be downloaded by the web browser.
func Attachment[T []byte | string](w http.ResponseWriter, r *http.Request, filename string, body T) {
builder := response.New(w, r)
builder.WithHeader("Content-Type", "text/xml; charset=utf-8")
builder.WithAttachment(filename)
builder.WithBody(body)
builder.Write()
}
v2-2.2.13/internal/http/response/xml/xml_test.go 0000664 0000000 0000000 00000004167 15062123773 0021563 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package xml // import "miniflux.app/v2/internal/http/response/xml"
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestOKResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
OK(w, r, "Some XML")
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusOK
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `Some XML`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
expectedContentType := "text/xml; charset=utf-8"
actualContentType := resp.Header.Get("Content-Type")
if actualContentType != expectedContentType {
t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
}
}
func TestAttachmentResponse(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Attachment(w, r, "file.xml", "Some XML")
})
handler.ServeHTTP(w, r)
resp := w.Result()
expectedStatusCode := http.StatusOK
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedBody := `Some XML`
actualBody := w.Body.String()
if actualBody != expectedBody {
t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
}
headers := map[string]string{
"Content-Type": "text/xml; charset=utf-8",
"Content-Disposition": "attachment; filename=file.xml",
}
for header, expected := range headers {
actual := resp.Header.Get(header)
if actual != expected {
t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
}
}
}
v2-2.2.13/internal/http/route/ 0000775 0000000 0000000 00000000000 15062123773 0016065 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/http/route/route.go 0000664 0000000 0000000 00000001353 15062123773 0017554 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package route // import "miniflux.app/v2/internal/http/route"
import (
"strconv"
"github.com/gorilla/mux"
)
// Path returns the defined route based on given arguments.
func Path(router *mux.Router, name string, args ...any) string {
route := router.Get(name)
if route == nil {
panic("route not found: " + name)
}
var pairs []string
for _, arg := range args {
switch param := arg.(type) {
case string:
pairs = append(pairs, param)
case int64:
pairs = append(pairs, strconv.FormatInt(param, 10))
}
}
result, err := route.URLPath(pairs...)
if err != nil {
panic(err)
}
return result.String()
}
v2-2.2.13/internal/http/server/ 0000775 0000000 0000000 00000000000 15062123773 0016235 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/http/server/httpd.go 0000664 0000000 0000000 00000026216 15062123773 0017716 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package server // import "miniflux.app/v2/internal/http/server"
import (
"crypto/tls"
"fmt"
"log/slog"
"net"
"net/http"
"os"
"strconv"
"strings"
"miniflux.app/v2/internal/api"
"miniflux.app/v2/internal/config"
"miniflux.app/v2/internal/fever"
"miniflux.app/v2/internal/googlereader"
"miniflux.app/v2/internal/http/request"
"miniflux.app/v2/internal/storage"
"miniflux.app/v2/internal/ui"
"miniflux.app/v2/internal/version"
"miniflux.app/v2/internal/worker"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
)
func StartWebServer(store *storage.Storage, pool *worker.Pool) []*http.Server {
listenAddresses := config.Opts.ListenAddr()
var httpServers []*http.Server
certFile := config.Opts.CertFile()
keyFile := config.Opts.CertKeyFile()
certDomain := config.Opts.CertDomain()
var sharedAutocertTLSConfig *tls.Config
if certDomain != "" {
slog.Debug("Configuring autocert manager and shared TLS config", slog.String("domain", certDomain))
certManager := autocert.Manager{
Cache: storage.NewCertificateCache(store),
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(certDomain),
}
sharedAutocertTLSConfig = &tls.Config{}
sharedAutocertTLSConfig.GetCertificate = certManager.GetCertificate
sharedAutocertTLSConfig.NextProtos = []string{"h2", "http/1.1", acme.ALPNProto}
challengeServer := &http.Server{
Handler: certManager.HTTPHandler(nil),
Addr: ":http",
}
slog.Info("Starting ACME HTTP challenge server for autocert", slog.String("address", challengeServer.Addr))
go func() {
if err := challengeServer.ListenAndServe(); err != http.ErrServerClosed {
slog.Error("ACME HTTP challenge server failed", slog.Any("error", err))
}
}()
config.Opts.SetHTTPSValue(true)
httpServers = append(httpServers, challengeServer)
}
for i, listenAddr := range listenAddresses {
server := &http.Server{
ReadTimeout: config.Opts.HTTPServerTimeout(),
WriteTimeout: config.Opts.HTTPServerTimeout(),
IdleTimeout: config.Opts.HTTPServerTimeout(),
Handler: setupHandler(store, pool),
}
if !strings.HasPrefix(listenAddr, "/") && os.Getenv("LISTEN_PID") != strconv.Itoa(os.Getpid()) {
server.Addr = listenAddr
}
shouldAddServer := true
switch {
case os.Getenv("LISTEN_PID") == strconv.Itoa(os.Getpid()):
if i == 0 {
slog.Info("Starting server using systemd socket for the first listen address", slog.String("address_info", listenAddr))
startSystemdSocketServer(server)
} else {
slog.Warn("Systemd socket activation: Only the first listen address is used by systemd. Other addresses ignored.", slog.String("skipped_address", listenAddr))
shouldAddServer = false
}
case strings.HasPrefix(listenAddr, "/"): // Unix socket
startUnixSocketServer(server, listenAddr)
case certDomain != "" && (listenAddr == ":https" || (i == 0 && strings.Contains(listenAddr, ":"))):
server.Addr = listenAddr
startAutoCertTLSServer(server, sharedAutocertTLSConfig)
case certFile != "" && keyFile != "":
server.Addr = listenAddr
startTLSServer(server, certFile, keyFile)
config.Opts.SetHTTPSValue(true)
default:
server.Addr = listenAddr
startHTTPServer(server)
}
if shouldAddServer {
httpServers = append(httpServers, server)
}
}
return httpServers
}
func startSystemdSocketServer(server *http.Server) {
go func() {
f := os.NewFile(3, "systemd socket")
listener, err := net.FileListener(f)
if err != nil {
printErrorAndExit(`Unable to create listener from systemd socket: %v`, err)
}
slog.Info(`Starting server using systemd socket`)
if err := server.Serve(listener); err != http.ErrServerClosed {
printErrorAndExit(`Systemd socket server failed to start: %v`, err)
}
}()
}
func startUnixSocketServer(server *http.Server, socketFile string) {
if err := os.Remove(socketFile); err != nil && !os.IsNotExist(err) {
printErrorAndExit("Unable to remove existing Unix socket %s: %v", socketFile, err)
}
listener, err := net.Listen("unix", socketFile)
if err != nil {
printErrorAndExit(`Server failed to listen on Unix socket %s: %v`, socketFile, err)
}
if err := os.Chmod(socketFile, 0666); err != nil {
printErrorAndExit(`Unable to change socket permission for %s: %v`, socketFile, err)
}
go func() {
certFile := config.Opts.CertFile()
keyFile := config.Opts.CertKeyFile()
if certFile != "" && keyFile != "" {
slog.Info("Starting TLS server using a Unix socket",
slog.String("socket", socketFile),
slog.String("cert_file", certFile),
slog.String("key_file", keyFile),
)
// Ensure HTTPS is marked as true if any listener uses TLS
config.Opts.SetHTTPSValue(true)
if err := server.ServeTLS(listener, certFile, keyFile); err != http.ErrServerClosed {
printErrorAndExit("TLS Unix socket server failed to start on %s: %v", socketFile, err)
}
} else {
slog.Info("Starting server using a Unix socket", slog.String("socket", socketFile))
if err := server.Serve(listener); err != http.ErrServerClosed {
printErrorAndExit("Unix socket server failed to start on %s: %v", socketFile, err)
}
}
}()
}
func startAutoCertTLSServer(server *http.Server, autoTLSConfig *tls.Config) {
if server.TLSConfig == nil {
server.TLSConfig = &tls.Config{}
}
server.TLSConfig.GetCertificate = autoTLSConfig.GetCertificate
server.TLSConfig.NextProtos = autoTLSConfig.NextProtos
go func() {
slog.Info("Starting TLS server using automatic certificate management",
slog.String("listen_address", server.Addr),
)
if err := server.ListenAndServeTLS("", ""); err != http.ErrServerClosed {
printErrorAndExit("Autocert server failed to start on %s: %v", server.Addr, err)
}
}()
}
func startTLSServer(server *http.Server, certFile, keyFile string) {
go func() {
slog.Info("Starting TLS server using a certificate",
slog.String("listen_address", server.Addr),
slog.String("cert_file", certFile),
slog.String("key_file", keyFile),
)
if err := server.ListenAndServeTLS(certFile, keyFile); err != http.ErrServerClosed {
printErrorAndExit("TLS server failed to start on %s: %v", server.Addr, err)
}
}()
}
func startHTTPServer(server *http.Server) {
go func() {
slog.Info("Starting HTTP server",
slog.String("listen_address", server.Addr),
)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
printErrorAndExit("HTTP server failed to start on %s: %v", server.Addr, err)
}
}()
}
func setupHandler(store *storage.Storage, pool *worker.Pool) *mux.Router {
livenessProbe := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
readinessProbe := func(w http.ResponseWriter, r *http.Request) {
if err := store.Ping(); err != nil {
http.Error(w, fmt.Sprintf("Database Connection Error: %q", err), http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
router := mux.NewRouter()
// These routes do not take the base path into consideration and are always available at the root of the server.
router.HandleFunc("/liveness", livenessProbe).Name("liveness")
router.HandleFunc("/healthz", livenessProbe).Name("healthz")
router.HandleFunc("/readiness", readinessProbe).Name("readiness")
router.HandleFunc("/readyz", readinessProbe).Name("readyz")
var subrouter *mux.Router
if config.Opts.BasePath() != "" {
subrouter = router.PathPrefix(config.Opts.BasePath()).Subrouter()
} else {
subrouter = router.NewRoute().Subrouter()
}
if config.Opts.HasMaintenanceMode() {
subrouter.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(config.Opts.MaintenanceMessage()))
})
})
}
subrouter.Use(middleware)
fever.Serve(subrouter, store)
googlereader.Serve(subrouter, store)
api.Serve(subrouter, store, pool)
ui.Serve(subrouter, store, pool)
subrouter.HandleFunc("/healthcheck", readinessProbe).Name("healthcheck")
subrouter.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(version.Version))
}).Name("version")
if config.Opts.HasMetricsCollector() {
subrouter.Handle("/metrics", promhttp.Handler()).Name("metrics")
subrouter.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
route := mux.CurrentRoute(r)
// Returns a 404 if the client is not authorized to access the metrics endpoint.
if route.GetName() == "metrics" && !isAllowedToAccessMetricsEndpoint(r) {
slog.Warn("Authentication failed while accessing the metrics endpoint",
slog.String("client_ip", request.ClientIP(r)),
slog.String("client_user_agent", r.UserAgent()),
slog.String("client_remote_addr", r.RemoteAddr),
)
http.NotFound(w, r)
return
}
next.ServeHTTP(w, r)
})
})
}
return router
}
func isAllowedToAccessMetricsEndpoint(r *http.Request) bool {
clientIP := request.ClientIP(r)
if config.Opts.MetricsUsername() != "" && config.Opts.MetricsPassword() != "" {
username, password, authOK := r.BasicAuth()
if !authOK {
slog.Warn("Metrics endpoint accessed without authentication header",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("client_user_agent", r.UserAgent()),
slog.String("client_remote_addr", r.RemoteAddr),
)
return false
}
if username == "" || password == "" {
slog.Warn("Metrics endpoint accessed with empty username or password",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("client_user_agent", r.UserAgent()),
slog.String("client_remote_addr", r.RemoteAddr),
)
return false
}
if username != config.Opts.MetricsUsername() || password != config.Opts.MetricsPassword() {
slog.Warn("Metrics endpoint accessed with invalid username or password",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("client_user_agent", r.UserAgent()),
slog.String("client_remote_addr", r.RemoteAddr),
)
return false
}
}
remoteIP := request.FindRemoteIP(r)
if remoteIP == "@" {
// This indicates a request sent via a Unix socket, always consider these trusted.
return true
}
for _, cidr := range config.Opts.MetricsAllowedNetworks() {
_, network, err := net.ParseCIDR(cidr)
if err != nil {
slog.Error("Metrics endpoint accessed with invalid CIDR",
slog.Bool("authentication_failed", true),
slog.String("client_ip", clientIP),
slog.String("client_user_agent", r.UserAgent()),
slog.String("client_remote_addr", r.RemoteAddr),
slog.String("cidr", cidr),
)
return false
}
// We use r.RemoteAddr in this case because HTTP headers like X-Forwarded-For can be easily spoofed.
// The recommendation is to use HTTP Basic authentication.
if network.Contains(net.ParseIP(remoteIP)) {
return true
}
}
return false
}
func printErrorAndExit(format string, a ...any) {
message := fmt.Sprintf(format, a...)
slog.Error(message)
fmt.Fprintf(os.Stderr, "%v\n", message)
os.Exit(1)
}
v2-2.2.13/internal/http/server/middleware.go 0000664 0000000 0000000 00000002203 15062123773 0020676 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package server // import "miniflux.app/v2/internal/http/server"
import (
"context"
"log/slog"
"net/http"
"time"
"miniflux.app/v2/internal/config"
"miniflux.app/v2/internal/http/request"
)
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clientIP := request.FindClientIP(r)
ctx := r.Context()
ctx = context.WithValue(ctx, request.ClientIPContextKey, clientIP)
if r.Header.Get("X-Forwarded-Proto") == "https" {
config.Opts.SetHTTPSValue(true)
}
t1 := time.Now()
defer func() {
slog.Debug("Incoming request",
slog.String("client_ip", clientIP),
slog.Group("request",
slog.String("method", r.Method),
slog.String("uri", r.RequestURI),
slog.String("protocol", r.Proto),
slog.Duration("execution_time", time.Since(t1)),
),
)
}()
if config.Opts.HTTPS() && config.Opts.HasHSTS() {
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
v2-2.2.13/internal/integration/ 0000775 0000000 0000000 00000000000 15062123773 0016273 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/apprise/ 0000775 0000000 0000000 00000000000 15062123773 0017736 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/apprise/apprise.go 0000664 0000000 0000000 00000004137 15062123773 0021735 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package apprise
import (
"bytes"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"time"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
servicesURL string
baseURL string
}
func NewClient(serviceURL, baseURL string) *Client {
return &Client{serviceURL, baseURL}
}
func (c *Client) SendNotification(feed *model.Feed, entries model.Entries) error {
if c.baseURL == "" || c.servicesURL == "" {
return fmt.Errorf("apprise: missing base URL or services URL")
}
for _, entry := range entries {
message := "[" + entry.Title + "]" + "(" + entry.URL + ")" + "\n\n"
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/notify")
if err != nil {
return fmt.Errorf(`apprise: invalid API endpoint: %v`, err)
}
requestBody, err := json.Marshal(map[string]any{
"urls": c.servicesURL,
"body": message,
"title": feed.Title,
})
if err != nil {
return fmt.Errorf("apprise: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("apprise: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
slog.Debug("Sending Apprise notification",
slog.String("apprise_url", c.baseURL),
slog.String("services_url", c.servicesURL),
slog.String("title", feed.Title),
slog.String("body", message),
slog.String("entry_url", entry.URL),
)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("apprise: unable to send request: %v", err)
}
response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("apprise: unable to send a notification: url=%s status=%d", apiEndpoint, response.StatusCode)
}
}
return nil
}
v2-2.2.13/internal/integration/betula/ 0000775 0000000 0000000 00000000000 15062123773 0017547 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/betula/betula.go 0000664 0000000 0000000 00000002701 15062123773 0021352 0 ustar 00root root 0000000 0000000 package betula
import (
"fmt"
"net/http"
"net/url"
"strings"
"time"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
url string
token string
}
func NewClient(url, token string) *Client {
return &Client{url: url, token: token}
}
func (c *Client) CreateBookmark(entryURL, entryTitle string, tags []string) error {
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.url, "/save-link")
if err != nil {
return fmt.Errorf("betula: unable to generate save-link endpoint: %v", err)
}
values := url.Values{}
values.Add("url", entryURL)
values.Add("title", entryTitle)
values.Add("tags", strings.Join(tags, ","))
request, err := http.NewRequest(http.MethodPost, apiEndpoint+"?"+values.Encode(), nil)
if err != nil {
return fmt.Errorf("betula: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.AddCookie(&http.Cookie{Name: "betula-token", Value: c.token})
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("betula: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("betula: unable to create bookmark: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
v2-2.2.13/internal/integration/cubox/ 0000775 0000000 0000000 00000000000 15062123773 0017413 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/cubox/cubox.go 0000664 0000000 0000000 00000003131 15062123773 0021060 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// Cubox API documentation: https://help.cubox.cc/save/api/
package cubox // import "miniflux.app/v2/internal/integration/cubox"
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
apiLink string
}
func NewClient(apiLink string) *Client {
return &Client{apiLink: apiLink}
}
func (c *Client) SaveLink(entryURL string) error {
if c.apiLink == "" {
return errors.New("cubox: missing API link")
}
requestBody, err := json.Marshal(&card{
Type: "url",
Content: entryURL,
})
if err != nil {
return fmt.Errorf("cubox: unable to encode request body: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), defaultClientTimeout)
defer cancel()
request, err := http.NewRequestWithContext(ctx, http.MethodPost, c.apiLink, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("cubox: unable to create request: %w", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
response, err := http.DefaultClient.Do(request)
if err != nil {
return fmt.Errorf("cubox: unable to send request: %w", err)
}
defer response.Body.Close()
if response.StatusCode != 200 {
return fmt.Errorf("cubox: unable to save link: status=%d", response.StatusCode)
}
return nil
}
type card struct {
Type string `json:"type"`
Content string `json:"content"`
}
v2-2.2.13/internal/integration/discord/ 0000775 0000000 0000000 00000000000 15062123773 0017722 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/discord/discord.go 0000664 0000000 0000000 00000005221 15062123773 0021700 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// Discord Webhooks documentation: https://discord.com/developers/docs/resources/webhook
package discord // import "miniflux.app/v2/internal/integration/discord"
import (
"bytes"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"time"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
const discordMsgColor = 5793266
type Client struct {
webhookURL string
}
func NewClient(webhookURL string) *Client {
return &Client{webhookURL: webhookURL}
}
func (c *Client) SendDiscordMsg(feed *model.Feed, entries model.Entries) error {
for _, entry := range entries {
requestBody, err := json.Marshal(&discordMessage{
Embeds: []discordEmbed{
{
Title: "RSS feed update from Miniflux",
Color: discordMsgColor,
Fields: []discordFields{
{
Name: "Updated feed",
Value: feed.Title,
},
{
Name: "Article link",
Value: "[" + entry.Title + "]" + "(" + entry.URL + ")",
},
{
Name: "Author",
Value: entry.Author,
Inline: true,
},
{
Name: "Source website",
Value: urllib.RootURL(feed.SiteURL),
Inline: true,
},
},
},
},
})
if err != nil {
return fmt.Errorf("discord: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, c.webhookURL, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("discord: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
slog.Debug("Sending Discord notification",
slog.String("webhookURL", c.webhookURL),
slog.String("title", feed.Title),
slog.String("entry_url", entry.URL),
)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("discord: unable to send request: %v", err)
}
response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("discord: unable to send a notification: url=%s status=%d", c.webhookURL, response.StatusCode)
}
}
return nil
}
type discordFields struct {
Name string `json:"name"`
Value string `json:"value"`
Inline bool `json:"inline,omitempty"`
}
type discordEmbed struct {
Title string `json:"title"`
Color int `json:"color"`
Fields []discordFields `json:"fields"`
}
type discordMessage struct {
Embeds []discordEmbed `json:"embeds"`
}
v2-2.2.13/internal/integration/espial/ 0000775 0000000 0000000 00000000000 15062123773 0017550 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/espial/espial.go 0000664 0000000 0000000 00000004122 15062123773 0021353 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package espial // import "miniflux.app/v2/internal/integration/espial"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
baseURL string
apiKey string
}
func NewClient(baseURL, apiKey string) *Client {
return &Client{baseURL: baseURL, apiKey: apiKey}
}
func (c *Client) CreateLink(entryURL, entryTitle, espialTags string) error {
if c.baseURL == "" || c.apiKey == "" {
return fmt.Errorf("espial: missing base URL or API key")
}
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/add")
if err != nil {
return fmt.Errorf("espial: invalid API endpoint: %v", err)
}
requestBody, err := json.Marshal(&espialDocument{
Title: entryTitle,
Url: entryURL,
ToRead: true,
Tags: espialTags,
})
if err != nil {
return fmt.Errorf("espial: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("espial: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "ApiKey "+c.apiKey)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("espial: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusCreated {
responseBody := new(bytes.Buffer)
responseBody.ReadFrom(response.Body)
return fmt.Errorf("espial: unable to create link: url=%s status=%d body=%s", apiEndpoint, response.StatusCode, responseBody.String())
}
return nil
}
type espialDocument struct {
Title string `json:"title,omitempty"`
Url string `json:"url,omitempty"`
ToRead bool `json:"toread,omitempty"`
Tags string `json:"tags,omitempty"`
}
v2-2.2.13/internal/integration/instapaper/ 0000775 0000000 0000000 00000000000 15062123773 0020441 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/instapaper/instapaper.go 0000664 0000000 0000000 00000003032 15062123773 0023134 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package instapaper // import "miniflux.app/v2/internal/integration/instapaper"
import (
"fmt"
"net/http"
"net/url"
"time"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
username string
password string
}
func NewClient(username, password string) *Client {
return &Client{username: username, password: password}
}
func (c *Client) AddURL(entryURL, entryTitle string) error {
if c.username == "" || c.password == "" {
return fmt.Errorf("instapaper: missing username or password")
}
values := url.Values{}
values.Add("url", entryURL)
values.Add("title", entryTitle)
apiEndpoint := "https://www.instapaper.com/api/add?" + values.Encode()
request, err := http.NewRequest(http.MethodGet, apiEndpoint, nil)
if err != nil {
return fmt.Errorf("instapaper: unable to create request: %v", err)
}
request.SetBasicAuth(c.username, c.password)
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("instapaper: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusCreated {
return fmt.Errorf("instapaper: unable to add URL: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
v2-2.2.13/internal/integration/integration.go 0000664 0000000 0000000 00000047641 15062123773 0021161 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package integration // import "miniflux.app/v2/internal/integration"
import (
"log/slog"
"miniflux.app/v2/internal/integration/apprise"
"miniflux.app/v2/internal/integration/betula"
"miniflux.app/v2/internal/integration/cubox"
"miniflux.app/v2/internal/integration/discord"
"miniflux.app/v2/internal/integration/espial"
"miniflux.app/v2/internal/integration/instapaper"
"miniflux.app/v2/internal/integration/karakeep"
"miniflux.app/v2/internal/integration/linkace"
"miniflux.app/v2/internal/integration/linkding"
"miniflux.app/v2/internal/integration/linktaco"
"miniflux.app/v2/internal/integration/linkwarden"
"miniflux.app/v2/internal/integration/matrixbot"
"miniflux.app/v2/internal/integration/notion"
"miniflux.app/v2/internal/integration/ntfy"
"miniflux.app/v2/internal/integration/nunuxkeeper"
"miniflux.app/v2/internal/integration/omnivore"
"miniflux.app/v2/internal/integration/pinboard"
"miniflux.app/v2/internal/integration/pushover"
"miniflux.app/v2/internal/integration/raindrop"
"miniflux.app/v2/internal/integration/readeck"
"miniflux.app/v2/internal/integration/readwise"
"miniflux.app/v2/internal/integration/shaarli"
"miniflux.app/v2/internal/integration/shiori"
"miniflux.app/v2/internal/integration/slack"
"miniflux.app/v2/internal/integration/telegrambot"
"miniflux.app/v2/internal/integration/wallabag"
"miniflux.app/v2/internal/integration/webhook"
"miniflux.app/v2/internal/model"
)
// SendEntry sends the entry to third-party providers when the user click on "Save".
func SendEntry(entry *model.Entry, userIntegrations *model.Integration) {
if userIntegrations.BetulaEnabled {
slog.Debug("Sending entry to Betula",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := betula.NewClient(userIntegrations.BetulaURL, userIntegrations.BetulaToken)
err := client.CreateBookmark(
entry.URL,
entry.Title,
entry.Tags,
)
if err != nil {
slog.Error("Unable to send entry to Betula",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.PinboardEnabled {
slog.Debug("Sending entry to Pinboard",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := pinboard.NewClient(userIntegrations.PinboardToken)
err := client.CreateBookmark(
entry.URL,
entry.Title,
userIntegrations.PinboardTags,
userIntegrations.PinboardMarkAsUnread,
)
if err != nil {
slog.Error("Unable to send entry to Pinboard",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.InstapaperEnabled {
slog.Debug("Sending entry to Instapaper",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := instapaper.NewClient(userIntegrations.InstapaperUsername, userIntegrations.InstapaperPassword)
if err := client.AddURL(entry.URL, entry.Title); err != nil {
slog.Error("Unable to send entry to Instapaper",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.WallabagEnabled {
slog.Debug("Sending entry to Wallabag",
slog.Int64("user_id", userIntegrations.UserID),
slog.String("user_tags", userIntegrations.WallabagTags),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := wallabag.NewClient(
userIntegrations.WallabagURL,
userIntegrations.WallabagClientID,
userIntegrations.WallabagClientSecret,
userIntegrations.WallabagUsername,
userIntegrations.WallabagPassword,
userIntegrations.WallabagTags,
userIntegrations.WallabagOnlyURL,
)
if err := client.CreateEntry(entry.URL, entry.Title, entry.Content); err != nil {
slog.Error("Unable to send entry to Wallabag",
slog.Int64("user_id", userIntegrations.UserID),
slog.String("user_tags", userIntegrations.WallabagTags),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.NotionEnabled {
slog.Debug("Sending entry to Notion",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := notion.NewClient(
userIntegrations.NotionToken,
userIntegrations.NotionPageID,
)
if err := client.UpdateDocument(entry.URL, entry.Title); err != nil {
slog.Error("Unable to send entry to Notion",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.NunuxKeeperEnabled {
slog.Debug("Sending entry to NunuxKeeper",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := nunuxkeeper.NewClient(
userIntegrations.NunuxKeeperURL,
userIntegrations.NunuxKeeperAPIKey,
)
if err := client.AddEntry(entry.URL, entry.Title, entry.Content); err != nil {
slog.Error("Unable to send entry to NunuxKeeper",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.EspialEnabled {
slog.Debug("Sending entry to Espial",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := espial.NewClient(
userIntegrations.EspialURL,
userIntegrations.EspialAPIKey,
)
if err := client.CreateLink(entry.URL, entry.Title, userIntegrations.EspialTags); err != nil {
slog.Error("Unable to send entry to Espial",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.LinkAceEnabled {
slog.Debug("Sending entry to LinkAce",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := linkace.NewClient(
userIntegrations.LinkAceURL,
userIntegrations.LinkAceAPIKey,
userIntegrations.LinkAceTags,
userIntegrations.LinkAcePrivate,
userIntegrations.LinkAceCheckDisabled,
)
if err := client.AddURL(entry.URL, entry.Title); err != nil {
slog.Error("Unable to send entry to LinkAce",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.LinkdingEnabled {
slog.Debug("Sending entry to Linkding",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := linkding.NewClient(
userIntegrations.LinkdingURL,
userIntegrations.LinkdingAPIKey,
userIntegrations.LinkdingTags,
userIntegrations.LinkdingMarkAsUnread,
)
if err := client.CreateBookmark(entry.URL, entry.Title); err != nil {
slog.Error("Unable to send entry to Linkding",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.LinktacoEnabled {
slog.Debug("Sending entry to LinkTaco",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := linktaco.NewClient(
userIntegrations.LinktacoAPIToken,
userIntegrations.LinktacoOrgSlug,
userIntegrations.LinktacoTags,
userIntegrations.LinktacoVisibility,
)
if err := client.CreateBookmark(entry.URL, entry.Title, entry.Content); err != nil {
slog.Error("Unable to send entry to LinkTaco",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.LinkwardenEnabled {
slog.Debug("Sending entry to linkwarden",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := linkwarden.NewClient(
userIntegrations.LinkwardenURL,
userIntegrations.LinkwardenAPIKey,
)
if err := client.CreateBookmark(entry.URL, entry.Title); err != nil {
slog.Error("Unable to send entry to Linkwarden",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.ReadeckEnabled {
slog.Debug("Sending entry to Readeck",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := readeck.NewClient(
userIntegrations.ReadeckURL,
userIntegrations.ReadeckAPIKey,
userIntegrations.ReadeckLabels,
userIntegrations.ReadeckOnlyURL,
)
if err := client.CreateBookmark(entry.URL, entry.Title, entry.Content); err != nil {
slog.Error("Unable to send entry to Readeck",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.ReadwiseEnabled {
slog.Debug("Sending entry to Readwise",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := readwise.NewClient(
userIntegrations.ReadwiseAPIKey,
)
if err := client.CreateDocument(entry.URL); err != nil {
slog.Error("Unable to send entry to Readwise",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.CuboxEnabled {
slog.Debug("Sending entry to Cubox",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := cubox.NewClient(userIntegrations.CuboxAPILink)
if err := client.SaveLink(entry.URL); err != nil {
slog.Error("Unable to send entry to Cubox",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.ShioriEnabled {
slog.Debug("Sending entry to Shiori",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := shiori.NewClient(
userIntegrations.ShioriURL,
userIntegrations.ShioriUsername,
userIntegrations.ShioriPassword,
)
if err := client.CreateBookmark(entry.URL, entry.Title); err != nil {
slog.Error("Unable to send entry to Shiori",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.ShaarliEnabled {
slog.Debug("Sending entry to Shaarli",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := shaarli.NewClient(
userIntegrations.ShaarliURL,
userIntegrations.ShaarliAPISecret,
)
if err := client.CreateLink(entry.URL, entry.Title); err != nil {
slog.Error("Unable to send entry to Shaarli",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.WebhookEnabled {
var webhookURL string
if entry.Feed != nil && entry.Feed.WebhookURL != "" {
webhookURL = entry.Feed.WebhookURL
} else {
webhookURL = userIntegrations.WebhookURL
}
slog.Debug("Sending entry to Webhook",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.String("webhook_url", webhookURL),
)
webhookClient := webhook.NewClient(webhookURL, userIntegrations.WebhookSecret)
if err := webhookClient.SendSaveEntryWebhookEvent(entry); err != nil {
slog.Error("Unable to send entry to Webhook",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.String("webhook_url", webhookURL),
slog.Any("error", err),
)
}
}
if userIntegrations.OmnivoreEnabled {
slog.Debug("Sending entry to Omnivore",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := omnivore.NewClient(userIntegrations.OmnivoreAPIKey, userIntegrations.OmnivoreURL)
if err := client.SaveUrl(entry.URL); err != nil {
slog.Error("Unable to send entry to Omnivore",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.KarakeepEnabled {
slog.Debug("Sending entry to Karakeep",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := karakeep.NewClient(userIntegrations.KarakeepAPIKey, userIntegrations.KarakeepURL)
if err := client.SaveURL(entry.URL); err != nil {
slog.Error("Unable to send entry to Karakeep",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
if userIntegrations.RaindropEnabled {
slog.Debug("Sending entry to Raindrop",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
client := raindrop.NewClient(userIntegrations.RaindropToken, userIntegrations.RaindropCollectionID, userIntegrations.RaindropTags)
if err := client.CreateRaindrop(entry.URL, entry.Title); err != nil {
slog.Error("Unable to send entry to Raindrop",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
}
// PushEntries pushes a list of entries to activated third-party providers during feed refreshes.
func PushEntries(feed *model.Feed, entries model.Entries, userIntegrations *model.Integration) {
if userIntegrations.MatrixBotEnabled {
slog.Debug("Sending new entries to Matrix",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int("nb_entries", len(entries)),
slog.Int64("feed_id", feed.ID),
)
err := matrixbot.PushEntries(
feed,
entries,
userIntegrations.MatrixBotURL,
userIntegrations.MatrixBotUser,
userIntegrations.MatrixBotPassword,
userIntegrations.MatrixBotChatID,
)
if err != nil {
slog.Error("Unable to send new entries to Matrix",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int("nb_entries", len(entries)),
slog.Int64("feed_id", feed.ID),
slog.Any("error", err),
)
}
}
if userIntegrations.WebhookEnabled {
var webhookURL string
if feed.WebhookURL != "" {
webhookURL = feed.WebhookURL
} else {
webhookURL = userIntegrations.WebhookURL
}
slog.Debug("Sending new entries to Webhook",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int("nb_entries", len(entries)),
slog.Int64("feed_id", feed.ID),
slog.String("webhook_url", webhookURL),
)
webhookClient := webhook.NewClient(webhookURL, userIntegrations.WebhookSecret)
if err := webhookClient.SendNewEntriesWebhookEvent(feed, entries); err != nil {
slog.Debug("Unable to send new entries to Webhook",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int("nb_entries", len(entries)),
slog.Int64("feed_id", feed.ID),
slog.String("webhook_url", webhookURL),
slog.Any("error", err),
)
}
}
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,
ntfyTopic,
userIntegrations.NtfyAPIToken,
userIntegrations.NtfyUsername,
userIntegrations.NtfyPassword,
userIntegrations.NtfyIconURL,
userIntegrations.NtfyInternalLinks,
feed.NtfyPriority,
)
if err := client.SendMessages(feed, entries); err != nil {
slog.Warn("Unable to send new entries to Ntfy", slog.Any("error", err))
}
}
if userIntegrations.AppriseEnabled {
slog.Debug("Sending new entries to Apprise",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int("nb_entries", len(entries)),
slog.Int64("feed_id", feed.ID),
)
appriseServiceURLs := userIntegrations.AppriseServicesURL
if feed.AppriseServiceURLs != "" {
appriseServiceURLs = feed.AppriseServiceURLs
}
client := apprise.NewClient(
appriseServiceURLs,
userIntegrations.AppriseURL,
)
if err := client.SendNotification(feed, entries); err != nil {
slog.Warn("Unable to send new entries to Apprise", slog.Any("error", err))
}
}
if userIntegrations.DiscordEnabled {
slog.Debug("Sending new entries to Discord",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int("nb_entries", len(entries)),
slog.Int64("feed_id", feed.ID),
)
client := discord.NewClient(
userIntegrations.DiscordWebhookLink,
)
if err := client.SendDiscordMsg(feed, entries); err != nil {
slog.Warn("Unable to send new entries to Discord", slog.Any("error", err))
}
}
if userIntegrations.SlackEnabled {
slog.Debug("Sending new entries to Slack",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int("nb_entries", len(entries)),
slog.Int64("feed_id", feed.ID),
)
client := slack.NewClient(
userIntegrations.SlackWebhookLink,
)
if err := client.SendSlackMsg(feed, entries); err != nil {
slog.Warn("Unable to send new entries to Slack", slog.Any("error", err))
}
}
if userIntegrations.PushoverEnabled && feed.PushoverEnabled {
slog.Debug("Sending new entries to Pushover",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int("nb_entries", len(entries)),
slog.Int64("feed_id", feed.ID),
)
client := pushover.New(
userIntegrations.PushoverUser,
userIntegrations.PushoverToken,
feed.PushoverPriority,
userIntegrations.PushoverDevice,
userIntegrations.PushoverPrefix,
)
if err := client.SendMessages(feed, entries); err != nil {
slog.Warn("Unable to send new entries to Pushover", slog.Any("error", err))
}
}
// Integrations that only support sending individual entries
if userIntegrations.TelegramBotEnabled {
for _, entry := range entries {
if userIntegrations.TelegramBotEnabled {
slog.Debug("Sending a new entry to Telegram",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
)
if err := telegrambot.PushEntry(
feed,
entry,
userIntegrations.TelegramBotToken,
userIntegrations.TelegramBotChatID,
userIntegrations.TelegramBotTopicID,
userIntegrations.TelegramBotDisableWebPagePreview,
userIntegrations.TelegramBotDisableNotification,
userIntegrations.TelegramBotDisableButtons,
); err != nil {
slog.Error("Unable to send entry to Telegram",
slog.Int64("user_id", userIntegrations.UserID),
slog.Int64("entry_id", entry.ID),
slog.String("entry_url", entry.URL),
slog.Any("error", err),
)
}
}
}
}
}
v2-2.2.13/internal/integration/karakeep/ 0000775 0000000 0000000 00000000000 15062123773 0020056 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/karakeep/karakeep.go 0000664 0000000 0000000 00000004276 15062123773 0022201 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package karakeep // import "miniflux.app/v2/internal/integration/karakeep"
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type errorResponse struct {
Code string `json:"code"`
Error string `json:"error"`
}
type saveURLPayload struct {
Type string `json:"type"`
URL string `json:"url"`
}
type Client struct {
wrapped *http.Client
apiEndpoint string
apiToken string
}
func NewClient(apiToken string, apiEndpoint string) *Client {
return &Client{wrapped: &http.Client{Timeout: defaultClientTimeout}, apiEndpoint: apiEndpoint, apiToken: apiToken}
}
func (c *Client) SaveURL(entryURL string) error {
requestBody, err := json.Marshal(&saveURLPayload{
Type: "link",
URL: entryURL,
})
if err != nil {
return fmt.Errorf("karakeep: unable to encode request body: %v", err)
}
req, err := http.NewRequest(http.MethodPost, c.apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("karakeep: unable to create request: %v", err)
}
req.Header.Set("Authorization", "Bearer "+c.apiToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Miniflux/"+version.Version)
resp, err := c.wrapped.Do(req)
if err != nil {
return fmt.Errorf("karakeep: unable to send request: %v", err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("karakeep: failed to parse response: %s", err)
}
if resp.Header.Get("Content-Type") != "application/json" {
return fmt.Errorf("karakeep: unexpected content type response: %s", resp.Header.Get("Content-Type"))
}
if resp.StatusCode != http.StatusCreated {
var errResponse errorResponse
if err := json.Unmarshal(responseBody, &errResponse); err != nil {
return fmt.Errorf("karakeep: unable to parse error response: status=%d body=%s", resp.StatusCode, string(responseBody))
}
return fmt.Errorf("karakeep: failed to save URL: status=%d errorcode=%s %s", resp.StatusCode, errResponse.Code, errResponse.Error)
}
return nil
}
v2-2.2.13/internal/integration/linkace/ 0000775 0000000 0000000 00000000000 15062123773 0017701 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/linkace/linkace.go 0000664 0000000 0000000 00000004421 15062123773 0021637 0 ustar 00root root 0000000 0000000 package linkace
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
baseURL string
apiKey string
tags string
private bool
checkDisabled bool
}
func NewClient(baseURL, apiKey, tags string, private bool, checkDisabled bool) *Client {
return &Client{baseURL: baseURL, apiKey: apiKey, tags: tags, private: private, checkDisabled: checkDisabled}
}
func (c *Client) AddURL(entryURL, entryTitle string) error {
if c.baseURL == "" || c.apiKey == "" {
return fmt.Errorf("linkace: missing base URL or API key")
}
tagsSplitFn := func(c rune) bool {
return c == ',' || c == ' '
}
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/v2/links")
if err != nil {
return fmt.Errorf("linkace: invalid API endpoint: %v", err)
}
requestBody, err := json.Marshal(&createItemRequest{
Url: entryURL,
Title: entryTitle,
Tags: strings.FieldsFunc(c.tags, tagsSplitFn),
Private: c.private,
CheckDisabled: c.checkDisabled,
})
if err != nil {
return fmt.Errorf("linkace: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("linkace: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Bearer "+c.apiKey)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("linkace: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("linkace: unable to create item: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
type createItemRequest struct {
Title string `json:"title,omitempty"`
Url string `json:"url"`
Tags []string `json:"tags,omitempty"`
Private bool `json:"is_private,omitempty"`
CheckDisabled bool `json:"check_disabled,omitempty"`
}
v2-2.2.13/internal/integration/linkding/ 0000775 0000000 0000000 00000000000 15062123773 0020072 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/linkding/linkding.go 0000664 0000000 0000000 00000004312 15062123773 0022220 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package linkding // import "miniflux.app/v2/internal/integration/linkding"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
baseURL string
apiKey string
tags string
unread bool
}
func NewClient(baseURL, apiKey, tags string, unread bool) *Client {
return &Client{baseURL: baseURL, apiKey: apiKey, tags: tags, unread: unread}
}
func (c *Client) CreateBookmark(entryURL, entryTitle string) error {
if c.baseURL == "" || c.apiKey == "" {
return fmt.Errorf("linkding: missing base URL or API key")
}
tagsSplitFn := func(c rune) bool {
return c == ',' || c == ' '
}
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/bookmarks/")
if err != nil {
return fmt.Errorf(`linkding: invalid API endpoint: %v`, err)
}
requestBody, err := json.Marshal(&linkdingBookmark{
Url: entryURL,
Title: entryTitle,
TagNames: strings.FieldsFunc(c.tags, tagsSplitFn),
Unread: c.unread,
})
if err != nil {
return fmt.Errorf("linkding: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("linkding: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Token "+c.apiKey)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("linkding: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("linkding: unable to create bookmark: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
type linkdingBookmark struct {
Url string `json:"url,omitempty"`
Title string `json:"title,omitempty"`
TagNames []string `json:"tag_names,omitempty"`
Unread bool `json:"unread,omitempty"`
}
v2-2.2.13/internal/integration/linktaco/ 0000775 0000000 0000000 00000000000 15062123773 0020077 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/linktaco/linktaco.go 0000664 0000000 0000000 00000007007 15062123773 0022236 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package linktaco // import "miniflux.app/v2/internal/integration/linktaco"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"miniflux.app/v2/internal/version"
)
const (
defaultClientTimeout = 10 * time.Second
defaultGraphQLURL = "https://api.linktaco.com/query"
maxTags = 10
maxDescriptionLength = 500
)
type Client struct {
graphqlURL string
apiToken string
orgSlug string
tags string
visibility string
}
func NewClient(apiToken, orgSlug, tags, visibility string) *Client {
if visibility == "" {
visibility = "PUBLIC"
}
return &Client{
graphqlURL: defaultGraphQLURL,
apiToken: apiToken,
orgSlug: orgSlug,
tags: tags,
visibility: visibility,
}
}
func (c *Client) CreateBookmark(entryURL, entryTitle, entryContent string) error {
if c.apiToken == "" || c.orgSlug == "" {
return fmt.Errorf("linktaco: missing API token or organization slug")
}
description := entryContent
if len(description) > maxDescriptionLength {
description = description[:maxDescriptionLength]
}
// tags (limit to 10)
tags := strings.FieldsFunc(c.tags, func(c rune) bool {
return c == ',' || c == ' '
})
if len(tags) > maxTags {
tags = tags[:maxTags]
}
// tagsStr is used in GraphQL query to pass comma separated tags
tagsStr := strings.Join(tags, ",")
mutation := `
mutation AddLink($input: LinkInput!) {
addLink(input: $input) {
id
url
title
}
}
`
variables := map[string]any{
"input": map[string]any{
"url": entryURL,
"title": entryTitle,
"description": description,
"orgSlug": c.orgSlug,
"visibility": c.visibility,
"unread": true,
"starred": false,
"archive": false,
"tags": tagsStr,
},
}
requestBody, err := json.Marshal(map[string]any{
"query": mutation,
"variables": variables,
})
if err != nil {
return fmt.Errorf("linktaco: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, c.graphqlURL, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("linktaco: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Bearer "+c.apiToken)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("linktaco: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("linktaco: unable to create bookmark: status=%d", response.StatusCode)
}
var graphqlResponse struct {
Data json.RawMessage `json:"data"`
Errors []json.RawMessage `json:"errors"`
}
if err := json.NewDecoder(response.Body).Decode(&graphqlResponse); err != nil {
return fmt.Errorf("linktaco: unable to decode response: %v", err)
}
if len(graphqlResponse.Errors) > 0 {
// Try to extract error message
var errorMsg string
for _, errJSON := range graphqlResponse.Errors {
var errObj struct {
Message string `json:"message"`
}
if json.Unmarshal(errJSON, &errObj) == nil && errObj.Message != "" {
errorMsg = errObj.Message
break
}
}
if errorMsg == "" {
// Fallback. Should never be reached.
errorMsg = "GraphQL error occurred (fallback message)"
}
return fmt.Errorf("linktaco: %s", errorMsg)
}
return nil
}
v2-2.2.13/internal/integration/linktaco/linktaco_test.go 0000664 0000000 0000000 00000030306 15062123773 0023273 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package linktaco
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestCreateBookmark(t *testing.T) {
tests := []struct {
name string
apiToken string
orgSlug string
tags string
visibility string
entryURL string
entryTitle string
entryContent string
serverResponse func(w http.ResponseWriter, r *http.Request)
wantErr bool
errContains string
}{
{
name: "successful bookmark creation",
apiToken: "test-token",
orgSlug: "test-org",
tags: "tag1, tag2",
visibility: "PUBLIC",
entryURL: "https://example.com",
entryTitle: "Test Article",
entryContent: "Test content",
serverResponse: func(w http.ResponseWriter, r *http.Request) {
// Verify authorization header
auth := r.Header.Get("Authorization")
if auth != "Bearer test-token" {
t.Errorf("Expected Authorization header 'Bearer test-token', got %s", auth)
}
// Verify content type
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
t.Errorf("Expected Content-Type 'application/json', got %s", contentType)
}
// Parse and verify request
body, _ := io.ReadAll(r.Body)
var req map[string]interface{}
if err := json.Unmarshal(body, &req); err != nil {
t.Errorf("Failed to parse request body: %v", err)
}
// Verify mutation exists
if _, ok := req["query"]; !ok {
t.Error("Missing 'query' field in request")
}
// Return success response
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"data": map[string]interface{}{
"addLink": map[string]interface{}{
"id": "123",
"url": "https://example.com",
"title": "Test Article",
},
},
})
},
wantErr: false,
},
{
name: "missing API token",
apiToken: "",
orgSlug: "test-org",
entryURL: "https://example.com",
entryTitle: "Test",
entryContent: "Content",
serverResponse: func(w http.ResponseWriter, r *http.Request) {
// Should not be called
t.Error("Server should not be called when API token is missing")
},
wantErr: true,
errContains: "missing API token or organization slug",
},
{
name: "missing organization slug",
apiToken: "test-token",
orgSlug: "",
entryURL: "https://example.com",
entryTitle: "Test",
entryContent: "Content",
serverResponse: func(w http.ResponseWriter, r *http.Request) {
// Should not be called
t.Error("Server should not be called when org slug is missing")
},
wantErr: true,
errContains: "missing API token or organization slug",
},
{
name: "GraphQL error response",
apiToken: "test-token",
orgSlug: "test-org",
entryURL: "https://example.com",
entryTitle: "Test",
entryContent: "Content",
serverResponse: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"errors": []interface{}{
map[string]interface{}{
"message": "Invalid input",
},
},
})
},
wantErr: true,
errContains: "Invalid input",
},
{
name: "HTTP error status",
apiToken: "test-token",
orgSlug: "test-org",
entryURL: "https://example.com",
entryTitle: "Test",
entryContent: "Content",
serverResponse: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
},
wantErr: true,
errContains: "status=401",
},
{
name: "private visibility permission error",
apiToken: "test-token",
orgSlug: "test-org",
visibility: "PRIVATE",
entryURL: "https://example.com",
entryTitle: "Test",
entryContent: "Content",
serverResponse: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"errors": []interface{}{
map[string]interface{}{
"message": "PRIVATE visibility requires a paid LinkTaco account",
},
},
})
},
wantErr: true,
errContains: "PRIVATE visibility requires a paid LinkTaco account",
},
{
name: "content truncation",
apiToken: "test-token",
orgSlug: "test-org",
entryURL: "https://example.com",
entryTitle: "Test",
entryContent: strings.Repeat("a", 600), // Content longer than 500 chars
serverResponse: func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
var req map[string]interface{}
json.Unmarshal(body, &req)
// Check that description was truncated
variables := req["variables"].(map[string]interface{})
input := variables["input"].(map[string]interface{})
description := input["description"].(string)
if len(description) != maxDescriptionLength {
t.Errorf("Expected description length %d, got %d", maxDescriptionLength, len(description))
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"data": map[string]interface{}{
"addLink": map[string]interface{}{"id": "123"},
},
})
},
wantErr: false,
},
{
name: "tag limiting",
apiToken: "test-token",
orgSlug: "test-org",
tags: "tag1,tag2,tag3,tag4,tag5,tag6,tag7,tag8,tag9,tag10,tag11,tag12",
entryURL: "https://example.com",
entryTitle: "Test",
entryContent: "Content",
serverResponse: func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
var req map[string]interface{}
json.Unmarshal(body, &req)
// Check that only 10 tags were sent
variables := req["variables"].(map[string]interface{})
input := variables["input"].(map[string]interface{})
tags := input["tags"].(string)
tagCount := len(strings.Split(tags, ","))
if tagCount != maxTags {
t.Errorf("Expected %d tags, got %d", maxTags, tagCount)
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"data": map[string]interface{}{
"addLink": map[string]interface{}{"id": "123"},
},
})
},
wantErr: false,
},
{
name: "invalid JSON response",
apiToken: "test-token",
orgSlug: "test-org",
entryURL: "https://example.com",
entryTitle: "Test",
entryContent: "Content",
serverResponse: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("invalid json"))
},
wantErr: true,
errContains: "unable to decode response",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create test server if we have a server response function
var serverURL string
if tt.serverResponse != nil {
server := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
defer server.Close()
serverURL = server.URL
}
// Create client with test server URL
client := &Client{
graphqlURL: serverURL,
apiToken: tt.apiToken,
orgSlug: tt.orgSlug,
tags: tt.tags,
visibility: tt.visibility,
}
// Call CreateBookmark
err := client.CreateBookmark(tt.entryURL, tt.entryTitle, tt.entryContent)
// Check error expectations
if tt.wantErr {
if err == nil {
t.Errorf("Expected error but got none")
} else if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("Expected error containing '%s', got '%s'", tt.errContains, err.Error())
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
})
}
}
func TestNewClient(t *testing.T) {
tests := []struct {
name string
apiToken string
orgSlug string
tags string
visibility string
expectedVisibility string
}{
{
name: "with all parameters",
apiToken: "token",
orgSlug: "org",
tags: "tag1,tag2",
visibility: "PRIVATE",
expectedVisibility: "PRIVATE",
},
{
name: "empty visibility defaults to PUBLIC",
apiToken: "token",
orgSlug: "org",
tags: "tag1",
visibility: "",
expectedVisibility: "PUBLIC",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := NewClient(tt.apiToken, tt.orgSlug, tt.tags, tt.visibility)
if client.apiToken != tt.apiToken {
t.Errorf("Expected apiToken %s, got %s", tt.apiToken, client.apiToken)
}
if client.orgSlug != tt.orgSlug {
t.Errorf("Expected orgSlug %s, got %s", tt.orgSlug, client.orgSlug)
}
if client.tags != tt.tags {
t.Errorf("Expected tags %s, got %s", tt.tags, client.tags)
}
if client.visibility != tt.expectedVisibility {
t.Errorf("Expected visibility %s, got %s", tt.expectedVisibility, client.visibility)
}
if client.graphqlURL != defaultGraphQLURL {
t.Errorf("Expected graphqlURL %s, got %s", defaultGraphQLURL, client.graphqlURL)
}
})
}
}
func TestGraphQLMutation(t *testing.T) {
// Test that the GraphQL mutation is properly formatted
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
var req map[string]interface{}
if err := json.Unmarshal(body, &req); err != nil {
t.Fatalf("Failed to parse request: %v", err)
}
// Verify mutation structure
query, ok := req["query"].(string)
if !ok {
t.Fatal("Missing query field")
}
// Check that mutation contains expected parts
if !strings.Contains(query, "mutation AddLink") {
t.Error("Mutation should contain 'mutation AddLink'")
}
if !strings.Contains(query, "$input: LinkInput!") {
t.Error("Mutation should contain input parameter")
}
if !strings.Contains(query, "addLink(input: $input)") {
t.Error("Mutation should contain addLink call")
}
// Verify variables structure
variables, ok := req["variables"].(map[string]interface{})
if !ok {
t.Fatal("Missing variables field")
}
input, ok := variables["input"].(map[string]interface{})
if !ok {
t.Fatal("Missing input in variables")
}
// Check all required fields
requiredFields := []string{"url", "title", "description", "orgSlug", "visibility", "unread", "starred", "archive", "tags"}
for _, field := range requiredFields {
if _, ok := input[field]; !ok {
t.Errorf("Missing required field: %s", field)
}
}
// Return success
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"data": map[string]interface{}{
"addLink": map[string]interface{}{
"id": "123",
},
},
})
}))
defer server.Close()
client := &Client{
graphqlURL: server.URL,
apiToken: "test-token",
orgSlug: "test-org",
tags: "test",
visibility: "PUBLIC",
}
err := client.CreateBookmark("https://example.com", "Test Title", "Test Content")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
func BenchmarkCreateBookmark(b *testing.B) {
// Create a mock server that always returns success
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"data": map[string]interface{}{
"addLink": map[string]interface{}{
"id": "123",
},
},
})
}))
defer server.Close()
client := &Client{
graphqlURL: server.URL,
apiToken: "test-token",
orgSlug: "test-org",
tags: "tag1,tag2,tag3",
visibility: "PUBLIC",
}
// Run benchmark
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = client.CreateBookmark("https://example.com", "Test Title", "Test Content")
}
}
func BenchmarkTagProcessing(b *testing.B) {
// Benchmark tag splitting and limiting
tags := "tag1,tag2,tag3,tag4,tag5,tag6,tag7,tag8,tag9,tag10,tag11,tag12,tag13,tag14,tag15"
b.ResetTimer()
for i := 0; i < b.N; i++ {
tagsSplitFn := func(c rune) bool {
return c == ',' || c == ' '
}
splitTags := strings.FieldsFunc(tags, tagsSplitFn)
if len(splitTags) > maxTags {
splitTags = splitTags[:maxTags]
}
_ = strings.Join(splitTags, ",")
}
}
v2-2.2.13/internal/integration/linkwarden/ 0000775 0000000 0000000 00000000000 15062123773 0020431 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/linkwarden/linkwarden.go 0000664 0000000 0000000 00000003423 15062123773 0023120 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package linkwarden // import "miniflux.app/v2/internal/integration/linkwarden"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
baseURL string
apiKey string
}
func NewClient(baseURL, apiKey string) *Client {
return &Client{baseURL: baseURL, apiKey: apiKey}
}
func (c *Client) CreateBookmark(entryURL, entryTitle string) error {
if c.baseURL == "" || c.apiKey == "" {
return fmt.Errorf("linkwarden: missing base URL or API key")
}
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/v1/links")
if err != nil {
return fmt.Errorf(`linkwarden: invalid API endpoint: %v`, err)
}
requestBody, err := json.Marshal(map[string]string{
"url": entryURL,
"name": entryTitle,
})
if err != nil {
return fmt.Errorf("linkwarden: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("linkwarden: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Bearer "+c.apiKey)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("linkwarden: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("linkwarden: unable to create link: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
v2-2.2.13/internal/integration/matrixbot/ 0000775 0000000 0000000 00000000000 15062123773 0020304 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/matrixbot/client.go 0000664 0000000 0000000 00000014265 15062123773 0022121 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package matrixbot // import "miniflux.app/v2/internal/integration/matrixbot"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"miniflux.app/v2/internal/crypto"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
matrixBaseURL string
}
func NewClient(matrixBaseURL string) *Client {
return &Client{matrixBaseURL: matrixBaseURL}
}
// Specs: https://spec.matrix.org/v1.8/client-server-api/#getwell-knownmatrixclient
func (c *Client) DiscoverEndpoints() (*DiscoveryEndpointResponse, error) {
endpointURL, err := url.JoinPath(c.matrixBaseURL, "/.well-known/matrix/client")
if err != nil {
return nil, fmt.Errorf("matrix: unable to join base URL and path: %w", err)
}
request, err := http.NewRequest(http.MethodGet, endpointURL, nil)
if err != nil {
return nil, fmt.Errorf("matrix: unable to create request: %v", err)
}
request.Header.Set("Accept", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("matrix: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return nil, fmt.Errorf("matrix: unexpected response from %s status code is %d", endpointURL, response.StatusCode)
}
var discoveryEndpointResponse DiscoveryEndpointResponse
if err := json.NewDecoder(response.Body).Decode(&discoveryEndpointResponse); err != nil {
return nil, fmt.Errorf("matrix: unable to decode discovery response: %w", err)
}
return &discoveryEndpointResponse, nil
}
// Specs https://spec.matrix.org/v1.8/client-server-api/#post_matrixclientv3login
func (c *Client) Login(homeServerURL, matrixUsername, matrixPassword string) (*LoginResponse, error) {
endpointURL, err := url.JoinPath(homeServerURL, "/_matrix/client/v3/login")
if err != nil {
return nil, fmt.Errorf("matrix: unable to join base URL and path: %w", err)
}
loginRequest := LoginRequest{
Type: "m.login.password",
Identifier: UserIdentifier{
Type: "m.id.user",
User: matrixUsername,
},
Password: matrixPassword,
}
requestBody, err := json.Marshal(loginRequest)
if err != nil {
return nil, fmt.Errorf("matrix: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, endpointURL, bytes.NewReader(requestBody))
if err != nil {
return nil, fmt.Errorf("matrix: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("matrix: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return nil, fmt.Errorf("matrix: unexpected response from %s status code is %d", endpointURL, response.StatusCode)
}
var loginResponse LoginResponse
if err := json.NewDecoder(response.Body).Decode(&loginResponse); err != nil {
return nil, fmt.Errorf("matrix: unable to decode login response: %w", err)
}
return &loginResponse, nil
}
// Specs https://spec.matrix.org/v1.8/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
func (c *Client) SendFormattedTextMessage(homeServerURL, accessToken, roomID, textMessage, formattedMessage string) (*RoomEventResponse, error) {
txnID := crypto.GenerateRandomStringHex(10)
endpointURL, err := url.JoinPath(homeServerURL, "/_matrix/client/v3/rooms/", roomID, "/send/m.room.message/", txnID)
if err != nil {
return nil, fmt.Errorf("matrix: unable to join base URL and path: %w", err)
}
messageEvent := TextMessageEventRequest{
MsgType: "m.text",
Body: textMessage,
Format: "org.matrix.custom.html",
FormattedBody: formattedMessage,
}
requestBody, err := json.Marshal(messageEvent)
if err != nil {
return nil, fmt.Errorf("matrix: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPut, endpointURL, bytes.NewReader(requestBody))
if err != nil {
return nil, fmt.Errorf("matrix: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Bearer "+accessToken)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("matrix: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return nil, fmt.Errorf("matrix: unexpected response from %s status code is %d", endpointURL, response.StatusCode)
}
var eventResponse RoomEventResponse
if err := json.NewDecoder(response.Body).Decode(&eventResponse); err != nil {
return nil, fmt.Errorf("matrix: unable to decode event response: %w", err)
}
return &eventResponse, nil
}
type HomeServerInformation struct {
BaseURL string `json:"base_url"`
}
type IdentityServerInformation struct {
BaseURL string `json:"base_url"`
}
type DiscoveryEndpointResponse struct {
HomeServerInformation HomeServerInformation `json:"m.homeserver"`
IdentityServerInformation IdentityServerInformation `json:"m.identity_server"`
}
type UserIdentifier struct {
Type string `json:"type"`
User string `json:"user"`
}
type LoginRequest struct {
Type string `json:"type"`
Identifier UserIdentifier `json:"identifier"`
Password string `json:"password"`
}
type LoginResponse struct {
UserID string `json:"user_id"`
AccessToken string `json:"access_token"`
DeviceID string `json:"device_id"`
HomeServer string `json:"home_server"`
}
type TextMessageEventRequest struct {
MsgType string `json:"msgtype"`
Body string `json:"body"`
Format string `json:"format"`
FormattedBody string `json:"formatted_body"`
}
type RoomEventResponse struct {
EventID string `json:"event_id"`
}
v2-2.2.13/internal/integration/matrixbot/matrixbot.go 0000664 0000000 0000000 00000002476 15062123773 0022655 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package matrixbot // import "miniflux.app/v2/internal/integration/matrixbot"
import (
"fmt"
"strings"
"miniflux.app/v2/internal/model"
)
// PushEntries pushes entries to matrix chat using integration settings provided
func PushEntries(feed *model.Feed, entries model.Entries, matrixBaseURL, matrixUsername, matrixPassword, matrixRoomID string) error {
client := NewClient(matrixBaseURL)
discovery, err := client.DiscoverEndpoints()
if err != nil {
return err
}
loginResponse, err := client.Login(discovery.HomeServerInformation.BaseURL, matrixUsername, matrixPassword)
if err != nil {
return err
}
var textMessages []string
var formattedTextMessages []string
for _, entry := range entries {
textMessages = append(textMessages, fmt.Sprintf(`[%s] %s - %s`, feed.Title, entry.Title, entry.URL))
formattedTextMessages = append(formattedTextMessages, fmt.Sprintf(`
"+strings.Join(formattedTextMessages, "\n")+"
",
)
return err
}
v2-2.2.13/internal/integration/notion/ 0000775 0000000 0000000 00000000000 15062123773 0017601 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/notion/notion.go 0000664 0000000 0000000 00000004066 15062123773 0021444 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package notion
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
apiToken string
pageID string
}
func NewClient(apiToken, pageID string) *Client {
return &Client{apiToken, pageID}
}
func (c *Client) UpdateDocument(entryURL string, entryTitle string) error {
if c.apiToken == "" || c.pageID == "" {
return fmt.Errorf("notion: missing API token or page ID")
}
apiEndpoint := "https://api.notion.com/v1/blocks/" + c.pageID + "/children"
requestBody, err := json.Marshal(¬ionDocument{
Children: []block{
{
Object: "block",
Type: "bookmark",
Bookmark: bookmarkObject{
Caption: []any{},
URL: entryURL,
},
},
},
})
if err != nil {
return fmt.Errorf("notion: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPatch, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("notion: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Notion-Version", "2022-06-28")
request.Header.Set("Authorization", "Bearer "+c.apiToken)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("notion: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return fmt.Errorf("notion: unable to update document: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
type notionDocument struct {
Children []block `json:"children"`
}
type block struct {
Object string `json:"object"`
Type string `json:"type"`
Bookmark bookmarkObject `json:"bookmark"`
}
type bookmarkObject struct {
Caption []any `json:"caption"`
URL string `json:"url"`
}
v2-2.2.13/internal/integration/ntfy/ 0000775 0000000 0000000 00000000000 15062123773 0017253 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/ntfy/ntfy.go 0000664 0000000 0000000 00000007365 15062123773 0020575 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package ntfy // import "miniflux.app/v2/internal/integration/ntfy"
import (
"bytes"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"net/url"
"time"
"miniflux.app/v2/internal/config"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/version"
)
const (
defaultClientTimeout = 10 * time.Second
defaultNtfyURL = "https://ntfy.sh"
)
type Client struct {
ntfyURL, ntfyTopic, ntfyApiToken, ntfyUsername, ntfyPassword, ntfyIconURL string
ntfyInternalLinks bool
ntfyPriority int
}
func NewClient(ntfyURL, ntfyTopic, ntfyApiToken, ntfyUsername, ntfyPassword, ntfyIconURL string, ntfyInternalLinks bool, ntfyPriority int) *Client {
if ntfyURL == "" {
ntfyURL = defaultNtfyURL
}
return &Client{ntfyURL, ntfyTopic, ntfyApiToken, ntfyUsername, ntfyPassword, ntfyIconURL, ntfyInternalLinks, ntfyPriority}
}
func (c *Client) SendMessages(feed *model.Feed, entries model.Entries) error {
for _, entry := range entries {
ntfyMessage := &ntfyMessage{
Topic: c.ntfyTopic,
Message: entry.Title,
Title: feed.Title,
Priority: c.ntfyPriority,
Click: entry.URL,
}
if c.ntfyIconURL != "" {
ntfyMessage.Icon = c.ntfyIconURL
}
if c.ntfyInternalLinks {
url, err := url.Parse(config.Opts.BaseURL())
if err != nil {
slog.Error("Unable to parse base URL", slog.Any("error", err))
} else {
ntfyMessage.Click = fmt.Sprintf("%s%s%d", url, "/unread/entry/", entry.ID)
}
}
slog.Debug("Sending Ntfy message",
slog.String("url", c.ntfyURL),
slog.String("topic", c.ntfyTopic),
slog.Int("priority", ntfyMessage.Priority),
slog.String("message", ntfyMessage.Message),
slog.String("entry_url", ntfyMessage.Click),
)
if err := c.makeRequest(ntfyMessage); err != nil {
return err
}
}
return nil
}
func (c *Client) makeRequest(payload any) error {
requestBody, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("ntfy: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, c.ntfyURL, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("ntfy: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
// See https://docs.ntfy.sh/publish/#access-tokens
if c.ntfyApiToken != "" {
request.Header.Set("Authorization", "Bearer "+c.ntfyApiToken)
}
// See https://docs.ntfy.sh/publish/#username-password
if c.ntfyUsername != "" && c.ntfyPassword != "" {
request.SetBasicAuth(c.ntfyUsername, c.ntfyPassword)
}
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("ntfy: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("ntfy: incorrect response status code %d for url %s", response.StatusCode, c.ntfyURL)
}
return nil
}
// See https://docs.ntfy.sh/publish/#publish-as-json
type ntfyMessage struct {
Topic string `json:"topic"`
Message string `json:"message"`
Title string `json:"title"`
Tags []string `json:"tags,omitempty"`
Priority int `json:"priority,omitempty"`
Icon string `json:"icon,omitempty"` // https://docs.ntfy.sh/publish/#icons
Click string `json:"click,omitempty"`
Actions []ntfyAction `json:"actions,omitempty"`
}
// See https://docs.ntfy.sh/publish/#action-buttons
type ntfyAction struct {
Action string `json:"action"`
Label string `json:"label"`
URL string `json:"url"`
}
v2-2.2.13/internal/integration/nunuxkeeper/ 0000775 0000000 0000000 00000000000 15062123773 0020644 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/nunuxkeeper/nunuxkeeper.go 0000664 0000000 0000000 00000004106 15062123773 0023545 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package nunuxkeeper // import "miniflux.app/v2/internal/integration/nunuxkeeper"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
baseURL string
apiKey string
}
func NewClient(baseURL, apiKey string) *Client {
return &Client{baseURL: baseURL, apiKey: apiKey}
}
func (c *Client) AddEntry(entryURL, entryTitle, entryContent string) error {
if c.baseURL == "" || c.apiKey == "" {
return fmt.Errorf("nunux-keeper: missing base URL or API key")
}
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/v2/documents")
if err != nil {
return fmt.Errorf(`nunux-keeper: invalid API endpoint: %v`, err)
}
requestBody, err := json.Marshal(&nunuxKeeperDocument{
Title: entryTitle,
Origin: entryURL,
Content: entryContent,
ContentType: "text/html",
})
if err != nil {
return fmt.Errorf("notion: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("nunux-keeper: unable to create request: %v", err)
}
request.SetBasicAuth("api", c.apiKey)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("nunux-keeper: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return fmt.Errorf("nunux-keeper: unable to create document: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
type nunuxKeeperDocument struct {
Title string `json:"title,omitempty"`
Origin string `json:"origin,omitempty"`
Content string `json:"content,omitempty"`
ContentType string `json:"contentType,omitempty"`
}
v2-2.2.13/internal/integration/omnivore/ 0000775 0000000 0000000 00000000000 15062123773 0020131 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/omnivore/omnivore.go 0000664 0000000 0000000 00000005605 15062123773 0022324 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package omnivore // import "miniflux.app/v2/internal/integration/omnivore"
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"miniflux.app/v2/internal/crypto"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
const defaultApiEndpoint = "https://api-prod.omnivore.app/api/graphql"
var mutation = `
mutation SaveUrl($input: SaveUrlInput!) {
saveUrl(input: $input) {
... on SaveSuccess {
url
clientRequestId
}
... on SaveError {
errorCodes
message
}
}
}
`
type SaveUrlInput struct {
ClientRequestId string `json:"clientRequestId"`
Source string `json:"source"`
Url string `json:"url"`
}
type errorResponse struct {
Errors []struct {
Message string `json:"message"`
} `json:"errors"`
}
type successResponse struct {
Data struct {
SaveUrl struct {
Url string `json:"url"`
ClientRequestId string `json:"clientRequestId"`
} `json:"saveUrl"`
} `json:"data"`
}
type Client interface {
SaveUrl(url string) error
}
type client struct {
wrapped *http.Client
apiEndpoint string
apiToken string
}
func NewClient(apiToken string, apiEndpoint string) Client {
if apiEndpoint == "" {
apiEndpoint = defaultApiEndpoint
}
return &client{wrapped: &http.Client{Timeout: defaultClientTimeout}, apiEndpoint: apiEndpoint, apiToken: apiToken}
}
func (c *client) SaveUrl(url string) error {
var payload = map[string]any{
"query": mutation,
"variables": map[string]any{
"input": map[string]any{
"clientRequestId": crypto.GenerateUUID(),
"source": "api",
"url": url,
},
},
}
b, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, c.apiEndpoint, bytes.NewReader(b))
if err != nil {
return err
}
req.Header.Set("Authorization", c.apiToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Miniflux/"+version.Version)
resp, err := c.wrapped.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
b, err = io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("omnivore: failed to parse response: %s", err)
}
if resp.StatusCode >= 400 {
var errResponse errorResponse
if err = json.Unmarshal(b, &errResponse); err != nil {
return fmt.Errorf("omnivore: failed to save URL: status=%d %s", resp.StatusCode, string(b))
}
return fmt.Errorf("omnivore: failed to save URL: status=%d %s", resp.StatusCode, errResponse.Errors[0].Message)
}
var successReponse successResponse
if err = json.Unmarshal(b, &successReponse); err != nil {
return fmt.Errorf("omnivore: failed to parse response, however the request appears successful, is the url correct?: status=%d %s", resp.StatusCode, string(b))
}
return nil
}
v2-2.2.13/internal/integration/pinboard/ 0000775 0000000 0000000 00000000000 15062123773 0020071 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/pinboard/pinboard.go 0000664 0000000 0000000 00000006356 15062123773 0022230 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package pinboard // import "miniflux.app/v2/internal/integration/pinboard"
import (
"encoding/xml"
"errors"
"fmt"
"net/http"
"net/url"
"time"
"miniflux.app/v2/internal/version"
)
var errPostNotFound = fmt.Errorf("pinboard: post not found")
var errMissingCredentials = fmt.Errorf("pinboard: missing auth token")
const defaultClientTimeout = 10 * time.Second
type Client struct {
authToken string
}
func NewClient(authToken string) *Client {
return &Client{authToken: authToken}
}
func (c *Client) CreateBookmark(entryURL, entryTitle, pinboardTags string, markAsUnread bool) error {
if c.authToken == "" {
return errMissingCredentials
}
// We check if the url is already bookmarked to avoid overriding existing data.
post, err := c.getBookmark(entryURL)
if err != nil && errors.Is(err, errPostNotFound) {
post = NewPost(entryURL, entryTitle)
} else if err != nil {
// In case of any other error, we return immediately to avoid overriding existing data.
return err
}
post.addTag(pinboardTags)
if markAsUnread {
post.SetToread()
}
values := url.Values{}
values.Add("auth_token", c.authToken)
post.AddValues(values)
apiEndpoint := "https://api.pinboard.in/v1/posts/add?" + values.Encode()
request, err := http.NewRequest(http.MethodGet, apiEndpoint, nil)
if err != nil {
return fmt.Errorf("pinboard: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("pinboard: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("pinboard: unable to create a bookmark: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
// getBookmark fetches a bookmark from Pinboard. https://www.pinboard.in/api/#posts_get
func (c *Client) getBookmark(entryURL string) (*Post, error) {
if c.authToken == "" {
return nil, errMissingCredentials
}
values := url.Values{}
values.Add("auth_token", c.authToken)
values.Add("url", entryURL)
apiEndpoint := "https://api.pinboard.in/v1/posts/get?" + values.Encode()
request, err := http.NewRequest(http.MethodGet, apiEndpoint, nil)
if err != nil {
return nil, fmt.Errorf("pinboard: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("pinboard: unable fetch bookmark: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return nil, fmt.Errorf("pinboard: unable to fetch bookmark, status=%d", response.StatusCode)
}
var results posts
err = xml.NewDecoder(response.Body).Decode(&results)
if err != nil {
return nil, fmt.Errorf("pinboard: unable to decode XML: %v", err)
}
if len(results.Posts) == 0 {
return nil, errPostNotFound
}
return &results.Posts[0], nil
}
v2-2.2.13/internal/integration/pinboard/post.go 0000664 0000000 0000000 00000002752 15062123773 0021413 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package pinboard // import "miniflux.app/v2/internal/integration/pinboard"
import (
"encoding/xml"
"net/url"
"strings"
"time"
)
// Post a Pinboard bookmark. "inspiration" from https://github.com/drags/pinboard/blob/master/posts.go#L32-L42
type Post struct {
XMLName xml.Name `xml:"post"`
Url string `xml:"href,attr"`
Description string `xml:"description,attr"`
Tags string `xml:"tag,attr"`
Extended string `xml:"extended,attr"`
Date time.Time `xml:"time,attr"`
Shared string `xml:"shared,attr"`
Toread string `xml:"toread,attr"`
}
// Posts A result of a Pinboard API call
type posts struct {
XMLName xml.Name `xml:"posts"`
Posts []Post `xml:"post"`
}
func NewPost(url string, description string) *Post {
return &Post{
Url: url,
Description: description,
Date: time.Now(),
Toread: "no",
}
}
func (p *Post) addTag(tag string) {
if !strings.Contains(p.Tags, tag) {
p.Tags += " " + tag
}
}
func (p *Post) SetToread() {
p.Toread = "yes"
}
func (p *Post) AddValues(values url.Values) {
values.Add("url", p.Url)
values.Add("description", p.Description)
values.Add("tags", p.Tags)
if p.Toread != "" {
values.Add("toread", p.Toread)
}
if p.Shared != "" {
values.Add("shared", p.Shared)
}
values.Add("dt", p.Date.Format(time.RFC3339))
values.Add("extended", p.Extended)
}
v2-2.2.13/internal/integration/pushover/ 0000775 0000000 0000000 00000000000 15062123773 0020146 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/pushover/pushover.go 0000664 0000000 0000000 00000005742 15062123773 0022360 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package pushover // import "miniflux.app/v2/internal/integration/pushover"
import (
"bytes"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"strings"
"time"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/version"
)
const (
defaultClientTimeout = 10 * time.Second
defaultPushoverURL = "https://api.pushover.net"
)
type Client struct {
prefix string
token string
user string
device string
priority int
}
type Message struct {
Token string `json:"token"`
User string `json:"user"`
Title string `json:"title"`
Message string `json:"message"`
Priority int `json:"priority"`
URL string `json:"url"`
URLTitle string `json:"url_title"`
Device string `json:"device,omitempty"`
}
type ErrorResponse struct {
User string `json:"user"`
Errors []string `json:"errors"`
Status int `json:"status"`
Request string `json:"request"`
}
func New(user, token string, priority int, device, urlPrefix string) *Client {
if urlPrefix == "" {
urlPrefix = defaultPushoverURL
}
if priority < -2 {
priority = -2
}
if priority > 2 {
priority = 2
}
return &Client{
user: user,
token: token,
device: device,
prefix: urlPrefix,
priority: priority,
}
}
func (c *Client) SendMessages(feed *model.Feed, entries model.Entries) error {
if c.token == "" || c.user == "" {
return fmt.Errorf("pushover token and user are required")
}
for _, entry := range entries {
msg := &Message{
User: c.user,
Token: c.token,
Device: c.device,
Message: entry.Title,
Title: feed.Title,
Priority: c.priority,
URL: entry.URL,
}
slog.Debug("Sending Pushover message",
slog.Int("priority", msg.Priority),
slog.String("message", msg.Message),
slog.String("entry_url", msg.URL),
)
if err := c.makeRequest(msg); err != nil {
return fmt.Errorf("c.makeRequest: %w", err)
}
}
return nil
}
func (c *Client) makeRequest(payload *Message) error {
jsonData, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("json.Marshal: %w", err)
}
url := c.prefix + "/1/messages.json"
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("http.NewRequest: %w", err)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
resp, err := httpClient.Do(req)
if err != nil {
return fmt.Errorf("httpClient.Do: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
errorMessage := resp.Status
var errResp ErrorResponse
if err := json.NewDecoder(resp.Body).Decode(&errResp); err != nil {
if len(errResp.Errors) > 0 {
errorMessage = strings.Join(errResp.Errors, ",")
}
}
return fmt.Errorf("pushover API error (%d): %s", resp.StatusCode, errorMessage)
}
return nil
}
v2-2.2.13/internal/integration/raindrop/ 0000775 0000000 0000000 00000000000 15062123773 0020111 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/raindrop/raindrop.go 0000664 0000000 0000000 00000004050 15062123773 0022255 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package raindrop // import "miniflux.app/v2/internal/integration/raindrop"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
token string
collectionID string
tags []string
}
func NewClient(token, collectionID, tags string) *Client {
return &Client{token: token, collectionID: collectionID, tags: strings.Split(tags, ",")}
}
// https://developer.raindrop.io/v1/raindrops/single#create-raindrop
func (c *Client) CreateRaindrop(entryURL, entryTitle string) error {
if c.token == "" {
return fmt.Errorf("raindrop: missing token")
}
var request *http.Request
requestBodyJson, err := json.Marshal(&raindrop{
Link: entryURL,
Title: entryTitle,
Collection: collection{Id: c.collectionID},
Tags: c.tags,
})
if err != nil {
return fmt.Errorf("raindrop: unable to encode request body: %v", err)
}
request, err = http.NewRequest(http.MethodPost, "https://api.raindrop.io/rest/v1/raindrop", bytes.NewReader(requestBodyJson))
if err != nil {
return fmt.Errorf("raindrop: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Bearer "+c.token)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("raindrop: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("raindrop: unable to create bookmark: status=%d", response.StatusCode)
}
return nil
}
type raindrop struct {
Link string `json:"link"`
Title string `json:"title"`
Collection collection `json:"collection,omitempty"`
Tags []string `json:"tags"`
}
type collection struct {
Id string `json:"$id"`
}
v2-2.2.13/internal/integration/readeck/ 0000775 0000000 0000000 00000000000 15062123773 0017671 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/readeck/readeck.go 0000664 0000000 0000000 00000010547 15062123773 0021625 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package readeck // import "miniflux.app/v2/internal/integration/readeck"
import (
"bytes"
"encoding/json"
"fmt"
"mime/multipart"
"net/http"
"strings"
"time"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
baseURL string
apiKey string
labels string
onlyURL bool
}
func NewClient(baseURL, apiKey, labels string, onlyURL bool) *Client {
return &Client{baseURL: baseURL, apiKey: apiKey, labels: labels, onlyURL: onlyURL}
}
func (c *Client) CreateBookmark(entryURL, entryTitle string, entryContent string) error {
if c.baseURL == "" || c.apiKey == "" {
return fmt.Errorf("readeck: missing base URL or API key")
}
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/bookmarks/")
if err != nil {
return fmt.Errorf(`readeck: invalid API endpoint: %v`, err)
}
labelsSplitFn := func(c rune) bool {
return c == ',' || c == ' '
}
labelsSplit := strings.FieldsFunc(c.labels, labelsSplitFn)
var request *http.Request
if c.onlyURL {
requestBodyJson, err := json.Marshal(&readeckBookmark{
Url: entryURL,
Title: entryTitle,
Labels: labelsSplit,
})
if err != nil {
return fmt.Errorf("readeck: unable to encode request body: %v", err)
}
request, err = http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBodyJson))
if err != nil {
return fmt.Errorf("readeck: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
} else {
requestBody := new(bytes.Buffer)
multipartWriter := multipart.NewWriter(requestBody)
urlPart, err := multipartWriter.CreateFormField("url")
if err != nil {
return fmt.Errorf("readeck: unable to encode request body (entry url): %v", err)
}
urlPart.Write([]byte(entryURL))
titlePart, err := multipartWriter.CreateFormField("title")
if err != nil {
return fmt.Errorf("readeck: unable to encode request body (entry title): %v", err)
}
titlePart.Write([]byte(entryTitle))
featurePart, err := multipartWriter.CreateFormField("feature_find_main")
if err != nil {
return fmt.Errorf("readeck: unable to encode request body (feature_find_main flag): %v", err)
}
featurePart.Write([]byte("false")) // false to disable readability
for _, label := range labelsSplit {
labelPart, err := multipartWriter.CreateFormField("labels")
if err != nil {
return fmt.Errorf("readeck: unable to encode request body (entry labels): %v", err)
}
labelPart.Write([]byte(label))
}
contentBodyHeader, err := json.Marshal(&partContentHeader{
Url: entryURL,
ContentHeader: contentHeader{ContentType: "text/html; charset=utf-8"},
})
if err != nil {
return fmt.Errorf("readeck: unable to encode request body (entry content header): %v", err)
}
contentPart, err := multipartWriter.CreateFormFile("resource", "blob")
if err != nil {
return fmt.Errorf("readeck: unable to encode request body (entry content): %v", err)
}
contentPart.Write(contentBodyHeader)
contentPart.Write([]byte("\n"))
contentPart.Write([]byte(entryContent))
err = multipartWriter.Close()
if err != nil {
return fmt.Errorf("readeck: unable to encode request body: %v", err)
}
request, err = http.NewRequest(http.MethodPost, apiEndpoint, requestBody)
if err != nil {
return fmt.Errorf("readeck: unable to create request: %v", err)
}
request.Header.Set("Content-Type", multipartWriter.FormDataContentType())
}
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Bearer "+c.apiKey)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("readeck: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("readeck: unable to create bookmark: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
type readeckBookmark struct {
Url string `json:"url"`
Title string `json:"title"`
Labels []string `json:"labels,omitempty"`
}
type contentHeader struct {
ContentType string `json:"content-type"`
}
type partContentHeader struct {
Url string `json:"url"`
ContentHeader contentHeader `json:"headers"`
}
v2-2.2.13/internal/integration/readwise/ 0000775 0000000 0000000 00000000000 15062123773 0020076 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/readwise/readwise.go 0000664 0000000 0000000 00000003245 15062123773 0022234 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// Readwise Reader API documentation: https://readwise.io/reader_api
package readwise // import "miniflux.app/v2/internal/integration/readwise"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"miniflux.app/v2/internal/version"
)
const (
readwiseApiEndpoint = "https://readwise.io/api/v3/save/"
defaultClientTimeout = 10 * time.Second
)
type Client struct {
apiKey string
}
func NewClient(apiKey string) *Client {
return &Client{apiKey: apiKey}
}
func (c *Client) CreateDocument(entryURL string) error {
if c.apiKey == "" {
return fmt.Errorf("readwise: missing API key")
}
requestBody, err := json.Marshal(&readwiseDocument{
URL: entryURL,
})
if err != nil {
return fmt.Errorf("readwise: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, readwiseApiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("readwise: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Token "+c.apiKey)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("readwise: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("readwise: unable to create document: url=%s status=%d", readwiseApiEndpoint, response.StatusCode)
}
return nil
}
type readwiseDocument struct {
URL string `json:"url"`
}
v2-2.2.13/internal/integration/rssbridge/ 0000775 0000000 0000000 00000000000 15062123773 0020257 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/rssbridge/rssbridge.go 0000664 0000000 0000000 00000004666 15062123773 0022606 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package rssbridge // import "miniflux.app/v2/internal/integration/rssbridge"
import (
"encoding/json"
"fmt"
"log/slog"
"net/http"
"net/url"
"strings"
"time"
)
const defaultClientTimeout = 30 * time.Second
type Bridge struct {
URL string `json:"url"`
BridgeMeta BridgeMeta `json:"bridgeMeta"`
}
type BridgeMeta struct {
Name string `json:"name"`
}
func DetectBridges(rssBridgeURL, rssBridgeToken, websiteURL string) ([]*Bridge, error) {
endpointURL, err := url.Parse(rssBridgeURL)
if err != nil {
return nil, fmt.Errorf("RSS-Bridge: unable to parse bridge URL: %w", err)
}
values := endpointURL.Query()
if rssBridgeToken != "" {
values.Add("token", rssBridgeToken)
}
values.Add("action", "findfeed")
values.Add("format", "atom")
values.Add("url", websiteURL)
endpointURL.RawQuery = values.Encode()
slog.Debug("Detecting RSS bridges", slog.String("url", endpointURL.String()))
request, err := http.NewRequest(http.MethodGet, endpointURL.String(), nil)
if err != nil {
return nil, fmt.Errorf("RSS-Bridge: unable to create request: %w", err)
}
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("RSS-Bridge: unable to execute request: %w", err)
}
defer response.Body.Close()
if response.StatusCode == http.StatusNotFound {
return nil, nil
}
if response.StatusCode > 400 {
return nil, fmt.Errorf("RSS-Bridge: unexpected status code %d", response.StatusCode)
}
var bridgeResponse []*Bridge
if err := json.NewDecoder(response.Body).Decode(&bridgeResponse); err != nil {
return nil, fmt.Errorf("RSS-Bridge: unable to decode bridge response: %w", err)
}
for _, bridge := range bridgeResponse {
slog.Debug("Found RSS bridge",
slog.String("name", bridge.BridgeMeta.Name),
slog.String("url", bridge.URL),
)
if strings.HasPrefix(bridge.URL, "./") {
bridge.URL = rssBridgeURL + bridge.URL[2:]
slog.Debug("Rewrited relative RSS bridge URL",
slog.String("name", bridge.BridgeMeta.Name),
slog.String("url", bridge.URL),
)
}
if rssBridgeToken != "" {
bridge.URL = bridge.URL + "&token=" + rssBridgeToken
slog.Debug("Appended token to RSS bridge URL",
slog.String("name", bridge.BridgeMeta.Name),
slog.String("url", bridge.URL),
)
}
}
return bridgeResponse, nil
}
v2-2.2.13/internal/integration/shaarli/ 0000775 0000000 0000000 00000000000 15062123773 0017716 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/shaarli/shaarli.go 0000664 0000000 0000000 00000004726 15062123773 0021701 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package shaarli // import "miniflux.app/v2/internal/integration/shaarli"
import (
"bytes"
"crypto/hmac"
"crypto/sha512"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"time"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
baseURL string
apiSecret string
}
func NewClient(baseURL, apiSecret string) *Client {
return &Client{baseURL: baseURL, apiSecret: apiSecret}
}
func (c *Client) CreateLink(entryURL, entryTitle string) error {
if c.baseURL == "" || c.apiSecret == "" {
return fmt.Errorf("shaarli: missing base URL or API secret")
}
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/v1/links")
if err != nil {
return fmt.Errorf("shaarli: invalid API endpoint: %v", err)
}
requestBody, err := json.Marshal(&addLinkRequest{
URL: entryURL,
Title: entryTitle,
Private: true,
})
if err != nil {
return fmt.Errorf("shaarli: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("shaarli: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Bearer "+c.generateBearerToken())
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("shaarli: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusCreated {
return fmt.Errorf("shaarli: unable to add link: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
func (c *Client) generateBearerToken() string {
header := base64.RawURLEncoding.EncodeToString([]byte(`{"typ":"JWT","alg":"HS512"}`))
payload := base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf(`{"iat":%d}`, time.Now().Unix())))
data := header + "." + payload
mac := hmac.New(sha512.New, []byte(c.apiSecret))
mac.Write([]byte(data))
signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
return data + "." + signature
}
type addLinkRequest struct {
URL string `json:"url"`
Title string `json:"title"`
Private bool `json:"private"`
}
v2-2.2.13/internal/integration/shiori/ 0000775 0000000 0000000 00000000000 15062123773 0017570 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/shiori/shiori.go 0000664 0000000 0000000 00000010163 15062123773 0021415 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package shiori // import "miniflux.app/v2/internal/integration/shiori"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
baseURL string
username string
password string
}
func NewClient(baseURL, username, password string) *Client {
return &Client{baseURL: baseURL, username: username, password: password}
}
func (c *Client) CreateBookmark(entryURL, entryTitle string) error {
if c.baseURL == "" || c.username == "" || c.password == "" {
return fmt.Errorf("shiori: missing base URL, username or password")
}
token, err := c.authenticate()
if err != nil {
return fmt.Errorf("shiori: unable to authenticate: %v", err)
}
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/bookmarks")
if err != nil {
return fmt.Errorf("shiori: invalid API endpoint: %v", err)
}
requestBody, err := json.Marshal(&addBookmarkRequest{
URL: entryURL,
Title: entryTitle,
Excerpt: "",
CreateArchive: true,
CreateEbook: false,
Public: 0,
Tags: make([]string, 0),
})
if err != nil {
return fmt.Errorf("shiori: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("shiori: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Bearer "+token)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("shiori: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return fmt.Errorf("shiori: unable to create bookmark: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
func (c *Client) authenticate() (token string, err error) {
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/v1/auth/login")
if err != nil {
return "", fmt.Errorf("shiori: invalid API endpoint: %v", err)
}
requestBody, err := json.Marshal(&authRequest{Username: c.username, Password: c.password, RememberMe: false})
if err != nil {
return "", fmt.Errorf("shiori: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return "", fmt.Errorf("shiori: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return "", fmt.Errorf("shiori: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return "", fmt.Errorf("shiori: unable to authenticate: url=%s status=%d", apiEndpoint, response.StatusCode)
}
var authResponse authResponse
if err := json.NewDecoder(response.Body).Decode(&authResponse); err != nil {
return "", fmt.Errorf("shiori: unable to decode response: %v", err)
}
return authResponse.Message.Token, nil
}
type authRequest struct {
Username string `json:"username"`
Password string `json:"password"`
RememberMe bool `json:"remember_me"`
}
type authResponse struct {
OK bool `json:"ok"`
Message authResponseMessage `json:"message"`
}
type authResponseMessage struct {
SessionID string `json:"session"`
Token string `json:"token"`
}
type addBookmarkRequest struct {
URL string `json:"url"`
Title string `json:"title"`
CreateArchive bool `json:"create_archive"`
CreateEbook bool `json:"create_ebook"`
Public int `json:"public"`
Excerpt string `json:"excerpt"`
Tags []string `json:"tags"`
}
v2-2.2.13/internal/integration/slack/ 0000775 0000000 0000000 00000000000 15062123773 0017370 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/slack/slack.go 0000664 0000000 0000000 00000005231 15062123773 0021015 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// Slack Webhooks documentation: https://api.slack.com/messaging/webhooks
package slack // import "miniflux.app/v2/internal/integration/slack"
import (
"bytes"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"time"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
const slackMsgColor = "#5865F2"
type Client struct {
webhookURL string
}
func NewClient(webhookURL string) *Client {
return &Client{webhookURL: webhookURL}
}
func (c *Client) SendSlackMsg(feed *model.Feed, entries model.Entries) error {
for _, entry := range entries {
requestBody, err := json.Marshal(&slackMessage{
Attachments: []slackAttachments{
{
Title: "RSS feed update from Miniflux",
Color: slackMsgColor,
Fields: []slackFields{
{
Title: "Updated feed",
Value: feed.Title,
},
{
Title: "Article title",
Value: entry.Title,
},
{
Title: "Article link",
Value: entry.URL,
},
{
Title: "Author",
Value: entry.Author,
Short: true,
},
{
Title: "Source website",
Value: urllib.RootURL(feed.SiteURL),
Short: true,
},
},
},
},
})
if err != nil {
return fmt.Errorf("slack: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, c.webhookURL, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("slack: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
slog.Debug("Sending Slack notification",
slog.String("webhookURL", c.webhookURL),
slog.String("title", feed.Title),
slog.String("entry_url", entry.URL),
)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("slack: unable to send request: %v", err)
}
response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("slack: unable to send a notification: url=%s status=%d", c.webhookURL, response.StatusCode)
}
}
return nil
}
type slackFields struct {
Title string `json:"title"`
Value string `json:"value"`
Short bool `json:"short,omitempty"`
}
type slackAttachments struct {
Title string `json:"title"`
Color string `json:"color"`
Fields []slackFields `json:"fields"`
}
type slackMessage struct {
Attachments []slackAttachments `json:"attachments"`
}
v2-2.2.13/internal/integration/telegrambot/ 0000775 0000000 0000000 00000000000 15062123773 0020600 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/telegrambot/client.go 0000664 0000000 0000000 00000012174 15062123773 0022412 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package telegrambot // import "miniflux.app/v2/internal/integration/telegrambot"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"miniflux.app/v2/internal/version"
)
const (
defaultClientTimeout = 10 * time.Second
telegramAPIEndpoint = "https://api.telegram.org"
MarkdownFormatting = "Markdown"
MarkdownV2Formatting = "MarkdownV2"
HTMLFormatting = "HTML"
)
type Client struct {
botToken string
chatID string
}
func NewClient(botToken, chatID string) *Client {
return &Client{
botToken: botToken,
chatID: chatID,
}
}
// Specs: https://core.telegram.org/bots/api#getme
func (c *Client) GetMe() (*User, error) {
endpointURL, err := url.JoinPath(telegramAPIEndpoint, "/bot"+c.botToken, "/getMe")
if err != nil {
return nil, fmt.Errorf("telegram: unable to join base URL and path: %w", err)
}
request, err := http.NewRequest(http.MethodGet, endpointURL, nil)
if err != nil {
return nil, fmt.Errorf("telegram: unable to create request: %v", err)
}
request.Header.Set("Accept", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("telegram: unable to send request: %v", err)
}
defer response.Body.Close()
var userResponse UserResponse
if err := json.NewDecoder(response.Body).Decode(&userResponse); err != nil {
return nil, fmt.Errorf("telegram: unable to decode user response: %w", err)
}
if !userResponse.Ok {
return nil, fmt.Errorf("telegram: unable to send message: %s (error code is %d)", userResponse.Description, userResponse.ErrorCode)
}
return &userResponse.Result, nil
}
// Specs: https://core.telegram.org/bots/api#sendmessage
func (c *Client) SendMessage(message *MessageRequest) (*Message, error) {
endpointURL, err := url.JoinPath(telegramAPIEndpoint, "/bot"+c.botToken, "/sendMessage")
if err != nil {
return nil, fmt.Errorf("telegram: unable to join base URL and path: %w", err)
}
requestBody, err := json.Marshal(message)
if err != nil {
return nil, fmt.Errorf("telegram: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, endpointURL, bytes.NewReader(requestBody))
if err != nil {
return nil, fmt.Errorf("telegram: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("telegram: unable to send request: %v", err)
}
defer response.Body.Close()
var messageResponse MessageResponse
if err := json.NewDecoder(response.Body).Decode(&messageResponse); err != nil {
return nil, fmt.Errorf("telegram: unable to decode discovery response: %w", err)
}
if !messageResponse.Ok {
return nil, fmt.Errorf("telegram: unable to send message: %s (error code is %d)", messageResponse.Description, messageResponse.ErrorCode)
}
return &messageResponse.Result, nil
}
type InlineKeyboard struct {
InlineKeyboard []InlineKeyboardRow `json:"inline_keyboard"`
}
type InlineKeyboardRow []*InlineKeyboardButton
type InlineKeyboardButton struct {
Text string `json:"text"`
URL string `json:"url,omitempty"`
}
type User struct {
ID int64 `json:"id"`
IsBot bool `json:"is_bot"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Username string `json:"username"`
LanguageCode string `json:"language_code"`
IsPremium bool `json:"is_premium"`
CanJoinGroups bool `json:"can_join_groups"`
CanReadAllGroupMessages bool `json:"can_read_all_group_messages"`
SupportsInlineQueries bool `json:"supports_inline_queries"`
}
type Chat struct {
ID int64 `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
}
type Message struct {
MessageID int64 `json:"message_id"`
From User `json:"from"`
Chat Chat `json:"chat"`
MessageThreadID int64 `json:"message_thread_id"`
Date int64 `json:"date"`
}
type BaseResponse struct {
Ok bool `json:"ok"`
ErrorCode int `json:"error_code"`
Description string `json:"description"`
}
type UserResponse struct {
BaseResponse
Result User `json:"result"`
}
type MessageRequest struct {
ChatID string `json:"chat_id"`
MessageThreadID int64 `json:"message_thread_id,omitempty"`
Text string `json:"text"`
ParseMode string `json:"parse_mode,omitempty"`
DisableWebPagePreview bool `json:"disable_web_page_preview"`
DisableNotification bool `json:"disable_notification"`
ReplyMarkup *InlineKeyboard `json:"reply_markup,omitempty"`
}
type MessageResponse struct {
BaseResponse
Result Message `json:"result"`
}
v2-2.2.13/internal/integration/telegrambot/telegrambot.go 0000664 0000000 0000000 00000003673 15062123773 0023445 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package telegrambot // import "miniflux.app/v2/internal/integration/telegrambot"
import (
"fmt"
"log/slog"
"strconv"
"miniflux.app/v2/internal/config"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/urllib"
)
func PushEntry(feed *model.Feed, entry *model.Entry, botToken, chatID string, topicID *int64, disableWebPagePreview, disableNotification bool, disableButtons bool) error {
formattedText := fmt.Sprintf(
`%s - %s`,
feed.Title,
entry.URL,
entry.Title,
)
message := &MessageRequest{
ChatID: chatID,
Text: formattedText,
ParseMode: HTMLFormatting,
DisableWebPagePreview: disableWebPagePreview,
DisableNotification: disableNotification,
}
if topicID != nil {
message.MessageThreadID = *topicID
}
if !disableButtons {
var markupRow []*InlineKeyboardButton
baseURL := config.Opts.BaseURL()
entryPath := "/unread/entry/" + strconv.FormatInt(entry.ID, 10)
minifluxEntryURL, err := urllib.JoinBaseURLAndPath(baseURL, entryPath)
if err != nil {
slog.Error("Unable to create Miniflux entry URL", slog.Any("error", err))
} else {
minifluxEntryURLButton := InlineKeyboardButton{Text: "Go to Miniflux", URL: minifluxEntryURL}
markupRow = append(markupRow, &minifluxEntryURLButton)
}
articleURLButton := InlineKeyboardButton{Text: "Go to article", URL: entry.URL}
markupRow = append(markupRow, &articleURLButton)
if entry.CommentsURL != "" {
commentURLButton := InlineKeyboardButton{Text: "Comments", URL: entry.CommentsURL}
markupRow = append(markupRow, &commentURLButton)
}
message.ReplyMarkup = &InlineKeyboard{}
message.ReplyMarkup.InlineKeyboard = append(message.ReplyMarkup.InlineKeyboard, markupRow)
}
client := NewClient(botToken, chatID)
_, err := client.SendMessage(message)
return err
}
v2-2.2.13/internal/integration/wallabag/ 0000775 0000000 0000000 00000000000 15062123773 0020045 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/wallabag/wallabag.go 0000664 0000000 0000000 00000010445 15062123773 0022152 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package wallabag // import "miniflux.app/v2/internal/integration/wallabag"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/version"
)
const defaultClientTimeout = 10 * time.Second
type Client struct {
baseURL string
clientID string
clientSecret string
username string
password string
tags string
onlyURL bool
}
func NewClient(baseURL, clientID, clientSecret, username, password, tags string, onlyURL bool) *Client {
return &Client{baseURL, clientID, clientSecret, username, password, tags, onlyURL}
}
func (c *Client) CreateEntry(entryURL, entryTitle, entryContent string) error {
if c.baseURL == "" || c.clientID == "" || c.clientSecret == "" || c.username == "" || c.password == "" {
return fmt.Errorf("wallabag: missing base URL, client ID, client secret, username or password")
}
accessToken, err := c.getAccessToken()
if err != nil {
return err
}
return c.createEntry(accessToken, entryURL, entryTitle, entryContent, c.tags)
}
func (c *Client) createEntry(accessToken, entryURL, entryTitle, entryContent, tags string) error {
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/api/entries.json")
if err != nil {
return fmt.Errorf("wallbag: unable to generate entries endpoint: %v", err)
}
if c.onlyURL {
entryContent = ""
}
requestBody, err := json.Marshal(&createEntryRequest{
URL: entryURL,
Title: entryTitle,
Content: entryContent,
Tags: tags,
})
if err != nil {
return fmt.Errorf("wallbag: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("wallbag: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("Authorization", "Bearer "+accessToken)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("wallabag: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("wallabag: unable to get save entry: url=%s status=%d", apiEndpoint, response.StatusCode)
}
return nil
}
func (c *Client) getAccessToken() (string, error) {
values := url.Values{}
values.Add("grant_type", "password")
values.Add("client_id", c.clientID)
values.Add("client_secret", c.clientSecret)
values.Add("username", c.username)
values.Add("password", c.password)
apiEndpoint, err := urllib.JoinBaseURLAndPath(c.baseURL, "/oauth/v2/token")
if err != nil {
return "", fmt.Errorf("wallbag: unable to generate token endpoint: %v", err)
}
request, err := http.NewRequest(http.MethodPost, apiEndpoint, strings.NewReader(values.Encode()))
if err != nil {
return "", fmt.Errorf("wallbag: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
request.Header.Set("Accept", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return "", fmt.Errorf("wallabag: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return "", fmt.Errorf("wallabag: unable to get access token: url=%s status=%d", apiEndpoint, response.StatusCode)
}
var responseBody tokenResponse
if err := json.NewDecoder(response.Body).Decode(&responseBody); err != nil {
return "", fmt.Errorf("wallabag: unable to decode token response: %v", err)
}
return responseBody.AccessToken, nil
}
type tokenResponse struct {
AccessToken string `json:"access_token"`
Expires int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
TokenType string `json:"token_type"`
}
type createEntryRequest struct {
URL string `json:"url"`
Title string `json:"title"`
Content string `json:"content,omitempty"`
Tags string `json:"tags,omitempty"`
}
v2-2.2.13/internal/integration/wallabag/wallabag_test.go 0000664 0000000 0000000 00000023464 15062123773 0023216 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package wallabag
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestCreateEntry(t *testing.T) {
entryURL := "https://example.com"
entryTitle := "title"
entryContent := "content"
tags := "tag1,tag2,tag3"
tests := []struct {
name string
username string
password string
clientID string
clientSecret string
tags string
onlyURL bool
entryURL string
entryTitle string
entryContent string
serverResponse func(w http.ResponseWriter, r *http.Request)
wantErr bool
errContains string
}{
{
name: "successful entry creation with url only",
wantErr: false,
onlyURL: true,
username: "username",
password: "password",
clientID: "clientId",
clientSecret: "clientSecret",
tags: tags,
entryURL: entryURL,
entryTitle: entryTitle,
entryContent: entryContent,
serverResponse: func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/oauth/v2/token") {
// Return success response
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]any{
"access_token": "test-token",
"expires_in": 3600,
"refresh_token": "token",
"scope": "scope",
"token_type": "token_type",
})
return
}
// Verify authorization header
auth := r.Header.Get("Authorization")
if auth != "Bearer test-token" {
t.Errorf("Expected Authorization header 'Bearer test-token', got %s", auth)
}
// Verify content type
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
t.Errorf("Expected Content-Type 'application/json', got %s", contentType)
}
// Parse and verify request
body, _ := io.ReadAll(r.Body)
var req map[string]any
if err := json.Unmarshal(body, &req); err != nil {
t.Errorf("Failed to parse request body: %v", err)
}
if requstEntryURL := req["url"]; requstEntryURL != entryURL {
t.Errorf("Expected entryURL %s, got %s", entryURL, requstEntryURL)
}
if requestEntryTitle := req["title"]; requestEntryTitle != entryTitle {
t.Errorf("Expected entryTitle %s, got %s", entryTitle, requestEntryTitle)
}
if _, ok := req["content"]; ok {
t.Errorf("Expected entryContent to be empty, got value")
}
if requestTags := req["tags"]; requestTags != tags {
t.Errorf("Expected tags %s, got %s", tags, requestTags)
} // Return success response
w.WriteHeader(http.StatusOK)
},
errContains: "",
},
{
name: "successful entry creation with content",
wantErr: false,
onlyURL: false,
username: "username",
password: "password",
clientID: "clientId",
clientSecret: "clientSecret",
tags: tags,
entryURL: entryURL,
entryTitle: entryTitle,
entryContent: entryContent,
serverResponse: func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/oauth/v2/token") {
// Return success response
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]any{
"access_token": "test-token",
"expires_in": 3600,
"refresh_token": "token",
"scope": "scope",
"token_type": "token_type",
})
return
}
// Verify authorization header
auth := r.Header.Get("Authorization")
if auth != "Bearer test-token" {
t.Errorf("Expected Authorization header 'Bearer test-token', got %s", auth)
}
// Verify content type
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
t.Errorf("Expected Content-Type 'application/json', got %s", contentType)
}
// Parse and verify request
body, _ := io.ReadAll(r.Body)
var req map[string]any
if err := json.Unmarshal(body, &req); err != nil {
t.Errorf("Failed to parse request body: %v", err)
}
if requstEntryURL := req["url"]; requstEntryURL != entryURL {
t.Errorf("Expected entryURL %s, got %s", entryURL, requstEntryURL)
}
if requestEntryTitle := req["title"]; requestEntryTitle != entryTitle {
t.Errorf("Expected entryTitle %s, got %s", entryTitle, requestEntryTitle)
}
if requestEntryContent := req["content"]; requestEntryContent != entryContent {
t.Errorf("Expected entryContent %s, got %s", entryContent, requestEntryContent)
}
if requestTags := req["tags"]; requestTags != tags {
t.Errorf("Expected tags %s, got %s", tags, requestTags)
} // Return success response
w.WriteHeader(http.StatusOK)
},
errContains: "",
},
{
name: "failed when unable to decode accessToken response",
wantErr: true,
onlyURL: true,
username: "username",
password: "password",
clientID: "clientId",
clientSecret: "clientSecret",
tags: tags,
entryURL: entryURL,
entryTitle: entryTitle,
entryContent: entryContent,
serverResponse: func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/oauth/v2/token") {
// Return success response
w.WriteHeader(http.StatusOK)
w.Write([]byte("invalid json"))
return
}
t.Error("Server should not be called when failed to get accessToken")
},
errContains: "unable to decode token response",
},
{
name: "failed when saving entry",
wantErr: true,
onlyURL: true,
username: "username",
password: "password",
clientID: "clientId",
clientSecret: "clientSecret",
tags: tags,
entryURL: entryURL,
entryTitle: entryTitle,
entryContent: entryContent,
serverResponse: func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/oauth/v2/token") {
// Return success response
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]any{
"access_token": "test-token",
"expires_in": 3600,
"refresh_token": "token",
"scope": "scope",
"token_type": "token_type",
})
return
}
w.WriteHeader(http.StatusUnauthorized)
},
errContains: "unable to get save entry",
},
{
name: "failure due to no accessToken",
wantErr: true,
onlyURL: false,
username: "username",
password: "password",
clientID: "clientId",
clientSecret: "clientSecret",
tags: tags,
entryURL: entryURL,
entryTitle: entryTitle,
entryContent: entryContent,
serverResponse: func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/oauth/v2/token") {
// Return error response
w.WriteHeader(http.StatusUnauthorized)
return
}
t.Error("Server should not be called when failed to get accessToken")
},
errContains: "unable to get access token",
},
{
name: "failure due to missing client parameters",
wantErr: true,
onlyURL: false,
tags: tags,
entryURL: entryURL,
entryTitle: entryTitle,
entryContent: entryContent,
serverResponse: func(w http.ResponseWriter, r *http.Request) {
t.Error("Server should not be called when failed to get accessToken")
},
errContains: "wallabag: missing base URL, client ID, client secret, username or password",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create test server if we have a server response function
var serverURL string
if tt.serverResponse != nil {
server := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
defer server.Close()
serverURL = server.URL
}
// Create client with test server URL
client := NewClient(serverURL, tt.clientID, tt.clientSecret, tt.username, tt.password, tt.tags, tt.onlyURL)
// Call CreateBookmark
err := client.CreateEntry(tt.entryURL, tt.entryTitle, tt.entryContent)
// Check error expectations
if tt.wantErr {
if err == nil {
t.Errorf("Expected error but got none")
} else if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("Expected error containing '%s', got '%s'", tt.errContains, err.Error())
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
})
}
}
func TestNewClient(t *testing.T) {
tests := []struct {
name string
baseURL string
clientID string
clientSecret string
username string
password string
tags string
onlyURL bool
}{
{
name: "with all parameters",
baseURL: "https://wallabag.example.com",
clientID: "clientID",
clientSecret: "clientSecret",
username: "wallabag",
password: "wallabag",
tags: "",
onlyURL: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := NewClient(tt.baseURL, tt.clientID, tt.clientSecret, tt.username, tt.password, tt.tags, tt.onlyURL)
if client.baseURL != tt.baseURL {
t.Errorf("Expected.baseURL %s, got %s", tt.baseURL, client.baseURL)
}
if client.username != tt.username {
t.Errorf("Expected username %s, got %s", tt.username, client.username)
}
if client.password != tt.password {
t.Errorf("Expected password %s, got %s", tt.password, client.password)
}
if client.clientID != tt.clientID {
t.Errorf("Expected clientID %s, got %s", tt.clientID, client.clientID)
}
if client.clientSecret != tt.clientSecret {
t.Errorf("Expected clientSecret %s, got %s", tt.clientSecret, client.clientSecret)
}
if client.tags != tt.tags {
t.Errorf("Expected tags %s, got %s", tt.tags, client.tags)
}
if client.onlyURL != tt.onlyURL {
t.Errorf("Expected onlyURL %v, got %v", tt.onlyURL, client.onlyURL)
}
})
}
}
v2-2.2.13/internal/integration/webhook/ 0000775 0000000 0000000 00000000000 15062123773 0017731 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/integration/webhook/webhook.go 0000664 0000000 0000000 00000013522 15062123773 0021721 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package webhook // import "miniflux.app/v2/internal/integration/webhook"
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"miniflux.app/v2/internal/crypto"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/version"
)
const (
defaultClientTimeout = 10 * time.Second
NewEntriesEventType = "new_entries"
SaveEntryEventType = "save_entry"
)
type Client struct {
webhookURL string
webhookSecret string
}
func NewClient(webhookURL, webhookSecret string) *Client {
return &Client{webhookURL, webhookSecret}
}
func (c *Client) SendSaveEntryWebhookEvent(entry *model.Entry) error {
return c.makeRequest(SaveEntryEventType, &WebhookSaveEntryEvent{
EventType: SaveEntryEventType,
Entry: &WebhookEntry{
ID: entry.ID,
UserID: entry.UserID,
FeedID: entry.FeedID,
Status: entry.Status,
Hash: entry.Hash,
Title: entry.Title,
URL: entry.URL,
CommentsURL: entry.CommentsURL,
Date: entry.Date,
CreatedAt: entry.CreatedAt,
ChangedAt: entry.ChangedAt,
Content: entry.Content,
Author: entry.Author,
ShareCode: entry.ShareCode,
Starred: entry.Starred,
ReadingTime: entry.ReadingTime,
Enclosures: entry.Enclosures,
Tags: entry.Tags,
Feed: &WebhookFeed{
ID: entry.Feed.ID,
UserID: entry.Feed.UserID,
CategoryID: entry.Feed.Category.ID,
Category: &WebhookCategory{ID: entry.Feed.Category.ID, Title: entry.Feed.Category.Title},
FeedURL: entry.Feed.FeedURL,
SiteURL: entry.Feed.SiteURL,
Title: entry.Feed.Title,
CheckedAt: entry.Feed.CheckedAt,
},
},
})
}
func (c *Client) SendNewEntriesWebhookEvent(feed *model.Feed, entries model.Entries) error {
if len(entries) == 0 {
return nil
}
var webhookEntries []*WebhookEntry
for _, entry := range entries {
webhookEntries = append(webhookEntries, &WebhookEntry{
ID: entry.ID,
UserID: entry.UserID,
FeedID: entry.FeedID,
Status: entry.Status,
Hash: entry.Hash,
Title: entry.Title,
URL: entry.URL,
CommentsURL: entry.CommentsURL,
Date: entry.Date,
CreatedAt: entry.CreatedAt,
ChangedAt: entry.ChangedAt,
Content: entry.Content,
Author: entry.Author,
ShareCode: entry.ShareCode,
Starred: entry.Starred,
ReadingTime: entry.ReadingTime,
Enclosures: entry.Enclosures,
Tags: entry.Tags,
})
}
return c.makeRequest(NewEntriesEventType, &WebhookNewEntriesEvent{
EventType: NewEntriesEventType,
Feed: &WebhookFeed{
ID: feed.ID,
UserID: feed.UserID,
CategoryID: feed.Category.ID,
Category: &WebhookCategory{ID: feed.Category.ID, Title: feed.Category.Title},
FeedURL: feed.FeedURL,
SiteURL: feed.SiteURL,
Title: feed.Title,
CheckedAt: feed.CheckedAt,
},
Entries: webhookEntries,
})
}
func (c *Client) makeRequest(eventType string, payload any) error {
if c.webhookURL == "" {
return fmt.Errorf(`webhook: missing webhook URL`)
}
requestBody, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("webhook: unable to encode request body: %v", err)
}
request, err := http.NewRequest(http.MethodPost, c.webhookURL, bytes.NewReader(requestBody))
if err != nil {
return fmt.Errorf("webhook: unable to create request: %v", err)
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "Miniflux/"+version.Version)
request.Header.Set("X-Miniflux-Signature", crypto.GenerateSHA256Hmac(c.webhookSecret, requestBody))
request.Header.Set("X-Miniflux-Event-Type", eventType)
httpClient := &http.Client{Timeout: defaultClientTimeout}
response, err := httpClient.Do(request)
if err != nil {
return fmt.Errorf("webhook: unable to send request: %v", err)
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("webhook: incorrect response status code %d for url %s", response.StatusCode, c.webhookURL)
}
return nil
}
type WebhookFeed struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
CategoryID int64 `json:"category_id"`
Category *WebhookCategory `json:"category,omitempty"`
FeedURL string `json:"feed_url"`
SiteURL string `json:"site_url"`
Title string `json:"title"`
CheckedAt time.Time `json:"checked_at"`
}
type WebhookCategory struct {
ID int64 `json:"id"`
Title string `json:"title"`
}
type WebhookEntry struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
FeedID int64 `json:"feed_id"`
Status string `json:"status"`
Hash string `json:"hash"`
Title string `json:"title"`
URL string `json:"url"`
CommentsURL string `json:"comments_url"`
Date time.Time `json:"published_at"`
CreatedAt time.Time `json:"created_at"`
ChangedAt time.Time `json:"changed_at"`
Content string `json:"content"`
Author string `json:"author"`
ShareCode string `json:"share_code"`
Starred bool `json:"starred"`
ReadingTime int `json:"reading_time"`
Enclosures model.EnclosureList `json:"enclosures"`
Tags []string `json:"tags"`
Feed *WebhookFeed `json:"feed,omitempty"`
}
type WebhookNewEntriesEvent struct {
EventType string `json:"event_type"`
Feed *WebhookFeed `json:"feed"`
Entries []*WebhookEntry `json:"entries"`
}
type WebhookSaveEntryEvent struct {
EventType string `json:"event_type"`
Entry *WebhookEntry `json:"entry"`
}
v2-2.2.13/internal/locale/ 0000775 0000000 0000000 00000000000 15062123773 0015207 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/locale/catalog.go 0000664 0000000 0000000 00000004250 15062123773 0017151 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package locale // import "miniflux.app/v2/internal/locale"
import (
"embed"
"encoding/json"
"fmt"
)
type translationDict struct {
singulars map[string]string
plurals map[string][]string
}
type catalog map[string]translationDict
var defaultCatalog = make(catalog, len(AvailableLanguages))
//go:embed translations/*.json
var translationFiles embed.FS
func getTranslationDict(language string) (translationDict, error) {
if _, ok := defaultCatalog[language]; !ok {
var err error
if defaultCatalog[language], err = loadTranslationFile(language); err != nil {
return translationDict{}, err
}
}
return defaultCatalog[language], nil
}
func loadTranslationFile(language string) (translationDict, error) {
translationFileData, err := translationFiles.ReadFile("translations/" + language + ".json")
if err != nil {
return translationDict{}, err
}
translationMessages, err := parseTranslationMessages(translationFileData)
if err != nil {
return translationDict{}, err
}
return translationMessages, nil
}
func (t *translationDict) UnmarshalJSON(data []byte) error {
var tmpMap map[string]any
err := json.Unmarshal(data, &tmpMap)
if err != nil {
return err
}
m := translationDict{
singulars: make(map[string]string),
plurals: make(map[string][]string),
}
for key, value := range tmpMap {
switch vtype := value.(type) {
case string:
m.singulars[key] = vtype
case []any:
for _, translation := range vtype {
if translationStr, ok := translation.(string); ok {
m.plurals[key] = append(m.plurals[key], translationStr)
} else {
return fmt.Errorf("invalid type for translation in an array: %v", translation)
}
}
default:
return fmt.Errorf("invalid type (%T) for translation: %v", vtype, value)
}
}
*t = m
return nil
}
func parseTranslationMessages(data []byte) (translationDict, error) {
var translationMessages translationDict
if err := json.Unmarshal(data, &translationMessages); err != nil {
return translationDict{}, fmt.Errorf(`invalid translation file: %w`, err)
}
return translationMessages, nil
}
v2-2.2.13/internal/locale/catalog_test.go 0000664 0000000 0000000 00000007105 15062123773 0020212 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package locale // import "miniflux.app/v2/internal/locale"
import (
"testing"
)
func TestParserWithInvalidData(t *testing.T) {
_, err := parseTranslationMessages([]byte(`{`))
if err == nil {
t.Fatal(`An error should be returned when parsing invalid data`)
}
}
func TestParser(t *testing.T) {
translations, err := parseTranslationMessages([]byte(`{"k": "v"}`))
if err != nil {
t.Fatalf(`Unexpected parsing error: %v`, err)
}
value, found := translations.singulars["k"]
if !found {
t.Fatalf(`The translation %v should contains the defined key`, translations.singulars)
}
if value != "v" {
t.Fatal(`The translation key should contains the defined value`)
}
}
func TestLoadCatalog(t *testing.T) {
for language := range AvailableLanguages {
_, err := loadTranslationFile(language)
if err != nil {
t.Fatal(err)
}
}
}
func TestAllKeysHaveValue(t *testing.T) {
for language := range AvailableLanguages {
messages, err := loadTranslationFile(language)
if err != nil {
t.Fatalf(`Unable to load translation messages for language %q`, language)
}
if len(messages.singulars) == 0 {
t.Fatalf(`The language %q doesn't have any messages for singulars`, language)
}
if len(messages.plurals) == 0 {
t.Fatalf(`The language %q doesn't have any messages for plurals`, language)
}
for k, v := range messages.singulars {
if len(v) == 0 {
t.Errorf(`The key %q for singulars for the language %q has an empty list as value`, k, language)
}
}
for k, v := range messages.plurals {
if len(v) == 0 {
t.Errorf(`The key %q for plurals for the language %q has an empty list as value`, k, language)
}
}
}
}
func TestMissingTranslations(t *testing.T) {
refLang := "en_US"
references, err := loadTranslationFile(refLang)
if err != nil {
t.Fatal(`Unable to parse reference language`)
}
for language := range AvailableLanguages {
if language == refLang {
continue
}
messages, err := loadTranslationFile(language)
if err != nil {
t.Fatalf(`Parsing error for language %q`, language)
}
for key := range references.singulars {
if _, found := messages.singulars[key]; !found {
t.Errorf(`Translation key %q not found in language %q singulars`, key, language)
}
}
for key := range references.plurals {
if _, found := messages.plurals[key]; !found {
t.Errorf(`Translation key %q not found in language %q plurals`, key, language)
}
}
}
}
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,
"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,
"uk_UA": 3,
"zh_CN": 1,
"zh_TW": 1,
}
for language := range AvailableLanguages {
messages, err := loadTranslationFile(language)
if err != nil {
t.Fatalf(`Unable to load translation messages for language %q`, language)
}
for k, v := range messages.plurals {
if len(v) != numberOfPluralFormsPerLanguage[language] {
t.Errorf(`The key %q for the language %q does not have the expected number of plurals, got %d instead of %d`, k, language, len(v), numberOfPluralFormsPerLanguage[language])
}
}
}
}
v2-2.2.13/internal/locale/error.go 0000664 0000000 0000000 00000002646 15062123773 0016677 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package locale // import "miniflux.app/v2/internal/locale"
import "errors"
type LocalizedErrorWrapper struct {
originalErr error
translationKey string
translationArgs []any
}
func NewLocalizedErrorWrapper(originalErr error, translationKey string, translationArgs ...any) *LocalizedErrorWrapper {
return &LocalizedErrorWrapper{
originalErr: originalErr,
translationKey: translationKey,
translationArgs: translationArgs,
}
}
func (l *LocalizedErrorWrapper) Error() error {
return l.originalErr
}
func (l *LocalizedErrorWrapper) Translate(language string) string {
if l.translationKey == "" {
return l.originalErr.Error()
}
return NewPrinter(language).Printf(l.translationKey, l.translationArgs...)
}
type LocalizedError struct {
translationKey string
translationArgs []any
}
func NewLocalizedError(translationKey string, translationArgs ...any) *LocalizedError {
return &LocalizedError{translationKey: translationKey, translationArgs: translationArgs}
}
func (v *LocalizedError) String() string {
return NewPrinter("en_US").Printf(v.translationKey, v.translationArgs...)
}
func (v *LocalizedError) Error() error {
return errors.New(v.String())
}
func (v *LocalizedError) Translate(language string) string {
return NewPrinter(language).Printf(v.translationKey, v.translationArgs...)
}
v2-2.2.13/internal/locale/error_test.go 0000664 0000000 0000000 00000020666 15062123773 0017740 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package locale // import "miniflux.app/v2/internal/locale"
import (
"errors"
"testing"
)
func TestNewLocalizedErrorWrapper(t *testing.T) {
originalErr := errors.New("original error message")
translationKey := "error.test_key"
args := []any{"arg1", 42}
wrapper := NewLocalizedErrorWrapper(originalErr, translationKey, args...)
if wrapper.originalErr != originalErr {
t.Errorf("Expected original error to be %v, got %v", originalErr, wrapper.originalErr)
}
if wrapper.translationKey != translationKey {
t.Errorf("Expected translation key to be %q, got %q", translationKey, wrapper.translationKey)
}
if len(wrapper.translationArgs) != 2 {
t.Errorf("Expected 2 translation args, got %d", len(wrapper.translationArgs))
}
if wrapper.translationArgs[0] != "arg1" || wrapper.translationArgs[1] != 42 {
t.Errorf("Expected translation args [arg1, 42], got %v", wrapper.translationArgs)
}
}
func TestLocalizedErrorWrapper_Error(t *testing.T) {
originalErr := errors.New("original error message")
wrapper := NewLocalizedErrorWrapper(originalErr, "error.test_key")
result := wrapper.Error()
if result != originalErr {
t.Errorf("Expected Error() to return original error %v, got %v", originalErr, result)
}
}
func TestLocalizedErrorWrapper_Translate(t *testing.T) {
// Set up test catalog
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.test_key": "Error: %s (code: %d)",
},
},
"fr_FR": translationDict{
singulars: map[string]string{
"error.test_key": "Erreur : %s (code : %d)",
},
},
}
originalErr := errors.New("original error")
wrapper := NewLocalizedErrorWrapper(originalErr, "error.test_key", "test message", 404)
// Test English translation
result := wrapper.Translate("en_US")
expected := "Error: test message (code: 404)"
if result != expected {
t.Errorf("Expected English translation %q, got %q", expected, result)
}
// Test French translation
result = wrapper.Translate("fr_FR")
expected = "Erreur : test message (code : 404)"
if result != expected {
t.Errorf("Expected French translation %q, got %q", expected, result)
}
// Test with missing language (should use key as fallback with args applied)
result = wrapper.Translate("invalid_lang")
expected = "error.test_key%!(EXTRA string=test message, int=404)"
if result != expected {
t.Errorf("Expected fallback translation %q, got %q", expected, result)
}
}
func TestLocalizedErrorWrapper_TranslateWithEmptyKey(t *testing.T) {
originalErr := errors.New("original error message")
wrapper := NewLocalizedErrorWrapper(originalErr, "")
result := wrapper.Translate("en_US")
expected := "original error message"
if result != expected {
t.Errorf("Expected original error message %q, got %q", expected, result)
}
}
func TestLocalizedErrorWrapper_TranslateWithNoArgs(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.simple": "Simple error message",
},
},
}
originalErr := errors.New("original error")
wrapper := NewLocalizedErrorWrapper(originalErr, "error.simple")
result := wrapper.Translate("en_US")
expected := "Simple error message"
if result != expected {
t.Errorf("Expected translation %q, got %q", expected, result)
}
}
func TestNewLocalizedError(t *testing.T) {
translationKey := "error.validation"
args := []any{"field1", "invalid"}
localizedErr := NewLocalizedError(translationKey, args...)
if localizedErr.translationKey != translationKey {
t.Errorf("Expected translation key to be %q, got %q", translationKey, localizedErr.translationKey)
}
if len(localizedErr.translationArgs) != 2 {
t.Errorf("Expected 2 translation args, got %d", len(localizedErr.translationArgs))
}
if localizedErr.translationArgs[0] != "field1" || localizedErr.translationArgs[1] != "invalid" {
t.Errorf("Expected translation args [field1, invalid], got %v", localizedErr.translationArgs)
}
}
func TestLocalizedError_String(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.validation": "Validation failed for %s: %s",
},
},
}
localizedErr := NewLocalizedError("error.validation", "username", "too short")
result := localizedErr.String()
expected := "Validation failed for username: too short"
if result != expected {
t.Errorf("Expected String() result %q, got %q", expected, result)
}
}
func TestLocalizedError_StringWithMissingTranslation(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{},
}
localizedErr := NewLocalizedError("error.missing", "arg1")
result := localizedErr.String()
expected := "error.missing%!(EXTRA string=arg1)"
if result != expected {
t.Errorf("Expected String() result %q, got %q", expected, result)
}
}
func TestLocalizedError_Error(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.database": "Database connection failed: %s",
},
},
}
localizedErr := NewLocalizedError("error.database", "timeout")
result := localizedErr.Error()
if result == nil {
t.Error("Expected Error() to return a non-nil error")
}
expected := "Database connection failed: timeout"
if result.Error() != expected {
t.Errorf("Expected Error() message %q, got %q", expected, result.Error())
}
}
func TestLocalizedError_Translate(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.permission": "Permission denied for %s",
},
},
"es_ES": translationDict{
singulars: map[string]string{
"error.permission": "Permiso denegado para %s",
},
},
}
localizedErr := NewLocalizedError("error.permission", "admin panel")
// Test English translation
result := localizedErr.Translate("en_US")
expected := "Permission denied for admin panel"
if result != expected {
t.Errorf("Expected English translation %q, got %q", expected, result)
}
// Test Spanish translation
result = localizedErr.Translate("es_ES")
expected = "Permiso denegado para admin panel"
if result != expected {
t.Errorf("Expected Spanish translation %q, got %q", expected, result)
}
// Test with missing language
result = localizedErr.Translate("invalid_lang")
expected = "error.permission%!(EXTRA string=admin panel)"
if result != expected {
t.Errorf("Expected fallback translation %q, got %q", expected, result)
}
}
func TestLocalizedError_TranslateWithNoArgs(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.generic": "An error occurred",
},
},
"de_DE": translationDict{
singulars: map[string]string{
"error.generic": "Ein Fehler ist aufgetreten",
},
},
}
localizedErr := NewLocalizedError("error.generic")
// Test English
result := localizedErr.Translate("en_US")
expected := "An error occurred"
if result != expected {
t.Errorf("Expected English translation %q, got %q", expected, result)
}
// Test German
result = localizedErr.Translate("de_DE")
expected = "Ein Fehler ist aufgetreten"
if result != expected {
t.Errorf("Expected German translation %q, got %q", expected, result)
}
}
func TestLocalizedError_TranslateWithComplexArgs(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.complex": "Error %d: %s occurred at %s with severity %s",
},
},
}
localizedErr := NewLocalizedError("error.complex", 500, "Internal Server Error", "2024-01-01", "high")
result := localizedErr.Translate("en_US")
expected := "Error 500: Internal Server Error occurred at 2024-01-01 with severity high"
if result != expected {
t.Errorf("Expected complex translation %q, got %q", expected, result)
}
}
func TestLocalizedErrorWrapper_WithNilError(t *testing.T) {
// This tests edge case behavior - what happens with nil error
wrapper := NewLocalizedErrorWrapper(nil, "error.test")
// Error() should return nil
result := wrapper.Error()
if result != nil {
t.Errorf("Expected Error() to return nil, got %v", result)
}
}
func TestLocalizedError_EmptyKey(t *testing.T) {
localizedErr := NewLocalizedError("")
result := localizedErr.String()
expected := ""
if result != expected {
t.Errorf("Expected empty string for empty key, got %q", result)
}
result = localizedErr.Translate("en_US")
if result != expected {
t.Errorf("Expected empty string for empty key translation, got %q", result)
}
}
v2-2.2.13/internal/locale/locale.go 0000664 0000000 0000000 00000001763 15062123773 0017004 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
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",
"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",
"uk_UA": "Українська",
"zh_CN": "简体中文",
"zh_TW": "繁體中文",
}
v2-2.2.13/internal/locale/locale_test.go 0000664 0000000 0000000 00000001042 15062123773 0020031 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package locale // import "miniflux.app/v2/internal/locale"
import "testing"
func TestAvailableLanguages(t *testing.T) {
results := AvailableLanguages
for k, v := range results {
if k == "" {
t.Errorf(`Empty language key detected`)
}
if v == "" {
t.Errorf(`Empty language value detected`)
}
}
if _, found := results["en_US"]; !found {
t.Errorf(`We must have at least the default language (en_US)`)
}
}
v2-2.2.13/internal/locale/plural.go 0000664 0000000 0000000 00000002560 15062123773 0017040 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package locale // import "miniflux.app/v2/internal/locale"
// See https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html
// And http://www.unicode.org/cldr/charts/29/supplemental/language_plural_rules.html
func getPluralForm(lang string, n int) int {
switch lang {
case "ar_AR":
switch {
case n == 0:
return 0
case n == 1:
return 1
case n == 2:
return 2
case n%100 >= 3 && n%100 <= 10:
return 3
case n%100 >= 11:
return 4
default:
return 5
}
case "cs_CZ":
switch {
case n == 1:
return 0
case n >= 2 && n <= 4:
return 1
default:
return 2
}
case "id_ID", "ja_JP":
return 0
case "pl_PL":
switch {
case n == 1:
return 0
case n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20):
return 1
default:
return 2
}
case "ro_RO":
switch {
case n == 1:
return 0
case n == 0 || (n%100 > 0 && n%100 < 20):
return 1
default:
return 2
}
case "ru_RU", "uk_UA", "sr_RS":
switch {
case n%10 == 1 && n%100 != 11:
return 0
case n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20):
return 1
default:
return 2
}
case "zh_CN", "zh_TW", "nan_Latn_pehoeji":
return 0
default: // includes fr_FR, pr_BR, tr_TR
if n > 1 {
return 1
}
return 0
}
}
v2-2.2.13/internal/locale/plural_test.go 0000664 0000000 0000000 00000012104 15062123773 0020072 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package locale // import "miniflux.app/v2/internal/locale"
import "testing"
func TestPluralRules(t *testing.T) {
scenarios := map[string]map[int]int{
// Default rule (covers fr_FR, pt_BR, tr_TR, and other unlisted languages)
"default": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
5: 1, // n > 1
},
// Arabic (ar_AR) - 6 forms
"ar_AR": {
0: 0, // n == 0
1: 1, // n == 1
2: 2, // n == 2
3: 3, // n%100 >= 3 && n%100 <= 10
5: 3, // n%100 >= 3 && n%100 <= 10
10: 3, // n%100 >= 3 && n%100 <= 10
11: 4, // n%100 >= 11
15: 4, // n%100 >= 11
99: 4, // n%100 >= 11
100: 5, // default case (n%100 == 0, doesn't match any condition)
101: 5, // default case (n%100 == 1, but n != 1)
200: 5, // default case
},
// Czech (cs_CZ) - 3 forms
"cs_CZ": {
1: 0, // n == 1
2: 1, // n >= 2 && n <= 4
3: 1, // n >= 2 && n <= 4
4: 1, // n >= 2 && n <= 4
5: 2, // default case
},
// French (fr_FR) - uses default rule
"fr_FR": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
5: 1, // n > 1
},
// Indonesian (id_ID) - always form 0
"id_ID": {
0: 0,
1: 0,
5: 0,
100: 0,
},
// Japanese (ja_JP) - always form 0
"ja_JP": {
0: 0,
1: 0,
2: 0,
5: 0,
100: 0,
},
// Polish (pl_PL) - 3 forms
"pl_PL": {
1: 0, // n == 1
2: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)
3: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)
4: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)
5: 2, // default case
10: 2, // default case (n%100 < 10, but n%10 not in 2-4)
11: 2, // default case (n%100 >= 10 and < 20)
12: 2, // default case (n%100 >= 10 and < 20)
22: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 >= 20)
24: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 >= 20)
},
// Portuguese Brazilian (pt_BR) - uses default rule
"pt_BR": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
5: 1, // n > 1
},
// Romanian (ro_RO) - 3 forms
"ro_RO": {
0: 1, // n == 0 || (n%100 > 0 && n%100 < 20)
1: 0, // n == 1
2: 1, // n == 0 || (n%100 > 0 && n%100 < 20)
5: 1, // n == 0 || (n%100 > 0 && n%100 < 20)
19: 1, // n == 0 || (n%100 > 0 && n%100 < 20)
20: 2, // default case
21: 2, // default case
100: 2, // default case (n%100 == 0, so condition fails)
101: 1, // n%100 == 1, so n%100 > 0 && n%100 < 20
},
// Russian (ru_RU) - 3 forms
"ru_RU": {
1: 0, // n%10 == 1 && n%100 != 11
2: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)
3: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)
4: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)
5: 2, // default case
11: 2, // n%10 == 1 but n%100 == 11, so default case
12: 2, // default case
21: 0, // n%10 == 1 && n%100 != 11
22: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 >= 20)
},
// Serbian (sr_RS) - same as Russian
"sr_RS": {
1: 0, // n%10 == 1 && n%100 != 11
2: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)
5: 2, // default case
11: 2, // n%10 == 1 but n%100 == 11, so default case
21: 0, // n%10 == 1 && n%100 != 11
},
// Turkish (tr_TR) - uses default rule
"tr_TR": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
5: 1, // n > 1
},
// Ukrainian (uk_UA) - same as Russian
"uk_UA": {
1: 0, // n%10 == 1 && n%100 != 11
2: 1, // n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)
5: 2, // default case
11: 2, // n%10 == 1 but n%100 == 11, so default case
21: 0, // n%10 == 1 && n%100 != 11
},
// Chinese Simplified (zh_CN) - always form 0
"zh_CN": {
0: 0,
1: 0,
5: 0,
100: 0,
},
// Chinese Traditional (zh_TW) - always form 0
"zh_TW": {
0: 0,
1: 0,
5: 0,
100: 0,
},
// Min Nan (nan_Latn_pehoeji) - always form 0
"nan_Latn_pehoeji": {
0: 0,
1: 0,
5: 0,
100: 0,
},
// Additional languages from AvailableLanguages that use default rule
"de_DE": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
},
"el_EL": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
},
"en_US": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
},
"es_ES": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
},
"fi_FI": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
},
"hi_IN": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
},
"it_IT": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
},
"nl_NL": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
},
// Test a language not in the switch (should use default rule)
"unknown_language": {
0: 0, // n <= 1
1: 0, // n <= 1
2: 1, // n > 1
},
}
for rule, values := range scenarios {
for input, expected := range values {
result := getPluralForm(rule, input)
if result != expected {
t.Errorf(`Unexpected result for %q rule, got %d instead of %d for %d as input`, rule, result, expected, input)
}
}
}
}
v2-2.2.13/internal/locale/printer.go 0000664 0000000 0000000 00000002307 15062123773 0017223 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package locale // import "miniflux.app/v2/internal/locale"
import "fmt"
// Printer converts translation keys to language-specific strings.
type Printer struct {
language string
}
// NewPrinter creates a new Printer instance for the given language.
func NewPrinter(language string) *Printer {
return &Printer{language}
}
func (p *Printer) Print(key string) string {
if dict, err := getTranslationDict(p.language); err == nil {
if str, ok := dict.singulars[key]; ok {
return str
}
}
return key
}
// Printf is like fmt.Printf, but using language-specific formatting.
func (p *Printer) Printf(key string, args ...any) string {
return fmt.Sprintf(p.Print(key), args...)
}
// Plural returns the translation of the given key by using the language plural form.
func (p *Printer) Plural(key string, n int, args ...any) string {
dict, err := getTranslationDict(p.language)
if err != nil {
return key
}
if choices, found := dict.plurals[key]; found {
index := getPluralForm(p.language, n)
if len(choices) > index {
return fmt.Sprintf(choices[index], args...)
}
}
return key
}
v2-2.2.13/internal/locale/printer_test.go 0000664 0000000 0000000 00000021610 15062123773 0020260 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package locale // import "miniflux.app/v2/internal/locale"
import "testing"
func TestPrintfWithMissingLanguage(t *testing.T) {
defaultCatalog = catalog{}
translation := NewPrinter("invalid").Printf("missing.key")
if translation != "missing.key" {
t.Errorf(`Wrong translation, got %q`, translation)
}
}
func TestPrintfWithMissingKey(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"k": "v",
},
},
}
translation := NewPrinter("en_US").Printf("missing.key")
if translation != "missing.key" {
t.Errorf(`Wrong translation, got %q`, translation)
}
}
func TestPrintfWithExistingKey(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"auth.username": "Login",
},
},
}
translation := NewPrinter("en_US").Printf("auth.username")
if translation != "Login" {
t.Errorf(`Wrong translation, got %q`, translation)
}
}
func TestPrintfWithExistingKeyAndPlaceholder(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"key": "Test: %s",
},
},
"fr_FR": translationDict{
singulars: map[string]string{
"key": "Test : %s",
},
},
}
translation := NewPrinter("fr_FR").Printf("key", "ok")
if translation != "Test : ok" {
t.Errorf(`Wrong translation, got %q`, translation)
}
}
func TestPrintfWithMissingKeyAndPlaceholder(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"auth.username": "Login",
},
},
"fr_FR": translationDict{
singulars: map[string]string{
"auth.username": "Identifiant",
},
},
}
translation := NewPrinter("fr_FR").Printf("Status: %s", "ok")
if translation != "Status: ok" {
t.Errorf(`Wrong translation, got %q`, translation)
}
}
func TestPrintWithMissingLanguage(t *testing.T) {
defaultCatalog = catalog{}
translation := NewPrinter("invalid").Print("missing.key")
if translation != "missing.key" {
t.Errorf(`Wrong translation, got %q`, translation)
}
}
func TestPrintWithMissingKey(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"existing.key": "value",
},
},
}
translation := NewPrinter("en_US").Print("missing.key")
if translation != "missing.key" {
t.Errorf(`Wrong translation, got %q`, translation)
}
}
func TestPrintWithExistingKey(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"auth.username": "Login",
},
},
}
translation := NewPrinter("en_US").Print("auth.username")
if translation != "Login" {
t.Errorf(`Wrong translation, got %q`, translation)
}
}
func TestPrintWithDifferentLanguages(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"greeting": "Hello",
},
},
"fr_FR": translationDict{
singulars: map[string]string{
"greeting": "Bonjour",
},
},
"es_ES": translationDict{
singulars: map[string]string{
"greeting": "Hola",
},
},
}
tests := []struct {
language string
expected string
}{
{"en_US", "Hello"},
{"fr_FR", "Bonjour"},
{"es_ES", "Hola"},
}
for _, test := range tests {
translation := NewPrinter(test.language).Print("greeting")
if translation != test.expected {
t.Errorf(`Wrong translation for %s, got %q instead of %q`, test.language, translation, test.expected)
}
}
}
func TestPrintWithEmptyKey(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"": "empty key translation",
},
},
}
translation := NewPrinter("en_US").Print("")
if translation != "empty key translation" {
t.Errorf(`Wrong translation for empty key, got %q`, translation)
}
}
func TestPrintWithEmptyTranslation(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"empty.value": "",
},
},
}
translation := NewPrinter("en_US").Print("empty.value")
if translation != "" {
t.Errorf(`Wrong translation for empty value, got %q`, translation)
}
}
func TestPluralWithDefaultRule(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
plurals: map[string][]string{
"number_of_users": {"%d user (%s)", "%d users (%s)"},
},
},
"fr_FR": translationDict{
plurals: map[string][]string{
"number_of_users": {"%d utilisateur (%s)", "%d utilisateurs (%s)"},
},
},
}
printer := NewPrinter("fr_FR")
translation := printer.Plural("number_of_users", 1, 1, "some text")
expected := "1 utilisateur (some text)"
if translation != expected {
t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected)
}
translation = printer.Plural("number_of_users", 2, 2, "some text")
expected = "2 utilisateurs (some text)"
if translation != expected {
t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected)
}
}
func TestPluralWithRussianRule(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
plurals: map[string][]string{
"time_elapsed.years": {"%d year", "%d years"},
},
},
"ru_RU": translationDict{
plurals: map[string][]string{
"time_elapsed.years": {"%d год назад", "%d года назад", "%d лет назад"},
},
},
}
printer := NewPrinter("ru_RU")
translation := printer.Plural("time_elapsed.years", 1, 1)
expected := "1 год назад"
if translation != expected {
t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected)
}
translation = printer.Plural("time_elapsed.years", 2, 2)
expected = "2 года назад"
if translation != expected {
t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected)
}
translation = printer.Plural("time_elapsed.years", 5, 5)
expected = "5 лет назад"
if translation != expected {
t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected)
}
}
func TestPluralWithMissingTranslation(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
plurals: map[string][]string{
"number_of_users": {"%d user (%s)", "%d users (%s)"},
},
},
"fr_FR": translationDict{},
}
translation := NewPrinter("fr_FR").Plural("number_of_users", 2)
expected := "number_of_users"
if translation != expected {
t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected)
}
}
func TestPluralWithMissingLanguage(t *testing.T) {
defaultCatalog = catalog{}
translation := NewPrinter("invalid_language").Plural("test.key", 2)
expected := "test.key"
if translation != expected {
t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected)
}
}
func TestPluralWithIndexOutOfBounds(t *testing.T) {
defaultCatalog = catalog{
"test_lang": translationDict{
plurals: map[string][]string{
"limited.key": {"only one form"},
},
},
}
// Force a scenario where getPluralForm might return an index >= len(plurals)
// We'll create a scenario with Czech language rules
defaultCatalog["cs_CZ"] = translationDict{
plurals: map[string][]string{
"limited.key": {"one form only"}, // Only one form, but Czech has 3 plural forms
},
}
printer := NewPrinter("cs_CZ")
// n=5 should return index 2 for Czech, but we only have 1 form (index 0)
translation := printer.Plural("limited.key", 5)
expected := "limited.key"
if translation != expected {
t.Errorf(`Wrong translation for out of bounds index, got %q instead of %q`, translation, expected)
}
}
func TestPluralWithVariousLanguageRules(t *testing.T) {
defaultCatalog = catalog{
"ar_AR": translationDict{
plurals: map[string][]string{
"items": {"no items", "one item", "two items", "few items", "many items", "other items"},
},
},
"pl_PL": translationDict{
plurals: map[string][]string{
"files": {"one file", "few files", "many files"},
},
},
"ja_JP": translationDict{
plurals: map[string][]string{
"photos": {"photos"},
},
},
}
tests := []struct {
language string
key string
n int
expected string
}{
// Arabic tests
{"ar_AR", "items", 0, "no items"},
{"ar_AR", "items", 1, "one item"},
{"ar_AR", "items", 2, "two items"},
{"ar_AR", "items", 5, "few items"}, // n%100 >= 3 && n%100 <= 10
{"ar_AR", "items", 15, "many items"}, // n%100 >= 11
// Polish tests
{"pl_PL", "files", 1, "one file"},
{"pl_PL", "files", 3, "few files"}, // n%10 >= 2 && n%10 <= 4
{"pl_PL", "files", 5, "many files"}, // default case
// Japanese tests (always uses same form)
{"ja_JP", "photos", 1, "photos"},
{"ja_JP", "photos", 10, "photos"},
}
for _, test := range tests {
printer := NewPrinter(test.language)
translation := printer.Plural(test.key, test.n)
if translation != test.expected {
t.Errorf(`Wrong translation for %s with n=%d, got %q instead of %q`,
test.language, test.n, translation, test.expected)
}
}
}
v2-2.2.13/internal/locale/translations/ 0000775 0000000 0000000 00000000000 15062123773 0017730 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/locale/translations/de_DE.json 0000664 0000000 0000000 00000116320 15062123773 0021566 0 ustar 00root root 0000000 0000000 {
"action.cancel": "abbrechen",
"action.download": "Herunterladen",
"action.edit": "Bearbeiten",
"action.home_screen": "Zum Startbildschirm hinzufügen",
"action.import": "Importieren",
"action.login": "Anmelden",
"action.or": "oder",
"action.remove": "Entfernen",
"action.remove_feed": "Dieses Abonnement entfernen",
"action.save": "Speichern",
"action.subscribe": "Abonnieren",
"action.update": "Aktualisieren",
"alert.account_linked": "Ihr externes Konto wurde verknüpft!",
"alert.account_unlinked": "Ihr externer Account ist jetzt getrennt!",
"alert.background_feed_refresh": "Alle Abonnements werden derzeit im Hintergrund aktualisiert. Sie können Miniflux weiterhin benutzen, während dieser Prozess ausgeführt wird.",
"alert.feed_error": "Es gibt ein Problem mit diesem Abonnement",
"alert.no_starred": "Es existieren derzeit keine markierten Artikel.",
"alert.no_category": "Es ist keine Kategorie vorhanden.",
"alert.no_category_entry": "Es befindet sich kein Artikel in dieser Kategorie.",
"alert.no_feed": "Es sind keine Abonnements vorhanden.",
"alert.no_feed_entry": "Es existiert kein Artikel für dieses Abonnement.",
"alert.no_feed_in_category": "Für diese Kategorie gibt es kein Abonnement.",
"alert.no_history": "Es existiert zur Zeit kein Verlauf.",
"alert.no_search_result": "Es gibt kein Ergebnis für diese Suche.",
"alert.no_shared_entry": "Es existieren derzeit keine geteilten Artikel.",
"alert.no_tag_entry": "Es gibt keine Artikel, die diesem Tag entsprechen.",
"alert.no_unread_entry": "Es existiert kein ungelesener Artikel.",
"alert.no_user": "Sie sind der einzige Benutzer.",
"alert.prefs_saved": "Einstellungen gespeichert!",
"alert.too_many_feeds_refresh": [
"Sie haben zu viele Aktualisierungen ausgelöst. Bitte warten Sie %d Minute, bevor Sie es erneut versuchen.",
"Sie haben zu viele Aktualisierungen ausgelöst. Bitte warten Sie %d Minuten, bevor Sie es erneut versuchen."
],
"confirm.loading": "In Arbeit...",
"confirm.no": "nein",
"confirm.question": "Sind Sie sicher?",
"confirm.question.refresh": "Möchten Sie eine erzwungene Aktualisierung durchführen?",
"confirm.yes": "ja",
"enclosure_media_controls.seek": "Vorspulen:",
"enclosure_media_controls.seek.title": "%s Sekunden vorspulen",
"enclosure_media_controls.speed": "Geschwindigkeit:",
"enclosure_media_controls.speed.faster": "Schneller",
"enclosure_media_controls.speed.faster.title": "%sx schneller",
"enclosure_media_controls.speed.reset": "Zurücksetzen",
"enclosure_media_controls.speed.reset.title": "Wiedergabegeschwindigkeit auf 1x zurücksetzen",
"enclosure_media_controls.speed.slower": "Langsamer",
"enclosure_media_controls.speed.slower.title": "%sx langsamer",
"entry.starred.toast.off": "Nicht markiert",
"entry.starred.toast.on": "Markiert",
"entry.starred.toggle.off": "Markierung entfernen",
"entry.starred.toggle.on": "Markierung hinzufügen",
"entry.comments.label": "Kommentare",
"entry.comments.title": "Kommentare anzeigen",
"entry.estimated_reading_time": [
"%d Minute zu lesen",
"%d Minuten zu lesen"
],
"entry.external_link.label": "Externer Link",
"entry.save.completed": "Erledigt!",
"entry.save.label": "Speichern",
"entry.save.title": "Diesen Artikel speichern",
"entry.save.toast.completed": "Artikel gespeichert",
"entry.scraper.completed": "Erledigt!",
"entry.scraper.label": "Herunterladen",
"entry.scraper.title": "Inhalt herunterladen",
"entry.share.label": "Teilen",
"entry.share.title": "Diesen Artikel teilen",
"entry.shared_entry.label": "Teilen",
"entry.shared_entry.title": "Öffnen Sie den öffentlichen Link",
"entry.state.loading": "Lade...",
"entry.state.saving": "Speichern...",
"entry.status.mark_as_read": "Als gelesen markieren",
"entry.status.mark_as_unread": "Als ungelesen markieren",
"entry.status.title": "Status des Artikels ändern",
"entry.status.toast.read": "Als gelesen markiert",
"entry.status.toast.unread": "Als ungelesen markiert",
"entry.tags.label": "Stichworte:",
"entry.tags.more_tags_label": [
"Zeige %d weiteres Schlagwort",
"Zeige %d weitere Schlagwörter"
],
"entry.unshare.label": "Nicht teilen",
"error.api_key_already_exists": "Dieser API-Schlüssel ist bereits vorhanden.",
"error.bad_credentials": "Benutzername oder Passwort ungültig.",
"error.category_already_exists": "Diese Kategorie existiert bereits.",
"error.category_not_found": "Diese Kategorie existiert nicht oder gehört nicht zu diesem Benutzer.",
"error.database_error": "Datenbank-Fehler: %v.",
"error.different_passwords": "Passwörter stimmen nicht überein.",
"error.duplicate_fever_username": "Es existiert bereits jemand mit diesem Fever-Benutzernamen!",
"error.duplicate_googlereader_username": "Es existiert bereits jemand mit diesem Google-Reader-Benutzernamen!",
"error.duplicate_linked_account": "Es ist bereits jemand mit diesem Anbieter assoziiert!",
"error.duplicated_feed": "Dieses Abonnement existiert bereits.",
"error.empty_file": "Diese Datei ist leer.",
"error.entries_per_page_invalid": "Die Anzahl der Artikel pro Seite ist ungültig.",
"error.feed_already_exists": "Dieser Feed existiert bereits.",
"error.feed_category_not_found": "Diese Kategorie existiert nicht oder gehört nicht zu diesem Benutzer.",
"error.feed_format_not_detected": "Das Format des Abonnements kann nicht erkannt werden: %v.",
"error.feed_invalid_blocklist_rule": "Die Blockierregel ist ungültig.",
"error.feed_invalid_keeplist_rule": "Die Erlaubnisregel ist ungültig.",
"error.feed_mandatory_fields": "Die URL und die Kategorie sind obligatorisch.",
"error.feed_not_found": "Dieses Abonnement existiert nicht oder gehört nicht zu diesem Benutzer.",
"error.feed_title_not_empty": "Der Feed-Titel darf nicht leer sein.",
"error.feed_url_not_empty": "Der Feed-URL darf nicht leer sein.",
"error.fields_mandatory": "Alle Felder sind obligatorisch.",
"error.http_bad_gateway": "Die Webseite ist aufgrund eines Bad-Gateway-Fehlers derzeit nicht verfügbar. Das Problem liegt nicht bei Miniflux. Bitte versuchen Sie es später erneut.",
"error.http_body_read": "Der HTTP-Inhalt kann nicht gelesen werden: %v",
"error.http_client_error": "HTTP-Client-Fehler: %v.",
"error.http_empty_response": "Die HTTP-Antwort ist leer. Vielleicht versucht die Webseite, sich vor Bots zu schützen?",
"error.http_empty_response_body": "Der Inhalt der HTTP-Antwort ist leer.",
"error.http_forbidden": "Der Zugriff auf diese Webseite ist verboten. Vielleicht versucht die Webseite, sich vor Bots zu schützen?",
"error.http_gateway_timeout": "Die Webseite ist aufgrund eines Gateway-Timeout-Fehlers derzeit nicht verfügbar. Das Problem liegt nicht bei Miniflux. Bitte versuchen Sie es später erneut.",
"error.http_internal_server_error": "Die Webseite steht durch einen Server-Fehler derzeit nicht zur Verfügung. Versuchen Sie es bitte später erneut.",
"error.http_not_authorized": "Der Zugriff auf diese Website ist nicht erlaubt. Möglicherweise ist der Benutzername oder das Passwort falsch.",
"error.http_resource_not_found": "Die gewünschte Quelle wurde nicht gefunden. Bitte stellen Sie sicher, dass die URL korrekt ist.",
"error.http_response_too_large": "Die HTTP-Antwort ist zu groß. Sie könnten die Grenze für die Größe der HTTP-Antwort in den globalen Einstellungen erhöhen (benötigt einen Neustart des Servers)",
"error.http_service_unavailable": "Die Webseite ist aufgrund eines Internal-Server-Fehlers derzeit nicht verfügbar. Das Problem liegt nicht bei Miniflux. Bitte versuchen Sie es später erneut.",
"error.http_too_many_requests": "Miniflux hat zu viele Anfragen an diese Webseite gestellt. Bitte versuchen Sie es später erneut oder ändern Sie die Konfiguration der Anwendung.",
"error.http_unexpected_status_code": "Die Webseite ist aufgrund eines eines unerwarteten HTTP-Fehlers derzeit nicht verfügbar: %d. Das Problem liegt nicht bei Miniflux. Bitte versuchen Sie es später erneut.",
"error.invalid_categories_sorting_order": "Ungültige Kategorie-Sortierreihenfolge.",
"error.invalid_default_home_page": "Ungültige Standard-Startseite!",
"error.invalid_display_mode": "Progressive-Web-App- (PWA-)Anzeigemodus",
"error.invalid_entry_direction": "Ungültige Sortierreihenfolge.",
"error.invalid_entry_order": "Ungültige Sortierreihenfolge.",
"error.invalid_feed_proxy_url": "Ungültige Proxy-URL.",
"error.invalid_feed_url": "Ungültiger Feed-URL.",
"error.invalid_gesture_nav": "Ungültige Gestennavigation.",
"error.invalid_language": "Ungültige Sprache.",
"error.invalid_site_url": "Ungültiger Site-URL.",
"error.invalid_theme": "Ungültiges Thema.",
"error.invalid_timezone": "Ungültige Zeitzone.",
"error.network_operation": "Miniflux kann die Webseite aufgrund eines Netzwerk-Fehlers nicht erreichen: %v",
"error.network_timeout": "Die Webseite ist zu langsam und die Anfrage ist abgelaufen: %v.",
"error.password_min_length": "Wenigstens 6 Zeichen müssen genutzt werden.",
"error.proxy_url_not_empty": "Die Proxy-URL darf nicht leer sein.",
"error.settings_block_rule_fieldname_invalid": "Ungültige Blockierregel: Regel #%d hat keinen gültigen Feldnamen (Optionen: %s)",
"error.settings_block_rule_invalid_regex": "Ungültige Blockierregel: Das Muster für Regel #%d ist kein zulässiger regulärer Ausdruck",
"error.settings_block_rule_regex_required": "Ungültige Blockierregel: Regel #%d hat kein Muster",
"error.settings_block_rule_separator_required": "Ungültige Blockierregel: Das Muster für Regel #%d muss per '=' getrennt werden",
"error.settings_invalid_domain_list": "Ungültige Domainliste. Bitte geben Sie eine per Leerzeichen getrennte Liste von Domains an.",
"error.settings_keep_rule_fieldname_invalid": "Ungültige Erlaubnisregel: Regel #%d hat keinen gültigen Feldnamen (Optionen: %s)",
"error.settings_keep_rule_invalid_regex": "Ungültige Erlaubnisregel: Das Muster für Regel #%d ist kein zulässiger regulärer Ausdruck",
"error.settings_keep_rule_regex_required": "Ungültige Erlaubnisregel: Regel #%d hat kein Muster",
"error.settings_keep_rule_separator_required": "Ungültige Erlaubnisregel: Das Muster für Regel #%d muss per '=' getrennt werden",
"error.settings_mandatory_fields": "Die Felder für Benutzername, Thema, Sprache und Zeitzone sind obligatorisch.",
"error.settings_media_playback_rate_range": "Die Wiedergabegeschwindigkeit liegt außerhalb des Bereichs",
"error.settings_reading_speed_is_positive": "Die Lesegeschwindigkeiten müssen positive ganze Zahlen sein.",
"error.site_url_not_empty": "Der Site-URL darf nicht leer sein.",
"error.subscription_not_found": "Es wurden keine Abonnements gefunden.",
"error.title_required": "Der Titel ist obligatorisch.",
"error.tls_error": "TLS-Fehler: %q. Wenn Sie mögen, können Sie versuchen die TLS-Verifizierung in den Einstellungen des Abonnements zu deaktivieren.",
"error.unable_to_create_api_key": "Dieser API-Schlüssel kann nicht erstellt werden.",
"error.unable_to_create_category": "Diese Kategorie konnte nicht angelegt werden.",
"error.unable_to_create_user": "Dieser Benutzer kann nicht erstellt werden.",
"error.unable_to_detect_rssbridge": "Abonnement kann nicht durch RSS-Bridge erkannt werden: %v.",
"error.unable_to_parse_feed": "Dieses Abonnement kann nicht gelesen werden: %v.",
"error.unable_to_update_category": "Diese Kategorie konnte nicht aktualisiert werden.",
"error.unable_to_update_feed": "Dieses Abonnement konnte nicht aktualisiert werden.",
"error.unable_to_update_user": "Dieser Benutzer konnte nicht aktualisiert werden.",
"error.unlink_account_without_password": "Sie müssen ein Passwort festlegen, sonst können Sie sich nicht erneut anmelden.",
"error.user_already_exists": "Dieser Benutzer existiert bereits.",
"error.user_mandatory_fields": "Der Benutzername ist obligatorisch.",
"error.linktaco_missing_required_fields": "LinkTaco API Token und Organization Slug sind erforderlich.",
"form.api_key.label.description": "API-Schlüsselbezeichnung",
"form.category.hide_globally": "Artikel in der globalen Ungelesen-Liste ausblenden",
"form.category.label.title": "Titel",
"form.feed.fieldset.general": "Allgemein",
"form.feed.fieldset.integration": "Drittanbieter-Dienste",
"form.feed.fieldset.network_settings": "Netzwerkeinstellungen",
"form.feed.fieldset.rules": "Regeln",
"form.feed.label.allow_self_signed_certificates": "Erlaube selbstsignierte oder ungültige Zertifikate",
"form.feed.label.apprise_service_urls": "Kommaseparierte Liste der Apprise-Service-URLs",
"form.feed.label.block_filter_entry_rules": "Eintrags-Sperrregeln",
"form.feed.label.blocklist_rules": "Regex-basierte Sperrfilter",
"form.feed.label.category": "Kategorie",
"form.feed.label.cookie": "Cookies setzen",
"form.feed.label.crawler": "Originalinhalt herunterladen",
"form.feed.label.description": "Beschreibung",
"form.feed.label.disable_http2": "HTTP/2 deaktivieren, um Fingerprinting zu verhindern",
"form.feed.label.disabled": "Dieses Abonnement nicht aktualisieren",
"form.feed.label.feed_password": "Passwort des Abonnements",
"form.feed.label.feed_url": "URL des Abonnements",
"form.feed.label.feed_username": "Benutzername des Abonnements",
"form.feed.label.fetch_via_proxy": "Den auf Anwendungsebene konfigurierten Proxy verwenden",
"form.feed.label.hide_globally": "Artikel in der globalen Ungelesen-Liste ausblenden",
"form.feed.label.ignore_http_cache": "Ignoriere HTTP-Cache",
"form.feed.label.keep_filter_entry_rules": "Eintrags-Erlaubnisregeln",
"form.feed.label.keeplist_rules": "Regex-basierte Behalte-Filter",
"form.feed.label.no_media_player": "Kein Media-Player (Audio/Video)",
"form.feed.label.ntfy_activate": "Artikel zu ntfy pushen",
"form.feed.label.ntfy_default_priority": "Normale Ntfy-Priorität",
"form.feed.label.ntfy_high_priority": "Hohe Ntfy-Priorität",
"form.feed.label.ntfy_low_priority": "Niedrige Ntfy-Priorität",
"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.proxy_url": "Proxy-URL",
"form.feed.label.pushover_activate": "Artikel an pushover.net senden",
"form.feed.label.pushover_default_priority": "Pushover-Standardpriorität",
"form.feed.label.pushover_high_priority": "Hohe Pushoverpriorität",
"form.feed.label.pushover_low_priority": "Niedrige Pushoverpriorität",
"form.feed.label.pushover_max_priority": "Höchste Pushoverpriorität",
"form.feed.label.pushover_min_priority": "Niedrigste Pushoverpriorität",
"form.feed.label.pushover_priority": "Pushover-Nachrichtenpriorität",
"form.feed.label.rewrite_rules": "Inhalts-Umschreibregeln",
"form.feed.label.scraper_rules": "Extraktionsregeln",
"form.feed.label.site_url": "URL der Webseite",
"form.feed.label.title": "Titel",
"form.feed.label.urlrewrite_rules": "Umschreibregeln für URL",
"form.feed.label.user_agent": "Standardbenutzeragenten überschreiben",
"form.feed.label.webhook_url": "Webhook-URL überschreiben",
"form.import.label.file": "OPML-Datei",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Artikel zu Apprise pushen",
"form.integration.apprise_services_url": "Kommaseparierte Liste von Apprise-Dienst-URLs",
"form.integration.apprise_url": "Apprise-API-URL",
"form.integration.betula_activate": "Artikel in Betula speichern",
"form.integration.betula_token": "Betula-Token",
"form.integration.betula_url": "Betula-Server-URL",
"form.integration.cubox_activate": "Artikel in Cubox speichern",
"form.integration.cubox_api_link": "Cubox-API-Link",
"form.integration.discord_activate": "Artikel zu Discord pushen",
"form.integration.discord_webhook_link": "Discord-Webhook-URL",
"form.integration.espial_activate": "Artikel in Espial speichern",
"form.integration.espial_api_key": "Espial-API-Schlüssel",
"form.integration.espial_endpoint": "Espial-API-Endpunkt",
"form.integration.espial_tags": "Espial-Tags",
"form.integration.fever_activate": "Fever-API aktivieren",
"form.integration.fever_endpoint": "Fever-API-Endpunkt:",
"form.integration.fever_password": "Fever-Passwort",
"form.integration.fever_username": "Fever-Benutzername",
"form.integration.googlereader_activate": "Google-Reader-API aktivieren",
"form.integration.googlereader_endpoint": "Google-Reader-API-Endpunkt:",
"form.integration.googlereader_password": "Google-Reader-Passwort",
"form.integration.googlereader_username": "Google-Reader-Benutzername",
"form.integration.instapaper_activate": "Artikel in Instapaper speichern",
"form.integration.instapaper_password": "Instapaper-Passwort",
"form.integration.instapaper_username": "Instapaper-Benutzername",
"form.integration.karakeep_activate": "Artikel in Karakeep speichern",
"form.integration.karakeep_api_key": "Karakeep-API-Schlüssel",
"form.integration.karakeep_url": "Karakeep-API-Endpunkt",
"form.integration.linkace_activate": "Artikel in LinkAce speichern",
"form.integration.linkace_api_key": "LinkAce-API-Schlüssel",
"form.integration.linkace_check_disabled": "Linkprüfung deaktivieren",
"form.integration.linkace_endpoint": "LinkAce-API-Endpunkt",
"form.integration.linkace_is_private": "Link als privat markieren",
"form.integration.linkace_tags": "LinkAce-Tags",
"form.integration.linkding_activate": "Artikel in Linkding speichern",
"form.integration.linkding_api_key": "Linkding-API-Schlüssel",
"form.integration.linkding_bookmark": "Lesezeichen als ungelesen markieren",
"form.integration.linkding_endpoint": "Linkding-API-Endpunkt",
"form.integration.linkding_tags": "Linkding-Tags",
"form.integration.linktaco_activate": "Artikel in LinkTaco speichern",
"form.integration.linktaco_api_token": "LinkTaco-API-Token",
"form.integration.linktaco_api_token_hint": "Holen Sie sich Ihr persönliches Zugriffstoken unter",
"form.integration.linktaco_org_slug": "Organisationstitel",
"form.integration.linktaco_tags": "Tags (max. 10, kommagetrennt)",
"form.integration.linktaco_tags_hint": "Maximal 10 Tags, kommagetrennt",
"form.integration.linktaco_visibility": "Sichtbarkeit",
"form.integration.linktaco_visibility_public": "Öffentlich",
"form.integration.linktaco_visibility_private": "Privat",
"form.integration.linktaco_visibility_hint": "PRIVATE Sichtbarkeit erfordert ein kostenpflichtiges LinkTaco-Konto",
"form.integration.linkwarden_activate": "Artikel in Linkwarden speichern",
"form.integration.linkwarden_api_key": "Linkwarden-API-Schlüssel",
"form.integration.linkwarden_endpoint": "Linkwarden-Base-URL",
"form.integration.matrix_bot_activate": "Neue Artikel in Matrix übertragen",
"form.integration.matrix_bot_chat_id": "ID des Matrix-Raums",
"form.integration.matrix_bot_password": "Passwort für Matrix-Benutzer",
"form.integration.matrix_bot_url": "URL des Matrix-Servers",
"form.integration.matrix_bot_user": "Benutzername für Matrix",
"form.integration.notion_activate": "Artikel in Notion speichern",
"form.integration.notion_page_id": "Notion-Page-ID",
"form.integration.notion_token": "Notion-Geheimnis-Token",
"form.integration.ntfy_activate": "Artikel zu ntfy pushen",
"form.integration.ntfy_api_token": "Ntfy-API-Token (optional)",
"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 (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",
"form.integration.nunux_keeper_api_key": "Nunux-Keeper-API-Schlüssel",
"form.integration.nunux_keeper_endpoint": "Nunux-Keeper-API-Endpunkt",
"form.integration.omnivore_activate": "Artikel in Omnivore speichern",
"form.integration.omnivore_api_key": "Omnivore-API-Schlüssel",
"form.integration.omnivore_url": "Omnivore-API-Endpunkt",
"form.integration.pinboard_activate": "Artikel in Pinboard speichern",
"form.integration.pinboard_bookmark": "Lesezeichen als ungelesen markieren",
"form.integration.pinboard_tags": "Pinboard-Tags",
"form.integration.pinboard_token": "Pinboard-API-Token",
"form.integration.pushover_activate": "Artikel an Pushover senden",
"form.integration.pushover_device": "Pushovergerät (optional)",
"form.integration.pushover_prefix": "Pushover-URL-Präfix (optional)",
"form.integration.pushover_token": "Pushover-Anwendungs-API-Token",
"form.integration.pushover_user": "Pushover-Benutzerschlüssel",
"form.integration.raindrop_activate": "Artikel in Raindrop speichern",
"form.integration.raindrop_collection_id": "Sammlungs-ID",
"form.integration.raindrop_tags": "Tags (kommagetrennt)",
"form.integration.raindrop_token": "(Test-)Token",
"form.integration.readeck_activate": "Artikel in Readeck speichern",
"form.integration.readeck_api_key": "Readeck-API-Schlüssel",
"form.integration.readeck_endpoint": "Readeck-URL",
"form.integration.readeck_labels": "Readeck-Labels",
"form.integration.readeck_only_url": "Nur URL senden (anstelle des vollständigen Inhalts)",
"form.integration.readwise_activate": "Artikel in Readwise Reader speichern",
"form.integration.readwise_api_key": "Readwise-Reader-Zugangstoken",
"form.integration.readwise_api_key_link": "Erhalten Sie Ihren Readwise-Zugangstoken",
"form.integration.rssbridge_activate": "Beim Hinzufügen von Abonnements RSS-Bridge prüfen.",
"form.integration.rssbridge_token": "RSS-Bridge-Authentifizierungs-Token",
"form.integration.rssbridge_url": "RSS-Bridge-Server-URL",
"form.integration.shaarli_activate": "Artikel in Shaarli speichern",
"form.integration.shaarli_api_secret": "Shaarli-API-Geheimnis",
"form.integration.shaarli_endpoint": "Shaarli-URL",
"form.integration.shiori_activate": "Artikel in Shiori speichern",
"form.integration.shiori_endpoint": "Shiori-API-Endpunkt",
"form.integration.shiori_password": "Shiori-Passwort",
"form.integration.shiori_username": "Shiori-Benutzername",
"form.integration.slack_activate": "Artikel zu Slack pushen",
"form.integration.slack_webhook_link": "Slack-Webhook-URL",
"form.integration.telegram_bot_activate": "Schicken Sie neue Artikel in den Telegram-Chat",
"form.integration.telegram_bot_disable_buttons": "Schaltfächen deaktivieren",
"form.integration.telegram_bot_disable_notification": "Benachrichtigungen deaktivieren",
"form.integration.telegram_bot_disable_web_page_preview": "Webseiten-Vorschau deaktivieren",
"form.integration.telegram_bot_token": "Bot-Token",
"form.integration.telegram_chat_id": "Chat-ID",
"form.integration.telegram_topic_id": "Thema-ID",
"form.integration.wallabag_activate": "Artikel in Wallabag speichern",
"form.integration.wallabag_client_id": "Wallabag-Client-ID",
"form.integration.wallabag_client_secret": "Wallabag-Client-Geheimnis",
"form.integration.wallabag_endpoint": "Wallabag-Basis-URL",
"form.integration.wallabag_only_url": "Nur URL senden (anstelle des vollständigen Inhalts)",
"form.integration.wallabag_password": "Wallabag-Passwort",
"form.integration.wallabag_username": "Wallabag-Benutzername",
"form.integration.wallabag_tags": "Wallabag-Tags",
"form.integration.webhook_activate": "Webhooks aktivieren",
"form.integration.webhook_secret": "Webhook-Geheimnis",
"form.integration.webhook_url": "Standard-Webhook-URL",
"form.prefs.fieldset.application_settings": "Anwendungseinstellungen",
"form.prefs.fieldset.authentication_settings": "Authentifizierungseinstellungen",
"form.prefs.fieldset.global_feed_settings": "Globale Feedeinstellungen",
"form.prefs.fieldset.reader_settings": "Reader-Einstellungen",
"form.prefs.help.external_font_hosts": "Per Leerzeichen getrennte Liste externer Schriftarten-Hosts, die erlaubt werden sollen. Beispiel: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.always_open_external_links": "Artikel immer mit Öffnen der Links lesen",
"form.prefs.label.categories_sorting_order": "Kategorie-Sortierung",
"form.prefs.label.cjk_reading_speed": "Lesegeschwindigkeit für Chinesisch, Koreanisch und Japanisch (Zeichen pro Minute)",
"form.prefs.label.custom_css": "Benutzerdefiniertes CSS",
"form.prefs.label.custom_js": "Benutzerdefiniertes JavaScript",
"form.prefs.label.default_home_page": "Standard-Startseite",
"form.prefs.label.default_reading_speed": "Lesegeschwindigkeit für andere Sprachen (Wörter pro Minute)",
"form.prefs.label.display_mode": "Anzeigemodus der progressiven Web-Anwendung (PWA)",
"form.prefs.label.entries_per_page": "Artikel pro Seite",
"form.prefs.label.entry_order": "Artikel-Sortierspalte",
"form.prefs.label.entry_sorting": "Sortierung der Artikel",
"form.prefs.label.entry_swipe": "Aktivieren Sie das Wischen von Artikeln auf Touchscreens",
"form.prefs.label.external_font_hosts": "Externe Schriftarten-Hosts",
"form.prefs.label.gesture_nav": "Geste zum Navigieren zwischen Artikeln",
"form.prefs.label.keyboard_shortcuts": "Tastaturkürzel aktivieren",
"form.prefs.label.language": "Sprache",
"form.prefs.label.mark_read_manually": "Artikel manuell als gelesen markieren",
"form.prefs.label.mark_read_on_media_completion": "Nur als gelesen markieren, wenn Audio/Video zu 90%% wiedergegeben wurden",
"form.prefs.label.mark_read_on_view": "Artikel automatisch als gelesen markieren, wenn sie angezeigt werden",
"form.prefs.label.mark_read_on_view_or_media_completion": "Artikel automatisch als gelesen markieren, wenn sie angezeigt werden. Audio/Video bei 90%% Wiedergabe als gelesen markieren",
"form.prefs.label.media_playback_rate": "Wiedergabegeschwindigkeit von Audio/Video",
"form.prefs.label.open_external_links_in_new_tab": "Externe Links in einem neuen Tab öffnen (fügt target=\"_blank\" zu Links hinzu)",
"form.prefs.label.show_reading_time": "Geschätzte Lesezeit für Artikel anzeigen",
"form.prefs.label.theme": "Thema",
"form.prefs.label.timezone": "Zeitzone",
"form.prefs.select.alphabetical": "Alphabetisch",
"form.prefs.select.browser": "Browser",
"form.prefs.select.created_time": "Artikel erstellt am",
"form.prefs.select.fullscreen": "Vollbildschirm",
"form.prefs.select.minimal_ui": "Minimal",
"form.prefs.select.none": "Keine",
"form.prefs.select.older_first": "Ältere Artikel zuerst",
"form.prefs.select.publish_time": "Artikel veröffentlicht am",
"form.prefs.select.recent_first": "Neue Artikel zuerst",
"form.prefs.select.standalone": "Eigenständige",
"form.prefs.select.swipe": "Wischen",
"form.prefs.select.tap": "Doppeltippen",
"form.prefs.select.unread_count": "Ungelesen",
"form.submit.loading": "Lade...",
"form.submit.saving": "Speichern...",
"form.user.label.admin": "Administrator",
"form.user.label.confirmation": "Passwortbestätigung",
"form.user.label.password": "Passwort",
"form.user.label.username": "Benutzername",
"menu.about": "Über",
"menu.add_feed": "Abonnement hinzufügen",
"menu.add_user": "Benutzer anlegen",
"menu.api_keys": "API-Schlüssel",
"menu.categories": "Kategorien",
"menu.create_api_key": "Erstellen Sie einen neuen API-Schlüssel",
"menu.create_category": "Kategorie anlegen",
"menu.edit_category": "Bearbeiten",
"menu.edit_feed": "Bearbeiten",
"menu.export": "Exportieren",
"menu.feed_entries": "Artikel",
"menu.feeds": "Abonnements",
"menu.flush_history": "Verlauf leeren",
"menu.history": "Verlauf",
"menu.home_page": "Startseite",
"menu.import": "Importieren",
"menu.integrations": "Dienste",
"menu.logout": "Abmelden",
"menu.mark_all_as_read": "Alle als gelesen markieren",
"menu.mark_page_as_read": "Diese Seite als gelesen markieren",
"menu.preferences": "Einstellungen",
"menu.refresh_all_feeds": "Alle Abonnements im Hintergrund aktualisieren",
"menu.refresh_feed": "Aktualisieren",
"menu.search": "Suche",
"menu.sessions": "Sitzungen",
"menu.settings": "Einstellungen",
"menu.shared_entries": "Geteilte Artikel",
"menu.show_all_entries": "Zeige alle Artikel",
"menu.show_only_starred_entries": "Nur markierte Artikel anzeigen",
"menu.show_only_unread_entries": "Nur ungelesene Artikel anzeigen",
"menu.starred": "Markiert",
"menu.title": "Menü",
"menu.unread": "Ungelesen",
"menu.users": "Benutzer",
"page.about.author": "Autor:",
"page.about.build_date": "Datum der Kompilierung:",
"page.about.credits": "Urheberrechte",
"page.about.db_usage": "Datenbankgröße:",
"page.about.git_commit": "Git-Commit:",
"page.about.global_config_options": "Globale Konfigurationsoptionen",
"page.about.go_version": "Go-Version:",
"page.about.license": "Lizenz:",
"page.about.postgres_version": "Postgres-Version:",
"page.about.title": "Über",
"page.about.version": "Version:",
"page.add_feed.choose_feed": "Abonnement auswählen",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Erweiterte Optionen",
"page.add_feed.no_category": "Es ist keine Kategorie vorhanden. Wenigstens eine Kategorie muss angelegt sein.",
"page.add_feed.submit": "Abonnement finden",
"page.add_feed.title": "Neues Abonnement",
"page.api_keys.never_used": "Nie benutzt",
"page.api_keys.table.actions": "Aktionen",
"page.api_keys.table.created_at": "Erstellungsdatum",
"page.api_keys.table.description": "Beschreibung",
"page.api_keys.table.last_used_at": "Zuletzt verwendeten",
"page.api_keys.table.token": "Zeichen",
"page.api_keys.title": "API-Schlüssel",
"page.categories.entries": "Artikel",
"page.categories.feed_count": [
"Es gibt %d Abonnement.",
"Es gibt %d Abonnements."
],
"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:",
"page.edit_feed.last_check": "Letzte Aktualisierung:",
"page.edit_feed.last_modified_header": "Zuletzt geändert:",
"page.edit_feed.last_parsing_error": "Letzter Analysefehler",
"page.edit_feed.no_header": "Nicht verfügbar",
"page.edit_feed.title": "Abonnement bearbeiten: %s",
"page.edit_user.title": "Benutzer bearbeiten: %s",
"page.entry.attachments": "Anhänge",
"page.feeds.error_count": [
"%d Fehler",
"%d Fehler"
],
"page.feeds.last_check": "Letzte Aktualisierung:",
"page.feeds.next_check": "Nächste Aktualisierung:",
"page.feeds.read_counter": "Anzahl der gelesenen Artikel",
"page.feeds.title": "Abonnements",
"page.footer.elevator": "Zurück nach oben",
"page.history.title": "Verlauf",
"page.import.title": "Importieren",
"page.integration.bookmarklet": "Bookmarklet",
"page.integration.bookmarklet.help": "Dieser spezielle Link ermöglicht es, eine Webseite direkt über ein Lesezeichen im Browser zu abonnieren.",
"page.integration.bookmarklet.instructions": "Ziehen Sie diesen Link in Ihre Lesezeichen.",
"page.integration.bookmarklet.name": "Mit Miniflux abonnieren",
"page.integration.miniflux_api": "Miniflux-API",
"page.integration.miniflux_api_endpoint": "API-Endpunkt",
"page.integration.miniflux_api_password": "Passwort",
"page.integration.miniflux_api_password_value": "Ihr Konto-Passwort",
"page.integration.miniflux_api_username": "Benutzername",
"page.integrations.title": "Dienste",
"page.keyboard_shortcuts.close_modal": "Liste der Tastenkürzel schließen",
"page.keyboard_shortcuts.download_content": "Vollständigen Inhalt herunterladen",
"page.keyboard_shortcuts.go_to_bottom_item": "Gehen Sie zum untersten Element",
"page.keyboard_shortcuts.go_to_categories": "Zu den Kategorien gehen",
"page.keyboard_shortcuts.go_to_feed": "Zum Abonnement gehen",
"page.keyboard_shortcuts.go_to_feeds": "Zu den Abonnements gehen",
"page.keyboard_shortcuts.go_to_history": "Zum Verlauf gehen",
"page.keyboard_shortcuts.go_to_next_item": "Zum nächsten Artikel gehen",
"page.keyboard_shortcuts.go_to_next_page": "Zur nächsten Seite gehen",
"page.keyboard_shortcuts.go_to_previous_item": "Zum vorherigen Artikel gehen",
"page.keyboard_shortcuts.go_to_previous_page": "Zur vorherigen Seite gehen",
"page.keyboard_shortcuts.go_to_search": "Fokus auf das Suchformular setzen",
"page.keyboard_shortcuts.go_to_settings": "Zu den Einstellungen gehen",
"page.keyboard_shortcuts.go_to_starred": "Zu den markierten Artikeln gehen",
"page.keyboard_shortcuts.go_to_top_item": "Zum obersten Artikel gehen",
"page.keyboard_shortcuts.go_to_unread": "Zu den ungelesenen Artikeln gehen",
"page.keyboard_shortcuts.mark_page_as_read": "Aktuelle Seite als gelesen markieren",
"page.keyboard_shortcuts.open_comments": "Kommentare öffnen",
"page.keyboard_shortcuts.open_comments_same_window": "Öffne den Kommentare-Link in der aktuellen Registerkarte",
"page.keyboard_shortcuts.open_item": "Gewählten Artikel öffnen",
"page.keyboard_shortcuts.open_original": "Original-Artikel öffnen",
"page.keyboard_shortcuts.open_original_same_window": "Öffne den Original-Link in der aktuellen Registerkarte",
"page.keyboard_shortcuts.refresh_all_feeds": "Alle Abonnements im Hintergrund aktualisieren",
"page.keyboard_shortcuts.remove_feed": "Dieses Abonnement entfernen",
"page.keyboard_shortcuts.save_article": "Artikel speichern",
"page.keyboard_shortcuts.scroll_item_to_top": "Artikel an den Anfang blättern",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Liste der Tastenkürzel anzeigen",
"page.keyboard_shortcuts.subtitle.actions": "Aktionen",
"page.keyboard_shortcuts.subtitle.items": "Navigation zwischen den Artikeln",
"page.keyboard_shortcuts.subtitle.pages": "Navigation zwischen den Seiten",
"page.keyboard_shortcuts.subtitle.sections": "Navigation zwischen den Menüpunkten",
"page.keyboard_shortcuts.title": "Tastenkürzel",
"page.keyboard_shortcuts.toggle_star_status": "Markierung hinzufügen/entfernen",
"page.keyboard_shortcuts.toggle_entry_attachments": "Artikelanhänge öffnen/schließen",
"page.keyboard_shortcuts.toggle_read_status_next": "Gewählten Artikel als gelesen/ungelesen markieren, nächsten auswählen",
"page.keyboard_shortcuts.toggle_read_status_prev": "Gewählten Artikel als gelesen/ungelesen markieren, vorherigen auswählen",
"page.login.google_signin": "Anmeldung mit Google",
"page.login.oidc_signin": "Anmeldung mit %s",
"page.login.title": "Anmeldung",
"page.login.webauthn_login": "Melden Sie sich mit dem Passkey an",
"page.login.webauthn_login.error": "Anmeldung mit Passkey nicht möglich",
"page.login.webauthn_login.help": "Bitte geben Sie Ihren Benutzernamen ein, sofern Sie einen Sicherheitsschlüssel verwenden. Dies ist nicht nötig, wenn Sie einen Passkey verwenden (auffindbare Anmeldeinformationen).",
"page.new_api_key.title": "Neuer API-Schlüssel",
"page.new_category.title": "Neue Kategorie",
"page.new_user.title": "Neuer Benutzer",
"page.offline.message": "Sie sind offline",
"page.offline.refresh_page": "Versuchen Sie, die Seite zu aktualisieren",
"page.offline.title": "Offline-Modus",
"page.read_entry_count": [
"%d gelesener Artikel",
"%d gelesene Artikel"
],
"page.search.title": "Suchergebnisse",
"page.sessions.table.actions": "Aktionen",
"page.sessions.table.current_session": "Aktuelle Sitzung",
"page.sessions.table.date": "Datum",
"page.sessions.table.ip": "IP-Adresse",
"page.sessions.table.user_agent": "Benutzeragent",
"page.sessions.title": "Sitzungen",
"page.settings.link_google_account": "Google-Konto verknüpfen",
"page.settings.link_oidc_account": "%s-Konto verknüpfen",
"page.settings.title": "Einstellungen",
"page.settings.unlink_google_account": "Verknüpfung mit Google-Konto entfernen",
"page.settings.unlink_oidc_account": "Verknüpfung mit %s-Konto entfernen",
"page.settings.webauthn.actions": "Aktionen",
"page.settings.webauthn.added_on": "Hinzugefügt am",
"page.settings.webauthn.delete": [
"Entfernen Sie %d Hauptschlüssel",
"%d Hauptschlüssel entfernen"
],
"page.settings.webauthn.last_seen_on": "Zuletzt genutzt",
"page.settings.webauthn.passkey_name": "Name des Passkeys",
"page.settings.webauthn.passkeys": "Passkeys",
"page.settings.webauthn.register": "Hauptschlüssel registrieren",
"page.settings.webauthn.register.error": "Hauptschlüssel kann nicht registriert werden",
"page.shared_entries.title": "Geteilte Artikel",
"page.shared_entries_count": [
"%d geteilter Artikel",
"%d geteilte Artikel"
],
"page.starred.title": "Markiert",
"page.starred_entry_count": [
"%d markierter Artikel",
"%d markierte Artikel"
],
"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.users.actions": "Aktionen",
"page.users.admin.no": "Nein",
"page.users.admin.yes": "Ja",
"page.users.is_admin": "Administrator",
"page.users.last_login": "Letzte Anmeldung",
"page.users.never_logged": "Niemals",
"page.users.title": "Benutzer",
"page.users.username": "Benutzername",
"page.webauthn_rename.title": "Passkey umbenennen",
"pagination.first": "Erste",
"pagination.last": "Letzte",
"pagination.next": "Nächste",
"pagination.previous": "Vorherige",
"search.label": "Suche",
"search.placeholder": "Suche...",
"search.submit": "Suchen",
"skip_to_content": "Zum Inhalt springen",
"time_elapsed.days": [
"vor %d Tag",
"vor %d Tagen"
],
"time_elapsed.hours": [
"vor %d Stunde",
"vor %d Stunden"
],
"time_elapsed.minutes": [
"vor %d Minute",
"vor %d Minuten"
],
"time_elapsed.months": [
"vor %d Monat",
"vor %d Monaten"
],
"time_elapsed.not_yet": "noch nicht",
"time_elapsed.now": "gerade",
"time_elapsed.weeks": [
"vor %d Woche",
"vor %d Wochen"
],
"time_elapsed.years": [
"vor %d Jahr",
"vor %d Jahren"
],
"time_elapsed.yesterday": "gestern",
"tooltip.keyboard_shortcuts": "Tastenkürzel: %s",
"tooltip.logged_user": "Angemeldet als %s"
}
v2-2.2.13/internal/locale/translations/el_EL.json 0000664 0000000 0000000 00000153763 15062123773 0021622 0 ustar 00root root 0000000 0000000 {
"action.cancel": "ακύρωση",
"action.download": "Λήψη",
"action.edit": "Επεξεργασία",
"action.home_screen": "Προσθήκη στην αρχική οθόνη",
"action.import": "Εισαγωγή",
"action.login": "Σύνδεση",
"action.or": "ή",
"action.remove": "Κατάργηση",
"action.remove_feed": "Κατάργηση αυτής της ροής",
"action.save": "Αποθηκεύσετε",
"action.subscribe": "Εγγραφείτε",
"action.update": "Ενημέρωση",
"alert.account_linked": "Ο εξωτερικός σας λογαριασμός είναι πλέον συνδεδεμένος!",
"alert.account_unlinked": "Ο εξωτερικός σας λογαριασμός είναι πλέον αποσυνδεδεμένος!",
"alert.background_feed_refresh": "Όλες οι ροές ανανεώνονται στο παρασκήνιο. Μπορείτε να συνεχίσετε να χρησιμοποιείτε το Miniflux όσο εκτελείται αυτή η διαδικασία.",
"alert.feed_error": "Υπάρχει πρόβλημα με αυτήν τη ροή",
"alert.no_starred": "Δεν υπάρχει σελιδοδείκτης αυτή τη στιγμή.",
"alert.no_category": "Δεν υπάρχει κατηγορία.",
"alert.no_category_entry": "Δεν υπάρχουν άρθρα σε αυτήν την κατηγορία.",
"alert.no_feed": "Δεν έχετε συνδρομές.",
"alert.no_feed_entry": "Δεν υπάρχουν άρθρα για αυτήν τη ροή.",
"alert.no_feed_in_category": "Δεν υπάρχει συνδρομή για αυτήν την κατηγορία.",
"alert.no_history": "Δεν υπάρχει ιστορικό αυτή τη στιγμή.",
"alert.no_search_result": "Δεν υπάρχουν αποτελέσματα για αυτήν την αναζήτηση.",
"alert.no_shared_entry": "Δεν υπάρχει κοινόχρηστη καταχώρηση.",
"alert.no_tag_entry": "Δεν υπάρχουν αντικείμενα που να ταιριάζουν με αυτή την ετικέτα.",
"alert.no_unread_entry": "Δεν υπάρχουν μη αναγνωσμένα άρθρα.",
"alert.no_user": "Είστε ο μόνος χρήστης.",
"alert.prefs_saved": "Οι προτιμήσεις αποθηκεύτηκαν!",
"alert.too_many_feeds_refresh": [
"Έχετε ενεργοποιήσει πάρα πολλές ανανεώσεις ροών. Παρακαλώ περιμένετε %d λεπτό πριν προσπαθήσετε ξανά.",
"Έχετε ενεργοποιήσει πάρα πολλές ανανεώσεις ροών. Παρακαλώ περιμένετε %d λεπτά πριν προσπαθήσετε ξανά."
],
"confirm.loading": "Σε εξέλιξη...",
"confirm.no": "όχι",
"confirm.question": "Είστε σίγουροι;",
"confirm.question.refresh": "Θέλετε να επιτελέσετε μια υποχρεωτική ανανέωση;",
"confirm.yes": "ναι",
"enclosure_media_controls.seek": "Αναζήτηση:",
"enclosure_media_controls.seek.title": "Αναζήτηση %s δευτερόλεπτα",
"enclosure_media_controls.speed": "Ταχύτητα:",
"enclosure_media_controls.speed.faster": "Γρηγορότερα",
"enclosure_media_controls.speed.faster.title": "Γρηγορότερα κατά %sx",
"enclosure_media_controls.speed.reset": "Επαναφορά",
"enclosure_media_controls.speed.reset.title": "Επαναφορά ταχύτητας σε 1x",
"enclosure_media_controls.speed.slower": "Πιο αργά",
"enclosure_media_controls.speed.slower.title": "Πιο αργά κατά %sx",
"entry.starred.toast.off": "Μη αγαπημένα",
"entry.starred.toast.on": "Αγαπημένα",
"entry.starred.toggle.off": "Αναίρεση αγαπημένου",
"entry.starred.toggle.on": "Αγαπημένο",
"entry.comments.label": "Σχόλια",
"entry.comments.title": "Δείτε Σχόλια",
"entry.estimated_reading_time": [
"%d λεπτό ανάγνωση",
"%d λεπτά ανάγνωση"
],
"entry.external_link.label": "Εξωτερικός σύνδεσμος",
"entry.save.completed": "Έγινε!",
"entry.save.label": "Αποθηκεύσετε",
"entry.save.title": "Αποθηκεύστε αυτό το άρθρο",
"entry.save.toast.completed": "Το άρθρο αποθηκεύτηκε",
"entry.scraper.completed": "Έγινε!",
"entry.scraper.label": "Λήψη",
"entry.scraper.title": "Λήψη αρχικού περιεχομένου",
"entry.share.label": "Διαμοιρασμός",
"entry.share.title": "Μοιραστείτε αυτό το άρθρο",
"entry.shared_entry.label": "Διαμοιρασμός",
"entry.shared_entry.title": "Ανοίξτε τον δημόσιο σύνδεσμο",
"entry.state.loading": "Φόρτωση...",
"entry.state.saving": "Aποθήκευση...",
"entry.status.mark_as_read": "Επισήμανση ως αναγνωσμένο",
"entry.status.mark_as_unread": "Επισήμανση ως μη αναγνωσμένο",
"entry.status.title": "Αλλαγή κατάστασης καταχώρησης",
"entry.status.toast.read": "Επισήμανση ως αναγνωσμένο",
"entry.status.toast.unread": "Επισήμανση ως μη αναγνωσμένο",
"entry.tags.label": "Ετικέτες:",
"entry.tags.more_tags_label": [
"Εμφάνιση %d ακόμη ετικέτας",
"Εμφάνιση %d ακόμη ετικετών"
],
"entry.unshare.label": "Aναίρεση Διαμοιρασμού",
"error.api_key_already_exists": "Αυτό το κλειδί API υπάρχει ήδη.",
"error.bad_credentials": "Μη έγκυρο όνομα χρήστη ή κωδικό πρόσβασης.",
"error.category_already_exists": "Αυτή η κατηγορία υπάρχει ήδη.",
"error.category_not_found": "Αυτή η κατηγορία δεν υπάρχει ή δεν ανήκει σε αυτόν τον χρήστη.",
"error.database_error": "Σφάλμα βάσης δεδομένων: %v.",
"error.different_passwords": "Οι κωδικοί πρόσβασης δεν είναι οι ίδιοι.",
"error.duplicate_fever_username": "Υπάρχει ήδη κάποιος άλλος με το ίδιο όνομα χρήστη Fever!",
"error.duplicate_googlereader_username": "Υπάρχει ήδη κάποιος άλλος με το ίδιο όνομα χρήστη Google Reader!",
"error.duplicate_linked_account": "Υπάρχει ήδη κάποιος που σχετίζεται με αυτόν τον πάροχο!",
"error.duplicated_feed": "Αυτή η ροή υπάρχει ήδη.",
"error.empty_file": "Αυτό το αρχείο είναι κενό.",
"error.entries_per_page_invalid": "Ο αριθμός των καταχωρήσεων ανά σελίδα δεν είναι έγκυρος.",
"error.feed_already_exists": "Αυτή η ροή υπάρχει ήδη.",
"error.feed_category_not_found": "Αυτή η κατηγορία δεν υπάρχει ή δεν ανήκει σε αυτόν τον χρήστη.",
"error.feed_format_not_detected": "Δεν είναι δυνατή η ανίχνευση της μορφής ροής: %v.",
"error.feed_invalid_blocklist_rule": "Ο κανόνας λίστας μπλοκ δεν είναι έγκυρος.",
"error.feed_invalid_keeplist_rule": "Ο κανόνας keep list δεν είναι έγκυρος.",
"error.feed_mandatory_fields": "Η διεύθυνση URL και η κατηγορία είναι υποχρεωτικά.",
"error.feed_not_found": "Αυτή η ροή δεν υπάρχει ή δεν ανήκει σε αυτόν τον χρήστη.",
"error.feed_title_not_empty": "Ο τίτλος ροής δεν μπορεί να είναι κενός.",
"error.feed_url_not_empty": "Η διεύθυνση URL ροής δεν μπορεί να είναι κενή.",
"error.fields_mandatory": "Όλα τα πεδία είναι υποχρεωτικά.",
"error.http_bad_gateway": "Ο ιστότοπος δεν είναι διαθέσιμος αυτήν τη στιγμή λόγω σφάλματος κακής πύλης. Το πρόβλημα δεν είναι στην πλευρά του Miniflux. Παρακαλώ δοκιμάστε ξανά αργότερα.",
"error.http_body_read": "Δεν είναι δυνατή η ανάγνωση του σώματος HTTP: %v.",
"error.http_client_error": "Σφάλμα πελάτη HTTP: %v.",
"error.http_empty_response": "Η απάντηση HTTP είναι κενή. Ίσως αυτός ο ιστότοπος χρησιμοποιεί μηχανισμό προστασίας από bot;",
"error.http_empty_response_body": "Το σώμα απάντησης HTTP είναι κενό.",
"error.http_forbidden": "Η πρόσβαση σε αυτόν τον ιστότοπο απαγορεύεται. Ίσως αυτός ο ιστότοπος διαθέτει μηχανισμό προστασίας από bot;",
"error.http_gateway_timeout": "Ο ιστότοπος δεν είναι διαθέσιμος αυτήν τη στιγμή λόγω σφάλματος χρονικού ορίου πύλης. Το πρόβλημα δεν είναι στην πλευρά του Miniflux. Παρακαλώ δοκιμάστε ξανά αργότερα.",
"error.http_internal_server_error": "Ο ιστότοπος δεν είναι διαθέσιμος αυτήν τη στιγμή λόγω σφάλματος διακομιστή. Το πρόβλημα δεν είναι στην πλευρά του Miniflux. Παρακαλώ δοκιμάστε ξανά αργότερα.",
"error.http_not_authorized": "Η πρόσβαση σε αυτόν τον ιστότοπο δεν είναι εξουσιοδοτημένη. Μπορεί να είναι λανθασμένο όνομα χρήστη ή κωδικός πρόσβασης.",
"error.http_resource_not_found": "Ο ζητούμενος πόρος δεν βρέθηκε. Επαληθεύστε τη διεύθυνση URL.",
"error.http_response_too_large": "Η απάντηση HTTP είναι πολύ μεγάλη. Μπορείτε να αυξήσετε το όριο μεγέθους απάντησης HTTP στις καθολικές ρυθμίσεις (απαιτεί επανεκκίνηση του διακομιστή).",
"error.http_service_unavailable": "Ο ιστότοπος δεν είναι διαθέσιμος αυτήν τη στιγμή λόγω εσωτερικού σφάλματος διακομιστή. Το πρόβλημα δεν είναι στην πλευρά του Miniflux. Παρακαλώ δοκιμάστε ξανά αργότερα.",
"error.http_too_many_requests": "Το Miniflux δημιούργησε πάρα πολλά αιτήματα σε αυτόν τον ιστότοπο. Παρακαλώ δοκιμάστε ξανά αργότερα ή αλλάξτε τη διαμόρφωση της εφαρμογής.",
"error.http_unexpected_status_code": "Ο ιστότοπος δεν είναι διαθέσιμος αυτήν τη στιγμή λόγω μη αναμενόμενου κωδικού κατάστασης HTTP: %d. Το πρόβλημα δεν είναι στην πλευρά του Miniflux. Παρακαλώ δοκιμάστε ξανά αργότερα.",
"error.invalid_categories_sorting_order": "Η κατηγορία δεν μπορεί να είναι κενή.",
"error.invalid_default_home_page": "Μη έγκυρη προεπιλεγμένη αρχική σελίδα!",
"error.invalid_display_mode": "Μη έγκυρη λειτουργία εμφάνισης εφαρμογών ιστού.",
"error.invalid_entry_direction": "Μη έγκυρη κατεύθυνση ταξινόμησης άρθρων.",
"error.invalid_entry_order": "Η σειρά των καταχωρήσεων είναι μη έγκυρη.",
"error.invalid_feed_proxy_url": "Μη έγκυρη διεύθυνση URL διακομιστή μεσολάβησης.",
"error.invalid_feed_url": "Μη έγκυρη διεύθυνση URL ροής.",
"error.invalid_gesture_nav": "Μη έγκυρη πλοήγηση με χειρονομίες.",
"error.invalid_language": "Μη έγκυρη γλώσσα.",
"error.invalid_site_url": "Μη έγκυρη διεύθυνση URL ιστότοπου.",
"error.invalid_theme": "Μη έγκυρο θέμα.",
"error.invalid_timezone": "Μη έγκυρη ζώνη ώρας.",
"error.network_operation": "Το Miniflux δεν μπορεί να φτάσει σε αυτόν τον ιστότοπο λόγω σφάλματος δικτύου: %v.",
"error.network_timeout": "Αυτός ο ιστότοπος είναι πολύ αργός και το αίτημα έληξε: %v",
"error.password_min_length": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 6 χαρακτήρες.",
"error.proxy_url_not_empty": "Η διεύθυνση URL του διακομιστή μεσολάβησης δεν μπορεί να είναι κενή.",
"error.settings_block_rule_fieldname_invalid": "Μη έγκυρος κανόνας αποκλεισμού: ο κανόνας #%d λείπει ένα έγκυρο όνομα πεδίου (Επιλογές: %s)",
"error.settings_block_rule_invalid_regex": "Μη έγκυρος κανόνας αποκλεισμού: το μοτίβο του κανόνα #%d δεν είναι έγκυρη κανονική έκφραση",
"error.settings_block_rule_regex_required": "Μη έγκυρος κανόνας αποκλεισμού: το μοτίβο του κανόνα #%d δεν παρέχεται",
"error.settings_block_rule_separator_required": "Μη έγκυρος κανόνας αποκλεισμού: το μοτίβο του κανόνα #%d απαιτείται να διαχωρίζεται με ένα '='",
"error.settings_invalid_domain_list": "Μη έγκυρη λίστα τομέων. Παρακαλώ δώστε μια λίστα τομέων διαχωρισμένων με κενό.",
"error.settings_keep_rule_fieldname_invalid": "Μη έγκυρος κανόνας διατήρησης: ο κανόνας #%d λείπει ένα έγκυρο όνομα πεδίου (Επιλογές: %s)",
"error.settings_keep_rule_invalid_regex": "Μη έγκυρος κανόνας διατήρησης: το μοτίβο του κανόνα #%d δεν είναι έγκυρη κανονική έκφραση",
"error.settings_keep_rule_regex_required": "Μη έγκυρος κανόνας διατήρησης: το μοτίβο του κανόνα #%d δεν παρέχεται",
"error.settings_keep_rule_separator_required": "Μη έγκυρος κανόνας διατήρησης: το μοτίβο του κανόνα #%d απαιτείται να διαχωρίζεται με ένα '='",
"error.settings_mandatory_fields": "Τα πεδία όνομα χρήστη, θέμα, Γλώσσα και ζώνη ώρας είναι υποχρεωτικά.",
"error.settings_media_playback_rate_range": "Η ταχύτητα αναπαραγωγής είναι εκτός εύρους",
"error.settings_reading_speed_is_positive": "Οι ταχύτητες ανάγνωσης πρέπει να είναι θετικοί ακέραιοι αριθμοί.",
"error.site_url_not_empty": "Η διεύθυνση URL του ιστότοπου δεν μπορεί να είναι κενή.",
"error.subscription_not_found": "Δεν είναι δυνατή η εύρεση συνδρομής.",
"error.title_required": "Ο τίτλος είναι υποχρεωτικός.",
"error.tls_error": "Σφάλμα TLS: %q. Μπορείτε να απενεργοποιήσετε την επαλήθευση TLS στις ρυθμίσεις ροής εάν το επιθυμείτε.",
"error.unable_to_create_api_key": "Δεν είναι δυνατή η δημιουργία αυτού του κλειδιού API.",
"error.unable_to_create_category": "Δεν είναι δυνατή η δημιουργία αυτής της κατηγορίας.",
"error.unable_to_create_user": "Δεν είναι δυνατή η δημιουργία αυτού του χρήστη.",
"error.unable_to_detect_rssbridge": "Δεν είναι δυνατή η ανίχνευση ροής με χρήση RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Δεν είναι δυνατή η ανάλυση αυτής της ροής: %v.",
"error.unable_to_update_category": "Δεν είναι δυνατή η ενημέρωση αυτής της κατηγορίας.",
"error.unable_to_update_feed": "Δεν είναι δυνατή η ενημέρωση αυτής της ροής.",
"error.unable_to_update_user": "Δεν είναι δυνατή η ενημέρωση αυτού του χρήστη.",
"error.unlink_account_without_password": "Πρέπει να ορίσετε έναν κωδικό πρόσβασης διαφορετικά δεν θα μπορείτε να συνδεθείτε ξανά.",
"error.user_already_exists": "Αυτός ο χρήστης υπάρχει ήδη.",
"error.user_mandatory_fields": "Το όνομα χρήστη είναι υποχρεωτικό.",
"error.linktaco_missing_required_fields": "Το LinkTaco API Token και το Organization Slug είναι απαραίτητα",
"form.api_key.label.description": "Ετικέτα κλειδιού API",
"form.category.hide_globally": "Απόκρυψη καταχωρήσεων σε γενική λίστα μη αναγνωσμένων",
"form.category.label.title": "Τίτλος",
"form.feed.fieldset.general": "Γενικά",
"form.feed.fieldset.integration": "Υπηρεσίες τρίτων",
"form.feed.fieldset.network_settings": "Ρυθμίσεις δικτύου",
"form.feed.fieldset.rules": "Κανόνες",
"form.feed.label.allow_self_signed_certificates": "Να επιτρέπονται αυτο-υπογεγραμμένα ή μη έγκυρα πιστοποιητικά",
"form.feed.label.apprise_service_urls": "Λίστα διευθύνσεων URL υπηρεσιών Apprise διαχωρισμένων με κόμμα",
"form.feed.label.block_filter_entry_rules": "Κανόνες Αποκλεισμού Καταχωρήσεων",
"form.feed.label.blocklist_rules": "Φίλτρα Αποκλεισμού Βασισμένα σε Regex",
"form.feed.label.category": "Κατηγορία",
"form.feed.label.cookie": "Ορισμός Cookies",
"form.feed.label.crawler": "Λήψη αρχικού περιεχομένου",
"form.feed.label.description": "Περιγραφή",
"form.feed.label.disable_http2": "Απενεργοποίηση HTTP/2 για αποφυγή δακτυλικών αποτυπωμάτων",
"form.feed.label.disabled": "Μη ανανέωση αυτής της ροής",
"form.feed.label.feed_password": "Κωδικός Πρόσβασης ροής",
"form.feed.label.feed_url": "Διεύθυνση URL ροής",
"form.feed.label.feed_username": "Όνομα Χρήστη ροής",
"form.feed.label.fetch_via_proxy": "Χρησιμοποιήστε τον διακομιστή μεσολάβησης που έχει ρυθμιστεί σε επίπεδο εφαρμογής",
"form.feed.label.hide_globally": "Απόκρυψη καταχωρήσεων σε γενική λίστα μη αναγνωσμένων",
"form.feed.label.ignore_http_cache": "Αγνοήστε την προσωρινή μνήμη HTTP",
"form.feed.label.keep_filter_entry_rules": "Κανόνες Επιτρεπόμενων Καταχωρήσεων",
"form.feed.label.keeplist_rules": "Φίλτρα Διατήρησης Βασισμένα σε Regex",
"form.feed.label.no_media_player": "Χωρίς πρόγραμμα αναπαραγωγής πολυμέσων (ήχος/βίντεο)",
"form.feed.label.ntfy_activate": "Προώθηση καταχωρήσεων στο ntfy",
"form.feed.label.ntfy_default_priority": "Προεπιλεγμένη προτεραιότητα Ntfy",
"form.feed.label.ntfy_high_priority": "Υψηλή προτεραιότητα Ntfy",
"form.feed.label.ntfy_low_priority": "Χαμηλή προτεραιότητα Ntfy",
"form.feed.label.ntfy_max_priority": "Μέγιστη προτεραιότητα Ntfy",
"form.feed.label.ntfy_min_priority": "Ελάχιστη προτεραιότητα Ntfy",
"form.feed.label.ntfy_priority": "Προτεραιότητα Ntfy",
"form.feed.label.ntfy_topic": "Θέμα Ntfy (προαιρετικό)",
"form.feed.label.proxy_url": "Διεύθυνση URL διακομιστή μεσολάβησης",
"form.feed.label.pushover_activate": "Προώθηση καταχωρήσεων στο pushover.net",
"form.feed.label.pushover_default_priority": "Προεπιλεγμένη προτεραιότητα Pushover",
"form.feed.label.pushover_high_priority": "Υψηλή προτεραιότητα Pushover",
"form.feed.label.pushover_low_priority": "Χαμηλή προτεραιότητα Pushover",
"form.feed.label.pushover_max_priority": "Μέγιστη προτεραιότητα Pushover",
"form.feed.label.pushover_min_priority": "Ελάχιστη προτεραιότητα Pushover",
"form.feed.label.pushover_priority": "Προτεραιότητα μηνύματος Pushover",
"form.feed.label.rewrite_rules": "Κανόνες Επανασύνταξης Περιεχομένου",
"form.feed.label.scraper_rules": "Κανόνες Scraper",
"form.feed.label.site_url": "Διεύθυνση URL ιστότοπου",
"form.feed.label.title": "Τίτλος",
"form.feed.label.urlrewrite_rules": "κανόνες επανεγγραφής για τη διεύθυνση URL.",
"form.feed.label.user_agent": "Παράκαμψη Προεπιλεγμένου User Agent Χρήστη",
"form.feed.label.webhook_url": "Παράκαμψη διεύθυνσης URL webhook",
"form.import.label.file": "Αρχείο OPML",
"form.import.label.url": "Διεύθυνση URL",
"form.integration.apprise_activate": "Προώθηση καταχωρήσεων στο Apprise",
"form.integration.apprise_services_url": "Λίστα διευθύνσεων URL υπηρεσιών Apprise διαχωρισμένων με κόμμα",
"form.integration.apprise_url": "Διεύθυνση URL API Apprise",
"form.integration.betula_activate": "Αποθήκευση καταχωρήσεων στο Betula",
"form.integration.betula_token": "Διακριτικό Betula",
"form.integration.betula_url": "Διεύθυνση URL διακομιστή Betula",
"form.integration.cubox_activate": "Αποθήκευση καταχωρήσεων στο Cubox",
"form.integration.cubox_api_link": "Σύνδεσμος API Cubox",
"form.integration.discord_activate": "Προώθηση καταχωρήσεων στο Discord",
"form.integration.discord_webhook_link": "Σύνδεσμος Webhook Discord",
"form.integration.espial_activate": "Αποθήκευση άρθρων στο Espial",
"form.integration.espial_api_key": "Κλειδί API Espial",
"form.integration.espial_endpoint": "Τελικό σημείο Espial API",
"form.integration.espial_tags": "Ετικέτες Espial",
"form.integration.fever_activate": "Ενεργοποιήστε το Fever API",
"form.integration.fever_endpoint": "Τελικό σημείο Fever API:",
"form.integration.fever_password": "Κωδικός Πρόσβασης Fever",
"form.integration.fever_username": "Όνομα Χρήστη Fever",
"form.integration.googlereader_activate": "Ενεργοποιήστε το Google Reader API",
"form.integration.googlereader_endpoint": "Τελικό σημείο Google Reader API:",
"form.integration.googlereader_password": "Κωδικός Πρόσβασης Google Reader",
"form.integration.googlereader_username": "Όνομα Χρήστη Google Reader",
"form.integration.instapaper_activate": "Αποθήκευση άρθρων στο Instapaper",
"form.integration.instapaper_password": "Κωδικός Πρόσβασης Instapaper",
"form.integration.instapaper_username": "Όνομα Χρήστη Instapaper",
"form.integration.karakeep_activate": "Αποθήκευση άρθρων στο Karakeep",
"form.integration.karakeep_api_key": "Κλειδί API Karakeep",
"form.integration.karakeep_url": "Τελικό σημείο Karakeep API",
"form.integration.linkace_activate": "Αποθήκευση καταχωρήσεων στο LinkAce",
"form.integration.linkace_api_key": "Κλειδί API LinkAce",
"form.integration.linkace_check_disabled": "Απενεργοποίηση ελέγχου συνδέσμου",
"form.integration.linkace_endpoint": "Τελικό σημείο API LinkAce",
"form.integration.linkace_is_private": "Σήμανση συνδέσμου ως ιδιωτικού",
"form.integration.linkace_tags": "Ετικέτες LinkAce",
"form.integration.linkding_activate": "Αποθήκευση άρθρων στο Linkding",
"form.integration.linkding_api_key": "Κλειδί API Linkding",
"form.integration.linkding_bookmark": "Σημείωση του σελιδοδείκτη ως μη αναγνωσμένου",
"form.integration.linkding_endpoint": "Τελικό σημείο Linkding API",
"form.integration.linkding_tags": "Ετικέτες Linkding",
"form.integration.linktaco_activate": "Αποθήκευση καταχωρήσεων στο LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Λάβετε το προσωπικό σας διακριτικό πρόσβασης στο",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Ετικέτες (μέγιστο 10, διαχωρισμένες με κόμμα)",
"form.integration.linktaco_tags_hint": "Μέγιστο 10 ετικέτες, διαχωρισμένες με κόμμα",
"form.integration.linktaco_visibility": "Ορατότητα",
"form.integration.linktaco_visibility_public": "Δημόσια",
"form.integration.linktaco_visibility_private": "Ιδιωτική",
"form.integration.linktaco_visibility_hint": "Η ΙΔΙΩΤΙΚΗ ορατότητα απαιτεί επί πληρωμή λογαριασμό LinkTaco",
"form.integration.linkwarden_activate": "Αποθήκευση άρθρων στο Linkwarden",
"form.integration.linkwarden_api_key": "Κλειδί API Linkwarden",
"form.integration.linkwarden_endpoint": "URL βάσης Linkwarden",
"form.integration.matrix_bot_activate": "Μεταφορά νέων άρθρων στο Matrix",
"form.integration.matrix_bot_chat_id": "Αναγνωριστικό της αίθουσας Matrix",
"form.integration.matrix_bot_password": "Κωδικός πρόσβασης για τον χρήστη Matrix",
"form.integration.matrix_bot_url": "URL διακομιστή Matrix",
"form.integration.matrix_bot_user": "Όνομα χρήστη για το Matrix",
"form.integration.notion_activate": "Αποθήκευση καταχωρήσεων στο Notion",
"form.integration.notion_page_id": "Αναγνωριστικό σελίδας Notion",
"form.integration.notion_token": "Μυστικό διακριτικό Notion",
"form.integration.ntfy_activate": "Προώθηση καταχωρήσεων στο ntfy",
"form.integration.ntfy_api_token": "Διακριτικό API Ntfy (προαιρετικό)",
"form.integration.ntfy_icon_url": "Διεύθυνση URL εικονιδίου Ntfy (προαιρετικό)",
"form.integration.ntfy_internal_links": "Χρήση εσωτερικών συνδέσμων με κλικ (προαιρετικό)",
"form.integration.ntfy_password": "Κωδικός πρόσβασης Ntfy (προαιρετικό)",
"form.integration.ntfy_topic": "Θέμα Ntfy (προεπιλογή χρησιμοποιείται εάν δεν οριστεί στη ροή)",
"form.integration.ntfy_url": "Διεύθυνση URL Ntfy (προαιρετικό, προεπιλογή είναι ntfy.sh)",
"form.integration.ntfy_username": "Όνομα χρήστη Ntfy (προαιρετικό)",
"form.integration.nunux_keeper_activate": "Αποθήκευση άρθρων στο Nunux Keeper",
"form.integration.nunux_keeper_api_key": "Κλειδί API Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Τελικό σημείο Nunux Keeper API",
"form.integration.omnivore_activate": "Αποθήκευση άρθρων στο Omnivore",
"form.integration.omnivore_api_key": "Κλειδί API Omnivore",
"form.integration.omnivore_url": "Τελικό σημείο Omnivore API",
"form.integration.pinboard_activate": "Αποθήκευση άρθρων στο Pinboard",
"form.integration.pinboard_bookmark": "Σημείωση του σελιδοδείκτη ως μη αναγνωσμένου",
"form.integration.pinboard_tags": "Ετικέτες Pinboard",
"form.integration.pinboard_token": "Διακριτικό API Pinboard",
"form.integration.pushover_activate": "Προώθηση καταχωρήσεων στο Pushover",
"form.integration.pushover_device": "Συσκευή Pushover (προαιρετικό)",
"form.integration.pushover_prefix": "Πρόθεμα διεύθυνσης URL Pushover (προαιρετικό)",
"form.integration.pushover_token": "Διακριτικό API εφαρμογής Pushover",
"form.integration.pushover_user": "Κλειδί χρήστη Pushover",
"form.integration.raindrop_activate": "Αποθήκευση καταχωρήσεων στο Raindrop",
"form.integration.raindrop_collection_id": "Αναγνωριστικό συλλογής",
"form.integration.raindrop_tags": "Ετικέτες (διαχωρισμένες με κόμμα)",
"form.integration.raindrop_token": "Διακριτικό (Δοκιμή)",
"form.integration.readeck_activate": "Αποθήκευση άρθρων στο Readeck",
"form.integration.readeck_api_key": "Κλειδί API Readeck",
"form.integration.readeck_endpoint": "Τελικό σημείο Readeck API",
"form.integration.readeck_labels": "Ετικέτες Readeck",
"form.integration.readeck_only_url": "Αποστολή μόνο URL (αντί για πλήρες περιεχόμενο)",
"form.integration.readwise_activate": "Αποθήκευση καταχωρήσεων στο Readwise Reader",
"form.integration.readwise_api_key": "Διακριτικό πρόσβασης Readwise Reader",
"form.integration.readwise_api_key_link": "Λήψη του διακριτικού πρόσβασης Readwise",
"form.integration.rssbridge_activate": "Έλεγχος RSS-Bridge κατά την προσθήκη συνδρομών",
"form.integration.rssbridge_token": "Διακριτικό ελέγχου ταυτότητας RSS-Bridge",
"form.integration.rssbridge_url": "Διεύθυνση URL διακομιστή RSS-Bridge",
"form.integration.shaarli_activate": "Αποθήκευση άρθρων στο Shaarli",
"form.integration.shaarli_api_secret": "Μυστικό API Shaarli",
"form.integration.shaarli_endpoint": "Διεύθυνση URL Shaarli",
"form.integration.shiori_activate": "Αποθήκευση άρθρων στο Shiori",
"form.integration.shiori_endpoint": "Τελικό σημείο Shiori",
"form.integration.shiori_password": "Κωδικός Πρόσβασης Shiori",
"form.integration.shiori_username": "Όνομα Χρήστη Shiori",
"form.integration.slack_activate": "Προώθηση καταχωρήσεων στο Slack",
"form.integration.slack_webhook_link": "Σύνδεσμος Webhook Slack",
"form.integration.telegram_bot_activate": "Προωθήστε νέα άρθρα στη συνομιλία Telegram",
"form.integration.telegram_bot_disable_buttons": "Απενεργοποίηση κουμπιών",
"form.integration.telegram_bot_disable_notification": "Απενεργοποίηση ειδοποίησης",
"form.integration.telegram_bot_disable_web_page_preview": "Απενεργοποίηση προεπισκόπησης ιστοσελίδας",
"form.integration.telegram_bot_token": "Διακριτικό bot",
"form.integration.telegram_chat_id": "Αναγνωριστικό συνομιλίας",
"form.integration.telegram_topic_id": "Αναγνωριστικό θέματος",
"form.integration.wallabag_activate": "Αποθήκευση άρθρων στο Wallabag",
"form.integration.wallabag_client_id": "Ταυτότητα πελάτη Wallabag",
"form.integration.wallabag_client_secret": "Wallabag Μυστικό Πελάτη",
"form.integration.wallabag_endpoint": "Βασική διεύθυνση URL Wallabag",
"form.integration.wallabag_only_url": "Αποστολή μόνο URL (αντί για πλήρες περιεχόμενο)",
"form.integration.wallabag_password": "Wallabag Κωδικός Πρόσβασης",
"form.integration.wallabag_username": "Όνομα Χρήστη Wallabag",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Ενεργοποίηση Webhooks",
"form.integration.webhook_secret": "Μυστικό Webhooks",
"form.integration.webhook_url": "Προεπιλεγμένη διεύθυνση URL Webhook",
"form.prefs.fieldset.application_settings": "Ρυθμίσεις εφαρμογής",
"form.prefs.fieldset.authentication_settings": "Ρυθμίσεις ελέγχου ταυτότητας",
"form.prefs.fieldset.global_feed_settings": "Καθολικές ρυθμίσεις ροής",
"form.prefs.fieldset.reader_settings": "Ρυθμίσεις αναγνώστη",
"form.prefs.help.external_font_hosts": "Λίστα εξωτερικών κεντρικών υπολογιστών γραμματοσειρών διαχωρισμένων με κενό για να επιτρέπονται. Για παράδειγμα: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.always_open_external_links": "Ανάγνωση άρθρων ανοίγοντας εξωτερικούς συνδέσμους",
"form.prefs.label.categories_sorting_order": "Ταξινόμηση κατηγοριών",
"form.prefs.label.cjk_reading_speed": "Ταχύτητα ανάγνωσης για κινέζικα, κορεάτικα και ιαπωνικά (χαρακτήρες ανά λεπτό)",
"form.prefs.label.custom_css": "Προσαρμοσμένο CSS",
"form.prefs.label.custom_js": "Προσαρμοσμένο JavaScript",
"form.prefs.label.default_home_page": "Προεπιλεγμένη αρχική σελίδα",
"form.prefs.label.default_reading_speed": "Ταχύτητα ανάγνωσης άλλων γλωσσών (λέξεις ανά λεπτό)",
"form.prefs.label.display_mode": "Λειτουργία προβολής προοδευτικής εφαρμογής Ιστού (PWA)",
"form.prefs.label.entries_per_page": "Καταχωρήσεις ανά σελίδα",
"form.prefs.label.entry_order": "Στήλη ταξινόμησης εισόδου",
"form.prefs.label.entry_sorting": "Ταξινόμηση",
"form.prefs.label.entry_swipe": "Ενεργοποιήστε το σάρωση καταχώρισης στις οθόνες αφής",
"form.prefs.label.external_font_hosts": "Εξωτερικοί κεντρικοί υπολογιστές γραμματοσειρών",
"form.prefs.label.gesture_nav": "Χειρονομία για πλοήγηση μεταξύ των καταχωρήσεων",
"form.prefs.label.keyboard_shortcuts": "Ενεργοποίηση συντομεύσεων πληκτρολογίου",
"form.prefs.label.language": "Γλώσσα",
"form.prefs.label.mark_read_manually": "Σήμανση καταχωρήσεων ως αναγνωσμένων με μη αυτόματο τρόπο",
"form.prefs.label.mark_read_on_media_completion": "Σήμανση ως αναγνωσμένου μόνο όταν η αναπαραγωγή ήχου/βίντεο φτάσει το 90%% ολοκλήρωσης",
"form.prefs.label.mark_read_on_view": "Αυτόματη επισήμανση καταχωρήσεων ως αναγνωσμένων κατά την προβολή",
"form.prefs.label.mark_read_on_view_or_media_completion": "Σήμανση καταχωρήσεων ως αναγνωσμένων κατά την προβολή. Για ήχο/βίντεο, σήμανση ως αναγνωσμένου στο 90%% ολοκλήρωσης",
"form.prefs.label.media_playback_rate": "Ταχύτητα αναπαραγωγής του ήχου/βίντεο",
"form.prefs.label.open_external_links_in_new_tab": "Άνοιγμα εξωτερικών συνδέσμων σε νέα καρτέλα (προσθέτει target=\"_blank\" στους συνδέσμους)",
"form.prefs.label.show_reading_time": "Εμφάνιση εκτιμώμενου χρόνου ανάγνωσης για άρθρα",
"form.prefs.label.theme": "Θέμα",
"form.prefs.label.timezone": "Ζώνη Ώρας",
"form.prefs.select.alphabetical": "Αλφαβητική σειρά",
"form.prefs.select.browser": "Περιηγητής",
"form.prefs.select.created_time": "Χρόνος δημιουργίας καταχώρησης",
"form.prefs.select.fullscreen": "Πλήρης οθόνη",
"form.prefs.select.minimal_ui": "Ελάχιστη",
"form.prefs.select.none": "Κανένας",
"form.prefs.select.older_first": "Παλαιότερες καταχωρήσεις πρώτα",
"form.prefs.select.publish_time": "Δημοσιευμένος χρόνος εισόδου",
"form.prefs.select.recent_first": "Πρόσφατες καταχωρήσεις πρώτα",
"form.prefs.select.standalone": "Μεμονωμένο",
"form.prefs.select.swipe": "Σουφρώνω",
"form.prefs.select.tap": "Διπλό χτύπημα",
"form.prefs.select.unread_count": "Αριθμός μη αναγνωσμένων",
"form.submit.loading": "Φόρτωση...",
"form.submit.saving": "Αποθήκευση...",
"form.user.label.admin": "Διαχειριστής",
"form.user.label.confirmation": "Επιβεβαίωση Κωδικού Πρόσβασης",
"form.user.label.password": "Κωδικός",
"form.user.label.username": "Χρήστης",
"menu.about": "Περί",
"menu.add_feed": "Προσθήκη συνδρομής",
"menu.add_user": "Προσθήκη χρήστη",
"menu.api_keys": "Κλειδιά API",
"menu.categories": "Κατηγορίες",
"menu.create_api_key": "Δημιουργήστε ένα νέο κλειδί API",
"menu.create_category": "Δημιουργήστε μια κατηγορία",
"menu.edit_category": "Επεξεργασία",
"menu.edit_feed": "Επεξεργασία",
"menu.export": "Εξαγωγή",
"menu.feed_entries": "Καταχωρήσεις",
"menu.feeds": "Ροές",
"menu.flush_history": "Εκκαθάριση ιστορικού",
"menu.history": "Ιστορικό",
"menu.home_page": "Αρχική σελίδα",
"menu.import": "Εισαγωγή",
"menu.integrations": "Ενσωμάτωσεις",
"menu.logout": "Αποσύνδεση",
"menu.mark_all_as_read": "Σημείωση όλων ως αναγνωσμένα",
"menu.mark_page_as_read": "Σημείωση αυτής της σελίδας ως αναγνωσμένη",
"menu.preferences": "Προτιμήσεις",
"menu.refresh_all_feeds": "Ανανέωση όλων των ροών στο παρασκήνιο",
"menu.refresh_feed": "Ανανέωση",
"menu.search": "Αναζήτηση",
"menu.sessions": "Συνδέσεις",
"menu.settings": "Ρυθμίσεις",
"menu.shared_entries": "Κοινόχρηστες καταχωρήσεις",
"menu.show_all_entries": "Εμφάνιση όλων των καταχωρήσεων",
"menu.show_only_starred_entries": "Εμφάνιση μόνο αγαπημένων καταχωρήσεων",
"menu.show_only_unread_entries": "Εμφάνιση μόνο μη αναγνωσμένων καταχωρήσεων",
"menu.starred": "Αγαπημένα",
"menu.title": "Μενού",
"menu.unread": "Μη αναγνωσμένα",
"menu.users": "Χρήστες",
"page.about.author": "Συγγραφέας:",
"page.about.build_date": "Ημερομηνία Κατασκευής:",
"page.about.credits": "Συνεισφέροντες",
"page.about.db_usage": "Μέγεθος βάσης δεδομένων:",
"page.about.git_commit": "Υποβολή Git:",
"page.about.global_config_options": "Γενικές ρυθμίσεις",
"page.about.go_version": "Έκδοση Go:",
"page.about.license": "Άδεια:",
"page.about.postgres_version": "Έκδοση Postgres:",
"page.about.title": "Περί",
"page.about.version": "Έκδοση:",
"page.add_feed.choose_feed": "Επιλέξτε μια συνδρομή",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Προχωρημένες Επιλογές",
"page.add_feed.no_category": "Δεν υπάρχει κατηγορία. Πρέπει να έχετε τουλάχιστον μία κατηγορία.",
"page.add_feed.submit": "Βρείτε μια συνδρομή",
"page.add_feed.title": "Νέα Συνδρομή",
"page.api_keys.never_used": "Δεν έχει χρησιμοποιηθεί ποτέ",
"page.api_keys.table.actions": "Eνέργειες",
"page.api_keys.table.created_at": "Ημερομηνία Δημιουργίας",
"page.api_keys.table.description": "Περιγραφή",
"page.api_keys.table.last_used_at": "Τελευταία Χρήση",
"page.api_keys.table.token": "Token",
"page.api_keys.title": "Κλειδιά API",
"page.categories.entries": "Άρθρα",
"page.categories.feed_count": [
"Υπάρχει μία %d ροή.",
"Υπάρχουν %d ροές."
],
"page.categories.feeds": "Συνδρομές",
"page.categories.no_feed": "Καμία ροή.",
"page.categories.title": "Κατηγορίες",
"page.categories_count": [
"%d κατηγορία",
"%d κατηγορίες"
],
"page.category_label": "Κατηγορία: %s",
"page.edit_category.title": "Επεξεργασία κατηγορίας: % s",
"page.edit_feed.etag_header": "Κεφαλίδα ETag:",
"page.edit_feed.last_check": "Τελευταίος έλεγχος:",
"page.edit_feed.last_modified_header": "LastModified κεφαλίδα:",
"page.edit_feed.last_parsing_error": "Τελευταίο Σφάλμα Ανάλυσης",
"page.edit_feed.no_header": "Καμία",
"page.edit_feed.title": "Επεξεργασία ροής: % s",
"page.edit_user.title": "Επεξεργασία χρήστη: % s",
"page.entry.attachments": "Συνημμένα",
"page.feeds.error_count": [
"%d σφάλμα",
"%d σφάλματα"
],
"page.feeds.last_check": "Τελευταίος έλεγχος:",
"page.feeds.next_check": "Επόμενος έλεγχος:",
"page.feeds.read_counter": "Αριθμός αναγνωσμένων καταχωρήσεων",
"page.feeds.title": "Ροές",
"page.footer.elevator": "Back to top",
"page.history.title": "Ιστορικό",
"page.import.title": "Εισαγωγή",
"page.integration.bookmarklet": "Bookmarklet",
"page.integration.bookmarklet.help": "Αυτός ο ειδικός σύνδεσμος σάς επιτρέπει να εγγραφείτε απευθείας σε έναν ιστότοπο χρησιμοποιώντας ένα σελιδοδείκτη στο πρόγραμμα περιήγησης ιστού σας.",
"page.integration.bookmarklet.instructions": "Σύρετε και αποθέστε αυτόν τον σύνδεσμο στους σελιδοδείκτες σας.",
"page.integration.bookmarklet.name": "Προσθήκη στο Miniflux",
"page.integration.miniflux_api": "Miniflux API",
"page.integration.miniflux_api_endpoint": "Τελικό σημείο API",
"page.integration.miniflux_api_password": "Κωδικός",
"page.integration.miniflux_api_password_value": "Ο κωδικός πρόσβασης του λογαριασμού σας",
"page.integration.miniflux_api_username": "Χρήστης",
"page.integrations.title": "Ενσωμάτωση",
"page.keyboard_shortcuts.close_modal": "Κλείσιμο παραθύρου διαλόγου",
"page.keyboard_shortcuts.download_content": "Κατεβάστε το αρχικό περιεχόμενο",
"page.keyboard_shortcuts.go_to_bottom_item": "Μετάβαση στο κάτω στοιχείο",
"page.keyboard_shortcuts.go_to_categories": "Μεταβείτε στις κατηγορίες",
"page.keyboard_shortcuts.go_to_feed": "Πηγαίνετε στη ροή",
"page.keyboard_shortcuts.go_to_feeds": "Μεταβείτε στις ροές",
"page.keyboard_shortcuts.go_to_history": "Μεταβείτε στο ιστορικό",
"page.keyboard_shortcuts.go_to_next_item": "Μετάβαση στο επόμενο στοιχείο",
"page.keyboard_shortcuts.go_to_next_page": "Μετάβαση στην επόμενη σελίδα",
"page.keyboard_shortcuts.go_to_previous_item": "Μεταβείτε στο προηγούμενο στοιχείο",
"page.keyboard_shortcuts.go_to_previous_page": "Μετάβαση στην προηγούμενη σελίδα",
"page.keyboard_shortcuts.go_to_search": "Ορίστε εστίαση στη φόρμα αναζήτησης",
"page.keyboard_shortcuts.go_to_settings": "Μεταβείτε στις ρυθμίσεις",
"page.keyboard_shortcuts.go_to_starred": "Μεταβείτε στους σελιδοδείκτες",
"page.keyboard_shortcuts.go_to_top_item": "Μετάβαση στο επάνω στοιχείο",
"page.keyboard_shortcuts.go_to_unread": "Μεταβείτε στα μη αναγνωσμένα",
"page.keyboard_shortcuts.mark_page_as_read": "Σημείωση της τρέχουσας σελίδας ως αναγνωσμένη",
"page.keyboard_shortcuts.open_comments": "Άνοιγμα συνδέσμου σχολίων",
"page.keyboard_shortcuts.open_comments_same_window": "Άνοιγμα συνδέσμου σχολίων στην τρέχουσα καρτέλα",
"page.keyboard_shortcuts.open_item": "Άνοιγμα επιλεγμένου στοιχείου",
"page.keyboard_shortcuts.open_original": "Άνοιγμα αρχικού συνδέσμου",
"page.keyboard_shortcuts.open_original_same_window": "Άνοιγμα αρχικού συνδέσμου στην τρέχουσα καρτέλα",
"page.keyboard_shortcuts.refresh_all_feeds": "Ανανέωση όλων των ροών στο παρασκήνιο",
"page.keyboard_shortcuts.remove_feed": "Κατάργηση αυτής της ροής",
"page.keyboard_shortcuts.save_article": "Αποθήκευση άρθρου",
"page.keyboard_shortcuts.scroll_item_to_top": "Μετακινηση στοιχείου στην κορυφή",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Εμφάνιση συντομεύσεων πληκτρολογίου",
"page.keyboard_shortcuts.subtitle.actions": "Ενέργειες",
"page.keyboard_shortcuts.subtitle.items": "Πλοήγηση Στοιχείων",
"page.keyboard_shortcuts.subtitle.pages": "Πλοήγηση Σελίδων",
"page.keyboard_shortcuts.subtitle.sections": "Πλοήγηση Τμημάτων",
"page.keyboard_shortcuts.title": "Συντομεύσεις Πληκτρολογίου",
"page.keyboard_shortcuts.toggle_star_status": "Εναλλαγή σελιδοδείκτη",
"page.keyboard_shortcuts.toggle_entry_attachments": "Εναλλαγή άνοιγμα/κλείσιμο συνημμένων καταχώρησης",
"page.keyboard_shortcuts.toggle_read_status_next": "Εναλλαγή ανάγνωσης / μη αναγνωσμένης, εστίαση στη συνέχεια",
"page.keyboard_shortcuts.toggle_read_status_prev": "Εναλλαγή ανάγνωσης / μη αναγνωσμένης, εστίαση στο προηγούμενο",
"page.login.google_signin": "Συνδεθείτε με τo Google",
"page.login.oidc_signin": "Συνδεθείτε με το %s",
"page.login.title": "Είσοδος",
"page.login.webauthn_login": "Είσοδος με κωδικό πρόσβασης",
"page.login.webauthn_login.error": "Δεν είναι δυνατή η σύνδεση με κωδικό πρόσβασης",
"page.login.webauthn_login.help": "Παρακαλώ εισαγάγετε το όνομα χρήστη σας εάν χρησιμοποιείτε κλειδί ασφαλείας. Αυτό δεν απαιτείται εάν χρησιμοποιείτε Passkey (ανακαλύψιμα διαπιστευτήρια).",
"page.new_api_key.title": "Νέο κλειδί API",
"page.new_category.title": "Νέα Κατηγορία",
"page.new_user.title": "Νέος Χρήστης",
"page.offline.message": "Είστε εκτός σύνδεσης",
"page.offline.refresh_page": "Προσπαθήστε να ανανεώσετε τη σελίδα",
"page.offline.title": "Λειτουργία Εκτός Σύνδεσης",
"page.read_entry_count": [
"%d αναγνωσμένη καταχώρηση",
"%d αναγνωσμένες καταχωρήσεις"
],
"page.search.title": "Αποτελέσματα Αναζήτησης",
"page.sessions.table.actions": "Eνέργειες",
"page.sessions.table.current_session": "Τρέχουσα Συνεδρία",
"page.sessions.table.date": "Ημερομηνία",
"page.sessions.table.ip": "Διεύθυνση IP",
"page.sessions.table.user_agent": "User Agent",
"page.sessions.title": "Συνεδρίες",
"page.settings.link_google_account": "Σύνδεση του λογαριασμό μου Google",
"page.settings.link_oidc_account": "Σύνδεση του λογαριασμού μου %s",
"page.settings.title": "Ρυθμίσεις",
"page.settings.unlink_google_account": "Αποσύνδεση του λογαριασμού μου Google",
"page.settings.unlink_oidc_account": "Αποσύνδεση του λογαριασμού μου %s",
"page.settings.webauthn.actions": "Ενέργειες",
"page.settings.webauthn.added_on": "Προστέθηκε στις",
"page.settings.webauthn.delete": [
"Κατάργηση %d κωδικού πρόσβασης",
"Κατάργηση %d κωδικών πρόσβασης"
],
"page.settings.webauthn.last_seen_on": "Τελευταία χρήση",
"page.settings.webauthn.passkey_name": "Όνομα κωδικού πρόσβασης",
"page.settings.webauthn.passkeys": "Κωδικοί πρόσβασης",
"page.settings.webauthn.register": "Εγγραφή κωδικού πρόσβασης",
"page.settings.webauthn.register.error": "Δεν είναι δυνατή η εγγραφή του κωδικού πρόσβασης",
"page.shared_entries.title": "Κοινόχρηστες Καταχωρήσεις",
"page.shared_entries_count": [
"%d κοινόχρηστη καταχώρηση",
"%d κοινόχρηστες καταχωρήσεις"
],
"page.starred.title": "Αγαπημένo",
"page.starred_entry_count": [
"%d καταχώρηση με αστέρι",
"%d καταχωρήσεις με αστέρι"
],
"page.total_entry_count": [
"%d καταχώρηση συνολικά",
"%d καταχωρήσεις συνολικά"
],
"page.unread.title": "Μη αναγνωσμένα",
"page.unread_entry_count": [
"%d μη αναγνωσμένη καταχώρηση",
"%d μη αναγνωσμένες καταχωρήσεις"
],
"page.users.actions": "Eνέργειες",
"page.users.admin.no": "Όχι",
"page.users.admin.yes": "Ναι.",
"page.users.is_admin": "Διαχειριστής",
"page.users.last_login": "Τελευταία Σύνδεση",
"page.users.never_logged": "Ποτέ",
"page.users.title": "Χρήστες",
"page.users.username": "Χρήστης",
"page.webauthn_rename.title": "Μετονομασία κωδικού πρόσβασης",
"pagination.first": "Πρώτο",
"pagination.last": "Τελευταίο",
"pagination.next": "Επόμενη",
"pagination.previous": "Προηγούμενη",
"search.label": "Αναζήτηση",
"search.placeholder": "Αναζήτηση...",
"search.submit": "Αναζήτηση",
"skip_to_content": "Μετάβαση στο περιεχόμενο",
"time_elapsed.days": [
"πριν %d ημέρα",
"πριν %d ημέρες"
],
"time_elapsed.hours": [
"πριν %d ώρα",
"πριν %d ώρες"
],
"time_elapsed.minutes": [
"πριν %d λεπτό",
"πριν %d λεπτά"
],
"time_elapsed.months": [
"πριν %d μήνα",
"πριν %d μήνες"
],
"time_elapsed.not_yet": "όχι ακόμα.",
"time_elapsed.now": "μόλις τώρα",
"time_elapsed.weeks": [
"πριν %d εβδομάδα",
"πριν %d εβδομάδες"
],
"time_elapsed.years": [
"πριν %d έτος",
"πριν %d έτη"
],
"time_elapsed.yesterday": "χθες",
"tooltip.keyboard_shortcuts": "Συντόμευση πληκτρολογίου: % s",
"tooltip.logged_user": "Συνδεδεμένος/η ως %s"
} v2-2.2.13/internal/locale/translations/en_US.json 0000664 0000000 0000000 00000107342 15062123773 0021643 0 ustar 00root root 0000000 0000000 {
"action.cancel": "cancel",
"action.download": "Download",
"action.edit": "Edit",
"action.home_screen": "Add to home screen",
"action.import": "Import",
"action.login": "Login",
"action.or": "or",
"action.remove": "Remove",
"action.remove_feed": "Remove this feed",
"action.save": "Save",
"action.subscribe": "Subscribe",
"action.update": "Update",
"alert.account_linked": "Your external account is now linked!",
"alert.account_unlinked": "Your external account is now dissociated!",
"alert.background_feed_refresh": "All feeds are being refreshed in the background. You can continue to use Miniflux while this process is running.",
"alert.feed_error": "There is a problem with this feed",
"alert.no_starred": "There are no starred entries.",
"alert.no_category": "There is no category.",
"alert.no_category_entry": "There are no entries in this category.",
"alert.no_feed": "You don’t have any feeds.",
"alert.no_feed_entry": "There are no entries for this feed.",
"alert.no_feed_in_category": "There is no feed for this category.",
"alert.no_history": "There is no history at the moment.",
"alert.no_search_result": "There are no results for this search.",
"alert.no_shared_entry": "There is no shared entry.",
"alert.no_tag_entry": "There are no entries matching this tag.",
"alert.no_unread_entry": "There are no unread entries.",
"alert.no_user": "You are the only user.",
"alert.prefs_saved": "Preferences saved!",
"alert.too_many_feeds_refresh": [
"You have triggered too many feed refreshes. Please wait %d minute before trying again.",
"You have triggered too many feed refreshes. Please wait %d minutes before trying again."
],
"confirm.loading": "In progress…",
"confirm.no": "no",
"confirm.question": "Are you sure?",
"confirm.question.refresh": "Are you sure you want to force refresh?",
"confirm.yes": "yes",
"enclosure_media_controls.seek": "Seek:",
"enclosure_media_controls.seek.title": "Seek %s seconds",
"enclosure_media_controls.speed": "Speed:",
"enclosure_media_controls.speed.faster": "Faster",
"enclosure_media_controls.speed.faster.title": "Faster by %sx",
"enclosure_media_controls.speed.reset": "Reset",
"enclosure_media_controls.speed.reset.title": "Reset speed to 1x",
"enclosure_media_controls.speed.slower": "Slower",
"enclosure_media_controls.speed.slower.title": "Slower by %sx",
"entry.starred.toast.off": "Unstarred",
"entry.starred.toast.on": "Starred",
"entry.starred.toggle.off": "Unstar",
"entry.starred.toggle.on": "Star",
"entry.comments.label": "Comments",
"entry.comments.title": "View Comments",
"entry.estimated_reading_time": [
"%d minute read",
"%d minutes read"
],
"entry.external_link.label": "External link",
"entry.save.completed": "Done!",
"entry.save.label": "Save",
"entry.save.title": "Save this entry",
"entry.save.toast.completed": "Entry saved",
"entry.scraper.completed": "Done!",
"entry.scraper.label": "Download",
"entry.scraper.title": "Fetch original content",
"entry.share.label": "Share",
"entry.share.title": "Share this entry",
"entry.shared_entry.label": "Share",
"entry.shared_entry.title": "Open the public link",
"entry.state.loading": "Loading…",
"entry.state.saving": "Saving…",
"entry.status.mark_as_read": "Mark as read",
"entry.status.mark_as_unread": "Mark as unread",
"entry.status.title": "Change entry status",
"entry.status.toast.read": "Marked as read",
"entry.status.toast.unread": "Marked as unread",
"entry.tags.label": "Tags:",
"entry.tags.more_tags_label": [
"Show %d more tag",
"Show %d more tags"
],
"entry.unshare.label": "Unshare",
"error.api_key_already_exists": "This API Key already exists.",
"error.bad_credentials": "Invalid username or password.",
"error.category_already_exists": "This category already exists.",
"error.category_not_found": "This category does not exist or does not belong to this user.",
"error.database_error": "Database error: %v.",
"error.different_passwords": "Passwords are not the same.",
"error.duplicate_fever_username": "There is already someone else with the same Fever username!",
"error.duplicate_googlereader_username": "There is already someone else with the same Google Reader username!",
"error.linktaco_missing_required_fields": "LinkTaco API Token and Organization Slug are required",
"error.duplicate_linked_account": "There is already someone associated with this provider!",
"error.duplicated_feed": "This feed already exists.",
"error.empty_file": "This file is empty.",
"error.entries_per_page_invalid": "The number of entries per page is not valid.",
"error.feed_already_exists": "This feed already exists.",
"error.feed_category_not_found": "This category does not exist or does not belong to this user.",
"error.feed_format_not_detected": "Unable to detect feed format: %v.",
"error.feed_invalid_blocklist_rule": "The block list rule is invalid.",
"error.feed_invalid_keeplist_rule": "The keep list rule is invalid.",
"error.feed_mandatory_fields": "The URL and the category are mandatory.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
"error.feed_title_not_empty": "The feed title cannot be empty.",
"error.feed_url_not_empty": "The feed URL cannot be empty.",
"error.fields_mandatory": "All fields are mandatory.",
"error.http_bad_gateway": "The website is not available at the moment due to a bad gateway error. The problem is not on Miniflux side. Please, try again later.",
"error.http_body_read": "Unable to read the HTTP body: %v.",
"error.http_client_error": "HTTP client error: %v.",
"error.http_empty_response": "The HTTP response is empty. Perhaps, this website is using a bot protection mechanism?",
"error.http_empty_response_body": "The HTTP response body is empty.",
"error.http_forbidden": "Access to this website is forbidden. Perhaps, this website has a bot protection mechanism?",
"error.http_gateway_timeout": "The website is not available at the moment due to a gateway timeout error. The problem is not on Miniflux side. Please, try again later.",
"error.http_internal_server_error": "The website is not available at the moment due to a server error. The problem is not on Miniflux side. Please, try again later.",
"error.http_not_authorized": "Access to this website is not authorized. It could be a bad username or password.",
"error.http_resource_not_found": "The requested resource is not found. Please, verify the URL.",
"error.http_response_too_large": "The HTTP response is too large. You could increase the HTTP response size limit in the global settings (requires a server restart).",
"error.http_service_unavailable": "The website is not available at the moment due to an internal server error. The problem is not on Miniflux side. Please, try again later.",
"error.http_too_many_requests": "Miniflux generated too many requests to this website. Please, try again later or change the application configuration.",
"error.http_unexpected_status_code": "The website is not available at the moment due to an unexpected HTTP status code: %d. The problem is not on Miniflux side. Please, try again later.",
"error.invalid_categories_sorting_order": "Invalid categories sorting order.",
"error.invalid_default_home_page": "Invalid default homepage!",
"error.invalid_display_mode": "Invalid web app display mode.",
"error.invalid_entry_direction": "Invalid entry direction.",
"error.invalid_entry_order": "Invalid entry order.",
"error.invalid_feed_proxy_url": "Invalid proxy URL.",
"error.invalid_feed_url": "Invalid feed URL.",
"error.invalid_gesture_nav": "Invalid gesture navigation.",
"error.invalid_language": "Invalid language.",
"error.invalid_site_url": "Invalid site URL.",
"error.invalid_theme": "Invalid theme.",
"error.invalid_timezone": "Invalid timezone.",
"error.network_operation": "Miniflux is not able to reach this website due to a network error: %v.",
"error.network_timeout": "This website is too slow and the request timed out: %v",
"error.password_min_length": "The password must have at least 6 characters.",
"error.proxy_url_not_empty": "The proxy URL cannot be empty.",
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
"error.settings_invalid_domain_list": "Invalid domain list. Please provide a space separated list of domains.",
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
"error.settings_mandatory_fields": "The username, theme, language and timezone fields are mandatory.",
"error.settings_media_playback_rate_range": "Playback speed is out of range",
"error.settings_reading_speed_is_positive": "The reading speeds must be positive integers.",
"error.site_url_not_empty": "The site URL cannot be empty.",
"error.subscription_not_found": "Unable to find any feed.",
"error.title_required": "The title is mandatory.",
"error.tls_error": "TLS error: %q. You could disable TLS verification in the feed settings if you would like.",
"error.unable_to_create_api_key": "Unable to create this API Key.",
"error.unable_to_create_category": "Unable to create this category.",
"error.unable_to_create_user": "Unable to create this user.",
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.unable_to_update_category": "Unable to update this category.",
"error.unable_to_update_feed": "Unable to update this feed.",
"error.unable_to_update_user": "Unable to update this user.",
"error.unlink_account_without_password": "You must define a password otherwise you won’t be able to login again.",
"error.user_already_exists": "This user already exists.",
"error.user_mandatory_fields": "The username is mandatory.",
"form.api_key.label.description": "API Key Label",
"form.category.hide_globally": "Hide entries in global unread list",
"form.category.label.title": "Title",
"form.feed.fieldset.general": "General",
"form.feed.fieldset.integration": "Third-Party Services",
"form.feed.fieldset.network_settings": "Network Settings",
"form.feed.fieldset.rules": "Rules",
"form.feed.label.allow_self_signed_certificates": "Allow self-signed or invalid certificates",
"form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
"form.feed.label.block_filter_entry_rules": "Entry Blocking Rules",
"form.feed.label.blocklist_rules": "Regex-Based Blocking Filters",
"form.feed.label.category": "Category",
"form.feed.label.cookie": "Set Cookies",
"form.feed.label.crawler": "Fetch original content",
"form.feed.label.description": "Description",
"form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
"form.feed.label.disabled": "Do not refresh this feed",
"form.feed.label.feed_password": "Feed Password",
"form.feed.label.feed_url": "Feed URL",
"form.feed.label.feed_username": "Feed Username",
"form.feed.label.fetch_via_proxy": "Use the proxy configured at the application level",
"form.feed.label.hide_globally": "Hide entries in global unread list",
"form.feed.label.ignore_http_cache": "Ignore HTTP cache",
"form.feed.label.keep_filter_entry_rules": "Entry Allow Rules",
"form.feed.label.keeplist_rules": "Regex-Based Keep Filters",
"form.feed.label.no_media_player": "No media player (audio/video)",
"form.feed.label.ntfy_activate": "Push entries to ntfy",
"form.feed.label.ntfy_default_priority": "Ntfy default priority",
"form.feed.label.ntfy_high_priority": "Ntfy high priority",
"form.feed.label.ntfy_low_priority": "Ntfy low priority",
"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.proxy_url": "Proxy URL",
"form.feed.label.pushover_activate": "Push entries to Pushover",
"form.feed.label.pushover_default_priority": "Default priority",
"form.feed.label.pushover_high_priority": "High priority",
"form.feed.label.pushover_low_priority": "Low priority",
"form.feed.label.pushover_max_priority": "Max priority",
"form.feed.label.pushover_min_priority": "Minimal priority",
"form.feed.label.pushover_priority": "Pushover message priority",
"form.feed.label.rewrite_rules": "Content Rewrite Rules",
"form.feed.label.scraper_rules": "Scraper Rules",
"form.feed.label.site_url": "Site URL",
"form.feed.label.title": "Title",
"form.feed.label.urlrewrite_rules": "URL Rewrite Rules",
"form.feed.label.user_agent": "Override Default User Agent",
"form.feed.label.webhook_url": "Override webhook url",
"form.import.label.file": "OPML file",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Push entries to Apprise",
"form.integration.apprise_services_url": "Comma separated list of Apprise service URLs",
"form.integration.apprise_url": "Apprise API URL",
"form.integration.betula_activate": "Save entries to Betula",
"form.integration.betula_token": "Betula Token",
"form.integration.betula_url": "Betula server URL",
"form.integration.cubox_activate": "Save entries to Cubox",
"form.integration.cubox_api_link": "Cubox API link",
"form.integration.discord_activate": "Push entries to Discord",
"form.integration.discord_webhook_link": "Discord Webhook link",
"form.integration.espial_activate": "Save entries to Espial",
"form.integration.espial_api_key": "Espial API key",
"form.integration.espial_endpoint": "Espial API Endpoint",
"form.integration.espial_tags": "Espial Tags",
"form.integration.fever_activate": "Activate Fever API",
"form.integration.fever_endpoint": "Fever API endpoint:",
"form.integration.fever_password": "Fever Password",
"form.integration.fever_username": "Fever Username",
"form.integration.googlereader_activate": "Activate Google Reader API",
"form.integration.googlereader_endpoint": "Google Reader API endpoint:",
"form.integration.googlereader_password": "Google Reader Password",
"form.integration.googlereader_username": "Google Reader Username",
"form.integration.instapaper_activate": "Save entries to Instapaper",
"form.integration.instapaper_password": "Instapaper Password",
"form.integration.instapaper_username": "Instapaper Username",
"form.integration.karakeep_activate": "Save entries to Karakeep",
"form.integration.karakeep_api_key": "Karakeep API key",
"form.integration.karakeep_url": "Karakeep API Endpoint",
"form.integration.linkace_activate": "Save entries to LinkAce",
"form.integration.linkace_api_key": "LinkAce API key",
"form.integration.linkace_check_disabled": "Disable link check",
"form.integration.linkace_endpoint": "LinkAce API Endpoint",
"form.integration.linkace_is_private": "Mark link as private",
"form.integration.linkace_tags": "LinkAce Tags",
"form.integration.linkding_activate": "Save entries to Linkding",
"form.integration.linkding_api_key": "Linkding API key",
"form.integration.linkding_bookmark": "Mark bookmark as unread",
"form.integration.linkding_endpoint": "Linkding API Endpoint",
"form.integration.linkding_tags": "Linkding Tags",
"form.integration.linktaco_activate": "Save entries to LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Get your personal access token at",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Tags (max 10, comma-separated)",
"form.integration.linktaco_tags_hint": "Maximum 10 tags, comma-separated",
"form.integration.linktaco_visibility": "Visibility",
"form.integration.linktaco_visibility_public": "Public",
"form.integration.linktaco_visibility_private": "Private",
"form.integration.linktaco_visibility_hint": "PRIVATE visibility requires a paid LinkTaco account",
"form.integration.linkwarden_activate": "Save entries to Linkwarden",
"form.integration.linkwarden_api_key": "Linkwarden API key",
"form.integration.linkwarden_endpoint": "Linkwarden Base URL",
"form.integration.matrix_bot_activate": "Push new entries to Matrix",
"form.integration.matrix_bot_chat_id": "ID of Matrix Room",
"form.integration.matrix_bot_password": "Password for Matrix user",
"form.integration.matrix_bot_url": "Matrix server URL",
"form.integration.matrix_bot_user": "Username for Matrix",
"form.integration.notion_activate": "Save entries to Notion",
"form.integration.notion_page_id": "Notion Page ID",
"form.integration.notion_token": "Notion Secret Token",
"form.integration.ntfy_activate": "Push entries to ntfy",
"form.integration.ntfy_api_token": "Ntfy API Token (optional)",
"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 (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",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API key",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API Endpoint",
"form.integration.omnivore_activate": "Save entries to Omnivore",
"form.integration.omnivore_api_key": "Omnivore API key",
"form.integration.omnivore_url": "Omnivore API Endpoint",
"form.integration.pinboard_activate": "Save entries to Pinboard",
"form.integration.pinboard_bookmark": "Mark bookmark as unread",
"form.integration.pinboard_tags": "Pinboard Tags",
"form.integration.pinboard_token": "Pinboard API Token",
"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.raindrop_activate": "Save entries to Raindrop",
"form.integration.raindrop_collection_id": "Collection ID",
"form.integration.raindrop_tags": "Tags (comma-separated)",
"form.integration.raindrop_token": "(Test) Token",
"form.integration.readeck_activate": "Save entries to readeck",
"form.integration.readeck_api_key": "Readeck API key",
"form.integration.readeck_endpoint": "Readeck URL",
"form.integration.readeck_labels": "Readeck Labels",
"form.integration.readeck_only_url": "Send only URL (instead of full content)",
"form.integration.readwise_activate": "Save entries to Readwise Reader",
"form.integration.readwise_api_key": "Readwise Reader Access Token",
"form.integration.readwise_api_key_link": "Get your Readwise Access Token",
"form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "RSS-Bridge server URL",
"form.integration.shaarli_activate": "Save articles to Shaarli",
"form.integration.shaarli_api_secret": "Shaarli API Secret",
"form.integration.shaarli_endpoint": "Shaarli URL",
"form.integration.shiori_activate": "Save articles to Shiori",
"form.integration.shiori_endpoint": "Shiori API Endpoint",
"form.integration.shiori_password": "Shiori Password",
"form.integration.shiori_username": "Shiori Username",
"form.integration.slack_activate": "Push entries to Slack",
"form.integration.slack_webhook_link": "Slack Webhook link",
"form.integration.telegram_bot_activate": "Push new entries to Telegram chat",
"form.integration.telegram_bot_disable_buttons": "Disable buttons",
"form.integration.telegram_bot_disable_notification": "Disable notification",
"form.integration.telegram_bot_disable_web_page_preview": "Disable web page preview",
"form.integration.telegram_bot_token": "Bot token",
"form.integration.telegram_chat_id": "Chat ID",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "Save entries to Wallabag",
"form.integration.wallabag_client_id": "Wallabag Client ID",
"form.integration.wallabag_client_secret": "Wallabag Client Secret",
"form.integration.wallabag_endpoint": "Wallabag Base URL",
"form.integration.wallabag_only_url": "Send only URL (instead of full content)",
"form.integration.wallabag_password": "Wallabag Password",
"form.integration.wallabag_username": "Wallabag Username",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Enable Webhooks",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Default Webhook URL",
"form.prefs.fieldset.application_settings": "Application Settings",
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
"form.prefs.fieldset.reader_settings": "Reader Settings",
"form.prefs.help.external_font_hosts": "Space separated list of external font hosts to allow. For example: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.always_open_external_links": "Read articles by opening external links",
"form.prefs.label.categories_sorting_order": "Categories sorting",
"form.prefs.label.cjk_reading_speed": "Reading speed for Chinese, Korean and Japanese (characters per minute)",
"form.prefs.label.custom_css": "Custom CSS",
"form.prefs.label.custom_js": "Custom JavaScript",
"form.prefs.label.default_home_page": "Default home page",
"form.prefs.label.default_reading_speed": "Reading speed for other languages (words per minute)",
"form.prefs.label.display_mode": "Progressive Web App (PWA) display mode",
"form.prefs.label.entries_per_page": "Entries per page",
"form.prefs.label.entry_order": "Entry sorting column",
"form.prefs.label.entry_sorting": "Entry sorting",
"form.prefs.label.entry_swipe": "Enable entry swipe on touch screens",
"form.prefs.label.external_font_hosts": "External font hosts",
"form.prefs.label.gesture_nav": "Gesture to navigate between entries",
"form.prefs.label.keyboard_shortcuts": "Enable keyboard shortcuts",
"form.prefs.label.language": "Language",
"form.prefs.label.mark_read_manually": "Mark entries as read manually",
"form.prefs.label.mark_read_on_media_completion": "Only mark as read when audio/video playback reaches 90%% completion",
"form.prefs.label.mark_read_on_view": "Automatically mark entries as read when viewed",
"form.prefs.label.mark_read_on_view_or_media_completion": "Mark entries as read when viewed. For audio/video, mark as read at 90%% completion",
"form.prefs.label.media_playback_rate": "Playback speed of the audio/video",
"form.prefs.label.open_external_links_in_new_tab": "Open external links in a new tab (adds target=\"_blank\" to links)",
"form.prefs.label.show_reading_time": "Show estimated reading time for entries",
"form.prefs.label.theme": "Theme",
"form.prefs.label.timezone": "Timezone",
"form.prefs.select.alphabetical": "Alphabetical",
"form.prefs.select.browser": "Browser",
"form.prefs.select.created_time": "Entry created time",
"form.prefs.select.fullscreen": "Fullscreen",
"form.prefs.select.minimal_ui": "Minimal",
"form.prefs.select.none": "None",
"form.prefs.select.older_first": "Older entries first",
"form.prefs.select.publish_time": "Entry published time",
"form.prefs.select.recent_first": "Recent entries first",
"form.prefs.select.standalone": "Standalone",
"form.prefs.select.swipe": "Swipe",
"form.prefs.select.tap": "Double tap",
"form.prefs.select.unread_count": "Unread count",
"form.submit.loading": "Loading…",
"form.submit.saving": "Saving…",
"form.user.label.admin": "Administrator",
"form.user.label.confirmation": "Password Confirmation",
"form.user.label.password": "Password",
"form.user.label.username": "Username",
"menu.about": "About",
"menu.add_feed": "Add feed",
"menu.add_user": "Add user",
"menu.api_keys": "API Keys",
"menu.categories": "Categories",
"menu.create_api_key": "Create a new API key",
"menu.create_category": "Create a category",
"menu.edit_category": "Edit",
"menu.edit_feed": "Edit",
"menu.export": "Export",
"menu.feed_entries": "Entries",
"menu.feeds": "Feeds",
"menu.flush_history": "Flush history",
"menu.history": "History",
"menu.home_page": "Home page",
"menu.import": "Import",
"menu.integrations": "Integrations",
"menu.logout": "Logout",
"menu.mark_all_as_read": "Mark all as read",
"menu.mark_page_as_read": "Mark this page as read",
"menu.preferences": "Preferences",
"menu.refresh_all_feeds": "Refresh all feeds in the background",
"menu.refresh_feed": "Refresh",
"menu.search": "Search",
"menu.sessions": "Sessions",
"menu.settings": "Settings",
"menu.shared_entries": "Shared entries",
"menu.show_all_entries": "Show all entries",
"menu.show_only_starred_entries": "Show only starred entries",
"menu.show_only_unread_entries": "Show only unread entries",
"menu.starred": "Starred",
"menu.title": "Menu",
"menu.unread": "Unread",
"menu.users": "Users",
"page.about.author": "Author:",
"page.about.build_date": "Build Date:",
"page.about.credits": "Credits",
"page.about.db_usage": "Database size:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "Global configuration options",
"page.about.go_version": "Go version:",
"page.about.license": "License:",
"page.about.postgres_version": "Postgres version:",
"page.about.title": "About",
"page.about.version": "Version:",
"page.add_feed.choose_feed": "Choose a feed",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Advanced Options",
"page.add_feed.no_category": "There is no category. You must have at least one category.",
"page.add_feed.submit": "Find a feed",
"page.add_feed.title": "New feed",
"page.api_keys.never_used": "Never Used",
"page.api_keys.table.actions": "Actions",
"page.api_keys.table.created_at": "Creation Date",
"page.api_keys.table.description": "Description",
"page.api_keys.table.last_used_at": "Last Used",
"page.api_keys.table.token": "Token",
"page.api_keys.title": "API Keys",
"page.categories.entries": "Entries",
"page.categories.feed_count": [
"There is %d feed.",
"There are %d feeds."
],
"page.categories.feeds": "Feeds",
"page.categories.no_feed": "No feed.",
"page.categories.title": "Categories",
"page.categories_count": [
"%d category",
"%d categories"
],
"page.category_label": "Category: %s",
"page.edit_category.title": "Edit Category: %s",
"page.edit_feed.etag_header": "ETag header:",
"page.edit_feed.last_check": "Last check:",
"page.edit_feed.last_modified_header": "LastModified header:",
"page.edit_feed.last_parsing_error": "Last Parsing Error",
"page.edit_feed.no_header": "None",
"page.edit_feed.title": "Edit Feed: %s",
"page.edit_user.title": "Edit User: %s",
"page.entry.attachments": "Attachments",
"page.feeds.error_count": [
"%d error",
"%d errors"
],
"page.feeds.last_check": "Last check:",
"page.feeds.next_check": "Next check:",
"page.feeds.read_counter": "Number of read entries",
"page.feeds.title": "Feeds",
"page.footer.elevator": "Back to top",
"page.history.title": "History",
"page.import.title": "Import",
"page.integration.bookmarklet": "Bookmarklet",
"page.integration.bookmarklet.help": "This special link allows you to subscribe to a website directly by using a bookmark in your web browser.",
"page.integration.bookmarklet.instructions": "Drag and drop this link to your bookmarks.",
"page.integration.bookmarklet.name": "Add to Miniflux",
"page.integration.miniflux_api": "Miniflux API",
"page.integration.miniflux_api_endpoint": "API Endpoint",
"page.integration.miniflux_api_password": "Password",
"page.integration.miniflux_api_password_value": "Your account password",
"page.integration.miniflux_api_username": "Username",
"page.integrations.title": "Integrations",
"page.keyboard_shortcuts.close_modal": "Close modal dialog",
"page.keyboard_shortcuts.download_content": "Download original content",
"page.keyboard_shortcuts.go_to_bottom_item": "Go to bottom item",
"page.keyboard_shortcuts.go_to_categories": "Go to categories",
"page.keyboard_shortcuts.go_to_feed": "Go to feed",
"page.keyboard_shortcuts.go_to_feeds": "Go to feeds",
"page.keyboard_shortcuts.go_to_history": "Go to history",
"page.keyboard_shortcuts.go_to_next_item": "Go to next item",
"page.keyboard_shortcuts.go_to_next_page": "Go to next page",
"page.keyboard_shortcuts.go_to_previous_item": "Go to previous item",
"page.keyboard_shortcuts.go_to_previous_page": "Go to previous page",
"page.keyboard_shortcuts.go_to_search": "Set focus on search form",
"page.keyboard_shortcuts.go_to_settings": "Go to settings",
"page.keyboard_shortcuts.go_to_starred": "Go to starred",
"page.keyboard_shortcuts.go_to_top_item": "Go to top item",
"page.keyboard_shortcuts.go_to_unread": "Go to unread",
"page.keyboard_shortcuts.mark_page_as_read": "Mark current page as read",
"page.keyboard_shortcuts.open_comments": "Open comments link",
"page.keyboard_shortcuts.open_comments_same_window": "Open comments link in current tab",
"page.keyboard_shortcuts.open_item": "Open selected item",
"page.keyboard_shortcuts.open_original": "Open original link",
"page.keyboard_shortcuts.open_original_same_window": "Open original link in current tab",
"page.keyboard_shortcuts.refresh_all_feeds": "Refresh all feeds in the background",
"page.keyboard_shortcuts.remove_feed": "Remove this feed",
"page.keyboard_shortcuts.save_article": "Save entry",
"page.keyboard_shortcuts.scroll_item_to_top": "Scroll item to top",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Show keyboard shortcuts",
"page.keyboard_shortcuts.subtitle.actions": "Actions",
"page.keyboard_shortcuts.subtitle.items": "Items Navigation",
"page.keyboard_shortcuts.subtitle.pages": "Pages Navigation",
"page.keyboard_shortcuts.subtitle.sections": "Sections Navigation",
"page.keyboard_shortcuts.title": "Keyboard Shortcuts",
"page.keyboard_shortcuts.toggle_star_status": "Toggle starred",
"page.keyboard_shortcuts.toggle_entry_attachments": "Toggle open/close entry attachments",
"page.keyboard_shortcuts.toggle_read_status_next": "Toggle read/unread, focus next",
"page.keyboard_shortcuts.toggle_read_status_prev": "Toggle read/unread, focus previous",
"page.login.google_signin": "Sign in with Google",
"page.login.oidc_signin": "Sign in with %s",
"page.login.title": "Sign In",
"page.login.webauthn_login": "Login with passkey",
"page.login.webauthn_login.error": "Unable to login with passkey",
"page.login.webauthn_login.help": "Please enter your username if you're using a security key. This is not required if you are using a Passkey (discoverable credentials).",
"page.new_api_key.title": "New API Key",
"page.new_category.title": "New Category",
"page.new_user.title": "New User",
"page.offline.message": "You are offline",
"page.offline.refresh_page": "Try to refresh the page",
"page.offline.title": "Offline Mode",
"page.read_entry_count": [
"%d read entry",
"%d read entries"
],
"page.search.title": "Search Results",
"page.sessions.table.actions": "Actions",
"page.sessions.table.current_session": "Current Session",
"page.sessions.table.date": "Date",
"page.sessions.table.ip": "IP Address",
"page.sessions.table.user_agent": "User Agent",
"page.sessions.title": "Sessions",
"page.settings.link_google_account": "Link my Google account",
"page.settings.link_oidc_account": "Link my %s account",
"page.settings.title": "Settings",
"page.settings.unlink_google_account": "Unlink my Google account",
"page.settings.unlink_oidc_account": "Unlink my %s account",
"page.settings.webauthn.actions": "Actions",
"page.settings.webauthn.added_on": "Added On",
"page.settings.webauthn.delete": [
"Remove %d passkey",
"Remove %d passkeys"
],
"page.settings.webauthn.last_seen_on": "Last Used",
"page.settings.webauthn.passkey_name": "Passkey Name",
"page.settings.webauthn.passkeys": "Passkeys",
"page.settings.webauthn.register": "Register passkey",
"page.settings.webauthn.register.error": "Unable to register passkey",
"page.shared_entries.title": "Shared entries",
"page.shared_entries_count": [
"%d shared entry",
"%d shared entries"
],
"page.starred.title": "Starred",
"page.starred_entry_count": [
"%d starred entry",
"%d starred entries"
],
"page.total_entry_count": [
"%d entry in total",
"%d entries in total"
],
"page.unread.title": "Unread",
"page.unread_entry_count": [
"%d unread entry",
"%d unread entries"
],
"page.users.actions": "Actions",
"page.users.admin.no": "No",
"page.users.admin.yes": "Yes",
"page.users.is_admin": "Administrator",
"page.users.last_login": "Last Login",
"page.users.never_logged": "Never",
"page.users.title": "Users",
"page.users.username": "Username",
"page.webauthn_rename.title": "Rename Passkey",
"pagination.first": "First",
"pagination.last": "Last",
"pagination.next": "Next",
"pagination.previous": "Previous",
"search.label": "Search",
"search.placeholder": "Search…",
"search.submit": "Search",
"skip_to_content": "Skip to content",
"time_elapsed.days": [
"%d day ago",
"%d days ago"
],
"time_elapsed.hours": [
"%d hour ago",
"%d hours ago"
],
"time_elapsed.minutes": [
"%d minute ago",
"%d minutes ago"
],
"time_elapsed.months": [
"%d month ago",
"%d months ago"
],
"time_elapsed.not_yet": "not yet",
"time_elapsed.now": "just now",
"time_elapsed.weeks": [
"%d week ago",
"%d weeks ago"
],
"time_elapsed.years": [
"%d year ago",
"%d years ago"
],
"time_elapsed.yesterday": "yesterday",
"tooltip.keyboard_shortcuts": "Keyboard Shortcut: %s",
"tooltip.logged_user": "Logged in as %s"
}
v2-2.2.13/internal/locale/translations/es_ES.json 0000664 0000000 0000000 00000116770 15062123773 0021635 0 ustar 00root root 0000000 0000000 {
"action.cancel": "Cancelar",
"action.download": "Descargar",
"action.edit": "Editar",
"action.home_screen": "Añadir a la pantalla principal",
"action.import": "Importar",
"action.login": "Iniciar sesión",
"action.or": "o",
"action.remove": "Eliminar",
"action.remove_feed": "Eliminar esta fuente",
"action.save": "Guardar",
"action.subscribe": "Suscribir",
"action.update": "Actualizar",
"alert.account_linked": "¡Tu cuenta externa ya está vinculada!",
"alert.account_unlinked": "¡Tu cuenta externa ya está desvinculada!",
"alert.background_feed_refresh": "Todos los feeds se actualizan en segundo plano. Puede continuar usando Miniflux mientras se ejecuta este proceso.",
"alert.feed_error": "Hay un problema con esta fuente.",
"alert.no_starred": "No hay marcador en este momento.",
"alert.no_category": "No hay categoría.",
"alert.no_category_entry": "No hay artículos en esta categoría.",
"alert.no_feed": "No tienes fuentes.",
"alert.no_feed_entry": "No hay artículos para esta fuente.",
"alert.no_feed_in_category": "No hay fuentes para esta categoría.",
"alert.no_history": "No hay historial en este momento.",
"alert.no_search_result": "No hay resultados para esta búsqueda.",
"alert.no_shared_entry": "No hay artículos compartidos.",
"alert.no_tag_entry": "No hay artículos con esta etiqueta.",
"alert.no_unread_entry": "No hay artículos sin leer.",
"alert.no_user": "Eres el único usuario.",
"alert.prefs_saved": "¡Las preferencias se han guardado!",
"alert.too_many_feeds_refresh": [
"Has activado demasiadas actualizaciones del feed. Espere %d minuto antes de volver a intentarlo.",
"Has activado demasiadas actualizaciones del feed. Espere %d minutos antes de volver a intentarlo."
],
"confirm.loading": "En progreso...",
"confirm.no": "no",
"confirm.question": "¿Estás seguro?",
"confirm.question.refresh": "¿Quieres forzar la actualización?",
"confirm.yes": "sí",
"enclosure_media_controls.seek": "Buscar:",
"enclosure_media_controls.seek.title": "Buscar %s segundos",
"enclosure_media_controls.speed": "Velocidad:",
"enclosure_media_controls.speed.faster": "Más rápido",
"enclosure_media_controls.speed.faster.title": "Más rápido a %sx",
"enclosure_media_controls.speed.reset": "Restablecer",
"enclosure_media_controls.speed.reset.title": "Restablecer la velocidad a 1x",
"enclosure_media_controls.speed.slower": "Despacio",
"enclosure_media_controls.speed.slower.title": "Más despacio a %sx",
"entry.starred.toast.off": "Sin estrellas",
"entry.starred.toast.on": "Sembrado de estrellas",
"entry.starred.toggle.off": "Desmarcar",
"entry.starred.toggle.on": "Marcar",
"entry.comments.label": "Comentarios",
"entry.comments.title": "Ver comentarios",
"entry.estimated_reading_time": [
"%d minuto de lectura",
"%d minutos de lectura"
],
"entry.external_link.label": "Enlace externo",
"entry.save.completed": "¡Hecho!",
"entry.save.label": "Guardar",
"entry.save.title": "Guardar este artículo",
"entry.save.toast.completed": "Artículos guardados",
"entry.scraper.completed": "¡Hecho!",
"entry.scraper.label": "Descargar",
"entry.scraper.title": "Obtener contenido original",
"entry.share.label": "Compartir",
"entry.share.title": "Compartir este artículo",
"entry.shared_entry.label": "Compartir",
"entry.shared_entry.title": "Abrir el enlace público",
"entry.state.loading": "Cargando...",
"entry.state.saving": "Guardando...",
"entry.status.mark_as_read": "Marcar como leído",
"entry.status.mark_as_unread": "Marcar como no leído",
"entry.status.title": "Cambiar estado del artículo",
"entry.status.toast.read": "Marcado como leído",
"entry.status.toast.unread": "Marcado como no leído",
"entry.tags.label": "Etiquetas:",
"entry.tags.more_tags_label": [
"Mostrar %d etiqueta más",
"Mostrar %d etiquetas más"
],
"entry.unshare.label": "No compartir",
"error.api_key_already_exists": "Esta clave API ya existe.",
"error.bad_credentials": "Usuario o contraseña no válido.",
"error.category_already_exists": "Esta categoría ya existe.",
"error.category_not_found": "Esta categoría no existe o no pertenece a este usuario.",
"error.database_error": "Error en la base de datos: %v.",
"error.different_passwords": "Las contraseñas no son las mismas.",
"error.duplicate_fever_username": "¡Ya hay alguien con el mismo nombre de usuario de Fever!",
"error.duplicate_googlereader_username": "¡Ya hay alguien con el mismo nombre de usuario de Google Reader!",
"error.duplicate_linked_account": "¡Ya hay alguien asociado a este servicio!",
"error.duplicated_feed": "Este feed ya existe.",
"error.empty_file": "Este archivo está vacío.",
"error.entries_per_page_invalid": "El número de artículos por página no es válido.",
"error.feed_already_exists": "Este feed ya existe.",
"error.feed_category_not_found": "Esta categoría no existe o no pertenece a este usuario.",
"error.feed_format_not_detected": "No se puede detectar el formato del feed: %v.",
"error.feed_invalid_blocklist_rule": "La regla de la lista de bloqueo no es válida.",
"error.feed_invalid_keeplist_rule": "La regla de mantener la lista no es válida.",
"error.feed_mandatory_fields": "Los campos de URL y categoría son obligatorios.",
"error.feed_not_found": "Este feed no existe o no pertenece a este usuario.",
"error.feed_title_not_empty": "El título del feed no puede estar vacío.",
"error.feed_url_not_empty": "La URL del feed no puede estar vacía.",
"error.fields_mandatory": "Todos los campos son obligatorios.",
"error.http_bad_gateway": "El sitio web no está disponible en este momento debido a un error en la puerta de enlace. El problema no está en el lado de Miniflux. Por favor, inténtalo de nuevo más tarde.",
"error.http_body_read": "Imposible leer el cuerpo HTTP: %v.",
"error.http_client_error": "Error cliente HTTP: %v.",
"error.http_empty_response": "La respuesta HTTP está vacía. ¿Quizás este sitio web tiene un mecanismo de protección contra bots?",
"error.http_empty_response_body": "El cuerpo de la respuesta HTTP está vacío.",
"error.http_forbidden": "El acceso a este sitio web está prohibido. ¿Quizás este sitio web tiene un mecanismo de protección contra bots?",
"error.http_gateway_timeout": "El sitio web no está disponible en este momento debido a un error de tiempo de espera de la puerta de enlace. El problema no está en el lado de Miniflux. Por favor, inténtalo de nuevo más tarde.",
"error.http_internal_server_error": "El sitio web no está disponible en estos momentos debido a un error del servidor. El problema no está en el lado de Miniflux. Por favor, inténtalo de nuevo más tarde.",
"error.http_not_authorized": "El acceso a este sitio web no está autorizado. Podría ser un nombre de usuario o contraseña incorrectos.",
"error.http_resource_not_found": "No se encuentra el recurso solicitado. Por favor, verifique la URL.",
"error.http_response_too_large": "La respuesta HTTP es demasiado grande. Puede aumentar el límite de tamaño de respuesta HTTP en la configuración global (requiere reiniciar el servidor).",
"error.http_service_unavailable": "El sitio web no está disponible en estos momentos debido a un error interno del servidor. El problema no está en el lado de Miniflux. Por favor, inténtalo de nuevo más tarde.",
"error.http_too_many_requests": "Miniflux generó demasiadas solicitudes a este sitio web. Por favor, inténtalo de nuevo más tarde o cambia la configuración de la aplicación.",
"error.http_unexpected_status_code": "El sitio web no está disponible en este momento debido a un código de estado HTTP inesperado: %d. El problema no está en el lado de Miniflux. Por favor, inténtalo de nuevo más tarde.",
"error.invalid_categories_sorting_order": "Orden de clasificación de categorías no válido.",
"error.invalid_default_home_page": "¡Página de inicio por defecto no válida!",
"error.invalid_display_mode": "Modo de visualización de la aplicación web no válido.",
"error.invalid_entry_direction": "Dirección de artículo no válida.",
"error.invalid_entry_order": "Orden de artículo no válido.",
"error.invalid_feed_proxy_url": "URL de proxy inválida.",
"error.invalid_feed_url": "URL de feed no válida.",
"error.invalid_gesture_nav": "Navegación por gestos no válida.",
"error.invalid_language": "Idioma no válido.",
"error.invalid_site_url": "URL del sitio no válida.",
"error.invalid_theme": "Tema no válido.",
"error.invalid_timezone": "Zona horaria no válida.",
"error.network_operation": "Miniflux no puede acceder a este sitio web debido a un error de red: %v.",
"error.network_timeout": "Este sitio web es demasiado lento y se agotó el tiempo de espera de la solicitud: %v",
"error.password_min_length": "La contraseña debería tener al menos 6 caracteres.",
"error.proxy_url_not_empty": "La URL del proxy no puede estar vacía.",
"error.settings_block_rule_fieldname_invalid": "Regla de bloqueo no válida: a la regla #%d le falta un nombre de campo válido (Opciones: %s)",
"error.settings_block_rule_invalid_regex": "Regla de bloqueo no válida: el patrón de la regla #%d no es una expresión regular válida",
"error.settings_block_rule_regex_required": "Regla de bloqueo no válida: no se ha proporcionado el patrón de la regla #%d",
"error.settings_block_rule_separator_required": "Regla de bloqueo no válida: el patrón de la regla #%d debe estar separado por un '='",
"error.settings_invalid_domain_list": "Lista de dominios inválida. Por favor proporcione una lista de dominios separados por espacios.",
"error.settings_keep_rule_fieldname_invalid": "Regla de mantenimiento no válida: a la regla #%d le falta un nombre de campo válido (Opciones: %s)",
"error.settings_keep_rule_invalid_regex": "Regla de mantenimiento no válida: el patrón de la regla #%d no es una expresión regular válida",
"error.settings_keep_rule_regex_required": "Regla de conservación no válida: no se ha proporcionado la regla #%d patrón",
"error.settings_keep_rule_separator_required": "Regla de mantenimiento no válida: el patrón de la regla #%d debe estar separado por un '='",
"error.settings_mandatory_fields": "Los campos de nombre de usuario, tema, idioma y zona horaria son obligatorios.",
"error.settings_media_playback_rate_range": "La velocidad de reproducción está fuera de rango",
"error.settings_reading_speed_is_positive": "Las velocidades de lectura deben ser números enteros positivos.",
"error.site_url_not_empty": "La URL del sitio no puede estar vacía.",
"error.subscription_not_found": "Incapaz de encontrar alguna fuente.",
"error.title_required": "El título es obligatorio.",
"error.tls_error": "Error de TLS: %q. Puede desactivar la verificación TLS en la configuración del feed si lo desea.",
"error.unable_to_create_api_key": "No se puede crear esta clave API.",
"error.unable_to_create_category": "Incapaz de crear esta categoría.",
"error.unable_to_create_user": "Incapaz de crear este usuario.",
"error.unable_to_detect_rssbridge": "No se puede detectar la fuente usando RSS-Bridge: %v.",
"error.unable_to_parse_feed": "No se puede analizar este feed: %v.",
"error.unable_to_update_category": "Incapaz de actualizar esta categoría.",
"error.unable_to_update_feed": "Incapaz de actualizar esta fuente.",
"error.unable_to_update_user": "Incapaz de actualizar este usuario.",
"error.unlink_account_without_password": "Debe definir una contraseña, de lo contrario no podrá volver a iniciar sesión.",
"error.user_already_exists": "Este usuario ya existe.",
"error.user_mandatory_fields": "El nombre de usuario es obligatorio.",
"error.linktaco_missing_required_fields": "LinkTaco API Token y Organization Slug son obligatorios.",
"form.api_key.label.description": "Etiqueta de clave API",
"form.category.hide_globally": "Ocultar artículos en la lista global de no leídos",
"form.category.label.title": "Título",
"form.feed.fieldset.general": "General",
"form.feed.fieldset.integration": "Servicios de terceros",
"form.feed.fieldset.network_settings": "Ajustes de red",
"form.feed.fieldset.rules": "Reglas",
"form.feed.label.allow_self_signed_certificates": "Permitir certificados autofirmados o no válidos",
"form.feed.label.apprise_service_urls": "Lista separada por comas de las URL del servicio Apprise",
"form.feed.label.block_filter_entry_rules": "Reglas de Bloqueo de Entradas",
"form.feed.label.blocklist_rules": "Filtros de Bloqueo Basados en Regex",
"form.feed.label.category": "Categoría",
"form.feed.label.cookie": "Configurar las cookies",
"form.feed.label.crawler": "Obtener rastreador original",
"form.feed.label.description": "Descripción",
"form.feed.label.disable_http2": "Deshabilite HTTP/2 para evitar huellas digitales",
"form.feed.label.disabled": "No actualice este feed",
"form.feed.label.feed_password": "Contraseña de la fuente",
"form.feed.label.feed_url": "URL de la fuente",
"form.feed.label.feed_username": "Nombre de usuario de la fuente",
"form.feed.label.fetch_via_proxy": "Usar el proxy configurado a nivel de la aplicación",
"form.feed.label.hide_globally": "Ocultar artículos en la lista global de no leídos",
"form.feed.label.ignore_http_cache": "Ignorar caché HTTP",
"form.feed.label.keep_filter_entry_rules": "Reglas de Permitir Entradas",
"form.feed.label.keeplist_rules": "Filtros de Mantener Basados en Regex",
"form.feed.label.no_media_player": "Sin reproductor multimedia (audio/video)",
"form.feed.label.ntfy_activate": "Enviar entradas a ntfy",
"form.feed.label.ntfy_default_priority": "Prioridad predeterminada a Ntfy",
"form.feed.label.ntfy_high_priority": "Prioridad alta a Ntfy",
"form.feed.label.ntfy_low_priority": "Prioridad baja a Ntfy",
"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.proxy_url": "URL del Proxy",
"form.feed.label.pushover_activate": "Enviar artículos a pushover.net",
"form.feed.label.pushover_default_priority": "Prioridad predeterminada de Pushover",
"form.feed.label.pushover_high_priority": "Prioridad alta de Pushover",
"form.feed.label.pushover_low_priority": "Prioridad baja de Pushover",
"form.feed.label.pushover_max_priority": "Prioridad máxima de Pushover",
"form.feed.label.pushover_min_priority": "Prioridad mínima de Pushover",
"form.feed.label.pushover_priority": "Prioridad del mensaje de Pushover",
"form.feed.label.rewrite_rules": "Reglas de Reescritura de Contenido",
"form.feed.label.scraper_rules": "Reglas de extracción de información",
"form.feed.label.site_url": "URL del sitio",
"form.feed.label.title": "Título",
"form.feed.label.urlrewrite_rules": "Reglas de Filtrado (Reescritura)",
"form.feed.label.user_agent": "Invalidar el agente de usuario predeterminado",
"form.feed.label.webhook_url": "Invalidar la URL del webhook",
"form.import.label.file": "Archivo OPML",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Enviar artículos a Apprise",
"form.integration.apprise_services_url": "Lista separada por comas de las URL del servicio Apprise",
"form.integration.apprise_url": "URL de la API de Apprise",
"form.integration.betula_activate": "Guardar artículos en Betula",
"form.integration.betula_token": "Token de Betula",
"form.integration.betula_url": "URL del servidor Betula",
"form.integration.cubox_activate": "Guardar artículos en Cubox",
"form.integration.cubox_api_link": "Enlace de la API de Cubox",
"form.integration.discord_activate": "Enviar artículos a Discord",
"form.integration.discord_webhook_link": "URL de la Webhook de Discord",
"form.integration.espial_activate": "Enviar artículos a Espial",
"form.integration.espial_api_key": "Clave de API de Espial",
"form.integration.espial_endpoint": "Acceso API de Espial",
"form.integration.espial_tags": "Etiquetas de Espial",
"form.integration.fever_activate": "Activar API de Fever",
"form.integration.fever_endpoint": "Acceso API de Fever:",
"form.integration.fever_password": "Contraseña de Fever",
"form.integration.fever_username": "Nombre de usuario de Fever",
"form.integration.googlereader_activate": "Activar API de Google Reader",
"form.integration.googlereader_endpoint": "Acceso API de Google Reader:",
"form.integration.googlereader_password": "Contraseña de Google Reader",
"form.integration.googlereader_username": "Nombre de usuario de Google Reader",
"form.integration.instapaper_activate": "Enviar artículos a Instapaper",
"form.integration.instapaper_password": "Contraseña de Instapaper",
"form.integration.instapaper_username": "Nombre de usuario de Instapaper",
"form.integration.karakeep_activate": "Enviar artículos a Karakeep",
"form.integration.karakeep_api_key": "Clave de API de Karakeep",
"form.integration.karakeep_url": "Acceso API de Karakeep",
"form.integration.linkace_activate": "Guardar artículos en LinkAce",
"form.integration.linkace_api_key": "Clave API de LinkAce",
"form.integration.linkace_check_disabled": "Deshabilitar la comprobación de enlace",
"form.integration.linkace_endpoint": "LinkAce API Endpoint",
"form.integration.linkace_is_private": "Marcar enlace como privado",
"form.integration.linkace_tags": "Etiquetas de LinkAce",
"form.integration.linkding_activate": "Enviar artículos a Linkding",
"form.integration.linkding_api_key": "Clave de API de Linkding",
"form.integration.linkding_bookmark": "Marcar marcador como no leído",
"form.integration.linkding_endpoint": "Acceso API de Linkding",
"form.integration.linkding_tags": "Etiquetas de Linkding",
"form.integration.linktaco_activate": "Guardar entradas en LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Obtenga su token de acceso personal en",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Etiquetas (máx. 10, separadas por comas)",
"form.integration.linktaco_tags_hint": "Máximo 10 etiquetas, separadas por comas",
"form.integration.linktaco_visibility": "Visibilidad",
"form.integration.linktaco_visibility_public": "Público",
"form.integration.linktaco_visibility_private": "Privado",
"form.integration.linktaco_visibility_hint": "La visibilidad PRIVADA requiere una cuenta de pago de LinkTaco",
"form.integration.linkwarden_activate": "Enviar artículos a Linkwarden",
"form.integration.linkwarden_api_key": "Clave de API de Linkwarden",
"form.integration.linkwarden_endpoint": "URL base de Linkwarden",
"form.integration.matrix_bot_activate": "Transferir nuevos artículos a Matrix",
"form.integration.matrix_bot_chat_id": "ID de la sala de Matrix",
"form.integration.matrix_bot_password": "Contraseña para el usuario de Matrix",
"form.integration.matrix_bot_url": "URL del servidor de Matrix",
"form.integration.matrix_bot_user": "Nombre de usuario para Matrix",
"form.integration.notion_activate": "Guardar entradas en Notion",
"form.integration.notion_page_id": "ID de página de Notion",
"form.integration.notion_token": "Token secreto de Notion",
"form.integration.ntfy_activate": "Enviar artículos a ntfy",
"form.integration.ntfy_api_token": "Token de API de Ntfy (opcional)",
"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 (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",
"form.integration.nunux_keeper_api_key": "Clave de API de Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Acceso API de Nunux Keeper",
"form.integration.omnivore_activate": "Enviar artículos a Omnivore",
"form.integration.omnivore_api_key": "Clave de API de Omnivore",
"form.integration.omnivore_url": "Acceso API de Omnivore",
"form.integration.pinboard_activate": "Enviar artículos a Pinboard",
"form.integration.pinboard_bookmark": "Marcar marcador como no leído",
"form.integration.pinboard_tags": "Etiquetas de Pinboard",
"form.integration.pinboard_token": "Token de API de Pinboard",
"form.integration.pushover_activate": "Enviar artículos a Pushover",
"form.integration.pushover_device": "Dispositivo Pushover (opcional)",
"form.integration.pushover_prefix": "Prefijo de URL de Pushover (opcional)",
"form.integration.pushover_token": "Token de API de la aplicación Pushover",
"form.integration.pushover_user": "Clave de usuario de Pushover",
"form.integration.raindrop_activate": "Guardar artículos en Raindrop",
"form.integration.raindrop_collection_id": "Colección ID",
"form.integration.raindrop_tags": "Etiquetas (separadas por comas)",
"form.integration.raindrop_token": "(Test) Token",
"form.integration.readeck_activate": "Enviar artículos a Readeck",
"form.integration.readeck_api_key": "Clave de API de Readeck",
"form.integration.readeck_endpoint": "Acceso API de Readeck",
"form.integration.readeck_labels": "Etiquetas de Readeck",
"form.integration.readeck_only_url": "Enviar solo URL (en lugar de contenido completo)",
"form.integration.readwise_activate": "Guardar artículos en Readwise Reader",
"form.integration.readwise_api_key": "Token de acceso a Readwise Reader",
"form.integration.readwise_api_key_link": "Obtener tu token de acceso a Readwise",
"form.integration.rssbridge_activate": "Vericar RSS-Bridge al agregar suscripciones",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "URL del servidro RSS-Bridge",
"form.integration.shaarli_activate": "Guardar artículos en Shaarli",
"form.integration.shaarli_api_secret": "Secreto API de Shaarli",
"form.integration.shaarli_endpoint": "URL de Shaarli",
"form.integration.shiori_activate": "Guardar artículos a Shiori",
"form.integration.shiori_endpoint": "Extremo de API de Shiori",
"form.integration.shiori_password": "Contraseña de Shiori",
"form.integration.shiori_username": "Nombre de usuario de Shiori",
"form.integration.slack_activate": "Enviar artículos a Slack",
"form.integration.slack_webhook_link": "URL de la Webhook de Slack",
"form.integration.telegram_bot_activate": "Envíe nuevos artículos al chat de Telegram",
"form.integration.telegram_bot_disable_buttons": "Deshabilitar botones",
"form.integration.telegram_bot_disable_notification": "Deshabilitar notificación",
"form.integration.telegram_bot_disable_web_page_preview": "Deshabilitar la vista previa de la página web",
"form.integration.telegram_bot_token": "Token de bot",
"form.integration.telegram_chat_id": "ID de chat",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "Enviar artículos a Wallabag",
"form.integration.wallabag_client_id": "ID de cliente de Wallabag",
"form.integration.wallabag_client_secret": "Secreto de cliente de Wallabag",
"form.integration.wallabag_endpoint": "URL base de Wallabag",
"form.integration.wallabag_only_url": "Enviar solo URL (en lugar de contenido completo)",
"form.integration.wallabag_password": "Contraseña de Wallabag",
"form.integration.wallabag_username": "Nombre de usuario de Wallabag",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Habilitar Webhooks",
"form.integration.webhook_secret": "Secreto de Webhooks",
"form.integration.webhook_url": "Defecto URL de Webhook",
"form.prefs.fieldset.application_settings": "Ajustes de la aplicación",
"form.prefs.fieldset.authentication_settings": "Ajustes de la autentificación",
"form.prefs.fieldset.global_feed_settings": "Ajustes globales del feed",
"form.prefs.fieldset.reader_settings": "Ajustes del lector",
"form.prefs.help.external_font_hosts": "Lista separada por espacios de hosts de fuentes externas permitidos. Por ejemplo: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.always_open_external_links": "Leer artículos abriendo enlaces externos",
"form.prefs.label.categories_sorting_order": "Clasificación por categorías",
"form.prefs.label.cjk_reading_speed": "Velocidad de lectura en chino, coreano y japonés (caracteres por minuto)",
"form.prefs.label.custom_css": "CSS personalizado",
"form.prefs.label.custom_js": "JavaScript personalizado",
"form.prefs.label.default_home_page": "Página de inicio por defecto",
"form.prefs.label.default_reading_speed": "Velocidad de lectura de otras lenguas (palabras por minuto)",
"form.prefs.label.display_mode": "Modo de visualización de aplicación web progresiva (PWA)",
"form.prefs.label.entries_per_page": "Artículos por página",
"form.prefs.label.entry_order": "Columna de clasificación de artículos",
"form.prefs.label.entry_sorting": "Clasificación de artículos",
"form.prefs.label.entry_swipe": "Habilitar deslizamiento de entrada en pantallas táctiles",
"form.prefs.label.external_font_hosts": "Hosts de fuentes externas",
"form.prefs.label.gesture_nav": "Gesto para navegar entre entradas",
"form.prefs.label.keyboard_shortcuts": "Habilitar atajos de teclado",
"form.prefs.label.language": "Idioma",
"form.prefs.label.mark_read_manually": "Marcar entradas como leídas manualmente",
"form.prefs.label.mark_read_on_media_completion": "Marcar como leído solo cuando la reproducción de audio/video alcance el 90%% de finalización",
"form.prefs.label.mark_read_on_view": "Marcar automáticamente las entradas como leídas cuando se vean",
"form.prefs.label.mark_read_on_view_or_media_completion": "Marcar las entradas como leídas cuando se vean. Para audio/video, marcar como leído al 90%% de finalización",
"form.prefs.label.media_playback_rate": "Velocidad de reproducción del audio/vídeo",
"form.prefs.label.open_external_links_in_new_tab": "Abrir enlaces externos en una nueva pestaña (agrega target=\"_blank\" a los enlaces)",
"form.prefs.label.show_reading_time": "Mostrar el tiempo estimado de lectura de los artículos",
"form.prefs.label.theme": "Tema",
"form.prefs.label.timezone": "Zona horaria",
"form.prefs.select.alphabetical": "Alfabético",
"form.prefs.select.browser": "Navegador",
"form.prefs.select.created_time": "Hora de creación del artículo",
"form.prefs.select.fullscreen": "Pantalla completa",
"form.prefs.select.minimal_ui": "Mínimo",
"form.prefs.select.none": "Ninguno",
"form.prefs.select.older_first": "Artículos antiguos primero",
"form.prefs.select.publish_time": "Hora de publicación del artículo",
"form.prefs.select.recent_first": "Artículos recientes primero",
"form.prefs.select.standalone": "Autónomo",
"form.prefs.select.swipe": "Golpe fuerte",
"form.prefs.select.tap": "Doble toque",
"form.prefs.select.unread_count": "Recuento de no leídos",
"form.submit.loading": "Cargando...",
"form.submit.saving": "Guardando...",
"form.user.label.admin": "Administrador",
"form.user.label.confirmation": "Confirmación de contraseña",
"form.user.label.password": "Contraseña",
"form.user.label.username": "Nombre de usuario",
"menu.about": "Acerca de",
"menu.add_feed": "Agregar fuente",
"menu.add_user": "Agregar usuario",
"menu.api_keys": "Claves API",
"menu.categories": "Categorías",
"menu.create_api_key": "Crear una nueva clave API",
"menu.create_category": "Crear una categoría",
"menu.edit_category": "Editar",
"menu.edit_feed": "Editar",
"menu.export": "Exportar",
"menu.feed_entries": "Artículos",
"menu.feeds": "Fuentes",
"menu.flush_history": "Borrar historial",
"menu.history": "Historial",
"menu.home_page": "Página de inicio",
"menu.import": "Importar",
"menu.integrations": "Integraciones",
"menu.logout": "Cerrar sesión",
"menu.mark_all_as_read": "Marcar todos como leídos",
"menu.mark_page_as_read": "Marcar esta página como leída",
"menu.preferences": "Preferencias",
"menu.refresh_all_feeds": "Refrescar todas las fuentes en segundo plano",
"menu.refresh_feed": "Refrescar",
"menu.search": "Buscar",
"menu.sessions": "Sesiones",
"menu.settings": "Configuración",
"menu.shared_entries": "Artículos compartidos",
"menu.show_all_entries": "Mostrar todos los artículos",
"menu.show_only_starred_entries": "Mostrar solo los artículos marcados con una estrella",
"menu.show_only_unread_entries": "Mostrar solo los artículos no leídos",
"menu.starred": "Marcadores",
"menu.title": "Menú",
"menu.unread": "No leídos",
"menu.users": "Usuarios",
"page.about.author": "Autor:",
"page.about.build_date": "Fecha de compilación:",
"page.about.credits": "Créditos",
"page.about.db_usage": "Tamaño de la base de datos:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "Opciones de configuración global",
"page.about.go_version": "Go versión:",
"page.about.license": "Licencia:",
"page.about.postgres_version": "Postgres versión:",
"page.about.title": "Acerca de",
"page.about.version": "Versión:",
"page.add_feed.choose_feed": "Elegir una fuente",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Opciones avanzadas",
"page.add_feed.no_category": "No hay categoría. Debe tener al menos una categoría.",
"page.add_feed.submit": "Encontrar una fuente",
"page.add_feed.title": "Nueva fuente",
"page.api_keys.never_used": "Nunca usado",
"page.api_keys.table.actions": "Acciones",
"page.api_keys.table.created_at": "Fecha de creación",
"page.api_keys.table.description": "Descripción",
"page.api_keys.table.last_used_at": "Último utilizado",
"page.api_keys.table.token": "simbólico",
"page.api_keys.title": "Claves API",
"page.categories.entries": "Artículos",
"page.categories.feed_count": [
"Hay %d fuente.",
"Hay %d fuentes."
],
"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:",
"page.edit_feed.last_check": "Última verificación:",
"page.edit_feed.last_modified_header": "Cabecera de LastModified:",
"page.edit_feed.last_parsing_error": "Último error de análisis",
"page.edit_feed.no_header": "Sin cabecera",
"page.edit_feed.title": "Editar fuente: %s",
"page.edit_user.title": "Editar usuario: %s",
"page.entry.attachments": "Archivos adjuntos",
"page.feeds.error_count": [
"%d error",
"%d errores"
],
"page.feeds.last_check": "Última verificación:",
"page.feeds.next_check": "Próxima verificación:",
"page.feeds.read_counter": "Número de artículos leídos",
"page.feeds.title": "Fuentes",
"page.footer.elevator": "Back to top",
"page.history.title": "Historial",
"page.import.title": "Importar",
"page.integration.bookmarklet": "Marcapáginas",
"page.integration.bookmarklet.help": "Este enlace especial te permite suscribirte a un sitio de web directamente usando un marcador del navegador.",
"page.integration.bookmarklet.instructions": "Arrastrar y soltar este enlace a tus marcadores del navegador.",
"page.integration.bookmarklet.name": "Agregar a Miniflux",
"page.integration.miniflux_api": "API de Miniflux",
"page.integration.miniflux_api_endpoint": "Extremo de API",
"page.integration.miniflux_api_password": "Contraseña",
"page.integration.miniflux_api_password_value": "Contraseña de tu cuenta",
"page.integration.miniflux_api_username": "Nombre de usuario",
"page.integrations.title": "Integraciones",
"page.keyboard_shortcuts.close_modal": "Cerrar el cuadro de diálogo modal",
"page.keyboard_shortcuts.download_content": "Descargar el contenido original",
"page.keyboard_shortcuts.go_to_bottom_item": "Ir al elemento inferior",
"page.keyboard_shortcuts.go_to_categories": "Ir a las categorías",
"page.keyboard_shortcuts.go_to_feed": "Ir a la fuente",
"page.keyboard_shortcuts.go_to_feeds": "Ir a las fuentes",
"page.keyboard_shortcuts.go_to_history": "Ir al historial",
"page.keyboard_shortcuts.go_to_next_item": "Ir al elemento siguiente",
"page.keyboard_shortcuts.go_to_next_page": "Ir al página siguiente",
"page.keyboard_shortcuts.go_to_previous_item": "Ir al elemento anterior",
"page.keyboard_shortcuts.go_to_previous_page": "Ir al página anterior",
"page.keyboard_shortcuts.go_to_search": "Centrarse en el cuadro de búsqueda",
"page.keyboard_shortcuts.go_to_settings": "Ir a la configuración",
"page.keyboard_shortcuts.go_to_starred": "Ir a los marcadores",
"page.keyboard_shortcuts.go_to_top_item": "Ir al elemento superior",
"page.keyboard_shortcuts.go_to_unread": "Ir a los no leídos",
"page.keyboard_shortcuts.mark_page_as_read": "Marcar página actual como leída",
"page.keyboard_shortcuts.open_comments": "Abrir el enlace de comentarios",
"page.keyboard_shortcuts.open_comments_same_window": "Abrir enlace de comentarios en la pestaña actual",
"page.keyboard_shortcuts.open_item": "Abrir el elemento seleccionado",
"page.keyboard_shortcuts.open_original": "Abrir el enlace original",
"page.keyboard_shortcuts.open_original_same_window": "Abrir enlace original en la pestaña actual",
"page.keyboard_shortcuts.refresh_all_feeds": "Refrescar todas las fuentes en segundo plano",
"page.keyboard_shortcuts.remove_feed": "Quitar esta fuente",
"page.keyboard_shortcuts.save_article": "Guardar artículo",
"page.keyboard_shortcuts.scroll_item_to_top": "Desplazar elemento hacia arriba",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Mostrar atajos de teclado",
"page.keyboard_shortcuts.subtitle.actions": "Acciones",
"page.keyboard_shortcuts.subtitle.items": "Navegación de artículos",
"page.keyboard_shortcuts.subtitle.pages": "Navegación de páginas",
"page.keyboard_shortcuts.subtitle.sections": "Navegación de secciones",
"page.keyboard_shortcuts.title": "Atajos de teclado",
"page.keyboard_shortcuts.toggle_star_status": "Agregar o quitar marcador",
"page.keyboard_shortcuts.toggle_entry_attachments": "Alternar abrir/cerrar adjuntos de la entrada",
"page.keyboard_shortcuts.toggle_read_status_next": "Marcar como leído o no leído, enfoque siguiente",
"page.keyboard_shortcuts.toggle_read_status_prev": "Marcar como leído o no leído, foco anterior",
"page.login.google_signin": "Iniciar sesión con tu cuenta de Google",
"page.login.oidc_signin": "Iniciar sesión con tu cuenta de %s",
"page.login.title": "Iniciar sesión",
"page.login.webauthn_login": "Iniciar sesión con clave de acceso",
"page.login.webauthn_login.error": "No se puede iniciar sesión con la clave de acceso",
"page.login.webauthn_login.help": "Por favor, introduce tu nombre de usuario si usas una clave de seguridad. Esto no es necesario si usas una Passkey (credenciales detectables).",
"page.new_api_key.title": "Nueva clave API",
"page.new_category.title": "Nueva categoría",
"page.new_user.title": "Nuevo usuario",
"page.offline.message": "Estas desconectado",
"page.offline.refresh_page": "Intenta actualizar la página",
"page.offline.title": "Modo offline",
"page.read_entry_count": [
"%d artículo leído",
"%d artículos leídos"
],
"page.search.title": "Resultados de la búsqueda",
"page.sessions.table.actions": "Acciones",
"page.sessions.table.current_session": "Sesión actual",
"page.sessions.table.date": "Fecha",
"page.sessions.table.ip": "Dirección de IP",
"page.sessions.table.user_agent": "Agente de usuario",
"page.sessions.title": "Sesiones",
"page.settings.link_google_account": "Vincular mi cuenta de Google",
"page.settings.link_oidc_account": "Vincular mi cuenta de %s",
"page.settings.title": "Ajustes",
"page.settings.unlink_google_account": "Desvincular mi cuenta de Google",
"page.settings.unlink_oidc_account": "Desvincular mi cuenta de %s",
"page.settings.webauthn.actions": "Acciones",
"page.settings.webauthn.added_on": "Añadido",
"page.settings.webauthn.delete": [
"Eliminar %d clave de acceso",
"Eliminar %d claves de acceso"
],
"page.settings.webauthn.last_seen_on": "Usado por última vez",
"page.settings.webauthn.passkey_name": "Nombre de clave de acceso",
"page.settings.webauthn.passkeys": "Claves de acceso",
"page.settings.webauthn.register": "Registrar clave de acceso",
"page.settings.webauthn.register.error": "No se puede registrar la clave de acceso",
"page.shared_entries.title": "Artículos compartidos",
"page.shared_entries_count": [
"%d artículo compartido",
"%d artículos compartidos"
],
"page.starred.title": "Marcadores",
"page.starred_entry_count": [
"%d artículo marcado",
"%d artículos marcados"
],
"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.users.actions": "Acciones",
"page.users.admin.no": "No",
"page.users.admin.yes": "Sí",
"page.users.is_admin": "Administrador",
"page.users.last_login": "Último ingreso",
"page.users.never_logged": "Nunca",
"page.users.title": "Usuarios",
"page.users.username": "Nombre de usuario",
"page.webauthn_rename.title": "Renombrar clave de acceso",
"pagination.first": "Primero",
"pagination.last": "Último",
"pagination.next": "Siguiente",
"pagination.previous": "Anterior",
"search.label": "Buscar",
"search.placeholder": "Búsqueda...",
"search.submit": "Buscar",
"skip_to_content": "Saltar al contenido",
"time_elapsed.days": [
"hace %d día",
"hace %d días"
],
"time_elapsed.hours": [
"hace %d hora",
"hace %d horas"
],
"time_elapsed.minutes": [
"hace %d minuto",
"hace %d minutos"
],
"time_elapsed.months": [
"hace %d mes",
"hace %d meses"
],
"time_elapsed.not_yet": "todavía no",
"time_elapsed.now": "ahora mismo",
"time_elapsed.weeks": [
"hace %d semana",
"hace %d semanas"
],
"time_elapsed.years": [
"hace %d año",
"hace %d años"
],
"time_elapsed.yesterday": "ayer",
"tooltip.keyboard_shortcuts": "Atajo de teclado: %s",
"tooltip.logged_user": "Registrado como %s"
} v2-2.2.13/internal/locale/translations/fi_FI.json 0000664 0000000 0000000 00000112720 15062123773 0021602 0 ustar 00root root 0000000 0000000 {
"action.cancel": "peru",
"action.download": "Lataa",
"action.edit": "Muokkaa",
"action.home_screen": "Lisää aloitusnäytölle",
"action.import": "Tuo",
"action.login": "Kirjaudu sisään",
"action.or": "tai",
"action.remove": "Poista",
"action.remove_feed": "Poista tämä syöte",
"action.save": "Tallenna",
"action.subscribe": "Tilaa",
"action.update": "Päivitä",
"alert.account_linked": "Ulkoinen tilisi on nyt linkitetty!",
"alert.account_unlinked": "Ulkoinen tilisi on nyt irrotettu!",
"alert.background_feed_refresh": "Kaikki syötteet päivitetään taustalla. Voit jatkaa Minifluxin käyttöä tämän prosessin aikana.",
"alert.feed_error": "Tässä syötteessä on ongelma",
"alert.no_starred": "Tällä hetkellä ei ole kirjanmerkkiä.",
"alert.no_category": "Ei ole kategoriaa.",
"alert.no_category_entry": "Tässä kategoriassa ei ole artikkeleita.",
"alert.no_feed": "Sinulla ei ole tilauksia.",
"alert.no_feed_entry": "Tässä syötteessä ei ole artikkeleita.",
"alert.no_feed_in_category": "Tälle kategorialle ei ole tilausta.",
"alert.no_history": "Tällä hetkellä ei ole historiaa.",
"alert.no_search_result": "Ei hakua vastaavia tuloksia.",
"alert.no_shared_entry": "Jaettua artikkelia ei ole.",
"alert.no_tag_entry": "Tätä tunnistetta vastaavia merkintöjä ei ole.",
"alert.no_unread_entry": "Ei ole lukemattomia artikkeleita.",
"alert.no_user": "Olet ainoa käyttäjä.",
"alert.prefs_saved": "Asetukset tallennettu!",
"alert.too_many_feeds_refresh": [
"Olet käynnistänyt liian monta syötteen päivitystä. Odota %d minuutti ennen kuin yrität uudelleen.",
"Olet käynnistänyt liian monta syötteen päivitystä. Odota %d minuuttia ennen kuin yrität uudelleen."
],
"confirm.loading": "Käynnissä...",
"confirm.no": "ei",
"confirm.question": "Oletko varma?",
"confirm.question.refresh": "Haluatko pakottaa päivityksen?",
"confirm.yes": "kyllä",
"enclosure_media_controls.seek": "Siirry:",
"enclosure_media_controls.seek.title": "Siirry %s sekuntia",
"enclosure_media_controls.speed": "Nopeus:",
"enclosure_media_controls.speed.faster": "Nopeammin",
"enclosure_media_controls.speed.faster.title": "Nopeampi %sx",
"enclosure_media_controls.speed.reset": "Palauta",
"enclosure_media_controls.speed.reset.title": "Palauta nopeus 1x",
"enclosure_media_controls.speed.slower": "Hitaammin",
"enclosure_media_controls.speed.slower.title": "Hitaampi %sx",
"entry.starred.toast.off": "Tähdettömät",
"entry.starred.toast.on": "Tähdellä merkityt",
"entry.starred.toggle.off": "Poista suosikeista",
"entry.starred.toggle.on": "Lisää suosikkeihin",
"entry.comments.label": "Kommentit",
"entry.comments.title": "Näytä kommentit",
"entry.estimated_reading_time": [
"%d minuutin lukuaika",
"%d minuutin lukuaika"
],
"entry.external_link.label": "Ulkoinen linkki",
"entry.save.completed": "Valmis!",
"entry.save.label": "Tallenna",
"entry.save.title": "Tallenna tämä artikkeli",
"entry.save.toast.completed": "Artikkeli tallennettu",
"entry.scraper.completed": "Valmis!",
"entry.scraper.label": "Lataa",
"entry.scraper.title": "Nouda alkuperäinen sisältö",
"entry.share.label": "Jaa",
"entry.share.title": "Jaa tämä artikkeli",
"entry.shared_entry.label": "Jaa",
"entry.shared_entry.title": "Avaa julkinen linkki",
"entry.state.loading": "Ladataan...",
"entry.state.saving": "Tallennetaan...",
"entry.status.mark_as_read": "Merkitse luetuksi",
"entry.status.mark_as_unread": "Merkitse lukemattomaksi",
"entry.status.title": "Vaihda artikkelin tilaa",
"entry.status.toast.read": "Merkitty luetuksi",
"entry.status.toast.unread": "Merkitty lukemattomaksi",
"entry.tags.label": "Tunnisteet:",
"entry.tags.more_tags_label": [
"Näytä %d lisää tunnistetta",
"Näytä %d lisää tunnisteita"
],
"entry.unshare.label": "Poista jako",
"error.api_key_already_exists": "API-avain on jo olemassa.",
"error.bad_credentials": "Virheellinen käyttäjänimi tai salasana.",
"error.category_already_exists": "Kategoria on jo olemassa. ",
"error.category_not_found": "Tämä kategoria ei ole olemassa tai se ei kuulu tälle käyttäjälle.",
"error.database_error": "Tietokantavirhe: %v.",
"error.different_passwords": "Salasanat eivät ole samat.",
"error.duplicate_fever_username": "Joku muu käyttää jo samaa Fever-käyttäjänimeä!",
"error.duplicate_googlereader_username": "On jo joku muu, jolla on sama Google-syötteenlukijan käyttäjätunnus!",
"error.duplicate_linked_account": "Joku on jo yhdistetty tähän palveluntarjoajaan!",
"error.duplicated_feed": "Tämä syöte on jo olemassa.",
"error.empty_file": "Tiedosto on tyhjä.",
"error.entries_per_page_invalid": "Artikkelien määrä sivulla ei kelpaa.",
"error.feed_already_exists": "Tämä syöte on jo olemassa.",
"error.feed_category_not_found": "Tätä kategoriaa ei ole olemassa tai se ei kuulu tälle käyttäjälle.",
"error.feed_format_not_detected": "Syötteen muotoa ei voitu tunnistaa: %v.",
"error.feed_invalid_blocklist_rule": "Estolistan sääntö on virheellinen.",
"error.feed_invalid_keeplist_rule": "Säilytettävien listan sääntö on virheellinen.",
"error.feed_mandatory_fields": "URL-osoite ja kategoria ovat pakollisia.",
"error.feed_not_found": "Tämä syöte ei ole olemassa tai se ei kuulu tälle käyttäjälle.",
"error.feed_title_not_empty": "Syötteen otsikko ei voi olla tyhjä.",
"error.feed_url_not_empty": "Syötteen URL-osoite ei voi olla tyhjä.",
"error.fields_mandatory": "Kaikki kentät ovat pakollisia.",
"error.http_bad_gateway": "Verkkosivusto ei ole tällä hetkellä saatavilla huonon yhdyskäytävän virheen vuoksi. Ongelma ei ole Miniflux-puolella. Yritä uudelleen myöhemmin.",
"error.http_body_read": "Unable to read the HTTP body: %v.",
"error.http_client_error": "HTTP client error: %v.",
"error.http_empty_response": "The HTTP response is empty. Perhaps, this website is using a bot protection mechanism?",
"error.http_empty_response_body": "The HTTP response body is empty.",
"error.http_forbidden": "Access to this website is forbidden. Perhaps, this website has a bot protection mechanism?",
"error.http_gateway_timeout": "The website is not available at the moment due to a gateway timeout error. The problem is not on Miniflux side. Please, try again later.",
"error.http_internal_server_error": "The website is not available at the moment due to a server error. The problem is not on Miniflux side. Please, try again later.",
"error.http_not_authorized": "Access to this website is not authorized. It could be a bad username or password.",
"error.http_resource_not_found": "The requested resource is not found. Please, verify the URL.",
"error.http_response_too_large": "The HTTP response is too large. You could increase the HTTP response size limit in the global settings (requires a server restart).",
"error.http_service_unavailable": "The website is not available at the moment due to an internal server error. The problem is not on Miniflux side. Please, try again later.",
"error.http_too_many_requests": "Miniflux generated too many requests to this website. Please, try again later or change the application configuration.",
"error.http_unexpected_status_code": "The website is not available at the moment due to an unexpected HTTP status code: %d. The problem is not on Miniflux side. Please, try again later.",
"error.invalid_categories_sorting_order": "Virheellinen kategorioiden lajittelujärjestys.",
"error.invalid_default_home_page": "Väärä oletusarvoinen kotisivu!",
"error.invalid_display_mode": "Virheellinen verkkosovelluksen näyttötila.",
"error.invalid_entry_direction": "Invalid entry direction.",
"error.invalid_entry_order": "Virheellinen artikkelin lajittelu.",
"error.invalid_feed_proxy_url": "Invalid proxy URL.",
"error.invalid_feed_url": "Virheellinen syötteen URL-osoite.",
"error.invalid_gesture_nav": "Virheellinen ele-navigointi.",
"error.invalid_language": "Virheellinen kieli.",
"error.invalid_site_url": "Virheellinen sivuston URL-osoite.",
"error.invalid_theme": "Virheellinen teema.",
"error.invalid_timezone": "Virheellinen aikavyöhyke.",
"error.network_operation": "Miniflux is not able to reach this website due to a network error: %v.",
"error.network_timeout": "This website is too slow and the request timed out: %v",
"error.password_min_length": "Salasanassa on oltava vähintään 6 merkkiä.",
"error.proxy_url_not_empty": "The proxy URL cannot be empty.",
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
"error.settings_invalid_domain_list": "Invalid domain list. Please provide a space separated list of domains.",
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
"error.settings_mandatory_fields": "Käyttäjätunnus, teema, kieli ja aikavyöhyke ovat pakollisia.",
"error.settings_media_playback_rate_range": "Toistonopeus on alueen ulkopuolella",
"error.settings_reading_speed_is_positive": "Lukunopeuksien on oltava positiivisia kokonaislukuja.",
"error.site_url_not_empty": "Sivuston URL-osoite ei voi olla tyhjä.",
"error.subscription_not_found": "Tilausta ei löydy.",
"error.title_required": "Otsikko on pakollinen.",
"error.tls_error": "TLS error: %q. You could disable TLS verification in the feed settings if you would like.",
"error.unable_to_create_api_key": "API-avainta ei voi luoda.",
"error.unable_to_create_category": "Kategoriaa ei voi luoda.",
"error.unable_to_create_user": "Käyttäjää ei voi luoda.",
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.unable_to_update_category": "Kategoriaa ei voi päivittää.",
"error.unable_to_update_feed": "Syötettä ei voi päivittää.",
"error.unable_to_update_user": "Käyttäjää ei voi päivittää.",
"error.unlink_account_without_password": "Sinun on määritettävä salasana, muuten et voi kirjautua uudelleen.",
"error.user_already_exists": "Käyttäjä on jo olemassa.",
"error.user_mandatory_fields": "Käyttäjätunnus on pakollinen.",
"error.linktaco_missing_required_fields": "LinkTaco API Token ja Organization Slug vaaditaan",
"form.api_key.label.description": "API Key Label",
"form.category.hide_globally": "Piilota artikkelit lukemattomien listassa",
"form.category.label.title": "Otsikko",
"form.feed.fieldset.general": "General",
"form.feed.fieldset.integration": "Third-Party Services",
"form.feed.fieldset.network_settings": "Network Settings",
"form.feed.fieldset.rules": "Rules",
"form.feed.label.allow_self_signed_certificates": "Salli itseallekirjoitetut tai virheelliset varmenteet",
"form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
"form.feed.label.block_filter_entry_rules": "Merkinnän estosäännöt",
"form.feed.label.blocklist_rules": "Regex-pohjaiset estosuodattimet",
"form.feed.label.category": "Kategoria",
"form.feed.label.cookie": "Aseta evästeet",
"form.feed.label.crawler": "Nouda alkuperäinen sisältö",
"form.feed.label.description": "Kuvaus",
"form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
"form.feed.label.disabled": "Älä päivitä tätä syötettä",
"form.feed.label.feed_password": "Syötteen salasana",
"form.feed.label.feed_url": "Syötteen URL-osoite",
"form.feed.label.feed_username": "Syötteen käyttäjätunnus",
"form.feed.label.fetch_via_proxy": "Käytä sovellustasolla määritettyä välityspalvelinta",
"form.feed.label.hide_globally": "Piilota artikkelit lukemattomien listassa",
"form.feed.label.ignore_http_cache": "Ohita HTTP-välimuisti",
"form.feed.label.keep_filter_entry_rules": "Merkinnän sallimissäännöt",
"form.feed.label.keeplist_rules": "Regex-pohjaiset säilytyssuodattimet",
"form.feed.label.no_media_player": "No media player (audio/video)",
"form.feed.label.ntfy_activate": "Push entries to ntfy",
"form.feed.label.ntfy_default_priority": "Ntfy default priority",
"form.feed.label.ntfy_high_priority": "Ntfy high priority",
"form.feed.label.ntfy_low_priority": "Ntfy low priority",
"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.proxy_url": "Proxy URL",
"form.feed.label.pushover_activate": "Push entries to pushover.net",
"form.feed.label.pushover_default_priority": "Pushover default priority",
"form.feed.label.pushover_high_priority": "Pushover high priority",
"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.rewrite_rules": "Sisällön uudelleenkirjoitussäännöt",
"form.feed.label.scraper_rules": "Scraper-säännöt",
"form.feed.label.site_url": "Sivuston URL-osoite",
"form.feed.label.title": "Otsikko",
"form.feed.label.urlrewrite_rules": "URL-osoitteen uudelleenkirjoitussäännöt",
"form.feed.label.user_agent": "Ohita oletuskäyttäjäagentti",
"form.feed.label.webhook_url": "Override webhook url",
"form.import.label.file": "OPML-tiedosto",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Push entries to Apprise",
"form.integration.apprise_services_url": "Comma separated list of Apprise service URLs",
"form.integration.apprise_url": "Apprise API URL",
"form.integration.betula_activate": "Save entries to Betula",
"form.integration.betula_token": "Betula Token",
"form.integration.betula_url": "Betula server URL",
"form.integration.cubox_activate": "Save entries to Cubox",
"form.integration.cubox_api_link": "Cubox API link",
"form.integration.discord_activate": "Push entries to Discord",
"form.integration.discord_webhook_link": "Discord Webhook link",
"form.integration.espial_activate": "Tallenna artikkelit Espialiin",
"form.integration.espial_api_key": "Espial API-avain",
"form.integration.espial_endpoint": "Espial API-päätepiste",
"form.integration.espial_tags": "Espial-tagit",
"form.integration.fever_activate": "Ota Fever API käyttöön",
"form.integration.fever_endpoint": "Fever API -päätepiste:",
"form.integration.fever_password": "Fever-salasana",
"form.integration.fever_username": "Fever-käyttäjätunnus",
"form.integration.googlereader_activate": "Aktivoi Google Reader API",
"form.integration.googlereader_endpoint": "Google Reader API -päätepiste:",
"form.integration.googlereader_password": "Google-lukijan salasana",
"form.integration.googlereader_username": "Google-lukijan käyttäjätunnus",
"form.integration.instapaper_activate": "Tallenna artikkelit Instapaperiin",
"form.integration.instapaper_password": "Instapaper-salasana",
"form.integration.instapaper_username": "Instapaper-käyttäjätunnus",
"form.integration.karakeep_activate": "Tallenna artikkelit Karakeepiin",
"form.integration.karakeep_api_key": "Karakeep API-avain",
"form.integration.karakeep_url": "Karakeep API-päätepiste",
"form.integration.linkace_activate": "Save entries to LinkAce",
"form.integration.linkace_api_key": "LinkAce API key",
"form.integration.linkace_check_disabled": "Disable link check",
"form.integration.linkace_endpoint": "LinkAce API Endpoint",
"form.integration.linkace_is_private": "Mark link as private",
"form.integration.linkace_tags": "LinkAce Tags",
"form.integration.linkding_activate": "Tallenna artikkelit Linkkiin",
"form.integration.linkding_api_key": "Linkding API-avain",
"form.integration.linkding_bookmark": "Merkitse kirjanmerkki lukemattomaksi",
"form.integration.linkding_endpoint": "Linkding API-päätepiste",
"form.integration.linkding_tags": "Linkding Tags",
"form.integration.linktaco_activate": "Tallenna kirjoituksia LinkTacoon",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Hanki henkilökohtainen pääsytunnistesi osoitteesta",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Tagit (enintään 10, pilkuilla erotettu)",
"form.integration.linktaco_tags_hint": "Enintään 10 tagia, pilkuilla erotettu",
"form.integration.linktaco_visibility": "Näkyvyys",
"form.integration.linktaco_visibility_public": "Julkinen",
"form.integration.linktaco_visibility_private": "Yksityinen",
"form.integration.linktaco_visibility_hint": "YKSITYINEN näkyvyys vaatii maksullisen LinkTaco-tilin",
"form.integration.linkwarden_activate": "Tallenna artikkelit Linkkiin",
"form.integration.linkwarden_api_key": "Linkwarden API-avain",
"form.integration.linkwarden_endpoint": "Linkwarden Base URL",
"form.integration.matrix_bot_activate": "Siirrä uudet artikkelit Matrixiin",
"form.integration.matrix_bot_chat_id": "Matrix-huoneen tunnus",
"form.integration.matrix_bot_password": "Matrix-käyttäjän salasana",
"form.integration.matrix_bot_url": "Matrix-palvelimen URL-osoite",
"form.integration.matrix_bot_user": "Matrixin käyttäjätunnus",
"form.integration.notion_activate": "Save entries to Notion",
"form.integration.notion_page_id": "Notion Page ID",
"form.integration.notion_token": "Notion Secret Token",
"form.integration.ntfy_activate": "Push entries to ntfy",
"form.integration.ntfy_api_token": "Ntfy API Token (optional)",
"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 (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",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API-avain",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API-päätepiste",
"form.integration.omnivore_activate": "Tallenna artikkelit Omnivoreiin",
"form.integration.omnivore_api_key": "Omnivore API-avain",
"form.integration.omnivore_url": "Omnivore API-päätepiste",
"form.integration.pinboard_activate": "Tallenna artikkelit Pinboardiin",
"form.integration.pinboard_bookmark": "Merkitse kirjanmerkki lukemattomaksi",
"form.integration.pinboard_tags": "Pinboard-tagit",
"form.integration.pinboard_token": "Pinboard API-tunnus",
"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.raindrop_activate": "Save entries to Raindrop",
"form.integration.raindrop_collection_id": "Collection ID",
"form.integration.raindrop_tags": "Tags (comma-separated)",
"form.integration.raindrop_token": "(Test) Token",
"form.integration.readeck_activate": "Tallenna artikkelit Readeckiin",
"form.integration.readeck_api_key": "Readeck API-avain",
"form.integration.readeck_endpoint": "Readeck API-päätepiste",
"form.integration.readeck_labels": "Readeck Labels",
"form.integration.readeck_only_url": "Lähetä vain URL-osoite (koko sisällön sijaan)",
"form.integration.readwise_activate": "Save entries to Readwise Reader",
"form.integration.readwise_api_key": "Readwise Reader Access Token",
"form.integration.readwise_api_key_link": "Get your Readwise Access Token",
"form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "RSS-Bridge server URL",
"form.integration.shaarli_activate": "Save articles to Shaarli",
"form.integration.shaarli_api_secret": "Shaarli API Secret",
"form.integration.shaarli_endpoint": "Shaarli URL",
"form.integration.shiori_activate": "Save articles to Shiori",
"form.integration.shiori_endpoint": "Shiori API Endpoint",
"form.integration.shiori_password": "Shiori Password",
"form.integration.shiori_username": "Shiori Username",
"form.integration.slack_activate": "Push entries to Slack",
"form.integration.slack_webhook_link": "Slack Webhook link",
"form.integration.telegram_bot_activate": "Lähetä uusia artikkeleita Telegram-chatiin",
"form.integration.telegram_bot_disable_buttons": "Disable buttons",
"form.integration.telegram_bot_disable_notification": "Disable notification",
"form.integration.telegram_bot_disable_web_page_preview": "Disable web page preview",
"form.integration.telegram_bot_token": "Bot-tunnus",
"form.integration.telegram_chat_id": "Chat ID",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "Tallenna artikkelit Wallabagiin",
"form.integration.wallabag_client_id": "Wallabag Client ID",
"form.integration.wallabag_client_secret": "Wallabag Client Secret",
"form.integration.wallabag_endpoint": "Wallabagin perus-URL-osoite",
"form.integration.wallabag_only_url": "Lähetä vain URL-osoite (koko sisällön sijaan)",
"form.integration.wallabag_password": "Wallabag-salasana",
"form.integration.wallabag_username": "Wallabag-käyttäjätunnus",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Enable Webhooks",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Default Webhook URL",
"form.prefs.fieldset.application_settings": "Application Settings",
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
"form.prefs.fieldset.reader_settings": "Reader Settings",
"form.prefs.help.external_font_hosts": "Space separated list of external font hosts to allow. For example: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.always_open_external_links": "Read articles by opening external links",
"form.prefs.label.categories_sorting_order": "Kategorioiden lajittelu",
"form.prefs.label.cjk_reading_speed": "Kiinan, Korean ja Japanin lukunopeus (merkkejä minuutissa)",
"form.prefs.label.custom_css": "Mukautettu CSS",
"form.prefs.label.custom_js": "Mukautettu JavaScript",
"form.prefs.label.default_home_page": "Oletusarvoinen etusivu",
"form.prefs.label.default_reading_speed": "Muiden kielten lukunopeus (sanaa minuutissa)",
"form.prefs.label.display_mode": "Progressive Web App (PWA) -näyttötila",
"form.prefs.label.entries_per_page": "Artikkelia sivulla",
"form.prefs.label.entry_order": "Lajittele sarakkeen mukaan",
"form.prefs.label.entry_sorting": "Lajittelu",
"form.prefs.label.entry_swipe": "Ota syöttöpyyhkäisy käyttöön kosketusnäytöissä",
"form.prefs.label.external_font_hosts": "External font hosts",
"form.prefs.label.gesture_nav": "Ele siirtyäksesi merkintöjen välillä",
"form.prefs.label.keyboard_shortcuts": "Ota pikanäppäimet käyttöön",
"form.prefs.label.language": "Kieli",
"form.prefs.label.mark_read_manually": "Mark entries as read manually",
"form.prefs.label.mark_read_on_media_completion": "Only mark as read when audio/video playback reaches 90%% completion",
"form.prefs.label.mark_read_on_view": "Merkitse kohdat automaattisesti luetuiksi, kun niitä tarkastellaan",
"form.prefs.label.mark_read_on_view_or_media_completion": "Mark entries as read when viewed. For audio/video, mark as read at 90%% completion",
"form.prefs.label.media_playback_rate": "Äänen/videon toistonopeus",
"form.prefs.label.open_external_links_in_new_tab": "Avaa ulkoiset linkit uuteen välilehteen (lisää target=\"_blank\" linkkeihin)",
"form.prefs.label.show_reading_time": "Näytä artikkeleiden arvioitu lukuaika",
"form.prefs.label.theme": "Teema",
"form.prefs.label.timezone": "Aikavyöhyke",
"form.prefs.select.alphabetical": "Aakkosjärjestys",
"form.prefs.select.browser": "Selain",
"form.prefs.select.created_time": "Luomisaika",
"form.prefs.select.fullscreen": "Kokoruututila",
"form.prefs.select.minimal_ui": "Minimaalinen",
"form.prefs.select.none": "Ei mitään",
"form.prefs.select.older_first": "Vanhin ensin",
"form.prefs.select.publish_time": "Julkaisuaika",
"form.prefs.select.recent_first": "Uusin ensin",
"form.prefs.select.standalone": "Itsenäinen tila",
"form.prefs.select.swipe": "Pyyhkäise",
"form.prefs.select.tap": "Kaksoisnapauta",
"form.prefs.select.unread_count": "Lukemattomien määrä",
"form.submit.loading": "Ladataan...",
"form.submit.saving": "Tallennetaan...",
"form.user.label.admin": "Ylläpitäjä",
"form.user.label.confirmation": "Salasanan vahvistus",
"form.user.label.password": "Salasana",
"form.user.label.username": "Käyttäjätunnus",
"menu.about": "Tietoja",
"menu.add_feed": "Lisää tilaus",
"menu.add_user": "Lisää käyttäjä",
"menu.api_keys": "API-avaimet",
"menu.categories": "Kategoriat",
"menu.create_api_key": "Luo uusi API-avain",
"menu.create_category": "Luo kategoria",
"menu.edit_category": "Muokkaa",
"menu.edit_feed": "Muokkaa",
"menu.export": "Vie",
"menu.feed_entries": "Artikkelit",
"menu.feeds": "Syötteet",
"menu.flush_history": "Tyhjennä historia",
"menu.history": "Historia",
"menu.home_page": "Etusivu",
"menu.import": "Tuo",
"menu.integrations": "Integraatiot",
"menu.logout": "Kirjaudu ulos",
"menu.mark_all_as_read": "Merkitse kaikki luetuksi",
"menu.mark_page_as_read": "Merkitse tämä sivu luetuksi",
"menu.preferences": "Asetukset",
"menu.refresh_all_feeds": "Päivitä kaikki syötteet taustalla",
"menu.refresh_feed": "Päivitä",
"menu.search": "Haku",
"menu.sessions": "Istunnot",
"menu.settings": "Asetukset",
"menu.shared_entries": "Jaetut artikkelit",
"menu.show_all_entries": "Näytä kaikki artikkelit",
"menu.show_only_starred_entries": "Näytä vain suosikit",
"menu.show_only_unread_entries": "Näytä vain lukemattomat artikkelit",
"menu.starred": "Suosikit",
"menu.title": "Menu",
"menu.unread": "Lukemattomat",
"menu.users": "Käyttäjät",
"page.about.author": "Tekijä:",
"page.about.build_date": "Valmistuspäivä:",
"page.about.credits": "Kiitokset",
"page.about.db_usage": "Database size:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "Yleiset asetukset",
"page.about.go_version": "Go-versio:",
"page.about.license": "Lisenssi:",
"page.about.postgres_version": "Postgres-versio:",
"page.about.title": "Tietoja",
"page.about.version": "Versio:",
"page.add_feed.choose_feed": "Valitse tilaus",
"page.add_feed.label.url": "URL-osoite",
"page.add_feed.legend.advanced_options": "Edistyneet asetukset",
"page.add_feed.no_category": "Ei ole ketegoriaa. Sinulla on oltava vähintään yksi ketegoria.",
"page.add_feed.submit": "Etsi tilaus",
"page.add_feed.title": "Uusi tilaus",
"page.api_keys.never_used": "Käyttämätön",
"page.api_keys.table.actions": "Toiminnot",
"page.api_keys.table.created_at": "Luomispäivä",
"page.api_keys.table.description": "Kuvaus",
"page.api_keys.table.last_used_at": "Viimeksi käytetty",
"page.api_keys.table.token": "Tunnus",
"page.api_keys.title": "API-avaimet",
"page.categories.entries": "Artikkelit",
"page.categories.feed_count": [
"On %d syöte.",
"On %d syötettä."
],
"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:",
"page.edit_feed.last_check": "Viimeisin tarkistus:",
"page.edit_feed.last_modified_header": "LastModified-otsikko:",
"page.edit_feed.last_parsing_error": "Viimeisin jäsennysvirhe",
"page.edit_feed.no_header": "Ei mitään",
"page.edit_feed.title": "Muokkaa syöte: %s",
"page.edit_user.title": "Muokkaa käyttäjä: %s",
"page.entry.attachments": "Liitteet",
"page.feeds.error_count": [
"%d virhe",
"%d virhettä"
],
"page.feeds.last_check": "Viimeisin tarkistus:",
"page.feeds.next_check": "Next check:",
"page.feeds.read_counter": "Luettujen artikkeleiden määrä",
"page.feeds.title": "Syötteet",
"page.footer.elevator": "Back to top",
"page.history.title": "Historia",
"page.import.title": "Tuo",
"page.integration.bookmarklet": "Sovelluskirjanmerkki",
"page.integration.bookmarklet.help": "This special link allows you to subscribe to a website directly by using a bookmark in your web browser.",
"page.integration.bookmarklet.instructions": "Vedä ja pudota tämä linkki kirjanmerkkeihisi.",
"page.integration.bookmarklet.name": "Lisää Minifluxiin",
"page.integration.miniflux_api": "Miniflux API",
"page.integration.miniflux_api_endpoint": "API-päätepiste",
"page.integration.miniflux_api_password": "Salasana",
"page.integration.miniflux_api_password_value": "Tilisi salasana",
"page.integration.miniflux_api_username": "Käyttäjätunnus",
"page.integrations.title": "Integraatiot",
"page.keyboard_shortcuts.close_modal": "Sulje modaalinen valintaikkuna",
"page.keyboard_shortcuts.download_content": "Lataa alkuperäinen sisältö",
"page.keyboard_shortcuts.go_to_bottom_item": "Siirry alimpaan kohtaan",
"page.keyboard_shortcuts.go_to_categories": "Siirry kategorioihin",
"page.keyboard_shortcuts.go_to_feed": "Siirry syötteeseen",
"page.keyboard_shortcuts.go_to_feeds": "Siirry syötteisiin",
"page.keyboard_shortcuts.go_to_history": "Siirry historiaan",
"page.keyboard_shortcuts.go_to_next_item": "Siirry seuraavaan kohteeseen",
"page.keyboard_shortcuts.go_to_next_page": "Siirry seuraavalle sivulle",
"page.keyboard_shortcuts.go_to_previous_item": "Siirry edelliseen kohteeseen",
"page.keyboard_shortcuts.go_to_previous_page": "Siirry edelliselle sivulle",
"page.keyboard_shortcuts.go_to_search": "Aseta painopiste hakukenttään",
"page.keyboard_shortcuts.go_to_settings": "Siirry asetuksiin",
"page.keyboard_shortcuts.go_to_starred": "Siirry kirjanmerkkeihin",
"page.keyboard_shortcuts.go_to_top_item": "Siirry alkuun",
"page.keyboard_shortcuts.go_to_unread": "Siirry lukemattomiin",
"page.keyboard_shortcuts.mark_page_as_read": "Merkitse nykyinen sivu luetuksi",
"page.keyboard_shortcuts.open_comments": "Avaa kommenttilinkki",
"page.keyboard_shortcuts.open_comments_same_window": "Avaa kommenttilinkki nykyisessä välilehdessä",
"page.keyboard_shortcuts.open_item": "Avaa valittu kohde",
"page.keyboard_shortcuts.open_original": "Avaa alkuperäinen linkki",
"page.keyboard_shortcuts.open_original_same_window": "Avaa alkuperäinen linkki nykyisessä välilehdessä",
"page.keyboard_shortcuts.refresh_all_feeds": "Päivitä kaikki syötteet taustalla",
"page.keyboard_shortcuts.remove_feed": "Poista tämä syöte",
"page.keyboard_shortcuts.save_article": "Tallenna artikkeli",
"page.keyboard_shortcuts.scroll_item_to_top": "Vieritä ylös",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Näytä pikanäppäimet",
"page.keyboard_shortcuts.subtitle.actions": "Toiminnot",
"page.keyboard_shortcuts.subtitle.items": "Kohteiden navigointi",
"page.keyboard_shortcuts.subtitle.pages": "Sivujen navigointi",
"page.keyboard_shortcuts.subtitle.sections": "Osion navigointi",
"page.keyboard_shortcuts.title": "Pikanäppäimet",
"page.keyboard_shortcuts.toggle_star_status": "Vaihda kirjanmerkki",
"page.keyboard_shortcuts.toggle_entry_attachments": "Toggle open/close entry attachments",
"page.keyboard_shortcuts.toggle_read_status_next": "Vaihda luettu/lukematon, keskity seuraavaksi",
"page.keyboard_shortcuts.toggle_read_status_prev": "Vaihda luettu/lukematon, keskity edelliseen",
"page.login.google_signin": "Kirjaudu sisään Googlella",
"page.login.oidc_signin": "Kirjaudu sisään %silla",
"page.login.title": "Kirjaudu sisään",
"page.login.webauthn_login": "Kirjaudu sisään salasanalla",
"page.login.webauthn_login.error": "Ei voida kirjautua sisään salasanalla",
"page.login.webauthn_login.help": "Please enter your username if you're using a security key. This is not required if you are using a Passkey (discoverable credentials).",
"page.new_api_key.title": "Uusi API-avain",
"page.new_category.title": "Uusi kategoria",
"page.new_user.title": "Uusi käyttäjä",
"page.offline.message": "Olet offline-tilassa",
"page.offline.refresh_page": "Yritä päivittää sivu",
"page.offline.title": "Offline-tila",
"page.read_entry_count": [
"%d read entry",
"%d read entries"
],
"page.search.title": "Hakutulokset",
"page.sessions.table.actions": "Toiminnot",
"page.sessions.table.current_session": "Nykyinen istunto",
"page.sessions.table.date": "Päivämäärä",
"page.sessions.table.ip": "IP-osoite",
"page.sessions.table.user_agent": "Käyttäjäagentti",
"page.sessions.title": "Istunnot",
"page.settings.link_google_account": "Linkitä Google-tilini",
"page.settings.link_oidc_account": "Linkitä %s -tilini",
"page.settings.title": "Asetukset",
"page.settings.unlink_google_account": "Poista Google-tilini linkitys",
"page.settings.unlink_oidc_account": "Poista %s -tilini linkitys",
"page.settings.webauthn.actions": "Actions",
"page.settings.webauthn.added_on": "Added On",
"page.settings.webauthn.delete": [
"Poista %d salasana",
"Poista %d salasanaa"
],
"page.settings.webauthn.last_seen_on": "Last Used",
"page.settings.webauthn.passkey_name": "Passkey Name",
"page.settings.webauthn.passkeys": "Passkeys",
"page.settings.webauthn.register": "Rekisteröi salasana",
"page.settings.webauthn.register.error": "Salasanaa ei voi rekisteröidä",
"page.shared_entries.title": "Jaetut artikkelit",
"page.shared_entries_count": [
"%d shared entry",
"%d shared entries"
],
"page.starred.title": "Suosikit",
"page.starred_entry_count": [
"%d starred entry",
"%d starred entries"
],
"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.users.actions": "Toiminnot",
"page.users.admin.no": "Ei",
"page.users.admin.yes": "Kyllä",
"page.users.is_admin": "Ylläpitäjä",
"page.users.last_login": "Viimeisin kirjautuminen",
"page.users.never_logged": "Ei koskaan",
"page.users.title": "Käyttäjät",
"page.users.username": "Käyttäjätunnus",
"page.webauthn_rename.title": "Rename Passkey",
"pagination.first": "Ensimmäinen",
"pagination.last": "Viimeinen",
"pagination.next": "Seuraava",
"pagination.previous": "Edellinen",
"search.label": "Haku",
"search.placeholder": "Hae...",
"search.submit": "Hae",
"skip_to_content": "Siirry sisältöön",
"time_elapsed.days": [
"%d päivä sitten",
"%d päivää sitten"
],
"time_elapsed.hours": [
"%d tunti sitten",
"%d tuntia sitten"
],
"time_elapsed.minutes": [
"%d minuutti sitten",
"%d minuuttia sitten"
],
"time_elapsed.months": [
"%d kuukausi sitten",
"%d kuukautta sitten"
],
"time_elapsed.not_yet": "ei vielä",
"time_elapsed.now": "juuri nyt",
"time_elapsed.weeks": [
"%d viikko sitten",
"%d viikkoa sitten"
],
"time_elapsed.years": [
"%d vuosi sitten",
"%d vuotta sitten"
],
"time_elapsed.yesterday": "eilen",
"tooltip.keyboard_shortcuts": "Pikanäppäin: %s",
"tooltip.logged_user": "Kirjautunut %s-käyttäjänä"
} v2-2.2.13/internal/locale/translations/fr_FR.json 0000664 0000000 0000000 00000120710 15062123773 0021622 0 ustar 00root root 0000000 0000000 {
"action.cancel": "annuler",
"action.download": "Télécharger",
"action.edit": "Modifier",
"action.home_screen": "Ajouter à l'écran d'accueil",
"action.import": "Importer",
"action.login": "Se connecter",
"action.or": "ou",
"action.remove": "Supprimer",
"action.remove_feed": "Supprimer ce flux",
"action.save": "Sauvegarder",
"action.subscribe": "S'abonner",
"action.update": "Mettre à jour",
"alert.account_linked": "Votre compte externe est maintenant associé !",
"alert.account_unlinked": "Votre compte externe est maintenant dissocié !",
"alert.background_feed_refresh": "Les abonnements sont en cours d'actualisation en arrière-plan. Vous pouvez continuer à naviguer dans l'application.",
"alert.feed_error": "Il y a un problème avec cet abonnement",
"alert.no_starred": "Il n'y a aucun favoris pour le moment.",
"alert.no_category": "Il n'y a aucune catégorie.",
"alert.no_category_entry": "Il n'y a aucun article dans cette catégorie.",
"alert.no_feed": "Vous n'avez aucun abonnement.",
"alert.no_feed_entry": "Il n'y a aucun article pour cet abonnement.",
"alert.no_feed_in_category": "Il n'y a pas d'abonnement pour cette catégorie.",
"alert.no_history": "Il n'y a aucun historique pour le moment.",
"alert.no_search_result": "Il n'y a aucun résultat pour cette recherche.",
"alert.no_shared_entry": "Il n'y a pas d'article partagé.",
"alert.no_tag_entry": "Il n'y a aucun article correspondant à ce tag.",
"alert.no_unread_entry": "Il n'y a rien de nouveau à lire.",
"alert.no_user": "Vous êtes le seul utilisateur.",
"alert.prefs_saved": "Préférences sauvegardées !",
"alert.too_many_feeds_refresh": [
"Vous avez déclenché trop d'actualisations de flux. Veuillez attendre %d minute avant de réessayer.",
"Vous avez déclenché trop d'actualisations de flux. Veuillez attendre %d minutes avant de réessayer."
],
"confirm.loading": "En cours...",
"confirm.no": "non",
"confirm.question": "Êtes-vous sûr ?",
"confirm.question.refresh": "Voulez-vous forcer le rafraîchissement ?",
"confirm.yes": "oui",
"enclosure_media_controls.seek": "Avancer/Reculer :",
"enclosure_media_controls.seek.title": "Avancer/Reculer de %s seconds",
"enclosure_media_controls.speed": "Vitesse :",
"enclosure_media_controls.speed.faster": "Accélérer",
"enclosure_media_controls.speed.faster.title": "Accélérer de %sx",
"enclosure_media_controls.speed.reset": "Réinitialiser",
"enclosure_media_controls.speed.reset.title": "Réinitialiser la vitesse de lecture à 1x",
"enclosure_media_controls.speed.slower": "Ralentir",
"enclosure_media_controls.speed.slower.title": "Ralentir de %sx",
"entry.starred.toast.off": "Enlevé des favoris",
"entry.starred.toast.on": "Ajouté aux favoris",
"entry.starred.toggle.off": "Enlever favoris",
"entry.starred.toggle.on": "Favoris",
"entry.comments.label": "Commentaires",
"entry.comments.title": "Voir les commentaires",
"entry.estimated_reading_time": [
"%d minute de lecture",
"%d minutes de lecture"
],
"entry.external_link.label": "Lien externe",
"entry.save.completed": "Terminé !",
"entry.save.label": "Sauvegarder",
"entry.save.title": "Sauvegarder cet article",
"entry.save.toast.completed": "Article sauvegardé",
"entry.scraper.completed": "Terminé !",
"entry.scraper.label": "Télécharger",
"entry.scraper.title": "Récupérer le contenu original",
"entry.share.label": "Partager",
"entry.share.title": "Partager cet article",
"entry.shared_entry.label": "Partage",
"entry.shared_entry.title": "Ouvrir le lien public",
"entry.state.loading": "Chargement...",
"entry.state.saving": "Sauvegarde en cours...",
"entry.status.mark_as_read": "Marquer comme lu",
"entry.status.mark_as_unread": "Marquer comme non lu",
"entry.status.title": "Changer le statut de l'entrée",
"entry.status.toast.read": "Marqué comme lu",
"entry.status.toast.unread": "Marqué comme non lu",
"entry.tags.label": "Libellés :",
"entry.tags.more_tags_label": [
"Afficher %d libellé supplémentaire",
"Afficher %d libellés supplémentaires"
],
"entry.unshare.label": "Enlever le partage",
"error.api_key_already_exists": "Cette clé d'API existe déjà.",
"error.bad_credentials": "Mauvais identifiant ou mot de passe.",
"error.category_already_exists": "Cette catégorie existe déjà.",
"error.category_not_found": "Cette catégorie n'existe pas ou n'appartient pas à cet utilisateur.",
"error.database_error": "Erreur de la base de données : %v.",
"error.different_passwords": "Les mots de passe ne sont pas les mêmes.",
"error.duplicate_fever_username": "Il y a déjà quelqu'un d'autre avec le même nom d'utilisateur Fever !",
"error.duplicate_googlereader_username": "Il y a déjà quelqu'un d'autre avec le même nom d'utilisateur Google Reader !",
"error.duplicate_linked_account": "Il y a déjà quelqu'un d'associé avec ce provider !",
"error.duplicated_feed": "Ce flux existe déjà.",
"error.empty_file": "Ce fichier est vide.",
"error.entries_per_page_invalid": "Le nombre d'entrées par page n'est pas valide.",
"error.feed_already_exists": "Ce flux existe déjà.",
"error.feed_category_not_found": "Cette catégorie n'existe pas ou n'appartient pas à cet utilisateur.",
"error.feed_format_not_detected": "Impossible de détecter le format du flux : %v.",
"error.feed_invalid_blocklist_rule": "La règle de blocage n'est pas valide.",
"error.feed_invalid_keeplist_rule": "La règle d'autorisation n'est pas valide.",
"error.feed_mandatory_fields": "L'URL et la catégorie sont obligatoire.",
"error.feed_not_found": "Impossible de trouver ce flux.",
"error.feed_title_not_empty": "Le titre du flux ne peut pas être vide.",
"error.feed_url_not_empty": "L'URL du flux ne peut pas être vide.",
"error.fields_mandatory": "Tous les champs sont obligatoire.",
"error.http_bad_gateway": "Le site web n'est pas disponible pour le moment à cause d'une erreur de passerelle réseau. Le problème ne vient pas de Miniflux. Veuillez réessayer plus tard.",
"error.http_body_read": "Impossible de lire le corps de la réponse HTTP : %v.",
"error.http_client_error": "Erreur du client HTTP : %v.",
"error.http_empty_response": "La réponse HTTP est vide. Peut-être que ce site web bloque Miniflux avec une protection anti-bot ?",
"error.http_empty_response_body": "Le corps de la réponse HTTP est vide.",
"error.http_forbidden": "Accès interdit à ce site web. Il se peut que ce site web bloque Miniflux avec une protection anti-bot.",
"error.http_gateway_timeout": "Le site web n'est pas disponible pour le moment à cause d'un délai d'attente dépassé. Le problème ne vient pas de Miniflux. Veuillez réessayer plus tard.",
"error.http_internal_server_error": "Le site web n'est pas disponible pour le moment à cause d'une erreur interne au serveur. Le problème ne vient pas de Miniflux. Veuillez réessayer plus tard.",
"error.http_not_authorized": "Accès non autorisé à ce site web. Veuillez vérifier les identifiants de cet abonnement.",
"error.http_resource_not_found": "La resource demandée n'existe pas sur ce site web. Veuillez vérifier l'URL.",
"error.http_response_too_large": "La réponse HTTP est trop volumineuse. Vous pouvez augmenter la limite de taille de réponse HTTP dans les paramètres de l'application (redémarrage de l'application nécessaire).",
"error.http_service_unavailable": "Le site web n'est pas disponible pour le moment. Le problème ne vient pas de Miniflux. Veuillez réessayer plus tard.",
"error.http_too_many_requests": "Miniflux a généré trop de requêtes vers ce site web. Veuillez réessayer plus tard ou changez la configuration de l'application.",
"error.http_unexpected_status_code": "Le site web a répondu avec un code HTTP inattendu : %d. Le problème ne vient pas de Miniflux. Veuillez réessayer plus tard.",
"error.invalid_categories_sorting_order": "L'ordre de tri des catégories n'est pas valide.",
"error.invalid_default_home_page": "Page d'accueil par défaut invalide !",
"error.invalid_display_mode": "Mode d'affichage de l'application web non valide.",
"error.invalid_entry_direction": "Ordre de trie non valide.",
"error.invalid_entry_order": "Ordre de tri non valide.",
"error.invalid_feed_proxy_url": "L'URL du proxy n'est pas valide.",
"error.invalid_feed_url": "URL de flux non valide.",
"error.invalid_gesture_nav": "Navigation gestuelle non valide.",
"error.invalid_language": "Langue non valide.",
"error.invalid_site_url": "URL de site non valide.",
"error.invalid_theme": "Thème non valide.",
"error.invalid_timezone": "Fuseau horaire non valide.",
"error.network_operation": "Miniflux n'est pas en mesure de se connecter à ce site web à cause d'un problème réseau : %v.",
"error.network_timeout": "Ce site web est trop lent à répondre : %v.",
"error.password_min_length": "Vous devez utiliser au moins 6 caractères pour le mot de passe.",
"error.proxy_url_not_empty": "L'URL du proxy ne peut pas être vide.",
"error.settings_block_rule_fieldname_invalid": "Règle de blocage invalide : la règle n°%d ne contient pas un nom de champ valide (Options : %s)",
"error.settings_block_rule_invalid_regex": "Règle de blocage invalide : le motif de la règle n°%d n'est pas une expression régulière valide",
"error.settings_block_rule_regex_required": "Règle de blocage invalide : le motif de la règle n°%d n'est pas fourni",
"error.settings_block_rule_separator_required": "Règle de blocage invalide : le motif de la règle n°%d doit être séparé par un '='",
"error.settings_invalid_domain_list": "Liste de domaines invalide. Veuillez fournir une liste de domaines séparés par des espaces.",
"error.settings_keep_rule_fieldname_invalid": "Règle de conservation invalide : la règle n°%d ne contient pas un nom de champ valide (Options : %s)",
"error.settings_keep_rule_invalid_regex": "Règle de conservation invalide : le motif de la règle n°%d n'est pas une expression régulière valide",
"error.settings_keep_rule_regex_required": "Règle de conservation invalide : le motif de la règle n°%d n'est pas fourni",
"error.settings_keep_rule_separator_required": "Règle de conservation invalide : le motif de la règle n°%d doit être séparé par un '='",
"error.settings_mandatory_fields": "Le nom d'utilisateur, le thème, la langue et le fuseau horaire sont obligatoire.",
"error.settings_media_playback_rate_range": "La vitesse de lecture est hors limites",
"error.settings_reading_speed_is_positive": "Les vitesses de lecture doivent être des entiers positifs.",
"error.site_url_not_empty": "L'URL du site ne peut pas être vide.",
"error.subscription_not_found": "Impossible de trouver un abonnement.",
"error.title_required": "Le titre est obligatoire.",
"error.tls_error": "Erreur TLS : %q. Vous pouvez désactiver la vérification TLS dans les paramètres de l'abonnement.",
"error.unable_to_create_api_key": "Impossible de créer cette clé d'API.",
"error.unable_to_create_category": "Impossible de créer cette catégorie.",
"error.unable_to_create_user": "Impossible de créer cet utilisateur.",
"error.unable_to_detect_rssbridge": "Impossible de détecter un flux RSS en utilisant RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Impossible d'analyser ce flux : %v.",
"error.unable_to_update_category": "Impossible de mettre à jour cette catégorie.",
"error.unable_to_update_feed": "Impossible de mettre à jour cet abonnement.",
"error.unable_to_update_user": "Impossible de mettre à jour cet utilisateur.",
"error.unlink_account_without_password": "Vous devez définir un mot de passe sinon vous ne pourrez plus vous connecter par la suite.",
"error.user_already_exists": "Cet utilisateur existe déjà.",
"error.user_mandatory_fields": "Le nom d'utilisateur est obligatoire.",
"error.linktaco_missing_required_fields": "Le token API LinkTaco et le slug de l'organisation sont requis.",
"form.api_key.label.description": "Libellé de la clé d'API",
"form.category.hide_globally": "Masquer les entrées dans la liste globale non lue",
"form.category.label.title": "Titre",
"form.feed.fieldset.general": "Général",
"form.feed.fieldset.integration": "Services tiers",
"form.feed.fieldset.network_settings": "Paramètres réseau",
"form.feed.fieldset.rules": "Règles",
"form.feed.label.allow_self_signed_certificates": "Autoriser les certificats auto-signés ou non valides",
"form.feed.label.apprise_service_urls": "Liste séparée par des virgules des URL du service Apprise",
"form.feed.label.block_filter_entry_rules": "Règles de blocage des entrées",
"form.feed.label.blocklist_rules": "Filtres de blocage basés sur des expressions régulières",
"form.feed.label.category": "Catégorie",
"form.feed.label.cookie": "Définir les cookies",
"form.feed.label.crawler": "Récupérer le contenu original",
"form.feed.label.description": "Description",
"form.feed.label.disable_http2": "Désactiver HTTP/2",
"form.feed.label.disabled": "Ne pas actualiser ce flux",
"form.feed.label.feed_password": "Mot de passe du flux",
"form.feed.label.feed_url": "URL du flux",
"form.feed.label.feed_username": "Nom d'utilisateur du flux",
"form.feed.label.fetch_via_proxy": "Utiliser le proxy configuré au niveau de l'application",
"form.feed.label.hide_globally": "Masquer les entrées dans la liste globale non lue",
"form.feed.label.ignore_http_cache": "Ignorer le cache HTTP",
"form.feed.label.keep_filter_entry_rules": "Règles d'autorisation des entrées",
"form.feed.label.keeplist_rules": "Filtres de conservation basés sur des expressions régulières",
"form.feed.label.no_media_player": "Pas de lecteur multimedia (audio/vidéo)",
"form.feed.label.ntfy_activate": "Activer les notifications",
"form.feed.label.ntfy_default_priority": "Priorité par défaut de notification",
"form.feed.label.ntfy_high_priority": "Priorité élevée de notification",
"form.feed.label.ntfy_low_priority": "Priorité basse de notification",
"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.proxy_url": "URL du proxy",
"form.feed.label.pushover_activate": "Activer les notifications vers Pushover",
"form.feed.label.pushover_default_priority": "Priorité par défaut",
"form.feed.label.pushover_high_priority": "Priorité élevée",
"form.feed.label.pushover_low_priority": "Priorité basse",
"form.feed.label.pushover_max_priority": "Priorité maximale",
"form.feed.label.pushover_min_priority": "Priorité minimale",
"form.feed.label.pushover_priority": "Priorité des notifications Pushover",
"form.feed.label.rewrite_rules": "Règles de réécriture du contenu",
"form.feed.label.scraper_rules": "Règles pour récupérer le contenu original",
"form.feed.label.site_url": "URL du site web",
"form.feed.label.title": "Titre",
"form.feed.label.urlrewrite_rules": "Règles de réécriture d'URL",
"form.feed.label.user_agent": "Remplacer l'agent utilisateur par défaut",
"form.feed.label.webhook_url": "Remplacer l'URL du webhook",
"form.import.label.file": "Fichier OPML",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Envoyer les articles vers Apprise",
"form.integration.apprise_services_url": "Liste des services Apprise séparés par des virgules",
"form.integration.apprise_url": "URL de l'API Apprise",
"form.integration.betula_activate": "Sauvegarder les entrées vers Betula",
"form.integration.betula_token": "Jeton de sécurité de l'API de Betula",
"form.integration.betula_url": "URL du serveur Betula",
"form.integration.cubox_activate": "Sauvegarder les entrées vers Cubox",
"form.integration.cubox_api_link": "Lien API Cubox",
"form.integration.discord_activate": "Envoyer les articles vers Discord",
"form.integration.discord_webhook_link": "URL du Webhook Discord",
"form.integration.espial_activate": "Sauvegarder les articles vers Espial",
"form.integration.espial_api_key": "Clé d'API de Espial",
"form.integration.espial_endpoint": "URL de l'API de Espial",
"form.integration.espial_tags": "Libellés de Espial",
"form.integration.fever_activate": "Activer l'API de Fever",
"form.integration.fever_endpoint": "Point de terminaison de l'API Fever :",
"form.integration.fever_password": "Mot de passe pour l'API de Fever",
"form.integration.fever_username": "Nom d'utilisateur pour l'API de Fever",
"form.integration.googlereader_activate": "Activer l'API de Google Reader",
"form.integration.googlereader_endpoint": "Point de terminaison de l'API Google Reader :",
"form.integration.googlereader_password": "Mot de passe pour l'API de Google Reader",
"form.integration.googlereader_username": "Nom d'utilisateur pour l'API de Google Reader",
"form.integration.instapaper_activate": "Sauvegarder les articles vers Instapaper",
"form.integration.instapaper_password": "Mot de passe Instapaper",
"form.integration.instapaper_username": "Nom d'utilisateur Instapaper",
"form.integration.karakeep_activate": "Sauvegarder les articles vers Karakeep",
"form.integration.karakeep_api_key": "Clé d'API de Karakeep",
"form.integration.karakeep_url": "URL de l'API de Karakeep",
"form.integration.linkace_activate": "Enregistrer les entrées vers LinkAce",
"form.integration.linkace_api_key": "Clé d'API LinkAce",
"form.integration.linkace_check_disabled": "Désactiver la vérification des liens",
"form.integration.linkace_endpoint": "Point de terminaison de l'API LinkAce",
"form.integration.linkace_is_private": "Marquer le lien comme privé",
"form.integration.linkace_tags": "Étiquettes LinkAce",
"form.integration.linkding_activate": "Sauvegarder les articles vers Linkding",
"form.integration.linkding_api_key": "Clé d'API de Linkding",
"form.integration.linkding_bookmark": "Marquer le lien comme non lu",
"form.integration.linkding_endpoint": "URL de l'API de Linkding",
"form.integration.linkding_tags": "Libellés",
"form.integration.linktaco_activate": "Sauvegarder les entrées vers LinkTaco",
"form.integration.linktaco_api_token": "Token API LinkTaco",
"form.integration.linktaco_api_token_hint": "Obtenez votre token d'accès personnel sur",
"form.integration.linktaco_org_slug": "Slug de l'organisation",
"form.integration.linktaco_tags": "Tags (max. 10, séparés par des virgules)",
"form.integration.linktaco_tags_hint": "Maximum 10 tags, séparés par des virgules",
"form.integration.linktaco_visibility": "Visibilité",
"form.integration.linktaco_visibility_public": "Public",
"form.integration.linktaco_visibility_private": "Privé",
"form.integration.linktaco_visibility_hint": "La visibilité PRIVÉE nécessite un compte LinkTaco payant",
"form.integration.linkwarden_activate": "Sauvegarder les articles vers Linkwarden",
"form.integration.linkwarden_api_key": "Clé d'API de Linkwarden",
"form.integration.linkwarden_endpoint": "URL de base de Linkwarden",
"form.integration.matrix_bot_activate": "Envoyer les nouveaux articles vers Matrix",
"form.integration.matrix_bot_chat_id": "Identifiant de la salle Matrix",
"form.integration.matrix_bot_password": "Mot de passe de l'utilisateur Matrix",
"form.integration.matrix_bot_url": "URL du serveur Matrix",
"form.integration.matrix_bot_user": "Nom de l'utilisateur Matrix",
"form.integration.notion_activate": "Sauvegarder les articles vers Notion",
"form.integration.notion_page_id": "Identifiant de la page Notion",
"form.integration.notion_token": "Jeton d'accès de l'API de Notion",
"form.integration.ntfy_activate": "Envoyer les entrées vers ntfy",
"form.integration.ntfy_api_token": "Jeton d'API Ntfy (optionnel)",
"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 (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",
"form.integration.nunux_keeper_api_key": "Clé d'API de Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "URL de l'API de Nunux Keeper",
"form.integration.omnivore_activate": "Sauvegarder les articles vers Omnivore",
"form.integration.omnivore_api_key": "Clé d'API de Omnivore",
"form.integration.omnivore_url": "URL de l'API de Omnivore",
"form.integration.pinboard_activate": "Sauvegarder les articles vers Pinboard",
"form.integration.pinboard_bookmark": "Marquer le lien comme non lu",
"form.integration.pinboard_tags": "Libellés de Pinboard",
"form.integration.pinboard_token": "Jeton de sécurité de l'API de Pinboard",
"form.integration.pushover_activate": "Envoyer les articles vers Pushover",
"form.integration.pushover_device": "Nom de l'appareil Pushover (facultatif)",
"form.integration.pushover_prefix": "URL de préfixe Pushover (facultatif)",
"form.integration.pushover_token": "Jeton d'API de l'application Pushover",
"form.integration.pushover_user": "Identifiant de l'utilisateur Pushover (user key)",
"form.integration.raindrop_activate": "Enregistrer les entrées vers Raindrop",
"form.integration.raindrop_collection_id": "Identifiant de la collection",
"form.integration.raindrop_tags": "Libellés (séparées par des virgules)",
"form.integration.raindrop_token": "Jeton d'accès de Raindrop",
"form.integration.readeck_activate": "Sauvegarder les articles vers Readeck",
"form.integration.readeck_api_key": "Clé d'API de Readeck",
"form.integration.readeck_endpoint": "URL de l'API de Readeck",
"form.integration.readeck_labels": "Libellés Readeck",
"form.integration.readeck_only_url": "Envoyer uniquement l'URL (au lieu du contenu complet)",
"form.integration.readwise_activate": "Enregistrer les entrées vers Readwise Reader",
"form.integration.readwise_api_key": "Jeton d'accès au lecteur Readwise",
"form.integration.readwise_api_key_link": "Obtenez votre jeton d'accès Readwise",
"form.integration.rssbridge_activate": "Vérifier RSS-Bridge lors de l'ajout d'abonnements",
"form.integration.rssbridge_token": "Jeton d'authentification RSS-Bridge",
"form.integration.rssbridge_url": "URL du serveur RSS-Bridge",
"form.integration.shaarli_activate": "Sauvegarder les articles vers Shaarli",
"form.integration.shaarli_api_secret": "Clé d'API de Shaarli API",
"form.integration.shaarli_endpoint": "URL de l'API de Shaarli",
"form.integration.shiori_activate": "Sauvegarder les articles vers Shiori",
"form.integration.shiori_endpoint": "URL de l'API de Shiori",
"form.integration.shiori_password": "Mot de passe de Shiori",
"form.integration.shiori_username": "Nom d'utilisateur de Shiori",
"form.integration.slack_activate": "Envoyer les articles vers Slack",
"form.integration.slack_webhook_link": "URL du Webhook Slack",
"form.integration.telegram_bot_activate": "Envoyer les nouveaux articles vers Telegram",
"form.integration.telegram_bot_disable_buttons": "Désactiver les boutons",
"form.integration.telegram_bot_disable_notification": "Désactiver les notifications",
"form.integration.telegram_bot_disable_web_page_preview": "Désactiver l'aperçu de la page Web",
"form.integration.telegram_bot_token": "Jeton de sécurité de l'API du Bot Telegram",
"form.integration.telegram_chat_id": "Identifiant de discussion (Chat ID)",
"form.integration.telegram_topic_id": "Identifiant du sujet (Topic ID)",
"form.integration.wallabag_activate": "Sauvegarder les articles vers Wallabag",
"form.integration.wallabag_client_id": "Identifiant unique du client Wallabag",
"form.integration.wallabag_client_secret": "Clé secrète du client Wallabag",
"form.integration.wallabag_endpoint": "URL de base de Wallabag",
"form.integration.wallabag_only_url": "Envoyer uniquement l'URL (au lieu du contenu complet)",
"form.integration.wallabag_password": "Mot de passe de Wallabag",
"form.integration.wallabag_username": "Nom d'utilisateur de Wallabag",
"form.integration.wallabag_tags": "Libellés Wallabag",
"form.integration.webhook_activate": "Activer le webhook",
"form.integration.webhook_secret": "Secret du webhook",
"form.integration.webhook_url": "URL du webhook",
"form.prefs.fieldset.application_settings": "Paramètres de l'application",
"form.prefs.fieldset.authentication_settings": "Paramètres d'authentification",
"form.prefs.fieldset.global_feed_settings": "Paramètres globaux des abonnements",
"form.prefs.fieldset.reader_settings": "Paramètres du lecteur",
"form.prefs.help.external_font_hosts": "Liste de domaine externes autorisés, séparés par des espaces. Par exemple : « fonts.gstatic.com fonts.googleapis.com ».",
"form.prefs.label.always_open_external_links": "Lire les articles en ouvrant les liens externes",
"form.prefs.label.categories_sorting_order": "Colonne de tri des catégories",
"form.prefs.label.cjk_reading_speed": "Vitesse de lecture pour le chinois, le coréen et le japonais (caractères par minute)",
"form.prefs.label.custom_css": "Feuille de style personnalisée",
"form.prefs.label.custom_js": "Code JavaScript personnalisé",
"form.prefs.label.default_home_page": "Page d'accueil par défaut",
"form.prefs.label.default_reading_speed": "Vitesse de lecture pour les autres langues (mots par minute)",
"form.prefs.label.display_mode": "Mode d'affichage de l'Application Web Progressive (PWA)",
"form.prefs.label.entries_per_page": "Entrées par page",
"form.prefs.label.entry_order": "Colonne de tri des entrées",
"form.prefs.label.entry_sorting": "Ordre des éléments",
"form.prefs.label.entry_swipe": "Activer le balayage des entrées sur les écrans tactiles",
"form.prefs.label.external_font_hosts": "Polices externes autorisées",
"form.prefs.label.gesture_nav": "Geste pour naviguer entre les entrées",
"form.prefs.label.keyboard_shortcuts": "Activer les raccourcis clavier",
"form.prefs.label.language": "Langue",
"form.prefs.label.mark_read_manually": "Marquer les entrées comme lues manuellement",
"form.prefs.label.mark_read_on_media_completion": "Marquer les entrées comme lues uniquement après 90%% de lecture de l'audio/vidéo",
"form.prefs.label.mark_read_on_view": "Marquer automatiquement les entrées comme lues lorsqu'elles sont consultées",
"form.prefs.label.mark_read_on_view_or_media_completion": "Marquer automatiquement les entrées comme lues lorsqu'elles sont consultées. Pour l'audio/vidéo, marquer comme lues après 90%%",
"form.prefs.label.media_playback_rate": "Vitesse de lecture de l'audio/vidéo",
"form.prefs.label.open_external_links_in_new_tab": "Ouvrir les liens externes dans un nouvel onglet (ajoute target=\"_blank\" aux liens)",
"form.prefs.label.show_reading_time": "Afficher le temps de lecture estimé des articles",
"form.prefs.label.theme": "Thème",
"form.prefs.label.timezone": "Fuseau horaire",
"form.prefs.select.alphabetical": "Alphabétique",
"form.prefs.select.browser": "Navigateur",
"form.prefs.select.created_time": "Heure de création de l'entrée",
"form.prefs.select.fullscreen": "Plein écran",
"form.prefs.select.minimal_ui": "Minimal",
"form.prefs.select.none": "Aucun",
"form.prefs.select.older_first": "Anciens éléments en premier",
"form.prefs.select.publish_time": "Heure de publication de l'entrée",
"form.prefs.select.recent_first": "Éléments récents en premier",
"form.prefs.select.standalone": "Autonome",
"form.prefs.select.swipe": "Glisser",
"form.prefs.select.tap": "Tapez deux fois",
"form.prefs.select.unread_count": "Nombre d'articles non lus",
"form.submit.loading": "Chargement...",
"form.submit.saving": "Sauvegarde en cours...",
"form.user.label.admin": "Administrateur",
"form.user.label.confirmation": "Confirmation du mot de passe",
"form.user.label.password": "Mot de passe",
"form.user.label.username": "Nom d'utilisateur",
"menu.about": "À propos",
"menu.add_feed": "Ajouter un abonnement",
"menu.add_user": "Ajouter un utilisateur",
"menu.api_keys": "Clés d'API",
"menu.categories": "Catégories",
"menu.create_api_key": "Créer une nouvelle clé d'API",
"menu.create_category": "Créer une catégorie",
"menu.edit_category": "Modifier",
"menu.edit_feed": "Modifier",
"menu.export": "Export",
"menu.feed_entries": "Articles",
"menu.feeds": "Abonnements",
"menu.flush_history": "Supprimer l'historique",
"menu.history": "Historique",
"menu.home_page": "Page d'accueil",
"menu.import": "Import",
"menu.integrations": "Intégrations",
"menu.logout": "Se déconnecter",
"menu.mark_all_as_read": "Tout marquer comme lu",
"menu.mark_page_as_read": "Marquer cette page comme lue",
"menu.preferences": "Préférences",
"menu.refresh_all_feeds": "Actualiser les abonnements en arrière-plan",
"menu.refresh_feed": "Actualiser",
"menu.search": "Recherche",
"menu.sessions": "Sessions",
"menu.settings": "Réglages",
"menu.shared_entries": "Articles partagés",
"menu.show_all_entries": "Afficher tous les articles",
"menu.show_only_starred_entries": "Afficher uniquement les favoris",
"menu.show_only_unread_entries": "Afficher uniquement les articles non lus",
"menu.starred": "Favoris",
"menu.title": "Menu",
"menu.unread": "Non lus",
"menu.users": "Utilisateurs",
"page.about.author": "Auteur :",
"page.about.build_date": "Date de la compilation :",
"page.about.credits": "Crédits",
"page.about.db_usage": "Taille de la base de données :",
"page.about.git_commit": "Commit Git :",
"page.about.global_config_options": "Options de configuration globales",
"page.about.go_version": "Version de Go :",
"page.about.license": "Licence :",
"page.about.postgres_version": "Version de Postgresql :",
"page.about.title": "À propos",
"page.about.version": "Version :",
"page.add_feed.choose_feed": "Choisissez un abonnement",
"page.add_feed.label.url": "Lien",
"page.add_feed.legend.advanced_options": "Options avancées",
"page.add_feed.no_category": "Il n'y a aucune catégorie. Vous devez avoir au moins une catégorie.",
"page.add_feed.submit": "Trouver un abonnement",
"page.add_feed.title": "Nouvel Abonnement",
"page.api_keys.never_used": "Jamais utilisé",
"page.api_keys.table.actions": "Actions",
"page.api_keys.table.created_at": "Date de création",
"page.api_keys.table.description": "Description",
"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.entries": "Articles",
"page.categories.feed_count": [
"Il y a %d abonnement.",
"Il y a %d abonnements."
],
"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 :",
"page.edit_feed.last_check": "Dernière vérification :",
"page.edit_feed.last_modified_header": "En-tête LastModified :",
"page.edit_feed.last_parsing_error": "Dernière erreur d'analyse",
"page.edit_feed.no_header": "Aucune",
"page.edit_feed.title": "Modification de l'abonnement : %s",
"page.edit_user.title": "Modification de l'utilisateur : %s",
"page.entry.attachments": "Pièces Jointes",
"page.feeds.error_count": [
"%d erreur",
"%d erreurs"
],
"page.feeds.last_check": "Dernière vérification :",
"page.feeds.next_check": "Prochaine vérification :",
"page.feeds.read_counter": "Nombre d'entrées lues",
"page.feeds.title": "Abonnements",
"page.footer.elevator": "Retour en haut",
"page.history.title": "Historique",
"page.import.title": "Importation",
"page.integration.bookmarklet": "Bookmarklet",
"page.integration.bookmarklet.help": "Ce lien spécial vous permet de vous abonner à un site web directement en utilisant un marque page dans votre navigateur web.",
"page.integration.bookmarklet.instructions": "Glisser-déposer ce lien dans vos favoris.",
"page.integration.bookmarklet.name": "Ajouter à Miniflux",
"page.integration.miniflux_api": "API de Miniflux",
"page.integration.miniflux_api_endpoint": "Point de terminaison de l'API",
"page.integration.miniflux_api_password": "Mot de passe",
"page.integration.miniflux_api_password_value": "Le mot de passe de votre compte",
"page.integration.miniflux_api_username": "Nom d'utilisateur",
"page.integrations.title": "Intégrations",
"page.keyboard_shortcuts.close_modal": "Fermer la boite de dialogue",
"page.keyboard_shortcuts.download_content": "Télécharger le contenu original",
"page.keyboard_shortcuts.go_to_bottom_item": "Aller à l'élément du bas",
"page.keyboard_shortcuts.go_to_categories": "Voir les catégories",
"page.keyboard_shortcuts.go_to_feed": "Voir abonnement",
"page.keyboard_shortcuts.go_to_feeds": "Voir les abonnements",
"page.keyboard_shortcuts.go_to_history": "Voir l'historique",
"page.keyboard_shortcuts.go_to_next_item": "Élément suivant",
"page.keyboard_shortcuts.go_to_next_page": "Page suivante",
"page.keyboard_shortcuts.go_to_previous_item": "Élément précédent",
"page.keyboard_shortcuts.go_to_previous_page": "Page précédente",
"page.keyboard_shortcuts.go_to_search": "Mettre le focus sur le champ de recherche",
"page.keyboard_shortcuts.go_to_settings": "Voir les réglages",
"page.keyboard_shortcuts.go_to_starred": "Voir les favoris",
"page.keyboard_shortcuts.go_to_top_item": "Aller à l'élément supérieur",
"page.keyboard_shortcuts.go_to_unread": "Aller aux éléments non lus",
"page.keyboard_shortcuts.mark_page_as_read": "Marquer la page actuelle comme lu",
"page.keyboard_shortcuts.open_comments": "Ouvrir le lien des commentaires",
"page.keyboard_shortcuts.open_comments_same_window": "Ouvrir le lien des commentaires dans l'onglet en cours",
"page.keyboard_shortcuts.open_item": "Ouvrir élément sélectionné",
"page.keyboard_shortcuts.open_original": "Ouvrir le lien original",
"page.keyboard_shortcuts.open_original_same_window": "Ouvrir le lien original dans l'onglet en cours",
"page.keyboard_shortcuts.refresh_all_feeds": "Actualiser les abonnements en arrière-plan",
"page.keyboard_shortcuts.remove_feed": "Supprimer ce flux",
"page.keyboard_shortcuts.save_article": "Sauvegarder l'article",
"page.keyboard_shortcuts.scroll_item_to_top": "Faire défiler l'élément vers le haut",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Voir les raccourcis clavier",
"page.keyboard_shortcuts.subtitle.actions": "Actions",
"page.keyboard_shortcuts.subtitle.items": "Navigation entre les éléments",
"page.keyboard_shortcuts.subtitle.pages": "Navigation entre les pages",
"page.keyboard_shortcuts.subtitle.sections": "Navigation entre les sections",
"page.keyboard_shortcuts.title": "Raccourcis clavier",
"page.keyboard_shortcuts.toggle_star_status": "Ajouter/Enlever favoris",
"page.keyboard_shortcuts.toggle_entry_attachments": "Ouvrir/Fermer les pièces jointes de l'entrée",
"page.keyboard_shortcuts.toggle_read_status_next": "Basculer entre lu/non lu, et changer le focus sur l'élément suivant",
"page.keyboard_shortcuts.toggle_read_status_prev": "Basculer entre lu/non lu, et changer le focus sur l'élément précédent",
"page.login.google_signin": "Se connecter avec Google",
"page.login.oidc_signin": "Se connecter avec %s",
"page.login.title": "Connexion",
"page.login.webauthn_login": "Se connecter avec une clé d’accès",
"page.login.webauthn_login.error": "Impossible de se connecter avec la clé d’accès",
"page.login.webauthn_login.help": "Veuillez saisir votre nom d'utilisateur si vous utilisez une clé de sécurité. Cela n'est pas nécessaire si vous utilisez une clé d'accès (Passkey).",
"page.new_api_key.title": "Nouvelle clé d'API",
"page.new_category.title": "Nouvelle catégorie",
"page.new_user.title": "Nouvel Utilisateur",
"page.offline.message": "Vous n'êtes pas connecté",
"page.offline.refresh_page": "Essayez de rafraîchir la page",
"page.offline.title": "Mode Hors-Ligne",
"page.read_entry_count": [
"%d entrée lue",
"%d entrées lues"
],
"page.search.title": "Résultats de la recherche",
"page.sessions.table.actions": "Actions",
"page.sessions.table.current_session": "Session actuelle",
"page.sessions.table.date": "Date",
"page.sessions.table.ip": "Adresse IP",
"page.sessions.table.user_agent": "Navigateur Web",
"page.sessions.title": "Sessions",
"page.settings.link_google_account": "Associer mon compte Google",
"page.settings.link_oidc_account": "Associer mon compte %s",
"page.settings.title": "Réglages",
"page.settings.unlink_google_account": "Dissocier mon compte Google",
"page.settings.unlink_oidc_account": "Dissocier mon compte %s",
"page.settings.webauthn.actions": "Actions",
"page.settings.webauthn.added_on": "Date de création",
"page.settings.webauthn.delete": [
"Supprimer %d clé d’accès",
"Supprimer %d clés d’accès"
],
"page.settings.webauthn.last_seen_on": "Dernière utilisation",
"page.settings.webauthn.passkey_name": "Nom de la clé d’accès",
"page.settings.webauthn.passkeys": "Clés d’accès",
"page.settings.webauthn.register": "Enregistrer une nouvelle clé d’accès",
"page.settings.webauthn.register.error": "Impossible d'enregistrer la clé d’accès",
"page.shared_entries.title": "Articles partagés",
"page.shared_entries_count": [
"%d article partagé",
"%d articles partagés"
],
"page.starred.title": "Favoris",
"page.starred_entry_count": [
"%d favori",
"%d 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.users.actions": "Actions",
"page.users.admin.no": "Non",
"page.users.admin.yes": "Oui",
"page.users.is_admin": "Administrateur",
"page.users.last_login": "Dernière connexion",
"page.users.never_logged": "Jamais",
"page.users.title": "Utilisateurs",
"page.users.username": "Nom d'utilisateur",
"page.webauthn_rename.title": "Renommer la clé d'accès",
"pagination.first": "Première page",
"pagination.last": "Dernière page",
"pagination.next": "Suivant",
"pagination.previous": "Précédent",
"search.label": "Recherche",
"search.placeholder": "Recherche...",
"search.submit": "Rechercher",
"skip_to_content": "Aller au contenu",
"time_elapsed.days": [
"il y a %d jour",
"il y a %d jours"
],
"time_elapsed.hours": [
"il y a %d heure",
"il y a %d heures"
],
"time_elapsed.minutes": [
"il y a %d minute",
"il y a %d minutes"
],
"time_elapsed.months": [
"il y a %d mois",
"il y a %d mois"
],
"time_elapsed.not_yet": "pas encore",
"time_elapsed.now": "à l'instant",
"time_elapsed.weeks": [
"il y a %d semaine",
"il y a %d semaines"
],
"time_elapsed.years": [
"il y a %d an",
"il y a %d ans"
],
"time_elapsed.yesterday": "hier",
"tooltip.keyboard_shortcuts": "Raccourci clavier : %s",
"tooltip.logged_user": "Connecté en tant que %s"
} v2-2.2.13/internal/locale/translations/hi_IN.json 0000664 0000000 0000000 00000146505 15062123773 0021624 0 ustar 00root root 0000000 0000000 {
"action.cancel": "रद्द करें",
"action.download": "डाउनलोड",
"action.edit": "संपाद करे",
"action.home_screen": "होम स्क्रीन में शामिल करें",
"action.import": "आयात करे",
"action.login": "लॉग इन करें",
"action.or": "या",
"action.remove": "हटाएँ",
"action.remove_feed": "इस फ़ीड को हटाएँ",
"action.save": "सहेजें",
"action.subscribe": "सदस्यता लें",
"action.update": "नवीनीकरण करे",
"alert.account_linked": "आपका बाहरी खाता अब लिंक हो गया है!",
"alert.account_unlinked": "आपका बाहरी खाता अब अलग कर दिया गया है!",
"alert.background_feed_refresh": "सभी फ़ीड्स पृष्ठभूमि में ताज़ा की जा रही हैं। जब यह प्रक्रिया चल रही हो, तो आप मिनीफ्लक्स का उपयोग जारी रख सकते हैं।",
"alert.feed_error": "इस फ़ीड में एक समस्या है",
"alert.no_starred": "इस समय कोई बुकमार्क नहीं है",
"alert.no_category": "कोई श्रेणी नहीं है।",
"alert.no_category_entry": "इस श्रेणी में कोई विषय-वस्तु नहीं है।",
"alert.no_feed": "आपके पास कोई सदस्यता नहीं है।",
"alert.no_feed_entry": "इस फ़ीड के लिए कोई विषय-वस्तु नहीं है।",
"alert.no_feed_in_category": "इस श्रेणी के लिए कोई सदस्यता नहीं है।",
"alert.no_history": "इस समय कोई इतिहास नहीं है",
"alert.no_search_result": "इस खोज के लिए कोई परिणाम नहीं हैं।",
"alert.no_shared_entry": "कोई साझा प्रविष्टि नहीं है",
"alert.no_tag_entry": "इस टैग से मेल खाती कोई प्रविष्टियाँ नहीं हैं।",
"alert.no_unread_entry": "कोई अपठित वस्तुत नहीं है।",
"alert.no_user": "आप एकमात्र उपयोगकर्ता हैं।",
"alert.prefs_saved": "प्राथमिकताएं सहेजी गईं!",
"alert.too_many_feeds_refresh": [
"आपने बहुत अधिक फ़ीड ताज़ा करने की प्रक्रिया शुरू कर दी है। कृपया पुनः प्रयास करने से पहले %d मिनट प्रतीक्षा करें।",
"आपने बहुत अधिक फ़ीड ताज़ा करने की प्रक्रिया शुरू कर दी है। कृपया पुनः प्रयास करने से पहले %d मिनट प्रतीक्षा करें।"
],
"confirm.loading": " प्रगति में है ...",
"confirm.no": " नहीं",
"confirm.question": "मंजूर है?",
"confirm.question.refresh": "क्या आप बल द्वारा ताज़ा करना चाहते हैं?",
"confirm.yes": "हाँ",
"enclosure_media_controls.seek": "खोजें:",
"enclosure_media_controls.seek.title": "%s सेकंड खोजें",
"enclosure_media_controls.speed": "गति:",
"enclosure_media_controls.speed.faster": "तेज",
"enclosure_media_controls.speed.faster.title": "%sx गुना तेज",
"enclosure_media_controls.speed.reset": "रीसेट करें",
"enclosure_media_controls.speed.reset.title": "गति 1x पर रीसेट करें",
"enclosure_media_controls.speed.slower": "धीमा",
"enclosure_media_controls.speed.slower.title": "%sx गुना धीमा",
"entry.starred.toast.off": "तारांकित न करे",
"entry.starred.toast.on": "तारांकित",
"entry.starred.toggle.off": "सितारा हटा दो",
"entry.starred.toggle.on": "सितारा दे",
"entry.comments.label": "टिप्पणियाँ",
"entry.comments.title": "टिप्पणियाँ देखे",
"entry.estimated_reading_time": [
"पढ़ने मे %d मिनट मागेगा",
"पढ़ने मे %d मिनट मागेगा"
],
"entry.external_link.label": "बाहरी संपर्क",
"entry.save.completed": "कार्य समाप्त हुआ!",
"entry.save.label": "सहेजे",
"entry.save.title": "एस लेख को सहेजे",
"entry.save.toast.completed": "लेख को सहेज लिया",
"entry.scraper.completed": "कार्य समाप्त हुआ!",
"entry.scraper.label": "डाउनलोड",
"entry.scraper.title": "मूल विषयवस्तु लाए",
"entry.share.label": "साझा करें",
"entry.share.title": "विषयवस्तु साझा करें",
"entry.shared_entry.label": "साझा करें",
"entry.shared_entry.title": "सार्वजनिक लिंक खोले",
"entry.state.loading": "लोड हो रहा है...",
"entry.state.saving": "सहेजा जा रहा है...",
"entry.status.mark_as_read": "पढ़े हुए का चिह्न",
"entry.status.mark_as_unread": "अपठित के रूप में चिह्नित करें",
"entry.status.title": "प्रविष्टि स्थिति बदलें",
"entry.status.toast.read": "पढ़ा हुआ चिह्नित करे",
"entry.status.toast.unread": "अपठित के रूप में चिह्नित",
"entry.tags.label": "टैग:",
"entry.tags.more_tags_label": [
"%d और टैग दिखाएँ",
"%d और टैग दिखाएँ"
],
"entry.unshare.label": "न साझा कारें",
"error.api_key_already_exists": "यह एपीआई कुंजी पहले से मौजूद है।",
"error.bad_credentials": "अमान्य उपयोगकर्ता नाम या पासवर्ड।",
"error.category_already_exists": "यह श्रेणी पहले से मौजूद है।",
"error.category_not_found": "यह श्रेणी मौजूद नहीं है या इस उपयोगकर्ता से संबंधित नहीं है।",
"error.database_error": "डेटाबेस त्रुटि: %v।",
"error.different_passwords": "पासवर्ड एक जैसे नहीं हैं।",
"error.duplicate_fever_username": "पहले से ही समान फीवर उपयोगकर्ता नाम वाला कोई और है!",
"error.duplicate_googlereader_username": "समान गूगल रीडर उपयोगकर्ता नाम वाला कोई और पहले से मौजूद है!",
"error.duplicate_linked_account": "इस प्रदाता के साथ पहले से ही कोई व्यक्ति जुड़ा हुआ है!",
"error.duplicated_feed": "यह फ़ीड पहले से मौजूद है।",
"error.empty_file": "यह फ़ाइल खाली है।",
"error.entries_per_page_invalid": "प्रति पृष्ठ प्रविष्टियों की संख्या मान्य नहीं है।",
"error.feed_already_exists": "यह फ़ीड पहले से मौजूद है.",
"error.feed_category_not_found": "यह श्रेणी मौजूद नहीं है या इस उपयोगकर्ता से संबंधित नहीं है।",
"error.feed_format_not_detected": "फ़ीड प्रारूप का पता नहीं लगा सकते: %v।",
"error.feed_invalid_blocklist_rule": "ब्लॉक सूची नियम अमान्य है।",
"error.feed_invalid_keeplist_rule": "सूची रखें नियम अमान्य है।",
"error.feed_mandatory_fields": "URL और श्रेणी अनिवार्य हैं।",
"error.feed_not_found": "यह फ़ीड मौजूद नहीं है या इस उपयोगकर्ता से संबंधित नहीं है।",
"error.feed_title_not_empty": "फ़ीड शीर्षक खाली नहीं हो सकता.",
"error.feed_url_not_empty": "फ़ीड यूआरएल खाली नहीं हो सकता.",
"error.fields_mandatory": "सभी फील्ड अनिवार्य।",
"error.http_bad_gateway": "खराब गेटवे त्रुटि के कारण वेबसाइट फिलहाल उपलब्ध नहीं है। समस्या Miniflux की तरफ नहीं है। कृपया बाद में फिर से कोशिश करें।",
"error.http_body_read": "HTTP बॉडी पढ़ने में असमर्थ: %v।",
"error.http_client_error": "HTTP क्लाइंट त्रुटि: %v।",
"error.http_empty_response": "HTTP प्रतिक्रिया खाली है। शायद यह वेबसाइट बॉट सुरक्षा तंत्र का उपयोग कर रही है?",
"error.http_empty_response_body": "HTTP प्रतिक्रिया बॉडी खाली है।",
"error.http_forbidden": "इस वेबसाइट तक पहुंच वर्जित है। शायद इस वेबसाइट में बॉट सुरक्षा तंत्र है?",
"error.http_gateway_timeout": "The website is not available at the moment due to a gateway timeout error. The problem is not on Miniflux side. Please, try again later.",
"error.http_internal_server_error": "The website is not available at the moment due to a server error. The problem is not on Miniflux side. Please, try again later.",
"error.http_not_authorized": "Access to this website is not authorized. It could be a bad username or password.",
"error.http_resource_not_found": "The requested resource is not found. Please, verify the URL.",
"error.http_response_too_large": "The HTTP response is too large. You could increase the HTTP response size limit in the global settings (requires a server restart).",
"error.http_service_unavailable": "The website is not available at the moment due to an internal server error. The problem is not on Miniflux side. Please, try again later.",
"error.http_too_many_requests": "Miniflux generated too many requests to this website. Please, try again later or change the application configuration.",
"error.http_unexpected_status_code": "The website is not available at the moment due to an unexpected HTTP status code: %d. The problem is not on Miniflux side. Please, try again later.",
"error.invalid_categories_sorting_order": "अमान्य श्रेणी क्रम।",
"error.invalid_default_home_page": "अमान्य डिफ़ॉल्ट मुखपृष्ठ!",
"error.invalid_display_mode": "अमान्य वेब ऐप्लिकेशन प्रदर्शन मोड.",
"error.invalid_entry_direction": "अमान्य प्रवेश दिशा।",
"error.invalid_entry_order": "अमान्य प्रविष्टि क्रम।",
"error.invalid_feed_proxy_url": "अमान्य प्रॉक्सी यूआरएल।",
"error.invalid_feed_url": "दृष्टिकोण यूआरएल.",
"error.invalid_gesture_nav": "अमान्य इशारा नेविगेशन।",
"error.invalid_language": "अमान्य भाषा.",
"error.invalid_site_url": "अमान्य साइट यूआरएल",
"error.invalid_theme": "अमान्य थीम.",
"error.invalid_timezone": "अमान्य समयक्षेत्र.",
"error.network_operation": "Miniflux is not able to reach this website due to a network error: %v.",
"error.network_timeout": "This website is too slow and the request timed out: %v",
"error.password_min_length": "पासवर्ड में कम से कम 6 अक्षर होने चाहिए।",
"error.proxy_url_not_empty": "The proxy URL cannot be empty.",
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
"error.settings_invalid_domain_list": "Invalid domain list. Please provide a space separated list of domains.",
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
"error.settings_mandatory_fields": "उपयोगकर्ता नाम, विषयवस्तु, भाषा और समयक्षेत्र फ़ील्ड अनिवार्य हैं।",
"error.settings_media_playback_rate_range": "प्लेबैक गति सीमा से बाहर है",
"error.settings_reading_speed_is_positive": "पढ़ने की गति सकारात्मक पूर्णांक होनी चाहिए।",
"error.site_url_not_empty": "साइट का यूआरएल खाली नहीं हो सकता.",
"error.subscription_not_found": "कोई सदस्यता ढूँढने में असमर्थ.",
"error.title_required": "शीर्षक अनिवार्य है।",
"error.tls_error": "TLS error: %q. You could disable TLS verification in the feed settings if you would like.",
"error.unable_to_create_api_key": "यह एपीआई कुंजी बनाने में असमर्थ।",
"error.unable_to_create_category": "यह श्रेणी बनाने में असमर्थ.",
"error.unable_to_create_user": "इस उपयोगकर्ता को बनाने में असमर्थ।",
"error.unable_to_detect_rssbridge": "RSS-Bridge का उपयोग करके फ़ीड का पता लगाने में असमर्थ: %v.",
"error.unable_to_parse_feed": "इस फ़ीड को पार्स करने में असमर्थ: %v.",
"error.unable_to_update_category": "इस श्रेणी को अपडेट करने में असमर्थ।",
"error.unable_to_update_feed": "इस फ़ीड को अपडेट करने में असमर्थ.",
"error.unable_to_update_user": "इस उपयोगकर्ता को अपडेट करने में असमर्थ.",
"error.unlink_account_without_password": "आपको एक पासवर्ड परिभाषित करना होगा अन्यथा आप फिर से लॉगिन नहीं कर पाएंगे।",
"error.user_already_exists": "यह उपयोगकर्ता पहले से ही मौजूद है।",
"error.user_mandatory_fields": "उपयोगकर्ता नाम अनिवार्य है।",
"error.linktaco_missing_required_fields": "LinkTaco API Token और Organization Slug आवश्यक हैं",
"form.api_key.label.description": "एपीआई कुंजी लेबल",
"form.category.hide_globally": "वैश्विक अपठित सूची में प्रविष्टियां छिपाएं",
"form.category.label.title": "शीर्षक",
"form.feed.fieldset.general": "General",
"form.feed.fieldset.integration": "Third-Party Services",
"form.feed.fieldset.network_settings": "Network Settings",
"form.feed.fieldset.rules": "Rules",
"form.feed.label.allow_self_signed_certificates": "स्व-हस्ताक्षरित या अमान्य प्रमाणपत्रों की अनुमति दें",
"form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
"form.feed.label.block_filter_entry_rules": "प्रविष्टि अवरोधन नियम",
"form.feed.label.blocklist_rules": "रेगेक्स-आधारित अवरोधन फिल्टर",
"form.feed.label.category": "श्रेणी",
"form.feed.label.cookie": "कुकीज़ सेट करें",
"form.feed.label.crawler": "मूल सामग्री प्राप्त करें",
"form.feed.label.description": "विवरण",
"form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
"form.feed.label.disabled": "इस फ़ीड को रीफ़्रेश न करें",
"form.feed.label.feed_password": "फ़ीड पासवर्ड",
"form.feed.label.feed_url": "फ़ीड यूआरएल",
"form.feed.label.feed_username": "फ़ीड उपयोगकर्ता नाम",
"form.feed.label.fetch_via_proxy": "एप्लिकेशन स्तर पर कॉन्फ़िगर किए गए प्रॉक्सी का उपयोग करें",
"form.feed.label.hide_globally": "वैश्विक अपठित सूची में प्रविष्टियां छिपाएं",
"form.feed.label.ignore_http_cache": "एचटीटीपी कैश पर ध्यान न दें",
"form.feed.label.keep_filter_entry_rules": "प्रविष्टि अनुमति नियम",
"form.feed.label.keeplist_rules": "रेगेक्स-आधारित रखने वाले फिल्टर",
"form.feed.label.no_media_player": "No media player (audio/video)",
"form.feed.label.ntfy_activate": "Push entries to ntfy",
"form.feed.label.ntfy_default_priority": "Ntfy default priority",
"form.feed.label.ntfy_high_priority": "Ntfy high priority",
"form.feed.label.ntfy_low_priority": "Ntfy low priority",
"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.proxy_url": "Proxy URL",
"form.feed.label.pushover_activate": "Push entries to pushover.net",
"form.feed.label.pushover_default_priority": "Pushover default priority",
"form.feed.label.pushover_high_priority": "Pushover high priority",
"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.rewrite_rules": "सामग्री पुनर्लेखन नियम",
"form.feed.label.scraper_rules": "खुरचनी नियम",
"form.feed.label.site_url": "साइट यूआरएल",
"form.feed.label.title": "शीर्षक",
"form.feed.label.urlrewrite_rules": " यूआरएल पुनर्लेखन नियम",
"form.feed.label.user_agent": "डिफ़ॉल्ट उपयोगकर्ता एजेंट को ओवरराइड करें",
"form.feed.label.webhook_url": "Override webhook url",
"form.import.label.file": "ओपीएमएल फ़ाइल",
"form.import.label.url": "यूआरएल",
"form.integration.apprise_activate": "Push entries to Apprise",
"form.integration.apprise_services_url": "Comma separated list of Apprise service URLs",
"form.integration.apprise_url": "Apprise API URL",
"form.integration.betula_activate": "Save entries to Betula",
"form.integration.betula_token": "Betula Token",
"form.integration.betula_url": "Betula server URL",
"form.integration.cubox_activate": "Save entries to Cubox",
"form.integration.cubox_api_link": "Cubox API link",
"form.integration.discord_activate": "Push entries to Discord",
"form.integration.discord_webhook_link": "Discord Webhook link",
"form.integration.espial_activate": "विषय-वस्तु को जासूसी में सहेजें",
"form.integration.espial_api_key": "जासूसी एपीआई कुंजी",
"form.integration.espial_endpoint": "जासूसी एपीआई समापन बिंदु",
"form.integration.espial_tags": "जासूसी टैग",
"form.integration.fever_activate": "फीवर एपीआई सक्रिय करें",
"form.integration.fever_endpoint": "फीवर एपीआई समापन बिंदु:",
"form.integration.fever_password": "फीवर पासवर्ड",
"form.integration.fever_username": "फीवर उपयोगकर्ता नाम",
"form.integration.googlereader_activate": "गूगल रीडर एपीआई सक्रिय करें",
"form.integration.googlereader_endpoint": "गूगल रीडर एपीआई समापन बिंदु:",
"form.integration.googlereader_password": "गूगल रीडर पासवर्ड",
"form.integration.googlereader_username": "गूगल रीडर उपयोगकर्ता नाम",
"form.integration.instapaper_activate": "विषय-वस्तु को इंस्टापेपर में सहेजें",
"form.integration.instapaper_password": "इंस्टापेपर पासवर्ड",
"form.integration.instapaper_username": "इंस्टापेपर यूजरनेम",
"form.integration.karakeep_activate": "Save entries to Karakeep",
"form.integration.karakeep_api_key": "Karakeep API key",
"form.integration.karakeep_url": "Karakeep API Endpoint",
"form.integration.linkace_activate": "Save entries to LinkAce",
"form.integration.linkace_api_key": "LinkAce API key",
"form.integration.linkace_check_disabled": "Disable link check",
"form.integration.linkace_endpoint": "LinkAce API Endpoint",
"form.integration.linkace_is_private": "Mark link as private",
"form.integration.linkace_tags": "LinkAce Tags",
"form.integration.linkding_activate": "लिंक्डिन में विषयवस्तु सहेजें",
"form.integration.linkding_api_key": "लिंकिंग एपीआई कुंजी",
"form.integration.linkding_bookmark": "बुकमार्क को अपठित के रूप में चिह्नित करें",
"form.integration.linkding_endpoint": "लिंकिंग एपीआई समापन बिंदु",
"form.integration.linkding_tags": "Linkding Tags",
"form.integration.linktaco_activate": "LinkTaco में प्रविष्टियाँ सहेजें",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "अपना व्यक्तिगत पहुँच टोकन प्राप्त करें",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "टैग (अधिकतम 10, कॉमा से अलग किए गए)",
"form.integration.linktaco_tags_hint": "अधिकतम 10 टैग, कॉमा से अलग किए गए",
"form.integration.linktaco_visibility": "दृश्यता",
"form.integration.linktaco_visibility_public": "सार्वजनिक",
"form.integration.linktaco_visibility_private": "निजी",
"form.integration.linktaco_visibility_hint": "निजी दृश्यता के लिए भुगतान LinkTaco खाता आवश्यक है",
"form.integration.linkwarden_activate": "Save entries to Linkwarden",
"form.integration.linkwarden_api_key": "Linkwarden API key",
"form.integration.linkwarden_endpoint": "लिंकवर्डन बेस यूआरएलL",
"form.integration.matrix_bot_activate": "नए लेखों को मैट्रिक्स में स्थानांतरित करें",
"form.integration.matrix_bot_chat_id": "मैट्रिक्स रूम की आईडी",
"form.integration.matrix_bot_password": "मैट्रिक्स उपयोगकर्ता के लिए पासवर्ड",
"form.integration.matrix_bot_url": "मैट्रिक्स सर्वर URL",
"form.integration.matrix_bot_user": "मैट्रिक्स के लिए उपयोगकर्ता नाम",
"form.integration.notion_activate": "Save entries to Notion",
"form.integration.notion_page_id": "Notion Page ID",
"form.integration.notion_token": "Notion Secret Token",
"form.integration.ntfy_activate": "Push entries to ntfy",
"form.integration.ntfy_api_token": "Ntfy API Token (optional)",
"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 (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": "विषय-वस्तु को ननक्स कीपर में सहेजें",
"form.integration.nunux_keeper_api_key": "ननक्स कीपर एपीआई कुंजी",
"form.integration.nunux_keeper_endpoint": "ननक्स कीपर एपीआई समापन बिंदु",
"form.integration.omnivore_activate": "Save entries to Omnivore",
"form.integration.omnivore_api_key": "Omnivore API key",
"form.integration.omnivore_url": "Omnivore API Endpoint",
"form.integration.pinboard_activate": "सहेजें विषयवस्तु प्रति का बोर्ड ",
"form.integration.pinboard_bookmark": "बुकमार्क को अपठित के रूप में चिह्नित करें",
"form.integration.pinboard_tags": "पिनबोर्ड टैग",
"form.integration.pinboard_token": "पिनबोर्ड एपीआई टोकन",
"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.raindrop_activate": "Save entries to Raindrop",
"form.integration.raindrop_collection_id": "Collection ID",
"form.integration.raindrop_tags": "Tags (comma-separated)",
"form.integration.raindrop_token": "(Test) Token",
"form.integration.readeck_activate": "Readeck में विषयवस्तु सहेजें",
"form.integration.readeck_api_key": "Readeck एपीआई कुंजी",
"form.integration.readeck_endpoint": "Readeck यूआरएल",
"form.integration.readeck_labels": "Readeck Labels",
"form.integration.readeck_only_url": "केवल URL भेजें (पूर्ण सामग्री के बजाय)",
"form.integration.readwise_activate": "Save entries to Readwise Reader",
"form.integration.readwise_api_key": "Readwise Reader Access Token",
"form.integration.readwise_api_key_link": "Get your Readwise Access Token",
"form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "RSS-Bridge server URL",
"form.integration.shaarli_activate": "Save articles to Shaarli",
"form.integration.shaarli_api_secret": "Shaarli API Secret",
"form.integration.shaarli_endpoint": "Shaarli URL",
"form.integration.shiori_activate": "Save articles to Shiori",
"form.integration.shiori_endpoint": "Shiori API Endpoint",
"form.integration.shiori_password": "Shiori Password",
"form.integration.shiori_username": "Shiori Username",
"form.integration.slack_activate": "Push entries to Slack",
"form.integration.slack_webhook_link": "Slack Webhook link",
"form.integration.telegram_bot_activate": "टेलीग्राम चैट के लिए नई विषय-कविता पुश करें",
"form.integration.telegram_bot_disable_buttons": "Disable buttons",
"form.integration.telegram_bot_disable_notification": "Disable notification",
"form.integration.telegram_bot_disable_web_page_preview": "Disable web page preview",
"form.integration.telegram_bot_token": "बॉट टोकन",
"form.integration.telegram_chat_id": "चैट आईडी",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "विषय सहेजें वालाबाग में ",
"form.integration.wallabag_client_id": "वालाबैग क्लाइंट आईडी",
"form.integration.wallabag_client_secret": "वालाबैग क्लाइंट सीक्रेट",
"form.integration.wallabag_endpoint": "वल्लाबैग बेस यूआरएल",
"form.integration.wallabag_only_url": "केवल URL भेजें (पूर्ण सामग्री के बजाय)",
"form.integration.wallabag_password": "वालाबैग पासवर्ड",
"form.integration.wallabag_username": "वालाबैग उपयोगकर्ता नाम",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Enable Webhooks",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Default Webhook URL",
"form.prefs.fieldset.application_settings": "Application Settings",
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
"form.prefs.fieldset.reader_settings": "Reader Settings",
"form.prefs.help.external_font_hosts": "Space separated list of external font hosts to allow. For example: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.always_open_external_links": "Read articles by opening external links",
"form.prefs.label.categories_sorting_order": "श्रेणियाँ छँटाई",
"form.prefs.label.cjk_reading_speed": "चीनी, कोरियाई और जापानी के लिए पढ़ने की गति (प्रति मिनट वर्ण)",
"form.prefs.label.custom_css": "कस्टम सीएसएस",
"form.prefs.label.custom_js": "कस्टम जेएस",
"form.prefs.label.default_home_page": "डिफ़ॉल्ट होमपेज़",
"form.prefs.label.default_reading_speed": "अन्य भाषाओं के लिए पढ़ने की गति (प्रति मिनट शब्द)",
"form.prefs.label.display_mode": "प्रोग्रेसिव वेब ऐप (PWA) डिस्प्ले मोड",
"form.prefs.label.entries_per_page": "प्रति पृष्ठ प्रविष्टियाँ",
"form.prefs.label.entry_order": "प्रवेश छँटाई कॉलम",
"form.prefs.label.entry_sorting": "प्रवेश छँटाई",
"form.prefs.label.entry_swipe": "टच स्क्रीन पर एंट्री स्वाइप सक्षम करें",
"form.prefs.label.external_font_hosts": "External font hosts",
"form.prefs.label.gesture_nav": "प्रविष्टियों के बीच नेविगेट करने के लिए इशारा",
"form.prefs.label.keyboard_shortcuts": "कीबोर्ड शॉर्टकट सक्षम करें",
"form.prefs.label.language": "भाषाओं",
"form.prefs.label.mark_read_manually": "Mark entries as read manually",
"form.prefs.label.mark_read_on_media_completion": "Only mark as read when audio/video playback reaches 90%% completion",
"form.prefs.label.mark_read_on_view": "देखे जाने पर स्वचालित रूप से प्रविष्टियों को पढ़ने के रूप में चिह्नित करें",
"form.prefs.label.mark_read_on_view_or_media_completion": "Mark entries as read when viewed. For audio/video, mark as read at 90%% completion",
"form.prefs.label.media_playback_rate": "ऑडियो/वीडियो की प्लेबैक गति",
"form.prefs.label.open_external_links_in_new_tab": "बाहरी लिंक को एक नए टैब में खोलें (लिंक में target=\"_blank\" जोड़ता है)",
"form.prefs.label.show_reading_time": "विषय के लिए अनुमानित पढ़ने का समय दिखाएं",
"form.prefs.label.theme": "थीम",
"form.prefs.label.timezone": "समय क्षेत्र",
"form.prefs.select.alphabetical": "वर्णक्रम",
"form.prefs.select.browser": "ब्राउज़र",
"form.prefs.select.created_time": "प्रवेश बनाया समय",
"form.prefs.select.fullscreen": "पूर्ण स्क्रीन",
"form.prefs.select.minimal_ui": "कम से कम",
"form.prefs.select.none": "कोई नहीं",
"form.prefs.select.older_first": "पहले पुरानी प्रविष्टियाँ",
"form.prefs.select.publish_time": "प्रवेश प्रकाशित समय",
"form.prefs.select.recent_first": "हाल की प्रविष्टियाँ पहले",
"form.prefs.select.standalone": "स्टैंडअलोन",
"form.prefs.select.swipe": "कड़ी चोट",
"form.prefs.select.tap": "दो बार टैप",
"form.prefs.select.unread_count": "अपठित गणना",
"form.submit.loading": "लोड हो रहा है...",
"form.submit.saving": "सहेजा जा रहा है...",
"form.user.label.admin": "प्रशासक",
"form.user.label.confirmation": "पासवर्ड पुष्टि",
"form.user.label.password": "पासवर्ड",
"form.user.label.username": "उपयोगकर्ता नाम",
"menu.about": "के बारे में",
"menu.add_feed": "सदस्यता जोरीय",
"menu.add_user": "उपयोगकर्ता जोड़ें",
"menu.api_keys": "एपीआई कुंजी",
"menu.categories": "श्रेणियाँ",
"menu.create_api_key": "नई एपीआई कुंजी बनाएं",
"menu.create_category": "श्रेणी बनाए",
"menu.edit_category": "श्रेणी संपाद करे",
"menu.edit_feed": "फ़ीड संपाद करे",
"menu.export": "निर्यात करे",
"menu.feed_entries": "प्रविष्टियाँ",
"menu.feeds": "फ़ीड",
"menu.flush_history": "इतिहास मिटाएँ",
"menu.history": "इतिहास",
"menu.home_page": "Home page",
"menu.import": "आयात करे",
"menu.integrations": "एकीकरण",
"menu.logout": "लॉग आउट",
"menu.mark_all_as_read": "सभी को पढ़ा हुआ मार्क करें",
"menu.mark_page_as_read": "इस पृष्ठ को पढ़ा हुआ चिह्नित करें",
"menu.preferences": "पसंद",
"menu.refresh_all_feeds": "पृष्ठभूमि में सभी फ़ीड को ताज़ा करें",
"menu.refresh_feed": "ताज़ा करें",
"menu.search": "खोज",
"menu.sessions": "सत्र",
"menu.settings": "समायोजन",
"menu.shared_entries": "साझा प्रविष्टियां",
"menu.show_all_entries": "सभी प्रविष्टियाँ दिखाए",
"menu.show_only_starred_entries": "Show only starred entries",
"menu.show_only_unread_entries": "सभी अपठित प्रविष्टियाँ दिखाए",
"menu.starred": "तारांकित",
"menu.title": "Menu",
"menu.unread": "अपठित",
"menu.users": "उपयोगकर्ताओं",
"page.about.author": "रचयिता:",
"page.about.build_date": "बनाने की तिथि:",
"page.about.credits": "आभार सूची",
"page.about.db_usage": "Database size:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "वैश्विक विन्यास विकल्प",
"page.about.go_version": "गो संस्करण:",
"page.about.license": "अनुज्ञा:",
"page.about.postgres_version": "पोस्तग्राइस संस्करण:",
"page.about.title": "पृष्ठ के बारे में",
"page.about.version": "संस्करण:",
"page.add_feed.choose_feed": "एक सदस्यता का चयन करे",
"page.add_feed.label.url": "यूआरएल",
"page.add_feed.legend.advanced_options": "उन्नत विकल्प",
"page.add_feed.no_category": "कोई श्रेणी नहीं है। एक श्रेणी अव्यशाक है।",
"page.add_feed.submit": "सदस्यता खोजे",
"page.add_feed.title": "नया सदस्यता",
"page.api_keys.never_used": "कभी प्रयोग नहीं हुआ",
"page.api_keys.table.actions": "कार्रवाई",
"page.api_keys.table.created_at": "निर्माण तिथि",
"page.api_keys.table.description": "विवरण",
"page.api_keys.table.last_used_at": "आखरी इस्त्तमाल किया गया",
"page.api_keys.table.token": "टोकन",
"page.api_keys.title": "एपीआई कुंजी",
"page.categories.entries": "विषयवस्तुया",
"page.categories.feed_count": [
"%d फ़ीड बाकी है।",
"%d फ़ीड बाकी है।"
],
"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": "ईटाग हैडर:",
"page.edit_feed.last_check": "अंतिम जांच:",
"page.edit_feed.last_modified_header": "अंतिम बार संशोधित हैडर:",
"page.edit_feed.last_parsing_error": "अंतिम पार्सिंग त्रुटि",
"page.edit_feed.no_header": "कोई भी नहीं",
"page.edit_feed.title": "%s फ़ीड संपाद करे",
"page.edit_user.title": "%s उपभोक्ता संपाद करे",
"page.entry.attachments": "संलग्नक",
"page.feeds.error_count": [
"%d समस्या",
"%d समस्याए"
],
"page.feeds.last_check": "आखरी जाँच",
"page.feeds.next_check": "Next check:",
"page.feeds.read_counter": "पड़े हुए विषयवस्तुया",
"page.feeds.title": "फ़ीड",
"page.footer.elevator": "Back to top",
"page.history.title": "इतिहास",
"page.import.title": "आयात",
"page.integration.bookmarklet": "बुकमार्कलेट",
"page.integration.bookmarklet.help": "यह विशेष लिंक आपको अपने वेब ब्राउज़र में बुकमार्क का उपयोग करके सीधे वेबसाइट की सदस्यता लेने की अनुमति देता है।",
"page.integration.bookmarklet.instructions": "इस लिंक को खींचकर अपने बुकमार्क पर छोड़ दें।",
"page.integration.bookmarklet.name": "मिनीफ्लक्स में जोड़ें",
"page.integration.miniflux_api": "मिनिफलक्ष एपीआई",
"page.integration.miniflux_api_endpoint": "एपीआई समापन बिंदु",
"page.integration.miniflux_api_password": "पासवर्ड",
"page.integration.miniflux_api_password_value": "आपका खाता पासवर्ड",
"page.integration.miniflux_api_username": "यूसर्नेम",
"page.integrations.title": "एकीकरण",
"page.keyboard_shortcuts.close_modal": "मोडल डायलॉग बंद करें",
"page.keyboard_shortcuts.download_content": "मूल सामग्री डाउनलोड करें",
"page.keyboard_shortcuts.go_to_bottom_item": "निचले आइटम पर जाएँ",
"page.keyboard_shortcuts.go_to_categories": "श्रेणि पर जाएं",
"page.keyboard_shortcuts.go_to_feed": "फ़ीड पर जाएं",
"page.keyboard_shortcuts.go_to_feeds": "फ़ीड पर जाएं",
"page.keyboard_shortcuts.go_to_history": "इतिहास पर जाएं",
"page.keyboard_shortcuts.go_to_next_item": "अगले आइटम पर जाएं",
"page.keyboard_shortcuts.go_to_next_page": "अगले पेज पर जाएं",
"page.keyboard_shortcuts.go_to_previous_item": "पिछले आइटम पर जाएं",
"page.keyboard_shortcuts.go_to_previous_page": "पिछले पृष्ठ पर जाएं",
"page.keyboard_shortcuts.go_to_search": "सर्च फॉर्म पर फोकस सेट करें",
"page.keyboard_shortcuts.go_to_settings": "सेटिंग्स में जाओ",
"page.keyboard_shortcuts.go_to_starred": "बुकमार्क पर जाएं",
"page.keyboard_shortcuts.go_to_top_item": "शीर्ष आइटम पर जाएँ",
"page.keyboard_shortcuts.go_to_unread": "अपठित पर जाएं",
"page.keyboard_shortcuts.mark_page_as_read": "मौजूदा पेज को पढ़ा हुआ चिह्नित करें",
"page.keyboard_shortcuts.open_comments": "टिप्पणी लिंक खोलें",
"page.keyboard_shortcuts.open_comments_same_window": "मौजूदा टैब में टिप्पणी लिंक खोलें",
"page.keyboard_shortcuts.open_item": "चयनित आइटम खोलें",
"page.keyboard_shortcuts.open_original": "मूल लिंक खोलें",
"page.keyboard_shortcuts.open_original_same_window": "वर्तमान टैब में मूल लिंक खोलें",
"page.keyboard_shortcuts.refresh_all_feeds": "बैकग्राउंड में सभी फ़ीड्स रीफ़्रेश करें",
"page.keyboard_shortcuts.remove_feed": "यह फ़ीड हटाएं",
"page.keyboard_shortcuts.save_article": "विषयवस्तु सहेजें",
"page.keyboard_shortcuts.scroll_item_to_top": "आइटम को ऊपर तक स्क्रॉल करें",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "कीबोर्ड शॉर्टकट दिखाएं",
"page.keyboard_shortcuts.subtitle.actions": "कार्रवाई",
"page.keyboard_shortcuts.subtitle.items": "आइटम नेविगेशन",
"page.keyboard_shortcuts.subtitle.pages": "पेज नेविगेशन",
"page.keyboard_shortcuts.subtitle.sections": "अनुभाग नेविगेशन",
"page.keyboard_shortcuts.title": "कुंजीपटल अल्प मार्ग",
"page.keyboard_shortcuts.toggle_star_status": "बुकमार्क टॉगल करें",
"page.keyboard_shortcuts.toggle_entry_attachments": "Toggle open/close entry attachments",
"page.keyboard_shortcuts.toggle_read_status_next": "पढ़ें/अपठित टॉगल करें, अगला फ़ोकस करें",
"page.keyboard_shortcuts.toggle_read_status_prev": "पढ़ें/अपठित टॉगल करें, पिछला फ़ोकस करें",
"page.login.google_signin": "गूगल के साथ साइन इन करें",
"page.login.oidc_signin": "ओपन-ईद के साथ साइन इन करें (%s)",
"page.login.title": "साइन इन करें",
"page.login.webauthn_login": "पासकी से लॉगिन करें",
"page.login.webauthn_login.error": "पासकी से लॉगिन करने में असमर्थ",
"page.login.webauthn_login.help": "Please enter your username if you're using a security key. This is not required if you are using a Passkey (discoverable credentials).",
"page.new_api_key.title": "नई एपीआई कुंजी",
"page.new_category.title": "नया श्रेणी",
"page.new_user.title": "नया उपभोक्ता",
"page.offline.message": "आप संपर्क में नहीं हैं",
"page.offline.refresh_page": "पृष्ठ को ताज़ा करने का प्रयास करें",
"page.offline.title": "ऑफ़लाइन मोड",
"page.read_entry_count": [
"%d read entry",
"%d read entries"
],
"page.search.title": "खोज का परिणाम",
"page.sessions.table.actions": "कार्रवाई",
"page.sessions.table.current_session": "वर्तमान सत्र",
"page.sessions.table.date": "दिनांक",
"page.sessions.table.ip": "आईपी पता",
"page.sessions.table.user_agent": "उपभोक्ता अभिकर्ता",
"page.sessions.title": "सत्र",
"page.settings.link_google_account": "मेरा गूगल खाता जोरीय",
"page.settings.link_oidc_account": "मेरा ओपन-ईद खाता जोरीय (%s)",
"page.settings.title": "समायोजन",
"page.settings.unlink_google_account": "मेरा गूगल खाता हटाय",
"page.settings.unlink_oidc_account": "मेरा ओपन-ईद खाता हटाय (%s)",
"page.settings.webauthn.actions": "Actions",
"page.settings.webauthn.added_on": "Added On",
"page.settings.webauthn.delete": [
"%d पासकुंजी निकालें",
"%d पासकी हटाएं"
],
"page.settings.webauthn.last_seen_on": "Last Used",
"page.settings.webauthn.passkey_name": "Passkey Name",
"page.settings.webauthn.passkeys": "Passkeys",
"page.settings.webauthn.register": "रजिस्टर पासकी",
"page.settings.webauthn.register.error": "पासकी पंजीकृत करने में असमर्थ",
"page.shared_entries.title": "साझा किया हुआ प्रविष्टि",
"page.shared_entries_count": [
"%d shared entry",
"%d shared entries"
],
"page.starred.title": "तारांकित",
"page.starred_entry_count": [
"%d starred entry",
"%d starred entries"
],
"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.users.actions": "कार्रवाई",
"page.users.admin.no": "नहीं",
"page.users.admin.yes": "हां",
"page.users.is_admin": "प्रशासक",
"page.users.last_login": "आखरी लॉगइन",
"page.users.never_logged": "कभी नहीं",
"page.users.title": "उपभोक्ता",
"page.users.username": "यूसर्नेम",
"page.webauthn_rename.title": "Rename Passkey",
"pagination.first": "पहला",
"pagination.last": "अंतिम",
"pagination.next": "अगला",
"pagination.previous": "पिछला",
"search.label": "खोजे",
"search.placeholder": "खोजे...",
"search.submit": "खोजें",
"skip_to_content": "सामग्री पर जाएं",
"time_elapsed.days": [
"%d दिन पहले",
"%d दिन पहले"
],
"time_elapsed.hours": [
"%d घंटेभर पहले",
"%d घंटो पहले"
],
"time_elapsed.minutes": [
"%d मिनट पहले",
"%d मिनट पहले"
],
"time_elapsed.months": [
"%d महीने पहले",
"%d महिनो पहले"
],
"time_elapsed.not_yet": "अभी तक नहीं",
"time_elapsed.now": "अभी",
"time_elapsed.weeks": [
"%d सप्ताह पहले",
"%d हफ्तों पहले"
],
"time_elapsed.years": [
"%d साल पहले",
"%d वर्षों पहले"
],
"time_elapsed.yesterday": "कल",
"tooltip.keyboard_shortcuts": "कुंजीपटल शॉर्टकट: %s",
"tooltip.logged_user": "%s के रूप में लॉग इन किया"
} v2-2.2.13/internal/locale/translations/id_ID.json 0000664 0000000 0000000 00000111100 15062123773 0021565 0 ustar 00root root 0000000 0000000 {
"action.cancel": "batal",
"action.download": "Unduh",
"action.edit": "Sunting",
"action.home_screen": "Tambahkan ke beranda",
"action.import": "Impor",
"action.login": "Masuk",
"action.or": "atau",
"action.remove": "Hapus",
"action.remove_feed": "Hapus umpan ini",
"action.save": "Simpan",
"action.subscribe": "Langgan",
"action.update": "Perbarui",
"alert.account_linked": "Akun eksternal Anda sudah terhubung!",
"alert.account_unlinked": "Akun eksternal Anda sudah terputus!",
"alert.background_feed_refresh": "Semua umpan sedang disegarkan di latar belakang. Anda bisa lanjut menggunakan Miniflux sembari proses ini berlanjut.",
"alert.feed_error": "Ada masalah dengan umpan ini",
"alert.no_starred": "Tidak ada markah.",
"alert.no_category": "Tidak ada kategori.",
"alert.no_category_entry": "Tidak ada artikel di kategori ini.",
"alert.no_feed": "Anda tidak memiliki langganan.",
"alert.no_feed_entry": "Tidak ada artikel di umpan ini.",
"alert.no_feed_in_category": "Tidak ada langganan untuk kategori ini.",
"alert.no_history": "Tidak ada riwayat untuk saat ini.",
"alert.no_search_result": "Tidak ada hasil untuk pencarian ini.",
"alert.no_shared_entry": "Tidak ada entri yang dibagikan.",
"alert.no_tag_entry": "Tidak ada entri yang cocok dengan tag ini.",
"alert.no_unread_entry": "Belum ada artikel yang dibaca.",
"alert.no_user": "Anda adalah satu-satunya pengguna.",
"alert.prefs_saved": "Preferensi disimpan!",
"alert.too_many_feeds_refresh": [
"Anda terlalu banyak menyegarkan umpan. Mohon tunggu %d menit sebelum mencoba lagi."
],
"confirm.loading": "Sedang progres...",
"confirm.no": "tidak",
"confirm.question": "Apakah Anda yakin?",
"confirm.question.refresh": "Apakah Anda ingin memaksa penyegaran?",
"confirm.yes": "ya",
"enclosure_media_controls.seek": "Putar:",
"enclosure_media_controls.seek.title": "Putar %s detik",
"enclosure_media_controls.speed": "Kecepatan:",
"enclosure_media_controls.speed.faster": "Lebih cepat",
"enclosure_media_controls.speed.faster.title": "Lebih cepat %sx",
"enclosure_media_controls.speed.reset": "Atur ulang",
"enclosure_media_controls.speed.reset.title": "Atur ulang ke 1x",
"enclosure_media_controls.speed.slower": "Lebih lambat",
"enclosure_media_controls.speed.slower.title": "Lebih lambat %sx",
"entry.starred.toast.off": "Batal Markahi",
"entry.starred.toast.on": "Markahi",
"entry.starred.toggle.off": "Batal Markahi",
"entry.starred.toggle.on": "Markahi",
"entry.comments.label": "Komentar",
"entry.comments.title": "Lihat Komentar",
"entry.estimated_reading_time": [
"%d menit untuk dibaca"
],
"entry.external_link.label": "Tautan eksternal",
"entry.save.completed": "Selesai!",
"entry.save.label": "Simpan",
"entry.save.title": "Simpan artikel ini",
"entry.save.toast.completed": "Artikel tersimpan",
"entry.scraper.completed": "Selesai!",
"entry.scraper.label": "Unduh",
"entry.scraper.title": "Ambil konten asli",
"entry.share.label": "Bagikan",
"entry.share.title": "Bagikan artikel ini",
"entry.shared_entry.label": "Bagikan",
"entry.shared_entry.title": "Buka tautan publik",
"entry.state.loading": "Memuat...",
"entry.state.saving": "Menyimpan...",
"entry.status.mark_as_read": "Telah dibaca",
"entry.status.mark_as_unread": "Belum dibaca",
"entry.status.title": "Ubah status entri",
"entry.status.toast.read": "Ditandai sebagai telah dibaca",
"entry.status.toast.unread": "Ditandai sebagai belum dibaca",
"entry.tags.label": "Tanda:",
"entry.tags.more_tags_label": [
"Tampilkan %d tag lainnya"
],
"entry.unshare.label": "Batal bagikan",
"error.api_key_already_exists": "Kunci API ini sudah ada.",
"error.bad_credentials": "Nama pengguna atau kata sandi tidak valid.",
"error.category_already_exists": "Kategori ini telah ada.",
"error.category_not_found": "Kategori ini tidak ada atau tidak dipunyai oleh pengguna ini.",
"error.database_error": "Galat basis data: %v.",
"error.different_passwords": "Kata sandi tidak sama.",
"error.duplicate_fever_username": "Sudah ada pengguna lain dengan nama pengguna Fever yang sama!",
"error.duplicate_googlereader_username": "Sudah ada pengguna lain dengan nama pengguna Google Reader yang sama!",
"error.duplicate_linked_account": "Sudah ada pengguna lain yang terhubung dengan penyedia ini!",
"error.duplicated_feed": "Umpan ini sudah ada.",
"error.empty_file": "Berkas ini kosong.",
"error.entries_per_page_invalid": "Jumlah entri per halaman tidak valid.",
"error.feed_already_exists": "Umpan ini sudah ada.",
"error.feed_category_not_found": "Kategori ini tidak ada atau tidak dipunyai oleh pengguna ini.",
"error.feed_format_not_detected": "Tidak dapat mendeteksi format umpan: %v.",
"error.feed_invalid_blocklist_rule": "Aturan blokir tidak valid.",
"error.feed_invalid_keeplist_rule": "Aturan simpan tidak valid.",
"error.feed_mandatory_fields": "Harus ada URL dan kategorinya.",
"error.feed_not_found": "Umpan ini tidak ada atau tidak dipunyai oleh pengguna ini",
"error.feed_title_not_empty": "Judul umpan tidak boleh kosong.",
"error.feed_url_not_empty": "URL umpan tidak boleh kosong.",
"error.fields_mandatory": "Semua bidang diharuskan.",
"error.http_bad_gateway": "Situs ini tidak tersedia saat ini karena kesalahan akses peladen situs. Masalah ini bukan pada sisi Miniflux. Coba lagi nanti.",
"error.http_body_read": "Tidak dapat membaca badan HTTP: %v.",
"error.http_client_error": "Galat klien HTTP: %v.",
"error.http_empty_response": "Balasan HTTP kosong. Mungkin, situs ini menggunakan mekanisme perlindungan dari bot?",
"error.http_empty_response_body": "Badan balasan HTTP kosong.",
"error.http_forbidden": "Akses ke situs ini terlarang. Mungkin, situs ini menggunakan mekanisme perlindungan dari bot?",
"error.http_gateway_timeout": "Situs ini tidak tersedia saat ini karena kesalahan akses jaringan peladen situs. Masalah ini bukan pada sisi Miniflux. Coba lagi nanti.",
"error.http_internal_server_error": "Situs ini tidak tersedia saat ini karena galat peladen situs. Masalah ini bukan pada sisi Miniflux. Coba lagi nanti.",
"error.http_not_authorized": "Akses ke situs ini tidak diizinkan. Mungkin nama pengguna atau kata sandinya salah.",
"error.http_resource_not_found": "Sumber daya yang diminta tidak ditemukan. Periksa kembali URL-nya.",
"error.http_response_too_large": "Balasan HTTP terlalu besar. Anda bisa menaikkan batas ukuran balasan HTTP di pengaturan global (membutuhkan pemulaian ulang peladen).",
"error.http_service_unavailable": "Situs ini tidak tersedia saat ini dikarenakan galat internal peladen situs. Masalah ini bukan pada sisi Miniflux. Coba lagi nanti.",
"error.http_too_many_requests": "Terlalu banyak koneksi dari Miniflux yang dibuat ke situs ini. Coba lagi nanti atau ubah konfigurasi aplikasi.",
"error.http_unexpected_status_code": "Situs ini tidak dapat dijangkau saat ini dikarenakan kode status HTTP tak diduga: %d Masalah ini bukan pada sisi Miniflux. Coba lagi nanti.",
"error.invalid_categories_sorting_order": "Urutan penyortiran kategori tidak valid.",
"error.invalid_default_home_page": "Beranda baku tidak valid!",
"error.invalid_display_mode": "Mode tampilan aplikasi web tidak valid.",
"error.invalid_entry_direction": "Urutan entri tidak valid.",
"error.invalid_entry_order": "Urutan entri tidak valid.",
"error.invalid_feed_proxy_url": "URL proksi tidak valid.",
"error.invalid_feed_url": "URL umpan tidak valid.",
"error.invalid_gesture_nav": "Navigasi gestur tidak valid.",
"error.invalid_language": "Bahasa tidak valid.",
"error.invalid_site_url": "URL situs tidak valid.",
"error.invalid_theme": "Tema tidak valid.",
"error.invalid_timezone": "Zona waktu tidak valid.",
"error.network_operation": "Miniflux tidak dapat menjangkau situs ini dikarenakan galat jaringan: %v.",
"error.network_timeout": "Situs ini terlalu lambat dan permintaan ke situs terlalu lama: %v",
"error.password_min_length": "Kata sandi harus memiliki setidaknya 6 karakter.",
"error.proxy_url_not_empty": "URL proksi tidak boleh kosong.",
"error.settings_block_rule_fieldname_invalid": "Aturan blokir tidak valid: aturan #%d tidak mempunyai nama bidang yang valid (Opsi: %s)",
"error.settings_block_rule_invalid_regex": "Aturan blokir tidak valid: aturan pola #%d bukan ekspresi regular (regex) yang valid",
"error.settings_block_rule_regex_required": "Aturan blokir tidak valid: aturan pola #%d tidak disediakan",
"error.settings_block_rule_separator_required": "Aturan blokir tidak valid: aturan pola #%d diharuskan dipisah menggunakan '='",
"error.settings_invalid_domain_list": "Daftar domain tidak valid. Mohon sediakan daftar domain yang dipisah spasi.",
"error.settings_keep_rule_fieldname_invalid": "Aturan simpan tidak valid: aturan #%d tidak mempunyai nama bidang yang valid (Opsi: %s)",
"error.settings_keep_rule_invalid_regex": "Aturan simpan tidak valid: aturan pola #%d bukan ekspresi regular (regex) yang valid",
"error.settings_keep_rule_regex_required": "Aturan simpan tidak valid: aturan pola #%d tidak disediakan",
"error.settings_keep_rule_separator_required": "Aturan simpan tidak valid: aturan pola #%d diharuskan dipisah menggunakan '='",
"error.settings_mandatory_fields": "Harus ada nama pengguna, tema, bahasa, dan zona waktu.",
"error.settings_media_playback_rate_range": "Kecepatan pemutaran di luar jangkauan",
"error.settings_reading_speed_is_positive": "Kecepatan membaca harus integer positif.",
"error.site_url_not_empty": "URL situs tidak boleh kosong.",
"error.subscription_not_found": "Tidak bisa mencari langganan apa pun.",
"error.title_required": "Judul harus ada.",
"error.tls_error": "Galat TLS: %q. Anda bisa mematikan verifikasi TLS di pengaturan umpan jika Anda mau.",
"error.unable_to_create_api_key": "Tidak bisa membuat kunci API ini.",
"error.unable_to_create_category": "Tidak bisa membuat kategori ini.",
"error.unable_to_create_user": "Tidak bisa membuat pengguna tersebut.",
"error.unable_to_detect_rssbridge": "Tidak dapat mendeteksi umpan menggunakan RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Tidak dapat membaca umpan: %v.",
"error.unable_to_update_category": "Tidak bisa memperbarui kategori ini.",
"error.unable_to_update_feed": "Tidak bisa memperbarui umpan ini.",
"error.unable_to_update_user": "Tidak bisa memperbarui pengguna tersebut.",
"error.unlink_account_without_password": "Anda harus mengatur kata sandi atau Anda tidak bisa masuk kembali.",
"error.user_already_exists": "Pengguna ini sudah ada.",
"error.user_mandatory_fields": "Harus ada nama pengguna.",
"error.linktaco_missing_required_fields": "LinkTaco API Token dan Organization Slug diperlukan",
"form.api_key.label.description": "Label Kunci API",
"form.category.hide_globally": "Sembunyikan entri di daftar belum dibaca global",
"form.category.label.title": "Judul",
"form.feed.fieldset.general": "Umum",
"form.feed.fieldset.integration": "Pengaturan Pihak Ketiga",
"form.feed.fieldset.network_settings": "Pengaturan Jaringan",
"form.feed.fieldset.rules": "Aturan",
"form.feed.label.allow_self_signed_certificates": "Perbolehkan sertifikat web tidak valid atau sertifikasi sendiri",
"form.feed.label.apprise_service_urls": "Daftar yang dipisahkan koma untuk URL layanan Apprise",
"form.feed.label.block_filter_entry_rules": "Aturan Pemblokiran Entri",
"form.feed.label.blocklist_rules": "Filter Pemblokiran Berbasis Regex",
"form.feed.label.category": "Kategori",
"form.feed.label.cookie": "Atur Kuki",
"form.feed.label.crawler": "Ambil konten asli",
"form.feed.label.description": "Deskripsi",
"form.feed.label.disable_http2": "Matikan HTTP/2 untuk menghindari pelacakan",
"form.feed.label.disabled": "Jangan perbarui umpan ini",
"form.feed.label.feed_password": "Kata Sandi Umpan",
"form.feed.label.feed_url": "URL Umpan",
"form.feed.label.feed_username": "Nama Pengguna Umpan",
"form.feed.label.fetch_via_proxy": "Gunakan proksi yang dikonfigurasi di tingkat aplikasi",
"form.feed.label.hide_globally": "Sembunyikan entri di daftar belum dibaca global",
"form.feed.label.ignore_http_cache": "Abaikan Tembolok HTTP",
"form.feed.label.keep_filter_entry_rules": "Aturan Izin Entri",
"form.feed.label.keeplist_rules": "Filter Simpan Berbasis Regex",
"form.feed.label.no_media_player": "Tidak ada pemutar media (audio/video)",
"form.feed.label.ntfy_activate": "Kirim artikel ke ntfy",
"form.feed.label.ntfy_default_priority": "Prioritas baku Ntfy",
"form.feed.label.ntfy_high_priority": "Priroritas tinggi Ntfy",
"form.feed.label.ntfy_low_priority": "Prioritas rendah Ntfy",
"form.feed.label.ntfy_max_priority": "Prioritas maksimal Ntfy",
"form.feed.label.ntfy_min_priority": "Prioritas minimal Ntfy",
"form.feed.label.ntfy_priority": "Prioritas Ntfy",
"form.feed.label.ntfy_topic": "Topik Ntfy (opsional)",
"form.feed.label.proxy_url": "URL Proksi",
"form.feed.label.pushover_activate": "Kirim artikel ke pushover.net",
"form.feed.label.pushover_default_priority": "Prioritas baku Pushover",
"form.feed.label.pushover_high_priority": "Prioritas tinggi Pushover",
"form.feed.label.pushover_low_priority": "Prioritas rendah Pushover",
"form.feed.label.pushover_max_priority": "Prioritas maksimal Pushover",
"form.feed.label.pushover_min_priority": "Prioritas minimal Pushover",
"form.feed.label.pushover_priority": "Prioritas pesan Pushover",
"form.feed.label.rewrite_rules": "Aturan Penulisan Ulang Konten",
"form.feed.label.scraper_rules": "Aturan Pengambil Data",
"form.feed.label.site_url": "URL Situs",
"form.feed.label.title": "Judul",
"form.feed.label.urlrewrite_rules": "Aturan Tulis Ulang URL",
"form.feed.label.user_agent": "Timpa User Agent Baku",
"form.feed.label.webhook_url": "Timpa URL Webhook",
"form.import.label.file": "Berkas OPML",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Kirim artikel ke Apprise",
"form.integration.apprise_services_url": "Daftar yang dipisahkan koma untuk URL layanan Apprise",
"form.integration.apprise_url": "URL API Apprise",
"form.integration.betula_activate": "Simpan artikel ke Betula",
"form.integration.betula_token": "Token Betula",
"form.integration.betula_url": "URL Peladen Betula",
"form.integration.cubox_activate": "Simpan artikel ke Cubox",
"form.integration.cubox_api_link": "Tautan API Cubox",
"form.integration.discord_activate": "Kirim artikel ke Discord",
"form.integration.discord_webhook_link": "Tautan Webhook Discord",
"form.integration.espial_activate": "Simpan artikel ke Espial",
"form.integration.espial_api_key": "Kunci API Espial",
"form.integration.espial_endpoint": "Titik URL API Espial",
"form.integration.espial_tags": "Tanda di Espial",
"form.integration.fever_activate": "Aktifkan API Fever",
"form.integration.fever_endpoint": "Titik URL API Fever:",
"form.integration.fever_password": "Kata Sandi Fever",
"form.integration.fever_username": "Nama Pengguna Fever",
"form.integration.googlereader_activate": "Aktifkan API Google Reader",
"form.integration.googlereader_endpoint": "Titik URL API Google Reader:",
"form.integration.googlereader_password": "Kata Sandi Google Reader",
"form.integration.googlereader_username": "Nama Pengguna Google Reader",
"form.integration.instapaper_activate": "Simpan artikel ke Instapaper",
"form.integration.instapaper_password": "Kata Sandi Instapaper",
"form.integration.instapaper_username": "Nama Pengguna Instapaper",
"form.integration.karakeep_activate": "Simpan artikel ke Karakeep",
"form.integration.karakeep_api_key": "Kunci API Karakeep",
"form.integration.karakeep_url": "Titik URL API Karakeep",
"form.integration.linkace_activate": "Simpan artikel ke LinkAce",
"form.integration.linkace_api_key": "Kunci API LinkAce",
"form.integration.linkace_check_disabled": "Matikan pemeriksaan tautan",
"form.integration.linkace_endpoint": "Titik URL API LinkAce",
"form.integration.linkace_is_private": "Tandai tautan sebagai pribadi",
"form.integration.linkace_tags": "Tanda LinkAce",
"form.integration.linkding_activate": "Simpan artikel ke Linkding",
"form.integration.linkding_api_key": "Kunci API Linkding",
"form.integration.linkding_bookmark": "Tandai markah sebagai belum dibaca",
"form.integration.linkding_endpoint": "Titik URL API Linkding",
"form.integration.linkding_tags": "Tanda Linkding",
"form.integration.linktaco_activate": "Simpan entri ke LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Dapatkan token akses pribadi Anda di",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Tag (maksimal 10, dipisahkan koma)",
"form.integration.linktaco_tags_hint": "Maksimal 10 tag, dipisahkan koma",
"form.integration.linktaco_visibility": "Visibilitas",
"form.integration.linktaco_visibility_public": "Publik",
"form.integration.linktaco_visibility_private": "Pribadi",
"form.integration.linktaco_visibility_hint": "Visibilitas PRIBADI membutuhkan akun LinkTaco berbayar",
"form.integration.linkwarden_activate": "Simpan artikel ke Linkwarden",
"form.integration.linkwarden_api_key": "Kunci API Linkwarden",
"form.integration.linkwarden_endpoint": "URL Dasar Linkwarden",
"form.integration.matrix_bot_activate": "Kirim entri baru ke Matrix",
"form.integration.matrix_bot_chat_id": "ID Ruang Matrix",
"form.integration.matrix_bot_password": "Kata Sandi Matrix",
"form.integration.matrix_bot_url": "URL Peladen Matrix",
"form.integration.matrix_bot_user": "Nama Pengguna Matrix",
"form.integration.notion_activate": "Simpan artikel ke Notion",
"form.integration.notion_page_id": "ID Halaman Notion",
"form.integration.notion_token": "Token Rahasia Notion",
"form.integration.ntfy_activate": "Kirim artikel ke ntfy",
"form.integration.ntfy_api_token": "Token API Ntfy (opsional)",
"form.integration.ntfy_icon_url": "URL ikon Ntfy (opsional)",
"form.integration.ntfy_internal_links": "Gunakan tautan internal ketika mengklik (opsional)",
"form.integration.ntfy_password": "Kata sandi Ntfy (opsional)",
"form.integration.ntfy_topic": "Topik Ntfy (yang akan digunakan jika tidak diatur di umpan)",
"form.integration.ntfy_url": "URL Ntfy (opsional, bawaan ke ntfy.sh)",
"form.integration.ntfy_username": "Nama pengguna Ntfy (opsional)",
"form.integration.nunux_keeper_activate": "Simpan artikel ke Nunux Keeper",
"form.integration.nunux_keeper_api_key": "Kunci API Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Titik URL API Nunux Keeper",
"form.integration.omnivore_activate": "Simpan artikel ke Omnivore",
"form.integration.omnivore_api_key": "Kunci API Omnivore",
"form.integration.omnivore_url": "Titik URL API Omnivore",
"form.integration.pinboard_activate": "Simpan artikel ke Pinboard",
"form.integration.pinboard_bookmark": "Tandai markah sebagai belum dibaca",
"form.integration.pinboard_tags": "Tanda di Pinboard",
"form.integration.pinboard_token": "Token API Pinboard",
"form.integration.pushover_activate": "Kirim artikel ke Pushover",
"form.integration.pushover_device": "Perangkat Pushover (opsional)",
"form.integration.pushover_prefix": "Prefiks URL Pushover (opsional)",
"form.integration.pushover_token": "Token API aplikasi Pushover",
"form.integration.pushover_user": "Kunci pengguna Pushover",
"form.integration.raindrop_activate": "Simpan artikel ke Raindrop",
"form.integration.raindrop_collection_id": "ID Koleksi",
"form.integration.raindrop_tags": "Tanda (dipisahkan koma)",
"form.integration.raindrop_token": "Token (Tes)",
"form.integration.readeck_activate": "Simpan artikel ke Readeck",
"form.integration.readeck_api_key": "Kunci API Readeck",
"form.integration.readeck_endpoint": "Titik URL API Readeck",
"form.integration.readeck_labels": "Tagar Readeck",
"form.integration.readeck_only_url": "Kirim hanya URL (alih-alih konten penuh)",
"form.integration.readwise_activate": "Simpan artikel ke Readwise",
"form.integration.readwise_api_key": "Token Akses Readwise",
"form.integration.readwise_api_key_link": "Dapatkan Token Akses Readwise Anda",
"form.integration.rssbridge_activate": "Periksa RSS-Bridge ketika menambahkan langganan",
"form.integration.rssbridge_token": "Token autentikasi RSS-Bridge",
"form.integration.rssbridge_url": "URL peladen RSS-Bridge",
"form.integration.shaarli_activate": "Simpan artikel ke Shaarli",
"form.integration.shaarli_api_secret": "Rahasia API Shaarli",
"form.integration.shaarli_endpoint": "URL Shaarli",
"form.integration.shiori_activate": "Simpan artikel ke Shiori",
"form.integration.shiori_endpoint": "Titik URL API Shiori",
"form.integration.shiori_password": "Kata Sandi Shiori",
"form.integration.shiori_username": "Nama Pengguna Shiori",
"form.integration.slack_activate": "Kirim artikel ke Slack",
"form.integration.slack_webhook_link": "Tautan Webhook Slack",
"form.integration.telegram_bot_activate": "Kirim artikel baru ke percakapan Telegram",
"form.integration.telegram_bot_disable_buttons": "Matikan tombol",
"form.integration.telegram_bot_disable_notification": "Matikan notifikasi",
"form.integration.telegram_bot_disable_web_page_preview": "Matikan tinjauan halaman web",
"form.integration.telegram_bot_token": "Token Bot",
"form.integration.telegram_chat_id": "ID Obrolan",
"form.integration.telegram_topic_id": "ID Topik",
"form.integration.wallabag_activate": "Simpan artikel ke Wallabag",
"form.integration.wallabag_client_id": "ID Klien Wallabag",
"form.integration.wallabag_client_secret": "Rahasia Klien Wallabag",
"form.integration.wallabag_endpoint": "URL Dasar Wallabag",
"form.integration.wallabag_only_url": "Kirim hanya URL (alih-alih konten penuh)",
"form.integration.wallabag_password": "Kata Sandi Wallabag",
"form.integration.wallabag_username": "Nama Pengguna Wallabag",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Aktifkan Webhook",
"form.integration.webhook_secret": "Rahasia Webhook",
"form.integration.webhook_url": "URL Webhook baku",
"form.prefs.fieldset.application_settings": "Pengaturan Aplikasi",
"form.prefs.fieldset.authentication_settings": "Pengaturan Autentikasi",
"form.prefs.fieldset.global_feed_settings": "Pengaturan Umpan Global",
"form.prefs.fieldset.reader_settings": "Pengaturan Pembaca",
"form.prefs.help.external_font_hosts": "Daftar yang dipisah spasi untuk peladen penyedia fonta eksternal yang diperbolehkan. Seperti: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.always_open_external_links": "Baca artikel dengan membuka tautan eksternal",
"form.prefs.label.categories_sorting_order": "Pengurutan Kategori",
"form.prefs.label.cjk_reading_speed": "Kecepatan membaca untuk bahasa Tiongkok, Korea, dan Jepang (karakter per menit)",
"form.prefs.label.custom_css": "Modifikasi CSS",
"form.prefs.label.custom_js": "Modifikasi JavaScript",
"form.prefs.label.default_home_page": "Beranda Baku",
"form.prefs.label.default_reading_speed": "Kecepatan membaca untuk bahasa lain (kata per menit)",
"form.prefs.label.display_mode": "Mode Tampilan Aplikasi Web (perlu pemasangan ulang)",
"form.prefs.label.entries_per_page": "Entri per Halaman",
"form.prefs.label.entry_order": "Pengurutan Kolom Entri",
"form.prefs.label.entry_sorting": "Pengurutan Entri",
"form.prefs.label.entry_swipe": "Aktifkan tindakan geser pada entri di ponsel",
"form.prefs.label.external_font_hosts": "Peladen penyedia fonta eksternal",
"form.prefs.label.gesture_nav": "Isyarat untuk menavigasi antar entri",
"form.prefs.label.keyboard_shortcuts": "Aktifkan pintasan papan tik",
"form.prefs.label.language": "Bahasa",
"form.prefs.label.mark_read_manually": "Tandai entri sebagai telah dibaca secara manual",
"form.prefs.label.mark_read_on_media_completion": "Tandai entri sebagai telah dibaca ketika audio/video sudah 90% didengar/ditonton",
"form.prefs.label.mark_read_on_view": "Secara otomatis menandai entri sebagai telah dibaca saat dilihat",
"form.prefs.label.mark_read_on_view_or_media_completion": "Tandai entri sebagai telah dibaca ketika dilihat. Untuk audio/video, tandai sebagai telah dibaca ketika sudah 90% didengar/ditonton.",
"form.prefs.label.media_playback_rate": "Kecepatan pemutaran audio/video",
"form.prefs.label.open_external_links_in_new_tab": "Buka tautan eksternal di tab baru (menambahkan target=\"_blank\" ke tautan)",
"form.prefs.label.show_reading_time": "Tampilkan perkiraan waktu baca untuk artikel",
"form.prefs.label.theme": "Tema",
"form.prefs.label.timezone": "Zona Waktu",
"form.prefs.select.alphabetical": "Secara alfabet",
"form.prefs.select.browser": "Peramban",
"form.prefs.select.created_time": "Waktu entri dibuat",
"form.prefs.select.fullscreen": "Layar Penuh",
"form.prefs.select.minimal_ui": "Minimal",
"form.prefs.select.none": "Tidak ada",
"form.prefs.select.older_first": "Entri tertua dulu",
"form.prefs.select.publish_time": "Waktu entri dipublikasikan",
"form.prefs.select.recent_first": "Entri terbaru dulu",
"form.prefs.select.standalone": "Tersendiri",
"form.prefs.select.swipe": "Geser",
"form.prefs.select.tap": "Ketuk dua kali",
"form.prefs.select.unread_count": "Jumlah yang belum dibaca",
"form.submit.loading": "Memuat...",
"form.submit.saving": "Menyimpan...",
"form.user.label.admin": "Administrator",
"form.user.label.confirmation": "Konfirmasi Kata Sandi",
"form.user.label.password": "Kata Sandi",
"form.user.label.username": "Nama Pengguna",
"menu.about": "Tentang",
"menu.add_feed": "Tambah langganan",
"menu.add_user": "Tambah pengguna",
"menu.api_keys": "Kunci API",
"menu.categories": "Kategori",
"menu.create_api_key": "Buat kunci API baru",
"menu.create_category": "Buat kategori",
"menu.edit_category": "Sunting",
"menu.edit_feed": "Sunting",
"menu.export": "Ekspor",
"menu.feed_entries": "Entri",
"menu.feeds": "Umpan",
"menu.flush_history": "Hapus riwayat",
"menu.history": "Riwayat",
"menu.home_page": "Beranda",
"menu.import": "Impor",
"menu.integrations": "Integrasi",
"menu.logout": "Keluar",
"menu.mark_all_as_read": "Tandai semua sebagai telah dibaca",
"menu.mark_page_as_read": "Tandai halaman ini sebagai telah dibaca",
"menu.preferences": "Preferensi",
"menu.refresh_all_feeds": "Muat ulang semua umpan di latar belakang",
"menu.refresh_feed": "Muat ulang",
"menu.search": "Cari",
"menu.sessions": "Sesi",
"menu.settings": "Pengaturan",
"menu.shared_entries": "Entri yang Dibagikan",
"menu.show_all_entries": "Tampilkan semua entri",
"menu.show_only_starred_entries": "Tampilkan hanya entri yang dimarkahkan",
"menu.show_only_unread_entries": "Tampilkan hanya entri yang belum dibaca",
"menu.starred": "Markah",
"menu.title": "Menu",
"menu.unread": "Belum Dibaca",
"menu.users": "Pengguna",
"page.about.author": "Pengembang:",
"page.about.build_date": "Tanggal Penyusunan:",
"page.about.credits": "Pengembang",
"page.about.db_usage": "Database size:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "Pengaturan Konfigurasi Global",
"page.about.go_version": "Versi Go:",
"page.about.license": "Lisensi:",
"page.about.postgres_version": "Versi Postgres:",
"page.about.title": "Tentang",
"page.about.version": "Versi:",
"page.add_feed.choose_feed": "Pilih Umpan",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Pilihan Tingkat Lanjut",
"page.add_feed.no_category": "Tidak ada kategori. Anda harus paling tidak memiliki satu kategori.",
"page.add_feed.submit": "Cari langganan",
"page.add_feed.title": "Langganan Baru",
"page.api_keys.never_used": "Tidak Pernah Digunakan",
"page.api_keys.table.actions": "Tindakan",
"page.api_keys.table.created_at": "Tanggal Pembuatan",
"page.api_keys.table.description": "Deskripsi",
"page.api_keys.table.last_used_at": "Terakhir Digunakan",
"page.api_keys.table.token": "Token",
"page.api_keys.title": "Kunci API",
"page.categories.entries": "Artikel",
"page.categories.feed_count": [
"Ada %d umpan."
],
"page.categories.feeds": "Langganan",
"page.categories.no_feed": "Tidak ada umpan.",
"page.categories.title": "Kategori",
"page.categories_count": [
"%d kategori"
],
"page.category_label": "Category: %s",
"page.edit_category.title": "Sunting Kategori: %s",
"page.edit_feed.etag_header": "Tajuk ETag:",
"page.edit_feed.last_check": "Terakhir diperiksa:",
"page.edit_feed.last_modified_header": "Tajuk LastModified:",
"page.edit_feed.last_parsing_error": "Galat Penguraian Terakhir",
"page.edit_feed.no_header": "Tidak Ada",
"page.edit_feed.title": "Sunting Umpan: %s",
"page.edit_user.title": "Sunting Pengguna: %s",
"page.entry.attachments": "Lampiran",
"page.feeds.error_count": [
"%d galat"
],
"page.feeds.last_check": "Terakhir diperiksa:",
"page.feeds.next_check": "Akan diperiksa kembali:",
"page.feeds.read_counter": "Jumlah entri yang telah dibaca",
"page.feeds.title": "Umpan",
"page.footer.elevator": "Back to top",
"page.history.title": "Riwayat",
"page.import.title": "Impor",
"page.integration.bookmarklet": "Bookmarklet",
"page.integration.bookmarklet.help": "Tautan spesial ini memperbolehkan Anda untuk berlangganan ke situs langsung dengan menggunakan markah di peramban web Anda.",
"page.integration.bookmarklet.instructions": "Seret dan tempatkan tautan ini ke markah Anda.",
"page.integration.bookmarklet.name": "Tambahkan ke Miniflux",
"page.integration.miniflux_api": "API Miniflux",
"page.integration.miniflux_api_endpoint": "Titik URL API",
"page.integration.miniflux_api_password": "Kata Sandi",
"page.integration.miniflux_api_password_value": "Kata sandi akun Anda",
"page.integration.miniflux_api_username": "Nama Pengguna",
"page.integrations.title": "Integrasi",
"page.keyboard_shortcuts.close_modal": "Tutup bilah modal",
"page.keyboard_shortcuts.download_content": "Unduh konten asli",
"page.keyboard_shortcuts.go_to_bottom_item": "Pergi ke item paling bawah",
"page.keyboard_shortcuts.go_to_categories": "Ke kategori",
"page.keyboard_shortcuts.go_to_feed": "Ke umpan",
"page.keyboard_shortcuts.go_to_feeds": "Ke umpan",
"page.keyboard_shortcuts.go_to_history": "Ke riwayat",
"page.keyboard_shortcuts.go_to_next_item": "Ke entri berikutnya",
"page.keyboard_shortcuts.go_to_next_page": "Ke halaman berikutnya",
"page.keyboard_shortcuts.go_to_previous_item": "Ke entri sebelumnya",
"page.keyboard_shortcuts.go_to_previous_page": "Ke halaman sebelumnya",
"page.keyboard_shortcuts.go_to_search": "Atur fokus ke pencaarian",
"page.keyboard_shortcuts.go_to_settings": "Ke pengaturan",
"page.keyboard_shortcuts.go_to_starred": "Ke markah",
"page.keyboard_shortcuts.go_to_top_item": "Pergi ke item teratas",
"page.keyboard_shortcuts.go_to_unread": "Ke bagian yang belum dibaca",
"page.keyboard_shortcuts.mark_page_as_read": "Tandai halaman saat ini sebagai telah dibaca",
"page.keyboard_shortcuts.open_comments": "Buka tautan komentar",
"page.keyboard_shortcuts.open_comments_same_window": "Buka tautan komentar di bilah saat ini",
"page.keyboard_shortcuts.open_item": "Buka entri yang dipilih",
"page.keyboard_shortcuts.open_original": "Buka tautan asli",
"page.keyboard_shortcuts.open_original_same_window": "Buka tautan asli di bilah saat ini",
"page.keyboard_shortcuts.refresh_all_feeds": "Muat ulang semua umpan di latar belakang",
"page.keyboard_shortcuts.remove_feed": "Hapus umpan ini",
"page.keyboard_shortcuts.save_article": "Simpan Artikel",
"page.keyboard_shortcuts.scroll_item_to_top": "Gulir ke atas",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Tampilkan pintasan papan tik",
"page.keyboard_shortcuts.subtitle.actions": "Tindakan",
"page.keyboard_shortcuts.subtitle.items": "Navigasi Entri",
"page.keyboard_shortcuts.subtitle.pages": "Navigasi Halaman",
"page.keyboard_shortcuts.subtitle.sections": "Navigasi Bagian",
"page.keyboard_shortcuts.title": "Pintasan Papan Tik",
"page.keyboard_shortcuts.toggle_star_status": "Ubah status markah",
"page.keyboard_shortcuts.toggle_entry_attachments": "Buka/tutup lampiran entri",
"page.keyboard_shortcuts.toggle_read_status_next": "Ubah status baca, fokus ke selanjutnya",
"page.keyboard_shortcuts.toggle_read_status_prev": "Ubah status baca, fokus ke sebelumnya",
"page.login.google_signin": "Masuk menggunakan Google",
"page.login.oidc_signin": "Masuk menggunakan %s",
"page.login.title": "Masuk",
"page.login.webauthn_login": "Masuk menggunakan passkey",
"page.login.webauthn_login.error": "Tidak dapat masuk menggunakan passkey",
"page.login.webauthn_login.help": "Mohon untuk memasukkan nama pengguna Anda jika Anda menggunakan kunci keamanan. Tidak diperlukan jika anda menggunakan Passkey (kredensial dapat ditemukan).",
"page.new_api_key.title": "Kunci API Baru",
"page.new_category.title": "Kategori Baru",
"page.new_user.title": "Pengguna Baru",
"page.offline.message": "Anda sedang luring",
"page.offline.refresh_page": "Coba untuk memuat ulang halaman ini",
"page.offline.title": "Mode Luring",
"page.read_entry_count": [
"%d entri dibaca"
],
"page.search.title": "Hasil Pencarian",
"page.sessions.table.actions": "Tindakan",
"page.sessions.table.current_session": "Sesi Saat Ini",
"page.sessions.table.date": "Tanggal",
"page.sessions.table.ip": "Alamat IP",
"page.sessions.table.user_agent": "User Agent",
"page.sessions.title": "Sesi",
"page.settings.link_google_account": "Tautkan akun Google saya",
"page.settings.link_oidc_account": "Tautkan akun %s saya",
"page.settings.title": "Pengaturan",
"page.settings.unlink_google_account": "Putuskan akun Google saya",
"page.settings.unlink_oidc_account": "Putuskan akun %s saya",
"page.settings.webauthn.actions": "Tindakan",
"page.settings.webauthn.added_on": "Ditambahkan Pada",
"page.settings.webauthn.delete": [
"Hapus %d passkey"
],
"page.settings.webauthn.last_seen_on": "Terakhir Digunakan",
"page.settings.webauthn.passkey_name": "Nama Passkey",
"page.settings.webauthn.passkeys": "Passkey",
"page.settings.webauthn.register": "Daftar passkey",
"page.settings.webauthn.register.error": "Tidak dapat mendaftarkan passkey",
"page.shared_entries.title": "Entri yang Dibagikan",
"page.shared_entries_count": [
"%d entri yang dibagikan"
],
"page.starred.title": "Markah",
"page.starred_entry_count": [
"%d entri dimarkahi"
],
"page.total_entry_count": [
"%d entri secara total"
],
"page.unread.title": "Belum Dibaca",
"page.unread_entry_count": [
"%d entri belum dibaca"
],
"page.users.actions": "Tindakan",
"page.users.admin.no": "Tidak",
"page.users.admin.yes": "Ya",
"page.users.is_admin": "Administrator",
"page.users.last_login": "Terakhir Masuk",
"page.users.never_logged": "Tidak Pernah",
"page.users.title": "Pengguna",
"page.users.username": "Nama Pengguna",
"page.webauthn_rename.title": "Ubah Nama Passkey",
"pagination.first": "Pertama",
"pagination.last": "Terakhir",
"pagination.next": "Berikutnya",
"pagination.previous": "Sebelumnya",
"search.label": "Cari",
"search.placeholder": "Cari...",
"search.submit": "Cari",
"skip_to_content": "Langsung ke konten",
"time_elapsed.days": [
"%d hari yang lalu"
],
"time_elapsed.hours": [
"%d jam yang lalu"
],
"time_elapsed.minutes": [
"%d menit yang lalu"
],
"time_elapsed.months": [
"%d bulan yang lalu"
],
"time_elapsed.not_yet": "belum",
"time_elapsed.now": "baru saja",
"time_elapsed.weeks": [
"%d pekan yang lalu"
],
"time_elapsed.years": [
"%d tahun yang lalu"
],
"time_elapsed.yesterday": "kemarin",
"tooltip.keyboard_shortcuts": "Pintasan Papan Tik: %s",
"tooltip.logged_user": "Masuk sebagai %s"
} v2-2.2.13/internal/locale/translations/it_IT.json 0000664 0000000 0000000 00000114140 15062123773 0021634 0 ustar 00root root 0000000 0000000 {
"action.cancel": "cancella",
"action.download": "Scarica",
"action.edit": "Modifica",
"action.home_screen": "Aggiungere alla schermata Home",
"action.import": "Importa",
"action.login": "Accedi",
"action.or": "o",
"action.remove": "Elimina",
"action.remove_feed": "Elimina questo feed",
"action.save": "Salva",
"action.subscribe": "Abbonati",
"action.update": "Aggiorna",
"alert.account_linked": "Il tuo account esterno ora è collegato!",
"alert.account_unlinked": "Il tuo account esterno ora è scollegato!",
"alert.background_feed_refresh": "Tutti i feed vengono aggiornati in background. Puoi continuare a usare Miniflux mentre questo processo è in esecuzione.",
"alert.feed_error": "Sembra ci sia un problema con questo feed",
"alert.no_starred": "Nessun preferito disponibile.",
"alert.no_category": "Nessuna categoria disponibile.",
"alert.no_category_entry": "Questa categoria non contiene alcun articolo.",
"alert.no_feed": "Nessun feed disponibile.",
"alert.no_feed_entry": "Questo feed non contiene alcun articolo.",
"alert.no_feed_in_category": "Non esiste un abbonamento per questa categoria.",
"alert.no_history": "La tua cronologia al momento è vuota.",
"alert.no_search_result": "La ricerca non ha prodotto risultati.",
"alert.no_shared_entry": "Non ci sono voci condivise.",
"alert.no_tag_entry": "Non ci sono voci corrispondenti a questo tag.",
"alert.no_unread_entry": "Nessun articolo da leggere.",
"alert.no_user": "Tu sei l'unico utente.",
"alert.prefs_saved": "Preferenze salvate!",
"alert.too_many_feeds_refresh": [
"Hai richiesto troppi aggiornamenti dei feed. Attendi %d minuto prima di riprovare.",
"Hai richiesto troppi aggiornamenti dei feed. Attendi %d minuti prima di riprovare."
],
"confirm.loading": "In corso...",
"confirm.no": "no",
"confirm.question": "Sei sicuro?",
"confirm.question.refresh": "Vuoi forzare l'aggiornamento?",
"confirm.yes": "sì",
"enclosure_media_controls.seek": "Sposta:",
"enclosure_media_controls.seek.title": "Sposta di %s secondi",
"enclosure_media_controls.speed": "Velocità:",
"enclosure_media_controls.speed.faster": "Più veloce",
"enclosure_media_controls.speed.faster.title": "Più veloce di %sx",
"enclosure_media_controls.speed.reset": "Reimposta",
"enclosure_media_controls.speed.reset.title": "Reimposta velocità a 1x",
"enclosure_media_controls.speed.slower": "Più lento",
"enclosure_media_controls.speed.slower.title": "Più lento di %sx",
"entry.starred.toast.off": "Non preferito",
"entry.starred.toast.on": "Preferito",
"entry.starred.toggle.off": "Rimuovi dai preferiti",
"entry.starred.toggle.on": "Aggiungi ai preferiti",
"entry.comments.label": "Commenti",
"entry.comments.title": "Mostra i commenti",
"entry.estimated_reading_time": [
"%d minuto di lettura",
"%d minuti di lettura"
],
"entry.external_link.label": "Link esterno",
"entry.save.completed": "Fatto!",
"entry.save.label": "Salva",
"entry.save.title": "Salva questo articolo",
"entry.save.toast.completed": "Articolo salvato",
"entry.scraper.completed": "Fatto!",
"entry.scraper.label": "Scarica",
"entry.scraper.title": "Scarica il contenuto integrale",
"entry.share.label": "Condividi",
"entry.share.title": "Condividi questo articolo",
"entry.shared_entry.label": "Condivisione",
"entry.shared_entry.title": "Apri il link pubblico",
"entry.state.loading": "Caricamento in corso...",
"entry.state.saving": "Salvataggio in corso...",
"entry.status.mark_as_read": "Segna come letto",
"entry.status.mark_as_unread": "Segna come non letto",
"entry.status.title": "Cambia lo stato dell'articolo",
"entry.status.toast.read": "Contrassegnato come letto",
"entry.status.toast.unread": "Contrassegnato come non letto",
"entry.tags.label": "Tag:",
"entry.tags.more_tags_label": [
"Mostra %d altro tag",
"Mostra %d altri tag"
],
"entry.unshare.label": "Rimuovi condivisione",
"error.api_key_already_exists": "Questa chiave API esiste già.",
"error.bad_credentials": "Nome utente o password non validi.",
"error.category_already_exists": "Questa categoria esiste già.",
"error.category_not_found": "Questa categoria non esiste o non appartiene a questo utente.",
"error.database_error": "Errore del database: %v.",
"error.different_passwords": "Le password non coincidono.",
"error.duplicate_fever_username": "Esiste già un account Fever con lo stesso nome utente!",
"error.duplicate_googlereader_username": "Esiste già un account Google Reader con lo stesso nome utente!",
"error.duplicate_linked_account": "Esiste già un account configurato per questo servizio!",
"error.duplicated_feed": "Questo feed esiste già.",
"error.empty_file": "Questo file è vuoto.",
"error.entries_per_page_invalid": "Il numero di articoli per pagina non è valido.",
"error.feed_already_exists": "Questo feed esiste già.",
"error.feed_category_not_found": "Questa categoria non esiste o non appartiene a questo utente.",
"error.feed_format_not_detected": "Impossibile rilevare il formato del feed: %v.",
"error.feed_invalid_blocklist_rule": "La regola dell'elenco di blocco non è valida.",
"error.feed_invalid_keeplist_rule": "La regola dell'elenco di conservazione non è valida.",
"error.feed_mandatory_fields": "L'URL e la categoria sono obbligatori.",
"error.feed_not_found": "Questo feed non esiste o non appartiene a questo utente.",
"error.feed_title_not_empty": "Il titolo del feed non può essere vuoto.",
"error.feed_url_not_empty": "L'URL del feed non può essere vuoto.",
"error.fields_mandatory": "Tutti i campi sono obbligatori.",
"error.http_bad_gateway": "Il sito web non è disponibile al momento a causa di un errore di gateway. Il problema non è dal lato di Miniflux. Per favore, riprova più tardi.",
"error.http_body_read": "Impossibile leggere il corpo HTTP: %v.",
"error.http_client_error": "Errore del client HTTP: %v.",
"error.http_empty_response": "La risposta HTTP è vuota. Forse questo sito web utilizza un meccanismo di protezione dai bot?",
"error.http_empty_response_body": "Il corpo della risposta HTTP è vuoto.",
"error.http_forbidden": "L'accesso a questo sito web è vietato. Forse questo sito web ha un meccanismo di protezione dai bot?",
"error.http_gateway_timeout": "The website is not available at the moment due to a gateway timeout error. The problem is not on Miniflux side. Please, try again later.",
"error.http_internal_server_error": "The website is not available at the moment due to a server error. The problem is not on Miniflux side. Please, try again later.",
"error.http_not_authorized": "Access to this website is not authorized. It could be a bad username or password.",
"error.http_resource_not_found": "The requested resource is not found. Please, verify the URL.",
"error.http_response_too_large": "The HTTP response is too large. You could increase the HTTP response size limit in the global settings (requires a server restart).",
"error.http_service_unavailable": "The website is not available at the moment due to an internal server error. The problem is not on Miniflux side. Please, try again later.",
"error.http_too_many_requests": "Miniflux generated too many requests to this website. Please, try again later or change the application configuration.",
"error.http_unexpected_status_code": "The website is not available at the moment due to an unexpected HTTP status code: %d. The problem is not on Miniflux side. Please, try again later.",
"error.invalid_categories_sorting_order": "L'ordinamento delle categorie non è valido.",
"error.invalid_default_home_page": "Pagina iniziale predefinita non valida!",
"error.invalid_display_mode": "Modalità di visualizzazione web app non valida.",
"error.invalid_entry_direction": "Ordinamento non valido.",
"error.invalid_entry_order": "L'ordinamento delle voci non è valido.",
"error.invalid_feed_proxy_url": "URL del proxy non valido.",
"error.invalid_feed_url": "URL del feed non valido.",
"error.invalid_gesture_nav": "Navigazione gestuale non valida.",
"error.invalid_language": "Lingua non valida.",
"error.invalid_site_url": "URL del sito non valido.",
"error.invalid_theme": "Tema non valido.",
"error.invalid_timezone": "Fuso orario non valido.",
"error.network_operation": "Miniflux non riesce a raggiungere questo sito web a causa di un errore di rete: %v.",
"error.network_timeout": "Questo sito web è troppo lento e la richiesta è scaduta: %v",
"error.password_min_length": "La password deve contenere almeno 6 caratteri.",
"error.proxy_url_not_empty": "L'URL del proxy non può essere vuoto.",
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
"error.settings_invalid_domain_list": "Invalid domain list. Please provide a space separated list of domains.",
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
"error.settings_mandatory_fields": "Il nome utente, il tema, la lingua ed il fuso orario sono campi obbligatori.",
"error.settings_media_playback_rate_range": "La velocità di riproduzione non rientra nell'intervallo",
"error.settings_reading_speed_is_positive": "Le velocità di lettura devono essere numeri interi positivi.",
"error.site_url_not_empty": "L'URL del sito non può essere vuoto.",
"error.subscription_not_found": "Non ho trovato nessun feed.",
"error.title_required": "Il titolo è obbligatorio.",
"error.tls_error": "TLS error: %q. You could disable TLS verification in the feed settings if you would like.",
"error.unable_to_create_api_key": "Impossibile creare questa chiave API.",
"error.unable_to_create_category": "Non sono riuscito ad aggiungere questa categoria.",
"error.unable_to_create_user": "Non sono riuscito ad aggiungere questo user.",
"error.unable_to_detect_rssbridge": "Impossibile rilevare il feed usando RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Impossibile analizzare questo feed: %v.",
"error.unable_to_update_category": "Non sono riuscito ad aggiornare questa categoria.",
"error.unable_to_update_feed": "Non sono riuscito ad aggiornare questo feed.",
"error.unable_to_update_user": "Non sono riuscito ad aggiornare questo utente.",
"error.unlink_account_without_password": "Devi scegliere una password altrimenti la prossima volta non riuscirai ad accedere.",
"error.user_already_exists": "Questo utente esiste già.",
"error.user_mandatory_fields": "Il nome utente è obbligatorio.",
"error.linktaco_missing_required_fields": "LinkTaco API Token e Organization Slug sono richiesti",
"form.api_key.label.description": "Etichetta chiave API",
"form.category.hide_globally": "Nascondere le voci nella lista globale dei non letti",
"form.category.label.title": "Titolo",
"form.feed.fieldset.general": "General",
"form.feed.fieldset.integration": "Third-Party Services",
"form.feed.fieldset.network_settings": "Network Settings",
"form.feed.fieldset.rules": "Rules",
"form.feed.label.allow_self_signed_certificates": "Consenti certificati autofirmati o non validi",
"form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
"form.feed.label.block_filter_entry_rules": "Regole di Blocco delle Voci",
"form.feed.label.blocklist_rules": "Filtri di Blocco Basati su Regex",
"form.feed.label.category": "Categoria",
"form.feed.label.cookie": "Installare i cookies",
"form.feed.label.crawler": "Scarica il contenuto integrale",
"form.feed.label.description": "Descrizione",
"form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
"form.feed.label.disabled": "Non aggiornare questo feed",
"form.feed.label.feed_password": "Password del feed",
"form.feed.label.feed_url": "URL del feed",
"form.feed.label.feed_username": "Nome utente del feed",
"form.feed.label.fetch_via_proxy": "Usa il proxy configurato a livello di applicazione",
"form.feed.label.hide_globally": "Nascondere le voci nella lista globale dei non letti",
"form.feed.label.ignore_http_cache": "Ignora cache HTTP",
"form.feed.label.keep_filter_entry_rules": "Regole di Permesso delle Voci",
"form.feed.label.keeplist_rules": "Filtri di Mantenimento Basati su Regex",
"form.feed.label.no_media_player": "No media player (audio/video)",
"form.feed.label.ntfy_activate": "Push entries to ntfy",
"form.feed.label.ntfy_default_priority": "Ntfy default priority",
"form.feed.label.ntfy_high_priority": "Ntfy high priority",
"form.feed.label.ntfy_low_priority": "Ntfy low priority",
"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.proxy_url": "Proxy URL",
"form.feed.label.pushover_activate": "Push entries to pushover.net",
"form.feed.label.pushover_default_priority": "Pushover default priority",
"form.feed.label.pushover_high_priority": "Pushover high priority",
"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.rewrite_rules": "Regole di Riscrittura del Contenuto",
"form.feed.label.scraper_rules": "Regole di estrazione del contenuto",
"form.feed.label.site_url": "URL del sito",
"form.feed.label.title": "Titolo",
"form.feed.label.urlrewrite_rules": "Regole di riscrittura URL",
"form.feed.label.user_agent": "Usa user agent personalizzato",
"form.feed.label.webhook_url": "Override webhook url",
"form.import.label.file": "File OPML",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Push entries to Apprise",
"form.integration.apprise_services_url": "Comma separated list of Apprise service URLs",
"form.integration.apprise_url": "Apprise API URL",
"form.integration.betula_activate": "Save entries to Betula",
"form.integration.betula_token": "Betula Token",
"form.integration.betula_url": "Betula server URL",
"form.integration.cubox_activate": "Save entries to Cubox",
"form.integration.cubox_api_link": "Cubox API link",
"form.integration.discord_activate": "Push entries to Discord",
"form.integration.discord_webhook_link": "Discord Webhook link",
"form.integration.espial_activate": "Salva gli articoli su Espial",
"form.integration.espial_api_key": "API key dell'account Espial",
"form.integration.espial_endpoint": "Endpoint dell'API di Espial",
"form.integration.espial_tags": "Tag di Espial",
"form.integration.fever_activate": "Abilita l'API di Fever",
"form.integration.fever_endpoint": "Endpoint dell'API di Fever:",
"form.integration.fever_password": "Password dell'account Fever",
"form.integration.fever_username": "Nome utente dell'account Fever",
"form.integration.googlereader_activate": "Abilita l'API di Google Reader",
"form.integration.googlereader_endpoint": "Endpoint dell'API di Google Reader:",
"form.integration.googlereader_password": "Password dell'account Google Reader",
"form.integration.googlereader_username": "Nome utente dell'account Google Reader",
"form.integration.instapaper_activate": "Salva gli articoli su Instapaper",
"form.integration.instapaper_password": "Password dell'account Instapaper",
"form.integration.instapaper_username": "Nome utente dell'account Instapaper",
"form.integration.karakeep_activate": "Salva gli articoli su Karakeep",
"form.integration.karakeep_api_key": "API key dell'account Karakeep",
"form.integration.karakeep_url": "Endpoint dell'API di Karakeep",
"form.integration.linkace_activate": "Salva gli articoli su LinkAce",
"form.integration.linkace_api_key": "API key dell'account LinkAce",
"form.integration.linkace_check_disabled": "Disabilita i controlli",
"form.integration.linkace_endpoint": "Endpoint dell'API di LinkAce",
"form.integration.linkace_is_private": "Rendi i link privati",
"form.integration.linkace_tags": "LinkAce Tags",
"form.integration.linkding_activate": "Salva gli articoli su Linkding",
"form.integration.linkding_api_key": "API key dell'account Linkding",
"form.integration.linkding_bookmark": "Segna i preferiti come non letti",
"form.integration.linkding_endpoint": "Endpoint dell'API di Linkding",
"form.integration.linkding_tags": "Linkding Tags",
"form.integration.linktaco_activate": "Salva le voci in LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Ottieni il tuo token di accesso personale su",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Tag (massimo 10, separati da virgola)",
"form.integration.linktaco_tags_hint": "Massimo 10 tag, separati da virgola",
"form.integration.linktaco_visibility": "Visibilità",
"form.integration.linktaco_visibility_public": "Pubblico",
"form.integration.linktaco_visibility_private": "Privato",
"form.integration.linktaco_visibility_hint": "La visibilità PRIVATA richiede un account LinkTaco a pagamento",
"form.integration.linkwarden_activate": "Salva gli articoli su Linkwarden",
"form.integration.linkwarden_api_key": "API key dell'account Linkwarden",
"form.integration.linkwarden_endpoint": "URL di base di Linkwarden",
"form.integration.matrix_bot_activate": "Trasferimento di nuovi articoli a Matrix",
"form.integration.matrix_bot_chat_id": "ID della stanza Matrix",
"form.integration.matrix_bot_password": "Password per l'utente Matrix",
"form.integration.matrix_bot_url": "URL del server Matrix",
"form.integration.matrix_bot_user": "Nome utente per Matrix",
"form.integration.notion_activate": "Save entries to Notion",
"form.integration.notion_page_id": "Notion Page ID",
"form.integration.notion_token": "Notion Secret Token",
"form.integration.ntfy_activate": "Push entries to ntfy",
"form.integration.ntfy_api_token": "Ntfy API Token (optional)",
"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 (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",
"form.integration.nunux_keeper_api_key": "API key dell'account Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Endpoint dell'API di Nunux Keeper",
"form.integration.omnivore_activate": "Salva gli articoli su Omnivore",
"form.integration.omnivore_api_key": "API key dell'account Omnivore",
"form.integration.omnivore_url": "Endpoint dell'API di Omnivore",
"form.integration.pinboard_activate": "Salva gli articoli su Pinboard",
"form.integration.pinboard_bookmark": "Segna i preferiti come non letti",
"form.integration.pinboard_tags": "Tag di Pinboard",
"form.integration.pinboard_token": "Token dell'API di Pinboard",
"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.raindrop_activate": "Save entries to Raindrop",
"form.integration.raindrop_collection_id": "Collection ID",
"form.integration.raindrop_tags": "Tags (comma-separated)",
"form.integration.raindrop_token": "(Test) Token",
"form.integration.readeck_activate": "Salva gli articoli su Readeck",
"form.integration.readeck_api_key": "API key dell'account Readeck",
"form.integration.readeck_endpoint": "Endpoint dell'API di Readeck",
"form.integration.readeck_labels": "Readeck Labels",
"form.integration.readeck_only_url": "Invia solo URL (invece del contenuto completo)",
"form.integration.readwise_activate": "Save entries to Readwise Reader",
"form.integration.readwise_api_key": "Readwise Reader Access Token",
"form.integration.readwise_api_key_link": "Get your Readwise Access Token",
"form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "RSS-Bridge server URL",
"form.integration.shaarli_activate": "Save articles to Shaarli",
"form.integration.shaarli_api_secret": "Shaarli API Secret",
"form.integration.shaarli_endpoint": "Shaarli URL",
"form.integration.shiori_activate": "Salva gli articoli su Shiori",
"form.integration.shiori_endpoint": "Endpoint dell'API di Shiori",
"form.integration.shiori_password": "Password dell'account Shiori",
"form.integration.shiori_username": "Nome utente dell'account Shiori",
"form.integration.slack_activate": "Push entries to Slack",
"form.integration.slack_webhook_link": "Slack Webhook link",
"form.integration.telegram_bot_activate": "Invia nuovi articoli alla chat di Telegram",
"form.integration.telegram_bot_disable_buttons": "Disable buttons",
"form.integration.telegram_bot_disable_notification": "Disable notification",
"form.integration.telegram_bot_disable_web_page_preview": "Disable web page preview",
"form.integration.telegram_bot_token": "Token bot",
"form.integration.telegram_chat_id": "ID chat",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "Salva gli articoli su Wallabag",
"form.integration.wallabag_client_id": "Client ID dell'account Wallabag",
"form.integration.wallabag_client_secret": "Client secret dell'account Wallabag",
"form.integration.wallabag_endpoint": "URL di base di Wallabagg",
"form.integration.wallabag_only_url": "Invia solo URL (invece del contenuto completo)",
"form.integration.wallabag_password": "Password dell'account Wallabag",
"form.integration.wallabag_username": "Nome utente dell'account Wallabag",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Enable Webhooks",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Default Webhook URL",
"form.prefs.fieldset.application_settings": "Application Settings",
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
"form.prefs.fieldset.reader_settings": "Reader Settings",
"form.prefs.help.external_font_hosts": "Space separated list of external font hosts to allow. For example: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.always_open_external_links": "Read articles by opening external links",
"form.prefs.label.categories_sorting_order": "Ordinamento delle categorie",
"form.prefs.label.cjk_reading_speed": "Velocità di lettura per cinese, coreano e giapponese (caratteri al minuto)",
"form.prefs.label.custom_css": "CSS personalizzati",
"form.prefs.label.custom_js": "JavaScript personalizzati",
"form.prefs.label.default_home_page": "Pagina iniziale predefinita",
"form.prefs.label.default_reading_speed": "Velocità di lettura di altre lingue (parole al minuto)",
"form.prefs.label.display_mode": "Modalità di visualizzazione dell'app Web progressiva (PWA).",
"form.prefs.label.entries_per_page": "Articoli per pagina",
"form.prefs.label.entry_order": "Colonna di ordinamento delle voci",
"form.prefs.label.entry_sorting": "Ordinamento articoli",
"form.prefs.label.entry_swipe": "Abilita lo scorrimento della voce sui touch screen",
"form.prefs.label.external_font_hosts": "External font hosts",
"form.prefs.label.gesture_nav": "Gesto per navigare tra le voci",
"form.prefs.label.keyboard_shortcuts": "Abilita le scorciatoie da tastiera",
"form.prefs.label.language": "Lingua",
"form.prefs.label.mark_read_manually": "Mark entries as read manually",
"form.prefs.label.mark_read_on_media_completion": "Only mark as read when audio/video playback reaches 90%% completion",
"form.prefs.label.mark_read_on_view": "Contrassegna automaticamente le voci come lette quando visualizzate",
"form.prefs.label.mark_read_on_view_or_media_completion": "Mark entries as read when viewed. For audio/video, mark as read at 90%% completion",
"form.prefs.label.media_playback_rate": "Velocità di riproduzione dell'audio/video",
"form.prefs.label.open_external_links_in_new_tab": "Apri i link esterni in una nuova scheda (aggiunge target=\"_blank\" ai link)",
"form.prefs.label.show_reading_time": "Mostra il tempo di lettura stimato per gli articoli",
"form.prefs.label.theme": "Tema",
"form.prefs.label.timezone": "Fuso orario",
"form.prefs.select.alphabetical": "In ordine alfabetico",
"form.prefs.select.browser": "Browser",
"form.prefs.select.created_time": "Tempo di creazione dell'entrata",
"form.prefs.select.fullscreen": "Schermo intero",
"form.prefs.select.minimal_ui": "Minimale",
"form.prefs.select.none": "Nessuno",
"form.prefs.select.older_first": "Prima i più vecchi",
"form.prefs.select.publish_time": "Ora di pubblicazione dell'entrata",
"form.prefs.select.recent_first": "Prima i più recenti",
"form.prefs.select.standalone": "Autonoma",
"form.prefs.select.swipe": "Scorri",
"form.prefs.select.tap": "Tocca due volte",
"form.prefs.select.unread_count": "Conteggio dei non letti",
"form.submit.loading": "Caricamento in corso...",
"form.submit.saving": "Salvataggio in corso...",
"form.user.label.admin": "Amministratore",
"form.user.label.confirmation": "Conferma password",
"form.user.label.password": "Password",
"form.user.label.username": "Nome utente",
"menu.about": "Informazioni",
"menu.add_feed": "Aggiungi feed",
"menu.add_user": "Aggiungi utente",
"menu.api_keys": "Chiavi API",
"menu.categories": "Categorie",
"menu.create_api_key": "Crea una nuova chiave API",
"menu.create_category": "Aggiungi una categoria",
"menu.edit_category": "Modifica",
"menu.edit_feed": "Modifica",
"menu.export": "Esporta",
"menu.feed_entries": "Articoli",
"menu.feeds": "Feed",
"menu.flush_history": "Svuota la cronologia",
"menu.history": "Cronologia",
"menu.home_page": "Home page",
"menu.import": "Importa",
"menu.integrations": "Integrazioni",
"menu.logout": "Esci",
"menu.mark_all_as_read": "Segna tutti gli articoli come letti",
"menu.mark_page_as_read": "Segna questa pagina come letta",
"menu.preferences": "Preferenze",
"menu.refresh_all_feeds": "Aggiorna tutti i feed in background",
"menu.refresh_feed": "Aggiorna",
"menu.search": "Cerca",
"menu.sessions": "Sessioni",
"menu.settings": "Impostazioni",
"menu.shared_entries": "Voci condivise",
"menu.show_all_entries": "Mostra tutte le voci",
"menu.show_only_starred_entries": "Mostra solo voci preferiti",
"menu.show_only_unread_entries": "Mostra solo voci non lette",
"menu.starred": "Preferiti",
"menu.title": "Menu",
"menu.unread": "Da leggere",
"menu.users": "Utenti",
"page.about.author": "Autore:",
"page.about.build_date": "Data della build:",
"page.about.credits": "Crediti",
"page.about.db_usage": "Database size:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "Opzioni di configurazione globali",
"page.about.go_version": "Go versione:",
"page.about.license": "Licenza:",
"page.about.postgres_version": "Postgres versione:",
"page.about.title": "Informazioni",
"page.about.version": "Versione:",
"page.add_feed.choose_feed": "Scegli un feed",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Opzioni avanzate",
"page.add_feed.no_category": "Nessuna categoria selezionata. Devi scegliere almeno una categoria.",
"page.add_feed.submit": "Abbonati al feed",
"page.add_feed.title": "Nuovo feed",
"page.api_keys.never_used": "Mai usato",
"page.api_keys.table.actions": "Azioni",
"page.api_keys.table.created_at": "Data di creazione",
"page.api_keys.table.description": "Descrizione",
"page.api_keys.table.last_used_at": "Ultimo uso",
"page.api_keys.table.token": "Gettone",
"page.api_keys.title": "Chiavi API",
"page.categories.entries": "Articoli",
"page.categories.feed_count": [
"C'è %d feed.",
"Ci sono %d feed."
],
"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:",
"page.edit_feed.last_check": "Ultimo controllo:",
"page.edit_feed.last_modified_header": "Header LastModified:",
"page.edit_feed.last_parsing_error": "Ultimo errore di parsing",
"page.edit_feed.no_header": "Nessun header",
"page.edit_feed.title": "Modifica feed: %s",
"page.edit_user.title": "Modifica utente: %s",
"page.entry.attachments": "Allegati",
"page.feeds.error_count": [
"%d errore",
"%d errori"
],
"page.feeds.last_check": "Ultimo controllo:",
"page.feeds.next_check": "Next check:",
"page.feeds.read_counter": "Numero di voci lette",
"page.feeds.title": "Feed",
"page.footer.elevator": "Back to top",
"page.history.title": "Cronologia",
"page.import.title": "Importa",
"page.integration.bookmarklet": "Segnalibro",
"page.integration.bookmarklet.help": "Questo collegamento speciale ti consente di abbonarti ad un sito web semplicemente usando un segnalibro del tuo browser.",
"page.integration.bookmarklet.instructions": "Trascina questo collegamento sui tuoi segnalibri.",
"page.integration.bookmarklet.name": "Aggiungi a Miniflux",
"page.integration.miniflux_api": "API di Miniflux",
"page.integration.miniflux_api_endpoint": "Endpoint dell'API di Miniflux",
"page.integration.miniflux_api_password": "Password",
"page.integration.miniflux_api_password_value": "La password del tuo account",
"page.integration.miniflux_api_username": "Nome utente",
"page.integrations.title": "Integrazioni",
"page.keyboard_shortcuts.close_modal": "Chiudi la finestra di dialogo",
"page.keyboard_shortcuts.download_content": "Scarica il contenuto integrale",
"page.keyboard_shortcuts.go_to_bottom_item": "Vai all'elemento in fondo",
"page.keyboard_shortcuts.go_to_categories": "Mostra le categorie",
"page.keyboard_shortcuts.go_to_feed": "Mostra il feed",
"page.keyboard_shortcuts.go_to_feeds": "Mostra i feed",
"page.keyboard_shortcuts.go_to_history": "Mostra la cronologia",
"page.keyboard_shortcuts.go_to_next_item": "Mostra l'articolo successivo",
"page.keyboard_shortcuts.go_to_next_page": "Mostra la pagina successiva",
"page.keyboard_shortcuts.go_to_previous_item": "Mostra l'articolo precedente",
"page.keyboard_shortcuts.go_to_previous_page": "Mostra la pagina precedente",
"page.keyboard_shortcuts.go_to_search": "Apri la casella di ricerca",
"page.keyboard_shortcuts.go_to_settings": "Mostra le impostazioni",
"page.keyboard_shortcuts.go_to_starred": "Mostra i preferiti",
"page.keyboard_shortcuts.go_to_top_item": "Vai all'elemento principale",
"page.keyboard_shortcuts.go_to_unread": "Mostra gli articoli da leggere",
"page.keyboard_shortcuts.mark_page_as_read": "Segna la pagina attuale come letta",
"page.keyboard_shortcuts.open_comments": "Apri la pagina web dei commenti",
"page.keyboard_shortcuts.open_comments_same_window": "Apri il link dei commenti nella scheda corrente",
"page.keyboard_shortcuts.open_item": "Apri l'articolo selezionato",
"page.keyboard_shortcuts.open_original": "Apri la pagina web originale",
"page.keyboard_shortcuts.open_original_same_window": "Apri il link originale nella scheda corrente",
"page.keyboard_shortcuts.refresh_all_feeds": "Aggiorna tutti i feed in background",
"page.keyboard_shortcuts.remove_feed": "Rimuovi questo feed",
"page.keyboard_shortcuts.save_article": "Salva l'articolo",
"page.keyboard_shortcuts.scroll_item_to_top": "Scorri l'articolo in alto",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Mostra le scorciatoie da tastiera",
"page.keyboard_shortcuts.subtitle.actions": "Azioni",
"page.keyboard_shortcuts.subtitle.items": "Navigazione articoli",
"page.keyboard_shortcuts.subtitle.pages": "Navigazione pagine",
"page.keyboard_shortcuts.subtitle.sections": "Navigazione sezioni",
"page.keyboard_shortcuts.title": "Scorciatoie da tastiera",
"page.keyboard_shortcuts.toggle_star_status": "Aggiungi/rimuovi dai preferiti",
"page.keyboard_shortcuts.toggle_entry_attachments": "Toggle open/close entry attachments",
"page.keyboard_shortcuts.toggle_read_status_next": "Cambia lo stato di lettura (letto/da leggere), concentrati dopo",
"page.keyboard_shortcuts.toggle_read_status_prev": "Cambia lo stato di lettura (letto/da leggere), focus precedente",
"page.login.google_signin": "Accedi tramite Google",
"page.login.oidc_signin": "Accedi tramite %s",
"page.login.title": "Accedi",
"page.login.webauthn_login": "Accedi con passkey",
"page.login.webauthn_login.error": "Impossibile accedere con passkey",
"page.login.webauthn_login.help": "Please enter your username if you're using a security key. This is not required if you are using a Passkey (discoverable credentials).",
"page.new_api_key.title": "Nuova chiave API",
"page.new_category.title": "Nuova categoria",
"page.new_user.title": "Nuovo utente",
"page.offline.message": "Sei offline",
"page.offline.refresh_page": "Prova ad aggiornare la pagina",
"page.offline.title": "Modalità offline",
"page.read_entry_count": [
"%d read entry",
"%d read entries"
],
"page.search.title": "Risultati della ricerca",
"page.sessions.table.actions": "Azioni",
"page.sessions.table.current_session": "Sessione corrente",
"page.sessions.table.date": "Data",
"page.sessions.table.ip": "Indirizzo IP",
"page.sessions.table.user_agent": "User Agent",
"page.sessions.title": "Sessioni",
"page.settings.link_google_account": "Collega il mio account Google",
"page.settings.link_oidc_account": "Collega il mio account %s",
"page.settings.title": "Impostazioni",
"page.settings.unlink_google_account": "Scollega il mio account Google",
"page.settings.unlink_oidc_account": "Scollega il mio account %s",
"page.settings.webauthn.actions": "Actions",
"page.settings.webauthn.added_on": "Added On",
"page.settings.webauthn.delete": [
"Rimuovi %d passkey",
"Rimuovi %d passkey"
],
"page.settings.webauthn.last_seen_on": "Last Used",
"page.settings.webauthn.passkey_name": "Passkey Name",
"page.settings.webauthn.passkeys": "Passkeys",
"page.settings.webauthn.register": "Registra la chiave di accesso",
"page.settings.webauthn.register.error": "Impossibile registrare la passkey",
"page.shared_entries.title": "Voci condivise",
"page.shared_entries_count": [
"%d shared entry",
"%d shared entries"
],
"page.starred.title": "Preferiti",
"page.starred_entry_count": [
"%d starred entry",
"%d starred entries"
],
"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.users.actions": "Azioni",
"page.users.admin.no": "No",
"page.users.admin.yes": "Sì",
"page.users.is_admin": "Amministratore",
"page.users.last_login": "Ultimo accesso",
"page.users.never_logged": "Mai",
"page.users.title": "Utenti",
"page.users.username": "Nome utente",
"page.webauthn_rename.title": "Rename Passkey",
"pagination.first": "Primo",
"pagination.last": "Ultimo",
"pagination.next": "Successivo",
"pagination.previous": "Precedente",
"search.label": "Cerca",
"search.placeholder": "Cerca...",
"search.submit": "Cerca",
"skip_to_content": "Salta al contenuto",
"time_elapsed.days": [
"%d giorno fa",
"%d giorni fa"
],
"time_elapsed.hours": [
"%d ora fa",
"%d ore fa"
],
"time_elapsed.minutes": [
"%d minuto fa",
"%d minuti fa"
],
"time_elapsed.months": [
"%d mese fa",
"%d mesi fa"
],
"time_elapsed.not_yet": "non ancora",
"time_elapsed.now": "adesso",
"time_elapsed.weeks": [
"%d settimana fa",
"%d settimane fa"
],
"time_elapsed.years": [
"%d anno fa",
"%d anni fa"
],
"time_elapsed.yesterday": "ieri",
"tooltip.keyboard_shortcuts": "Scorciatoia da tastiera: %s",
"tooltip.logged_user": "Autenticato come %s"
} v2-2.2.13/internal/locale/translations/ja_JP.json 0000664 0000000 0000000 00000116667 15062123773 0021627 0 ustar 00root root 0000000 0000000 {
"action.cancel": "取り消し",
"action.download": "ダウンロード",
"action.edit": "編集",
"action.home_screen": "ホームスクリーンに追加",
"action.import": "インポート",
"action.login": "ログイン",
"action.or": "または",
"action.remove": "削除",
"action.remove_feed": "このフィードを削除",
"action.save": "保存",
"action.subscribe": "フィードを購読",
"action.update": "更新",
"alert.account_linked": "外部アカウントとリンクされました!",
"alert.account_unlinked": "外部アカウントとのリンクが解除されました!",
"alert.background_feed_refresh": "すべてのフィードがバックグラウンドで更新されています。この処理中も Miniflux を使い続けることができます。",
"alert.feed_error": "このフィードには問題があります。",
"alert.no_starred": "現在星付きはありません。",
"alert.no_category": "カテゴリが存在しません。",
"alert.no_category_entry": "このカテゴリには記事がありません。",
"alert.no_feed": "何も購読していません。",
"alert.no_feed_entry": "このフィードには記事がありません。",
"alert.no_feed_in_category": "このカテゴリには購読中のフィードがありません。",
"alert.no_history": "現在履歴はありません。",
"alert.no_search_result": "検索で何も見つかりませんでした。",
"alert.no_shared_entry": "共有エントリはありません。",
"alert.no_tag_entry": "このタグに一致するエントリーはありません。",
"alert.no_unread_entry": "未読の記事はありません。",
"alert.no_user": "あなたが唯一のユーザーです。",
"alert.prefs_saved": "設定情報は保存されました!",
"alert.too_many_feeds_refresh": [
"フィードの更新を要求しすぎました。%d 分後に再度お試しください。"
],
"confirm.loading": "実行中…",
"confirm.no": "いいえ",
"confirm.question": "よろしいですか?",
"confirm.question.refresh": "強制的に更新しますか?",
"confirm.yes": "はい",
"enclosure_media_controls.seek": "シーク:",
"enclosure_media_controls.seek.title": "%s 秒シーク",
"enclosure_media_controls.speed": "速度:",
"enclosure_media_controls.speed.faster": "速く",
"enclosure_media_controls.speed.faster.title": "%sx 速く",
"enclosure_media_controls.speed.reset": "リセット",
"enclosure_media_controls.speed.reset.title": "速度を1xにリセット",
"enclosure_media_controls.speed.slower": "遅く",
"enclosure_media_controls.speed.slower.title": "%sx 遅く",
"entry.starred.toast.off": "星を外しました",
"entry.starred.toast.on": "星を付けました",
"entry.starred.toggle.off": "星を外す",
"entry.starred.toggle.on": "星を付ける",
"entry.comments.label": "コメント",
"entry.comments.title": "コメントを見る",
"entry.estimated_reading_time": [
"%d 分で読めます"
],
"entry.external_link.label": "外部リンク",
"entry.save.completed": "完了!",
"entry.save.label": "保存",
"entry.save.title": "この記事を保存",
"entry.save.toast.completed": "記事は保存されました",
"entry.scraper.completed": "完了!",
"entry.scraper.label": "ダウンロード",
"entry.scraper.title": "オリジナルの内容を取得",
"entry.share.label": "共有",
"entry.share.title": "この記事を共有する",
"entry.shared_entry.label": "共有する",
"entry.shared_entry.title": "公開リンクを開く",
"entry.state.loading": "読み込み中…",
"entry.state.saving": "保存中…",
"entry.status.mark_as_read": "既読にする",
"entry.status.mark_as_unread": "未読に戻す",
"entry.status.title": "記事の状態を変更",
"entry.status.toast.read": "既読にしました",
"entry.status.toast.unread": "未読にしました",
"entry.tags.label": "タグ:",
"entry.tags.more_tags_label": [
"%d 個のタグ"
],
"entry.unshare.label": "共有を解除",
"error.api_key_already_exists": "この API キーは既に存在します。",
"error.bad_credentials": "ユーザー名かパスワードが間違っています。",
"error.category_already_exists": "このカテゴリは既に存在します。",
"error.category_not_found": "このカテゴリは存在しないか、このユーザーに属していません。",
"error.database_error": "データベースエラー: %v。",
"error.different_passwords": "パスワードが一致しません。",
"error.duplicate_fever_username": "既に同じ名前の Fever ユーザー名が使われています!",
"error.duplicate_googlereader_username": "既に同じ名前の Google Reader ユーザー名が使われています!",
"error.duplicate_linked_account": "別なユーザーが既にこのサービスの同じユーザーとリンクしています。",
"error.duplicated_feed": "このフィードは既に存在します。",
"error.empty_file": "このファイルは空です。",
"error.entries_per_page_invalid": "ページあたりの記事数が無効です。",
"error.feed_already_exists": "このフィードは既に存在します。",
"error.feed_category_not_found": "このカテゴリは存在しないか、このユーザーに属していません。",
"error.feed_format_not_detected": "フィードの形式を検出できません: %v.",
"error.feed_invalid_blocklist_rule": "ブロックリストルールが無効です。",
"error.feed_invalid_keeplist_rule": "リストの保持ルールが無効です。",
"error.feed_mandatory_fields": "URL と カテゴリが必要です。",
"error.feed_not_found": "このフィードは存在しないか、このユーザーに属していません。",
"error.feed_title_not_empty": "フィードのタイトルを空にすることはできません。",
"error.feed_url_not_empty": "フィード URL を空にすることはできません。",
"error.fields_mandatory": "すべての項目が必要です。",
"error.http_bad_gateway": "ウェブサイトは、不正なゲートウェイエラーのため現在利用できません。問題はMiniflux側にはありません。後でもう一度お試しください。",
"error.http_body_read": "HTTP本文を読み取れません: %v。",
"error.http_client_error": "HTTPクライアントエラー: %v。",
"error.http_empty_response": "HTTP応答が空です。おそらく、このウェブサイトはボット保護メカニズムを使用していますか?",
"error.http_empty_response_body": "HTTP応答本文が空です。",
"error.http_forbidden": "このウェブサイトへのアクセスは禁止されています。おそらく、このウェブサイトはボット保護メカニズムを持っていますか?",
"error.http_gateway_timeout": "The website is not available at the moment due to a gateway timeout error. The problem is not on Miniflux side. Please, try again later.",
"error.http_internal_server_error": "The website is not available at the moment due to a server error. The problem is not on Miniflux side. Please, try again later.",
"error.http_not_authorized": "Access to this website is not authorized. It could be a bad username or password.",
"error.http_resource_not_found": "The requested resource is not found. Please, verify the URL.",
"error.http_response_too_large": "The HTTP response is too large. You could increase the HTTP response size limit in the global settings (requires a server restart).",
"error.http_service_unavailable": "The website is not available at the moment due to an internal server error. The problem is not on Miniflux side. Please, try again later.",
"error.http_too_many_requests": "Miniflux generated too many requests to this website. Please, try again later or change the application configuration.",
"error.http_unexpected_status_code": "The website is not available at the moment due to an unexpected HTTP status code: %d. The problem is not on Miniflux side. Please, try again later.",
"error.invalid_categories_sorting_order": "カテゴリの表示順が無効です。",
"error.invalid_default_home_page": "デフォルトのトップページが無効です",
"error.invalid_display_mode": "Web アプリの表示モードが無効です。",
"error.invalid_entry_direction": "記事の表示順が無効です。",
"error.invalid_entry_order": "記事の表示順が無効です。",
"error.invalid_feed_proxy_url": "プロキシURLが無効です。",
"error.invalid_feed_url": "フィード URL が無効です。",
"error.invalid_gesture_nav": "ジェスチャー ナビゲーションが無効です。",
"error.invalid_language": "言語が無効です。",
"error.invalid_site_url": "サイト URL が無効です。",
"error.invalid_theme": "テーマが無効です。",
"error.invalid_timezone": "タイムゾーンが無効です。",
"error.network_operation": "Miniflux はネットワークエラーのためこのウェブサイトに到達できません: %v.",
"error.network_timeout": "このウェブサイトは応答が遅すぎるためタイムアウトしました: %v",
"error.password_min_length": "パスワードは6文字以上である必要があります。",
"error.proxy_url_not_empty": "プロキシURLを空にすることはできません。",
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
"error.settings_invalid_domain_list": "Invalid domain list. Please provide a space separated list of domains.",
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
"error.settings_mandatory_fields": "ユーザー名、テーマ、言語、タイムゾーンのすべてが必要です。",
"error.settings_media_playback_rate_range": "再生速度が範囲外",
"error.settings_reading_speed_is_positive": "読書速度は正の整数である必要があります。",
"error.site_url_not_empty": "サイトの URL を空にすることはできません。",
"error.subscription_not_found": "フィードが見つかりません。",
"error.title_required": "タイトルが必要です。",
"error.tls_error": "TLS error: %q. You could disable TLS verification in the feed settings if you would like.",
"error.unable_to_create_api_key": "この API キーを作成できません。",
"error.unable_to_create_category": "このカテゴリは作成できません。",
"error.unable_to_create_user": "このユーザーは作成できません。",
"error.unable_to_detect_rssbridge": "RSS-Bridge を使ってフィードを検出できません: %v.",
"error.unable_to_parse_feed": "このフィードを解析できません: %v.",
"error.unable_to_update_category": "このカテゴリは更新できません。",
"error.unable_to_update_feed": "このフィードは更新できません。",
"error.unable_to_update_user": "このユーザーは更新できません。",
"error.unlink_account_without_password": "パスワードを設定しなければ再びログインすることはできません。",
"error.user_already_exists": "このユーザーは既に存在します。",
"error.user_mandatory_fields": "ユーザー名が必要です。",
"error.linktaco_missing_required_fields": "LinkTaco API TokenとOrganization Slugが必要です",
"form.api_key.label.description": "API キーラベル",
"form.category.hide_globally": "未読一覧に記事を表示しない",
"form.category.label.title": "タイトル",
"form.feed.fieldset.general": "General",
"form.feed.fieldset.integration": "Third-Party Services",
"form.feed.fieldset.network_settings": "Network Settings",
"form.feed.fieldset.rules": "Rules",
"form.feed.label.allow_self_signed_certificates": "自己署名証明書または無効な証明書を許可する",
"form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
"form.feed.label.block_filter_entry_rules": "エントリブロッキングルール",
"form.feed.label.blocklist_rules": "正規表現ベースのブロッキングフィルター",
"form.feed.label.category": "カテゴリ",
"form.feed.label.cookie": "Cookie の設定",
"form.feed.label.crawler": "オリジナルの内容を取得",
"form.feed.label.description": "説明",
"form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
"form.feed.label.disabled": "このフィードを更新しない",
"form.feed.label.feed_password": "フィードのパスワード",
"form.feed.label.feed_url": "フィード URL",
"form.feed.label.feed_username": "フィードのユーザー名",
"form.feed.label.fetch_via_proxy": "アプリケーションレベルで設定されたプロキシを使用する",
"form.feed.label.hide_globally": "未読一覧に記事を表示しない",
"form.feed.label.ignore_http_cache": "HTTPキャッシュを無視",
"form.feed.label.keep_filter_entry_rules": "エントリ許可ルール",
"form.feed.label.keeplist_rules": "正規表現ベースのキープフィルター",
"form.feed.label.no_media_player": "No media player (audio/video)",
"form.feed.label.ntfy_activate": "Push entries to ntfy",
"form.feed.label.ntfy_default_priority": "Ntfy default priority",
"form.feed.label.ntfy_high_priority": "Ntfy high priority",
"form.feed.label.ntfy_low_priority": "Ntfy low priority",
"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.proxy_url": "Proxy URL",
"form.feed.label.pushover_activate": "Push entries to pushover.net",
"form.feed.label.pushover_default_priority": "Pushover default priority",
"form.feed.label.pushover_high_priority": "Pushover high priority",
"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.rewrite_rules": "コンテンツ書き換えルール",
"form.feed.label.scraper_rules": "Scraper ルール",
"form.feed.label.site_url": "サイト URL",
"form.feed.label.title": "タイトル",
"form.feed.label.urlrewrite_rules": "Rewrite URL ルール",
"form.feed.label.user_agent": "デフォルトの User Agent を上書きする",
"form.feed.label.webhook_url": "Override webhook url",
"form.import.label.file": "OPML ファイル",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Push entries to Apprise",
"form.integration.apprise_services_url": "Comma separated list of Apprise service URLs",
"form.integration.apprise_url": "Apprise API URL",
"form.integration.betula_activate": "Save entries to Betula",
"form.integration.betula_token": "Betula Token",
"form.integration.betula_url": "Betula server URL",
"form.integration.cubox_activate": "Save entries to Cubox",
"form.integration.cubox_api_link": "Cubox API link",
"form.integration.discord_activate": "Push entries to Discord",
"form.integration.discord_webhook_link": "Discord Webhook link",
"form.integration.espial_activate": "Espial に記事を保存する",
"form.integration.espial_api_key": "Espial の API key",
"form.integration.espial_endpoint": "Espial の API Endpoint",
"form.integration.espial_tags": "Espial の Tag",
"form.integration.fever_activate": "Fever API を有効にする",
"form.integration.fever_endpoint": "Fever API endpoint:",
"form.integration.fever_password": "Fever のパスワード",
"form.integration.fever_username": "Fever のユーザー名",
"form.integration.googlereader_activate": "Google Reader API を有効にする",
"form.integration.googlereader_endpoint": "Google Reader API endpoint:",
"form.integration.googlereader_password": "Google Reader のパスワード",
"form.integration.googlereader_username": "Google Reader のユーザー名",
"form.integration.instapaper_activate": "Instapaper に記事を保存する",
"form.integration.instapaper_password": "Instapaper のパスワード",
"form.integration.instapaper_username": "Instapaper のユーザー名",
"form.integration.karakeep_activate": "Karakeep に記事を保存する",
"form.integration.karakeep_api_key": "Karakeep の API key",
"form.integration.karakeep_url": "Karakeep の API Endpoint",
"form.integration.linkace_activate": "Save entries to LinkAce",
"form.integration.linkace_api_key": "LinkAce API key",
"form.integration.linkace_check_disabled": "Disable link check",
"form.integration.linkace_endpoint": "LinkAce API Endpoint",
"form.integration.linkace_is_private": "Mark link as private",
"form.integration.linkace_tags": "LinkAce Tags",
"form.integration.linkding_activate": "Linkding に記事を保存する",
"form.integration.linkding_api_key": "Linkding の API key",
"form.integration.linkding_bookmark": "ブックマークを未読にする",
"form.integration.linkding_endpoint": "Linkding の API Endpoint",
"form.integration.linkding_tags": "Linkding Tags",
"form.integration.linktaco_activate": "LinkTacoでエントリを保存する",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "パーソナルアクセストークンを取得する",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "タグ (最大10件、カンマ区切り)",
"form.integration.linktaco_tags_hint": "最大10件のタグ、カンマ区切り",
"form.integration.linktaco_visibility": "公開設定",
"form.integration.linktaco_visibility_public": "公開",
"form.integration.linktaco_visibility_private": "非公開",
"form.integration.linktaco_visibility_hint": "非公開設定には有料のLinkTacoアカウントが必要です",
"form.integration.linkwarden_activate": "Linkwarden に記事を保存する",
"form.integration.linkwarden_api_key": "Linkwarden の API key",
"form.integration.linkwarden_endpoint": "リンクワーデン ベース URL",
"form.integration.matrix_bot_activate": "新しい記事をMatrixに転送する",
"form.integration.matrix_bot_chat_id": "MatrixルームのID",
"form.integration.matrix_bot_password": "Matrixユーザ用パスワード",
"form.integration.matrix_bot_url": "MatrixサーバーのURL",
"form.integration.matrix_bot_user": "Matrixのユーザー名",
"form.integration.notion_activate": "Save entries to Notion",
"form.integration.notion_page_id": "Notion Page ID",
"form.integration.notion_token": "Notion Secret Token",
"form.integration.ntfy_activate": "Push entries to ntfy",
"form.integration.ntfy_api_token": "Ntfy API Token (optional)",
"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 (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 に記事を保存する",
"form.integration.nunux_keeper_api_key": "Nunux Keeper の API key",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper の API Endpoint",
"form.integration.omnivore_activate": "Omnivore に記事を保存する",
"form.integration.omnivore_api_key": "Omnivore の API key",
"form.integration.omnivore_url": "Omnivore の API Endpoint",
"form.integration.pinboard_activate": "Pinboard に記事を保存する",
"form.integration.pinboard_bookmark": "ブックマークを未読にする",
"form.integration.pinboard_tags": "Pinboard の Tag",
"form.integration.pinboard_token": "Pinboard の API Token",
"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.raindrop_activate": "Save entries to Raindrop",
"form.integration.raindrop_collection_id": "Collection ID",
"form.integration.raindrop_tags": "Tags (comma-separated)",
"form.integration.raindrop_token": "(Test) Token",
"form.integration.readeck_activate": "Readeck に記事を保存する",
"form.integration.readeck_api_key": "Readeck の API key",
"form.integration.readeck_endpoint": "Readeck の API Endpoint",
"form.integration.readeck_labels": "Readeck Labels",
"form.integration.readeck_only_url": "URL のみを送信 (完全なコンテンツではなく)",
"form.integration.readwise_activate": "Save entries to Readwise Reader",
"form.integration.readwise_api_key": "Readwise Reader Access Token",
"form.integration.readwise_api_key_link": "Get your Readwise Access Token",
"form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "RSS-Bridge server URL",
"form.integration.shaarli_activate": "Save articles to Shaarli",
"form.integration.shaarli_api_secret": "Shaarli API Secret",
"form.integration.shaarli_endpoint": "Shaarli URL",
"form.integration.shiori_activate": "Shiori に記事を保存する",
"form.integration.shiori_endpoint": "Shiori の API Endpoint",
"form.integration.shiori_password": "Shiori の パスワード",
"form.integration.shiori_username": "Shiori の ユーザー名",
"form.integration.slack_activate": "Slack entries to Discord",
"form.integration.slack_webhook_link": "Slack Webhook link",
"form.integration.telegram_bot_activate": "新しい記事を Telegram チャットにプッシュする",
"form.integration.telegram_bot_disable_buttons": "Disable buttons",
"form.integration.telegram_bot_disable_notification": "Disable notification",
"form.integration.telegram_bot_disable_web_page_preview": "Disable web page preview",
"form.integration.telegram_bot_token": "ボットトークン",
"form.integration.telegram_chat_id": "チャット ID",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "Wallabag に記事を保存する",
"form.integration.wallabag_client_id": "Wallabag の Client ID",
"form.integration.wallabag_client_secret": "Wallabag の Client Secret",
"form.integration.wallabag_endpoint": "ワラバッグベースURL",
"form.integration.wallabag_only_url": "URL のみを送信 (完全なコンテンツではなく)",
"form.integration.wallabag_password": "Wallabag のパスワード",
"form.integration.wallabag_username": "Wallabag のユーザー名",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Enable Webhooks",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Default Webhook URL",
"form.prefs.fieldset.application_settings": "Application Settings",
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
"form.prefs.fieldset.reader_settings": "Reader Settings",
"form.prefs.help.external_font_hosts": "Space separated list of external font hosts to allow. For example: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.always_open_external_links": "Read articles by opening external links",
"form.prefs.label.categories_sorting_order": "カテゴリの表示順",
"form.prefs.label.cjk_reading_speed": "中国語、韓国語、日本語の読書速度(文字数/分)",
"form.prefs.label.custom_css": "カスタム CSS",
"form.prefs.label.custom_js": "カスタム JavaScript",
"form.prefs.label.default_home_page": "デフォルトのトップページ",
"form.prefs.label.default_reading_speed": "他言語の読書速度(単語/分)",
"form.prefs.label.display_mode": "プログレッシブ Web アプリ (PWA) 表示モード",
"form.prefs.label.entries_per_page": "ページあたりの記事数",
"form.prefs.label.entry_order": "記事の表示順の基準",
"form.prefs.label.entry_sorting": "記事の表示順",
"form.prefs.label.entry_swipe": "タッチスクリーンでスワイプ入力を有効にする",
"form.prefs.label.external_font_hosts": "External font hosts",
"form.prefs.label.gesture_nav": "エントリ間を移動するジェスチャー",
"form.prefs.label.keyboard_shortcuts": "キーボードショートカットを有効にする",
"form.prefs.label.language": "言語",
"form.prefs.label.mark_read_manually": "Mark entries as read manually",
"form.prefs.label.mark_read_on_media_completion": "Only mark as read when audio/video playback reaches 90%% completion",
"form.prefs.label.mark_read_on_view": "表示時にエントリを自動的に既読としてマークします",
"form.prefs.label.mark_read_on_view_or_media_completion": "Mark entries as read when viewed. For audio/video, mark as read at 90%% completion",
"form.prefs.label.media_playback_rate": "オーディオ/ビデオの再生速度",
"form.prefs.label.open_external_links_in_new_tab": "外部リンクを新しいタブで開く(リンクに target=\"_blank\" を追加)",
"form.prefs.label.show_reading_time": "記事の推定読書時間を表示する",
"form.prefs.label.theme": "テーマ",
"form.prefs.label.timezone": "タイムゾーン",
"form.prefs.select.alphabetical": "アルファベット順",
"form.prefs.select.browser": "Browser",
"form.prefs.select.created_time": "記事の取得時刻",
"form.prefs.select.fullscreen": "Fullscreen",
"form.prefs.select.minimal_ui": "Minimal",
"form.prefs.select.none": "なし",
"form.prefs.select.older_first": "古い記事を最初に",
"form.prefs.select.publish_time": "記事の公開時刻",
"form.prefs.select.recent_first": "新しい記事を最初に",
"form.prefs.select.standalone": "Standalone",
"form.prefs.select.swipe": "スワイプ",
"form.prefs.select.tap": "ダブルタップ",
"form.prefs.select.unread_count": "未読数",
"form.submit.loading": "読み込み中…",
"form.submit.saving": "保存中…",
"form.user.label.admin": "管理者",
"form.user.label.confirmation": "パスワード確認",
"form.user.label.password": "パスワード",
"form.user.label.username": "ユーザー名",
"menu.about": "ソフトウェア情報",
"menu.add_feed": "フィードを購読",
"menu.add_user": "ユーザーを追加",
"menu.api_keys": "API キー",
"menu.categories": "カテゴリ",
"menu.create_api_key": "新しい API キーを作成する",
"menu.create_category": "カテゴリを作成",
"menu.edit_category": "編集",
"menu.edit_feed": "編集",
"menu.export": "エクスポート",
"menu.feed_entries": "記事一覧",
"menu.feeds": "フィード一覧",
"menu.flush_history": "履歴をクリア",
"menu.history": "履歴",
"menu.home_page": "Home page",
"menu.import": "インポート",
"menu.integrations": "連携",
"menu.logout": "ログアウト",
"menu.mark_all_as_read": "すべて既読にする",
"menu.mark_page_as_read": "このページを既読にする",
"menu.preferences": "設定情報",
"menu.refresh_all_feeds": "すべてのフィードをバックグラウンドで更新",
"menu.refresh_feed": "更新",
"menu.search": "検索",
"menu.sessions": "セッション",
"menu.settings": "設定",
"menu.shared_entries": "共有エントリ",
"menu.show_all_entries": "すべての記事を表示",
"menu.show_only_starred_entries": "Show only starred entries",
"menu.show_only_unread_entries": "未読の記事だけを表示",
"menu.starred": "星付き",
"menu.title": "Menu",
"menu.unread": "未読",
"menu.users": "ユーザー一覧",
"page.about.author": "作者:",
"page.about.build_date": "ビルド日時:",
"page.about.credits": "著作権表示",
"page.about.db_usage": "Database size:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "グローバル構成オプション",
"page.about.go_version": "Go バージョン:",
"page.about.license": "ライセンス:",
"page.about.postgres_version": "Postgres バージョン:",
"page.about.title": "ソフトウェア情報",
"page.about.version": "バージョン:",
"page.add_feed.choose_feed": "フィードを選択",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "高度な設定",
"page.add_feed.no_category": "カテゴリが存在しません。カテゴリが少なくとも1つ必要です。",
"page.add_feed.submit": "フィードを探索して追加",
"page.add_feed.title": "新規フィード",
"page.api_keys.never_used": "未使用",
"page.api_keys.table.actions": "アクション",
"page.api_keys.table.created_at": "作成日",
"page.api_keys.table.description": "説明",
"page.api_keys.table.last_used_at": "最終使用",
"page.api_keys.table.token": "トークン",
"page.api_keys.title": "API キー",
"page.categories.entries": "記事一覧",
"page.categories.feed_count": [
"%d 件のフィードがあります。"
],
"page.categories.feeds": "フィード一覧",
"page.categories.no_feed": "フィードはありません。",
"page.categories.title": "カテゴリ",
"page.categories_count": [
"%d 件のカテゴリ"
],
"page.category_label": "Category: %s",
"page.edit_category.title": "カテゴリを編集: %s",
"page.edit_feed.etag_header": "ETag ヘッダー:",
"page.edit_feed.last_check": "最終チェック:",
"page.edit_feed.last_modified_header": "Last-Modified ヘッダー:",
"page.edit_feed.last_parsing_error": "直近の解析エラー",
"page.edit_feed.no_header": "なし",
"page.edit_feed.title": "フィードを編集: %s",
"page.edit_user.title": "ユーザーを編集: %s",
"page.entry.attachments": "添付ファイル",
"page.feeds.error_count": [
"%d 個のエラー"
],
"page.feeds.last_check": "最終チェック:",
"page.feeds.next_check": "Next check:",
"page.feeds.read_counter": "既読記事の数",
"page.feeds.title": "フィード一覧",
"page.footer.elevator": "Back to top",
"page.history.title": "履歴",
"page.import.title": "インポート",
"page.integration.bookmarklet": "ブックマークレット",
"page.integration.bookmarklet.help": "この特別なリンクを使ってブラウザから直接ウェブサイトのフィードを購読できます。",
"page.integration.bookmarklet.instructions": "このリンクをブラウザのブックマークへドラッグしてください。",
"page.integration.bookmarklet.name": "Miniflux に追加",
"page.integration.miniflux_api": "Miniflux API",
"page.integration.miniflux_api_endpoint": "API Endpoint",
"page.integration.miniflux_api_password": "パスワード",
"page.integration.miniflux_api_password_value": "アカウントのパスワード",
"page.integration.miniflux_api_username": "ユーザー名",
"page.integrations.title": "連携",
"page.keyboard_shortcuts.close_modal": "モーダルダイアログを閉じる",
"page.keyboard_shortcuts.download_content": "オリジナルの内容をダウンロード",
"page.keyboard_shortcuts.go_to_bottom_item": "一番下の項目に移動",
"page.keyboard_shortcuts.go_to_categories": "カテゴリ",
"page.keyboard_shortcuts.go_to_feed": "フィード",
"page.keyboard_shortcuts.go_to_feeds": "フィード一覧",
"page.keyboard_shortcuts.go_to_history": "履歴",
"page.keyboard_shortcuts.go_to_next_item": "次のアイテム",
"page.keyboard_shortcuts.go_to_next_page": "次のページ",
"page.keyboard_shortcuts.go_to_previous_item": "前のアイテム",
"page.keyboard_shortcuts.go_to_previous_page": "前のページ",
"page.keyboard_shortcuts.go_to_search": "検索フォームに移動",
"page.keyboard_shortcuts.go_to_settings": "設定",
"page.keyboard_shortcuts.go_to_starred": "星付き",
"page.keyboard_shortcuts.go_to_top_item": "先頭の項目に移動",
"page.keyboard_shortcuts.go_to_unread": "未読",
"page.keyboard_shortcuts.mark_page_as_read": "現在のページの記事をすべて既読にする",
"page.keyboard_shortcuts.open_comments": "コメントリンクを開く",
"page.keyboard_shortcuts.open_comments_same_window": "現在のタブでコメントリンクを開く",
"page.keyboard_shortcuts.open_item": "選択されたアイテムを開く",
"page.keyboard_shortcuts.open_original": "オリジナルのリンクを開く",
"page.keyboard_shortcuts.open_original_same_window": "現在のタブでオリジナルのリンクを開く",
"page.keyboard_shortcuts.refresh_all_feeds": "すべてのフィードをバックグラウンドで更新",
"page.keyboard_shortcuts.remove_feed": "このフィードを削除",
"page.keyboard_shortcuts.save_article": "記事を保存",
"page.keyboard_shortcuts.scroll_item_to_top": "アイテムが上端になるようにスクロール",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "キーボードショートカットを表示",
"page.keyboard_shortcuts.subtitle.actions": "アクション",
"page.keyboard_shortcuts.subtitle.items": "アイテム間を移動する",
"page.keyboard_shortcuts.subtitle.pages": "ページ間を移動する",
"page.keyboard_shortcuts.subtitle.sections": "セクションを移動する",
"page.keyboard_shortcuts.title": "キーボードショートカット",
"page.keyboard_shortcuts.toggle_star_status": "星を付ける/外す",
"page.keyboard_shortcuts.toggle_entry_attachments": "添付ファイルを開く/閉じる",
"page.keyboard_shortcuts.toggle_read_status_next": "既読/未読を切り替えて次のアイテムに移動",
"page.keyboard_shortcuts.toggle_read_status_prev": "既読/未読を切り替えて前のアイテムに移動",
"page.login.google_signin": "Google アカウントでログイン",
"page.login.oidc_signin": "%s アカウントでログイン",
"page.login.title": "ログイン",
"page.login.webauthn_login": "パスキーでログイン",
"page.login.webauthn_login.error": "パスキーでログインできない",
"page.login.webauthn_login.help": "Please enter your username if you're using a security key. This is not required if you are using a Passkey (discoverable credentials).",
"page.new_api_key.title": "新しい API キー",
"page.new_category.title": "新規カテゴリ",
"page.new_user.title": "新規ユーザー",
"page.offline.message": "オフラインです",
"page.offline.refresh_page": "ページを更新してみてください",
"page.offline.title": "オフラインモード",
"page.read_entry_count": [
"%d 件の既読エントリ"
],
"page.search.title": "検索結果",
"page.sessions.table.actions": "アクション",
"page.sessions.table.current_session": "現在のセッション",
"page.sessions.table.date": "日付",
"page.sessions.table.ip": "IP アドレス",
"page.sessions.table.user_agent": "User Agent",
"page.sessions.title": "セッション",
"page.settings.link_google_account": "Google アカウントと接続する",
"page.settings.link_oidc_account": "%s アカウントと接続する",
"page.settings.title": "設定",
"page.settings.unlink_google_account": "Google アカウントと接続を解除する",
"page.settings.unlink_oidc_account": "%s アカウントと接続を解除する",
"page.settings.webauthn.actions": "Actions",
"page.settings.webauthn.added_on": "Added On",
"page.settings.webauthn.delete": [
"%d 個のパスキーを削除"
],
"page.settings.webauthn.last_seen_on": "Last Used",
"page.settings.webauthn.passkey_name": "Passkey Name",
"page.settings.webauthn.passkeys": "Passkeys",
"page.settings.webauthn.register": "パスキーを登録する",
"page.settings.webauthn.register.error": "パスキーを登録できません",
"page.shared_entries.title": "共有エントリ",
"page.shared_entries_count": [
"%d 件の共有エントリ"
],
"page.starred.title": "星付き",
"page.starred_entry_count": [
"%d 件の星付きエントリ"
],
"page.total_entry_count": [
"合計 %d 件のエントリ"
],
"page.unread.title": "未読",
"page.unread_entry_count": [
"%d 件の未読エントリ"
],
"page.users.actions": "アクション",
"page.users.admin.no": "非管理者",
"page.users.admin.yes": "管理者",
"page.users.is_admin": "管理者",
"page.users.last_login": "最終ログイン",
"page.users.never_logged": "未ログイン",
"page.users.title": "ユーザー一覧",
"page.users.username": "ユーザー名",
"page.webauthn_rename.title": "Rename Passkey",
"pagination.first": "最初",
"pagination.last": "最後",
"pagination.next": "次",
"pagination.previous": "前",
"search.label": "検索",
"search.placeholder": "…を検索",
"search.submit": "検索",
"skip_to_content": "コンテンツへスキップ",
"time_elapsed.days": [
"%d 日前"
],
"time_elapsed.hours": [
"%d 時間前"
],
"time_elapsed.minutes": [
"%d 分前"
],
"time_elapsed.months": [
"%d か月前"
],
"time_elapsed.not_yet": "未来",
"time_elapsed.now": "今",
"time_elapsed.weeks": [
"%d 週間前"
],
"time_elapsed.years": [
"%d 年前"
],
"time_elapsed.yesterday": "昨日",
"tooltip.keyboard_shortcuts": "キーボードショートカット: %s",
"tooltip.logged_user": "%s としてログイン中"
} v2-2.2.13/internal/locale/translations/nan_Latn_pehoeji.json 0000664 0000000 0000000 00000122103 15062123773 0024057 0 ustar 00root root 0000000 0000000 {
"action.cancel": "Chhú-siau",
"action.download": "Lia̍h----loh-lâi",
"action.edit": "Pian-chi̍p",
"action.home_screen": "Chng tī chú ōe-bīn",
"action.import": "Hōe--li̍p",
"action.login": "Teng-lo̍k",
"action.or": "ah-sī",
"action.remove": "Thâi tiāu",
"action.remove_feed": "Thâi tiāu chit ê siau-sit lâi-goân",
"action.save": "Pó-chûn",
"action.subscribe": "Tēng",
"action.update": "Ōaⁿ-sin",
"alert.account_linked": "Í-keng kah lí ê gōa-pō͘ kháu-chō kiat chòe-hé--ah!",
"alert.account_unlinked": "Kah lí ê gōa-pō͘ kháu-chō ê kiat í-keng phah khui--ah!",
"alert.background_feed_refresh": "Tng leh pōe-āu ōaⁿ-sin só͘-ū siau-sit lâi-goân, lí ē-sái kè-sio̍k sú-iōng Miniflux。",
"alert.feed_error": "Chit ê siau-sit lâi-goân ū būn-tôe",
"alert.no_starred": "Chit-má ah bô siu-chông",
"alert.no_category": "Chit-má ah bô lūi-pia̍t",
"alert.no_category_entry": "Chit ê lūi-pah ah bô siau-sit",
"alert.no_feed": "Chit-má ah bô siau-sit lâi-goân",
"alert.no_feed_entry": "Chit ê siau-sit lâi-goân lāi bô siau-sit",
"alert.no_feed_in_category": "Bô chit ê lūi-pia̍t ê siau-sit lâi-goân",
"alert.no_history": "Chit-má ah bô kì-lo̍k",
"alert.no_search_result": "Bô hû-ha̍p ê chhiau-chhē kiat-kó",
"alert.no_shared_entry": "Chit-má ah bô hun-hióng ê siau-sit",
"alert.no_tag_entry": "Bô kah chit ê khan-á ū hû-ha̍p ê siau-sit",
"alert.no_unread_entry": "Chit-má ah-bô tha̍k kè ê siau-sit",
"alert.no_user": "Lí sī ûi-it ê sú-iōng-lâng",
"alert.prefs_saved": "Siat-tēng í-keng pó-chûn--ah!",
"alert.too_many_feeds_refresh": [
"Lí í-keng ín-khí siuⁿ chōe pái siau-sit lâi-goân ōaⁿ-sin, chhiáⁿ tán-hāu %d hun-cheng āu koh chhì-khòaⁿ-māi."
],
"confirm.loading": "Tng leh chip-hêng…",
"confirm.no": "Hóⁿ",
"confirm.question": "Kám ū khak-tēng?",
"confirm.question.refresh": "Kám beh kiông-chè têng lia̍h?",
"confirm.yes": "Sī",
"enclosure_media_controls.seek": "Sóa-ūi:",
"enclosure_media_controls.seek.title": "Sóa %s bió",
"enclosure_media_controls.speed": "Sok-tō͘",
"enclosure_media_controls.speed.faster": "Cheng-ka sok-tō͘",
"enclosure_media_controls.speed.faster.title": "Cheng-ka sok-tō͘ %sx",
"enclosure_media_controls.speed.reset": "Têng siat-tēng",
"enclosure_media_controls.speed.reset.title": "Têng siat-tēng pàng ê sok-tō͘ chòe 1x",
"enclosure_media_controls.speed.slower": "Pàng bān",
"enclosure_media_controls.speed.slower.title": "Pàng bān %sx",
"entry.starred.toast.off": "Chhú-siau siu-chông chòe soah",
"entry.starred.toast.on": "Sin cheng-ka siu-chông chòe soah",
"entry.starred.toggle.off": "Chhú-siau siu-chông",
"entry.starred.toggle.on": "Siu-chông khí-lâi",
"entry.comments.label": "Hôe-èng",
"entry.comments.title": "Khòaⁿ hôe-èng",
"entry.estimated_reading_time": [
"Ài %d hun-cheng lâi tha̍k"
],
"entry.external_link.label": "Gōa-pō͘ liân-kiat",
"entry.save.completed": "Pó-chûn chò soah",
"entry.save.label": "Pó-chûn",
"entry.save.title": "Pó-chûn chit ê siau-sit",
"entry.save.toast.completed": "Pó-chûn chò soah",
"entry.scraper.completed": "Lia̍h soah--ah",
"entry.scraper.label": "Lia̍h--lo̍h-lâi",
"entry.scraper.title": "Lia̍h goân-tóe lōe-iông",
"entry.share.label": "Hun-hióng",
"entry.share.title": "Hun-hióng chit ê siau-sit",
"entry.shared_entry.label": "Hun-hióng",
"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.mark_as_read": "Chù chòe tha̍k kè",
"entry.status.mark_as_unread": "Chù chòe ah-bōe tha̍k",
"entry.status.title": "Kái chōng-thài",
"entry.status.toast.read": "Chù chòe tha̍k kè chòe soah",
"entry.status.toast.unread": "Chù chòe ah-bōe tha̍k chòe soah",
"entry.tags.label": "Khan-á:",
"entry.tags.more_tags_label": [
"Kah %d khan-á"
],
"entry.unshare.label": "Chhú-siau hun-hióng",
"error.api_key_already_exists": "Chit ê API só-sî í-keng chûn-chāi",
"error.bad_credentials": "M̄-tio̍h ê kháu-chō miâ ah-sī bi̍t-bé.",
"error.category_already_exists": "Lūi-pia̍t í-keng chûn-chāi.",
"error.category_not_found": "Chit ê lūi-pia̍t bô chûn-chāi ah-sī bô sio̍k-tī lí.",
"error.database_error": "Chu-liāu khò͘ ū m̄-tiō: %v.",
"error.different_passwords": "Su-li̍p ê bi̍t-bé chit nn̄g pái bô kâng.",
"error.duplicate_fever_username": "Fever ê kháu-chō miâ í-keng hō͘ lâng iōng khì--ah!",
"error.duplicate_googlereader_username": "Google Reader ê kháu-chō miâ í-keng hō͘ lâng iōng khì--ah!",
"error.duplicate_linked_account": "Chit ê beh kiat chòe-hé--ê í-keng seng hō͘ lâng kiat khì--ah!",
"error.duplicated_feed": "Chit ê siau-sit lâi-goân í-keng chûn-chāi.",
"error.empty_file": "Chit ê tóng-àn sī khang--ê.",
"error.entries_per_page_invalid": "Ta̍k ia̍h ê siau-sit sò͘ ū būn-tôe.",
"error.feed_already_exists": "Chit ê siau-sit lâi-goân í-keng chûn-chāi.",
"error.feed_category_not_found": "Bô chit ê lūi-pia̍t ah-sī kóng bô sio̍k-tī chit ê sú-iōng-lâng.",
"error.feed_format_not_detected": "Bōe līn chit ê siau-sit lâi-goân ê keh-sek: %v.",
"error.feed_invalid_blocklist_rule": "Hong-só kui-chek bô-hāu.",
"error.feed_invalid_keeplist_rule": "Pó-liû kui-chek bô-hāu.",
"error.feed_mandatory_fields": "Tio̍h-ài su-lip bāng-chí kah lūi-pia̍t.",
"error.feed_not_found": "Chhē bô chit ê siau-sit lâi-goân ah-sī bô sio̍k-tī lí",
"error.feed_title_not_empty": "Beh tēng ê siau-sit lâi-goân ê piau-tôe bōe-sái sī khang--ê.",
"error.feed_url_not_empty": "Beh tēng ê siau-sit lâi-goân bāng-chí bōe-sái sī khang--ê.",
"error.fields_mandatory": "Tio̍h-ài kā chu-liāu lóng siá chê.",
"error.http_bad_gateway": "Chit ê bāng-chām chit-má in-ūi gateway ū būn-tôe bô-hoat-tō͘ iōng, m̄ sī Miniflux chia ê būn-tôe, chhiáⁿ tán--chi̍t-ē chiah koh chhì-khòaⁿ-māi.",
"error.http_body_read": "Bô-hoat-tō͘ tha̍k HTTP body lōe-iông: %v。",
"error.http_client_error": "HTTP kheh-hō͘ thâu ū m̄-tio̍h: %v.",
"error.http_empty_response": "HTTP hôe-èng lōe-iông sī khang--ê, ū khó-lêng sī hit ê bāng-chām ū pó-hō͘ ki-chè.",
"error.http_empty_response_body": "HTTP hôe-èng body sī khang--ê.",
"error.http_forbidden": "Hō͘ kū-choa̍t chûn-chhú chit ê bāng-chām, ū khó-lêng chit ê bāng-chām ū pó-hō͘ ki-chè.",
"error.http_gateway_timeout": "Tán chit ê bāng-chām ê hôe-èng í-keng chhiau-kè sî-kan, m̄ sī Miniflux chia ê būn-tôe, chhiáⁿ tán--chi̍t-ē chiah koh chhì-khòaⁿ-māi.",
"error.http_internal_server_error": "Chit ê bāng-chām ê su-hāu-khì in ka-kī ū būn-tôe, m̄ sī Miniflux chia ê būn-tôe, chhiáⁿ tán--chi̍t-ē chiah koh chhì-khòaⁿ-māi.",
"error.http_not_authorized": "Bô khoân chûn-chhú chit ê bāng-chām, chhiáⁿ kiám-cha kháu-chō miâ kah bi̍t-bé。",
"error.http_resource_not_found": "Chhē bô chit ê liân-kiat, chhiáⁿ khak-līn bāng-chí kám ū chèng-khak.",
"error.http_response_too_large": "HTTP hôe-èng siuⁿ tōa. Lí ē-sái tī choân-he̍k siat-tēng lāi kā siōng koân hān-tō͘ kái khah koân (ài têng khui su-hāu-khì)。",
"error.http_service_unavailable": "Chit ê bāng-chām in-ūi in ka-kī lāi-pō͘ ū būn-tôe,m̄ sī Miniflux chia ê būn-tôe, chhiáⁿ tán--chi̍t-ē chiah koh chhì-khòaⁿ-māi.",
"error.http_too_many_requests": "Miniflux tùi chit ê bāng-chām ê chhéng-kiû siuⁿ kè chōe, chhiáⁿ têng chhì-khòaⁿ-māi ah-sī tiâu-chéng thêng-sek siat-tēng.",
"error.http_unexpected_status_code": "Chit ê bāng-chām chòe liáu chi̍t ê liāu-bōe-tio̍h ê HTTP chōng-thài bé: %d, chhiáⁿ tán--chi̍t-ē chiah koh chhì-khòaⁿ-māi.",
"error.invalid_categories_sorting_order": "Lūi-pia̍t ê chōe pái bô-hāu, chhiáⁿ tán-hāu %d hun-cheng āu koh chhì-khòaⁿ-māi.",
"error.invalid_default_home_page": "Ū-siat chú-ia̍h ū būn-tôe!",
"error.invalid_display_mode": "Ū būn-tôe ê su-li̍p bô͘-sek.",
"error.invalid_entry_direction": "Ū būn-tôe ê su-li̍p hong-hiòng.",
"error.invalid_entry_order": "Siau-sit ê chōe pái bô-hāu, chhiáⁿ tán-hāu %d hun-cheng āu koh chhì-khòaⁿ-māi.",
"error.invalid_feed_proxy_url": "Proxy URL ū būn-tôe.",
"error.invalid_feed_url": "Beh tēng ê siau-sit lâi-goân ê bāng-chí ū būn-tôe.",
"error.invalid_gesture_nav": "Chhiú-sè tō-lám ū būn-tôe.",
"error.invalid_language": "Ū būn-tôe ê gú-giân.",
"error.invalid_site_url": "Siau-sit lâi-goân ê bāng-chām ê bāng-chí ū būn-tôe.",
"error.invalid_theme": "Ū būn-tôe ê chú-tôe.",
"error.invalid_timezone": "Ū būn-tôe ê sî-khu.",
"error.network_operation": "Miniflux bô-hoat-tō͘ liân kàu chit ê bāng-chām, ū khó-lêng sī bāng-lō͘ būn-tôe: %v.",
"error.network_timeout": "Chit ê bāng-chām ê hôe-èng siuⁿ bān, chhéng-kiû chhiau-kè sî-kan: %v.",
"error.password_min_length": "Chhiáⁿ chì-chió ài su-li̍p la̍k ê lī goân.",
"error.proxy_url_not_empty": "Proxy URL bōe-sái sī khang--ê.",
"error.settings_block_rule_fieldname_invalid": "Bô-hāu ê hong-só kui-chek: kui-chek #%d khiàm ū-hāu ê lân-ūi miâ (e-sai ê soán-hāng: %s)",
"error.settings_block_rule_invalid_regex": "Bô-hāu ê hong-só kui-chek: kui-chek #%d ê bô͘-sek m̄ sī ha̍p-hoat ê chiàⁿ-kui piáu-ta̍t sek",
"error.settings_block_rule_regex_required": "Bô-hāu ê hong-só kui-chek: kui-chek #%d bô thê-kiong chiàⁿ-kui piáu-ta̍t sek",
"error.settings_block_rule_separator_required": "Bô-hāu ê hong-só kui-chek: kui-chek #%d ê bô͘-sek tio̍h-ài iōng '=' keh khui.",
"error.settings_invalid_domain_list": "Bāng-he̍k chheng-toaⁿ ū būn-tôe, chhiáⁿ iōng khang-keh keh khui bô kâng ê bāng-he̍k.",
"error.settings_keep_rule_fieldname_invalid": "Bô-hāu ê pó-liû kui-chek: kui-chek #%d khiàm ū-hāu ê lân-ūi miâ (e-sai ê soán-hāng: %s)",
"error.settings_keep_rule_invalid_regex": "Bô-hāu ê pó-liû kui-chek: kui-chek #%d d ê bô͘-sek m̄ sī ha̍p-hoat ê chiàⁿ-kui piáu-ta̍t sek",
"error.settings_keep_rule_regex_required": "Bô-hāu ê pó-liû kui-chek: kui-chek #%d bô thê-kiong chiàⁿ-kui piáu-ta̍t sek",
"error.settings_keep_rule_separator_required": "Bô-hāu ê pó-liû kui-chek: kui-chek #%d ê bô͘-sek tio̍h-ài iōng '=' keh khui.",
"error.settings_mandatory_fields": "Tio̍h-ài su-li̍p kháu-chō miâ, chú-tôe, gú-giân, sî-khu.",
"error.settings_media_playback_rate_range": "Pàng ê sok-tō͘ chhiau-kè hoān-ûi",
"error.settings_reading_speed_is_positive": "Tha̍k ê sok-tō͘ tio̍h-ài sī chiaⁿ chéng-sò͘",
"error.site_url_not_empty": "Siau-sit lâi-goân ê bāng-chām ê bāng-chí bōe-sái sī khang--ê.",
"error.subscription_not_found": "Chhē bōe tio̍h līm-hô tēng ê siau-sit lâi-goân",
"error.title_required": "Tio̍h-ài su-li̍p piau-tôe.",
"error.tls_error": "TLS m̄-tio̍h: %q。Nā-sī beh pàng-ba̍k TSL chèng-bêng, ē-sái tī siau-sit lâi-goân siat-tēng lāi thêng-tiong.",
"error.unable_to_create_api_key": "Bô-hoat-tō͘ sin cheng-ka chit ê API só-sî.",
"error.unable_to_create_category": "Bô-hoat-tō͘ sin cheng-ka chit ê lūi-pia̍t",
"error.unable_to_create_user": "Bô-hoat-tō͘ sin cheng-ka chit ê sú-iōng-lâng",
"error.unable_to_detect_rssbridge": "Sú-iōng RSS-Bridge sî chhē bô līm-hô siau-sit lâi-goân: %v.",
"error.unable_to_parse_feed": "Bô-hoat-tō͘ kái-sek chit ê siau-sit lâi-goân: %v.",
"error.unable_to_update_category": "Bô-hoat-tō͘ ōaⁿ-sin chit ê lūi-pia̍t",
"error.unable_to_update_feed": "Bô-hoat-tō͘ ōaⁿ-sin chit ê siau-sit lâi-goân",
"error.unable_to_update_user": "Bô-hoat-tō͘ ōaⁿ-sin chit ê sú-iōng-lâng",
"error.unlink_account_without_password": "Lí it-tēng ài siat-tēng bi̍t-bé, bô lí ē bô-hoat-tō͘ koh teng-lo̍k.",
"error.user_already_exists": "Chit ê sú-iōng-lâng í-keng chûn-chāi.",
"error.user_mandatory_fields": "Tio̍h-ài su-li̍p kháu-chō miâ",
"error.linktaco_missing_required_fields": "LinkTaco API Token kâh Organization Slug sio̍kêi",
"form.api_key.label.description": "API só-sîkhan-á",
"form.category.hide_globally": "Mài hián-sī siau-sit tī choân-he̍k ah-bōe tha̍k lia̍t-pió lāi",
"form.category.label.title": "Piau-tôe",
"form.feed.fieldset.general": "Thong-iōng",
"form.feed.fieldset.integration": "Tē-saⁿ hong ho̍k-bū",
"form.feed.fieldset.network_settings": "Bāng-lō͘ siat-tēng",
"form.feed.fieldset.rules": "Kui-chek",
"form.feed.label.allow_self_signed_certificates": "ún-chún chū chhiam ah-sī bô-hāu ê pîn-chèng",
"form.feed.label.apprise_service_urls": "Sú-iōng tō͘-tiám keh khui ê Apprise ho̍k-bū bāng-chí lia̍t-pió",
"form.feed.label.block_filter_entry_rules": "Entry Blocking Rules",
"form.feed.label.blocklist_rules": "Regex-Based Blocking Filters",
"form.feed.label.category": "lūi-pia̍t",
"form.feed.label.cookie": "Siat-tēng Cookies",
"form.feed.label.crawler": "Lia̍h goân-tóe lōe-iông",
"form.feed.label.description": "Biâu-su̍t",
"form.feed.label.disable_http2": "Thêng iōng HTTP/2 pī-bián chéng-thâu-á-hûn tui-chong",
"form.feed.label.disabled": "Mài tha̍k chit ê siau-sit lâi-goân ê sin siau-sit",
"form.feed.label.feed_password": "Siau-sit lâi-goân bi̍t-bé",
"form.feed.label.feed_url": "Siau-sit lâi-goân bāng-chí",
"form.feed.label.feed_username": "Siau-sit lâi-goân kháu-chō miâ",
"form.feed.label.fetch_via_proxy": "Iōng tī su-hāu-khì siat-tēng ê proxy",
"form.feed.label.hide_globally": "Tī choân-he̍k ah-bōe tha̍k--ê lia̍t-pió am-khàm siau-sit",
"form.feed.label.ignore_http_cache": "Pàng-ba̍k HTTP cache",
"form.feed.label.keep_filter_entry_rules": "Entry Allow Rules",
"form.feed.label.keeplist_rules": "Regex-Based Keep Filters",
"form.feed.label.no_media_player": "Bô mûi-thé hòng-sàng khì (im-sìn, sī-sìn)",
"form.feed.label.ntfy_activate": "Thui-sàng siau-sit khì ntfy",
"form.feed.label.ntfy_default_priority": "Ntfy ū-siat iu-sian sūn-sū",
"form.feed.label.ntfy_high_priority": "Ntfy koân iu-sian sūn-sū",
"form.feed.label.ntfy_low_priority": "Ntfy kē iu-sian sūn-sū",
"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.proxy_url": "Proxy URL",
"form.feed.label.pushover_activate": "Pó-chûn siau-sit kàu pushover.net",
"form.feed.label.pushover_default_priority": "Pushover ū-siat iu-sian sūn-sū",
"form.feed.label.pushover_high_priority": "Pushover koân iu-sian sūn-sū",
"form.feed.label.pushover_low_priority": "Pushover kē iu-sian sūn-sū",
"form.feed.label.pushover_max_priority": "Pushover siōng koân iu-sian sūn-sū",
"form.feed.label.pushover_min_priority": "Pushover siōng kē iu-sian sūn-sū",
"form.feed.label.pushover_priority": "Pushover siau-sit iu-sian sūn-sū",
"form.feed.label.rewrite_rules": "Content Rewrite Rules",
"form.feed.label.scraper_rules": "Lia̍h ê kui-chek",
"form.feed.label.site_url": "Bāng-chām bāng-chí",
"form.feed.label.title": "Piau-tôe",
"form.feed.label.urlrewrite_rules": "Bāng-chí têng siá kui-chek",
"form.feed.label.user_agent": "Ngī kái sú-iōng-lâng tāi-lí",
"form.feed.label.webhook_url": "Ngī kái webhook bāng-chí",
"form.import.label.file": "OPML tóng-àn",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Thui sàng siau-sit khì Apprise",
"form.integration.apprise_services_url": "Iōng tō͘-tiám keh khui ê Apprise ho̍k-bū bāng-chí lia̍t-pió",
"form.integration.apprise_url": "Apprise API bāng-chí",
"form.integration.betula_activate": "Pó-chûn siau-sit kàu Betula",
"form.integration.betula_token": "Betula Token",
"form.integration.betula_url": "Betula su-hāu-khì bāng-chí",
"form.integration.cubox_activate": "Pó-chûn siau-sit khì Cubox",
"form.integration.cubox_api_link": "Cubox API liân-kiat",
"form.integration.discord_activate": "Thui-sàng siau-sit kàu Discord",
"form.integration.discord_webhook_link": "Discord Webhook liân-kiat",
"form.integration.espial_activate": "Pó-chûn siau-sit kàu Espial",
"form.integration.espial_api_key": "Espial API só-sî",
"form.integration.espial_endpoint": "Espial API thâu",
"form.integration.espial_tags": "Espial khan-á",
"form.integration.fever_activate": "Khai-sí iōng Fever API",
"form.integration.fever_endpoint": "Fever API thâu",
"form.integration.fever_password": "Fever bi̍t-bé",
"form.integration.fever_username": "Fever kháu-chō miâ",
"form.integration.googlereader_activate": "Khai-sí iōng Google Reader API",
"form.integration.googlereader_endpoint": "Google Reader API thâu:",
"form.integration.googlereader_password": "Google Reader bi̍t-bé",
"form.integration.googlereader_username": "Google Reader Kháu-chō miâ",
"form.integration.instapaper_activate": "Pó-chûn siau-sit kàu Instapaper",
"form.integration.instapaper_password": "Instapaper bi̍t-bé",
"form.integration.instapaper_username": "Instapaper Kháu-chō miâ",
"form.integration.karakeep_activate": "Pó-chûn siau-sit kàu Karakeep",
"form.integration.karakeep_api_key": "Karakeep API só-sî",
"form.integration.karakeep_url": "Karakeep API thâu",
"form.integration.linkace_activate": "Pó-chûn siau-sit kàu LinkAce",
"form.integration.linkace_api_key": "LinkAce API só-sî",
"form.integration.linkace_check_disabled": "Thêng iōng liân-kiat kiám-cha",
"form.integration.linkace_endpoint": "LinkAce API thâu",
"form.integration.linkace_is_private": "Chù chòe su-lîn ê liân-kiat",
"form.integration.linkace_tags": "LinkAce khan-á",
"form.integration.linkding_activate": "Pó-chûn siau-sit kàu Linkding",
"form.integration.linkding_api_key": "Linkding API só-sî",
"form.integration.linkding_bookmark": "Chù chòe ah-bōe tha̍k",
"form.integration.linkding_endpoint": "Linkding API thâu",
"form.integration.linkding_tags": "Linkding khan-á",
"form.integration.linktaco_activate": "Pó-chûn siau-sit kàu LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Chhú-tek lí ê kò-jîn chún-chhú token tī",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "khan-á (siōn-koân 10, iōng tō͘-tiám keh khui)",
"form.integration.linktaco_tags_hint": "Siōn-koân 10 khan-á, iōng tō͘-tiám keh khui",
"form.integration.linktaco_visibility": "Kò-chhiah-kì sìa?",
"form.integration.linktaco_visibility_public": "Kò-chhiah-kì",
"form.integration.linktaco_visibility_private": "Su-lîn",
"form.integration.linktaco_visibility_hint": "Su-lîn sìa tík tio̍h-ài chù-hêng LinkTaco kháu-chō",
"form.integration.linkwarden_activate": "Pó-chûn siau-sit kàu Linkwarden",
"form.integration.linkwarden_api_key": "Linkwarden API só-sî",
"form.integration.linkwarden_endpoint": "Linkwarden Base URL",
"form.integration.matrix_bot_activate": "Thui-sàng siau-sit kàu Matrix",
"form.integration.matrix_bot_chat_id": "Matrix pâng-keng ID",
"form.integration.matrix_bot_password": "Matrix bi̍t-bé",
"form.integration.matrix_bot_url": "Matrix su-hāu-khìbāng-chí",
"form.integration.matrix_bot_user": "Matrix kháu-chō miâ",
"form.integration.notion_activate": "Pó-chûn siau-sit kàu Notion",
"form.integration.notion_page_id": "Notion Page ID",
"form.integration.notion_token": "Notion Secret Token",
"form.integration.ntfy_activate": "Thui-sàng siau-sit kàu Ntfy",
"form.integration.ntfy_api_token": "Ntfy API só-sî (soán thiⁿ)",
"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 (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",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API só-sî",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API thâu",
"form.integration.omnivore_activate": "Pó-chûn siau-sit kàu Omnivore",
"form.integration.omnivore_api_key": "Omnivore API só-sî",
"form.integration.omnivore_url": "Omnivore API thâu",
"form.integration.pinboard_activate": "Pó-chûn siau-sit kàu Pinboard",
"form.integration.pinboard_bookmark": "Chù chòe ah-bōe tha̍k",
"form.integration.pinboard_tags": "Pinboard khan-á",
"form.integration.pinboard_token": "Pinboard API Token",
"form.integration.pushover_activate": "Pó-chûn siau-sit kàu Pushover",
"form.integration.pushover_device": "Pushover ki-hì (soán thiⁿ)",
"form.integration.pushover_prefix": "Pushover URL tó͘-bí (soán thiⁿ)",
"form.integration.pushover_token": "Pushover application API só-sî",
"form.integration.pushover_user": "Pushover sú-iōng-lâng só-sî",
"form.integration.raindrop_activate": "Pó-chûn siau-sit kàu Raindrop",
"form.integration.raindrop_collection_id": "Collection ID",
"form.integration.raindrop_tags": "khan-á (iōng tō͘-tiám keh khui)",
"form.integration.raindrop_token": "Raindrop Acess Token",
"form.integration.readeck_activate": "Pó-chûn siau-sit kàu Readeck",
"form.integration.readeck_api_key": "Readeck API só-sî",
"form.integration.readeck_endpoint": "Readeck API thâu",
"form.integration.readeck_labels": "Readeck Labels",
"form.integration.readeck_only_url": "Kan-na thoân bāng-chí (m̄ sī oân-chéng ê lōe-iông)",
"form.integration.readwise_activate": "Pó-chûn siau-sit kàu Readwise Reader",
"form.integration.readwise_api_key": "Readwise Reader Acess Token",
"form.integration.readwise_api_key_link": "Chhú-tek lí ê Readwise Acess Token",
"form.integration.rssbridge_activate": "Sin cheng-ka siau-sit lâi-goân ê sî tio̍h RSS-Bridge",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "RSS-Bridge su-hāu-khì的bāng-chí",
"form.integration.shaarli_activate": "Pó-chûn siau-sit kàu Shaarli",
"form.integration.shaarli_api_secret": "Shaarli API só-sî",
"form.integration.shaarli_endpoint": "Shaarli bāng-chí",
"form.integration.shiori_activate": "Pó-chûn siau-sit kàu Shiori",
"form.integration.shiori_endpoint": "Shiori API thâu",
"form.integration.shiori_password": "Shiori bi̍t-bé",
"form.integration.shiori_username": "Shiori kháu-chō miâ",
"form.integration.slack_activate": "Thui-sàng siau-sit kàu Slack",
"form.integration.slack_webhook_link": "Slack Webhook liân-kiat",
"form.integration.telegram_bot_activate": "Thui-sàng siau-sit kàu Telegram",
"form.integration.telegram_bot_disable_buttons": "Mài hián-sī khai-koan",
"form.integration.telegram_bot_disable_notification": "Têng iōng thong-ti",
"form.integration.telegram_bot_disable_web_page_preview": "Thêng iōng bāng-ia̍h ū-lám",
"form.integration.telegram_bot_token": "Bot Token",
"form.integration.telegram_chat_id": "Chat ID",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "Pó-chûn siau-sit kàu Wallabag",
"form.integration.wallabag_client_id": "Wallabag kheh-hō͘ thâu ID",
"form.integration.wallabag_client_secret": "Wallabag kheh-hō͘ thâu só-sî",
"form.integration.wallabag_endpoint": "Wallabag Base URL",
"form.integration.wallabag_only_url": "Kan-na thoân bāng-chí (m̄ sī oân-chéng ê lōe-iông)",
"form.integration.wallabag_password": "Wallabag bi̍t-bé",
"form.integration.wallabag_username": "Wallabag kháu-chō miâ",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Khai-sí iōng Webhooks",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Default Webhook bāng-chí",
"form.prefs.fieldset.application_settings": "Èng-iōng thêng-sek siat-tēng",
"form.prefs.fieldset.authentication_settings": "Sú-iōng-lâng giām-chèng siat-tēng",
"form.prefs.fieldset.global_feed_settings": "Choân-he̍k siau-sit lâi-goân siat-tēng",
"form.prefs.fieldset.reader_settings": "Ia̍t-tha̍k khì siat-tēng",
"form.prefs.help.external_font_hosts": "Iōng khang-keh keh khui ún-chún ê gōa-pō͘ lī-hêng lâi-goân. Phì-lû \"fonts.gstatic.com fonts.googleapis.com\"",
"form.prefs.label.always_open_external_links": "Chhiau-chhē bûn-chiong sī iōng gōa-pō͘ liân-kiat phah khui",
"form.prefs.label.categories_sorting_order": "Lūi-pia̍t hián-sī sūn-sū",
"form.prefs.label.cjk_reading_speed": "Tiong-bûn, Hân-bûn, Li̍t-bûn tha̍k ê sok-tō͘ (múi hun-cheng ē-sái tha̍k kúi ê lī-goân)",
"form.prefs.label.custom_css": "Chū tēng ê CSS",
"form.prefs.label.custom_js": "Chū tēng ê JavaScript",
"form.prefs.label.default_home_page": "Ū-siat chú-ia̍h",
"form.prefs.label.default_reading_speed": "Kî-thaⁿ gú-giân tha̍k ê sok-tō͘ (múi hun-cheng ē-sái tha̍k kúi ê lī)",
"form.prefs.label.display_mode": "Chiām-chìn sek bāng-lō͘ èng-iōng theng-sek (PWA) ê hián-sī bô͘-sek",
"form.prefs.label.entries_per_page": "Ta̍k ia̍h siau-sit sò͘",
"form.prefs.label.entry_order": "Siau-sit hián-sī sūn-sū ê i-kù",
"form.prefs.label.entry_sorting": "Siau-sit sūn-sū",
"form.prefs.label.entry_swipe": "Ē-sái tī chhiok-khòng sek êng-bō͘ ùi siau-sit iōng thoa tāng chhau-chok",
"form.prefs.label.external_font_hosts": "Gōa-pō͘ lī-hêng lâi-goân",
"form.prefs.label.gesture_nav": "Tī siau-sit kan sóa-ūi ê chhiú-sè",
"form.prefs.label.keyboard_shortcuts": "Ē-sái iōng khí-pôaⁿ khoài-sok khí",
"form.prefs.label.language": "Gú-giân",
"form.prefs.label.mark_read_manually": "Ka-kī chhau-chok kám beh chù chòe tha̍k kè",
"form.prefs.label.mark_read_on_media_completion": "Kan-na tī im-sìn, sī-sìn hòng-sàng kàu 90%% ê si-chun chù chòe tha̍k kè",
"form.prefs.label.mark_read_on_view": "Phah khui ê sî-chūn sūn-sòa kā siau-sit chù chòe tha̍k kè",
"form.prefs.label.mark_read_on_view_or_media_completion": "Phah khui ê sî-chūn sūn-sòa kā siau-sit chù chòe tha̍k kè, m̄-koh nā-sī im-sìn, sī-sìn tio̍h tī hòng-sàng kàu 90%% ê si-chun chiah lâi chù",
"form.prefs.label.media_playback_rate": "Im-sìn, sī-sìn pàng ê sok-tō͘",
"form.prefs.label.open_external_links_in_new_tab": "Chhiau-chhē gōa-pō͘ liân-kiat sī tī sin ê ia̍h phah khui (kā liân-kiat chhē target=\"_blank\")",
"form.prefs.label.show_reading_time": "Hián-sī siau-sit àn-sǹg ài gōa-kú lâi tha̍k",
"form.prefs.label.theme": "Chú-tôe",
"form.prefs.label.timezone": "Sî-khu",
"form.prefs.select.alphabetical": "Chiàu lī-bú pâi",
"form.prefs.select.browser": "Iû-lâm-khì",
"form.prefs.select.created_time": "Siau-sit kiàn-li̍p sî-kan",
"form.prefs.select.fullscreen": "Choân êng-bō͘",
"form.prefs.select.minimal_ui": "Siōng sió UI",
"form.prefs.select.none": "Bô",
"form.prefs.select.older_first": "Ùi kū--ê khai-sí pâi",
"form.prefs.select.publish_time": "Siau-sit hoat-pò͘ sî-kan",
"form.prefs.select.recent_first": "Ùi sin--ê khai-sí pâi",
"form.prefs.select.standalone": "To̍k-li̍p--ê",
"form.prefs.select.swipe": "Iōng thoa--ê",
"form.prefs.select.tap": "Tiám nn̄g pái",
"form.prefs.select.unread_count": "Ah-bōe tha̍k ê sò͘-liōng",
"form.submit.loading": "Tng leh chip-hêng…",
"form.submit.saving": "Tng leh pó-chûn…",
"form.user.label.admin": "Koán-lí-lâng",
"form.user.label.confirmation": "Koh su-li̍p chi̍t pái bi̍t-bé",
"form.user.label.password": "Bi̍t-bé",
"form.user.label.username": "Kháu-chō miâ",
"menu.about": "Iú-koan",
"menu.add_feed": "Sin cheng-ka siau-sit lâi-goân",
"menu.add_user": "Sin cheng-ka sú-iōng-lâng",
"menu.api_keys": "API só-sî",
"menu.categories": "Lūi-pia̍t",
"menu.create_api_key": "Sin cheng-ka chi̍t ê API só-sî",
"menu.create_category": "Sin cheng-ka lūi-pia̍t",
"menu.edit_category": "Pian-chi̍p",
"menu.edit_feed": "Pian-chi̍p",
"menu.export": "Hōe--chhut",
"menu.feed_entries": "Bûn-chiong",
"menu.feeds": "Siau-sit lâi-goân",
"menu.flush_history": "Hìⁿ-sak kì-lo̍k",
"menu.history": "Kì-lo̍k",
"menu.home_page": "Siú ia̍h",
"menu.import": "Hōe--li̍p",
"menu.integrations": "Chéng-ha̍p",
"menu.logout": "Teng-chhut",
"menu.mark_all_as_read": "Choân-pō͘ chù chòe tha̍k kè",
"menu.mark_page_as_read": "Kā chit ia̍h--ê lóng chù chòe tha̍k kè",
"menu.preferences": "Siat-tēng",
"menu.refresh_all_feeds": "Tī pōe-āu têng lia̍h só͘-ū ê siau-sit lâi-goân",
"menu.refresh_feed": "Têng lia̍h",
"menu.search": "Chhiau-chhē",
"menu.sessions": "Ū teng-lo̍k--ê",
"menu.settings": "Siat-tēng",
"menu.shared_entries": "Hun-hióng kè ê siau-sit",
"menu.show_all_entries": "Hián-sī só͘-ū ê siau-sit",
"menu.show_only_starred_entries": "Kan-na hián-sī siu-chông ê siau-sit",
"menu.show_only_unread_entries": "Kan-na hián-sī ah-bōe tha̍k kè ê siau-sit",
"menu.starred": "Siu-chông",
"menu.title": "Tō-lám",
"menu.unread": "Ah-bōe tha̍k",
"menu.users": "Sú-iōng-lâng",
"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 chhài-chhiú:",
"page.about.git_commit": "Git Commit:",
"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:",
"page.about.postgres_version": "Postgres pán-pún:",
"page.about.title": "Iú-koan",
"page.about.version": "Pán-pún:",
"page.add_feed.choose_feed": "Soán-te̍k chi̍t ê Siau-sit lâi-goân",
"page.add_feed.label.url": "Bāng-chí",
"page.add_feed.legend.advanced_options": "Chìn-kai soán-hāng",
"page.add_feed.no_category": "Ah bô lūi-pia̍t, chì-chió ài ū chi̍t ê",
"page.add_feed.submit": "Chhē Siau-sit lâi-goân",
"page.add_feed.title": "Sin cheng-ka Siau-sit lâi-goân",
"page.api_keys.never_used": "Bô iōng kè",
"page.api_keys.table.actions": "Chhau-chok",
"page.api_keys.table.created_at": "Kiàn-tì li̍t-kî",
"page.api_keys.table.description": "Biâu-su̍t",
"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.entries": "Siau-sit",
"page.categories.feed_count": [
"Ū %d ê Siau-sit lâi-goân"
],
"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:",
"page.edit_feed.last_check": "Siōng-bóe pái kiám-cha sî-kan",
"page.edit_feed.last_modified_header": "Siōng-bóe pái siu-kái piau-thâu:",
"page.edit_feed.last_parsing_error": "Siōng-bóe pái kái-sek m̄-tio̍h",
"page.edit_feed.no_header": "Bô",
"page.edit_feed.title": "Pian-chi̍p Siau-sit lâi-goân: %s",
"page.edit_user.title": "pian-chi̍p sú-iōng-lâng: %s",
"page.entry.attachments": "Hù-kiāⁿ",
"page.feeds.error_count": [
"%d ê m̄-tio̍h"
],
"page.feeds.last_check": "Siōng-bóe kiám-cha sî-kan:",
"page.feeds.next_check": "Āu-pái kiám-cha sî-kan:",
"page.feeds.read_counter": "Tha̍k kè--ê siau-sit sò͘",
"page.feeds.title": "Siau-sit lâi-goân",
"page.footer.elevator": "Back to top",
"page.history.title": "Kì-lo̍k",
"page.import.title": "Hōe-li̍p",
"page.integration.bookmarklet": "Chheh-chhiam ke-si",
"page.integration.bookmarklet.help": "Lí ē-sái iōng chit ê te̍k-pia̍t ê chheh-chhiam ti̍t-chiap tēng bāng-ia̍h ê siau-sit",
"page.integration.bookmarklet.instructions": "Kā chit ê liân-kiat thoa khì iû-lám khì ê chheh-chhiam lân",
"page.integration.bookmarklet.name": "Siu-chông Miniflux",
"page.integration.miniflux_api": "Miniflux API",
"page.integration.miniflux_api_endpoint": "API thâu",
"page.integration.miniflux_api_password": "Bi̍t-bé",
"page.integration.miniflux_api_password_value": "Lí ê kháu-chō ê bi̍t-bé",
"page.integration.miniflux_api_username": "Kháu-chō miâ",
"page.integrations.title": "Chéng-ha̍p",
"page.keyboard_shortcuts.close_modal": "Kìm tiāu tùi-ōe thang",
"page.keyboard_shortcuts.download_content": "Liah goân-tóe ê siau-sit lōe-iông",
"page.keyboard_shortcuts.go_to_bottom_item": "Sóa khì thōng ē-kha ê siau-sit",
"page.keyboard_shortcuts.go_to_categories": "Phah khui lūi-pia̍t ia̍h",
"page.keyboard_shortcuts.go_to_feed": "Khì siau-sit lâi-goân",
"page.keyboard_shortcuts.go_to_feeds": "Phah khui siau-sit lâi-goân ia̍h",
"page.keyboard_shortcuts.go_to_history": "Phah khui kì-lo̍k ia̍h",
"page.keyboard_shortcuts.go_to_next_item": "Āu-chi̍t ê siau-sit",
"page.keyboard_shortcuts.go_to_next_page": "Āu-chi̍t ia̍h",
"page.keyboard_shortcuts.go_to_previous_item": "Téng-chi̍t ê siau-sit",
"page.keyboard_shortcuts.go_to_previous_page": "Téng-chi̍t ia̍h",
"page.keyboard_shortcuts.go_to_search": "Phah khui chhiau-chhē ia̍h",
"page.keyboard_shortcuts.go_to_settings": "Phah khui siat-tēng ia̍h",
"page.keyboard_shortcuts.go_to_starred": "Phah khui siu-chông--ê ia̍h",
"page.keyboard_shortcuts.go_to_top_item": "Sóa khì thōng téng-koân ê siau-sit",
"page.keyboard_shortcuts.go_to_unread": "Phah khui ah-bōe tha̍k--ê ia̍h",
"page.keyboard_shortcuts.mark_page_as_read": "Kā chit ia̍h--ê lóng chù chòe tha̍k--kè",
"page.keyboard_shortcuts.open_comments": "Phah khui hôe-èng liân-kiat",
"page.keyboard_shortcuts.open_comments_same_window": "Tī chit-má ê hun-ia̍h phah khui hôe-èng liân-kiat",
"page.keyboard_shortcuts.open_item": "Phah khui soán-te̍k ê siau-sit",
"page.keyboard_shortcuts.open_original": "Phah khui siau-sit goân-tóe ê liân-kiat",
"page.keyboard_shortcuts.open_original_same_window": "Tī chit-má ê hun-ia̍h phah khui siau-sit goân-tóe ê liân-kiat",
"page.keyboard_shortcuts.refresh_all_feeds": "Tī pōe-āu ōaⁿ-sin siau-sit lâi-goân",
"page.keyboard_shortcuts.remove_feed": "Thâi tiāu siau-sit lâi-goân",
"page.keyboard_shortcuts.save_article": "Pó-chûn siau-sit",
"page.keyboard_shortcuts.scroll_item_to_top": "Sóa khì bāng-ia̍h siōng téng-koân",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Hián-sī khoài-sok khí",
"page.keyboard_shortcuts.subtitle.actions": "Chhau-chok",
"page.keyboard_shortcuts.subtitle.items": "Bûn-chiong tō-lám",
"page.keyboard_shortcuts.subtitle.pages": "Ia̍h bīn tō-lám",
"page.keyboard_shortcuts.subtitle.sections": "Hun lân tō-lám",
"page.keyboard_shortcuts.title": "Khoài-sok khí",
"page.keyboard_shortcuts.toggle_star_status": "Chhet-li̍p siu-chông chōng-thài",
"page.keyboard_shortcuts.toggle_entry_attachments": "Chhet-li̍p thián khui kah siu-ha̍p siau-sit hù-kiāⁿ ê chōng-thài",
"page.keyboard_shortcuts.toggle_read_status_next": "Chhet-li̍p tha̍k--kè, ah-bōe tha̍k ê chōng-thài, koh chiau-tiám tī āu-chi̍t--ê",
"page.keyboard_shortcuts.toggle_read_status_prev": "Chhet-li̍p tha̍k--kè, ah-bōe tha̍k ê chōng-thài, koh chiau-tiám tī téng-chi̍t--ê",
"page.login.google_signin": "Sú-iōng Google teng-lo̍k",
"page.login.oidc_signin": "Sú-iōng %s teng-lo̍k",
"page.login.title": "teng-lo̍k",
"page.login.webauthn_login": "Sú-iōng bi̍t-bé teng-lo̍k",
"page.login.webauthn_login.error": "Bô-hoat-tō͘ iōng bi̍t-bé teng-lo̍k",
"page.login.webauthn_login.help": "Sú-iōng an-choân só-sî teng-lo̍k ê sî-chūn, chhiáⁿ su-li̍p kháu-chō miâ. Nā-sī iōng thang chhiau-chhē ê Passkey (discoverable credentials) tio̍h bián.",
"page.new_api_key.title": "Sin ê API só-sî",
"page.new_category.title": "Sin lūi-pia̍t",
"page.new_user.title": "Sin sú-iōng-lâng",
"page.offline.message": "Lí í-keng lî-sòaⁿ",
"page.offline.refresh_page": "Chhì-khòaⁿ-māi têng tha̍k bāng-ia̍h",
"page.offline.title": "Lî-sòaⁿ bô͘-sek",
"page.read_entry_count": [
"%d ê tha̍k kè ê siau-sit"
],
"page.search.title": "Chhiau-chhē kiat-kó",
"page.sessions.table.actions": "Chhau-chok",
"page.sessions.table.current_session": "Chit-má teng-lo̍k--ê",
"page.sessions.table.date": "Li̍t-kî",
"page.sessions.table.ip": "IP tōe-chí",
"page.sessions.table.user_agent": "Sú-iōng-lâng tāi-lí",
"page.sessions.title": "Ū teng-lo̍k--ê",
"page.settings.link_google_account": "Kah góa ê Google kháu-chō kiat chòe-hé",
"page.settings.link_oidc_account": "Kah góa ê %s kháu-chō kiat chòe-hé",
"page.settings.title": "Siat-tēng",
"page.settings.unlink_google_account": "Phah khui kah góa ê Google kháu-chō ê kiat",
"page.settings.unlink_oidc_account": "Phah khui kah góa ê %s kháu-chō ê kiat",
"page.settings.webauthn.actions": "Chhau-chok",
"page.settings.webauthn.added_on": "Sin cheng-ka ê sî-kan",
"page.settings.webauthn.delete": [
"Thâi tiāu %d ê Passkey"
],
"page.settings.webauthn.last_seen_on": "Siōng-bóe pái sú-iōng sî-kan",
"page.settings.webauthn.passkey_name": "Passkey miâ",
"page.settings.webauthn.passkeys": "Passkeys",
"page.settings.webauthn.register": "Chù-chheh Passkey",
"page.settings.webauthn.register.error": "Bô-hoat-tō͘ chù-chheh Passkey",
"page.shared_entries.title": "Hun-hióng kè ê siau-sit",
"page.shared_entries_count": [
"Í-keng hun-hióng %d ê siau-sit"
],
"page.starred.title": "Siu-chông",
"page.starred_entry_count": [
"%d ê siu-chông ê siau-sit"
],
"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.users.actions": "chhau-chok",
"page.users.admin.no": "Hóⁿ",
"page.users.admin.yes": "Sī",
"page.users.is_admin": "Koán-lí-lâng",
"page.users.last_login": "Siōng-bóe pái teng-lo̍k",
"page.users.never_logged": "Chū-lâi bô teng-lo̍k kè",
"page.users.title": "Sú-iōng-lâng",
"page.users.username": "Sú-iōng-lâng miâ",
"page.webauthn_rename.title": "Tiông-sin hō͘ miâ Passkey",
"pagination.first": "Thâu-chi̍t ia̍h",
"pagination.last": "Siōng-bóe ia̍h",
"pagination.next": "Āu-chi̍t ia̍h",
"pagination.previous": "Téng-chi̍t ia̍h",
"search.label": "Chhiau-chhē",
"search.placeholder": "Chhiau-chhē...",
"search.submit": "Chhiau-chhē",
"skip_to_content": "Thiaⁿ--khì chhòng-bûn",
"time_elapsed.days": [
"%d kang chêng"
],
"time_elapsed.hours": [
"%d tiám-cheng chêng"
],
"time_elapsed.minutes": [
"%d hun-cheng chêng"
],
"time_elapsed.months": [
"%d kò ge̍h chêng"
],
"time_elapsed.not_yet": "ah-bōe",
"time_elapsed.now": "tú-chiah",
"time_elapsed.weeks": [
"%d lé-pài chêng"
],
"time_elapsed.years": [
"%d nî chêng"
],
"time_elapsed.yesterday": "cha-hng",
"tooltip.keyboard_shortcuts": "Khoài-sok khí:%s",
"tooltip.logged_user": "Chit-má teng-lo̍k--ê: %s"
}
v2-2.2.13/internal/locale/translations/nl_NL.json 0000664 0000000 0000000 00000112701 15062123773 0021627 0 ustar 00root root 0000000 0000000 {
"action.cancel": "annuleren",
"action.download": "Downloaden",
"action.edit": "Bewerken",
"action.home_screen": "Toevoegen aan startscherm",
"action.import": "Importeren",
"action.login": "Inloggen",
"action.or": "of",
"action.remove": "Verwijderen",
"action.remove_feed": "Verwijder deze feed",
"action.save": "Opslaan",
"action.subscribe": "Abonneren",
"action.update": "Bijwerken",
"alert.account_linked": "Jouw externe account is nu gekoppeld!",
"alert.account_unlinked": "Jouw externe account is nu ontkoppeld!",
"alert.background_feed_refresh": "Alle feeds worden op de achtergrond vernieuwd. Je kunt Miniflux blijven gebruiker terwijl dit proces draait.",
"alert.feed_error": "Er is een probleem met deze feed",
"alert.no_starred": "Er zijn geen favorieten.",
"alert.no_category": "Er zijn geen categorieën.",
"alert.no_category_entry": "Er zijn geen artikelen in deze categorie.",
"alert.no_feed": "Je hebt nog geen feed geabonneerd.",
"alert.no_feed_entry": "Er zijn geen artikelen in deze feed.",
"alert.no_feed_in_category": "Er is geen feed voor deze categorie.",
"alert.no_history": "Geschiedenis is op dit moment leeg.",
"alert.no_search_result": "Er is geen resultaat voor deze zoekopdracht.",
"alert.no_shared_entry": "Er is geen gedeeld artikel.",
"alert.no_tag_entry": "Er zijn geen artikelen die overeenkomen met deze tag.",
"alert.no_unread_entry": "Er zijn geen ongelezen artikelen.",
"alert.no_user": "Je bent de enige gebruiker.",
"alert.prefs_saved": "Instellingen opgeslagen!",
"alert.too_many_feeds_refresh": [
"Je hebt te veel feed-vernieuwingen getriggered. Wacht aub %d minuut voor opnieuw proberen.",
"Je hebt te veel feed-vernieuwingen getriggered. Wacht aub %d minuten voor opnieuw proberen."
],
"confirm.loading": "Bezig...",
"confirm.no": "nee",
"confirm.question": "Weet je het zeker?",
"confirm.question.refresh": "Wil je vernieuwen forceren?",
"confirm.yes": "ja",
"enclosure_media_controls.seek": "Vooruit/terug:",
"enclosure_media_controls.seek.title": " Vooruit/terug met %s seconden",
"enclosure_media_controls.speed": "Snelheid:",
"enclosure_media_controls.speed.faster": "Versnel",
"enclosure_media_controls.speed.faster.title": "Versnel met %sx",
"enclosure_media_controls.speed.reset": "Resetten",
"enclosure_media_controls.speed.reset.title": "Reset snelheid naar 1x",
"enclosure_media_controls.speed.slower": "Vertraag",
"enclosure_media_controls.speed.slower.title": "Vertraag met %sx",
"entry.starred.toast.off": "Favoriet verwijderd",
"entry.starred.toast.on": "Favoriet toegevoegd",
"entry.starred.toggle.off": "Favoriet verwijderen",
"entry.starred.toggle.on": "Favoriet",
"entry.comments.label": "Reacties",
"entry.comments.title": "Bekijk reacties",
"entry.estimated_reading_time": [
"%d minuut leestijd",
"%d minuten leestijd"
],
"entry.external_link.label": "Externe link",
"entry.save.completed": "Klaar!",
"entry.save.label": "Opslaan",
"entry.save.title": "Artikel opslaan",
"entry.save.toast.completed": "Artikel opgeslagen",
"entry.scraper.completed": "Klaar!",
"entry.scraper.label": "Downloaden",
"entry.scraper.title": "Originele inhoud ophalen",
"entry.share.label": "Delen",
"entry.share.title": "Deel dit artikel",
"entry.shared_entry.label": "Delen",
"entry.shared_entry.title": "Open de openbare link",
"entry.state.loading": "Laden...",
"entry.state.saving": "Opslaan...",
"entry.status.mark_as_read": "Markeren als gelezen",
"entry.status.mark_as_unread": "Markeren als ongelezen",
"entry.status.title": "Verander artikelstatus",
"entry.status.toast.read": "Gemarkeerd als gelezen",
"entry.status.toast.unread": "Gemarkeerd als ongelezen",
"entry.tags.label": "Tags:",
"entry.tags.more_tags_label": [
"Toon %d extra tag",
"Toon %d extra tags"
],
"entry.unshare.label": "Delen ongedaan maken",
"error.api_key_already_exists": "Deze API-sleutel bestaat al.",
"error.bad_credentials": "Onjuiste gebruikersnaam of wachtwoord.",
"error.category_already_exists": "Deze categorie bestaat al.",
"error.category_not_found": "Deze categorie bestaat niet of hoort niet bij deze gebruiker.",
"error.database_error": "Database fout: %v.",
"error.different_passwords": "Wachtwoorden zijn niet hetzelfde.",
"error.duplicate_fever_username": "Er is al iemand met dezelfde Fever gebruikersnaam!",
"error.duplicate_googlereader_username": "Er is al iemand met dezelfde Google Reader gebruikersnaam!",
"error.duplicate_linked_account": "Er is al iemand geregistreerd met deze provider!",
"error.duplicated_feed": "Deze feed bestaat al.",
"error.empty_file": "Dit bestand is leeg.",
"error.entries_per_page_invalid": "Het aantal artikelen per pagina is niet geldig.",
"error.feed_already_exists": "Deze feed bestaat al.",
"error.feed_category_not_found": "Deze categorie bestaat niet of behoort niet tot deze gebruiker.",
"error.feed_format_not_detected": "Feed-formaat kan niet worden gedetecteerd: %v.",
"error.feed_invalid_blocklist_rule": "De blokkeerregel is ongeldig.",
"error.feed_invalid_keeplist_rule": "De bewaarregel is ongeldig.",
"error.feed_mandatory_fields": "De velden URL en categorie zijn verplicht.",
"error.feed_not_found": "Deze feed bestaat niet of is niet van deze gebruiker.",
"error.feed_title_not_empty": "De feed titel mag niet leeg zijn.",
"error.feed_url_not_empty": "De feed URL mag niet leeg zijn.",
"error.fields_mandatory": "Alle velden moeten ingevuld zijn.",
"error.http_bad_gateway": "De website is momenteel niet beschikbaar vanwege een slechte-gateway-fout. De oorzaak hiervan ligt niet bij Miniflux. Probeer het later nogmaals aub.",
"error.http_body_read": "Kan de HTTP-body niet lezen: %v.",
"error.http_client_error": "HTTP-client-fout: %v.",
"error.http_empty_response": "De HTTP-respons is leeg. Misschien gebruikt deze website een botbeveiligingsmechanisme?",
"error.http_empty_response_body": "De HTTP-respons body is leeg.",
"error.http_forbidden": "Toegang tot deze website is verboden. Misschien heeft deze website een botbeveiligingsmechanisme?",
"error.http_gateway_timeout": "De website is momenteel niet beschikbaar vanwege een timeout bij de gateway. De oorzaak hiervan ligt niet bij Miniflux. Probeer het later nogmaals aub.",
"error.http_internal_server_error": "De website is momenteel niet beschikbaar vanwege een interne-server-fout. De oorzaak hiervan ligt niet bij Miniflux. Probeer het later nogmaals aub.",
"error.http_not_authorized": "Toegang tot deze website is niet geautoriseerd. Het kan een foute gebruikersnaam of wachtwoord zijn.",
"error.http_resource_not_found": "De gevraagde bron is niet gevonden. Controleer de URL.",
"error.http_response_too_large": "De HTTP-respons is te groot. Je kunt de limiet voor de HTTP-responsgrootte verhogen in de globale instellingen (server herstart noodzakelijk)",
"error.http_service_unavailable": "De website is momenteel niet beschikbaar vanwege een interne-server-fout. De oorzaak hiervan ligt niet bij Miniflux. Probeer het later nogmaals aub.",
"error.http_too_many_requests": "Miniflux heeft te veel aanvragen gegenereerd voor deze website. Probeer het later nog eens of wijzig de applicatieconfiguratie.",
"error.http_unexpected_status_code": "De website is momenteel niet beschikbaar vanwege een onverwachte HTTP-statuscode: %d. De oorzaak hiervan ligt niet bij Miniflux. Probeer het later nogmaals aub.",
"error.invalid_categories_sorting_order": "Ongeldige volgorde van categorieën.",
"error.invalid_default_home_page": "Ongeldige startpagina!",
"error.invalid_display_mode": "Ongeldige weergavemodus voor de webapp.",
"error.invalid_entry_direction": "Ongeldige sorteervolgorde.",
"error.invalid_entry_order": "Ongeldige volgorde van artikelen.",
"error.invalid_feed_proxy_url": "Ongeldige proxy-URL.",
"error.invalid_feed_url": "Ongeldige feed URL.",
"error.invalid_gesture_nav": "Ongeldige gebarennavigatie.",
"error.invalid_language": "Ongeldige taal.",
"error.invalid_site_url": "Ongeldige site URL.",
"error.invalid_theme": "Ongeldig thema.",
"error.invalid_timezone": "Ongeldige tijdzone.",
"error.network_operation": "Miniflux kan deze website niet bereiken vanwege een netwerkfout: %v.",
"error.network_timeout": "Deze website is te traag en de aanvraag gaf timeout: %v",
"error.password_min_length": "Minimaal 6 tekens gebruiken.",
"error.proxy_url_not_empty": "De proxy-URL mag niet leeg zijn.",
"error.settings_block_rule_fieldname_invalid": "Ongeldige blokkeerregel: regel #%d mist een geldige veldnaam (Opties: %s)",
"error.settings_block_rule_invalid_regex": "Ongeldige blokkeerregel: het patroon van regel #%d is geen geldige regex",
"error.settings_block_rule_regex_required": "Ongeldige blokkeerregel: het patroon van regel #%d is niet opgegeven",
"error.settings_block_rule_separator_required": "Ongeldige blokkeerregel: het patroon van regel #%d moet worden gescheiden door een '='",
"error.settings_invalid_domain_list": "Ongeldige domeinlijst. Geef een spatiegescheiden lijst van domeinen op.",
"error.settings_keep_rule_fieldname_invalid": "Ongeldige bewaarregel: regel #%d mist een geldige veldnaam (Options: %s)",
"error.settings_keep_rule_invalid_regex": "Ongeldige bewaarregel: het patroon van regel #%d is geen geldige regex",
"error.settings_keep_rule_regex_required": "Ongeldige bewaarregel: het patroon van regel #%d is niet opgegeven",
"error.settings_keep_rule_separator_required": "Ongeldige bewaarregel: het patroon van regel #%d moet worden gescheiden door een '='",
"error.settings_mandatory_fields": "Gebruikersnaam, thema, taal en tijdzone zijn verplichte velden.",
"error.settings_media_playback_rate_range": "Afspeelsnelheid is buiten bereik",
"error.settings_reading_speed_is_positive": "De leessnelheden moeten positieve gehele getallen zijn.",
"error.site_url_not_empty": "De site URL mag niet leeg zijn.",
"error.subscription_not_found": "Kan geen feeds vinden.",
"error.title_required": "De titel is verplicht.",
"error.tls_error": "TLS fout: %q. Als je wilt, kun je TLS-verificatie uitschakelen in de feed-instellingen.",
"error.unable_to_create_api_key": "Kan deze API-sleutel niet aanmaken.",
"error.unable_to_create_category": "Kan deze categorie niet aanmaken.",
"error.unable_to_create_user": "Kan deze gebruiker niet aanmaken.",
"error.unable_to_detect_rssbridge": "Kan feed niet detecteren met RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Kan deze feed niet verwerken: %v.",
"error.unable_to_update_category": "Kan categorie niet bijwerken.",
"error.unable_to_update_feed": "Kan deze feed niet bijwerken.",
"error.unable_to_update_user": "Kan deze gebruiker niet bijwerken.",
"error.unlink_account_without_password": "Je moet een wachtwoord opgeven anders kun je niet meer inloggen.",
"error.user_already_exists": "Deze gebruiker bestaat al.",
"error.user_mandatory_fields": "Gebruikersnaam is verplicht",
"error.linktaco_missing_required_fields": "LinkTaco API Token en Organization Slug zijn verplicht",
"form.api_key.label.description": "API-sleutel omschrijving",
"form.category.hide_globally": "Verberg artikelen in de globale ongelezen lijst",
"form.category.label.title": "Titel",
"form.feed.fieldset.general": "Algemeen",
"form.feed.fieldset.integration": "Diensten van derden",
"form.feed.fieldset.network_settings": "Netwerk Instellingen",
"form.feed.fieldset.rules": "Regels",
"form.feed.label.allow_self_signed_certificates": "Zelfondertekende of ongeldige certificaten toestaan",
"form.feed.label.apprise_service_urls": "Door komma's gescheiden lijst van Apprise service URL's",
"form.feed.label.block_filter_entry_rules": "Blokkeerregels voor Items",
"form.feed.label.blocklist_rules": "Regex-gebaseerde Blokkeerfilters",
"form.feed.label.category": "Categorie",
"form.feed.label.cookie": "Cookies instellen",
"form.feed.label.crawler": "Download originele inhoud",
"form.feed.label.description": "Omschrijving",
"form.feed.label.disable_http2": "HTTP/2 uitschakelen om fingerprinting te voorkomen",
"form.feed.label.disabled": "Deze feed niet vernieuwen",
"form.feed.label.feed_password": "Feed wachtwoord",
"form.feed.label.feed_url": "Feed URL",
"form.feed.label.feed_username": "Feed gebruikersnaam",
"form.feed.label.fetch_via_proxy": "Gebruik de proxy die op applicatieniveau is geconfigureerd",
"form.feed.label.hide_globally": "Verberg artikelen in de globale ongelezen lijst",
"form.feed.label.ignore_http_cache": "Negeer HTTP-cache",
"form.feed.label.keep_filter_entry_rules": "Toestaan Regels voor Items",
"form.feed.label.keeplist_rules": "Regex-gebaseerde Bewaarfilters",
"form.feed.label.no_media_player": "Geen mediaspeler (audio/video)",
"form.feed.label.ntfy_activate": "Artikelen naar ntfy sturen",
"form.feed.label.ntfy_default_priority": "Ntfy standaard prioriteit",
"form.feed.label.ntfy_high_priority": "Ntfy hoge prioriteit",
"form.feed.label.ntfy_low_priority": "Ntfy lage prioriteit",
"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 onderwerp (optioneel)",
"form.feed.label.proxy_url": "Proxy URL",
"form.feed.label.pushover_activate": "Stuur artikelen naar pushover.net",
"form.feed.label.pushover_default_priority": "Pushover standaard prioriteit",
"form.feed.label.pushover_high_priority": "Pushover hoge prioriteit",
"form.feed.label.pushover_low_priority": "Pushover lage prioriteit",
"form.feed.label.pushover_max_priority": "Pushover maximale prioriteit",
"form.feed.label.pushover_min_priority": "Pushover minimale prioriteit",
"form.feed.label.pushover_priority": "Pushover berichtprioriteit",
"form.feed.label.rewrite_rules": "Inhoud Herschrijfregels",
"form.feed.label.scraper_rules": "Extractieregels",
"form.feed.label.site_url": "Website URL",
"form.feed.label.title": "Titel",
"form.feed.label.urlrewrite_rules": "Herschrijfregels voor URL's",
"form.feed.label.user_agent": "Standaard User-agent overschrijven",
"form.feed.label.webhook_url": "Overschrijf webhook URL",
"form.import.label.file": "OPML-bestand",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Artikelen opslaan in Apprise",
"form.integration.apprise_services_url": "Door komma's gescheiden lijst van Apprise service URL's",
"form.integration.apprise_url": "Apprise API URL",
"form.integration.betula_activate": "Artikelen opslaan in Betula",
"form.integration.betula_token": "Betula Token",
"form.integration.betula_url": "Betula server URL",
"form.integration.cubox_activate": "Artikelen opslaan in Cubox",
"form.integration.cubox_api_link": "Cubox API-link",
"form.integration.discord_activate": "Artikelen opslaan in Discord",
"form.integration.discord_webhook_link": "Discord Webhook link",
"form.integration.espial_activate": "Artikelen opslaan in Espial",
"form.integration.espial_api_key": "Espial API-sleutel",
"form.integration.espial_endpoint": "Espial URL",
"form.integration.espial_tags": "Espial tags",
"form.integration.fever_activate": "Activeer Fever API",
"form.integration.fever_endpoint": "Fever URL:",
"form.integration.fever_password": "Fever wachtwoord",
"form.integration.fever_username": "Fever gebruikersnaam",
"form.integration.googlereader_activate": "Activeer Google Reader API",
"form.integration.googlereader_endpoint": "Google Reader API-endpoint:",
"form.integration.googlereader_password": "Google Reader wachtwoord",
"form.integration.googlereader_username": "Google Reader gebruikersnaam",
"form.integration.instapaper_activate": "Artikelen opslaan in Instapaper",
"form.integration.instapaper_password": "Instapaper wachtwoord",
"form.integration.instapaper_username": "Instapaper gebruikersnaam",
"form.integration.karakeep_activate": "Artikelen opslaan in Karakeep",
"form.integration.karakeep_api_key": "Karakeep API-sleutel",
"form.integration.karakeep_url": "Karakeep URL",
"form.integration.linkace_activate": "Artikelen opslaan in LinkAce",
"form.integration.linkace_api_key": "LinkAce API-sleutel",
"form.integration.linkace_check_disabled": "Koppelingcontrole uitschakelen",
"form.integration.linkace_endpoint": "LinkAce API Endpoint",
"form.integration.linkace_is_private": "Koppeling als privé markeren",
"form.integration.linkace_tags": "LinkAce tags",
"form.integration.linkding_activate": "Artikelen opslaan in Linkding",
"form.integration.linkding_api_key": "Linkding API-sleutel",
"form.integration.linkding_bookmark": "Markeer favoriet als ongelezen",
"form.integration.linkding_endpoint": "Linkding URL",
"form.integration.linkding_tags": "Linkding tags",
"form.integration.linktaco_activate": "Artikelen opslaan in LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Verkrijg uw persoonlijke toegangstoken op",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Tags (max 10, kommagescheiden)",
"form.integration.linktaco_tags_hint": "Maximaal 10 tags, kommagescheiden",
"form.integration.linktaco_visibility": "Zichtbaarheid",
"form.integration.linktaco_visibility_public": "Openbaar",
"form.integration.linktaco_visibility_private": "Privé",
"form.integration.linktaco_visibility_hint": "PRIVÉ zichtbaarheid vereist een betaald LinkTaco account",
"form.integration.linkwarden_activate": "Artikelen opslaan in Linkwarden",
"form.integration.linkwarden_api_key": "Linkwarden API-sleutel",
"form.integration.linkwarden_endpoint": "Linkwarden Basis URL",
"form.integration.matrix_bot_activate": "Nieuwe artikelen opslaan in Matrix",
"form.integration.matrix_bot_chat_id": "ID van Matrix-kamer",
"form.integration.matrix_bot_password": "Wachtwoord voor Matrix-gebruiker",
"form.integration.matrix_bot_url": "URL van de Matrix-server",
"form.integration.matrix_bot_user": "Matrix gebruikersnaam",
"form.integration.notion_activate": "Artikelen opslaan in Notion",
"form.integration.notion_page_id": "Notion Page ID",
"form.integration.notion_token": "Notion Secret Token",
"form.integration.ntfy_activate": "Stuur artikelen naar ntfy",
"form.integration.ntfy_api_token": "Ntfy API Token (optioneel)",
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optioneel)",
"form.integration.ntfy_internal_links": "Gebruik interne links bij klikken (optioneel)",
"form.integration.ntfy_password": "Ntfy wachtwoord (optioneel)",
"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",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API-sleutel",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper URL",
"form.integration.omnivore_activate": "Artikelen opslaan in Omnivore",
"form.integration.omnivore_api_key": "Omnivore API-sleutel",
"form.integration.omnivore_url": "Omnivore URL",
"form.integration.pinboard_activate": "Artikelen opslaan in Pinboard",
"form.integration.pinboard_bookmark": "Markeer favoriet als ongelezen",
"form.integration.pinboard_tags": "Pinboard tags",
"form.integration.pinboard_token": "Pinboard API token",
"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.raindrop_activate": "Artikelen opslaan in Raindrop",
"form.integration.raindrop_collection_id": "Collectie ID",
"form.integration.raindrop_tags": "Tags (commagescheiden)",
"form.integration.raindrop_token": "Raindrop Token",
"form.integration.readeck_activate": "Artikelen opslaan in Readeck",
"form.integration.readeck_api_key": "Readeck API-sleutel",
"form.integration.readeck_endpoint": "Readeck URL",
"form.integration.readeck_labels": "Readeck Labels",
"form.integration.readeck_only_url": "Alleen URL verzenden (in plaats van volledige inhoud)",
"form.integration.readwise_activate": "Artikelen opslaan in Readwise Reader",
"form.integration.readwise_api_key": "Readwise Reader Access Token",
"form.integration.readwise_api_key_link": "Readwise Access Token ophalen",
"form.integration.rssbridge_activate": "Controleer RSS-Bridge bij het toevoegen van abonnementen",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "RSS-Bridge server URL",
"form.integration.shaarli_activate": "Artikelen opslaan in Shaarli",
"form.integration.shaarli_api_secret": "Shaarli API Secret",
"form.integration.shaarli_endpoint": "Shaarli URL",
"form.integration.shiori_activate": "Artikelen opslaan in Shiori",
"form.integration.shiori_endpoint": "Shiori URL",
"form.integration.shiori_password": "Shiori wachtwoord",
"form.integration.shiori_username": "Shiori gebruikersnaam",
"form.integration.slack_activate": "Artikelen opslaan in Slack",
"form.integration.slack_webhook_link": "Slack Webhook link",
"form.integration.telegram_bot_activate": "Stuur nieuwe artikelen naar Telegram",
"form.integration.telegram_bot_disable_buttons": "Knoppen uitschakelen",
"form.integration.telegram_bot_disable_notification": "Notificatie uitschakelen",
"form.integration.telegram_bot_disable_web_page_preview": "Webpaginavoorbeeld uitschakelen",
"form.integration.telegram_bot_token": "Bot token",
"form.integration.telegram_chat_id": "Chat ID",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "Artikelen opslaan in Wallabag",
"form.integration.wallabag_client_id": "Wallabag Client-ID",
"form.integration.wallabag_client_secret": "Wallabag Client-Secret",
"form.integration.wallabag_endpoint": "Wallabag basis-URL",
"form.integration.wallabag_only_url": "Alleen URL verzenden (in plaats van volledige inhoud)",
"form.integration.wallabag_password": "Wallabag wachtwoord",
"form.integration.wallabag_username": "Wallabag gebruikersnaam",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Webhooks activeren",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Standard Webhook URL",
"form.prefs.fieldset.application_settings": "Applicatie Instellingen",
"form.prefs.fieldset.authentication_settings": "Authenticatie Instellingen",
"form.prefs.fieldset.global_feed_settings": "Globale Feed Instellingen",
"form.prefs.fieldset.reader_settings": "Lees Instellingen",
"form.prefs.help.external_font_hosts": "Spatiegescheiden lijst van externe font-hosts die zijn toegestaan. Bijvoorbeeld: 'fonts.gstatic.com fonts.googleapis.com'.",
"form.prefs.label.always_open_external_links": "Lees artikelen door externe links te openen",
"form.prefs.label.categories_sorting_order": "Volgorde categorieën",
"form.prefs.label.cjk_reading_speed": "Leessnelheid voor Chinees, Koreaans en Japans (tekens per minuut)",
"form.prefs.label.custom_css": "Aangepaste CSS",
"form.prefs.label.custom_js": "Aangepaste JavaScript",
"form.prefs.label.default_home_page": "Startpagina",
"form.prefs.label.default_reading_speed": "Leessnelheid voor andere talen (woorden per minuut)",
"form.prefs.label.display_mode": "Weergavemodus Progressive Web App (PWA).",
"form.prefs.label.entries_per_page": "Artikelen per pagina",
"form.prefs.label.entry_order": "Artikelen sorteren",
"form.prefs.label.entry_sorting": "Volgorde van artikelen",
"form.prefs.label.entry_swipe": "Vegen tussen artikelen inschakelen op aanraakschermen",
"form.prefs.label.external_font_hosts": "Externe font-hosts",
"form.prefs.label.gesture_nav": "Gebaar om tussen artikelen te navigeren",
"form.prefs.label.keyboard_shortcuts": "Sneltoetsen inschakelen",
"form.prefs.label.language": "Taal",
"form.prefs.label.mark_read_manually": "Markeer artikelen handmatig als gelezen",
"form.prefs.label.mark_read_on_media_completion": "Markeer artikelen alleen als gelezen wanneer het afspelen van audio/video 90%% heeft bereikt",
"form.prefs.label.mark_read_on_view": "Markeer artikelen automatisch als gelezen wanneer ze worden bekeken",
"form.prefs.label.mark_read_on_view_or_media_completion": "Markeer artikelen als gelezen wanneer ze worden bekeken. Voor audio/video, markeer als gelezen bij 90%% voltooiing",
"form.prefs.label.media_playback_rate": "Afspeelsnelheid van de audio/video",
"form.prefs.label.open_external_links_in_new_tab": "Open externe links in een nieuw tabblad (voegt target=\"_blank\" toe aan links)",
"form.prefs.label.show_reading_time": "Toon geschatte leestijd van artikelen",
"form.prefs.label.theme": "Thema",
"form.prefs.label.timezone": "Tijdzone",
"form.prefs.select.alphabetical": "Alfabetisch",
"form.prefs.select.browser": "Browser",
"form.prefs.select.created_time": "Tijdstip van aanmaken artikel",
"form.prefs.select.fullscreen": "Volledig scherm",
"form.prefs.select.minimal_ui": "Minimaal",
"form.prefs.select.none": "Geen",
"form.prefs.select.older_first": "Oudere artikelen eerst",
"form.prefs.select.publish_time": "Tijdstip van publiceren artikel",
"form.prefs.select.recent_first": "Recente artikelen eerst",
"form.prefs.select.standalone": "Standalone",
"form.prefs.select.swipe": "Vegen",
"form.prefs.select.tap": "Dubbeltik",
"form.prefs.select.unread_count": "Aantal ongelezen artikelen",
"form.submit.loading": "Laden...",
"form.submit.saving": "Opslaan...",
"form.user.label.admin": "Beheerder",
"form.user.label.confirmation": "Bevestig wachtwoord",
"form.user.label.password": "Wachtwoord",
"form.user.label.username": "Gebruikersnaam",
"menu.about": "Over",
"menu.add_feed": "Feed toevoegen",
"menu.add_user": "Gebruiker toevoegen",
"menu.api_keys": "API-sleutels",
"menu.categories": "Categorieën",
"menu.create_api_key": "Maak een nieuwe API-sleutel",
"menu.create_category": "Categorie toevoegen",
"menu.edit_category": "Bewerken",
"menu.edit_feed": "Bewerken",
"menu.export": "Exporteren",
"menu.feed_entries": "Artikelen",
"menu.feeds": "Feeds",
"menu.flush_history": "Verwijder geschiedenis",
"menu.history": "Geschiedenis",
"menu.home_page": "Startpagina",
"menu.import": "Importeren",
"menu.integrations": "Integraties",
"menu.logout": "Uitloggen",
"menu.mark_all_as_read": "Markeer alles als gelezen",
"menu.mark_page_as_read": "Markeer deze pagina als gelezen",
"menu.preferences": "Voorkeuren",
"menu.refresh_all_feeds": "Vernieuw alle feeds in de achtergrond",
"menu.refresh_feed": "Vernieuwen",
"menu.search": "Zoeken",
"menu.sessions": "Sessies",
"menu.settings": "Instellingen",
"menu.shared_entries": "Gedeelde artikelen",
"menu.show_all_entries": "Toon alle artikelen",
"menu.show_only_starred_entries": "Toon alleen favorieten",
"menu.show_only_unread_entries": "Toon alleen ongelezen artikelen",
"menu.starred": "Favorieten",
"menu.title": "Menu",
"menu.unread": "Ongelezen",
"menu.users": "Gebruikers",
"page.about.author": "Auteur:",
"page.about.build_date": "Compilatiedatum:",
"page.about.credits": "Credits",
"page.about.db_usage": "Databasegrootte:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "Globale Configuratie Opties",
"page.about.go_version": "Go versie:",
"page.about.license": "Licentie:",
"page.about.postgres_version": "Postgres versie:",
"page.about.title": "Over",
"page.about.version": "Versie:",
"page.add_feed.choose_feed": "Feed kiezen",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Geavanceerde opties",
"page.add_feed.no_category": "Er is geen categorie. Je moet minstens één categorie hebben.",
"page.add_feed.submit": "Feed zoeken",
"page.add_feed.title": "Nieuwe feed",
"page.api_keys.never_used": "Nooit gebruikt",
"page.api_keys.table.actions": "Acties",
"page.api_keys.table.created_at": "Aanmaakdatum",
"page.api_keys.table.description": "Omschrijving",
"page.api_keys.table.last_used_at": "Laatst gebruikt",
"page.api_keys.table.token": "Token",
"page.api_keys.title": "API-sleutels",
"page.categories.entries": "Artikelen",
"page.categories.feed_count": [
"Er is %d feed.",
"Er zijn %d feeds."
],
"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:",
"page.edit_feed.last_check": "Laatste controle:",
"page.edit_feed.last_modified_header": "LastModified header:",
"page.edit_feed.last_parsing_error": "Laatste analysefout",
"page.edit_feed.no_header": "Geen",
"page.edit_feed.title": "Bewerk feed: %s",
"page.edit_user.title": "Bewerk gebruiker: %s",
"page.entry.attachments": "Bijlagen",
"page.feeds.error_count": [
"%d fout",
"%d fouten"
],
"page.feeds.last_check": "Laatste controle:",
"page.feeds.next_check": "Volgende controle:",
"page.feeds.read_counter": "Aantal gelezen artikelen",
"page.feeds.title": "Feeds",
"page.footer.elevator": "Back to top",
"page.history.title": "Geschiedenis",
"page.import.title": "Importeren",
"page.integration.bookmarklet": "Bookmarklet",
"page.integration.bookmarklet.help": "Gebruik deze link als bookmark in je browser om je direct te abonneren op een website.",
"page.integration.bookmarklet.instructions": "Sleep deze link naar je bookmarks.",
"page.integration.bookmarklet.name": "Toevoegen aan Miniflux",
"page.integration.miniflux_api": "Miniflux API",
"page.integration.miniflux_api_endpoint": "API-URL",
"page.integration.miniflux_api_password": "Wachtwoord",
"page.integration.miniflux_api_password_value": "Wachtwoord van jouw account",
"page.integration.miniflux_api_username": "Gebruikersnaam",
"page.integrations.title": "Integraties",
"page.keyboard_shortcuts.close_modal": "Dialoogvenster sluiten",
"page.keyboard_shortcuts.download_content": "Download originele inhoud",
"page.keyboard_shortcuts.go_to_bottom_item": "Ga naar het onderste artikel",
"page.keyboard_shortcuts.go_to_categories": "Ga naar categorieën",
"page.keyboard_shortcuts.go_to_feed": "Ga naar feed",
"page.keyboard_shortcuts.go_to_feeds": "Ga naar feeds",
"page.keyboard_shortcuts.go_to_history": "Ga naar geschiedenis",
"page.keyboard_shortcuts.go_to_next_item": "Volgend artikel",
"page.keyboard_shortcuts.go_to_next_page": "Volgende pagina",
"page.keyboard_shortcuts.go_to_previous_item": "Vorig artikel",
"page.keyboard_shortcuts.go_to_previous_page": "Vorige pagina",
"page.keyboard_shortcuts.go_to_search": "Focus instellen op zoekformulier",
"page.keyboard_shortcuts.go_to_settings": "Ga naar instellingen",
"page.keyboard_shortcuts.go_to_starred": "Ga naar favorieten",
"page.keyboard_shortcuts.go_to_top_item": "Ga naar het bovenste artikel",
"page.keyboard_shortcuts.go_to_unread": "Ga naar ongelezen",
"page.keyboard_shortcuts.mark_page_as_read": "Markeer huidige pagina als gelezen",
"page.keyboard_shortcuts.open_comments": "Open reacties",
"page.keyboard_shortcuts.open_comments_same_window": "Open reacties in huidig tabblad",
"page.keyboard_shortcuts.open_item": "Open geselecteerd artikel",
"page.keyboard_shortcuts.open_original": "Open originele link",
"page.keyboard_shortcuts.open_original_same_window": "Open originele link in huidig tabblad",
"page.keyboard_shortcuts.refresh_all_feeds": "Vernieuw alle feeds in de achtergrond",
"page.keyboard_shortcuts.remove_feed": "Verwijder deze feed",
"page.keyboard_shortcuts.save_article": "Artikel opslaan",
"page.keyboard_shortcuts.scroll_item_to_top": "Scroll artikel naar boven",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Sneltoetsen tonen",
"page.keyboard_shortcuts.subtitle.actions": "Acties",
"page.keyboard_shortcuts.subtitle.items": "Navigeren door artikelen",
"page.keyboard_shortcuts.subtitle.pages": "Navigeren door pagina's",
"page.keyboard_shortcuts.subtitle.sections": "Navigeren door menu's",
"page.keyboard_shortcuts.title": "Sneltoetsen",
"page.keyboard_shortcuts.toggle_star_status": "Favoriet toevoegen/verwijderen",
"page.keyboard_shortcuts.toggle_entry_attachments": "Bijlagen van artikel openen/sluiten",
"page.keyboard_shortcuts.toggle_read_status_next": "Markeer gelezen/ongelezen, focus volgende",
"page.keyboard_shortcuts.toggle_read_status_prev": "Markeer gelezen/ongelezen, focus vorige",
"page.login.google_signin": "Inloggen met Google",
"page.login.oidc_signin": "Inloggen met %s",
"page.login.title": "Inloggen",
"page.login.webauthn_login": "Inloggen met passkey",
"page.login.webauthn_login.error": "Kan niet inloggen met passkey",
"page.login.webauthn_login.help": "Voer je gebruikersnaam in als je een beveiligingssleutel gebruikt. Dit is niet nodig als je een Passkey (ontdekkingsbare referenties) gebruikt.",
"page.new_api_key.title": "Nieuwe API-sleutel",
"page.new_category.title": "Nieuwe categorie",
"page.new_user.title": "Nieuwe gebruiker",
"page.offline.message": "Je bent offline",
"page.offline.refresh_page": "Probeer de pagina te vernieuwen",
"page.offline.title": "Offline modus",
"page.read_entry_count": [
"%d gelezen artikel",
"%d gelezen artikelen"
],
"page.search.title": "Zoekresultaten",
"page.sessions.table.actions": "Acties",
"page.sessions.table.current_session": "Huidige sessie",
"page.sessions.table.date": "Datum",
"page.sessions.table.ip": "IP-adres",
"page.sessions.table.user_agent": "User-agent",
"page.sessions.title": "Sessies",
"page.settings.link_google_account": "Koppel mijn Google-account",
"page.settings.link_oidc_account": "Koppel mijn %s account",
"page.settings.title": "Instellingen",
"page.settings.unlink_google_account": "Ontkoppel mijn Google-account",
"page.settings.unlink_oidc_account": "Ontkoppel mijn %s account",
"page.settings.webauthn.actions": "Acties",
"page.settings.webauthn.added_on": "Toegevoegd op",
"page.settings.webauthn.delete": [
"Verwijder %d passkey",
"Verwijder %d passkeys"
],
"page.settings.webauthn.last_seen_on": "Laatst gebruikt",
"page.settings.webauthn.passkey_name": "Passkey Naam",
"page.settings.webauthn.passkeys": "Passkeys",
"page.settings.webauthn.register": "Passkey registreren",
"page.settings.webauthn.register.error": "Kan passkey niet registreren",
"page.shared_entries.title": "Gedeelde artikelen",
"page.shared_entries_count": [
"%d gedeeld artikel",
"%d gedeelde artikelen"
],
"page.starred.title": "Favorieten",
"page.starred_entry_count": [
"%d favoriet artikel",
"%d favoriete artikelen"
],
"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.users.actions": "Acties",
"page.users.admin.no": "Nee",
"page.users.admin.yes": "Ja",
"page.users.is_admin": "Beheerder",
"page.users.last_login": "Laatste login",
"page.users.never_logged": "Nooit",
"page.users.title": "Gebruikers",
"page.users.username": "Gebruikersnaam",
"page.webauthn_rename.title": "Hernoem Passkey",
"pagination.first": "Eerste",
"pagination.last": "Laatste",
"pagination.next": "Volgende",
"pagination.previous": "Vorige",
"search.label": "Zoeken",
"search.placeholder": "Zoeken...",
"search.submit": "Zoeken",
"skip_to_content": "Ga naar inhoud",
"time_elapsed.days": [
"%d dag geleden",
"%d dagen geleden"
],
"time_elapsed.hours": [
"%d uur geleden",
"%d uur geleden"
],
"time_elapsed.minutes": [
"%d minuut geleden",
"%d minuten geleden"
],
"time_elapsed.months": [
"%d maand geleden",
"%d maanden geleden"
],
"time_elapsed.not_yet": "nog niet",
"time_elapsed.now": "minder dan een minuut geleden",
"time_elapsed.weeks": [
"%d week geleden",
"%d weken geleden"
],
"time_elapsed.years": [
"%d jaar geleden",
"%d jaar geleden"
],
"time_elapsed.yesterday": "gisteren",
"tooltip.keyboard_shortcuts": "Sneltoets: %s",
"tooltip.logged_user": "Ingelogd als %s"
} v2-2.2.13/internal/locale/translations/pl_PL.json 0000664 0000000 0000000 00000117351 15062123773 0021641 0 ustar 00root root 0000000 0000000 {
"action.cancel": "anuluj",
"action.download": "Pobierz",
"action.edit": "Edytuj",
"action.home_screen": "Dodaj do ekranu głównego",
"action.import": "Importuj",
"action.login": "Zaloguj się",
"action.or": "lub",
"action.remove": "Usuń",
"action.remove_feed": "Usuń ten kanał",
"action.save": "Zapisz",
"action.subscribe": "Subskrypcja",
"action.update": "Zaktualizuj",
"alert.account_linked": "Twoje konto zewnętrzne jest teraz połączone!",
"alert.account_unlinked": "Twoje konto zewnętrzne jest teraz zdysocjowane!",
"alert.background_feed_refresh": "Wszystkie kanały są odświeżane w tle. Możesz kontynuować korzystanie z Miniflux podczas trwania tego procesu.",
"alert.feed_error": "Z tym kanałem jest problem",
"alert.no_starred": "Brak ulubionych w tej chwili.",
"alert.no_category": "Brak kategorii!",
"alert.no_category_entry": "Brak wpisów w tej kategorii",
"alert.no_feed": "Nie masz żadnej subskrypcji.",
"alert.no_feed_entry": "Brak wpisów tego kanału.",
"alert.no_feed_in_category": "Nie ma subskrypcji tej kategorii.",
"alert.no_history": "Obecnie nie ma żadnej historii.",
"alert.no_search_result": "Brak wyników tego wyszukiwania.",
"alert.no_shared_entry": "Brak udostępnionego wpisu.",
"alert.no_tag_entry": "Brak wpisów pasujących do tego znacznika.",
"alert.no_unread_entry": "Nie ma żadnych nieprzeczytanych wpisów.",
"alert.no_user": "Jesteś jedynym użytkownikiem.",
"alert.prefs_saved": "Ustawienia zapisane!",
"alert.too_many_feeds_refresh": [
"Wykonano zbyt wiele odświeżeń kanału. Poczekaj %d minutę przed ponowną próbą.",
"Wykonano zbyt wiele odświeżeń kanału. Poczekaj %d minuty przed ponowną próbą.",
"Wykonano zbyt wiele odświeżeń kanału. Poczekaj %d minut przed ponowną próbą."
],
"confirm.loading": "W toku…",
"confirm.no": "nie",
"confirm.question": "Czy na pewno?",
"confirm.question.refresh": "Czy na pewno chcesz wymusić odświeżenie?",
"confirm.yes": "tak",
"enclosure_media_controls.seek": "Przewiń:",
"enclosure_media_controls.seek.title": "Przewiń o %s sek.",
"enclosure_media_controls.speed": "Szybkość:",
"enclosure_media_controls.speed.faster": "Szybciej",
"enclosure_media_controls.speed.faster.title": "Szybciej o %sx",
"enclosure_media_controls.speed.reset": "Przywróć",
"enclosure_media_controls.speed.reset.title": "Przywróć szybkość do 1x",
"enclosure_media_controls.speed.slower": "Wolniej",
"enclosure_media_controls.speed.slower.title": "Wolniej o %sx",
"entry.starred.toast.off": "Usunięto z ulubionych",
"entry.starred.toast.on": "Dodano do ulubionych",
"entry.starred.toggle.off": "Usuń z ulubionych",
"entry.starred.toggle.on": "Dodaj do ulubionych",
"entry.comments.label": "Komentarze",
"entry.comments.title": "Zobacz komentarze",
"entry.estimated_reading_time": [
"%d minuta czytania",
"%d minuty czytania",
"%d minut czytania"
],
"entry.external_link.label": "Łącze zewnętrzne",
"entry.save.completed": "Gotowe!",
"entry.save.label": "Zapisz",
"entry.save.title": "Zapisz ten wpis",
"entry.save.toast.completed": "Zapisano wpis",
"entry.scraper.completed": "Gotowe!",
"entry.scraper.label": "Pobierz treść",
"entry.scraper.title": "Pobierz oryginalną treść",
"entry.share.label": "Udostępnij",
"entry.share.title": "Udostępnij ten wpis",
"entry.shared_entry.label": "Udostępnij",
"entry.shared_entry.title": "Otwórz publiczne łącze",
"entry.state.loading": "Ładowanie…",
"entry.state.saving": "Zapisywanie…",
"entry.status.mark_as_read": "Oznacz jako przeczytany",
"entry.status.mark_as_unread": "Oznacz jako nieprzeczytany",
"entry.status.title": "Zmień status wpisu",
"entry.status.toast.read": "Oznaczono jako przeczytany",
"entry.status.toast.unread": "Oznaczono jako nieprzeczytany",
"entry.tags.label": "Znaczniki:",
"entry.tags.more_tags_label": [
"Dodaj znacznik",
"Dodaj %d znaczniki",
"Dodaj %d znaczników"
],
"entry.unshare.label": "Cofnij udostępnianie",
"error.api_key_already_exists": "Ten klucz API już istnieje.",
"error.bad_credentials": "Nieprawidłowa nazwa użytkownika lub hasło.",
"error.category_already_exists": "Ta kategoria już istnieje.",
"error.category_not_found": "Ta kategoria nie istnieje lub nie należy do tego użytkownika.",
"error.database_error": "Błąd bazy danych: %v.",
"error.different_passwords": "Hasła nie są identyczne.",
"error.duplicate_fever_username": "Już ktoś inny używa tej nazwy użytkownika Fever!",
"error.duplicate_googlereader_username": "Istnieje już ktoś inny z tą samą nazwą użytkownika Google Reader!",
"error.duplicate_linked_account": "Już ktoś jest powiązany z tym dostawcą!",
"error.duplicated_feed": "Ten kanał już istnieje.",
"error.empty_file": "Ten plik jest pusty.",
"error.entries_per_page_invalid": "Liczba wpisów na stronę jest nieprawidłowa.",
"error.feed_already_exists": "Ten kanał już istnieje.",
"error.feed_category_not_found": "Ta kategoria nie istnieje lub nie należy do tego użytkownika.",
"error.feed_format_not_detected": "Nie można wykryć formatu kanału: %v.",
"error.feed_invalid_blocklist_rule": "Reguła listy zablokowanych jest nieprawidłowa.",
"error.feed_invalid_keeplist_rule": "Reguła listy zachowywania jest nieprawidłowa.",
"error.feed_mandatory_fields": "Adres URL i kategoria są obowiązkowe.",
"error.feed_not_found": "Ten kanał nie istnieje lub nie należy do tego użytkownika.",
"error.feed_title_not_empty": "Tytuł kanału nie może być pusty.",
"error.feed_url_not_empty": "Adres URL kanału nie może być pusty.",
"error.fields_mandatory": "Wszystkie pola są obowiązkowe.",
"error.http_bad_gateway": "Strona jest w tej chwili niedostępna z powodu błędu nieprawidłowej bramy. Problem nie leży po stronie Miniflux. Spróbuj ponownie później.",
"error.http_body_read": "Nie można odczytać treści HTTP: %v.",
"error.http_client_error": "Błąd klienta HTTP: %v.",
"error.http_empty_response": "Odpowiedź HTTP jest pusta. Być może ta witryna korzysta z mechanizmu ochrony przed botami?",
"error.http_empty_response_body": "Treść odpowiedzi HTTP jest pusta.",
"error.http_forbidden": "Dostęp do tej strony jest zabroniony. Być może ta strona ma mechanizm zabezpieczający przed botami?",
"error.http_gateway_timeout": "Strona internetowa jest w tej chwili niedostępna z powodu błędu przekroczenia limitu czasu bramy. Problem nie leży po stronie Miniflux. Spróbuj ponownie później.",
"error.http_internal_server_error": "Strona jest w tej chwili niedostępna z powodu błędu serwera. Problem nie leży po stronie Miniflux. Spróbuj ponownie później.",
"error.http_not_authorized": "Dostęp do tej witryny nie jest autoryzowany. Może to być błędna nazwa użytkownika lub hasło.",
"error.http_resource_not_found": "Nie znaleziono żądanego zasobu. Sprawdź adres URL.",
"error.http_response_too_large": "Odpowiedź HTTP jest za duża. Możesz zwiększyć limit rozmiaru odpowiedzi HTTP w ustawieniach globalnych (wymaga ponownego uruchomienia serwera).",
"error.http_service_unavailable": "Strona jest w tej chwili niedostępna z powodu wewnętrznego błędu serwera. Problem nie leży po stronie Miniflux. Spróbuj ponownie później.",
"error.http_too_many_requests": "Miniflux wygenerował zbyt wiele żądań do tej witryny. Spróbuj ponownie później lub zmień konfigurację aplikacji.",
"error.http_unexpected_status_code": "Strona jest w tej chwili niedostępna z powodu nieoczekiwanego kodu stanu HTTP: %d. Problem nie leży po stronie Miniflux. Spróbuj ponownie później.",
"error.invalid_categories_sorting_order": "Nieprawidłowa kolejność sortowania kategorii.",
"error.invalid_default_home_page": "Nieprawidłowa domyślna strona główna!",
"error.invalid_display_mode": "Nieprawidłowy tryb wyświetlania aplikacji sieciowej.",
"error.invalid_entry_direction": "Nieprawidłowa kolejność sortowania.",
"error.invalid_entry_order": "Nieprawidłowa kolejność sortowania wpisów.",
"error.invalid_feed_proxy_url": "Nieprawidłowy adres URL serwera proxy.",
"error.invalid_feed_url": "Nieprawidłowy adres URL kanału.",
"error.invalid_gesture_nav": "Nieprawidłowa nawigacja gestami.",
"error.invalid_language": "Nieprawidłowy język.",
"error.invalid_site_url": "Nieprawidłowy adres URL witryny.",
"error.invalid_theme": "Nieprawidłowy motyw.",
"error.invalid_timezone": "Nieprawidłowa strefa czasowa.",
"error.network_operation": "Miniflux nie może połączyć się z tą witryną z powodu błędu sieci: %v.",
"error.network_timeout": "Ta witryna internetowa jest zbyt wolna i upłynął limit czasu żądania: %v",
"error.password_min_length": "Musisz użyć co najmniej 6 znaków.",
"error.proxy_url_not_empty": "Adres URL serwera proxy nie może być pusty.",
"error.settings_block_rule_fieldname_invalid": "Nieprawidłowa reguła blokowania: w regule #%d brakuje prawidłowej nazwy pola (opcje: %s)",
"error.settings_block_rule_invalid_regex": "Nieprawidłowa reguła blokowania: wzór reguły #%d nie jest prawidłowym wyrażeniem regularnym",
"error.settings_block_rule_regex_required": "Nieprawidłowa reguła blokowania: nie podano wzorca reguły #%d",
"error.settings_block_rule_separator_required": "Nieprawidłowa reguła blokowania: wzór reguły #%d musi być oddzielony znakiem '='",
"error.settings_invalid_domain_list": "Nieprawidłowa lista domen. Podaj listę domen rozdzielonych spacjami.",
"error.settings_keep_rule_fieldname_invalid": "Nieprawidłowa reguła utrzymywania: w regule #%d brakuje prawidłowej nazwy pola (opcje: %s)",
"error.settings_keep_rule_invalid_regex": "Nieprawidłowa reguła utrzymywania: wzór reguły #%d nie jest prawidłowym wyrażeniem regularnym",
"error.settings_keep_rule_regex_required": "Nieprawidłowa reguła utrzymywania nie podano wzorca reguły #%d",
"error.settings_keep_rule_separator_required": "Nieprawidłowa reguła utrzymywania: wzór reguły #%d musi być oddzielony znakiem '='",
"error.settings_mandatory_fields": "Pola nazwy użytkownika, tematu, języka i strefy czasowej są obowiązkowe.",
"error.settings_media_playback_rate_range": "Szybkość odtwarzania jest poza zakresem",
"error.settings_reading_speed_is_positive": "Szybkości czytania muszą być dodatnimi liczbami całkowitymi.",
"error.site_url_not_empty": "Adres URL witryny nie może być pusty.",
"error.subscription_not_found": "Nie znaleziono żadnych kanałów.",
"error.title_required": "Tytuł jest obowiązkowy.",
"error.tls_error": "Błąd TLS: %q. Jeśli chcesz, możesz wyłączyć weryfikację TLS w ustawieniach kanału.",
"error.unable_to_create_api_key": "Nie można utworzyć tego klucza API.",
"error.unable_to_create_category": "Ta kategoria nie mogła zostać utworzona.",
"error.unable_to_create_user": "Nie można utworzyć tego użytkownika.",
"error.unable_to_detect_rssbridge": "Nie można wykryć kanału za pomocą RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Nie można przeanalizować tego kanału: %v.",
"error.unable_to_update_category": "Ta kategoria nie mogła zostać zaktualizowana.",
"error.unable_to_update_feed": "Nie można zaktualizować tego kanału.",
"error.unable_to_update_user": "Nie można zaktualizować tego użytkownika.",
"error.unlink_account_without_password": "Musisz zdefiniować hasło, inaczej nie będziesz mógł się ponownie zalogować.",
"error.user_already_exists": "Ten użytkownik już istnieje.",
"error.user_mandatory_fields": "Nazwa użytkownika jest obowiązkowa.",
"error.linktaco_missing_required_fields": "Token API LinkTaco i ślimak organizacji są wymagane",
"form.api_key.label.description": "Etykieta klucza API",
"form.category.hide_globally": "Ukryj wpisy na globalnej liście nieprzeczytanych",
"form.category.label.title": "Tytuł",
"form.feed.fieldset.general": "Ogólne",
"form.feed.fieldset.integration": "Usługi dostawców zewnętrznych",
"form.feed.fieldset.network_settings": "Ustawienia sieci",
"form.feed.fieldset.rules": "Reguły",
"form.feed.label.allow_self_signed_certificates": "Zezwalaj na samopodpisane lub nieprawidłowe certyfikaty",
"form.feed.label.apprise_service_urls": "Rozdzielana przecinkami lista adresów URL usług Appprise",
"form.feed.label.block_filter_entry_rules": "Reguły blokowania wpisów",
"form.feed.label.blocklist_rules": "Filtry blokowania oparte na wyrażeniach regularnych",
"form.feed.label.category": "Kategoria",
"form.feed.label.cookie": "Ustaw ciasteczka",
"form.feed.label.crawler": "Pobierz oryginalną treść",
"form.feed.label.description": "Opis",
"form.feed.label.disable_http2": "Wyłącz protokół HTTP/2, aby uniknąć identyfikowania",
"form.feed.label.disabled": "Nie aktualizuj tego kanału",
"form.feed.label.feed_password": "Hasło do subskrypcji",
"form.feed.label.feed_url": "Adres URL kanału",
"form.feed.label.feed_username": "Nazwa użytkownika subskrypcji",
"form.feed.label.fetch_via_proxy": "Użyj serwera proxy skonfigurowanego na poziomie aplikacji",
"form.feed.label.hide_globally": "Ukryj wpisy na globalnej liście nieprzeczytanych",
"form.feed.label.ignore_http_cache": "Zignoruj pamięć podręczną HTTP",
"form.feed.label.keep_filter_entry_rules": "Reguły zachowywania wpisów",
"form.feed.label.keeplist_rules": "Filtry zachowywania oparte na wyrażeniach regularnych",
"form.feed.label.no_media_player": "Brak odtwarzacza multimedialnego (audio i wideo)",
"form.feed.label.ntfy_activate": "Prześlij wpisy do ntfy",
"form.feed.label.ntfy_default_priority": "Domyślny priorytet ntfy",
"form.feed.label.ntfy_high_priority": "Wysoki priorytet ntfy",
"form.feed.label.ntfy_low_priority": "Niski priorytet ntfy",
"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.ntfy_topic": "Temat ntfy (opcjonalny)",
"form.feed.label.proxy_url": "Adres URL serwera proxy",
"form.feed.label.pushover_activate": "Prześlij wpisy do pushover.net",
"form.feed.label.pushover_default_priority": "Domyślny priorytet Pushover",
"form.feed.label.pushover_high_priority": "Wysoki priorytet Pushover",
"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 przepisywania treści",
"form.feed.label.scraper_rules": "Reguły ekstrakcji",
"form.feed.label.site_url": "Adres URL strony",
"form.feed.label.title": "Tytuł",
"form.feed.label.urlrewrite_rules": "Reguły przepisywania adresów URL",
"form.feed.label.user_agent": "Zastąp domyślny agent użytkownika",
"form.feed.label.webhook_url": "Zastąp adres URL webhooka",
"form.import.label.file": "Plik OPML",
"form.import.label.url": "Adres URL",
"form.integration.apprise_activate": "Przesyłaj wpisy do Apprise",
"form.integration.apprise_services_url": "Oddzielona przecinkami lista adresów URL usługi Apprise",
"form.integration.apprise_url": "Adres URL API Apprise",
"form.integration.betula_activate": "Zapisuj wpisy w Betula",
"form.integration.betula_token": "Token do Betula",
"form.integration.betula_url": "Adres URL serwera Betula",
"form.integration.cubox_activate": "Zapisuj wpisy w Cubox",
"form.integration.cubox_api_link": "Łącze API Cubox",
"form.integration.discord_activate": "Przesyłaj wpisy do Discord",
"form.integration.discord_webhook_link": "Adres URL webhooka Discord",
"form.integration.espial_activate": "Zapisuj wpisy w Espial",
"form.integration.espial_api_key": "Klucz API do Espial",
"form.integration.espial_endpoint": "Punkt końcowy API Espial",
"form.integration.espial_tags": "Znaczniki Espial",
"form.integration.fever_activate": "Aktywuj API Fever",
"form.integration.fever_endpoint": "Punkt końcowy API Fever:",
"form.integration.fever_password": "Hasło do Fever",
"form.integration.fever_username": "Login do Fever",
"form.integration.googlereader_activate": "Aktywuj API Google Reader",
"form.integration.googlereader_endpoint": "Punkt końcowy API Google Reader:",
"form.integration.googlereader_password": "Hasło do Google Reader",
"form.integration.googlereader_username": "Login do Google Reader",
"form.integration.instapaper_activate": "Zapisuj wpisy w Instapaper",
"form.integration.instapaper_password": "Hasło do Instapaper",
"form.integration.instapaper_username": "Login do Instapaper",
"form.integration.karakeep_activate": "Zapisuj wpisy w Karakeep",
"form.integration.karakeep_api_key": "Klucz API do Karakeep",
"form.integration.karakeep_url": "Punkt końcowy API Karakeep",
"form.integration.linkace_activate": "Zapisuj wpisy w LinkAce",
"form.integration.linkace_api_key": "Klucz API do LinkAce",
"form.integration.linkace_check_disabled": "Wyłącz sprawdzanie łączy",
"form.integration.linkace_endpoint": "Punkt końcowy API LinkAce",
"form.integration.linkace_is_private": "Oznacz łącze jako prywatne",
"form.integration.linkace_tags": "Znaczniki LinkAce",
"form.integration.linkding_activate": "Zapisuj wpisy w Linkding",
"form.integration.linkding_api_key": "Klucz API do Linkding",
"form.integration.linkding_bookmark": "Oznacz zakładkę jako nieprzeczytaną",
"form.integration.linkding_endpoint": "Punkt końcowy API Linkding",
"form.integration.linkding_tags": "Znaczniki Linkding",
"form.integration.linktaco_activate": "Zapisuj wpisy w LinkTaco",
"form.integration.linktaco_api_token": "Token API LinkTaco",
"form.integration.linktaco_api_token_hint": "Uzyskaj osobisty token dostępu na",
"form.integration.linktaco_org_slug": "Ślimak organizacji",
"form.integration.linktaco_tags": "Znaczniki (maks. 10, oddzielone przecinkami)",
"form.integration.linktaco_tags_hint": "Maksymalnie 10 znaczników, oddzielone przecinkami",
"form.integration.linktaco_visibility": "Widoczność",
"form.integration.linktaco_visibility_public": "Publiczne",
"form.integration.linktaco_visibility_private": "Prywatne",
"form.integration.linktaco_visibility_hint": "Widoczność PRYWATNE wymaga płatnego konta LinkTaco",
"form.integration.linkwarden_activate": "Zapisuj wpisy w Linkwarden",
"form.integration.linkwarden_api_key": "Klucz API do Linkwarden",
"form.integration.linkwarden_endpoint": "Podstawowy adres URL Linkwarden",
"form.integration.matrix_bot_activate": "Przesyłaj nowe wpisy do Matrix",
"form.integration.matrix_bot_chat_id": "Identyfikator pokoju Matrix",
"form.integration.matrix_bot_password": "Hasło do Matrix",
"form.integration.matrix_bot_url": "Adres URL serwera Matrix",
"form.integration.matrix_bot_user": "Login do Matrix",
"form.integration.notion_activate": "Zapisuj wpisy w Notion",
"form.integration.notion_page_id": "Identyfikator strony Notion",
"form.integration.notion_token": "Tajny token do Notion",
"form.integration.ntfy_activate": "Przesyłaj wpisy do ntfy",
"form.integration.ntfy_api_token": "Token API ntfy (opcjonalny)",
"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": "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",
"form.integration.nunux_keeper_api_key": "Klucz API do Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Punkt końcowy API Nunux Keeper",
"form.integration.omnivore_activate": "Zapisuj wpisy w Omnivore",
"form.integration.omnivore_api_key": "Klucz API do Omnivore",
"form.integration.omnivore_url": "Punkt końcowy API Omnivore",
"form.integration.pinboard_activate": "Zapisuj wpisy w Pinboard",
"form.integration.pinboard_bookmark": "Zaznacz zakładkę jako nieprzeczytaną",
"form.integration.pinboard_tags": "Znaczniki Pinboard",
"form.integration.pinboard_token": "Token API do Pinboard",
"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)",
"form.integration.raindrop_token": "Token (testowy)",
"form.integration.readeck_activate": "Zapisuj wpisy do Readeck",
"form.integration.readeck_api_key": "Tajny klucz API Readeck",
"form.integration.readeck_endpoint": "Adres URL Readeck",
"form.integration.readeck_labels": "Etykiety Readeck",
"form.integration.readeck_only_url": "Wysyłaj tylko adres URL (zamiast pełnej treści)",
"form.integration.readwise_activate": "Zapisuj wpisy w czytniku Readwise",
"form.integration.readwise_api_key": "Token dostępu do czytnika Readwise",
"form.integration.readwise_api_key_link": "Zdobądź token dostępu Readwise",
"form.integration.rssbridge_activate": "Sprawdź RSS-Bridge podczas dodawania subskrypcji",
"form.integration.rssbridge_token": "Token uwierzytelniający RSS-Bridge",
"form.integration.rssbridge_url": "Adres URL serwera RSS-Bridge",
"form.integration.shaarli_activate": "Zapisuj artykuły w Shaarli",
"form.integration.shaarli_api_secret": "Tajny klucz API do Shaarli",
"form.integration.shaarli_endpoint": "Adres URL Shaarli",
"form.integration.shiori_activate": "Zapisuj artykuły w Shiori",
"form.integration.shiori_endpoint": "Punkt końcowy API Shiori",
"form.integration.shiori_password": "Hasło do Shiori",
"form.integration.shiori_username": "Login do Shiori",
"form.integration.slack_activate": "Przesyłaj wpisy do Slack",
"form.integration.slack_webhook_link": "Łącze webhooka Slack",
"form.integration.telegram_bot_activate": "Przesyłaj nowe wpisy do czatu Telegram",
"form.integration.telegram_bot_disable_buttons": "Wyłącz przyciski",
"form.integration.telegram_bot_disable_notification": "Wyłącz powiadomienie",
"form.integration.telegram_bot_disable_web_page_preview": "Wyłącz podgląd strony internetowej",
"form.integration.telegram_bot_token": "Token do bota",
"form.integration.telegram_chat_id": "Identyfikator czatu",
"form.integration.telegram_topic_id": "Identyfikator tematu",
"form.integration.wallabag_activate": "Zapisuj wpisy w Wallabag",
"form.integration.wallabag_client_id": "Identyfikator klienta Wallabag",
"form.integration.wallabag_client_secret": "Tajny klucz klienta Wallabag",
"form.integration.wallabag_endpoint": "Podstawowy adres URL Wallabag",
"form.integration.wallabag_tags": "Znaczniki Wallabag",
"form.integration.wallabag_only_url": "Przesyłaj tylko adres URL (zamiast pełnej treści)",
"form.integration.wallabag_password": "Hasło do Wallabag",
"form.integration.wallabag_username": "Login do Wallabag",
"form.integration.webhook_activate": "Włącz webhooki",
"form.integration.webhook_secret": "Tajny klucz do webhooków",
"form.integration.webhook_url": "Domyślny adres URL webhooka",
"form.prefs.fieldset.application_settings": "Ustawienia aplikacji",
"form.prefs.fieldset.authentication_settings": "Ustawienia uwierzytelniania",
"form.prefs.fieldset.global_feed_settings": "Globalne ustawienia kanałów",
"form.prefs.fieldset.reader_settings": "Ustawienia czytnika",
"form.prefs.help.external_font_hosts": "Lista hostów zewnętrznych czcionek, na które należy zezwolić, rozdzielona spacjami. Na przykład: „fonts.gstatic.com fonts.googleapis.com”.",
"form.prefs.label.always_open_external_links": "Czytaj artykuły, otwierając łącza zewnętrzne",
"form.prefs.label.categories_sorting_order": "Sortowanie kategorii",
"form.prefs.label.cjk_reading_speed": "Szybkość czytania w języku chińskim, koreańskim i japońskim (znaki na minutę)",
"form.prefs.label.custom_css": "Niestandardowy CSS",
"form.prefs.label.custom_js": "Niestandardowy JavaScript",
"form.prefs.label.default_home_page": "Domyślna strona główna",
"form.prefs.label.default_reading_speed": "Szybkość czytania w innych językach (słowa na minutę)",
"form.prefs.label.display_mode": "Tryb wyświetlania progresywnej aplikacji sieciowej (PWA)",
"form.prefs.label.entries_per_page": "Wpisy na stronę",
"form.prefs.label.entry_order": "Kolumna sortowania wpisów",
"form.prefs.label.entry_sorting": "Sortowanie wpisów",
"form.prefs.label.entry_swipe": "Włącz przesuwanie wpisów na ekranach dotykowych",
"form.prefs.label.external_font_hosts": "Hosty zewnętrznych czcionek",
"form.prefs.label.gesture_nav": "Gest do poruszania się między wpisami",
"form.prefs.label.keyboard_shortcuts": "Włącz skróty klawiszowe",
"form.prefs.label.language": "Język",
"form.prefs.label.mark_read_manually": "Oznacz wpisy jako przeczytane ręcznie",
"form.prefs.label.mark_read_on_media_completion": "Oznacz jako przeczytane dopiero wtedy, gdy odtwarzanie audio i wideo osiągnie 90%% ukończenia",
"form.prefs.label.mark_read_on_view": "Automatycznie oznacz wpisy jako przeczytane podczas przeglądania",
"form.prefs.label.mark_read_on_view_or_media_completion": "Oznacz wpisy jako przeczytane po wyświetleniu. W przypadku audio i wideo oznacz jako przeczytane po ukończeniu 90%%",
"form.prefs.label.media_playback_rate": "Szybkość odtwarzania audio i wideo",
"form.prefs.label.open_external_links_in_new_tab": "Otwieraj łącza zewnętrzne w nowej karcie (dodaje target=\"_blank\" do łączy)",
"form.prefs.label.show_reading_time": "Pokaż szacowany czas czytania wpisów",
"form.prefs.label.theme": "Wygląd",
"form.prefs.label.timezone": "Strefa czasowa",
"form.prefs.select.alphabetical": "Alfabetycznie",
"form.prefs.select.browser": "Przeglądarkowy",
"form.prefs.select.created_time": "Czas utworzenia wpisu",
"form.prefs.select.fullscreen": "Pełnoekranowy",
"form.prefs.select.minimal_ui": "Minimalny",
"form.prefs.select.none": "Brak",
"form.prefs.select.older_first": "Najstarsze wpisy jako pierwsze",
"form.prefs.select.publish_time": "Czas publikacji wpisu",
"form.prefs.select.recent_first": "Najnowsze wpisy jako pierwsze",
"form.prefs.select.standalone": "Samodzielny",
"form.prefs.select.swipe": "Przesuwanie",
"form.prefs.select.tap": "Podwójne stuknięcie",
"form.prefs.select.unread_count": "Liczba nieprzeczytanych",
"form.submit.loading": "Ładowanie…",
"form.submit.saving": "Zapisywanie…",
"form.user.label.admin": "Administrator",
"form.user.label.confirmation": "Potwierdzenie hasła",
"form.user.label.password": "Hasło",
"form.user.label.username": "Nazwa użytkownika",
"menu.about": "O czytniku",
"menu.add_feed": "Dodaj kanał",
"menu.add_user": "Dodaj użytkownika",
"menu.api_keys": "Klucze API",
"menu.categories": "Kategorie",
"menu.create_api_key": "Utwórz nowy klucz API",
"menu.create_category": "Utwórz kategorię",
"menu.edit_category": "Edytuj",
"menu.edit_feed": "Edytuj",
"menu.export": "Eksportuj",
"menu.feed_entries": "Wpisy",
"menu.feeds": "Kanały",
"menu.flush_history": "Usuń historię",
"menu.history": "Historia",
"menu.home_page": "Strona główna",
"menu.import": "Importuj",
"menu.integrations": "Usługi",
"menu.logout": "Wyloguj się",
"menu.mark_all_as_read": "Oznacz wszystkie jako przeczytane",
"menu.mark_page_as_read": "Oznacz jako przeczytane",
"menu.preferences": "Preferencje",
"menu.refresh_all_feeds": "Odśwież w tle wszystkie subskrypcje",
"menu.refresh_feed": "Odśwież",
"menu.search": "Szukaj",
"menu.sessions": "Sesje",
"menu.settings": "Ustawienia",
"menu.shared_entries": "Udostępnione wpisy",
"menu.show_all_entries": "Pokaż wszystkie wpisy",
"menu.show_only_starred_entries": "Pokaż tylko ulubione wpisy",
"menu.show_only_unread_entries": "Pokaż tylko nieprzeczytane wpisy",
"menu.starred": "Ulubione",
"menu.title": "Menu",
"menu.unread": "Nieprzeczytane",
"menu.users": "Użytkownicy",
"page.about.author": "Autor:",
"page.about.build_date": "Data opracowania:",
"page.about.credits": "Prawa autorskie",
"page.about.db_usage": "Rozmiar bazy danych:",
"page.about.git_commit": "Zatwierdzenie Git:",
"page.about.global_config_options": "Globalne opcje konfiguracji",
"page.about.go_version": "Wersja Go:",
"page.about.license": "Licencja:",
"page.about.postgres_version": "Wersja PostgreSQL:",
"page.about.title": "O stronie",
"page.about.version": "Wersja:",
"page.add_feed.choose_feed": "Wybierz subskrypcję",
"page.add_feed.label.url": "Adres URL",
"page.add_feed.legend.advanced_options": "Opcje zaawansowane",
"page.add_feed.no_category": "Nie ma żadnej kategorii. Musisz mieć co najmniej jedną kategorię.",
"page.add_feed.submit": "Znajdź subskrypcję",
"page.add_feed.title": "Nowa subskrypcja",
"page.api_keys.never_used": "Nigdy nie używany",
"page.api_keys.table.actions": "Działania",
"page.api_keys.table.created_at": "Data utworzenia",
"page.api_keys.table.description": "Opis",
"page.api_keys.table.last_used_at": "Ostatnio używane",
"page.api_keys.table.token": "Token",
"page.api_keys.title": "Klucze API",
"page.categories.entries": "Wpisy",
"page.categories.feed_count": [
"Jest %d kanał.",
"Są %d kanały.",
"Jest %d kanałów."
],
"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:",
"page.edit_feed.last_check": "Ostatnia aktualizacja:",
"page.edit_feed.last_modified_header": "Ostatnio zmienione:",
"page.edit_feed.last_parsing_error": "Ostatni błąd analizy",
"page.edit_feed.no_header": "Brak",
"page.edit_feed.title": "Edytuj kanał: %s",
"page.edit_user.title": "Edytuj użytkownika: %s",
"page.entry.attachments": "Załączniki",
"page.feeds.error_count": [
"%d błąd",
"%d błędy",
"%d błędów"
],
"page.feeds.last_check": "Ostatnia aktualizacja:",
"page.feeds.next_check": "Następna aktualizacja:",
"page.feeds.read_counter": "Liczba przeczytanych wpisów",
"page.feeds.title": "Kanały",
"page.footer.elevator": "Wróć do góry",
"page.history.title": "Historia",
"page.import.title": "Importuj",
"page.integration.bookmarklet": "Skryptozakładka",
"page.integration.bookmarklet.help": "To łącze umożliwia subskrypcję strony internetowej bezpośrednio za pomocą zakładki w przeglądarce internetowej.",
"page.integration.bookmarklet.instructions": "Przeciągnij i upuść to łącze do zakładek.",
"page.integration.bookmarklet.name": "Dodaj do Miniflux",
"page.integration.miniflux_api": "API Miniflux",
"page.integration.miniflux_api_endpoint": "Punkt końcowy API",
"page.integration.miniflux_api_password": "Hasło",
"page.integration.miniflux_api_password_value": "Hasło do konta",
"page.integration.miniflux_api_username": "Nazwa użytkownika",
"page.integrations.title": "Usługi",
"page.keyboard_shortcuts.close_modal": "Zamknij listę skrótów klawiszowych",
"page.keyboard_shortcuts.download_content": "Pobierz oryginalną treść",
"page.keyboard_shortcuts.go_to_bottom_item": "Przejdź do dolnego elementu",
"page.keyboard_shortcuts.go_to_categories": "Przejdź do kategorii",
"page.keyboard_shortcuts.go_to_feed": "Przejdź do subskrypcji",
"page.keyboard_shortcuts.go_to_feeds": "Przejdź do kanałów",
"page.keyboard_shortcuts.go_to_history": "Przejdź do historii",
"page.keyboard_shortcuts.go_to_next_item": "Przejdź do następnego elementu",
"page.keyboard_shortcuts.go_to_next_page": "Przejdź do następnej strony",
"page.keyboard_shortcuts.go_to_previous_item": "Przejdź do poprzedniego elementu",
"page.keyboard_shortcuts.go_to_previous_page": "Przejdź do poprzedniej strony",
"page.keyboard_shortcuts.go_to_search": "Ustaw fokus na formularzu wyszukiwania",
"page.keyboard_shortcuts.go_to_settings": "Przejdź do ustawień",
"page.keyboard_shortcuts.go_to_starred": "Przejdź do ulubionych",
"page.keyboard_shortcuts.go_to_top_item": "Przejdź do górnego elementu",
"page.keyboard_shortcuts.go_to_unread": "Przejdź do nieprzeczytanych",
"page.keyboard_shortcuts.mark_page_as_read": "Zaznacz aktualną stronę jako przeczytaną",
"page.keyboard_shortcuts.open_comments": "Otwórz łącze do komentarzy",
"page.keyboard_shortcuts.open_comments_same_window": "Otwórz łącze do komentarzy w bieżącej karcie",
"page.keyboard_shortcuts.open_item": "Otwórz zaznaczony element",
"page.keyboard_shortcuts.open_original": "Otwórz oryginalne łącze",
"page.keyboard_shortcuts.open_original_same_window": "Otwórz oryginalne łącze w bieżącej karcie",
"page.keyboard_shortcuts.refresh_all_feeds": "Odśwież w tle wszystkie kanały",
"page.keyboard_shortcuts.remove_feed": "Usuń ten kanał",
"page.keyboard_shortcuts.save_article": "Zapisz wpis",
"page.keyboard_shortcuts.scroll_item_to_top": "Przewiń element do góry",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Pokaż listę skrótów klawiszowych",
"page.keyboard_shortcuts.subtitle.actions": "Działania",
"page.keyboard_shortcuts.subtitle.items": "Nawigacja między elementami",
"page.keyboard_shortcuts.subtitle.pages": "Nawigacja między stronami",
"page.keyboard_shortcuts.subtitle.sections": "Nawigacja między punktami menu",
"page.keyboard_shortcuts.title": "Skróty klawiszowe",
"page.keyboard_shortcuts.toggle_star_status": "Przełącz dodanie do ulubionych",
"page.keyboard_shortcuts.toggle_entry_attachments": "Przełącz otwieranie/zamykanie załączników wpisów",
"page.keyboard_shortcuts.toggle_read_status_next": "Przełącz przeczytane/nieprzeczytane, przejdź dalej",
"page.keyboard_shortcuts.toggle_read_status_prev": "Przełącz przeczytane/nieprzeczytane, przejdź wstecz",
"page.login.google_signin": "Zaloguj się przez Google",
"page.login.oidc_signin": "Zaloguj się przez %s",
"page.login.title": "Zaloguj się",
"page.login.webauthn_login": "Zaloguj się przez klucz dostępu",
"page.login.webauthn_login.error": "Nie można zalogować się za pomocą klucza dostępu",
"page.login.webauthn_login.help": "Wpisz swoją nazwę użytkownika, jeśli używasz klucza bezpieczeństwa. Nie jest to wymagane, jeśli używasz klucza dostępu (wykrywalnych danych uwierzytelniających).",
"page.new_api_key.title": "Nowy klucz API",
"page.new_category.title": "Nowa kategoria",
"page.new_user.title": "Nowy użytkownik",
"page.offline.message": "Jesteś odłączony od sieci",
"page.offline.refresh_page": "Spróbuj odświeżyć stronę",
"page.offline.title": "Tryb offline",
"page.read_entry_count": [
"%d przeczytany wpis",
"%d przeczytane wpisy",
"%d przeczytanych wpisów"
],
"page.search.title": "Wyniki wyszukiwania",
"page.sessions.table.actions": "Działania",
"page.sessions.table.current_session": "Bieżąca sesja",
"page.sessions.table.date": "Data",
"page.sessions.table.ip": "Adres IP",
"page.sessions.table.user_agent": "Agent użytkownika",
"page.sessions.title": "Sesje",
"page.settings.link_google_account": "Połącz z moim kontem Google",
"page.settings.link_oidc_account": "Połącz z moim kontem %s",
"page.settings.title": "Ustawienia",
"page.settings.unlink_google_account": "Odłącz moje konto Google",
"page.settings.unlink_oidc_account": "Odłącz moje konto %s",
"page.settings.webauthn.actions": "Działania",
"page.settings.webauthn.added_on": "Dodano",
"page.settings.webauthn.delete": [
"Usuń %d klucz dostępu",
"Usuń %d klucze dostępu",
"Usuń %d kluczy dostępu"
],
"page.settings.webauthn.last_seen_on": "Ostatnio użyte",
"page.settings.webauthn.passkey_name": "Nazwa klucza dostępu",
"page.settings.webauthn.passkeys": "Klucze dostępu",
"page.settings.webauthn.register": "Zarejestruj klucz dostępu",
"page.settings.webauthn.register.error": "Nie można zarejestrować klucza dostępu",
"page.shared_entries.title": "Udostępnione wpisy",
"page.shared_entries_count": [
"%d udostępniony wpis",
"%d udostępnione wpisy",
"%d udostępnionych wpisów"
],
"page.starred.title": "Ulubione",
"page.starred_entry_count": [
"%d ulubiony wpis",
"%d ulubione wpisy",
"%d ulubionych wpisów"
],
"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.users.actions": "Działania",
"page.users.admin.no": "Nie",
"page.users.admin.yes": "Tak",
"page.users.is_admin": "Administrator",
"page.users.last_login": "Ostatnie logowanie",
"page.users.never_logged": "Nigdy",
"page.users.title": "Użytkownicy",
"page.users.username": "Nazwa użytkownika",
"page.webauthn_rename.title": "Zmień nazwę klucza dostępu",
"pagination.first": "Pierwsza",
"pagination.last": "Ostatnia",
"pagination.next": "Następna",
"pagination.previous": "Poprzednia",
"search.label": "Szukaj",
"search.placeholder": "Szukaj…",
"search.submit": "Szukaj",
"skip_to_content": "Przejdź do treści",
"time_elapsed.days": [
"%d dzień temu",
"%d dni temu",
"%d dni temu"
],
"time_elapsed.hours": [
"%d godzinę temu",
"%d godziny temu",
"%d godzin temu"
],
"time_elapsed.minutes": [
"%d minuta temu",
"%d minuty temu",
"%d minut temu"
],
"time_elapsed.months": [
"%d miesiąc temu",
"%d miesiące temu",
"%d miesięcy temu"
],
"time_elapsed.not_yet": "jeszcze nie",
"time_elapsed.now": "przed chwilą",
"time_elapsed.weeks": [
"%d tydzień temu",
"%d tygodnie temu",
"%d tygodni temu"
],
"time_elapsed.years": [
"%d rok temu",
"%d lat temu",
"%d lat temu"
],
"time_elapsed.yesterday": "wczoraj",
"tooltip.keyboard_shortcuts": "Skróty klawiszowe: %s",
"tooltip.logged_user": "Zalogowany jako %s"
} v2-2.2.13/internal/locale/translations/pt_BR.json 0000664 0000000 0000000 00000115026 15062123773 0021636 0 ustar 00root root 0000000 0000000 {
"action.cancel": "Cancelar",
"action.download": "Baixar",
"action.edit": "Editar",
"action.home_screen": "Voltar para a tela inicial",
"action.import": "Importar",
"action.login": "Iniciar sessão",
"action.or": "Ou",
"action.remove": "Remover",
"action.remove_feed": "Remover fonte",
"action.save": "Salvar",
"action.subscribe": "Inscrever",
"action.update": "Atualizar",
"alert.account_linked": "Sua conta externa está vinculada!",
"alert.account_unlinked": "Sua conta externa está desvinculada!",
"alert.background_feed_refresh": "Todas as fontes estão sendo atualizadas em segundo plano. Você pode continuar usando o Miniflux enquanto este processo está em execução.",
"alert.feed_error": "Ocorreu um problema com esta fonte.",
"alert.no_starred": "Não há favorito neste momento.",
"alert.no_category": "Não há categoria.",
"alert.no_category_entry": "Não há itens nesta categoria.",
"alert.no_feed": "Não há inscrições.",
"alert.no_feed_entry": "Não há itens nessa fonte.",
"alert.no_feed_in_category": "Não há inscrições nessa categoria.",
"alert.no_history": "Não há histórico nesse momento.",
"alert.no_search_result": "Não há resultados para essa busca.",
"alert.no_shared_entry": "Não há itens compartilhados.",
"alert.no_tag_entry": "Não há itens que correspondam a esta etiqueta.",
"alert.no_unread_entry": "Não há itens não lidos.",
"alert.no_user": "Você é o único usuário.",
"alert.prefs_saved": "Suas preferências foram salvas!",
"alert.too_many_feeds_refresh": [
"Você acionou muitas atualizações de fontes. Por favor, aguarde %d minuto antes de tentar novamente.",
"Você acionou muitas atualizações de fontes. Por favor, aguarde %d minutos antes de tentar novamente."
],
"confirm.loading": "Carregando...",
"confirm.no": "Não",
"confirm.question": "Tem certeza?",
"confirm.question.refresh": "Você deseja forçar a atualização?",
"confirm.yes": "Sim",
"enclosure_media_controls.seek": "Procurar:",
"enclosure_media_controls.seek.title": "Procurar %s segundos",
"enclosure_media_controls.speed": "Velocidade:",
"enclosure_media_controls.speed.faster": "Mais Rápido",
"enclosure_media_controls.speed.faster.title": "Mais rápido em %sx",
"enclosure_media_controls.speed.reset": "Resetar",
"enclosure_media_controls.speed.reset.title": "Resetar velocidade para 1x",
"enclosure_media_controls.speed.slower": "Mais Lento",
"enclosure_media_controls.speed.slower.title": "Mais lento em %sx",
"entry.starred.toast.off": "Desfavoritado",
"entry.starred.toast.on": "Favoritado",
"entry.starred.toggle.off": "Remover dos Favoritos",
"entry.starred.toggle.on": "Favoritar",
"entry.comments.label": "Comentários",
"entry.comments.title": "Ver comentários",
"entry.estimated_reading_time": [
"Leitura de %d minuto",
"Leitura de %d minutos"
],
"entry.external_link.label": "Link externo",
"entry.save.completed": "Feito!",
"entry.save.label": "Salvar",
"entry.save.title": "Salvar esse item",
"entry.save.toast.completed": "Item guardado",
"entry.scraper.completed": "Feito!",
"entry.scraper.label": "Baixar",
"entry.scraper.title": "Obter conteúdo completo",
"entry.share.label": "Compartilhar",
"entry.share.title": "Compartilhar esse item",
"entry.shared_entry.label": "Compartilhar",
"entry.shared_entry.title": "Abrir link público",
"entry.state.loading": "Carregando...",
"entry.state.saving": "Salvando...",
"entry.status.mark_as_read": "Marcar como lido",
"entry.status.mark_as_unread": "Marcar como não lido",
"entry.status.title": "Modificar estado deste item",
"entry.status.toast.read": "Marcado como lido",
"entry.status.toast.unread": "Marcado como não lido",
"entry.tags.label": "Etiquetas:",
"entry.tags.more_tags_label": [
"Mostrar mais %d etiqueta",
"Mostrar mais %d etiquetas"
],
"entry.unshare.label": "Descompartilhar",
"error.api_key_already_exists": "Essa chave de API já existe.",
"error.bad_credentials": "Usuário ou senha são inválidos.",
"error.category_already_exists": "Esta categoria já existe.",
"error.category_not_found": "Esta categoria não existe ou não pertence a este usuário.",
"error.database_error": "Erro no banco de dados: %v.",
"error.different_passwords": "As senhas não são iguais.",
"error.duplicate_fever_username": "Alguém já está utilizando esse nome de usuário do Fever!",
"error.duplicate_googlereader_username": "Alguém já está utilizando esse nome de usuário do Google Reader!",
"error.duplicate_linked_account": "Alguém já está vinculado a esse serviço!",
"error.duplicated_feed": "Esta fonte já existe.",
"error.empty_file": "Esse arquivo está vazio.",
"error.entries_per_page_invalid": "O número de itens por página é inválido.",
"error.feed_already_exists": "Este feed já existe.",
"error.feed_category_not_found": "Esta categoria não existe ou não pertence a este usuário.",
"error.feed_format_not_detected": "Não foi possível detectar o formato da fonte: %v.",
"error.feed_invalid_blocklist_rule": "A regra da lista de bloqueio é inválida.",
"error.feed_invalid_keeplist_rule": "A regra de manutenção da lista é inválida.",
"error.feed_mandatory_fields": "O campo de URL e categoria são obrigatórios.",
"error.feed_not_found": "Esta fonte não existe ou não pertence a este usuário.",
"error.feed_title_not_empty": "O título do feed não pode estar vazio.",
"error.feed_url_not_empty": "O URL do feed não pode estar vazio.",
"error.fields_mandatory": "Todos os campos são obrigatórios.",
"error.http_bad_gateway": "O site não está disponível no momento devido a um erro de gateway. O problema não está no Miniflux. Por favor, tente novamente mais tarde.",
"error.http_body_read": "Não foi possível ler o corpo HTTP: %v.",
"error.http_client_error": "Erro do cliente HTTP: %v.",
"error.http_empty_response": "A resposta HTTP está vazia. Talvez este site esteja usando um mecanismo de proteção contra bots?",
"error.http_empty_response_body": "O corpo da resposta HTTP está vazio.",
"error.http_forbidden": "O acesso a este site está proibido. Talvez este site tenha um mecanismo de proteção contra bots?",
"error.http_gateway_timeout": "O site não está disponível no momento devido a um erro de tempo limite do gateway. O problema não está no Miniflux. Por favor, tente novamente mais tarde.",
"error.http_internal_server_error": "O site não está disponível no momento devido a um erro interno do servidor. O problema não está no Miniflux. Por favor, tente novamente mais tarde.",
"error.http_not_authorized": "O acesso a este site não está autorizado. Pode ser um nome de usuário ou senha incorretos.",
"error.http_resource_not_found": "O recurso solicitado não foi encontrado. Por favor, verifique a URL.",
"error.http_response_too_large": "A resposta HTTP é muito grande. Você pode aumentar o limite de tamanho da resposta HTTP nas configurações globais (requer reinício do servidor).",
"error.http_service_unavailable": "O site não está disponível no momento devido a um erro interno do servidor. O problema não está no Miniflux. Por favor, tente novamente mais tarde.",
"error.http_too_many_requests": "O Miniflux gerou muitas solicitações para este site. Por favor, tente novamente mais tarde ou altere a configuração do aplicativo.",
"error.http_unexpected_status_code": "O site não está disponível no momento devido a um código de status HTTP inesperado: %d. O problema não está no Miniflux. Por favor, tente novamente mais tarde.",
"error.invalid_categories_sorting_order": "A ordem de classificação das categorias não é válida.",
"error.invalid_default_home_page": "Página inicial por defeito inválida!",
"error.invalid_display_mode": "Modo de exibição de aplicativo inválido da web.",
"error.invalid_entry_direction": "Direção de entrada inválida.",
"error.invalid_entry_order": "A ordem de entrada é inválida.",
"error.invalid_feed_proxy_url": "URL de proxy inválido.",
"error.invalid_feed_url": "URL de feed inválido.",
"error.invalid_gesture_nav": "Navegação por gestos inválida.",
"error.invalid_language": "Idioma inválido.",
"error.invalid_site_url": "URL de site inválido.",
"error.invalid_theme": "Tema inválido.",
"error.invalid_timezone": "Fuso horário inválido.",
"error.network_operation": "O Miniflux não conseguiu acessar este site devido a um erro de rede: %v.",
"error.network_timeout": "Este site está muito lento e a solicitação expirou: %v",
"error.password_min_length": "A senha deve ter no mínimo 6 caracteres.",
"error.proxy_url_not_empty": "A URL do proxy não pode estar vazia.",
"error.settings_block_rule_fieldname_invalid": "Regra de bloqueio inválida: a regra #%d está sem um nome de campo válido (Opções: %s)",
"error.settings_block_rule_invalid_regex": "Regra de bloqueio inválida: o padrão da regra #%d não é uma expressão regular válida",
"error.settings_block_rule_regex_required": "Regra de bloqueio inválida: o padrão da regra #%d não foi fornecido",
"error.settings_block_rule_separator_required": "Regra de bloqueio inválida: o padrão da regra #%d deve ser separado por um '='",
"error.settings_invalid_domain_list": "Lista de domínios inválida. Por favor, forneça uma lista de domínios separados por espaço.",
"error.settings_keep_rule_fieldname_invalid": "Regra de permissão inválida: a regra #%d está sem um nome de campo válido (Opções: %s)",
"error.settings_keep_rule_invalid_regex": "Regra de permissão inválida: o padrão da regra #%d não é uma expressão regular válida",
"error.settings_keep_rule_regex_required": "Regra de permissão inválida: o padrão da regra #%d não foi fornecido",
"error.settings_keep_rule_separator_required": "Regra de permissão inválida: o padrão da regra #%d deve ser separado por um '='",
"error.settings_mandatory_fields": "Os campos de nome de usuário, tema, idioma e fuso horário são obrigatórios.",
"error.settings_media_playback_rate_range": "A velocidade de reprodução está fora do intervalo",
"error.settings_reading_speed_is_positive": "As velocidades de leitura devem ser inteiros positivos.",
"error.site_url_not_empty": "O URL do site não pode estar vazio.",
"error.subscription_not_found": "Não foi possível encontrar uma inscrição.",
"error.title_required": "O título é obrigatório.",
"error.tls_error": "Erro TLS: %q. Você pode desabilitar a verificação TLS nas configurações do feed se desejar.",
"error.unable_to_create_api_key": "Não foi possível criar uma chave de API.",
"error.unable_to_create_category": "Não foi possível criar essa categoria.",
"error.unable_to_create_user": "Não foi possível criar esse usuário.",
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.unable_to_update_category": "Não foi possível atualizar essa categoria.",
"error.unable_to_update_feed": "Não foi possível atualizar essa fonte.",
"error.unable_to_update_user": "Não foi possível atualizar esse usuário.",
"error.unlink_account_without_password": "Você deve definir uma senha, senão não será possível efetuar a sessão novamente.",
"error.user_already_exists": "Esse usuário já existe.",
"error.user_mandatory_fields": "O nome de usuário é obrigatório.",
"error.linktaco_missing_required_fields": "LinkTaco API Token e Organization Slug são obrigatórios",
"form.api_key.label.description": "Etiqueta da chave de API",
"form.category.hide_globally": "Ocultar entradas na lista global não lida",
"form.category.label.title": "Título",
"form.feed.fieldset.general": "Geral",
"form.feed.fieldset.integration": "Serviços de Terceiros",
"form.feed.fieldset.network_settings": "Configurações de Rede",
"form.feed.fieldset.rules": "Regras",
"form.feed.label.allow_self_signed_certificates": "Permitir certificados autoassinados ou inválidos",
"form.feed.label.apprise_service_urls": "Lista de URLs de serviços Apprise separadas por vírgula",
"form.feed.label.block_filter_entry_rules": "Regras de Bloqueio de Entradas",
"form.feed.label.blocklist_rules": "Filtros de Bloqueio Baseados em Regex",
"form.feed.label.category": "Categoria",
"form.feed.label.cookie": "Definir Cookies",
"form.feed.label.crawler": "Obter conteúdo original",
"form.feed.label.description": "Descrição",
"form.feed.label.disable_http2": "Desativar HTTP/2 para evitar fingerprinting",
"form.feed.label.disabled": "Não atualizar esta fonte",
"form.feed.label.feed_password": "Senha da fonte",
"form.feed.label.feed_url": "URL da fonte",
"form.feed.label.feed_username": "Nome de usuário da fonte",
"form.feed.label.fetch_via_proxy": "Usar o proxy configurado no nível da aplicação",
"form.feed.label.hide_globally": "Ocultar entradas na lista global não lida",
"form.feed.label.ignore_http_cache": "Ignorar cache HTTP",
"form.feed.label.keep_filter_entry_rules": "Regras de Permissão de Entradas",
"form.feed.label.keeplist_rules": "Filtros de Manutenção Baseados em Regex",
"form.feed.label.no_media_player": "Sem reprodutor de mídia (áudio/vídeo)",
"form.feed.label.ntfy_activate": "Enviar itens para o ntfy",
"form.feed.label.ntfy_default_priority": "Prioridade padrão do ntfy",
"form.feed.label.ntfy_high_priority": "Alta prioridade do ntfy",
"form.feed.label.ntfy_low_priority": "Baixa prioridade do ntfy",
"form.feed.label.ntfy_max_priority": "Prioridade máxima do ntfy",
"form.feed.label.ntfy_min_priority": "Prioridade mínima do ntfy",
"form.feed.label.ntfy_priority": "Prioridade do ntfy",
"form.feed.label.ntfy_topic": "Tópico do ntfy (opcional)",
"form.feed.label.proxy_url": "Proxy URL",
"form.feed.label.pushover_activate": "Enviar itens para o pushover.net",
"form.feed.label.pushover_default_priority": "Prioridade padrão do Pushover",
"form.feed.label.pushover_high_priority": "Alta prioridade do Pushover",
"form.feed.label.pushover_low_priority": "Baixa prioridade do Pushover",
"form.feed.label.pushover_max_priority": "Prioridade máxima do Pushover",
"form.feed.label.pushover_min_priority": "Prioridade mínima do Pushover",
"form.feed.label.pushover_priority": "Prioridade da mensagem do Pushover",
"form.feed.label.rewrite_rules": "Regras de Reescrita de Conteúdo",
"form.feed.label.scraper_rules": "Regras do scraper",
"form.feed.label.site_url": "URL do site",
"form.feed.label.title": "Título",
"form.feed.label.urlrewrite_rules": "Regras de reescrita de URL",
"form.feed.label.user_agent": "Sobrescrever o agente de usuário (user-agent) padrão",
"form.feed.label.webhook_url": "Sobrescrever URL do webhook",
"form.import.label.file": "Arquivo OPML",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Enviar itens para o Apprise",
"form.integration.apprise_services_url": "Lista de URLs de serviços Apprise separadas por vírgula",
"form.integration.apprise_url": "Apprise API URL",
"form.integration.betula_activate": "Save entries to Betula",
"form.integration.betula_token": "Betula Token",
"form.integration.betula_url": "Betula server URL",
"form.integration.cubox_activate": "Save entries to Cubox",
"form.integration.cubox_api_link": "Cubox API link",
"form.integration.discord_activate": "Push entries to Discord",
"form.integration.discord_webhook_link": "Discord Webhook link",
"form.integration.espial_activate": "Salvar itens no Espial",
"form.integration.espial_api_key": "Chave de API do Espial",
"form.integration.espial_endpoint": "Endpoint de API do Espial",
"form.integration.espial_tags": "Etiquetas (tags) do Espial",
"form.integration.fever_activate": "Ativar API do Fever",
"form.integration.fever_endpoint": "Endpoint da API do Fever:",
"form.integration.fever_password": "Senha do Fever",
"form.integration.fever_username": "Nome de usuário do Fever",
"form.integration.googlereader_activate": "Ativar API do Google Reader",
"form.integration.googlereader_endpoint": "Endpoint da API do Google Reader:",
"form.integration.googlereader_password": "Senha do Google Reader",
"form.integration.googlereader_username": "Nome de usuário do Google Reader",
"form.integration.instapaper_activate": "Salvar itens no Instapaper",
"form.integration.instapaper_password": "Senha do Instapaper",
"form.integration.instapaper_username": "Nome do usuário do Instapaper",
"form.integration.karakeep_activate": "Salvar itens no Karakeep",
"form.integration.karakeep_api_key": "Chave de API do Karakeep",
"form.integration.karakeep_url": "Endpoint de API do Karakeep",
"form.integration.linkace_activate": "Salvar itens no LinkAce",
"form.integration.linkace_api_key": "Chave de API do LinkAce",
"form.integration.linkace_check_disabled": "Desativar verificação de link",
"form.integration.linkace_endpoint": "Endpoint de API do LinkAce",
"form.integration.linkace_is_private": "Marcar link como privado",
"form.integration.linkace_tags": "Etiquetas do LinkAce",
"form.integration.linkding_activate": "Salvar itens no Linkding",
"form.integration.linkding_api_key": "Chave de API do Linkding",
"form.integration.linkding_bookmark": "Salvar marcador como não lido",
"form.integration.linkding_endpoint": "Endpoint de API do Linkding",
"form.integration.linkding_tags": "Linkding Tags",
"form.integration.linktaco_activate": "Salvar itens no LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Obtenha seu token de acesso pessoal em",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Tags (máx 10, separadas por vírgula)",
"form.integration.linktaco_tags_hint": "Máximo 10 tags, separadas por vírgula",
"form.integration.linktaco_visibility": "Visibilidade",
"form.integration.linktaco_visibility_public": "Público",
"form.integration.linktaco_visibility_private": "Privado",
"form.integration.linktaco_visibility_hint": "Visibilidade PRIVADA requer uma conta LinkTaco paga",
"form.integration.linkwarden_activate": "Salvar itens no Linkwarden",
"form.integration.linkwarden_api_key": "Chave de API do Linkwarden",
"form.integration.linkwarden_endpoint": "URL base do Linkwarden",
"form.integration.matrix_bot_activate": "Transferir novos artigos para o Matrix",
"form.integration.matrix_bot_chat_id": "Identificação da sala Matrix",
"form.integration.matrix_bot_password": "Palavra-passe para utilizador da Matrix",
"form.integration.matrix_bot_url": "URL do servidor Matrix",
"form.integration.matrix_bot_user": "Nome de utilizador para Matrix",
"form.integration.notion_activate": "Salvar itens no Notion",
"form.integration.notion_page_id": "ID da página do Notion",
"form.integration.notion_token": "Token secreto do Notion",
"form.integration.ntfy_activate": "Enviar itens para o ntfy",
"form.integration.ntfy_api_token": "Ntfy API Token (opcional)",
"form.integration.ntfy_icon_url": "Ntfy Icon URL (opcional)",
"form.integration.ntfy_internal_links": "Usar links internos ao clicar (opcional)",
"form.integration.ntfy_password": "Ntfy Password (opcional)",
"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",
"form.integration.nunux_keeper_api_key": "Chave de API do Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Endpoint de API do Nunux Keeper",
"form.integration.omnivore_activate": "Salvar itens no Omnivore",
"form.integration.omnivore_api_key": "Chave de API do Omnivore",
"form.integration.omnivore_url": "Endpoint de API do Omnivore",
"form.integration.pinboard_activate": "Salvar itens no Pinboard",
"form.integration.pinboard_bookmark": "Salvar marcador como não lido",
"form.integration.pinboard_tags": "Etiquetas (tags) do Pinboard",
"form.integration.pinboard_token": "Token de API do Pinboard",
"form.integration.pushover_activate": "Enviar itens para o Pushover",
"form.integration.pushover_device": "Dispositivo Pushover (opcional)",
"form.integration.pushover_prefix": "Prefixo da URL do Pushover (opcional)",
"form.integration.pushover_token": "Token de API do aplicativo Pushover",
"form.integration.pushover_user": "Chave do usuário Pushover",
"form.integration.raindrop_activate": "Salvar itens no Raindrop",
"form.integration.raindrop_collection_id": "ID da coleção",
"form.integration.raindrop_tags": "Etiquetas (separadas por vírgula)",
"form.integration.raindrop_token": "Token (teste)",
"form.integration.readeck_activate": "Salvar itens no Readeck",
"form.integration.readeck_api_key": "Chave de API do Readeck",
"form.integration.readeck_endpoint": "Endpoint de API do Readeck",
"form.integration.readeck_labels": "Readeck Labels",
"form.integration.readeck_only_url": "Enviar apenas URL (em vez de conteúdo completo)",
"form.integration.readwise_activate": "Salvar itens no Readwise Reader",
"form.integration.readwise_api_key": "Token de acesso do Readwise Reader",
"form.integration.readwise_api_key_link": "Obtenha seu token de acesso do Readwise",
"form.integration.rssbridge_activate": "Verificar RSS-Bridge ao adicionar inscrições",
"form.integration.rssbridge_token": "Token de autenticação do RSS-Bridge",
"form.integration.rssbridge_url": "URL do servidor RSS-Bridge",
"form.integration.shaarli_activate": "Salvar artigos no Shaarli",
"form.integration.shaarli_api_secret": "Segredo da API do Shaarli",
"form.integration.shaarli_endpoint": "URL do Shaarli",
"form.integration.shiori_activate": "Salvar itens no Shiori",
"form.integration.shiori_endpoint": "Endpoint da API do Shiori",
"form.integration.shiori_password": "Senha do Shiori",
"form.integration.shiori_username": "Nome de usuário do Shiori",
"form.integration.slack_activate": "Slack entries to Discord",
"form.integration.slack_webhook_link": "Slack Webhook link",
"form.integration.telegram_bot_activate": "Envie novos artigos para o chat do Telegram",
"form.integration.telegram_bot_disable_buttons": "Disable buttons",
"form.integration.telegram_bot_disable_notification": "Disable notification",
"form.integration.telegram_bot_disable_web_page_preview": "Disable web page preview",
"form.integration.telegram_bot_token": "Token de bot",
"form.integration.telegram_chat_id": "ID de bate-papo",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "Salvar itens no Wallabag",
"form.integration.wallabag_client_id": "ID de cliente (Client ID) do Wallabag",
"form.integration.wallabag_client_secret": "Segredo do cliente (Client Secret) do Wallabag",
"form.integration.wallabag_endpoint": "URL base do Wallabag",
"form.integration.wallabag_only_url": "Enviar apenas URL (em vez de conteúdo completo)",
"form.integration.wallabag_password": "Senha do Wallabag",
"form.integration.wallabag_username": "Nome de usuário do Wallabag",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Enable Webhooks",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Default Webhook URL",
"form.prefs.fieldset.application_settings": "Configurações do aplicativo",
"form.prefs.fieldset.authentication_settings": "Configurações de autenticação",
"form.prefs.fieldset.global_feed_settings": "Configurações globais de fontes",
"form.prefs.fieldset.reader_settings": "Configurações do leitor",
"form.prefs.help.external_font_hosts": "Lista separada por espaço de hosts de fontes externas permitidos. Por exemplo: 'fonts.gstatic.com fonts.googleapis.com'.",
"form.prefs.label.always_open_external_links": "Ler artigos abrindo links externos",
"form.prefs.label.categories_sorting_order": "Classificação das categorias",
"form.prefs.label.cjk_reading_speed": "Velocidade de leitura para chinês, coreano e japonês (caracteres por minuto)",
"form.prefs.label.custom_css": "CSS customizado",
"form.prefs.label.custom_js": "JavaScript customizado",
"form.prefs.label.default_home_page": "Página inicial predefinida",
"form.prefs.label.default_reading_speed": "Velocidade de leitura para outros idiomas (palavras por minuto)",
"form.prefs.label.display_mode": "Modo de exibição Progressive Web App (PWA)",
"form.prefs.label.entries_per_page": "Itens por página",
"form.prefs.label.entry_order": "Coluna de Ordenação de Entrada",
"form.prefs.label.entry_sorting": "Ordenação dos itens",
"form.prefs.label.entry_swipe": "Ativar entrada de furto em telas sensíveis ao toque",
"form.prefs.label.external_font_hosts": "Hosts de fontes externas",
"form.prefs.label.gesture_nav": "Gesto para navegar entre as entradas",
"form.prefs.label.keyboard_shortcuts": "Habilitar atalhos do teclado",
"form.prefs.label.language": "Idioma",
"form.prefs.label.mark_read_manually": "Marcar itens como lidos manualmente",
"form.prefs.label.mark_read_on_media_completion": "Marcar como lido apenas quando a reprodução de áudio/vídeo atingir 90%% de conclusão",
"form.prefs.label.mark_read_on_view": "Marcar automaticamente as entradas como lidas quando visualizadas",
"form.prefs.label.mark_read_on_view_or_media_completion": "Marcar itens como lidos quando visualizados. Para áudio/vídeo, marcar como lido em 90%% de conclusão",
"form.prefs.label.media_playback_rate": "Velocidade de reprodução do áudio/vídeo",
"form.prefs.label.open_external_links_in_new_tab": "Abrir links externos em uma nova aba (adiciona target=\"_blank\" aos links)",
"form.prefs.label.show_reading_time": "Mostrar tempo estimado de leitura de artigos",
"form.prefs.label.theme": "Tema",
"form.prefs.label.timezone": "Fuso horário",
"form.prefs.select.alphabetical": "Por ordem alfabética",
"form.prefs.select.browser": "Navegador",
"form.prefs.select.created_time": "Entrada tempo criado",
"form.prefs.select.fullscreen": "Tela completa",
"form.prefs.select.minimal_ui": "Mínimo",
"form.prefs.select.none": "Nenhum",
"form.prefs.select.older_first": "Itens mais velhos primeiro",
"form.prefs.select.publish_time": "Entrada hora de publicação",
"form.prefs.select.recent_first": "Itens mais recentes",
"form.prefs.select.standalone": "Autônomo",
"form.prefs.select.swipe": "Deslize",
"form.prefs.select.tap": "Toque duplo",
"form.prefs.select.unread_count": "Contagem não lida",
"form.submit.loading": "Carregando...",
"form.submit.saving": "Salvando...",
"form.user.label.admin": "Administrador",
"form.user.label.confirmation": "Confirmação de senha",
"form.user.label.password": "Senha",
"form.user.label.username": "Nome de usuário",
"menu.about": "Sobre",
"menu.add_feed": "Adicionar inscrição",
"menu.add_user": "Adicionar usuário",
"menu.api_keys": "Chaves de API",
"menu.categories": "Categorias",
"menu.create_api_key": "Criar uma nova chave de API",
"menu.create_category": "Criar uma categoria",
"menu.edit_category": "Editar",
"menu.edit_feed": "Editar",
"menu.export": "Exportar",
"menu.feed_entries": "Itens",
"menu.feeds": "Fontes",
"menu.flush_history": "Limpar histórico",
"menu.history": "Histórico",
"menu.home_page": "Home page",
"menu.import": "Importar",
"menu.integrations": "Integrações",
"menu.logout": "Encerrar sessão",
"menu.mark_all_as_read": "Marcar todos como lido",
"menu.mark_page_as_read": "Marcar essa página como lida",
"menu.preferences": "Preferências",
"menu.refresh_all_feeds": "Atualizar todas as fontes",
"menu.refresh_feed": "Atualizar",
"menu.search": "Buscar",
"menu.sessions": "Sessões",
"menu.settings": "Configurações",
"menu.shared_entries": "Itens compartilhados",
"menu.show_all_entries": "Mostrar todas os itens",
"menu.show_only_starred_entries": "Mostrar apenas os favoritos",
"menu.show_only_unread_entries": "Mostrar apenas itens não lidos",
"menu.starred": "Favoritos",
"menu.title": "Menu",
"menu.unread": "Não lido",
"menu.users": "Usuários",
"page.about.author": "Autor:",
"page.about.build_date": "Compilado em:",
"page.about.credits": "Créditos",
"page.about.db_usage": "Database size:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "opções de configuração global",
"page.about.go_version": "Go versão:",
"page.about.license": "Licença:",
"page.about.postgres_version": "Postgres versão:",
"page.about.title": "Sobre",
"page.about.version": "Versão:",
"page.add_feed.choose_feed": "Escolher uma fonte",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Opções avançadas",
"page.add_feed.no_category": "Não existe uma categoria. Deve existir pelo menos uma categoria.",
"page.add_feed.submit": "Buscar uma fonte",
"page.add_feed.title": "Nova inscrição",
"page.api_keys.never_used": "Nunca usado",
"page.api_keys.table.actions": "Ações",
"page.api_keys.table.created_at": "Data de criação",
"page.api_keys.table.description": "Descrição",
"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.entries": "Itens",
"page.categories.feed_count": [
"Existe %d fonte.",
"Existem %d fontes."
],
"page.categories.feeds": "Inscrições",
"page.categories.no_feed": "Sem fonte.",
"page.categories.title": "Categorias",
"page.categories_count": [
"%d categoria",
"%d categorias"
],
"page.category_label": "Categoria: %s",
"page.edit_category.title": "Editar categoria: %s",
"page.edit_feed.etag_header": "Cabeçalho 'ETag':",
"page.edit_feed.last_check": "Última verificação:",
"page.edit_feed.last_modified_header": "Cabeçalho 'LastModified':",
"page.edit_feed.last_parsing_error": "Último erro durante processamento",
"page.edit_feed.no_header": "Sem cabeçalhos",
"page.edit_feed.title": "Editar fonte: %s",
"page.edit_user.title": "Editar usuário: %s",
"page.entry.attachments": "Anexos",
"page.feeds.error_count": [
"%d erro",
"%d erros"
],
"page.feeds.last_check": "Última verificação:",
"page.feeds.next_check": "Próxima verificação:",
"page.feeds.read_counter": "Número de itens lidos",
"page.feeds.title": "Fontes",
"page.footer.elevator": "Back to top",
"page.history.title": "Histórico",
"page.import.title": "Importar",
"page.integration.bookmarklet": "Bookmarklet",
"page.integration.bookmarklet.help": "Esse link especial permite você se inscrever a um site diretamente usando favorito do navegador.",
"page.integration.bookmarklet.instructions": "Arrasta e solta esse link para os favoritos do teu navegador.",
"page.integration.bookmarklet.name": "Adicionar ao Miniflux",
"page.integration.miniflux_api": "API do Miniflux",
"page.integration.miniflux_api_endpoint": "Endpoint da API",
"page.integration.miniflux_api_password": "Senha",
"page.integration.miniflux_api_password_value": "Senha da sua Conta",
"page.integration.miniflux_api_username": "Nome de usuário",
"page.integrations.title": "Integrações",
"page.keyboard_shortcuts.close_modal": "Fechar janela",
"page.keyboard_shortcuts.download_content": "Buscar o conteúdo original",
"page.keyboard_shortcuts.go_to_bottom_item": "Ir para o item inferior",
"page.keyboard_shortcuts.go_to_categories": "Ir as categorias",
"page.keyboard_shortcuts.go_to_feed": "Ir a fonte",
"page.keyboard_shortcuts.go_to_feeds": "Ir as inscrições",
"page.keyboard_shortcuts.go_to_history": "Ir ao histórico",
"page.keyboard_shortcuts.go_to_next_item": "Ir ao tem seguinte",
"page.keyboard_shortcuts.go_to_next_page": "Ir a página seguinte",
"page.keyboard_shortcuts.go_to_previous_item": "Ir ao item anterior",
"page.keyboard_shortcuts.go_to_previous_page": "Ir a página anterior",
"page.keyboard_shortcuts.go_to_search": "Ir para o campo de busca",
"page.keyboard_shortcuts.go_to_settings": "Ir as configurações",
"page.keyboard_shortcuts.go_to_starred": "Ir aos favoritos",
"page.keyboard_shortcuts.go_to_top_item": "Ir para o item superior",
"page.keyboard_shortcuts.go_to_unread": "Ir aos não lidos",
"page.keyboard_shortcuts.mark_page_as_read": "Marcar página atual como lida",
"page.keyboard_shortcuts.open_comments": "Abrir os comentários",
"page.keyboard_shortcuts.open_comments_same_window": "Abrir os comentários na janela atual",
"page.keyboard_shortcuts.open_item": "Abrir o item selecionado",
"page.keyboard_shortcuts.open_original": "Abrir o conteúdo original",
"page.keyboard_shortcuts.open_original_same_window": "Abrir o conteúdo original na janela atual",
"page.keyboard_shortcuts.refresh_all_feeds": "Atualizar todas as fontes",
"page.keyboard_shortcuts.remove_feed": "Remover essa fonte",
"page.keyboard_shortcuts.save_article": "Salvar item",
"page.keyboard_shortcuts.scroll_item_to_top": "Role o item para cima",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Mostrar atalhos de teclado",
"page.keyboard_shortcuts.subtitle.actions": "Ações",
"page.keyboard_shortcuts.subtitle.items": "Navegação de itens",
"page.keyboard_shortcuts.subtitle.pages": "Navegação de páginas",
"page.keyboard_shortcuts.subtitle.sections": "Navegação de seções",
"page.keyboard_shortcuts.title": "Atalhos de teclado",
"page.keyboard_shortcuts.toggle_star_status": "Marcar ou desmarcar como favorito",
"page.keyboard_shortcuts.toggle_entry_attachments": "Alternar abrir/fechar anexos do item",
"page.keyboard_shortcuts.toggle_read_status_next": "Inverter estado de leitura do item, focar próximo item",
"page.keyboard_shortcuts.toggle_read_status_prev": "Inverter estado de leitura do item, focar item anterior",
"page.login.google_signin": "Iniciar Sessão com sua conta do Google",
"page.login.oidc_signin": "Iniciar Sessão com sua conta do %s",
"page.login.title": "Iniciar Sessão",
"page.login.webauthn_login": "Entrar com senha",
"page.login.webauthn_login.error": "Não é possível fazer login com senha",
"page.login.webauthn_login.help": "Please enter your username if you're using a security key. This is not required if you are using a Passkey (discoverable credentials).",
"page.new_api_key.title": "Nova chave de API",
"page.new_category.title": "Nova categoria",
"page.new_user.title": "Novo usuário",
"page.offline.message": "Você está offline",
"page.offline.refresh_page": "Tente atualizar a página",
"page.offline.title": "Modo offline",
"page.read_entry_count": [
"%d item lido",
"%d itens lidos"
],
"page.search.title": "Resultados da busca",
"page.sessions.table.actions": "Ações",
"page.sessions.table.current_session": "Sessão Atual",
"page.sessions.table.date": "Data",
"page.sessions.table.ip": "Endereço IP",
"page.sessions.table.user_agent": "Agente de usuário",
"page.sessions.title": "Sessões",
"page.settings.link_google_account": "Vincular minha conta do Google",
"page.settings.link_oidc_account": "Vincular minha conta do %s",
"page.settings.title": "Ajustes",
"page.settings.unlink_google_account": "Desvincular minha conta do Google",
"page.settings.unlink_oidc_account": "Desvincular minha conta do %s",
"page.settings.webauthn.actions": "Ações",
"page.settings.webauthn.added_on": "Adicionado em",
"page.settings.webauthn.delete": [
"Remover %d senha",
"Remover %d senhas"
],
"page.settings.webauthn.last_seen_on": "Último uso",
"page.settings.webauthn.passkey_name": "Nome da senha",
"page.settings.webauthn.passkeys": "Senhas",
"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 item compartilhado",
"%d itens compartilhados"
],
"page.starred.title": "Favoritos",
"page.starred_entry_count": [
"%d item favorito",
"%d itens favoritos"
],
"page.total_entry_count": [
"%d item no total",
"%d itens no total"
],
"page.unread.title": "Não lidos",
"page.unread_entry_count": [
"%d item não lido",
"%d itens não lidos"
],
"page.users.actions": "Ações",
"page.users.admin.no": "Não",
"page.users.admin.yes": "Sim",
"page.users.is_admin": "Administrador",
"page.users.last_login": "Último acesso",
"page.users.never_logged": "Nunca",
"page.users.title": "Usuários",
"page.users.username": "Nome de usuário",
"page.webauthn_rename.title": "Renomear senha",
"pagination.first": "Primeira",
"pagination.last": "Última",
"pagination.next": "Próximo",
"pagination.previous": "Anterior",
"search.label": "Buscar",
"search.placeholder": "Buscar por...",
"search.submit": "Buscar",
"skip_to_content": "Pular para o conteúdo",
"time_elapsed.days": [
"há %d dia",
"há %d dias"
],
"time_elapsed.hours": [
"há %d hora",
"há %d horas"
],
"time_elapsed.minutes": [
"há %d minuto",
"há %d minutos"
],
"time_elapsed.months": [
"há %d mês",
"há %d meses"
],
"time_elapsed.not_yet": "ainda não",
"time_elapsed.now": "agora mesmo",
"time_elapsed.weeks": [
"há %d semana",
"há %d semanas"
],
"time_elapsed.years": [
"há %d ano",
"há %d anos"
],
"time_elapsed.yesterday": "ontem",
"tooltip.keyboard_shortcuts": "Atalho do teclado: %s",
"tooltip.logged_user": "Autenticado como %s"
} v2-2.2.13/internal/locale/translations/ro_RO.json 0000664 0000000 0000000 00000116153 15062123773 0021652 0 ustar 00root root 0000000 0000000 {
"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_starred": "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.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.starred.toast.off": "Fără stea",
"entry.starred.toast.on": "Cu stea",
"entry.starred.toggle.off": "Fără stea",
"entry.starred.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.mark_as_unread": "Marcați ca necitit",
"entry.status.title": "Modifică starea intrării",
"entry.status.toast.read": "Marcat ca citit",
"entry.status.toast.unread": "Marcat ca necitit",
"entry.tags.label": "Etichete:",
"entry.tags.more_tags_label": [
"Afișează încă o etichetă",
"Afișează încă %d etichete",
"Afișează încă %d de 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_categories_sorting_order": "Ordinea de sortare a categoriilor nu este validă.",
"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_entry_order": "Direcție de sortare invalidă.",
"error.invalid_feed_proxy_url": "URL proxy invalid.",
"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.proxy_url_not_empty": "URL-ul proxy nu poate fi gol.",
"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 este invalidă. Vă rugăm să furnizați o listă de domenii separate prin spațiu.",
"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.",
"error.linktaco_missing_required_fields": "LinkTaco API Token și Organization Slug sunt necesare",
"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.block_filter_entry_rules": "Reguli de Blocare a Intrărilor",
"form.feed.label.blocklist_rules": "Filtre de Blocare Bazate pe Regex",
"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": "Utilizați proxy-ul configurat la nivelul aplicației",
"form.feed.label.hide_globally": "Ascunde intrările în lista globală de articole necitite",
"form.feed.label.ignore_http_cache": "Ignoră cache HTTP",
"form.feed.label.keep_filter_entry_rules": "Reguli de Permitere a Intrărilor",
"form.feed.label.keeplist_rules": "Filtre de Păstrare Bazate pe Regex",
"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.proxy_url": "URL Proxy",
"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": "Reguli de Rescriere a Conținutului",
"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": "Salvează intrările în 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.karakeep_activate": "Salvare înregistrări în Karakeep",
"form.integration.karakeep_api_key": "Cheie API Karakeep",
"form.integration.karakeep_url": "Punct acces API Karakeep",
"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.linktaco_activate": "Salvează înregistrările în LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Obțineți jetonul de acces personal la",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Tag-uri (maxim 10, separate prin virgule)",
"form.integration.linktaco_tags_hint": "Maxim 10 tag-uri, separate prin virgule",
"form.integration.linktaco_visibility": "Vizibilitate",
"form.integration.linktaco_visibility_public": "Public",
"form.integration.linktaco_visibility_private": "Privat",
"form.integration.linktaco_visibility_hint": "Vizibilitatea PRIVATĂ necesită un cont LinkTaco plătit",
"form.integration.linkwarden_activate": "Salvează intrările în Linkwarden",
"form.integration.linkwarden_api_key": "Cheie API Linkwarden",
"form.integration.linkwarden_endpoint": "URL-ul de bază 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.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_token": "RSS-Bridge authentication token",
"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_tags": "Wallabag Tags",
"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.always_open_external_links": "Citește articolele deschizând linkurile externe",
"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.open_external_links_in_new_tab": "Deschide linkurile externe într-o filă nouă (adaugă target=\"_blank\" la linkuri)",
"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.git_commit": "Git Commit:",
"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.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.categories_count": [
"%d categorie",
"%d categorii",
"%d categorie găsită"
],
"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.footer.elevator": "Back to top",
"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_star_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.title": "Înregistrări partajate",
"page.shared_entries_count": [
"%d înregistrare partajată",
"%d înregistrări partajate",
"%d înregistrări partajate"
],
"page.starred.title": "Marcate",
"page.starred_entry_count": [
"%d înregistrare marcată",
"%d Înregistrări marcate",
"%d Înregistrări marcate"
],
"page.total_entry_count": [
"%d intrare în total",
"%d intrări în total",
"%d intrări în total"
],
"page.unread.title": "Necitite",
"page.unread_entry_count": [
"%d înregistrare necitită",
"%d înregistrări necitite",
"%d înregistrări 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"
} v2-2.2.13/internal/locale/translations/ru_RU.json 0000664 0000000 0000000 00000144654 15062123773 0021675 0 ustar 00root root 0000000 0000000 {
"action.cancel": "закрыть",
"action.download": "Загрузить",
"action.edit": "Изменить",
"action.home_screen": "Добавить на домашний экран",
"action.import": "Импорт",
"action.login": "Войти",
"action.or": "или",
"action.remove": "Удалить",
"action.remove_feed": "Удалить эту подписку",
"action.save": "Сохранить",
"action.subscribe": "Подписаться",
"action.update": "Обновить",
"alert.account_linked": "Ваш внешний аккаунт теперь привязан!",
"alert.account_unlinked": "Ваш внешний аккаунт теперь отвязан!",
"alert.background_feed_refresh": "Все подписки обновляются в фоновом режиме. Вы можете продолжать использовать Miniflux пока идёт этот процесс.",
"alert.feed_error": "С этой подпиской есть проблема",
"alert.no_starred": "Избранное отсутствует.",
"alert.no_category": "Категории отсутствуют.",
"alert.no_category_entry": "В этой категории нет статей.",
"alert.no_feed": "У вас нет ни одной подписки.",
"alert.no_feed_entry": "В этой подписке отсутствуют статьи.",
"alert.no_feed_in_category": "Для этой категории нет подписки.",
"alert.no_history": "Истории пока что нет.",
"alert.no_search_result": "Нет результатов для данного поискового запроса.",
"alert.no_shared_entry": "Общедоступные статьи отсутствуют.",
"alert.no_tag_entry": "Нет записей, соответствующих этому тегу.",
"alert.no_unread_entry": "Нет непрочитанных статей.",
"alert.no_user": "Вы единственный пользователь.",
"alert.prefs_saved": "Предпочтения сохранены!",
"alert.too_many_feeds_refresh": [
"Вы запустили слишком много обновлений подписок. Подождите %d минуту для нового запуска",
"Вы запустили слишком много обновлений подписок. Подождите %d минут для нового запуска",
"Вы запустили слишком много обновлений подписок. Подождите %d минут для нового запуска"
],
"confirm.loading": "В процессе…",
"confirm.no": "нет",
"confirm.question": "Вы уверены?",
"confirm.question.refresh": "Вы хотите выполнить принудительное обновление?",
"confirm.yes": "да",
"enclosure_media_controls.seek": "Перемотка:",
"enclosure_media_controls.seek.title": "Перемотать на %s секунд",
"enclosure_media_controls.speed": "Скорость:",
"enclosure_media_controls.speed.faster": "Быстрее",
"enclosure_media_controls.speed.faster.title": "Ускорить в %s раз",
"enclosure_media_controls.speed.reset": "Сбросить",
"enclosure_media_controls.speed.reset.title": "Сбросить скорость до 1x",
"enclosure_media_controls.speed.slower": "Медленнее",
"enclosure_media_controls.speed.slower.title": "Замедлить в %s раз",
"entry.starred.toast.off": "Без пометок",
"entry.starred.toast.on": "Помеченные",
"entry.starred.toggle.off": "Удалить из Избранного",
"entry.starred.toggle.on": "Добавить в Избранное",
"entry.comments.label": "Комментарии",
"entry.comments.title": "Показать комментарии",
"entry.estimated_reading_time": [
"%d минута чтения",
"%d минуты чтения",
"%d минут чтения"
],
"entry.external_link.label": "Внешняя ссылка",
"entry.save.completed": "Готово!",
"entry.save.label": "Сохранить",
"entry.save.title": "Сохранить эту статью",
"entry.save.toast.completed": "Статья сохранена",
"entry.scraper.completed": "Готово!",
"entry.scraper.label": "Скачать",
"entry.scraper.title": "Извлечь оригинальное содержимое",
"entry.share.label": "Поделиться",
"entry.share.title": "Поделиться этой статьёй",
"entry.shared_entry.label": "Поделиться",
"entry.shared_entry.title": "Открыть публичную ссылку",
"entry.state.loading": "Загрузка…",
"entry.state.saving": "Сохранение…",
"entry.status.mark_as_read": "Отметить как прочитанное",
"entry.status.mark_as_unread": "Пометить как непрочитанное",
"entry.status.title": "Изменить статус записи",
"entry.status.toast.read": "Помечено как прочитанное",
"entry.status.toast.unread": "Помечено как непрочитанное",
"entry.tags.label": "Теги:",
"entry.tags.more_tags_label": [
"Ещё %d тег",
"Ещё %d тега",
"Ещё %d тегов"
],
"entry.unshare.label": "Удалить из общедоступных",
"error.api_key_already_exists": "Этот API-ключ уже существует.",
"error.bad_credentials": "Неверное имя пользователя или пароль.",
"error.category_already_exists": "Эта категория уже существует.",
"error.category_not_found": "Эта категория не существует или не принадлежит этому пользователю.",
"error.database_error": "Ошибка базы данных: %v.",
"error.different_passwords": "Пароли не совпадают.",
"error.duplicate_fever_username": "Уже есть кто-то с таким же именем пользователя Fever!",
"error.duplicate_googlereader_username": "Уже есть кто-то с таким же именем пользователя Google Reader!",
"error.duplicate_linked_account": "Уже есть кто-то, кто ассоциирован с этим аккаунтом!",
"error.duplicated_feed": "Эта подписка уже существует.",
"error.empty_file": "Этот файл пуст.",
"error.entries_per_page_invalid": "Недопустимое значение количества записей на странице.",
"error.feed_already_exists": "Эта подписка уже существует.",
"error.feed_category_not_found": "Эта категория не существует или не принадлежит этому пользователю.",
"error.feed_format_not_detected": "Не удалось определить формат подписки: %v.",
"error.feed_invalid_blocklist_rule": "Правило черного списка некорректно.",
"error.feed_invalid_keeplist_rule": "Правило белого списка некорректно.",
"error.feed_mandatory_fields": "Ссылка и категория обязательны.",
"error.feed_not_found": "Эта подписка не существует или не принадлежит этому пользователю.",
"error.feed_title_not_empty": "Заголовок подписки не может быть пустым.",
"error.feed_url_not_empty": "URL-адрес подписки не может быть пустым.",
"error.fields_mandatory": "Все поля обязательны.",
"error.http_bad_gateway": "В данный момент сайт недоступен из-за ошибки шлюза. Проблема не связана с Miniflux. Пожалуйста, попробуйте позже.",
"error.http_body_read": "Невозможно прочитать тело HTTP-сообщения: %v.",
"error.http_client_error": "Ошибка HTTP-клиента: %v.",
"error.http_empty_response": "Пустой ответ HTTP. Возможно этот сайт использует защиту от ботов?",
"error.http_empty_response_body": "Пустое тело HTTP-ответа.",
"error.http_forbidden": "Доступ к сайту запрещён. Возможно этот сайт использует защиту от ботов?",
"error.http_gateway_timeout": "В данный момент сайт недоступен из-за превышения времени ожидания ответа от шлюза. Проблема не связана с Miniflux. Пожалуйста, попробуйте позже.",
"error.http_internal_server_error": "В данный момент сайт недоступен из-за ошибки сервера. Проблема не связана с Miniflux. Пожалуйста, попробуйте позже.",
"error.http_not_authorized": "Доступ к сайту запрещён. Возможно используется неправильное имя пользователя или пароль.",
"error.http_resource_not_found": "Запрашиваемый ресурс не найден. Пожалуйста, проверьте URL.",
"error.http_response_too_large": "Превышен размер HTTP-ответа. Вы можете увеличить лимит размера HTTP-ответа в настройках (для применения новых настроек потребуется перезагрузка приложения).",
"error.http_service_unavailable": "В данный момент сайт недоступен из-за ошибки сервера. Проблема не связана с Miniflux. Пожалуйста, попробуйте позже.",
"error.http_too_many_requests": "Miniflux отправил слишком много запросов к этому сайту. Пожалуйста, попробуйте позже или измените настройки приложения.",
"error.http_unexpected_status_code": "В данный момент сайт недоступен из-за непредвиденного кода HTTP-ответа: %d. Проблема не связана с Miniflux. Пожалуйста, попробуйте позже.",
"error.invalid_categories_sorting_order": "Недопустимый порядок сортировки категорий.",
"error.invalid_default_home_page": "Недопустимая домашняя страница по умолчанию!",
"error.invalid_display_mode": "Недопустимый режим отображения веб-приложения.",
"error.invalid_entry_direction": "Недопустимая сортировка записей.",
"error.invalid_entry_order": "Недопустимый порядок статей.",
"error.invalid_feed_proxy_url": "Недействительный URL прокси.",
"error.invalid_feed_url": "Недействительная ссылка подписки.",
"error.invalid_gesture_nav": "Недопустимая навигация жестами.",
"error.invalid_language": "Недопустимый язык.",
"error.invalid_site_url": "Недействительный ссылка сайта.",
"error.invalid_theme": "Недопустимая тема.",
"error.invalid_timezone": "Недопустимый часовой пояс.",
"error.network_operation": "Miniflux не может открыть сайт из-за ошибки сети: %v.",
"error.network_timeout": "Этот сайт слишком медленный и время ожидания запроса истекло: %v",
"error.password_min_length": "Вы должны использовать минимум 6 символов.",
"error.proxy_url_not_empty": "URL прокси не может быть пустым.",
"error.settings_block_rule_fieldname_invalid": "Недопустимое правило блокировки: у правила #%d отсутствует корректное имя поля (Возможные варианты: %s)",
"error.settings_block_rule_invalid_regex": "Недопустимое правило блокировки: шаблон правила #%d не является корректным регулярным выражением",
"error.settings_block_rule_regex_required": "Недопустимое правило блокировки: не указан шаблон для правила #%d",
"error.settings_block_rule_separator_required": "Недопустимое правило блокировки: шаблон правила #%d должен быть отделен символом '='",
"error.settings_invalid_domain_list": "Недопустимый список доменов. Пожалуйста, укажите список доменов, разделенных пробелами.",
"error.settings_keep_rule_fieldname_invalid": "Недопустимое правило сохранения: у правила #%d отсутствует корректное имя поля (Возможные варианты: %s)",
"error.settings_keep_rule_invalid_regex": "Недопустимое правило сохранения: шаблон правила #%d не является корректным регулярным выражением",
"error.settings_keep_rule_regex_required": "Недопустимое правило сохранения: не указан шаблон для правила #%d",
"error.settings_keep_rule_separator_required": "Недопустимое правило сохранения: шаблон правила #%d должен быть отделен символом '='",
"error.settings_mandatory_fields": "Имя пользователя, тема, язык и часовой пояс обязательны.",
"error.settings_media_playback_rate_range": "Скорость воспроизведения выходит за пределы диапазона",
"error.settings_reading_speed_is_positive": "Скорость чтения должна быть целым положительным числом.",
"error.site_url_not_empty": "Ссылка на сайт не может быть пустой.",
"error.subscription_not_found": "Не удалось найти подписки.",
"error.title_required": "Название обязательно.",
"error.tls_error": "Ошибка TLS: %q. Вы можете отключить проверку TLS в настройках подписки.",
"error.unable_to_create_api_key": "Невозможно создать этот API-ключ.",
"error.unable_to_create_category": "Не удалось создать эту категорию.",
"error.unable_to_create_user": "Не удалось создать этого пользователя.",
"error.unable_to_detect_rssbridge": "Не удалось обнаружить подписку с помощью RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Не удалось обработать эту подписку: %v.",
"error.unable_to_update_category": "Не удалось обновить эту категорию.",
"error.unable_to_update_feed": "Не удалось обновить эту подписку.",
"error.unable_to_update_user": "Не удалось обновить этого пользователя.",
"error.unlink_account_without_password": "Вы должны установить пароль, иначе вы не сможете войти снова.",
"error.user_already_exists": "Этот пользователь уже существует.",
"error.user_mandatory_fields": "Имя пользователя обязательно.",
"error.linktaco_missing_required_fields": "LinkTaco API Token и Organization Slug обязательны",
"form.api_key.label.description": "Описание API-ключа",
"form.category.hide_globally": "Скрыть записи в глобальном списке непрочитанных",
"form.category.label.title": "Название",
"form.feed.fieldset.general": "Общие",
"form.feed.fieldset.integration": "Сторонние сервисы",
"form.feed.fieldset.network_settings": "Настройки сети",
"form.feed.fieldset.rules": "Правила",
"form.feed.label.allow_self_signed_certificates": "Разрешить самоподписанные или недействительные сертификаты",
"form.feed.label.apprise_service_urls": "Список ссылок сервисов Apprise, разделенный запятой",
"form.feed.label.block_filter_entry_rules": "Правила блокировки записей",
"form.feed.label.blocklist_rules": "Фильтры блокировки на основе регулярных выражений",
"form.feed.label.category": "Категория",
"form.feed.label.cookie": "Установить куки",
"form.feed.label.crawler": "Извлечь оригинальное содержимое",
"form.feed.label.description": "Описание",
"form.feed.label.disable_http2": "Отключить HTTP/2 для предотвращения фингерпринтинга",
"form.feed.label.disabled": "Не обновлять эту подписку",
"form.feed.label.feed_password": "Пароль подписки",
"form.feed.label.feed_url": "Адрес подписки",
"form.feed.label.feed_username": "Имя пользователя подписки",
"form.feed.label.fetch_via_proxy": "Использовать прокси, настроенный на уровне приложения",
"form.feed.label.hide_globally": "Скрыть записи в глобальном списке непрочитанных",
"form.feed.label.ignore_http_cache": "Игнорировать HTTP кеш",
"form.feed.label.keep_filter_entry_rules": "Правила разрешения записей",
"form.feed.label.keeplist_rules": "Фильтры сохранения на основе регулярных выражений",
"form.feed.label.no_media_player": "Отключить медиаплеер (аудио и видео)",
"form.feed.label.ntfy_activate": "Отправлять статьи в ntfy",
"form.feed.label.ntfy_default_priority": "По умолчанию",
"form.feed.label.ntfy_high_priority": "Высший",
"form.feed.label.ntfy_low_priority": "Низкий",
"form.feed.label.ntfy_max_priority": "Высокий",
"form.feed.label.ntfy_min_priority": "Минимальный",
"form.feed.label.ntfy_priority": "Приоритет ntfy",
"form.feed.label.ntfy_topic": "Топик ntfy (опционально)",
"form.feed.label.proxy_url": "URL прокси",
"form.feed.label.pushover_activate": "Отправлять статьи в pushover.net",
"form.feed.label.pushover_default_priority": "По умолчанию",
"form.feed.label.pushover_high_priority": "Высокий",
"form.feed.label.pushover_low_priority": "Низкий",
"form.feed.label.pushover_max_priority": "Высший",
"form.feed.label.pushover_min_priority": "Минимальный",
"form.feed.label.pushover_priority": "Приоритет сообщений Pushover",
"form.feed.label.rewrite_rules": "Правила переписывания содержимого",
"form.feed.label.scraper_rules": "Правила сборщика",
"form.feed.label.site_url": "Адрес сайта",
"form.feed.label.title": "Название",
"form.feed.label.urlrewrite_rules": "Правила перезаписи URL",
"form.feed.label.user_agent": "Переопределить User-Agent по умолчанию",
"form.feed.label.webhook_url": "Переопределить URL вебхука",
"form.import.label.file": "OPML файл",
"form.import.label.url": "Ссылка",
"form.integration.apprise_activate": "Отправить статьи в Apprise",
"form.integration.apprise_services_url": "Список ссылок сервисов Apprise, разделенный запятой",
"form.integration.apprise_url": "Ссылка на Apprise API",
"form.integration.betula_activate": "Сохранять статьи в Betula",
"form.integration.betula_token": "Токен Betula",
"form.integration.betula_url": "Адрес сервера Betula",
"form.integration.cubox_activate": "Сохранять статьи в Cubox",
"form.integration.cubox_api_link": "Ссылка на Cubox API",
"form.integration.discord_activate": "Отправить статьи в Discord",
"form.integration.discord_webhook_link": "Ссылка на Discord Webhook",
"form.integration.espial_activate": "Сохранять статьи в Espial",
"form.integration.espial_api_key": "API-ключ Espial",
"form.integration.espial_endpoint": "Конечная точка Espial API",
"form.integration.espial_tags": "Теги Espial",
"form.integration.fever_activate": "Активировать Fever API",
"form.integration.fever_endpoint": "Конечная точка Fever API:",
"form.integration.fever_password": "Пароль Fever",
"form.integration.fever_username": "Имя пользователя Fever",
"form.integration.googlereader_activate": "Активировать Google Reader API",
"form.integration.googlereader_endpoint": "Конечная точка Google Reader API:",
"form.integration.googlereader_password": "Пароль Google Reader",
"form.integration.googlereader_username": "Имя пользователя Google Reader",
"form.integration.instapaper_activate": "Сохранять статьи в Instapaper",
"form.integration.instapaper_password": "Пароль Instapaper",
"form.integration.instapaper_username": "Имя пользователя Instapaper",
"form.integration.karakeep_activate": "Сохранять статьи в Karakeep",
"form.integration.karakeep_api_key": "API-ключ Karakeep",
"form.integration.karakeep_url": "Конечная точка Karakeep API",
"form.integration.linkace_activate": "Сохранять статьи в LinkAce",
"form.integration.linkace_api_key": "API-ключ LinkAce",
"form.integration.linkace_check_disabled": "Отключить проверку ссылок",
"form.integration.linkace_endpoint": "Конечная точка LinkAce API",
"form.integration.linkace_is_private": "Отмечать ссылки как приватные",
"form.integration.linkace_tags": "Теги LinkAce",
"form.integration.linkding_activate": "Сохранять статьи в Linkding",
"form.integration.linkding_api_key": "API-ключ Linkding",
"form.integration.linkding_bookmark": "Помечать закладки как непрочитанное",
"form.integration.linkding_endpoint": "Конечная точка Linkding API",
"form.integration.linkding_tags": "Теги Linkding",
"form.integration.linktaco_activate": "Сохранять статьи в LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Получить ваш персональный токен доступа на",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Теги (макс. 10, через запятую)",
"form.integration.linktaco_tags_hint": "Максимум 10 тегов, через запятую",
"form.integration.linktaco_visibility": "Видимость",
"form.integration.linktaco_visibility_public": "Публично",
"form.integration.linktaco_visibility_private": "Приватно",
"form.integration.linktaco_visibility_hint": "ПРИВАТНАЯ видимость требует платного аккаунта LinkTaco",
"form.integration.linkwarden_activate": "Сохранять статьи в Linkwarden",
"form.integration.linkwarden_api_key": "API-ключ Linkwarden",
"form.integration.linkwarden_endpoint": "Базовый URL-адрес Linkwarden",
"form.integration.matrix_bot_activate": "Отправлять статьи в Matrix",
"form.integration.matrix_bot_chat_id": "ID комнаты Matrix",
"form.integration.matrix_bot_password": "Пароль пользователя Matrix",
"form.integration.matrix_bot_url": "Ссылка на сервер Matrix",
"form.integration.matrix_bot_user": "Имя пользователя Matrix",
"form.integration.notion_activate": "Сохранить статьи в Notion",
"form.integration.notion_page_id": "Идентификатор страницы Notion",
"form.integration.notion_token": "Секретный токен Notion",
"form.integration.ntfy_activate": "Отправлять статьи в ntfy",
"form.integration.ntfy_api_token": "API-токен ntfy (опционально)",
"form.integration.ntfy_icon_url": "URL иконки ntfy (опционально)",
"form.integration.ntfy_internal_links": "Использовать внутренние ссылки по клику (опционально)",
"form.integration.ntfy_password": "Пароль ntfy (опционально)",
"form.integration.ntfy_topic": "Тема ntfy (по умолчанию, если не задана в подписке)",
"form.integration.ntfy_url": "URL ntfy (опционально, по умолчанию ntfy.sh)",
"form.integration.ntfy_username": "Имя пользователя ntfy (опционально)",
"form.integration.nunux_keeper_activate": "Сохранять статьи в Nunux Keeper",
"form.integration.nunux_keeper_api_key": "API-ключ Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Конечная точка Nunux Keeper API",
"form.integration.omnivore_activate": "Сохранять статьи в Omnivore",
"form.integration.omnivore_api_key": "API-ключ Omnivore",
"form.integration.omnivore_url": "Конечная точка Omnivore API",
"form.integration.pinboard_activate": "Сохранять статьи в Pinboard",
"form.integration.pinboard_bookmark": "Помечать закладки как непрочитанное",
"form.integration.pinboard_tags": "Теги Pinboard",
"form.integration.pinboard_token": "Токен Pinboard API",
"form.integration.pushover_activate": "Отправлять статьи Pushover",
"form.integration.pushover_device": "Устройство Pushover (опционально)",
"form.integration.pushover_prefix": "URL-префикс Pushover (опционально)",
"form.integration.pushover_token": "API-токен приложения Pushover",
"form.integration.pushover_user": "Пользовательский ключ Pushover",
"form.integration.raindrop_activate": "Сохранять статьи в Raindrop",
"form.integration.raindrop_collection_id": "ID коллекции",
"form.integration.raindrop_tags": "Теги (через запятую)",
"form.integration.raindrop_token": "Токен (тестовый)",
"form.integration.readeck_activate": "Сохранять статьи в Readeck",
"form.integration.readeck_api_key": "API-ключ Readeck",
"form.integration.readeck_endpoint": "Конечная точка Readeck API",
"form.integration.readeck_labels": "Теги Readeck",
"form.integration.readeck_only_url": "Отправлять только ссылку (без содержимого)",
"form.integration.readwise_activate": "Сохранить статьи в Readwise",
"form.integration.readwise_api_key": "Токен доступа в Readwise",
"form.integration.readwise_api_key_link": "Получить токен доступа Readwise",
"form.integration.rssbridge_activate": "Проверять RSS-Bridge при добавлении подписок",
"form.integration.rssbridge_token": "Токен аутентификации RSS-Bridge",
"form.integration.rssbridge_url": "URL сервера RSS-Bridge",
"form.integration.shaarli_activate": "Сохранить статьи в Shaarli",
"form.integration.shaarli_api_secret": "Секретный ключ Shaarli API",
"form.integration.shaarli_endpoint": "Ссылка Shaarli",
"form.integration.shiori_activate": "Сохранять статьи в Shiori",
"form.integration.shiori_endpoint": "Конечная точка Shiori API",
"form.integration.shiori_password": "Пароль Shiori",
"form.integration.shiori_username": "Имя пользователя Shiori",
"form.integration.slack_activate": "Отправить статьи в Slack",
"form.integration.slack_webhook_link": "Ссылка на Slack Webhook",
"form.integration.telegram_bot_activate": "Отправлять статьи в Telegram-чат",
"form.integration.telegram_bot_disable_buttons": "Отключить кнопки",
"form.integration.telegram_bot_disable_notification": "Отключить уведомления",
"form.integration.telegram_bot_disable_web_page_preview": "Отключить предпросмотр веб-страниц",
"form.integration.telegram_bot_token": "Токен бота",
"form.integration.telegram_chat_id": "ID чата",
"form.integration.telegram_topic_id": "ID топика",
"form.integration.wallabag_activate": "Сохранять статьи в Wallabag",
"form.integration.wallabag_client_id": "Номер клиента Wallabag",
"form.integration.wallabag_client_secret": "Секретный код клиента Wallabag",
"form.integration.wallabag_endpoint": "URL-адрес базы Валлабаг",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.wallabag_only_url": "Отправлять только ссылку (без содержимого)",
"form.integration.wallabag_password": "Пароль Wallabag",
"form.integration.wallabag_username": "Имя пользователя Wallabag",
"form.integration.webhook_activate": "Включить вебхуки",
"form.integration.webhook_secret": "Секретный ключ для вебхуков",
"form.integration.webhook_url": "Адрес вебхуков",
"form.prefs.fieldset.application_settings": "Настройки приложения",
"form.prefs.fieldset.authentication_settings": "Настройки аутентификации",
"form.prefs.fieldset.global_feed_settings": "Глобальные настройки подписок",
"form.prefs.fieldset.reader_settings": "Настройки чтения",
"form.prefs.help.external_font_hosts": "Список разрешённых внешних хостов для шрифтов, разделенных пробелами. Например: \"fonts.gstatic.com fonts.googleapis.com\".",
"form.prefs.label.always_open_external_links": "Читать статьи, открывая внешние ссылки",
"form.prefs.label.categories_sorting_order": "Сортировка категорий",
"form.prefs.label.cjk_reading_speed": "Скорость чтения на китайском, корейском и японском языках (знаков в минуту)",
"form.prefs.label.custom_css": "Пользовательский CSS",
"form.prefs.label.custom_js": "Пользовательский JavaScript",
"form.prefs.label.default_home_page": "Домашняя страница по умолчанию",
"form.prefs.label.default_reading_speed": "Скорость чтения на других языках (слов в минуту)",
"form.prefs.label.display_mode": "Режим отображения Progressive Web App (PWA)",
"form.prefs.label.entries_per_page": "Количество статей на страницу",
"form.prefs.label.entry_order": "Столбец сортировки статей",
"form.prefs.label.entry_sorting": "Сортировка статей",
"form.prefs.label.entry_swipe": "Включить пролистывание свайпом на сенсорных экранах",
"form.prefs.label.external_font_hosts": "Внешние хосты шрифтов",
"form.prefs.label.gesture_nav": "Жест для перехода между статьями",
"form.prefs.label.keyboard_shortcuts": "Включить горячие клавиши",
"form.prefs.label.language": "Язык",
"form.prefs.label.mark_read_manually": "Отмечать статьи как прочитанные вручную",
"form.prefs.label.mark_read_on_media_completion": "Отмечать как прочитанное только когда воспроизведение аудио/видео достигает 90%% завершения",
"form.prefs.label.mark_read_on_view": "Автоматически отмечать записи как прочитанные при просмотре",
"form.prefs.label.mark_read_on_view_or_media_completion": "Отмечать статьи как прочитанные при просмотре. Для аудио/видео - при 90%% завершения воспроизведения",
"form.prefs.label.media_playback_rate": "Скорость воспроизведения аудио/видео",
"form.prefs.label.open_external_links_in_new_tab": "Открывать внешние ссылки в новой вкладке (добавляет target=\"_blank\" к ссылкам)",
"form.prefs.label.show_reading_time": "Показать примерное время чтения статей",
"form.prefs.label.theme": "Тема",
"form.prefs.label.timezone": "Часовой пояс",
"form.prefs.select.alphabetical": "В алфавитном порядке",
"form.prefs.select.browser": "Браузер",
"form.prefs.select.created_time": "Время создания статьи",
"form.prefs.select.fullscreen": "Полноэкранный",
"form.prefs.select.minimal_ui": "Минимальный",
"form.prefs.select.none": "Отключить",
"form.prefs.select.older_first": "Сначала старые записи",
"form.prefs.select.publish_time": "Время публикации статьи",
"form.prefs.select.recent_first": "Сначала новые записи",
"form.prefs.select.standalone": "Автономный",
"form.prefs.select.swipe": "Свайп",
"form.prefs.select.tap": "Двойное нажатие",
"form.prefs.select.unread_count": "Количество непрочитанных",
"form.submit.loading": "Загрузка…",
"form.submit.saving": "Сохранение…",
"form.user.label.admin": "Администратор",
"form.user.label.confirmation": "Подтверждение пароля",
"form.user.label.password": "Пароль",
"form.user.label.username": "Имя пользователя",
"menu.about": "О приложении",
"menu.add_feed": "Добавить подписку",
"menu.add_user": "Добавить пользователя",
"menu.api_keys": "API-ключи",
"menu.categories": "Категории",
"menu.create_api_key": "Создать новый API-ключ",
"menu.create_category": "Создать категорию",
"menu.edit_category": "Изменить",
"menu.edit_feed": "Изменить",
"menu.export": "Экспорт",
"menu.feed_entries": "Статьи",
"menu.feeds": "Подписки",
"menu.flush_history": "Очистить историю",
"menu.history": "История",
"menu.home_page": "Главная",
"menu.import": "Импорт",
"menu.integrations": "Интеграции",
"menu.logout": "Выйти",
"menu.mark_all_as_read": "Отметить всё как прочитанное",
"menu.mark_page_as_read": "Отметить эту страницу прочитанной",
"menu.preferences": "Предпочтения",
"menu.refresh_all_feeds": "Обновить все подписки в фоне",
"menu.refresh_feed": "Обновить",
"menu.search": "Поиск",
"menu.sessions": "Сессии",
"menu.settings": "Настройки",
"menu.shared_entries": "Общие записи",
"menu.show_all_entries": "Показать все статьи",
"menu.show_only_starred_entries": "Показывать только избранные статьи",
"menu.show_only_unread_entries": "Показывать только непрочитанные статьи",
"menu.starred": "Избранное",
"menu.title": "Меню",
"menu.unread": "Непрочитанное",
"menu.users": "Пользователи",
"page.about.author": "Автор:",
"page.about.build_date": "Дата сборки:",
"page.about.credits": "Авторы",
"page.about.db_usage": "Размер базы данных:",
"page.about.git_commit": "Git-коммит:",
"page.about.global_config_options": "Глобальные параметры конфигурации",
"page.about.go_version": "Версия Go:",
"page.about.license": "Лицензия:",
"page.about.postgres_version": "Версия PostgreSQL:",
"page.about.title": "О приложении",
"page.about.version": "Версия:",
"page.add_feed.choose_feed": "Выберите подписку",
"page.add_feed.label.url": "Ссылка",
"page.add_feed.legend.advanced_options": "Расширенные настройки",
"page.add_feed.no_category": "Категории отсутствуют. У вас должна быть хотя бы одна категория.",
"page.add_feed.submit": "Найти подписку",
"page.add_feed.title": "Новая подписка",
"page.api_keys.never_used": "Никогда не использовался",
"page.api_keys.table.actions": "Действия",
"page.api_keys.table.created_at": "Дата создания",
"page.api_keys.table.description": "Описание",
"page.api_keys.table.last_used_at": "Последнее использование",
"page.api_keys.table.token": "Токен",
"page.api_keys.title": "API-ключи",
"page.categories.entries": "Статьи",
"page.categories.feed_count": [
"Есть %d подписка.",
"Есть %d подписки.",
"Есть %d подписок."
],
"page.categories.feeds": "Подписки",
"page.categories.no_feed": "Нет подписок.",
"page.categories.title": "Категории",
"page.categories_count": [
"%d категория",
"%d категории",
"%d категорий"
],
"page.category_label": "Категории: %s",
"page.edit_category.title": "Изменить категорию: %s",
"page.edit_feed.etag_header": "Заголовок ETag:",
"page.edit_feed.last_check": "Последняя проверка:",
"page.edit_feed.last_modified_header": "Заголовок LastModified:",
"page.edit_feed.last_parsing_error": "Последняя ошибка парсинга",
"page.edit_feed.no_header": "Отсутствует",
"page.edit_feed.title": "Изменить подписку: %s",
"page.edit_user.title": "Изменить пользователя: %s",
"page.entry.attachments": "Вложения",
"page.feeds.error_count": [
"%d ошибка",
"%d ошибки",
"%d ошибок"
],
"page.feeds.last_check": "Последнее обновление:",
"page.feeds.next_check": "Следующее обновление:",
"page.feeds.read_counter": "Количество прочитанных статей",
"page.feeds.title": "Подписки",
"page.footer.elevator": "Back to top",
"page.history.title": "История",
"page.import.title": "Импорт",
"page.integration.bookmarklet": "Букмарклет",
"page.integration.bookmarklet.help": "Эта специальная ссылка позволит вам подписаться на сайт, используя обыкновенную закладку в вашем браузере.",
"page.integration.bookmarklet.instructions": "Перетащите эту ссылку в ваши закладки.",
"page.integration.bookmarklet.name": "Добавить в Miniflux",
"page.integration.miniflux_api": "API Miniflux",
"page.integration.miniflux_api_endpoint": "Конечная точка API",
"page.integration.miniflux_api_password": "Пароль",
"page.integration.miniflux_api_password_value": "Пароль вашего аккаунта",
"page.integration.miniflux_api_username": "Имя пользователя",
"page.integrations.title": "Интеграции",
"page.keyboard_shortcuts.close_modal": "Закрыть модальный диалог",
"page.keyboard_shortcuts.download_content": "Загрузить оригинальное содержимое",
"page.keyboard_shortcuts.go_to_bottom_item": "Перейти к нижнему элементу",
"page.keyboard_shortcuts.go_to_categories": "Перейти к Категориям",
"page.keyboard_shortcuts.go_to_feed": "Перейти к подписке",
"page.keyboard_shortcuts.go_to_feeds": "Перейти к Подпискам",
"page.keyboard_shortcuts.go_to_history": "Перейти к Истории",
"page.keyboard_shortcuts.go_to_next_item": "Перейти к следующему элементу",
"page.keyboard_shortcuts.go_to_next_page": "Перейти к следующей странице",
"page.keyboard_shortcuts.go_to_previous_item": "Перейти к предыдущему элементу",
"page.keyboard_shortcuts.go_to_previous_page": "Перейти к предыдущей странице",
"page.keyboard_shortcuts.go_to_search": "Установить фокус в поисковой форме",
"page.keyboard_shortcuts.go_to_settings": "Перейти к Настройкам",
"page.keyboard_shortcuts.go_to_starred": "Перейти к Избранному",
"page.keyboard_shortcuts.go_to_top_item": "Перейти к верхнему элементу",
"page.keyboard_shortcuts.go_to_unread": "Перейти к Непрочитанным",
"page.keyboard_shortcuts.mark_page_as_read": "Отметить текущую страницу прочитанной",
"page.keyboard_shortcuts.open_comments": "Открыть ссылку для комментариев",
"page.keyboard_shortcuts.open_comments_same_window": "Открыть ссылку на комментарии в текущей вкладке",
"page.keyboard_shortcuts.open_item": "Открыть выбранный элемент",
"page.keyboard_shortcuts.open_original": "Открыть оригинальную ссылку",
"page.keyboard_shortcuts.open_original_same_window": "Открыть оригинальную ссылку в текущей вкладке",
"page.keyboard_shortcuts.refresh_all_feeds": "Обновить все подписки в фоне",
"page.keyboard_shortcuts.remove_feed": "Удалить эту подписку",
"page.keyboard_shortcuts.save_article": "Сохранить статью",
"page.keyboard_shortcuts.scroll_item_to_top": "Прокрутите элемент вверх",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Показать сочетания клавиш",
"page.keyboard_shortcuts.subtitle.actions": "Действия",
"page.keyboard_shortcuts.subtitle.items": "Навигация по элементам",
"page.keyboard_shortcuts.subtitle.pages": "Навигация по страницам",
"page.keyboard_shortcuts.subtitle.sections": "Навигация по секциям",
"page.keyboard_shortcuts.title": "Горячие клавиши",
"page.keyboard_shortcuts.toggle_star_status": "Переключатель избранного",
"page.keyboard_shortcuts.toggle_entry_attachments": "Переключатель показать/скрыть вложения",
"page.keyboard_shortcuts.toggle_read_status_next": "Переключатель прочитанного, сосредоточиться на следующем",
"page.keyboard_shortcuts.toggle_read_status_prev": "Переключатель прочитанного, фокус предыдущий",
"page.login.google_signin": "Войти с помощью Google",
"page.login.oidc_signin": "Войти с помощью %s",
"page.login.title": "Войти",
"page.login.webauthn_login": "Войти с паролем",
"page.login.webauthn_login.error": "Невозможно войти с паролем",
"page.login.webauthn_login.help": "Пожалуйста, введите имя пользователя, если вы используете ключ безопасности. Это не требуется при использовании Passkey (обнаруживаемые учетные данные).",
"page.new_api_key.title": "Новый API-ключ",
"page.new_category.title": "Новая категория",
"page.new_user.title": "Новый пользователь",
"page.offline.message": "Нет соединения",
"page.offline.refresh_page": "Попробуйте обновить страницу",
"page.offline.title": "Автономный режим",
"page.read_entry_count": [
"%d прочитанная статья",
"%d прочитанных статьи",
"%d прочитанных статей"
],
"page.search.title": "Результаты поиска",
"page.sessions.table.actions": "Действия",
"page.sessions.table.current_session": "Текущая сессия",
"page.sessions.table.date": "Время",
"page.sessions.table.ip": "IP адрес",
"page.sessions.table.user_agent": "User-Agent",
"page.sessions.title": "Сессии",
"page.settings.link_google_account": "Привязать мой Google аккаунт",
"page.settings.link_oidc_account": "Привязать мой %s аккаунт",
"page.settings.title": "Настройки",
"page.settings.unlink_google_account": "Отвязать мой Google аккаунт",
"page.settings.unlink_oidc_account": "Отвязать мой %s аккаунт",
"page.settings.webauthn.actions": "Действия",
"page.settings.webauthn.added_on": "Добавлен",
"page.settings.webauthn.delete": [
"Удалить %d пароль",
"Удалить %d пароля",
"Удалить %d пароля"
],
"page.settings.webauthn.last_seen_on": "Последнее использование",
"page.settings.webauthn.passkey_name": "Название ключа доступа",
"page.settings.webauthn.passkeys": "Ключи доступа",
"page.settings.webauthn.register": "Зарегистрировать пароль",
"page.settings.webauthn.register.error": "Не удается зарегистрировать пароль",
"page.shared_entries.title": "Общедоступные статьи",
"page.shared_entries_count": [
"%d общедоступная статья",
"%d общедоступных статьи",
"%d общедоступных статей"
],
"page.starred.title": "Избранное",
"page.starred_entry_count": [
"%d избранная статья",
"%d избранные статьи",
"%d избранных статей"
],
"page.total_entry_count": [
"%d статья всего",
"%d статьи всего",
"%d статей всего"
],
"page.unread.title": "Непрочитанное",
"page.unread_entry_count": [
"%d непрочитанная статья",
"%d непрочитанных статьи",
"%d непрочитанных статей"
],
"page.users.actions": "Действия",
"page.users.admin.no": "Нет",
"page.users.admin.yes": "Да",
"page.users.is_admin": "Администратор",
"page.users.last_login": "Последний вход",
"page.users.never_logged": "Никогда",
"page.users.title": "Пользователи",
"page.users.username": "Имя пользователя",
"page.webauthn_rename.title": "Переименовать ключ доступа",
"pagination.first": "Первая",
"pagination.last": "Последняя",
"pagination.next": "Следующая",
"pagination.previous": "Предыдущая",
"search.label": "Поиск",
"search.placeholder": "Поиск…",
"search.submit": "Искать",
"skip_to_content": "Перейти к содержимому",
"time_elapsed.days": [
"%d день назад",
"%d дня назад",
"%d дней назад"
],
"time_elapsed.hours": [
"%d час назад",
"%d часа назад",
"%d часов назад"
],
"time_elapsed.minutes": [
"%d минуту назад",
"%d минуты назад",
"%d минут назад"
],
"time_elapsed.months": [
"%d месяц назад",
"%d месяца назад",
"%d месяцев назад"
],
"time_elapsed.not_yet": "ещё нет",
"time_elapsed.now": "только что",
"time_elapsed.weeks": [
"%d неделю назад",
"%d недели назад",
"%d недель назад"
],
"time_elapsed.years": [
"%d год назад",
"%d года назад",
"%d лет назад"
],
"time_elapsed.yesterday": "вчера",
"tooltip.keyboard_shortcuts": "Сочетания клавиш: %s",
"tooltip.logged_user": "Авторизован как %s"
} v2-2.2.13/internal/locale/translations/tr_TR.json 0000664 0000000 0000000 00000114124 15062123773 0021660 0 ustar 00root root 0000000 0000000 {
"action.cancel": "iptal",
"action.download": "İndir",
"action.edit": "Düzenle",
"action.home_screen": "Ana ekrana ekle",
"action.import": "İçeri Aktar",
"action.login": "Giriş",
"action.or": "veya",
"action.remove": "Kaldır",
"action.remove_feed": "Bu beslemeyi kaldır",
"action.save": "Kaydet",
"action.subscribe": "Abone Ol",
"action.update": "Güncelle",
"alert.account_linked": "Harici hesabınız bağlandı!",
"alert.account_unlinked": "Harici hesabınızın bağlantısı kaldırıldı!",
"alert.background_feed_refresh": "Tüm beslemeler arkaplanda yenileniyor. Bu süreç devam ederken Miniflux'ı kullanmaya devam edebilirsiniz.",
"alert.feed_error": "Bu beslemeyle ilgili bir problem var",
"alert.no_starred": "Yıldızlanmış makale yok.",
"alert.no_category": "Hiç kategori yok.",
"alert.no_category_entry": "Bu kategoride hiç makele yok.",
"alert.no_feed": "Hiç beslemeniz yok.",
"alert.no_feed_entry": "Bu besleme için makele yok.",
"alert.no_feed_in_category": "Bu kategori için besleme yok.",
"alert.no_history": "Şu anda hiç geçmiş yok.",
"alert.no_search_result": "Bu arama için sonuç yok",
"alert.no_shared_entry": "Paylaşılan bir makele yok.",
"alert.no_tag_entry": "Bu etiketle eşleşen hiçbir giriş yok.",
"alert.no_unread_entry": "Okunmamış makele yok",
"alert.no_user": "Tek kullanıcı sizsiniz",
"alert.prefs_saved": "Tercihler kaydedildi!",
"alert.too_many_feeds_refresh": [
"Çok fazla besleme yenilemesi başlattınız. Tekrar denemeden önce lütfen %d dakika bekleyin.",
"Çok fazla besleme yenilemesi başlattınız. Tekrar denemeden önce lütfen %d dakika bekleyin."
],
"confirm.loading": "Devam ediyor...",
"confirm.no": "hayır",
"confirm.question": "Emin misiniz?",
"confirm.question.refresh": "Zorla yenilemek istiyor musunuz?",
"confirm.yes": "evet",
"enclosure_media_controls.seek": "Sar:",
"enclosure_media_controls.seek.title": "%s saniye sar",
"enclosure_media_controls.speed": "Hız:",
"enclosure_media_controls.speed.faster": "Daha hızlı",
"enclosure_media_controls.speed.faster.title": "%sx kat daha hızlı",
"enclosure_media_controls.speed.reset": "Sıfırla",
"enclosure_media_controls.speed.reset.title": "Hızı 1x'e sıfırla",
"enclosure_media_controls.speed.slower": "Daha yavaş",
"enclosure_media_controls.speed.slower.title": "%sx kat daha yavaş",
"entry.starred.toast.off": "Yıldızsız",
"entry.starred.toast.on": "Yıldızlı",
"entry.starred.toggle.off": "Yıldızı kaldır",
"entry.starred.toggle.on": "Yıldız ekle",
"entry.comments.label": "Yorumlar",
"entry.comments.title": "Yorumları Göster",
"entry.estimated_reading_time": [
"%d dakika okuma süresi",
"%d dakika okuma süresi"
],
"entry.external_link.label": "Dış bağlantı",
"entry.save.completed": "Tamamlandı!",
"entry.save.label": "Kaydet",
"entry.save.title": "Bu makeleyi kaydet",
"entry.save.toast.completed": "Makele kaydedildi",
"entry.scraper.completed": "Tamamlandı!",
"entry.scraper.label": "İndir",
"entry.scraper.title": "Orijinal içeriği çek",
"entry.share.label": "Paylaş",
"entry.share.title": "Bu makeleyi paylaş",
"entry.shared_entry.label": "Paylaş",
"entry.shared_entry.title": "Herkese açık bağlantıyı aç",
"entry.state.loading": "Yükleniyor...",
"entry.state.saving": "Kaydediliyor...",
"entry.status.mark_as_read": "Okundu olarak işaretle",
"entry.status.mark_as_unread": "Okunmadı olarak işaretle",
"entry.status.title": "Makele okundu durumunu değiştir",
"entry.status.toast.read": "Okundu olarak işaretlendi",
"entry.status.toast.unread": "Okunmamış olarak işaretlendi",
"entry.tags.label": "Etiketler:",
"entry.tags.more_tags_label": [
"%d tane daha etiket göster",
"%d tane daha etiket göster"
],
"entry.unshare.label": "Paylaşma",
"error.api_key_already_exists": "Bu API anahtarı zaten mevcut.",
"error.bad_credentials": "Geçersiz kullanıcı veya parola.",
"error.category_already_exists": "Bu kategori zaten mevcut.",
"error.category_not_found": "Bu kategori mevcut değil ya da bu kullanıcıya ait değil.",
"error.database_error": "Veritabanı hatası: %v.",
"error.different_passwords": "Parolalar eşleşmiyor.",
"error.duplicate_fever_username": "Aynı Fever kullanıcı adına sahip başka biri zaten var!",
"error.duplicate_googlereader_username": "Aynı Google Reader kullanıcı adına sahip başka biri zaten var!",
"error.duplicate_linked_account": "Bu sağlayıcıyla ilişkilendirilmiş biri zaten var!",
"error.duplicated_feed": "Bu makele zaten var.",
"error.empty_file": "Bu dosya boş.",
"error.entries_per_page_invalid": "Sayfa başına makele sayısı geçersiz.",
"error.feed_already_exists": "Bu besleme zaten mevcut.",
"error.feed_category_not_found": "Bu kategori mevcut değil ya da bu kullanıcıya ait değil.",
"error.feed_format_not_detected": "Besleme formatı algılanamadı: %v.",
"error.feed_invalid_blocklist_rule": "Engelleme listesi kuralı geçersiz.",
"error.feed_invalid_keeplist_rule": "Saklama listesi kuralı geçersiz.",
"error.feed_mandatory_fields": "URL ve kategori zorunlu.",
"error.feed_not_found": "Bu makele mevcut değil ya da bu kullanıcıya ait değil.",
"error.feed_title_not_empty": "Besleme başlığı boş olamaz.",
"error.feed_url_not_empty": "Besleme URL'si boş olamaz.",
"error.fields_mandatory": "Tüm alanlar zorunlu.",
"error.http_bad_gateway": "Kötü ağ geçidi hatası nedeniyle bu website şu anda kullanılamıyor. Sorun Miniflux tarafında değil. Lütfen daha sonra tekrar deneyiniz.",
"error.http_body_read": "HTTP gövdesi okunamıyor: %v.",
"error.http_client_error": "HTTP istemci hatası: %v.",
"error.http_empty_response": "HTTP yanıtı boş. Belki bu web sitesi bir bot koruma mekanizması kullanıyordur?",
"error.http_empty_response_body": "HTTP yanıt gövdesi boş.",
"error.http_forbidden": "Bu siteye erişim yasak. Belki bu web sitesinin bir bot koruma mekanizması vardır?",
"error.http_gateway_timeout": "Ağ geçidi zaman aşımı hatası nedeniyle bu websitesi şu anda kullanılamıyor. Sorun Miniflux tarafında değil. Lütfen daha sonra tekrar deneyiniz.",
"error.http_internal_server_error": "Sunucu hatası nedeniyle bu websitesi şu anda kullanılamıyor. Sorun Miniflux tarafında değil. Lütfen daha sonra tekrar deneyiniz.",
"error.http_not_authorized": "Bu web sitesine erişim izni verilmemektedir. Kötü bir kullanıcı adı veya şifreden kaynaklanıyor olabilir.",
"error.http_resource_not_found": "İstenilen kaynak bulunamadı. Lütfen URL'yi doğrulayın.",
"error.http_response_too_large": "HTTP yanıtı çok büyük. Genel ayarlardan HTTP yanıt boyutu sınırını artırabilirsiniz (sunucunun yeniden başlatılmasını gerektirir).",
"error.http_service_unavailable": "Dahili sunucu hatası nedeniyle web sitesi şu anda kullanılamıyor. Sorun Miniflux tarafında değil. Lütfen daha sonra tekrar deneyiniz.",
"error.http_too_many_requests": "Miniflux bu web sitesine çok fazla istek oluşturdu. Lütfen daha sonra tekrar deneyin veya uygulama yapılandırmasını değiştirin.",
"error.http_unexpected_status_code": "Beklenmeyen bir HTTP durum kodu nedeniyle bu websitesi şu anda kullanılamıyor: %d. Sorun Miniflux tarafında değil. Lütfen daha sonra tekrar deneyiniz.",
"error.invalid_categories_sorting_order": "Geçersiz kategori sıralama düzeni.",
"error.invalid_default_home_page": "Geçersiz varsayılan ana sayfa!",
"error.invalid_display_mode": "Geçersiz web uygulaması görüntüleme modu.",
"error.invalid_entry_direction": "Geçersiz makele sıralaması.",
"error.invalid_entry_order": "Geçersiz makele sıralaması.",
"error.invalid_feed_proxy_url": "Geçersiz proxy URL'si.",
"error.invalid_feed_url": "Geçersiz besleme URL'si.",
"error.invalid_gesture_nav": "Hareketle gezinme geçersiz.",
"error.invalid_language": "Geçersiz dil.",
"error.invalid_site_url": "Geçersiz site URL'si.",
"error.invalid_theme": "Geçersiz tema.",
"error.invalid_timezone": "Geçersiz saat dilimi.",
"error.network_operation": "Miniflux bir ağ hatası nedeniyle bu websitesine erişemiyor: %v.",
"error.network_timeout": "Bu websitesi çok yavaş ve istek zaman aşımına uğradı: %v",
"error.password_min_length": "Parola en az 6 karakter içermeli.",
"error.proxy_url_not_empty": "Proxy URL'si boş olamaz.",
"error.settings_block_rule_fieldname_invalid": "Geçersiz Engelleme kuralı: #%d kuralında geçerli bir alan adı eksik (Seçenekler: %s)",
"error.settings_block_rule_invalid_regex": "Geçersiz Engelleme kuralı: #%d kuralı modeli geçerli bir düzenli ifade değil",
"error.settings_block_rule_regex_required": "Geçersiz Engelleme kuralı: #%d kuralı modeli sağlanmadı",
"error.settings_block_rule_separator_required": "Geçersiz Engelleme kuralı: #%d kuralı modelinin '=' ile ayrılması gerekiyor",
"error.settings_invalid_domain_list": "Geçersiz alan adı listesi. Lütfen boşlukla ayrılmış bir alan adı listesi girin.",
"error.settings_keep_rule_fieldname_invalid": "Geçersiz Koruma kuralı: #%d kuralında geçerli bir alan adı eksik (Seçenekler: %s)",
"error.settings_keep_rule_invalid_regex": "Geçersiz Koruma kuralı: #%d kuralı modeli geçerli bir düzenli ifade değil",
"error.settings_keep_rule_regex_required": "Geçersiz Koruma kuralı: #%d kuralı modeli sağlanmadı",
"error.settings_keep_rule_separator_required": "Geçersiz Koruma kuralı: #%d kuralı modelinin '=' ile ayrılması gerekiyor",
"error.settings_mandatory_fields": "Kullanıcı ad, tema, dil ve saat dilimi zorunlu.",
"error.settings_media_playback_rate_range": "Oynatma hızı aralık dışında",
"error.settings_reading_speed_is_positive": "Okuma hızları pozitif tam sayılar olmalıdır.",
"error.site_url_not_empty": "Site URL'si boş olamaz.",
"error.subscription_not_found": "Herhangi bir abonelik bulunamadı.",
"error.title_required": "Başlık zorunlu.",
"error.tls_error": "TLS hatası: %q. İsterseniz feed ayarlarından TLS doğrulamasını devre dışı bırakabilirsiniz.",
"error.unable_to_create_api_key": "Bu API anahtarı oluşturulamıyor.",
"error.unable_to_create_category": "Bu kategori oluşturulamıyor.",
"error.unable_to_create_user": "Bu kullanıcı oluşturulamıyor.",
"error.unable_to_detect_rssbridge": "RSS-Bridge kullanılarak besleme algılanamıyor: %v.",
"error.unable_to_parse_feed": "Bu besleme ayrıştırılamıyor: %v.",
"error.unable_to_update_category": "Bu kategori güncellenemiyor.",
"error.unable_to_update_feed": "Bu besleme güncellenemiyor.",
"error.unable_to_update_user": "Bu kullanıcı güncellenemiyor.",
"error.unlink_account_without_password": "Bir şifre belirlemelisiniz, aksi takdirde tekrar oturum açamazsınız.",
"error.user_already_exists": "Bu kullanıcı zaten mevcut.",
"error.user_mandatory_fields": "Kullanıcı adı zorunlu.",
"error.linktaco_missing_required_fields": "LinkTaco API Token ve Organization Slug gereklidir",
"form.api_key.label.description": "API Anahtar Etiketi",
"form.category.hide_globally": "Genel okunmamış listesindeki girişleri gizle",
"form.category.label.title": "Başlık",
"form.feed.fieldset.general": "Genel",
"form.feed.fieldset.integration": "Üçüncü Taraf Hizmetleri",
"form.feed.fieldset.network_settings": "Ağ Ayarları",
"form.feed.fieldset.rules": "Kurallar",
"form.feed.label.allow_self_signed_certificates": "Kendinden imzalı veya geçersiz sertifikalara izin ver",
"form.feed.label.apprise_service_urls": "Apprise hizmet URL'lerinin virgülle ayrılmış listesi",
"form.feed.label.block_filter_entry_rules": "Giriş Engelleme Kuralları",
"form.feed.label.blocklist_rules": "Regex Tabanlı Engelleme Filtreleri",
"form.feed.label.category": "Kategori",
"form.feed.label.cookie": "Çerezleri Ayarla",
"form.feed.label.crawler": "Orijinal içeriği çek",
"form.feed.label.description": "Açıklama",
"form.feed.label.disable_http2": "Parmak izini önlemek için HTTP/2'yi devre dışı bırakın",
"form.feed.label.disabled": "Bu beslemeyi yenileme",
"form.feed.label.feed_password": "Besleme Parolası",
"form.feed.label.feed_url": "Besleme URL'si",
"form.feed.label.feed_username": "Besleme Kullanıcı Adı",
"form.feed.label.fetch_via_proxy": "Uygulama düzeyinde yapılandırılmış proxy'yi kullan",
"form.feed.label.hide_globally": "Genel okunmamış listesindeki girişleri gizle",
"form.feed.label.ignore_http_cache": "HTTP önbelleğini yoksay",
"form.feed.label.keep_filter_entry_rules": "Giriş İzin Kuralları",
"form.feed.label.keeplist_rules": "Regex Tabanlı Tutma Filtreleri",
"form.feed.label.no_media_player": "Medya oynatıcı yok (ses/video)",
"form.feed.label.ntfy_activate": "Makaleleri ntfy'ye gönder",
"form.feed.label.ntfy_default_priority": "Ntfy varsayılan öncelik",
"form.feed.label.ntfy_high_priority": "Ntfy yüksek öncelik",
"form.feed.label.ntfy_low_priority": "Ntfy düşük öncelik",
"form.feed.label.ntfy_max_priority": "Ntfy maksimum öncelik",
"form.feed.label.ntfy_min_priority": "Ntfy minimum öncelik",
"form.feed.label.ntfy_priority": "Ntfy öncelik",
"form.feed.label.ntfy_topic": "Ntfy konusu (isteğe bağlı)",
"form.feed.label.proxy_url": "Proxy URL",
"form.feed.label.pushover_activate": "Makaleleri pushover.net'e gönder",
"form.feed.label.pushover_default_priority": "Pushover varsayılan öncelik",
"form.feed.label.pushover_high_priority": "Pushover yüksek öncelik",
"form.feed.label.pushover_low_priority": "Pushover düşük öncelik",
"form.feed.label.pushover_max_priority": "Pushover maksimum öncelik",
"form.feed.label.pushover_min_priority": "Pushover minimum öncelik",
"form.feed.label.pushover_priority": "Pushover mesaj önceliği",
"form.feed.label.rewrite_rules": "İçerik Yeniden Yazma Kuralları",
"form.feed.label.scraper_rules": "Scrapper Kuralları",
"form.feed.label.site_url": "Site URL'si",
"form.feed.label.title": "Başlık",
"form.feed.label.urlrewrite_rules": "URL Yeniden Yazma Kuralları",
"form.feed.label.user_agent": "Varsayılan User Agent'i Geçersiz Kıl",
"form.feed.label.webhook_url": "Webhook URL'sini geçersiz kıl",
"form.import.label.file": "OPML dosyası",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "Makaleleri Apprise'a gönder",
"form.integration.apprise_services_url": "Apprise hizmet URL'lerinin virgülle ayrılmış listesi",
"form.integration.apprise_url": "Apprise API URL",
"form.integration.betula_activate": "Makaleleri Betula'ya kaydet",
"form.integration.betula_token": "Betula Token",
"form.integration.betula_url": "Betula sunucu URLsi",
"form.integration.cubox_activate": "Makaleleri Cubox'a kaydet",
"form.integration.cubox_api_link": "Cubox API bağlantısı",
"form.integration.discord_activate": "Makaleleri Discord'a gönder",
"form.integration.discord_webhook_link": "Discord hizmet Webhook'lerinin virgülle ayrılmış listesi",
"form.integration.espial_activate": "Makaleleri Espial'e kaydet",
"form.integration.espial_api_key": "Espial API Anahtarı",
"form.integration.espial_endpoint": "Espial API Uç Noktası",
"form.integration.espial_tags": "Espial Etiketleri",
"form.integration.fever_activate": "Fever API'yi Etkinleştir",
"form.integration.fever_endpoint": "Fever API uç noktası:",
"form.integration.fever_password": "Fever Parolası",
"form.integration.fever_username": "Fever Kullanıcı Adı",
"form.integration.googlereader_activate": "Google Reader API'yi Etkinleştir",
"form.integration.googlereader_endpoint": "Google Reader API uç noktası:",
"form.integration.googlereader_password": "Google Reader Parolası",
"form.integration.googlereader_username": "Google Reader Kullanıcı Adı",
"form.integration.instapaper_activate": "Makaleleri Instapaper'a kaydet",
"form.integration.instapaper_password": "Instapaper Parolası",
"form.integration.instapaper_username": "Instapaper Kullanıcı Adı",
"form.integration.karakeep_activate": "Makaleleri Karakeep'a kaydet",
"form.integration.karakeep_api_key": "Karakeep API anahtarı",
"form.integration.karakeep_url": "Karakeep API Uç Noktası",
"form.integration.linkace_activate": "Makaleleri LinkAce'e kaydet",
"form.integration.linkace_api_key": "LinkAce API anahtarı",
"form.integration.linkace_check_disabled": "Link kontrolünü devre dışı bırak",
"form.integration.linkace_endpoint": "LinkAce API Uç Noktası",
"form.integration.linkace_is_private": "Linki özel olarak işaretle",
"form.integration.linkace_tags": "LinkAce Etiketleri",
"form.integration.linkding_activate": "Makaleleri Linkding'e kaydet",
"form.integration.linkding_api_key": "Linkding API Anahtarı",
"form.integration.linkding_bookmark": "Yer imini okunmadı olarak işaretle",
"form.integration.linkding_endpoint": "Linkding API Uç Noktası",
"form.integration.linkding_tags": "Linkding Etiketleri",
"form.integration.linktaco_activate": "Makaleleri LinkTaco'ya kaydet",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Kişisel erişim tokenınızı edinin",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Etiketler (maks 10, virgülle ayrılmış)",
"form.integration.linktaco_tags_hint": "Maksimum 10 etiket, virgülle ayrılmış",
"form.integration.linktaco_visibility": "Görünürlük",
"form.integration.linktaco_visibility_public": "Genel",
"form.integration.linktaco_visibility_private": "Özel",
"form.integration.linktaco_visibility_hint": "ÖZEL görünürlük ücretli bir LinkTaco hesabı gerektirir",
"form.integration.linkwarden_activate": "Makaleleri Linkwarden'e kaydet",
"form.integration.linkwarden_api_key": "Linkwarden API Anahtarı",
"form.integration.linkwarden_endpoint": "Linkwarden Temel URL'si",
"form.integration.matrix_bot_activate": "Yeni makaleleri Matrix'e aktarın",
"form.integration.matrix_bot_chat_id": "Matrix odasının kimliği",
"form.integration.matrix_bot_password": "Matrix kullanıcısı için parola",
"form.integration.matrix_bot_url": "Matrix sunucu URL'si",
"form.integration.matrix_bot_user": "Matrix için Kullanıcı Adı",
"form.integration.notion_activate": "Makaleleri Notion'a kaydet",
"form.integration.notion_page_id": "Notion Sayfa ID'si",
"form.integration.notion_token": "Notion Secret Token",
"form.integration.ntfy_activate": "Push entries to ntfy",
"form.integration.ntfy_api_token": "Ntfy API Token (optional)",
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Tıklamada dahili bağlantıları kullan (isteğe bağlı)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"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",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API anahtarı",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API Uç Noktası",
"form.integration.omnivore_activate": "Makaleleri Omnivore'a kaydet",
"form.integration.omnivore_api_key": "Omnivore API anahtarı",
"form.integration.omnivore_url": "Omnivore API Uç Noktası",
"form.integration.pinboard_activate": "Makaleleri Pinboard'a kaydet",
"form.integration.pinboard_bookmark": "Yer imini okunmadı olarak işaretle",
"form.integration.pinboard_tags": "Pinboard Etiketleri",
"form.integration.pinboard_token": "Pinboard API Token",
"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.raindrop_activate": "Makaleleri Raindrop'a kaydet",
"form.integration.raindrop_collection_id": "Koleksiyon ID",
"form.integration.raindrop_tags": "Etiketler (virgülle ayrılmış)",
"form.integration.raindrop_token": "(Test) Token",
"form.integration.readeck_activate": "Makaleleri Readeck'e kaydet",
"form.integration.readeck_api_key": "Readeck API Anahtarı",
"form.integration.readeck_endpoint": "Readeck API Uç Noktası",
"form.integration.readeck_labels": "Readeck Etiketleri",
"form.integration.readeck_only_url": "Yalnızca URL gönder (tam makale yerine)",
"form.integration.readwise_activate": "Makaleleri Readwise Reader'a kaydet",
"form.integration.readwise_api_key": "Readwise Reader Access Token",
"form.integration.readwise_api_key_link": "Readwise Access Token'ınızı alın",
"form.integration.rssbridge_activate": "Abonelik eklerken RSS-Bridge'i kontrol edin",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "RSS-Bridge server URL",
"form.integration.shaarli_activate": "Makaleleri Shaarli'ye kaydet",
"form.integration.shaarli_api_secret": "Shaarli API Secret",
"form.integration.shaarli_endpoint": "Shaarli URL",
"form.integration.shiori_activate": "Makaleleri Shiori'ye kaydet",
"form.integration.shiori_endpoint": "Shiori API Uç Noktası",
"form.integration.shiori_password": "Shiori Parolası",
"form.integration.shiori_username": "Shiori Kullanıcı Adı",
"form.integration.slack_activate": "Makaleleri Slack'a gönder",
"form.integration.slack_webhook_link": "Slack hizmet Webhook'lerinin virgülle ayrılmış listesi",
"form.integration.telegram_bot_activate": "Yeni makaleleri Telegram sohbetine gönderin",
"form.integration.telegram_bot_disable_buttons": "Butonları devre dışı bırak",
"form.integration.telegram_bot_disable_notification": "Bildirimleri devre dışı bırak",
"form.integration.telegram_bot_disable_web_page_preview": "Web sayfası önizlemesini devre dışı bırak",
"form.integration.telegram_bot_token": "Bot token",
"form.integration.telegram_chat_id": "Sohbet ID",
"form.integration.telegram_topic_id": "Konu ID",
"form.integration.wallabag_activate": "Makaleleri Wallabag'e kaydet",
"form.integration.wallabag_client_id": "Wallabag Client ID",
"form.integration.wallabag_client_secret": "Wallabag Client Secret",
"form.integration.wallabag_endpoint": "Wallabag Üssü URL",
"form.integration.wallabag_only_url": "Yalnızca URL gönder (tam makale yerine)",
"form.integration.wallabag_password": "Wallabag Parolası",
"form.integration.wallabag_username": "Wallabag Kullanıcı Adı",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "Webhook'u etkinleştir",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Default Webhook URL",
"form.prefs.fieldset.application_settings": "Uygulama Ayarları",
"form.prefs.fieldset.authentication_settings": "Kimlik Doğrulama Ayarları",
"form.prefs.fieldset.global_feed_settings": "Genel Besleme Ayarları",
"form.prefs.fieldset.reader_settings": "Okuyucu Ayarları",
"form.prefs.help.external_font_hosts": "İzin verilecek harici font sunucularının boşlukla ayrılmış listesi. Örneğin: 'fonts.gstatic.com fonts.googleapis.com'.",
"form.prefs.label.always_open_external_links": "Makaleleri harici bağlantıları açarak oku",
"form.prefs.label.categories_sorting_order": "Kategori sıralaması",
"form.prefs.label.cjk_reading_speed": "Çince, Korece ve Japonca için okuma hızı (dakika başına karakter)",
"form.prefs.label.custom_css": "Özel CSS",
"form.prefs.label.custom_js": "Özel JavaScript",
"form.prefs.label.default_home_page": "Varsayılan ana sayfa",
"form.prefs.label.default_reading_speed": "Diğer diller için okuma hızı (dakika başına kelime)",
"form.prefs.label.display_mode": "Progressive Web App (PWA) görüntüleme modu",
"form.prefs.label.entries_per_page": "Sayfa başına makale",
"form.prefs.label.entry_order": "Makale Sıralama Sütunu",
"form.prefs.label.entry_sorting": "Makale Sıralaması",
"form.prefs.label.entry_swipe": "Dokunmatik ekranlarda makale kaydırmayı etkinleştir",
"form.prefs.label.external_font_hosts": "Harici font sunucuları",
"form.prefs.label.gesture_nav": "Makaleler arasında gezinmek için dokunma hareketi",
"form.prefs.label.keyboard_shortcuts": "Klavye kısayollarını etkinleştir",
"form.prefs.label.language": "Dil",
"form.prefs.label.mark_read_manually": "Mark entries as read manually",
"form.prefs.label.mark_read_on_media_completion": "Only mark as read when audio/video playback reaches 90%% completion",
"form.prefs.label.mark_read_on_view": "Makaleler görüntülendiğinde otomatik olarak okundu olarak işaretle",
"form.prefs.label.mark_read_on_view_or_media_completion": "Mark entries as read when viewed. For audio/video, mark as read at 90%% completion",
"form.prefs.label.media_playback_rate": "Ses/video oynatma hızı",
"form.prefs.label.open_external_links_in_new_tab": "Harici bağlantıları yeni bir sekmede aç (bağlantılara target=\"_blank\" ekler)",
"form.prefs.label.show_reading_time": "Makaleler için tahmini okuma süresini göster",
"form.prefs.label.theme": "Tema",
"form.prefs.label.timezone": "Saat Dilimi",
"form.prefs.select.alphabetical": "Alfabetik",
"form.prefs.select.browser": "Tarayıcı",
"form.prefs.select.created_time": "İçeriğin oluşturulma zamanı",
"form.prefs.select.fullscreen": "Tam Ekran",
"form.prefs.select.minimal_ui": "Minimal",
"form.prefs.select.none": "Hiçbiri",
"form.prefs.select.older_first": "Önce eski makaleler",
"form.prefs.select.publish_time": "Makale yayınlanma zamanı",
"form.prefs.select.recent_first": "Önce yeni makaleler",
"form.prefs.select.standalone": "Bağımsız",
"form.prefs.select.swipe": "Kaydırma",
"form.prefs.select.tap": "Çift dokunma",
"form.prefs.select.unread_count": "Okunmamış sayısı",
"form.submit.loading": "Yükleniyor...",
"form.submit.saving": "Kaydediliyor...",
"form.user.label.admin": "Yönetici",
"form.user.label.confirmation": "Parola Doğrulama",
"form.user.label.password": "Parola",
"form.user.label.username": "Kullanıcı Adı",
"menu.about": "Hakkında",
"menu.add_feed": "Besleme ekle",
"menu.add_user": "Kullanıcı ekle",
"menu.api_keys": "API Anahtarları",
"menu.categories": "Kategoriler",
"menu.create_api_key": "Yeni bir API anahtarı oluştur",
"menu.create_category": "Kategori oluştur",
"menu.edit_category": "Düzenle",
"menu.edit_feed": "Düzenle",
"menu.export": "Dışarı Aktar",
"menu.feed_entries": "Makaleler",
"menu.feeds": "Beslemeler",
"menu.flush_history": "Geçmişi temizle",
"menu.history": "Geçmiş",
"menu.home_page": "Anasayfa",
"menu.import": "İçeri Aktar",
"menu.integrations": "Entegrasyonlar",
"menu.logout": "Çıkış",
"menu.mark_all_as_read": "Tümünü okundu olarak işaretle",
"menu.mark_page_as_read": "Bu sayfayı okundu olarak işaretle",
"menu.preferences": "Tercihler",
"menu.refresh_all_feeds": "Tüm beslemeleri arka planda yenile",
"menu.refresh_feed": "Yenile",
"menu.search": "Ara",
"menu.sessions": "Oturumlar",
"menu.settings": "Ayarlar",
"menu.shared_entries": "Paylaşılan makaleler",
"menu.show_all_entries": "Tüm makaleleri göster",
"menu.show_only_starred_entries": "Sadece yıldızlanmış makaleleri göster",
"menu.show_only_unread_entries": "Sadece okunmamış makaleleri göster",
"menu.starred": "Yıldız",
"menu.title": "Menü",
"menu.unread": "Okunmadı",
"menu.users": "Kullanıcılar",
"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.git_commit": "Git Commit:",
"page.about.global_config_options": "Global yapılandırma seçenekleri",
"page.about.go_version": "Go sürümü:",
"page.about.license": "Lisans:",
"page.about.postgres_version": "Postgres sürümü:",
"page.about.title": "Hakkında",
"page.about.version": "Sürüm:",
"page.add_feed.choose_feed": "Bir Besleme Seçin",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Gelişmiş Seçenekler",
"page.add_feed.no_category": "Kategori yok. En az bir kategoriye sahip olmalısınız.",
"page.add_feed.submit": "Besleme bul",
"page.add_feed.title": "Yeni Besleme",
"page.api_keys.never_used": "Hiç Kullanılmadı",
"page.api_keys.table.actions": "Hareketler",
"page.api_keys.table.created_at": "Oluşturulma Tarihi",
"page.api_keys.table.description": "Açıklama",
"page.api_keys.table.last_used_at": "Son Kullanılma",
"page.api_keys.table.token": "Token",
"page.api_keys.title": "API Anahtarları",
"page.categories.entries": "Makaleler",
"page.categories.feed_count": [
"%d besleme var.",
"%d besleme var."
],
"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ığı:",
"page.edit_feed.last_check": "Son kontrol:",
"page.edit_feed.last_modified_header": "LastModified başlığı:",
"page.edit_feed.last_parsing_error": "Son Ayrıştırma Hatası",
"page.edit_feed.no_header": "Hiçbiri",
"page.edit_feed.title": "Beslemeyi düzenle: %s",
"page.edit_user.title": "Kullanıcıyı Düzenle: %s",
"page.entry.attachments": "Ekler",
"page.feeds.error_count": [
"%d hatası",
"%d hatası"
],
"page.feeds.last_check": "Son kontrol:",
"page.feeds.next_check": "Sonraki kontrol:",
"page.feeds.read_counter": "Okunmuş makalelerin sayısı",
"page.feeds.title": "Beslemeler",
"page.footer.elevator": "Back to top",
"page.history.title": "Geçmiş",
"page.import.title": "İçeri Aktar",
"page.integration.bookmarklet": "Bookmarklet",
"page.integration.bookmarklet.help": "Bu özel bağlantı, web tarayıcınızdaki yer imini kullanarak bir websitesine doğrudan abone olmanızı sağlar.",
"page.integration.bookmarklet.instructions": "Bu bağlantıyı yer imlerinize sürükleyip bırakın",
"page.integration.bookmarklet.name": "Miniflux'a Ekle",
"page.integration.miniflux_api": "Miniflux API",
"page.integration.miniflux_api_endpoint": "API Uç Noktası",
"page.integration.miniflux_api_password": "Parola",
"page.integration.miniflux_api_password_value": "Hesap parolan",
"page.integration.miniflux_api_username": "Kullanıcı adı",
"page.integrations.title": "Entegrasyonlar",
"page.keyboard_shortcuts.close_modal": "İletişim kutusunu kapat",
"page.keyboard_shortcuts.download_content": "Orijinal içeriği indir",
"page.keyboard_shortcuts.go_to_bottom_item": "Alt makeleye git",
"page.keyboard_shortcuts.go_to_categories": "Kategorilere git",
"page.keyboard_shortcuts.go_to_feed": "Beslemeye git",
"page.keyboard_shortcuts.go_to_feeds": "Beslemelere git",
"page.keyboard_shortcuts.go_to_history": "Geçmişe git",
"page.keyboard_shortcuts.go_to_next_item": "Sonraki makeleye git",
"page.keyboard_shortcuts.go_to_next_page": "Sonraki sayfaya git",
"page.keyboard_shortcuts.go_to_previous_item": "Önceki makeleye git",
"page.keyboard_shortcuts.go_to_previous_page": "Önceki sayfaya git",
"page.keyboard_shortcuts.go_to_search": "Arama formuna odakla",
"page.keyboard_shortcuts.go_to_settings": "Ayarlara git",
"page.keyboard_shortcuts.go_to_starred": "Yer imlerine git",
"page.keyboard_shortcuts.go_to_top_item": "En üstteki makeleye git",
"page.keyboard_shortcuts.go_to_unread": "Okunmamışa git",
"page.keyboard_shortcuts.mark_page_as_read": "Mevcut sayfayı okundu olarak işaretle",
"page.keyboard_shortcuts.open_comments": "Yorumlar bağlantısını aç",
"page.keyboard_shortcuts.open_comments_same_window": "Yorumlar bağlantısını mevcut sekmede aç",
"page.keyboard_shortcuts.open_item": "Seçili makeleyi aç",
"page.keyboard_shortcuts.open_original": "Orijinal bağlantıyı aç",
"page.keyboard_shortcuts.open_original_same_window": "Orijinal bağlantıyı mevcut sekmede aç",
"page.keyboard_shortcuts.refresh_all_feeds": "Tüm beslemeleri arka planda yenile",
"page.keyboard_shortcuts.remove_feed": "Bu beslemeyi kaldır",
"page.keyboard_shortcuts.save_article": "İçeriği kaydet",
"page.keyboard_shortcuts.scroll_item_to_top": "Makaleyi en üste kaydır",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Klavye kısayollarını göster",
"page.keyboard_shortcuts.subtitle.actions": "Eylemler",
"page.keyboard_shortcuts.subtitle.items": "Makalelerde Gezinme",
"page.keyboard_shortcuts.subtitle.pages": "Sayfalarda Gezinme",
"page.keyboard_shortcuts.subtitle.sections": "Bölümlerde Gezinme",
"page.keyboard_shortcuts.title": "Klavye Kısayolları",
"page.keyboard_shortcuts.toggle_star_status": "Yıldız ekle/kaldır",
"page.keyboard_shortcuts.toggle_entry_attachments": "Makele eklerini açma/kapama arasında geçiş yap",
"page.keyboard_shortcuts.toggle_read_status_next": "Okundu/okunmadı arasında geçiş yap, sonrakine odaklan",
"page.keyboard_shortcuts.toggle_read_status_prev": "Okundu/okunmadı arasında geçiş yap, öncekine odaklan",
"page.login.google_signin": "Google ile oturum aç",
"page.login.oidc_signin": "%s ile oturum aç",
"page.login.title": "Oturum aç",
"page.login.webauthn_login": "Passkey ile giriş yap",
"page.login.webauthn_login.error": "Passkey ile giriş yapılamıyor",
"page.login.webauthn_login.help": "Please enter your username if you're using a security key. This is not required if you are using a Passkey (discoverable credentials).",
"page.new_api_key.title": "Yeni API Anahtarı",
"page.new_category.title": "Yeni Kategori",
"page.new_user.title": "Yeni Kullanıcı",
"page.offline.message": "Çevrimdışısınız",
"page.offline.refresh_page": "Sayfayı yenilemeyi dene",
"page.offline.title": "Çevrimdışı Modu",
"page.read_entry_count": [
"%d okunmuş makale",
"%d okunmuş makale"
],
"page.search.title": "Arama Sonuçları",
"page.sessions.table.actions": "Eylemler",
"page.sessions.table.current_session": "Mevcut Oturum",
"page.sessions.table.date": "Tarih",
"page.sessions.table.ip": "IP Adresi",
"page.sessions.table.user_agent": "User Agent",
"page.sessions.title": "Oturumlar",
"page.settings.link_google_account": "Google hesabımı bağla",
"page.settings.link_oidc_account": "%s hesabımı bağla",
"page.settings.title": "Ayarlar",
"page.settings.unlink_google_account": "Google hesabımın bağlantısını kaldır",
"page.settings.unlink_oidc_account": "%s hesabımın bağlantısını kaldır",
"page.settings.webauthn.actions": "Eylemler",
"page.settings.webauthn.added_on": "Eklendi",
"page.settings.webauthn.delete": [
"%d passkey'i kaldır",
"%d passkey'i kaldır"
],
"page.settings.webauthn.last_seen_on": "Son Kullanım",
"page.settings.webauthn.passkey_name": "Passkey Adı",
"page.settings.webauthn.passkeys": "Passkeyler",
"page.settings.webauthn.register": "Passkey'i kaydet",
"page.settings.webauthn.register.error": "Passkey kaydedilemiyor",
"page.shared_entries.title": "Paylaşılan makaleler",
"page.shared_entries_count": [
"%d paylaşılan makaleler",
"%d paylaşılan makaleler"
],
"page.starred.title": "Yıldızlı",
"page.starred_entry_count": [
"%d yıldızlanmış makale",
"%d yıldızlanmış makale"
],
"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.users.actions": "Eylemler",
"page.users.admin.no": "Hayır",
"page.users.admin.yes": "Evet",
"page.users.is_admin": "Yönetici",
"page.users.last_login": "Son Giriş",
"page.users.never_logged": "Asla",
"page.users.title": "Kullanıcılar",
"page.users.username": "Kullanıcı adı",
"page.webauthn_rename.title": "Passkey'i Yeniden Adlandır",
"pagination.first": "İlk",
"pagination.last": "Son",
"pagination.next": "Sonraki",
"pagination.previous": "Önceki",
"search.label": "Ara",
"search.placeholder": "Ara...",
"search.submit": "Ara",
"skip_to_content": "İçeriğe atla",
"time_elapsed.days": [
"%d gün önce",
"%d gün önce"
],
"time_elapsed.hours": [
"%d saat önce",
"%d saat önce"
],
"time_elapsed.minutes": [
"%d dakika önce",
"%d dakika önce"
],
"time_elapsed.months": [
"%d ay önce",
"%d ay önce"
],
"time_elapsed.not_yet": "henüz değil",
"time_elapsed.now": "şimdi",
"time_elapsed.weeks": [
"%d hafta önce",
"%d hafta önce"
],
"time_elapsed.years": [
"%d yıl önce",
"%d yıl önce"
],
"time_elapsed.yesterday": "dün",
"tooltip.keyboard_shortcuts": "Klavye Kısayolu: %s",
"tooltip.logged_user": "%s olarak giriş yapıldı"
} v2-2.2.13/internal/locale/translations/uk_UA.json 0000664 0000000 0000000 00000140210 15062123773 0021625 0 ustar 00root root 0000000 0000000 {
"action.cancel": "скасувати",
"action.download": "Завантажити",
"action.edit": "Редагувати",
"action.home_screen": "Додати до головного екрану",
"action.import": "Імпортувати",
"action.login": "Увійти",
"action.or": "або",
"action.remove": "Видалити",
"action.remove_feed": "Видалити стрічку",
"action.save": "Зберегти",
"action.subscribe": "Підписатись",
"action.update": "Зберегти",
"alert.account_linked": "Тепер ваш зовнішній обліковий запис від’єднано!",
"alert.account_unlinked": "Тепер ваш зовнішній обліковий запис підключено!",
"alert.background_feed_refresh": "Всі стрічки оновлюються у фоновому режимі. Ви можете продовжувати користуватися Miniflux, поки триває цей процес.",
"alert.feed_error": "З цією стрічкою трапилась помилка",
"alert.no_starred": "Наразі закладки відсутні.",
"alert.no_category": "Немає категорії.",
"alert.no_category_entry": "У цій категорії немає записів.",
"alert.no_feed": "У вас немає підписок.",
"alert.no_feed_entry": "У цій стрічці немає записів.",
"alert.no_feed_in_category": "У цій категорії немає підписок.",
"alert.no_history": "Наразі історія порожня.",
"alert.no_search_result": "Немає результатів для цього пошуку.",
"alert.no_shared_entry": "Немає спільного запису.",
"alert.no_tag_entry": "Немає записів, що відповідають цьому тегу.",
"alert.no_unread_entry": "Немає непрочитаних статей.",
"alert.no_user": "Ви єдиний користувач.",
"alert.prefs_saved": "Уподобання збережено!",
"alert.too_many_feeds_refresh": [
"Ви запустили надто багато оновлень стрічок. Будь ласка, зачекайте %d хвилину перед повторною спробою.",
"Ви запустили надто багато оновлень стрічок. Будь ласка, зачекайте %d хвилини перед повторною спробою.",
"Ви запустили надто багато оновлень стрічок. Будь ласка, зачекайте %d хвилин перед повторною спробою."
],
"confirm.loading": "В процесі...",
"confirm.no": "ні",
"confirm.question": "Ви впевнені?",
"confirm.question.refresh": "Ви хочете змусити оновити?",
"confirm.yes": "так",
"enclosure_media_controls.seek": "Пошук:",
"enclosure_media_controls.seek.title": "Пошук %s секунд",
"enclosure_media_controls.speed": "Швидкість:",
"enclosure_media_controls.speed.faster": "Швидше",
"enclosure_media_controls.speed.faster.title": "Швидше на %sx",
"enclosure_media_controls.speed.reset": "Скинути",
"enclosure_media_controls.speed.reset.title": "Скинути швидкість до 1x",
"enclosure_media_controls.speed.slower": "Повільніше",
"enclosure_media_controls.speed.slower.title": "Повільніше на %sx",
"entry.starred.toast.off": "Без зірочки",
"entry.starred.toast.on": "З зірочкою",
"entry.starred.toggle.off": "Прибрати зірочку",
"entry.starred.toggle.on": "Поставити зірочку",
"entry.comments.label": "Коментарі",
"entry.comments.title": "Дивитися коментарі",
"entry.estimated_reading_time": [
"читати %d хвилину",
"читати %d хвилини",
"читати %d хвилин"
],
"entry.external_link.label": "Зовнішнє посилання",
"entry.save.completed": "Готово!",
"entry.save.label": "Зберегти",
"entry.save.title": "Зберегти цю статтю",
"entry.save.toast.completed": "Стаття збережена",
"entry.scraper.completed": "Готово!",
"entry.scraper.label": "Завантажити",
"entry.scraper.title": "Отримати оригінальний зміст",
"entry.share.label": "Поділитись",
"entry.share.title": "Поділитись статтєю",
"entry.shared_entry.label": "Поділитись",
"entry.shared_entry.title": "Відкрити публічне посилання",
"entry.state.loading": "Завантаження...",
"entry.state.saving": "Зберігаю...",
"entry.status.mark_as_read": "Позначити як прочитане",
"entry.status.mark_as_unread": "Позначити як непрочитане",
"entry.status.title": "Змінити стан запису",
"entry.status.toast.read": "Відмічено прочитаним",
"entry.status.toast.unread": "Відмічено непрочитаним",
"entry.tags.label": "Теги:",
"entry.tags.more_tags_label": [
"Ще %d тег",
"Ще %d теги",
"Ще %d тегів"
],
"entry.unshare.label": "Не ділитися",
"error.api_key_already_exists": "Такий ключ API вже існує.",
"error.bad_credentials": "Невірне ім’я користувача або пароль.",
"error.category_already_exists": "Така категорія вже існує.",
"error.category_not_found": "Ця категорія не існує або не належить цьому користувачу.",
"error.database_error": "Помилка бази даних: %v.",
"error.different_passwords": "Паролі не співпадають.",
"error.duplicate_fever_username": "Вже є обліковий запис з таким самим користувачем Fever!",
"error.duplicate_googlereader_username": "Вже є обліковий запис з таким самим користувачем Google Reader!",
"error.duplicate_linked_account": "Вже є обліковий запис, під’єднаний до цього провайдера!",
"error.duplicated_feed": "Ця стрічка вже існує.",
"error.empty_file": "Цей файл порожній.",
"error.entries_per_page_invalid": "Число записів на сторінку недійсне.",
"error.feed_already_exists": "Така стрічка вже існує.",
"error.feed_category_not_found": "Категорія не існує або належить до іншого користувача.",
"error.feed_format_not_detected": "Не вдалося визначити формат стрічки: %v.",
"error.feed_invalid_blocklist_rule": "Правило списку блокувань недійсне.",
"error.feed_invalid_keeplist_rule": "Правило списку дозволень недійсне.",
"error.feed_mandatory_fields": "URL та категорія є обов’язковими.",
"error.feed_not_found": "Ця стрічка не існує або не належить цьому користувачу.",
"error.feed_title_not_empty": "Назва стрічки не може бути порожньою.",
"error.feed_url_not_empty": "URL-адреса стрічки не може бути порожньою.",
"error.fields_mandatory": "Всі поля є обов’язковими.",
"error.http_bad_gateway": "Сайт наразі недоступний через помилку шлюзу. Проблема не на стороні Miniflux. Будь ласка, спробуйте пізніше.",
"error.http_body_read": "Не вдалося прочитати HTTP-вміст: %v.",
"error.http_client_error": "Помилка HTTP-клієнта: %v.",
"error.http_empty_response": "Відповідь HTTP порожня. Можливо, цей сайт використовує захист від ботів?",
"error.http_empty_response_body": "Тіло відповіді HTTP порожнє.",
"error.http_forbidden": "Доступ до цього сайту заборонено. Можливо, сайт має захист від ботів?",
"error.http_gateway_timeout": "Сайт наразі недоступний через помилку тайм-ауту шлюзу. Проблема не на стороні Miniflux. Будь ласка, спробуйте пізніше.",
"error.http_internal_server_error": "Сайт наразі недоступний через внутрішню помилку сервера. Проблема не на стороні Miniflux. Будь ласка, спробуйте пізніше.",
"error.http_not_authorized": "Доступ до цього сайту не дозволено. Можливо, неправильне ім’я користувача або пароль.",
"error.http_resource_not_found": "Запитаний ресурс не знайдено. Будь ласка, перевірте URL.",
"error.http_response_too_large": "Відповідь HTTP занадто велика. Ви можете збільшити ліміт розміру HTTP-відповіді у глобальних налаштуваннях (потрібен перезапуск сервера).",
"error.http_service_unavailable": "Сайт наразі недоступний через внутрішню помилку сервера. Проблема не на стороні Miniflux. Будь ласка, спробуйте пізніше.",
"error.http_too_many_requests": "Miniflux згенерував надто багато запитів до цього сайту. Будь ласка, спробуйте пізніше або змініть налаштування програми.",
"error.http_unexpected_status_code": "Сайт наразі недоступний через неочікуваний HTTP-код: %d. Проблема не на стороні Miniflux. Будь ласка, спробуйте пізніше.",
"error.invalid_categories_sorting_order": "Недійсний порядок сортування категорій.",
"error.invalid_default_home_page": "Недійсна домашня сторінка за замовчуванням!",
"error.invalid_display_mode": "Недійсний режим відображення.",
"error.invalid_entry_direction": "Недійсний напрямок запису.",
"error.invalid_entry_order": "Недійсний порядок запису.",
"error.invalid_feed_proxy_url": "Недійсний proxy URL.",
"error.invalid_feed_url": "Недійсна URL-адреса стрічки.",
"error.invalid_gesture_nav": "Недійсна навігація жестами.",
"error.invalid_language": "Недійсна мова.",
"error.invalid_site_url": "Недійсна URL-адреса сайту.",
"error.invalid_theme": "Недійсна тема.",
"error.invalid_timezone": "Недійсний часовий пояс.",
"error.network_operation": "Miniflux не може отримати доступ до цього сайту через помилку мережі: %v.",
"error.network_timeout": "Цей сайт занадто повільний і запит перевищив час очікування: %v",
"error.password_min_length": "Пароль має складати щонайменше 6 символів.",
"error.proxy_url_not_empty": "Proxy URL не може бути порожнім.",
"error.settings_block_rule_fieldname_invalid": "Недійсне правило блокування: у правилі #%d відсутнє коректне ім’я поля (Опції: %s)",
"error.settings_block_rule_invalid_regex": "Недійсне правило блокування: шаблон правила #%d не є коректним регулярним виразом",
"error.settings_block_rule_regex_required": "Недійсне правило блокування: не вказано шаблон для правила #%d",
"error.settings_block_rule_separator_required": "Недійсне правило блокування: шаблон правила #%d має бути розділений знаком '='",
"error.settings_invalid_domain_list": "Недійсний список доменів. Будь ласка, вкажіть список доменів, розділених пробілами.",
"error.settings_keep_rule_fieldname_invalid": "Недійсне правило дозволення: у правилі #%d відсутнє коректне ім’я поля (Опції: %s)",
"error.settings_keep_rule_invalid_regex": "Недійсне правило дозволення: шаблон правила #%d не є коректним регулярним виразом",
"error.settings_keep_rule_regex_required": "Недійсне правило дозволення: не вказано шаблон для правила #%d",
"error.settings_keep_rule_separator_required": "Недійсне правило дозволення: шаблон правила #%d має бути розділений знаком '='",
"error.settings_mandatory_fields": "Поля імені, теми, мови та часового поясу є обов’язковими.",
"error.settings_media_playback_rate_range": "Швидкість відтворення виходить за межі діапазону",
"error.settings_reading_speed_is_positive": "Швидкість читання має бути додатнім цілим числом.",
"error.site_url_not_empty": "URL-адреса сайту не може бути порожньою.",
"error.subscription_not_found": "Не знайшлося жодної підписки.",
"error.title_required": "Назва є обов’язковою.",
"error.tls_error": "Помилка TLS: %q. Ви можете відключити перевірку TLS в налаштуваннях фіду, якщо хочете.",
"error.unable_to_create_api_key": "Не вдається створити такий ключ API",
"error.unable_to_create_category": "Не вдається сворити категорію.",
"error.unable_to_create_user": "Не вдається створити користувача.",
"error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.unable_to_update_category": "Не вдається відредагувати категорію.",
"error.unable_to_update_feed": "Не вдається оновити стрічку.",
"error.unable_to_update_user": "Не вдається оновити користувача.",
"error.unlink_account_without_password": "Ви маєте встановити пароль, щоб мати можливість увійти наступного разу",
"error.user_already_exists": "Такий користувач вже існує.",
"error.user_mandatory_fields": "Ім'я користувача є обов'язковим.",
"error.linktaco_missing_required_fields": "LinkTaco API Token і Organization Slug є обов'язковими",
"form.api_key.label.description": "Назва ключа API",
"form.category.hide_globally": "Приховати записи в глобальному списку непрочитаного",
"form.category.label.title": "Назва",
"form.feed.fieldset.general": "Загальні",
"form.feed.fieldset.integration": "Сторонні сервіси",
"form.feed.fieldset.network_settings": "Налаштування мережі",
"form.feed.fieldset.rules": "Правила",
"form.feed.label.allow_self_signed_certificates": "Дозволити сертифікати з власним підписом або недійсні",
"form.feed.label.apprise_service_urls": "Список URL сервісів Apprise, розділених комами",
"form.feed.label.block_filter_entry_rules": "Правила блокування записів",
"form.feed.label.blocklist_rules": "Фільтри блокування на основі регулярних виразів",
"form.feed.label.category": "Категорія",
"form.feed.label.cookie": "Встановити кукі",
"form.feed.label.crawler": "Завантажувати оригінальний вміст",
"form.feed.label.description": "Опис",
"form.feed.label.disable_http2": "Вимкнути HTTP/2 для уникнення відбитків",
"form.feed.label.disabled": "Не оновлювати цю стрічку",
"form.feed.label.feed_password": "Пароль для завантаження",
"form.feed.label.feed_url": "URL-адреса стрічки",
"form.feed.label.feed_username": "Ім’я користувача для завантаження",
"form.feed.label.fetch_via_proxy": "Використовувати проксі, налаштований на рівні програми",
"form.feed.label.hide_globally": "Приховати записи в глобальному списку непрочитаного",
"form.feed.label.ignore_http_cache": "Ігнорувати кеш HTTP",
"form.feed.label.keep_filter_entry_rules": "Правила дозволу записів",
"form.feed.label.keeplist_rules": "Фільтри збереження на основі регулярних виразів",
"form.feed.label.no_media_player": "Немає медіаплеєра (аудіо/відео)",
"form.feed.label.ntfy_activate": "Надсилати записи у ntfy",
"form.feed.label.ntfy_default_priority": "Стандартний пріоритет ntfy",
"form.feed.label.ntfy_high_priority": "Високий пріоритет ntfy",
"form.feed.label.ntfy_low_priority": "Низький пріоритет ntfy",
"form.feed.label.ntfy_max_priority": "Максимальний пріоритет ntfy",
"form.feed.label.ntfy_min_priority": "Мінімальний пріоритет ntfy",
"form.feed.label.ntfy_priority": "Пріоритет ntfy",
"form.feed.label.ntfy_topic": "Тема ntfy (необов’язково)",
"form.feed.label.proxy_url": "Proxy URL",
"form.feed.label.pushover_activate": "Надсилати записи у pushover.net",
"form.feed.label.pushover_default_priority": "Стандартний пріоритет Pushover",
"form.feed.label.pushover_high_priority": "Високий пріоритет Pushover",
"form.feed.label.pushover_low_priority": "Низький пріоритет Pushover",
"form.feed.label.pushover_max_priority": "Максимальний пріоритет Pushover",
"form.feed.label.pushover_min_priority": "Мінімальний пріоритет Pushover",
"form.feed.label.pushover_priority": "Пріоритет повідомлення Pushover",
"form.feed.label.rewrite_rules": "Правила перезапису вмісту",
"form.feed.label.scraper_rules": "Правила Scraper",
"form.feed.label.site_url": "URL-адреса сайту",
"form.feed.label.title": "Назва",
"form.feed.label.urlrewrite_rules": "Правила перезапису URL-адрес",
"form.feed.label.user_agent": "Назначити User Agent",
"form.feed.label.webhook_url": "Перевизначити URL вебхука",
"form.import.label.file": "Файл OPML",
"form.import.label.url": "URL-адреса",
"form.integration.apprise_activate": "Надсилати записи у Apprise",
"form.integration.apprise_services_url": "Список URL сервісів Apprise, розділених комами",
"form.integration.apprise_url": "Apprise API URL",
"form.integration.betula_activate": "Save entries to Betula",
"form.integration.betula_token": "Betula Token",
"form.integration.betula_url": "Betula server URL",
"form.integration.cubox_activate": "Зберігати статті до Cubox",
"form.integration.cubox_api_link": "Посилання на Cubox API",
"form.integration.discord_activate": "Push entries to Discord",
"form.integration.discord_webhook_link": "Discord Webhook link",
"form.integration.espial_activate": "Зберігати статті до Espial",
"form.integration.espial_api_key": "Ключ API Espial",
"form.integration.espial_endpoint": "Espial API Endpoint",
"form.integration.espial_tags": "Теги для Espial",
"form.integration.fever_activate": "Увімкнути API Fever",
"form.integration.fever_endpoint": "Адреса доступу API Fever:",
"form.integration.fever_password": "Пароль Fever",
"form.integration.fever_username": "Ім’я користувача Fever",
"form.integration.googlereader_activate": "Увімкнути API Google Reader",
"form.integration.googlereader_endpoint": "Адреса доступу API Google Reader:",
"form.integration.googlereader_password": "Пароль Google Reader",
"form.integration.googlereader_username": "Ім’я користувача Google Reader",
"form.integration.instapaper_activate": "Зберігати статті до Instapaper",
"form.integration.instapaper_password": "Пароль Instapaper",
"form.integration.instapaper_username": "Ім’я користувача Instapaper",
"form.integration.karakeep_activate": "Зберігати статті до Karakeep",
"form.integration.karakeep_api_key": "Ключ API Karakeep",
"form.integration.karakeep_url": "Karakeep API Endpoint",
"form.integration.linkace_activate": "Save entries to LinkAce",
"form.integration.linkace_api_key": "LinkAce API key",
"form.integration.linkace_check_disabled": "Disable link check",
"form.integration.linkace_endpoint": "LinkAce API Endpoint",
"form.integration.linkace_is_private": "Mark link as private",
"form.integration.linkace_tags": "LinkAce Tags",
"form.integration.linkding_activate": "Зберігати статті до Linkding",
"form.integration.linkding_api_key": "Ключ API Linkding",
"form.integration.linkding_bookmark": "Відмічати закладку як непрочитану",
"form.integration.linkding_endpoint": "Linkding API Endpoint",
"form.integration.linkding_tags": "Linkding Tags",
"form.integration.linktaco_activate": "Зберігати статті в LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "Отримайте ваш персональний токен доступу на",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "Теги (макс. 10, через кому)",
"form.integration.linktaco_tags_hint": "Максимум 10 тегів, через кому",
"form.integration.linktaco_visibility": "Видимість",
"form.integration.linktaco_visibility_public": "Публічно",
"form.integration.linktaco_visibility_private": "Приватно",
"form.integration.linktaco_visibility_hint": "ПРИВАТНА видимість потребує платного акаунта LinkTaco",
"form.integration.linkwarden_activate": "Зберігати статті до Linkwarden",
"form.integration.linkwarden_api_key": "Ключ API Linkwarden",
"form.integration.linkwarden_endpoint": "Базова URL-адреса Linkwarden",
"form.integration.matrix_bot_activate": "Перенесення нових статей в Матрицю",
"form.integration.matrix_bot_chat_id": "Ідентифікатор кімнати Матриці",
"form.integration.matrix_bot_password": "Пароль для користувача Matrix",
"form.integration.matrix_bot_url": "URL-адреса сервера Матриці",
"form.integration.matrix_bot_user": "Ім'я користувача для Matrix",
"form.integration.notion_activate": "Save entries to Notion",
"form.integration.notion_page_id": "Notion Page ID",
"form.integration.notion_token": "Notion Secret Token",
"form.integration.ntfy_activate": "Надсилати записи у ntfy",
"form.integration.ntfy_api_token": "Ntfy API Token (optional)",
"form.integration.ntfy_icon_url": "Ntfy Icon URL (optional)",
"form.integration.ntfy_internal_links": "Використовувати внутрішні посилання при натисканні (необов’язково)",
"form.integration.ntfy_password": "Ntfy Password (optional)",
"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",
"form.integration.nunux_keeper_api_key": "Ключ API Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API Endpoint",
"form.integration.omnivore_activate": "Зберігати статті до Omnivore",
"form.integration.omnivore_api_key": "Ключ API Omnivore",
"form.integration.omnivore_url": "Omnivore API Endpoint",
"form.integration.pinboard_activate": "Зберігати статті до Pinboard",
"form.integration.pinboard_bookmark": "Відмічати закладку як непрочитану",
"form.integration.pinboard_tags": "Теги для Pinboard",
"form.integration.pinboard_token": "API ключ від Pinboard",
"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.raindrop_activate": "Save entries to Raindrop",
"form.integration.raindrop_collection_id": "Collection ID",
"form.integration.raindrop_tags": "Tags (comma-separated)",
"form.integration.raindrop_token": "(Test) Token",
"form.integration.readeck_activate": "Зберігати статті до Readeck",
"form.integration.readeck_api_key": "Ключ API Readeck",
"form.integration.readeck_endpoint": "Readeck URL",
"form.integration.readeck_labels": "Readeck Labels",
"form.integration.readeck_only_url": "Надіслати лише URL (замість повного вмісту)",
"form.integration.readwise_activate": "Save entries to Readwise Reader",
"form.integration.readwise_api_key": "Readwise Reader Access Token",
"form.integration.readwise_api_key_link": "Get your Readwise Access Token",
"form.integration.rssbridge_activate": "Check RSS-Bridge when adding subscriptions",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "RSS-Bridge server URL",
"form.integration.shaarli_activate": "Save articles to Shaarli",
"form.integration.shaarli_api_secret": "Shaarli API Secret",
"form.integration.shaarli_endpoint": "Shaarli URL",
"form.integration.shiori_activate": "Save articles to Shiori",
"form.integration.shiori_endpoint": "Shiori API Endpoint",
"form.integration.shiori_password": "Shiori Password",
"form.integration.shiori_username": "Shiori Username",
"form.integration.slack_activate": "Slack entries to Discord",
"form.integration.slack_webhook_link": "Slack Webhook link",
"form.integration.telegram_bot_activate": "Відправляти нові статті до чату Telegram",
"form.integration.telegram_bot_disable_buttons": "Disable buttons",
"form.integration.telegram_bot_disable_notification": "Disable notification",
"form.integration.telegram_bot_disable_web_page_preview": "Disable web page preview",
"form.integration.telegram_bot_token": "Токен боту",
"form.integration.telegram_chat_id": "ID чату",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "Зберігати статті до Wallabag",
"form.integration.wallabag_client_id": "Wallabag Client ID",
"form.integration.wallabag_client_secret": "Wallabag Client Secret",
"form.integration.wallabag_endpoint": "Базова URL-адреса Wallabag",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.wallabag_only_url": "Надіслати лише URL (замість повного вмісту)",
"form.integration.wallabag_password": "Пароль Wallabag",
"form.integration.wallabag_username": "Ім’я користувача Wallabag",
"form.integration.webhook_activate": "Enable Webhooks",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Default Webhook URL",
"form.prefs.fieldset.application_settings": "Application Settings",
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
"form.prefs.fieldset.reader_settings": "Reader Settings",
"form.prefs.help.external_font_hosts": "Список дозволених зовнішніх хостів шрифтів, розділених пробілами. Наприклад: 'fonts.gstatic.com fonts.googleapis.com'.",
"form.prefs.label.always_open_external_links": "Читати статті, відкриваючи зовнішні посилання",
"form.prefs.label.categories_sorting_order": "Сортування за категоріями",
"form.prefs.label.cjk_reading_speed": "Швидкість читання для китайської, корейської та японської мови (символів на хвилину)",
"form.prefs.label.custom_css": "Спеціальний CSS",
"form.prefs.label.custom_js": "Спеціальний JavaScript",
"form.prefs.label.default_home_page": "Домашня сторінка за умовчанням",
"form.prefs.label.default_reading_speed": "Швидкість читання для інших мов (слів на хвилину)",
"form.prefs.label.display_mode": "Режим відображення Progressive Web App (PWA).",
"form.prefs.label.entries_per_page": "Кількість записів на сторінку",
"form.prefs.label.entry_order": "Стовпець сортування записів",
"form.prefs.label.entry_sorting": "Сортування записів",
"form.prefs.label.entry_swipe": "Увімкніть введення пальцем на сенсорних екранах",
"form.prefs.label.external_font_hosts": "Зовнішні хости шрифтів",
"form.prefs.label.gesture_nav": "Жест для переходу між записами",
"form.prefs.label.keyboard_shortcuts": "Увімкнути комбінації клавиш",
"form.prefs.label.language": "Мова",
"form.prefs.label.mark_read_manually": "Mark entries as read manually",
"form.prefs.label.mark_read_on_media_completion": "Only mark as read when audio/video playback reaches 90%% completion",
"form.prefs.label.mark_read_on_view": "Автоматично позначати записи як прочитані під час перегляду",
"form.prefs.label.mark_read_on_view_or_media_completion": "Mark entries as read when viewed. For audio/video, mark as read at 90%% completion",
"form.prefs.label.media_playback_rate": "Швидкість відтворення аудіо/відео",
"form.prefs.label.open_external_links_in_new_tab": "Відкривати зовнішні посилання у новій вкладці (додає target=\"_blank\" до посилань)",
"form.prefs.label.show_reading_time": "Показувати приблизний час читання для записів",
"form.prefs.label.theme": "Тема",
"form.prefs.label.timezone": "Часовий пояс",
"form.prefs.select.alphabetical": "За алфавітом",
"form.prefs.select.browser": "Браузер",
"form.prefs.select.created_time": "Дата створення запису",
"form.prefs.select.fullscreen": "Повний екран",
"form.prefs.select.minimal_ui": "Мінімальний",
"form.prefs.select.none": "Жодного",
"form.prefs.select.older_first": "Старіші записи спочатку",
"form.prefs.select.publish_time": "Дата публікації запису",
"form.prefs.select.recent_first": "Останні записи спочатку",
"form.prefs.select.standalone": "Автономний",
"form.prefs.select.swipe": "Проведіть пальцем",
"form.prefs.select.tap": "Двічі натисніть",
"form.prefs.select.unread_count": "Кількість непрочитаних",
"form.submit.loading": "Завантаження...",
"form.submit.saving": "Зберігаю...",
"form.user.label.admin": "Адміністратор",
"form.user.label.confirmation": "Підтверждення паролю",
"form.user.label.password": "Пароль",
"form.user.label.username": "Ім’я користувача",
"menu.about": "Про додаток",
"menu.add_feed": "Додати підписку",
"menu.add_user": "Додати користувачв",
"menu.api_keys": "Ключі API",
"menu.categories": "Категорії",
"menu.create_api_key": "Створити новий ключ API",
"menu.create_category": "Створити категорію",
"menu.edit_category": "Редагувати",
"menu.edit_feed": "Редагувати",
"menu.export": "Експорт",
"menu.feed_entries": "Записи",
"menu.feeds": "Стрічки",
"menu.flush_history": "Очистити історію",
"menu.history": "Історія",
"menu.home_page": "Головна сторінка",
"menu.import": "Імпорт",
"menu.integrations": "Інтеграції",
"menu.logout": "Вийти",
"menu.mark_all_as_read": "Відмітити все як прочитане",
"menu.mark_page_as_read": "Відмітити цю сторінку як прочитане",
"menu.preferences": "Уподобання",
"menu.refresh_all_feeds": "Оновити всі стрічки у фоновому режимі",
"menu.refresh_feed": "Оновити",
"menu.search": "Пошук",
"menu.sessions": "Сеанси",
"menu.settings": "Налаштування",
"menu.shared_entries": "Спільні записи",
"menu.show_all_entries": "Показати всі записи",
"menu.show_only_starred_entries": "Показати тільки записи з зірочкою",
"menu.show_only_unread_entries": "Показати тільки непрочитані записи",
"menu.starred": "З зірочкою",
"menu.title": "Меню",
"menu.unread": "Непрочитане",
"menu.users": "Користувачі",
"page.about.author": "Автор:",
"page.about.build_date": "Дата побудови:",
"page.about.credits": "Титри",
"page.about.db_usage": "Database size:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "Параметри глобальної конфігурації",
"page.about.go_version": "Версія Go:",
"page.about.license": "Ліцензія:",
"page.about.postgres_version": "Версія Postgres:",
"page.about.title": "Про додадок",
"page.about.version": "Версія:",
"page.add_feed.choose_feed": "Обрати підписку",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "Розширені опції",
"page.add_feed.no_category": "Немає категорії. Ви маєте додати принаймні одну категорію.",
"page.add_feed.submit": "Знайти підписку",
"page.add_feed.title": "Нова підписка",
"page.api_keys.never_used": "Ніколи не використався",
"page.api_keys.table.actions": "Дії",
"page.api_keys.table.created_at": "Дата створення",
"page.api_keys.table.description": "Опис",
"page.api_keys.table.last_used_at": "Дата останнього використання",
"page.api_keys.table.token": "Токен",
"page.api_keys.title": "Ключі API",
"page.categories.entries": "Статті",
"page.categories.feed_count": [
"Містить %d стрічку.",
"Містить %d стрічки.",
"Містить %d стрічок."
],
"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:",
"page.edit_feed.last_check": "Остання перевірка:",
"page.edit_feed.last_modified_header": "Заголовок LastModified:",
"page.edit_feed.last_parsing_error": "Остання помилка аналізу",
"page.edit_feed.no_header": "Немає",
"page.edit_feed.title": "Редагування стрічки: %s",
"page.edit_user.title": "Редагування користувача: %s",
"page.entry.attachments": "Додатки",
"page.feeds.error_count": [
"%d помилка",
"%d помилки",
"%d помилок"
],
"page.feeds.last_check": "Остання перевірка:",
"page.feeds.next_check": "Наступна перевірка:",
"page.feeds.read_counter": "Кількість прочитаних записів",
"page.feeds.title": "Стрічки",
"page.footer.elevator": "Back to top",
"page.history.title": "Історія",
"page.import.title": "Імпорт",
"page.integration.bookmarklet": "Букмарклет",
"page.integration.bookmarklet.help": "Це спеціальне посилання дозволяє підписатися на веб-сайт безпосередньо за допомогою закладки у вашому веб-браузері.",
"page.integration.bookmarklet.instructions": "Перетягніть це посилання до своїх закладок.",
"page.integration.bookmarklet.name": "Додати до Miniflux",
"page.integration.miniflux_api": "Miniflux API",
"page.integration.miniflux_api_endpoint": "Адреса доступу API",
"page.integration.miniflux_api_password": "Пароль",
"page.integration.miniflux_api_password_value": "Пароль до вашого облікового запису",
"page.integration.miniflux_api_username": "Ім’я користувача",
"page.integrations.title": "Інтеграції",
"page.keyboard_shortcuts.close_modal": "Закрити модальне діалогове вікно",
"page.keyboard_shortcuts.download_content": "Завантажити оригінальний зміст",
"page.keyboard_shortcuts.go_to_bottom_item": "Перейти до нижнього пункту",
"page.keyboard_shortcuts.go_to_categories": "Перейти до категорій",
"page.keyboard_shortcuts.go_to_feed": "Перейти до стрічки",
"page.keyboard_shortcuts.go_to_feeds": "Перейти до стрічок",
"page.keyboard_shortcuts.go_to_history": "Перейти до історії",
"page.keyboard_shortcuts.go_to_next_item": "Перейти до наступного запису",
"page.keyboard_shortcuts.go_to_next_page": "Перейти до наступної сторінки",
"page.keyboard_shortcuts.go_to_previous_item": "Перейти до попереднього запису",
"page.keyboard_shortcuts.go_to_previous_page": "Перейти до попередньої сторінки",
"page.keyboard_shortcuts.go_to_search": "Поставити фокус на поле пошуку",
"page.keyboard_shortcuts.go_to_settings": "Перейти до налаштувань",
"page.keyboard_shortcuts.go_to_starred": "Перейти до закладок",
"page.keyboard_shortcuts.go_to_top_item": "Перейти до верхнього пункту",
"page.keyboard_shortcuts.go_to_unread": "Перейти до непрочитаних",
"page.keyboard_shortcuts.mark_page_as_read": "Відмітити поточну сторінку як прочитане",
"page.keyboard_shortcuts.open_comments": "Відкрити посилання на коментарі",
"page.keyboard_shortcuts.open_comments_same_window": "Відкрити посилання на коментарі в поточній вкладці",
"page.keyboard_shortcuts.open_item": "Відкрити виділений запис",
"page.keyboard_shortcuts.open_original": "Відкрити оригінальне посилання",
"page.keyboard_shortcuts.open_original_same_window": "Відкрити оригінальне посилання в поточній вкладці",
"page.keyboard_shortcuts.refresh_all_feeds": "Оновити всі стрічки в фоновому режимі",
"page.keyboard_shortcuts.remove_feed": "Видалити цю стрічку",
"page.keyboard_shortcuts.save_article": "Зберегти статтю",
"page.keyboard_shortcuts.scroll_item_to_top": "Прокрутити запис догори",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "Показати комбінації клавиш",
"page.keyboard_shortcuts.subtitle.actions": "Дії",
"page.keyboard_shortcuts.subtitle.items": "Навігація по записах",
"page.keyboard_shortcuts.subtitle.pages": "Навігація по сторінках",
"page.keyboard_shortcuts.subtitle.sections": "Навігація по розділах",
"page.keyboard_shortcuts.title": "Комбінації клавиш",
"page.keyboard_shortcuts.toggle_star_status": "Переключити статус закладки",
"page.keyboard_shortcuts.toggle_entry_attachments": "Toggle open/close entry attachments",
"page.keyboard_shortcuts.toggle_read_status_next": "Переключити статус читання, перейти до наступного",
"page.keyboard_shortcuts.toggle_read_status_prev": "Переключити статус читання, перейти до попереднього",
"page.login.google_signin": "Увійти через Google",
"page.login.oidc_signin": "Увійти через %s",
"page.login.title": "Вхід",
"page.login.webauthn_login": "Увійти за допомогою пароля",
"page.login.webauthn_login.error": "Неможливо ввійти за допомогою ключа доступу",
"page.login.webauthn_login.help": "Please enter your username if you're using a security key. This is not required if you are using a Passkey (discoverable credentials).",
"page.new_api_key.title": "Створити ключ API",
"page.new_category.title": "Нова категорія",
"page.new_user.title": "Новий користувач",
"page.offline.message": "Ви офлайн",
"page.offline.refresh_page": "Спробуйте оновити сторінку",
"page.offline.title": "Автономний режим",
"page.read_entry_count": [
"%d read entry",
"%d read entries",
"%d read entries"
],
"page.search.title": "Результати пошуку",
"page.sessions.table.actions": "Дії",
"page.sessions.table.current_session": "Поточний сеанс",
"page.sessions.table.date": "Дата",
"page.sessions.table.ip": "IP адреса",
"page.sessions.table.user_agent": "User Agent",
"page.sessions.title": "Сеанси",
"page.settings.link_google_account": "Підключити мій обліковий запис Google",
"page.settings.link_oidc_account": "Підключити мій обліковий запис %s",
"page.settings.title": "Налаштування ",
"page.settings.unlink_google_account": "Відключити мій обліковий запис Google",
"page.settings.unlink_oidc_account": "Відключити мій обліковий запис %s",
"page.settings.webauthn.actions": "Actions",
"page.settings.webauthn.added_on": "Added On",
"page.settings.webauthn.delete": [
"Видалити %d ключ доступу",
"Видаліть %d ключа доступу",
"Видаліть %d ключа доступу"
],
"page.settings.webauthn.last_seen_on": "Last Used",
"page.settings.webauthn.passkey_name": "Passkey Name",
"page.settings.webauthn.passkeys": "Passkeys",
"page.settings.webauthn.register": "Зареєструвати пароль",
"page.settings.webauthn.register.error": "Не вдалося зареєструвати ключ доступу",
"page.shared_entries.title": "Спільні записи",
"page.shared_entries_count": [
"%d shared entry",
"%d shared entries",
"%d shared entries"
],
"page.starred.title": "З зірочкою",
"page.starred_entry_count": [
"%d starred entry",
"%d starred entries",
"%d starred entries"
],
"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.users.actions": "Дії",
"page.users.admin.no": "Ні",
"page.users.admin.yes": "Так",
"page.users.is_admin": "Адміністратор",
"page.users.last_login": "Дата останнього входу",
"page.users.never_logged": "Ніколи",
"page.users.title": "Користувачі",
"page.users.username": "Ім’я користувача",
"page.webauthn_rename.title": "Rename Passkey",
"pagination.first": "Перша",
"pagination.last": "Остання",
"pagination.next": "Наступна",
"pagination.previous": "Попередня",
"search.label": "Пошук",
"search.placeholder": "Шукати...",
"search.submit": "Знайти",
"skip_to_content": "Skip to content",
"time_elapsed.days": [
"%d день тому",
"%d дні тому",
"%d днів тому"
],
"time_elapsed.hours": [
"%d годину тому",
"%d години тому",
"%d годин тому"
],
"time_elapsed.minutes": [
"%d хвилину тому",
"%d хвилини тому",
"%d хвилин тому"
],
"time_elapsed.months": [
"%d місяць тому",
"%d місяця тому",
"%d місяців тому"
],
"time_elapsed.not_yet": "ще ні",
"time_elapsed.now": "прямо зараз",
"time_elapsed.weeks": [
"%d тиждень тому",
"%d тижня тому",
"%d тижнів тому"
],
"time_elapsed.years": [
"%d рік тому",
"%d роки тому",
"%d років тому"
],
"time_elapsed.yesterday": "вчора",
"tooltip.keyboard_shortcuts": "Комбінація клавіш: %s",
"tooltip.logged_user": "Здійснено вхід як %s"
} v2-2.2.13/internal/locale/translations/zh_CN.json 0000664 0000000 0000000 00000105436 15062123773 0021635 0 ustar 00root root 0000000 0000000 {
"action.cancel": "取消",
"action.download": "下载",
"action.edit": "编辑",
"action.home_screen": "添加到主屏幕",
"action.import": "导入",
"action.login": "登录",
"action.or": "或",
"action.remove": "移除",
"action.remove_feed": "移除此订阅源",
"action.save": "保存",
"action.subscribe": "订阅",
"action.update": "更新",
"alert.account_linked": "您的外部账号已关联!",
"alert.account_unlinked": "您的外部帐户已解除关联!",
"alert.background_feed_refresh": "所有订阅源正在后台刷新。您可以在刷新过程中继续使用 Miniflux。",
"alert.feed_error": "此订阅源存在问题",
"alert.no_starred": "没有收藏的条目。",
"alert.no_category": "没有分类。",
"alert.no_category_entry": "此分类下没有条目。",
"alert.no_feed": "你没有任何订阅源。",
"alert.no_feed_entry": "此订阅源中没有条目。",
"alert.no_feed_in_category": "此分类中没有订阅源。",
"alert.no_history": "当前没有历史记录。",
"alert.no_search_result": "此搜索没有结果。",
"alert.no_shared_entry": "没有已分享条目。",
"alert.no_tag_entry": "没有匹配此标签的条目。",
"alert.no_unread_entry": "没有未读条目。",
"alert.no_user": "您是唯一的用户。",
"alert.prefs_saved": "偏好设置已保存!",
"alert.too_many_feeds_refresh": [
"您触发了太多次订阅源刷新。请在 %d 分钟后重试。"
],
"confirm.loading": "进行中…",
"confirm.no": "否",
"confirm.question": "您确定吗?",
"confirm.question.refresh": "您确定要强制刷新吗?",
"confirm.yes": "是",
"enclosure_media_controls.seek": "查找:",
"enclosure_media_controls.seek.title": "查找 %s 秒",
"enclosure_media_controls.speed": "速度:",
"enclosure_media_controls.speed.faster": "快进",
"enclosure_media_controls.speed.faster.title": "速度快进到 %sx",
"enclosure_media_controls.speed.reset": "重置",
"enclosure_media_controls.speed.reset.title": "重置速度到 1x",
"enclosure_media_controls.speed.slower": "减慢",
"enclosure_media_controls.speed.slower.title": "速度减慢到 %sx",
"entry.starred.toast.off": "已取消收藏",
"entry.starred.toast.on": "已添加收藏",
"entry.starred.toggle.off": "取消收藏",
"entry.starred.toggle.on": "添加收藏",
"entry.comments.label": "评论",
"entry.comments.title": "查看评论",
"entry.estimated_reading_time": [
"需要 %d 分钟阅读"
],
"entry.external_link.label": "外部链接",
"entry.save.completed": "完成!",
"entry.save.label": "保存",
"entry.save.title": "保存此条目",
"entry.save.toast.completed": "条目已保存",
"entry.scraper.completed": "完成!",
"entry.scraper.label": "下载",
"entry.scraper.title": "获取原始内容",
"entry.share.label": "分享",
"entry.share.title": "分享此条目",
"entry.shared_entry.label": "分享",
"entry.shared_entry.title": "打开公开链接",
"entry.state.loading": "加载中…",
"entry.state.saving": "保存中…",
"entry.status.mark_as_read": "标为已读",
"entry.status.mark_as_unread": "标为未读",
"entry.status.title": "更改条目状态",
"entry.status.toast.read": "已标为已读",
"entry.status.toast.unread": "已标为未读",
"entry.tags.label": "标签:",
"entry.tags.more_tags_label": [
"显示 %d 个更多标签"
],
"entry.unshare.label": "取消分享",
"error.api_key_already_exists": "此 API 密钥已存在。",
"error.bad_credentials": "用户名或密码无效。",
"error.category_already_exists": "此分类已存在。",
"error.category_not_found": "此分类不存在或不属于此用户。",
"error.database_error": "数据库错误: %v。",
"error.different_passwords": "密码不一致。",
"error.duplicate_fever_username": "已存在其他用户使用相同的 Fever 用户名!",
"error.duplicate_googlereader_username": "已存在其他用户使用相同的 Google Reader 用户名!",
"error.duplicate_linked_account": "已有人与该提供商关联!",
"error.duplicated_feed": "此订阅源已经存在。",
"error.empty_file": "此文件为空。",
"error.entries_per_page_invalid": "每页的条目数无效。",
"error.feed_already_exists": "此订阅源已存在。",
"error.feed_category_not_found": "此分类不存在或不属于此用户。",
"error.feed_format_not_detected": "无法解析订阅源格式:%v。",
"error.feed_invalid_blocklist_rule": "阻止列表规则无效。",
"error.feed_invalid_keeplist_rule": "保留列表规则无效。",
"error.feed_mandatory_fields": "必须填写 URL 和分类。",
"error.feed_not_found": "此订阅源不存在或不属于此用户。",
"error.feed_title_not_empty": "订阅源的标题不能为空。",
"error.feed_url_not_empty": "订阅源的 URL 不能为空。",
"error.fields_mandatory": "必须填写全部信息。",
"error.http_bad_gateway": "由于网关错误,网站暂不可用。这不是 Miniflux 的问题,请稍后重试。",
"error.http_body_read": "无法读取 HTTP 正文:%v。",
"error.http_client_error": "HTTP 客户端错误:%v。",
"error.http_empty_response": "HTTP 响应为空,该网站可能使用了反爬虫机制。",
"error.http_empty_response_body": "HTTP 响应正文为空。",
"error.http_forbidden": "禁止访问该网站。可能该网站使用了反爬虫机制?",
"error.http_gateway_timeout": "由于网关超时,网站暂不可用。这不是 Miniflux 的问题,请稍后重试。",
"error.http_internal_server_error": "由于服务器错误,网站暂不可用。这不是 Miniflux 的问题,请稍后重试。",
"error.http_not_authorized": "未经授权访问此网站。可能是用户名或密码错误。",
"error.http_resource_not_found": "未找到请求的资源。请检查 URL。",
"error.http_response_too_large": "HTTP 响应过大。您可以在全局设置中增加 HTTP 响应大小限制(需重启服务器)。",
"error.http_service_unavailable": "由于内部服务器错误,网站暂不可用。这不是 Miniflux 的问题,请稍后重试。",
"error.http_too_many_requests": "Miniflux 向此网站生成了过多请求。请稍后重试或更改应用程序配置。",
"error.http_unexpected_status_code": "由于意外的 HTTP 状态码 %d,网站暂不可用。这不是 Miniflux 的问题,请稍后重试。",
"error.invalid_categories_sorting_order": "无效的分类排序顺序。",
"error.invalid_default_home_page": "无效的默认主页!",
"error.invalid_display_mode": "无效的网页应用显示模式。",
"error.invalid_entry_direction": "无效的条目方向。",
"error.invalid_entry_order": "无效的条目排序。",
"error.invalid_feed_proxy_url": "无效的代理 URL。",
"error.invalid_feed_url": "无效的订阅源 URL。",
"error.invalid_gesture_nav": "无效的手势导航。",
"error.invalid_language": "无效的语言。",
"error.invalid_site_url": "无效的网站 URL。",
"error.invalid_theme": "无效的主题。",
"error.invalid_timezone": "无效的时区。",
"error.network_operation": "由于网络错误,Miniflux 无法访问此网站:%v。",
"error.network_timeout": "该网站响应过慢,请求已超时:%v",
"error.password_min_length": "密码长度至少为 6 个字符。",
"error.proxy_url_not_empty": "代理 URL 不能为空。",
"error.settings_block_rule_fieldname_invalid": "无效的阻止规则:规则 #%d 缺少合法的字段名(可选:%s)",
"error.settings_block_rule_invalid_regex": "无效的阻止规则:规则 #%d 的模式字符不是合法的正则表达式",
"error.settings_block_rule_regex_required": "无效的阻止规则:规则 #%d 的模式字符没有提供",
"error.settings_block_rule_separator_required": "无效的阻止规则:规则 #%d 的模式字符必须用‘=’分开",
"error.settings_invalid_domain_list": "无效的域名列表。请提供以空格分隔的域名列表。",
"error.settings_keep_rule_fieldname_invalid": "无效的保留规则:规则 #%d 缺少合法的字段名(可选:%s)",
"error.settings_keep_rule_invalid_regex": "无效的保留规则:规则 #%d 的模式字符不是合法的正则表达式",
"error.settings_keep_rule_regex_required": "无效的保留规则:规则 #%d 的模式字符没有提供",
"error.settings_keep_rule_separator_required": "无效的保留规则:规则 #%d 的模式字符必须用‘=’分开",
"error.settings_mandatory_fields": "必须填写用户名、主题、语言以及时区。",
"error.settings_media_playback_rate_range": "播放速度超出范围",
"error.settings_reading_speed_is_positive": "阅读速度必须是正整数。",
"error.site_url_not_empty": "站点 URL 不能为空。",
"error.subscription_not_found": "无法找到任何订阅源。",
"error.title_required": "必须填写标题。",
"error.tls_error": "TLS 错误: %q。如果您愿意的话可以在订阅源设置里关闭 TLS 验证。",
"error.unable_to_create_api_key": "无法创建此 API 密钥。",
"error.unable_to_create_category": "无法创建此分类。",
"error.unable_to_create_user": "无法创建此用户。",
"error.unable_to_detect_rssbridge": "无法使用 RSS-Bridge 检测订阅源:%v。",
"error.unable_to_parse_feed": "无法解析此订阅源:%v。",
"error.unable_to_update_category": "无法更新此分类。",
"error.unable_to_update_feed": "无法更新此订阅源。",
"error.unable_to_update_user": "无法更新此用户。",
"error.unlink_account_without_password": "您必须设置密码,否则您将无法再次登录。",
"error.user_already_exists": "此用户已存在。",
"error.user_mandatory_fields": "必须填写用户名。",
"error.linktaco_missing_required_fields": "LinkTaco API Token 和 Organization Slug 是必需的",
"form.api_key.label.description": "API 密钥标签",
"form.category.hide_globally": "在全局未读列表中隐藏条目",
"form.category.label.title": "标题",
"form.feed.fieldset.general": "常规",
"form.feed.fieldset.integration": "第三方服务",
"form.feed.fieldset.network_settings": "网络设置",
"form.feed.fieldset.rules": "规则",
"form.feed.label.allow_self_signed_certificates": "允许自签名证书或无效证书",
"form.feed.label.apprise_service_urls": "使用逗号分隔的 Apprise 服务 URL 列表",
"form.feed.label.block_filter_entry_rules": "条目屏蔽规则",
"form.feed.label.blocklist_rules": "基于正则表达式的屏蔽过滤器",
"form.feed.label.category": "分类",
"form.feed.label.cookie": "设置 Cookie",
"form.feed.label.crawler": "获取原始内容",
"form.feed.label.description": "描述",
"form.feed.label.disable_http2": "禁用 HTTP/2 以避免指纹识别",
"form.feed.label.disabled": "不刷新此订阅",
"form.feed.label.feed_password": "订阅源密码",
"form.feed.label.feed_url": "订阅源 URL",
"form.feed.label.feed_username": "订阅源用户名",
"form.feed.label.fetch_via_proxy": "使用在应用程序级别配置的代理",
"form.feed.label.hide_globally": "在全局未读列表中隐藏条目",
"form.feed.label.ignore_http_cache": "忽略 HTTP 缓存",
"form.feed.label.keep_filter_entry_rules": "条目允许规则",
"form.feed.label.keeplist_rules": "基于正则表达式的保留过滤器",
"form.feed.label.no_media_player": "无媒体播放器(音频/视频)",
"form.feed.label.ntfy_activate": "推送条目到 Ntfy",
"form.feed.label.ntfy_default_priority": "Ntfy 默认优先级",
"form.feed.label.ntfy_high_priority": "Ntfy 高优先级",
"form.feed.label.ntfy_low_priority": "Ntfy 低优先级",
"form.feed.label.ntfy_max_priority": "Ntfy 最高优先级",
"form.feed.label.ntfy_min_priority": "Ntfy 最低优先级",
"form.feed.label.ntfy_priority": "Ntfy 优先级",
"form.feed.label.ntfy_topic": "Ntfy 主题(可选)",
"form.feed.label.proxy_url": "代理 URL",
"form.feed.label.pushover_activate": "推送条目到 Pushover",
"form.feed.label.pushover_default_priority": "Pushover 默认优先级",
"form.feed.label.pushover_high_priority": "Pushover 高优先级",
"form.feed.label.pushover_low_priority": "Pushover 低优先级",
"form.feed.label.pushover_max_priority": "Pushover 最高优先级",
"form.feed.label.pushover_min_priority": "Pushover 最低优先级",
"form.feed.label.pushover_priority": "Pushover 消息优先级",
"form.feed.label.rewrite_rules": "内容重写规则",
"form.feed.label.scraper_rules": "抓取规则",
"form.feed.label.site_url": "站点 URL",
"form.feed.label.title": "标题",
"form.feed.label.urlrewrite_rules": "URL 重写规则",
"form.feed.label.user_agent": "覆盖默认的用户代理",
"form.feed.label.webhook_url": "覆盖 Webhook URL",
"form.import.label.file": "OPML 文件",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "将新条目推送到 Apprise",
"form.integration.apprise_services_url": "使用逗号分隔的 Apprise 服务 URL 列表",
"form.integration.apprise_url": "Apprise API URL",
"form.integration.betula_activate": "保存条目到 Betula",
"form.integration.betula_token": "Betula 令牌",
"form.integration.betula_url": "Betula 服务端 URL",
"form.integration.cubox_activate": "保存条目到 Cubox",
"form.integration.cubox_api_link": "Cubox API 链接",
"form.integration.discord_activate": "推送条目到 Discord",
"form.integration.discord_webhook_link": "Discord Webhook 链接",
"form.integration.espial_activate": "保存条目到 Espial",
"form.integration.espial_api_key": "Espial API 密钥",
"form.integration.espial_endpoint": "Espial API 端点",
"form.integration.espial_tags": "Espial 标签",
"form.integration.fever_activate": "启用 Fever API",
"form.integration.fever_endpoint": "Fever API 端点",
"form.integration.fever_password": "Fever 密码",
"form.integration.fever_username": "Fever 用户名",
"form.integration.googlereader_activate": "启用 Google Reader API",
"form.integration.googlereader_endpoint": "Google Reader API 端点:",
"form.integration.googlereader_password": "Google Reader 密码",
"form.integration.googlereader_username": "Google Reader 用户名",
"form.integration.instapaper_activate": "保存条目到 Instapaper",
"form.integration.instapaper_password": "Instapaper 密码",
"form.integration.instapaper_username": "Instapaper 用户名",
"form.integration.karakeep_activate": "保存条目到 Karakeep",
"form.integration.karakeep_api_key": "Karakeep API 密钥",
"form.integration.karakeep_url": "Karakeep API 端点",
"form.integration.linkace_activate": "保存条目到 LinkAce",
"form.integration.linkace_api_key": "LinkAce API 密钥",
"form.integration.linkace_check_disabled": "禁用链接检查",
"form.integration.linkace_endpoint": "LinkAce API 端点",
"form.integration.linkace_is_private": "将链接标记为私有",
"form.integration.linkace_tags": "LinkAce 标签",
"form.integration.linkding_activate": "保存条目到 Linkding",
"form.integration.linkding_api_key": "Linkding API 密钥",
"form.integration.linkding_bookmark": "将书签标记为未读",
"form.integration.linkding_endpoint": "Linkding API 端点",
"form.integration.linkding_tags": "Linkding 标签",
"form.integration.linktaco_activate": "保存条目到 LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "在此获取您的个人访问令牌",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "标签(最多10个,逗号分隔)",
"form.integration.linktaco_tags_hint": "最多10个标签,逗号分隔",
"form.integration.linktaco_visibility": "可见性",
"form.integration.linktaco_visibility_public": "公开",
"form.integration.linktaco_visibility_private": "私人",
"form.integration.linktaco_visibility_hint": "私人可见性需要付费的 LinkTaco 帐户",
"form.integration.linkwarden_activate": "保存条目到 Linkwarden",
"form.integration.linkwarden_api_key": "Linkwarden API 密钥",
"form.integration.linkwarden_endpoint": "Linkwarden 基本 URL",
"form.integration.matrix_bot_activate": "推送新条目到 Matrix",
"form.integration.matrix_bot_chat_id": "Matrix 房间 ID",
"form.integration.matrix_bot_password": "Matrix 用户密码",
"form.integration.matrix_bot_url": "Matrix 服务器 URL",
"form.integration.matrix_bot_user": "Matrix 用户名",
"form.integration.notion_activate": "保存条目到 Notion",
"form.integration.notion_page_id": "Notion 页面 ID",
"form.integration.notion_token": "Notion 密钥令牌",
"form.integration.ntfy_activate": "推送条目到 Ntfy",
"form.integration.ntfy_api_token": "Ntfy API 令牌(可选)",
"form.integration.ntfy_icon_url": "Ntfy 图标 URL(可选)",
"form.integration.ntfy_internal_links": "点击时使用内部链接(可选)",
"form.integration.ntfy_password": "Ntfy 密码(可选)",
"form.integration.ntfy_topic": "Ntfy 主题(如果订阅源中未设置则使用默认值)",
"form.integration.ntfy_url": "Ntfy URL(可选,默认为 ntfy.sh)",
"form.integration.ntfy_username": "Ntfy 用户名(可选)",
"form.integration.nunux_keeper_activate": "保存条目到 Nunux Keeper",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API 密钥",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API 端点",
"form.integration.omnivore_activate": "保存条目到 Omnivore",
"form.integration.omnivore_api_key": "Omnivore API 密钥",
"form.integration.omnivore_url": "Omnivore API 端点",
"form.integration.pinboard_activate": "保存条目到 Pinboard",
"form.integration.pinboard_bookmark": "将书签标记为未读",
"form.integration.pinboard_tags": "Pinboard 标签",
"form.integration.pinboard_token": "Pinboard API 令牌",
"form.integration.pushover_activate": "推送条目到 Pushover",
"form.integration.pushover_device": "Pushover 设备(可选)",
"form.integration.pushover_prefix": "Pushover URL 前缀(可选)",
"form.integration.pushover_token": "Pushover 应用 API 令牌",
"form.integration.pushover_user": "Pushover 用户密钥",
"form.integration.raindrop_activate": "保存条目到 Raindrop",
"form.integration.raindrop_collection_id": "集合 ID",
"form.integration.raindrop_tags": "标签(逗号分隔)",
"form.integration.raindrop_token": "(测试)令牌",
"form.integration.readeck_activate": "保存条目到 Readeck",
"form.integration.readeck_api_key": "Readeck API 密钥",
"form.integration.readeck_endpoint": "Readeck API 端点",
"form.integration.readeck_labels": "Readeck 标签",
"form.integration.readeck_only_url": "仅发送 URL(而非完整内容)",
"form.integration.readwise_activate": "保存条目到 Readwise Reader",
"form.integration.readwise_api_key": "Readwise Reader 访问令牌",
"form.integration.readwise_api_key_link": "获取你的 Readwise 访问令牌",
"form.integration.rssbridge_activate": "添加订阅时检查 RSS-Bridge",
"form.integration.rssbridge_token": "RSS-Bridge 认证令牌",
"form.integration.rssbridge_url": "RSS-Bridge 服务器 URL",
"form.integration.shaarli_activate": "保存条目到 Shaarli",
"form.integration.shaarli_api_secret": "Shaarli API 密钥",
"form.integration.shaarli_endpoint": "Shaarli URL",
"form.integration.shiori_activate": "保存条目到 Shiori",
"form.integration.shiori_endpoint": "Shiori API 端点",
"form.integration.shiori_password": "Shiori 密码",
"form.integration.shiori_username": "Shiori 用户名",
"form.integration.slack_activate": "推送条目到 Slack",
"form.integration.slack_webhook_link": "Slack Webhook 链接",
"form.integration.telegram_bot_activate": "推送新条目到 Telegram 聊天",
"form.integration.telegram_bot_disable_buttons": "禁用按钮",
"form.integration.telegram_bot_disable_notification": "禁用通知",
"form.integration.telegram_bot_disable_web_page_preview": "禁用网页预览",
"form.integration.telegram_bot_token": "机器人令牌",
"form.integration.telegram_chat_id": "聊天 ID",
"form.integration.telegram_topic_id": "主题 ID",
"form.integration.wallabag_activate": "保存条目到 Wallabag",
"form.integration.wallabag_client_id": "Wallabag 客户端 ID",
"form.integration.wallabag_client_secret": "Wallabag 客户端密钥",
"form.integration.wallabag_endpoint": "Wallabag 基础 URL",
"form.integration.wallabag_only_url": "仅发送 URL(而非完整内容)",
"form.integration.wallabag_password": "Wallabag 密码",
"form.integration.wallabag_username": "Wallabag 用户名",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "启用 Webhooks",
"form.integration.webhook_secret": "Webhooks 密钥",
"form.integration.webhook_url": "默认 Webhook URL",
"form.prefs.fieldset.application_settings": "应用设置",
"form.prefs.fieldset.authentication_settings": "认证设置",
"form.prefs.fieldset.global_feed_settings": "全局订阅源设置",
"form.prefs.fieldset.reader_settings": "阅读器设置",
"form.prefs.help.external_font_hosts": "允许外部字体托管的空格分隔列表。例如:\"fonts.gstatic.com fonts.googleapis.com\"。",
"form.prefs.label.always_open_external_links": "打开外部链接阅读条目",
"form.prefs.label.categories_sorting_order": "分类排序",
"form.prefs.label.cjk_reading_speed": "中文、韩文和日文的阅读速度(每分钟字符数)",
"form.prefs.label.custom_css": "自定义 CSS",
"form.prefs.label.custom_js": "自定义 JavaScript",
"form.prefs.label.default_home_page": "默认主页",
"form.prefs.label.default_reading_speed": "其他语言的阅读速度(每分钟字数)",
"form.prefs.label.display_mode": "渐进式网络应用程序(PWA)显示模式",
"form.prefs.label.entries_per_page": "每页条目数",
"form.prefs.label.entry_order": "条目排序字段",
"form.prefs.label.entry_sorting": "条目排序",
"form.prefs.label.entry_swipe": "在触摸屏上启用条目滑动",
"form.prefs.label.external_font_hosts": "外部字体主机",
"form.prefs.label.gesture_nav": "在条目间导航的手势",
"form.prefs.label.keyboard_shortcuts": "启用键盘快捷键",
"form.prefs.label.language": "语言",
"form.prefs.label.mark_read_manually": "手动标记条目为已读",
"form.prefs.label.mark_read_on_media_completion": "仅当音频/视频播放完成 90%% 时标记为已读",
"form.prefs.label.mark_read_on_view": "查看时自动将条目标记为已读",
"form.prefs.label.mark_read_on_view_or_media_completion": "当浏览时标记条目为已读。对于音频/视频,当播放完成 90%% 时标记为已读",
"form.prefs.label.media_playback_rate": "音频/视频的播放速度",
"form.prefs.label.open_external_links_in_new_tab": "在新标签页中打开外部链接(为链接添加 target=\"_blank\")",
"form.prefs.label.show_reading_time": "显示条目的预计阅读时间",
"form.prefs.label.theme": "主题",
"form.prefs.label.timezone": "时区",
"form.prefs.select.alphabetical": "字母顺序",
"form.prefs.select.browser": "浏览器",
"form.prefs.select.created_time": "条目创建时间",
"form.prefs.select.fullscreen": "全屏",
"form.prefs.select.minimal_ui": "最小",
"form.prefs.select.none": "没有任何",
"form.prefs.select.older_first": "旧->新",
"form.prefs.select.publish_time": "条目发布时间",
"form.prefs.select.recent_first": "新->旧",
"form.prefs.select.standalone": "独立",
"form.prefs.select.swipe": "滑动",
"form.prefs.select.tap": "双击",
"form.prefs.select.unread_count": "未读计数",
"form.submit.loading": "加载中…",
"form.submit.saving": "保存中…",
"form.user.label.admin": "管理员",
"form.user.label.confirmation": "确认密码",
"form.user.label.password": "密码",
"form.user.label.username": "用户名",
"menu.about": "关于",
"menu.add_feed": "添加订阅源",
"menu.add_user": "添加用户",
"menu.api_keys": "API 密钥",
"menu.categories": "分类",
"menu.create_api_key": "创建新 API 密钥",
"menu.create_category": "创建分类",
"menu.edit_category": "编辑",
"menu.edit_feed": "编辑",
"menu.export": "导出",
"menu.feed_entries": "条目",
"menu.feeds": "订阅源",
"menu.flush_history": "清除历史记录",
"menu.history": "历史记录",
"menu.home_page": "主页",
"menu.import": "导入",
"menu.integrations": "集成",
"menu.logout": "登出",
"menu.mark_all_as_read": "全部标为已读",
"menu.mark_page_as_read": "将此页标为已读",
"menu.preferences": "偏好设置",
"menu.refresh_all_feeds": "后台刷新所有订阅源",
"menu.refresh_feed": "刷新",
"menu.search": "搜索",
"menu.sessions": "会话",
"menu.settings": "设置",
"menu.shared_entries": "已共享的条目",
"menu.show_all_entries": "显示所有条目",
"menu.show_only_starred_entries": "仅显示已收藏条目",
"menu.show_only_unread_entries": "仅显示未读条目",
"menu.starred": "收藏",
"menu.title": "菜单",
"menu.unread": "未读",
"menu.users": "用户",
"page.about.author": "作者:",
"page.about.build_date": "构建日期:",
"page.about.credits": "鸣谢",
"page.about.db_usage": "数据库大小:",
"page.about.git_commit": "Git 提交:",
"page.about.global_config_options": "全局配置选项",
"page.about.go_version": "Go 版本:",
"page.about.license": "许可证:",
"page.about.postgres_version": "Postgres 版本:",
"page.about.title": "关于",
"page.about.version": "版本:",
"page.add_feed.choose_feed": "选择订阅源",
"page.add_feed.label.url": "URL",
"page.add_feed.legend.advanced_options": "高级选项",
"page.add_feed.no_category": "没有分类。您必须至少有一个分类。",
"page.add_feed.submit": "查找订阅源",
"page.add_feed.title": "新建订阅源",
"page.api_keys.never_used": "从未使用",
"page.api_keys.table.actions": "操作",
"page.api_keys.table.created_at": "创建日期",
"page.api_keys.table.description": "描述",
"page.api_keys.table.last_used_at": "最后使用",
"page.api_keys.table.token": "令牌",
"page.api_keys.title": "API 密钥",
"page.categories.entries": "条目",
"page.categories.feed_count": [
"有 %d 个订阅源"
],
"page.categories.feeds": "订阅源",
"page.categories.no_feed": "无订阅源。",
"page.categories.title": "分类",
"page.categories_count": [
"%d 个分类"
],
"page.category_label": "分类: %s",
"page.edit_category.title": "编辑分类:%s",
"page.edit_feed.etag_header": "ETag 标题:",
"page.edit_feed.last_check": "最后检查时间:",
"page.edit_feed.last_modified_header": "最后修改的 Header:",
"page.edit_feed.last_parsing_error": "最后一次解析错误",
"page.edit_feed.no_header": "无 Header",
"page.edit_feed.title": "编辑订阅源: %s",
"page.edit_user.title": "编辑用户: %s",
"page.entry.attachments": "附件",
"page.feeds.error_count": [
"%d 错误"
],
"page.feeds.last_check": "最后检查:",
"page.feeds.next_check": "下次检查:",
"page.feeds.read_counter": "已读条目数",
"page.feeds.title": "订阅源",
"page.footer.elevator": "Back to top",
"page.history.title": "历史记录",
"page.import.title": "导入",
"page.integration.bookmarklet": "书签小应用",
"page.integration.bookmarklet.help": "此链接允许您通过浏览器书签直接订阅网站。",
"page.integration.bookmarklet.instructions": "将此链接拖动到您的书签栏。",
"page.integration.bookmarklet.name": "添加到 Miniflux",
"page.integration.miniflux_api": "Miniflux API",
"page.integration.miniflux_api_endpoint": "API 端点",
"page.integration.miniflux_api_password": "密码",
"page.integration.miniflux_api_password_value": "您账号的密码",
"page.integration.miniflux_api_username": "用户名",
"page.integrations.title": "集成",
"page.keyboard_shortcuts.close_modal": "关闭对话窗口",
"page.keyboard_shortcuts.download_content": "下载原始内容",
"page.keyboard_shortcuts.go_to_bottom_item": "跳转到最后一条",
"page.keyboard_shortcuts.go_to_categories": "转到分类",
"page.keyboard_shortcuts.go_to_feed": "转到订阅源",
"page.keyboard_shortcuts.go_to_feeds": "转到订阅源列表",
"page.keyboard_shortcuts.go_to_history": "转到历史记录",
"page.keyboard_shortcuts.go_to_next_item": "转到下一条目",
"page.keyboard_shortcuts.go_to_next_page": "转到下一页",
"page.keyboard_shortcuts.go_to_previous_item": "转到上一条目",
"page.keyboard_shortcuts.go_to_previous_page": "转到上一页",
"page.keyboard_shortcuts.go_to_search": "聚焦到搜索框",
"page.keyboard_shortcuts.go_to_settings": "转到设置",
"page.keyboard_shortcuts.go_to_starred": "转到收藏",
"page.keyboard_shortcuts.go_to_top_item": "转到第一条",
"page.keyboard_shortcuts.go_to_unread": "转到未读",
"page.keyboard_shortcuts.mark_page_as_read": "标记当前页为已读",
"page.keyboard_shortcuts.open_comments": "打开评论链接",
"page.keyboard_shortcuts.open_comments_same_window": "在当前标签页中打开评论链接",
"page.keyboard_shortcuts.open_item": "打开选定的条目",
"page.keyboard_shortcuts.open_original": "打开原始链接",
"page.keyboard_shortcuts.open_original_same_window": "在当前标签页中打开原始链接",
"page.keyboard_shortcuts.refresh_all_feeds": "在后台刷新全部订阅源",
"page.keyboard_shortcuts.remove_feed": "移除此订阅源",
"page.keyboard_shortcuts.save_article": "保存条目",
"page.keyboard_shortcuts.scroll_item_to_top": "滚动到顶部",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "显示快捷键帮助",
"page.keyboard_shortcuts.subtitle.actions": "操作",
"page.keyboard_shortcuts.subtitle.items": "条目导航",
"page.keyboard_shortcuts.subtitle.pages": "页面导航",
"page.keyboard_shortcuts.subtitle.sections": "区域导航",
"page.keyboard_shortcuts.title": "键盘快捷键",
"page.keyboard_shortcuts.toggle_star_status": "切换收藏状态",
"page.keyboard_shortcuts.toggle_entry_attachments": "切换展开/折叠条目附件",
"page.keyboard_shortcuts.toggle_read_status_next": "切换已读/未读状态,并切换到下一项",
"page.keyboard_shortcuts.toggle_read_status_prev": "切换已读/未读状态,并切换到上一项",
"page.login.google_signin": "使用 Google 登录",
"page.login.oidc_signin": "使用 %s 登录",
"page.login.title": "登录",
"page.login.webauthn_login": "使用通行密钥登录",
"page.login.webauthn_login.error": "无法使用通行密钥登录",
"page.login.webauthn_login.help": "如果您正在使用安全密钥,请输入您的用户名。如果您正在使用通行密钥(可发现凭证),则无需输入。",
"page.new_api_key.title": "新的 API 密钥",
"page.new_category.title": "新建分类",
"page.new_user.title": "新建用户",
"page.offline.message": "您已离线",
"page.offline.refresh_page": "尝试刷新页面",
"page.offline.title": "离线模式",
"page.read_entry_count": [
"%d 个已读条目"
],
"page.search.title": "搜索结果",
"page.sessions.table.actions": "操作",
"page.sessions.table.current_session": "当前会话",
"page.sessions.table.date": "日期",
"page.sessions.table.ip": "IP 地址",
"page.sessions.table.user_agent": "用户代理",
"page.sessions.title": "会话",
"page.settings.link_google_account": "关联我的 Google 账号",
"page.settings.link_oidc_account": "关联我的 %s 账号",
"page.settings.title": "设置",
"page.settings.unlink_google_account": "解除 Google 账号关联",
"page.settings.unlink_oidc_account": "解除 %s 账号关联",
"page.settings.webauthn.actions": "操作",
"page.settings.webauthn.added_on": "添加于",
"page.settings.webauthn.delete": [
"删除 %d 个通行密钥"
],
"page.settings.webauthn.last_seen_on": "最后使用",
"page.settings.webauthn.passkey_name": "通行密钥名称",
"page.settings.webauthn.passkeys": "通行密钥",
"page.settings.webauthn.register": "注册通行密钥",
"page.settings.webauthn.register.error": "无法注册通行密钥",
"page.shared_entries.title": "已共享的条目",
"page.shared_entries_count": [
"%d 个共享条目"
],
"page.starred.title": "收藏",
"page.starred_entry_count": [
"%d 个收藏条目"
],
"page.total_entry_count": [
"%d 个条目"
],
"page.unread.title": "未读",
"page.unread_entry_count": [
"%d 个未读条目"
],
"page.users.actions": "操作",
"page.users.admin.no": "否",
"page.users.admin.yes": "是",
"page.users.is_admin": "管理员",
"page.users.last_login": "最后登录",
"page.users.never_logged": "从未",
"page.users.title": "用户",
"page.users.username": "用户名",
"page.webauthn_rename.title": "重命名通行密钥",
"pagination.first": "第一页",
"pagination.last": "最后一页",
"pagination.next": "下一页",
"pagination.previous": "上一页",
"search.label": "搜索",
"search.placeholder": "搜索…",
"search.submit": "搜索",
"skip_to_content": "跳转至内容",
"time_elapsed.days": [
"%d 天前"
],
"time_elapsed.hours": [
"%d 小时前"
],
"time_elapsed.minutes": [
"%d 分钟前"
],
"time_elapsed.months": [
"%d 月前"
],
"time_elapsed.not_yet": "未来",
"time_elapsed.now": "刚刚",
"time_elapsed.weeks": [
"%d 周前"
],
"time_elapsed.years": [
"%d 年前"
],
"time_elapsed.yesterday": "昨天",
"tooltip.keyboard_shortcuts": "键盘快捷键:%s",
"tooltip.logged_user": "登录用户:%s"
}
v2-2.2.13/internal/locale/translations/zh_TW.json 0000664 0000000 0000000 00000104755 15062123773 0021672 0 ustar 00root root 0000000 0000000 {
"action.cancel": "取消",
"action.download": "下載",
"action.edit": "編輯",
"action.home_screen": "新增到主螢幕",
"action.import": "匯入",
"action.login": "登入",
"action.or": "或",
"action.remove": "刪除",
"action.remove_feed": "刪除此 Feed",
"action.save": "儲存",
"action.subscribe": "訂閱",
"action.update": "更新",
"alert.account_linked": "您的外部帳號已成功關聯!",
"alert.account_unlinked": "您的外部帳戶已解除關聯!",
"alert.background_feed_refresh": "所有 Feed 正在背景中更新,您可以繼續使用 Miniflux。",
"alert.feed_error": "該 Feed 存在問題",
"alert.no_starred": "目前沒有收藏",
"alert.no_category": "目前沒有分類",
"alert.no_category_entry": "該分類下沒有文章",
"alert.no_feed": "目前沒有 Feed",
"alert.no_feed_entry": "該 Feed 中沒有文章",
"alert.no_feed_in_category": "沒有該類別的 Feed。",
"alert.no_history": "目前沒有歷史",
"alert.no_search_result": "沒有符合搜尋的結果",
"alert.no_shared_entry": "沒有分享文章。",
"alert.no_tag_entry": "沒有與此標籤相符的文章。",
"alert.no_unread_entry": "目前沒有未讀文章",
"alert.no_user": "您是唯一的使用者",
"alert.prefs_saved": "設定已儲存!",
"alert.too_many_feeds_refresh": [
"您已觸發過太多次 Feed 更新,請等待 %d 分鐘後再嘗試。"
],
"confirm.loading": "執行中…",
"confirm.no": "否",
"confirm.question": "您確定嗎?",
"confirm.question.refresh": "您想要強制重新整理嗎?",
"confirm.yes": "是",
"enclosure_media_controls.seek": "移動:",
"enclosure_media_controls.seek.title": "移動 %s 秒",
"enclosure_media_controls.speed": "速度:",
"enclosure_media_controls.speed.faster": "加快",
"enclosure_media_controls.speed.faster.title": "加快 %sx",
"enclosure_media_controls.speed.reset": "重設",
"enclosure_media_controls.speed.reset.title": "重設播放速度為 1x",
"enclosure_media_controls.speed.slower": "放慢",
"enclosure_media_controls.speed.slower.title": "放慢 %sx",
"entry.starred.toast.off": "已取消收藏",
"entry.starred.toast.on": "已新增收藏",
"entry.starred.toggle.off": "取消收藏",
"entry.starred.toggle.on": "新增收藏",
"entry.comments.label": "評論",
"entry.comments.title": "檢視評論",
"entry.estimated_reading_time": [
"需要 %d 分鐘閱讀"
],
"entry.external_link.label": "外部連結",
"entry.save.completed": "完成",
"entry.save.label": "儲存",
"entry.save.title": "儲存這篇文章",
"entry.save.toast.completed": "已儲存文章",
"entry.scraper.completed": "下載完成",
"entry.scraper.label": "下載原文",
"entry.scraper.title": "下載原文內容",
"entry.share.label": "分享",
"entry.share.title": "分享這篇文章",
"entry.shared_entry.label": "分享",
"entry.shared_entry.title": "開啟公共連結",
"entry.state.loading": "載入中…",
"entry.state.saving": "儲存中…",
"entry.status.mark_as_read": "標記為已讀",
"entry.status.mark_as_unread": "標記為未讀",
"entry.status.title": "更改狀態",
"entry.status.toast.read": "已標記為已讀",
"entry.status.toast.unread": "已標記為未讀",
"entry.tags.label": "標籤:",
"entry.tags.more_tags_label": [
"還有 %d 個標籤"
],
"entry.unshare.label": "取消分享",
"error.api_key_already_exists": "此 API 金鑰已存在。",
"error.bad_credentials": "使用者名稱或密碼無效",
"error.category_already_exists": "分類已存在",
"error.category_not_found": "此分類不存在或不屬於您。",
"error.database_error": "資料庫錯誤:%v。",
"error.different_passwords": "兩次輸入的密碼不同",
"error.duplicate_fever_username": "Fever 使用者名稱已被佔用!",
"error.duplicate_googlereader_username": "Google Reader 使用者名稱已被佔用!",
"error.duplicate_linked_account": "該提供者已被其他人綁定!",
"error.duplicated_feed": "該 Feed 已存在。",
"error.empty_file": "該檔案為空",
"error.entries_per_page_invalid": "每頁的文章數無效。",
"error.feed_already_exists": "此 Feed 已存在。",
"error.feed_category_not_found": "此類別不存在或不屬於該使用者。",
"error.feed_format_not_detected": "無法辨識 Feed 格式:%v。",
"error.feed_invalid_blocklist_rule": "阻擋規則無效。",
"error.feed_invalid_keeplist_rule": "保留規則無效。",
"error.feed_mandatory_fields": "必須填寫網址和分類",
"error.feed_not_found": "無法找到此 Feed 或不屬於您。",
"error.feed_title_not_empty": "訂閱的標題不能為空。",
"error.feed_url_not_empty": "訂閱網址不能為空。",
"error.fields_mandatory": "必須填寫全部資訊",
"error.http_bad_gateway": "此網站目前因閘道錯誤無法使用,問題不在 Miniflux,請稍後重試。",
"error.http_body_read": "無法讀取 HTTP 本體內容:%v。",
"error.http_client_error": "HTTP 客戶端錯誤:%v。",
"error.http_empty_response": "HTTP 回應內容為空,可能該網站有防護機制。",
"error.http_empty_response_body": "HTTP 回應本體為空。",
"error.http_forbidden": "拒絕存取此網站,可能該網站有防護機制。",
"error.http_gateway_timeout": "此網站回應逾時,問題不在 Miniflux,請稍後重試。",
"error.http_internal_server_error": "此網站目前因伺服器錯誤無法使用,問題不在 Miniflux,請稍後重試。",
"error.http_not_authorized": "未授權存取此網站,請檢查使用者名稱與密碼。",
"error.http_resource_not_found": "找不到該連結,請確認網址是否正確。",
"error.http_response_too_large": "HTTP 回應過大。您可以在全域設定中提高上限 (需重啟伺服器)。",
"error.http_service_unavailable": "此網站目前因內部問題無法使用,問題不在 Miniflux,請稍後重試。",
"error.http_too_many_requests": "Miniflux 對此網站的請求過多,請稍後重試或調整程式設定。",
"error.http_unexpected_status_code": "此網站回應了意外的 HTTP 狀態碼:%d,請稍後重試。",
"error.invalid_categories_sorting_order": "無效的分類排序",
"error.invalid_default_home_page": "預設主頁無效!",
"error.invalid_display_mode": "無效的顯示模式。",
"error.invalid_entry_direction": "無效的輸入方向。",
"error.invalid_entry_order": "無效的文章排序依據。",
"error.invalid_feed_proxy_url": "代理伺服器網址無效。",
"error.invalid_feed_url": "訂閱網址無效。",
"error.invalid_gesture_nav": "手勢導覽無效。",
"error.invalid_language": "無效的語言。",
"error.invalid_site_url": "Feed 網站的網址無效。",
"error.invalid_theme": "無效的主題。",
"error.invalid_timezone": "無效的時區。",
"error.network_operation": "Miniflux 無法連線到該網站,可能是網路問題:%v。",
"error.network_timeout": "該網站回應過慢,請求逾時:%v。",
"error.password_min_length": "請至少輸入 6 個字元",
"error.proxy_url_not_empty": "代理伺服器網址不能為空。",
"error.settings_block_rule_fieldname_invalid": "無效的封鎖規則:規則 #%d 缺少有效的欄位名稱 (可用選項:%s)",
"error.settings_block_rule_invalid_regex": "無效的封鎖規則:規則 #%d 的模式不是合法的正規表示式",
"error.settings_block_rule_regex_required": "無效的封鎖規則:規則 #%d 沒有提供正規表示式",
"error.settings_block_rule_separator_required": "無效的封鎖規則:規則 #%d 的模式必須用 '=' 分隔",
"error.settings_invalid_domain_list": "網域清單無效。請以空白分隔多個網域。",
"error.settings_keep_rule_fieldname_invalid": "無效的保留規則:規則 #%d 缺少有效的欄位名稱 (可用選項:%s)",
"error.settings_keep_rule_invalid_regex": "無效的保留規則:規則 #%d 的模式不是合法的正規表示式",
"error.settings_keep_rule_regex_required": "無效的保留規則:規則 #%d 沒有提供正規表示式",
"error.settings_keep_rule_separator_required": "無效的保留規則:規則 #%d 的模式必須用 '=' 分隔",
"error.settings_mandatory_fields": "必須填寫使用者名稱、主題、語言以及時區",
"error.settings_media_playback_rate_range": "播放速度超出範圍",
"error.settings_reading_speed_is_positive": "閱讀速度必須是正整數。",
"error.site_url_not_empty": "Feed 網站的網址不能為空。",
"error.subscription_not_found": "找不到任何訂閱",
"error.title_required": "必須填寫標題",
"error.tls_error": "TLS 錯誤:%q。若需忽略 TLS 驗證,可在 Feed 設定中停用。",
"error.unable_to_create_api_key": "無法建立此 API 金鑰。",
"error.unable_to_create_category": "無法建立這個分類",
"error.unable_to_create_user": "無法建立此使用者",
"error.unable_to_detect_rssbridge": "使用 RSS-Bridge 無法找到任何訂閱:%v。",
"error.unable_to_parse_feed": "無法解析此 Feed:%v。",
"error.unable_to_update_category": "無法更新該分類",
"error.unable_to_update_feed": "無法更新此 Feed",
"error.unable_to_update_user": "無法更新此使用者",
"error.unlink_account_without_password": "您必須設定密碼,否則您將無法再次登入。",
"error.user_already_exists": "使用者已存在",
"error.user_mandatory_fields": "必須填寫使用者名稱",
"error.linktaco_missing_required_fields": "LinkTaco API Token 和 Organization Slug 是必需的",
"form.api_key.label.description": "API 金鑰標籤",
"form.category.hide_globally": "在全域未讀列表中隱藏文章",
"form.category.label.title": "標題",
"form.feed.fieldset.general": "通用",
"form.feed.fieldset.integration": "第三方服務",
"form.feed.fieldset.network_settings": "網路設定",
"form.feed.fieldset.rules": "規則",
"form.feed.label.allow_self_signed_certificates": "允許自簽或無效的憑證",
"form.feed.label.apprise_service_urls": "使用逗號分隔的 Apprise 服務網址列表",
"form.feed.label.block_filter_entry_rules": "條目封鎖規則",
"form.feed.label.blocklist_rules": "基於正則表達式的封鎖過濾器",
"form.feed.label.category": "類別",
"form.feed.label.cookie": "設定 Cookies",
"form.feed.label.crawler": "下載原文內容",
"form.feed.label.description": "描述",
"form.feed.label.disable_http2": "停用 HTTP/2 以避免指紋追蹤",
"form.feed.label.disabled": "不要更新此 Feed",
"form.feed.label.feed_password": "Feed 密碼",
"form.feed.label.feed_url": "Feed 網址",
"form.feed.label.feed_username": "Feed 使用者名稱",
"form.feed.label.fetch_via_proxy": "使用應用程式層級設定的代理",
"form.feed.label.hide_globally": "在全域未讀列表中隱藏文章",
"form.feed.label.ignore_http_cache": "忽略 HTTP 快取",
"form.feed.label.keep_filter_entry_rules": "條目允許規則",
"form.feed.label.keeplist_rules": "基於正則表達式的保留過濾器",
"form.feed.label.no_media_player": "無媒體播放器 (音訊/視訊)",
"form.feed.label.ntfy_activate": "推送文章到 ntfy",
"form.feed.label.ntfy_default_priority": "Ntfy 預設優先順序",
"form.feed.label.ntfy_high_priority": "Ntfy 高優先順序",
"form.feed.label.ntfy_low_priority": "Ntfy 低優先順序",
"form.feed.label.ntfy_max_priority": "Ntfy 最高優先順序",
"form.feed.label.ntfy_min_priority": "Ntfy 最低優先順序",
"form.feed.label.ntfy_priority": "Ntfy 優先順序",
"form.feed.label.ntfy_topic": "Ntfy topic (選填)",
"form.feed.label.proxy_url": "代理URL",
"form.feed.label.pushover_activate": "Push entries to pushover.net",
"form.feed.label.pushover_default_priority": "Pushover default priority",
"form.feed.label.pushover_high_priority": "Pushover high priority",
"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消息優先級",
"form.feed.label.rewrite_rules": "內容重寫規則",
"form.feed.label.scraper_rules": "抓取規則",
"form.feed.label.site_url": "網站網址",
"form.feed.label.title": "標題",
"form.feed.label.urlrewrite_rules": "網址重寫規則",
"form.feed.label.user_agent": "覆蓋預設的使用者代理",
"form.feed.label.webhook_url": "覆蓋webhook URL",
"form.import.label.file": "OPML 檔案",
"form.import.label.url": "URL",
"form.integration.apprise_activate": "推送文章到 Apprise",
"form.integration.apprise_services_url": "使用逗號分隔的 Apprise 服務網址列表",
"form.integration.apprise_url": "Apprise API 網址",
"form.integration.betula_activate": "儲存文章到 Betula",
"form.integration.betula_token": "Betula令牌",
"form.integration.betula_url": "Betula 伺服器網址",
"form.integration.cubox_activate": "儲存文章到 Cubox",
"form.integration.cubox_api_link": "Cubox API 連結",
"form.integration.discord_activate": "推送文章到 Discord",
"form.integration.discord_webhook_link": "Discord Webhook 連結",
"form.integration.espial_activate": "儲存文章到 Espial",
"form.integration.espial_api_key": "Espial API 金鑰",
"form.integration.espial_endpoint": "Espial API 端點",
"form.integration.espial_tags": "Espial 標籤",
"form.integration.fever_activate": "啟用 Fever API",
"form.integration.fever_endpoint": "Fever API 端點",
"form.integration.fever_password": "Fever 密碼",
"form.integration.fever_username": "Fever 使用者名稱",
"form.integration.googlereader_activate": "啟用 Google Reader API",
"form.integration.googlereader_endpoint": "Google Reader API 端點:",
"form.integration.googlereader_password": "Google Reader 密碼",
"form.integration.googlereader_username": "Google Reader 使用者名稱",
"form.integration.instapaper_activate": "儲存文章到 Instapaper",
"form.integration.instapaper_password": "Instapaper 密碼",
"form.integration.instapaper_username": "Instapaper 使用者名稱",
"form.integration.karakeep_activate": "儲存文章到 Karakeep",
"form.integration.karakeep_api_key": "Karakeep API 金鑰",
"form.integration.karakeep_url": "Karakeep API 端點",
"form.integration.linkace_activate": "儲存文章到 LinkAce",
"form.integration.linkace_api_key": "LinkAce API 金鑰",
"form.integration.linkace_check_disabled": "停用連結檢查",
"form.integration.linkace_endpoint": "LinkAce API 端點",
"form.integration.linkace_is_private": "標記為私人連結",
"form.integration.linkace_tags": "LinkAce 標籤",
"form.integration.linkding_activate": "儲存文章到 Linkding",
"form.integration.linkding_api_key": "Linkding API 金鑰",
"form.integration.linkding_bookmark": "標記為未讀",
"form.integration.linkding_endpoint": "Linkding API 端點",
"form.integration.linkding_tags": "Linkding 標籤",
"form.integration.linktaco_activate": "儲存文章到 LinkTaco",
"form.integration.linktaco_api_token": "LinkTaco API Token",
"form.integration.linktaco_api_token_hint": "在此取得您的個人存取權杖",
"form.integration.linktaco_org_slug": "Organization Slug",
"form.integration.linktaco_tags": "標籤(最多10個,逗號分隔)",
"form.integration.linktaco_tags_hint": "最多10個標籤,逗號分隔",
"form.integration.linktaco_visibility": "可見性",
"form.integration.linktaco_visibility_public": "公開",
"form.integration.linktaco_visibility_private": "私人",
"form.integration.linktaco_visibility_hint": "私人可見性需要付費的 LinkTaco 帳戶",
"form.integration.linkwarden_activate": "儲存文章到 Linkwarden",
"form.integration.linkwarden_api_key": "Linkwarden API 金鑰",
"form.integration.linkwarden_endpoint": "Linkwarden 基本 URL",
"form.integration.matrix_bot_activate": "推送文章到 Matrix",
"form.integration.matrix_bot_chat_id": "Matrix 房間 ID",
"form.integration.matrix_bot_password": "Matrix 密碼",
"form.integration.matrix_bot_url": "Matrix 伺服器網址",
"form.integration.matrix_bot_user": "Matrix 使用者名稱",
"form.integration.notion_activate": "儲存文章到 Notion",
"form.integration.notion_page_id": "Notion Page ID",
"form.integration.notion_token": "Notion Secret Token",
"form.integration.ntfy_activate": "推送文章到 Ntfy",
"form.integration.ntfy_api_token": "Ntfy API 金鑰 (選填)",
"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_url": "Ntfy 網址 (選填,預設為 ntfy.sh)",
"form.integration.ntfy_username": "Ntfy 使用者名稱 (選填)",
"form.integration.nunux_keeper_activate": "儲存文章到 Nunux Keeper",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API 金鑰",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API 端點",
"form.integration.omnivore_activate": "儲存文章到 Omnivore",
"form.integration.omnivore_api_key": "Omnivore API 金鑰",
"form.integration.omnivore_url": "Omnivore API 端點",
"form.integration.pinboard_activate": "儲存文章到 Pinboard",
"form.integration.pinboard_bookmark": "標記為未讀",
"form.integration.pinboard_tags": "Pinboard 標籤",
"form.integration.pinboard_token": "Pinboard API Token",
"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.raindrop_activate": "儲存文章到 Raindrop",
"form.integration.raindrop_collection_id": "Collection ID",
"form.integration.raindrop_tags": "標籤 (以逗號分隔)",
"form.integration.raindrop_token": "Raindrop 存取金鑰",
"form.integration.readeck_activate": "儲存文章到 Readeck",
"form.integration.readeck_api_key": "Readeck API 金鑰",
"form.integration.readeck_endpoint": "Readeck API 端點",
"form.integration.readeck_labels": "Readeck Labels",
"form.integration.readeck_only_url": "僅傳送網址(而不是完整內容)",
"form.integration.readwise_activate": "儲存文章到 Readwise Reader",
"form.integration.readwise_api_key": "Readwise Reader 存取金鑰",
"form.integration.readwise_api_key_link": "取得您的 Readwise 存取金鑰",
"form.integration.rssbridge_activate": "新增訂閱時檢查 RSS-Bridge",
"form.integration.rssbridge_token": "RSS-Bridge authentication token",
"form.integration.rssbridge_url": "RSS-Bridge 伺服器的網址",
"form.integration.shaarli_activate": "儲存文章到 Shaarli",
"form.integration.shaarli_api_secret": "Shaarli API 金鑰",
"form.integration.shaarli_endpoint": "Shaarli 網址",
"form.integration.shiori_activate": "儲存文章到 Shiori",
"form.integration.shiori_endpoint": "Shiori API 端點",
"form.integration.shiori_password": "Shiori 密碼",
"form.integration.shiori_username": "Shiori 使用者名稱",
"form.integration.slack_activate": "推送文章到 Slack",
"form.integration.slack_webhook_link": "Slack Webhook 連結",
"form.integration.telegram_bot_activate": "推送文章到 Telegram",
"form.integration.telegram_bot_disable_buttons": "不顯示按鈕",
"form.integration.telegram_bot_disable_notification": "停用通知",
"form.integration.telegram_bot_disable_web_page_preview": "停用網頁預覽",
"form.integration.telegram_bot_token": "Bot Token",
"form.integration.telegram_chat_id": "Chat ID",
"form.integration.telegram_topic_id": "Topic ID",
"form.integration.wallabag_activate": "儲存文章到 Wallabag",
"form.integration.wallabag_client_id": "Wallabag 客戶端 ID",
"form.integration.wallabag_client_secret": "Wallabag 客戶端金鑰",
"form.integration.wallabag_endpoint": "Wallabag 基本網址",
"form.integration.wallabag_only_url": "僅傳送網址(而不是完整內容)",
"form.integration.wallabag_password": "Wallabag 密碼",
"form.integration.wallabag_username": "Wallabag 使用者名稱",
"form.integration.wallabag_tags": "Wallabag Tags",
"form.integration.webhook_activate": "啟用 Webhooks",
"form.integration.webhook_secret": "Webhooks Secret",
"form.integration.webhook_url": "Default Webhook 網址",
"form.prefs.fieldset.application_settings": "應用程式設定",
"form.prefs.fieldset.authentication_settings": "使用者認證設定",
"form.prefs.fieldset.global_feed_settings": "全域 Feed 設定",
"form.prefs.fieldset.reader_settings": "閱讀器設定",
"form.prefs.help.external_font_hosts": "以空白分隔允許的外部字型來源。例如:「fonts.gstatic.com fonts.googleapis.com」。",
"form.prefs.label.always_open_external_links": "Read articles by opening external links",
"form.prefs.label.categories_sorting_order": "分類排序",
"form.prefs.label.cjk_reading_speed": "中文、韓文和日文的閱讀速度(每分鐘字元數)",
"form.prefs.label.custom_css": "自訂 CSS",
"form.prefs.label.custom_js": "自訂 JavaScript",
"form.prefs.label.default_home_page": "預設主頁",
"form.prefs.label.default_reading_speed": "其他語言的閱讀速度(每分鐘字)",
"form.prefs.label.display_mode": "漸進式網路應用程式(PWA)顯示模式",
"form.prefs.label.entries_per_page": "每頁文章數",
"form.prefs.label.entry_order": "文章排序依據",
"form.prefs.label.entry_sorting": "文章排序",
"form.prefs.label.entry_swipe": "在觸控式螢幕上啟用文章滑動",
"form.prefs.label.external_font_hosts": "外部字型來源",
"form.prefs.label.gesture_nav": "在文章之間導覽的手勢",
"form.prefs.label.keyboard_shortcuts": "啟用鍵盤快捷鍵",
"form.prefs.label.language": "語言",
"form.prefs.label.mark_read_manually": "僅手動標記為已讀",
"form.prefs.label.mark_read_on_media_completion": "僅在音訊/視訊播放達 90% 時標記為已讀",
"form.prefs.label.mark_read_on_view": "檢視時自動將文章標記為已讀",
"form.prefs.label.mark_read_on_view_or_media_completion": "檢視文章即標記為已讀;若是音訊/視訊則在 90% 播放完成時標記",
"form.prefs.label.media_playback_rate": "音訊/視訊播放速度",
"form.prefs.label.open_external_links_in_new_tab": "在新分頁中開啟外部連結(為連結加上 target=\"_blank\")",
"form.prefs.label.show_reading_time": "顯示文章的預計閱讀時間",
"form.prefs.label.theme": "主題",
"form.prefs.label.timezone": "時區",
"form.prefs.select.alphabetical": "按字母順序",
"form.prefs.select.browser": "瀏覽器",
"form.prefs.select.created_time": "文章建立時間",
"form.prefs.select.fullscreen": "全螢幕",
"form.prefs.select.minimal_ui": "最小",
"form.prefs.select.none": "無",
"form.prefs.select.older_first": "舊→新",
"form.prefs.select.publish_time": "文章發布時間",
"form.prefs.select.recent_first": "新→舊",
"form.prefs.select.standalone": "獨立",
"form.prefs.select.swipe": "滑動",
"form.prefs.select.tap": "雙擊",
"form.prefs.select.unread_count": "未讀計數",
"form.submit.loading": "載入中…",
"form.submit.saving": "儲存中…",
"form.user.label.admin": "管理員",
"form.user.label.confirmation": "再次輸入密碼",
"form.user.label.password": "密碼",
"form.user.label.username": "使用者名稱",
"menu.about": "關於",
"menu.add_feed": "新增 Feed",
"menu.add_user": "新建使用者",
"menu.api_keys": "API 金鑰",
"menu.categories": "分類",
"menu.create_api_key": "建立一個新的 API 金鑰",
"menu.create_category": "新建分類",
"menu.edit_category": "編輯",
"menu.edit_feed": "編輯",
"menu.export": "匯出",
"menu.feed_entries": "文章",
"menu.feeds": "Feeds",
"menu.flush_history": "清理歷史",
"menu.history": "歷史",
"menu.home_page": "主頁",
"menu.import": "匯入",
"menu.integrations": "整合",
"menu.logout": "登出",
"menu.mark_all_as_read": "全部標為已讀",
"menu.mark_page_as_read": "將此頁面標記為已讀",
"menu.preferences": "設定",
"menu.refresh_all_feeds": "在背景更新所有 Feed",
"menu.refresh_feed": "更新",
"menu.search": "搜尋",
"menu.sessions": "工作階段",
"menu.settings": "設定",
"menu.shared_entries": "已分享的文章",
"menu.show_all_entries": "顯示所有文章",
"menu.show_only_starred_entries": "僅顯示收藏文章",
"menu.show_only_unread_entries": "僅顯示未讀文章",
"menu.starred": "收藏",
"menu.title": "導覽",
"menu.unread": "未讀",
"menu.users": "使用者",
"page.about.author": "作者:",
"page.about.build_date": "建構日期:",
"page.about.credits": "版權",
"page.about.db_usage": "Database size:",
"page.about.git_commit": "Git Commit:",
"page.about.global_config_options": "全域設定選項",
"page.about.go_version": "Go 版本:",
"page.about.license": "授權:",
"page.about.postgres_version": "Postgres 版本:",
"page.about.title": "關於",
"page.about.version": "版本:",
"page.add_feed.choose_feed": "選擇一個 Feed",
"page.add_feed.label.url": "網址",
"page.add_feed.legend.advanced_options": "進階選項",
"page.add_feed.no_category": "沒有類別,至少需要有一個類別",
"page.add_feed.submit": "查詢 Feed",
"page.add_feed.title": "新增 Feed",
"page.api_keys.never_used": "沒用過",
"page.api_keys.table.actions": "操作",
"page.api_keys.table.created_at": "建立日期",
"page.api_keys.table.description": "描述",
"page.api_keys.table.last_used_at": "最後使用",
"page.api_keys.table.token": "金鑰",
"page.api_keys.title": "API 金鑰",
"page.categories.entries": "檢視內容",
"page.categories.feed_count": [
"有 %d 個 Feed"
],
"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 標頭:",
"page.edit_feed.last_check": "最後檢查時間:",
"page.edit_feed.last_modified_header": "最後修改的 Header:",
"page.edit_feed.last_parsing_error": "最後一次解析錯誤",
"page.edit_feed.no_header": "無",
"page.edit_feed.title": "編輯 Feed : %s",
"page.edit_user.title": "編輯使用者 : %s",
"page.entry.attachments": "附件",
"page.feeds.error_count": [
"%d 錯誤"
],
"page.feeds.last_check": "最後檢查時間:",
"page.feeds.next_check": "下次檢查時間:",
"page.feeds.read_counter": "已讀文章數",
"page.feeds.title": "Feeds",
"page.footer.elevator": "Back to top",
"page.history.title": "歷史",
"page.import.title": "匯入",
"page.integration.bookmarklet": "書籤小工具",
"page.integration.bookmarklet.help": "您可以透過這個特殊的書籤直接訂閱網站",
"page.integration.bookmarklet.instructions": "拖動這個連結到瀏覽器書籤欄",
"page.integration.bookmarklet.name": "收藏 Miniflux",
"page.integration.miniflux_api": "Miniflux API",
"page.integration.miniflux_api_endpoint": "API 端點",
"page.integration.miniflux_api_password": "密碼",
"page.integration.miniflux_api_password_value": "您帳號的密碼",
"page.integration.miniflux_api_username": "使用者名稱",
"page.integrations.title": "整合",
"page.keyboard_shortcuts.close_modal": "關閉對話視窗",
"page.keyboard_shortcuts.download_content": "下載原文內容",
"page.keyboard_shortcuts.go_to_bottom_item": "轉到底端項目",
"page.keyboard_shortcuts.go_to_categories": "開啟分類頁面",
"page.keyboard_shortcuts.go_to_feed": "轉到 Feed 頁面",
"page.keyboard_shortcuts.go_to_feeds": "開啟 Feeds 頁面",
"page.keyboard_shortcuts.go_to_history": "開啟歷史頁面",
"page.keyboard_shortcuts.go_to_next_item": "下一文章",
"page.keyboard_shortcuts.go_to_next_page": "下一頁",
"page.keyboard_shortcuts.go_to_previous_item": "上一文章",
"page.keyboard_shortcuts.go_to_previous_page": "上一頁",
"page.keyboard_shortcuts.go_to_search": "將焦點放在搜尋表單上",
"page.keyboard_shortcuts.go_to_settings": "開啟設定頁面",
"page.keyboard_shortcuts.go_to_starred": "開啟收藏頁面",
"page.keyboard_shortcuts.go_to_top_item": "轉到頂端項目",
"page.keyboard_shortcuts.go_to_unread": "開啟未讀頁面",
"page.keyboard_shortcuts.mark_page_as_read": "將此頁面標記為已讀",
"page.keyboard_shortcuts.open_comments": "開啟評論連結",
"page.keyboard_shortcuts.open_comments_same_window": "在目前標籤頁中開啟評論連結",
"page.keyboard_shortcuts.open_item": "開啟選定的文章",
"page.keyboard_shortcuts.open_original": "開啟原始連結",
"page.keyboard_shortcuts.open_original_same_window": "在目前標籤頁中開啟原始連結",
"page.keyboard_shortcuts.refresh_all_feeds": "在背景更新所有 Feed",
"page.keyboard_shortcuts.remove_feed": "刪除此 Feed",
"page.keyboard_shortcuts.save_article": "儲存文章",
"page.keyboard_shortcuts.scroll_item_to_top": "捲動到頂端",
"page.keyboard_shortcuts.show_keyboard_shortcuts": "顯示快捷鍵幫助",
"page.keyboard_shortcuts.subtitle.actions": "操作",
"page.keyboard_shortcuts.subtitle.items": "文章導覽",
"page.keyboard_shortcuts.subtitle.pages": "頁面導覽",
"page.keyboard_shortcuts.subtitle.sections": "分欄導覽",
"page.keyboard_shortcuts.title": "快捷鍵",
"page.keyboard_shortcuts.toggle_star_status": "切換收藏狀態",
"page.keyboard_shortcuts.toggle_entry_attachments": "展開/折疊文章附件",
"page.keyboard_shortcuts.toggle_read_status_next": "切換已讀/未讀狀態,並聚焦到下一個",
"page.keyboard_shortcuts.toggle_read_status_prev": "切換已讀/未讀狀態,並聚焦到上一個",
"page.login.google_signin": "使用 Google 登入",
"page.login.oidc_signin": "使用 %s 登入",
"page.login.title": "登入",
"page.login.webauthn_login": "使用密碼登入",
"page.login.webauthn_login.error": "無法使用密碼登入",
"page.login.webauthn_login.help": "使用安全金鑰登入時,請輸入使用者名稱。若使用可探索式 Passkey 則無需輸入。",
"page.new_api_key.title": "新的 API 金鑰",
"page.new_category.title": "新分類",
"page.new_user.title": "新使用者",
"page.offline.message": "您已離線",
"page.offline.refresh_page": "嘗試重新整理頁面",
"page.offline.title": "離線模式",
"page.read_entry_count": [
"%d 篇已讀文章"
],
"page.search.title": "搜尋結果",
"page.sessions.table.actions": "操作",
"page.sessions.table.current_session": "目前工作階段",
"page.sessions.table.date": "日期",
"page.sessions.table.ip": "IP 位址",
"page.sessions.table.user_agent": "使用者代理",
"page.sessions.title": "工作階段",
"page.settings.link_google_account": "關聯我的 Google 帳號",
"page.settings.link_oidc_account": "關聯我的 %s 帳號",
"page.settings.title": "設定",
"page.settings.unlink_google_account": "解除 Google 帳號關聯",
"page.settings.unlink_oidc_account": "解除 %s 帳號關聯",
"page.settings.webauthn.actions": "操作",
"page.settings.webauthn.added_on": "新增時間",
"page.settings.webauthn.delete": [
"刪除 %d 個 Passkey"
],
"page.settings.webauthn.last_seen_on": "最後使用時間",
"page.settings.webauthn.passkey_name": "Passkey 名稱",
"page.settings.webauthn.passkeys": "Passkeys",
"page.settings.webauthn.register": "註冊 Passkey",
"page.settings.webauthn.register.error": "無法註冊 Passkey",
"page.shared_entries.title": "已分享的文章",
"page.shared_entries_count": [
"已分享 %d 篇文章"
],
"page.starred.title": "收藏",
"page.starred_entry_count": [
"%d 篇收藏文章"
],
"page.total_entry_count": [
"總共 %d 篇文章"
],
"page.unread.title": "未讀",
"page.unread_entry_count": [
"%d 篇未讀文章"
],
"page.users.actions": "操作",
"page.users.admin.no": "否",
"page.users.admin.yes": "是",
"page.users.is_admin": "管理員",
"page.users.last_login": "最後登入時間",
"page.users.never_logged": "從未登入",
"page.users.title": "使用者",
"page.users.username": "使用者名稱",
"page.webauthn_rename.title": "重新命名 Passkey",
"pagination.first": "第一頁",
"pagination.last": "最後一頁",
"pagination.next": "下一頁",
"pagination.previous": "上一頁",
"search.label": "搜尋",
"search.placeholder": "搜尋…",
"search.submit": "送出",
"skip_to_content": "跳到主要內容",
"time_elapsed.days": [
"%d 天前"
],
"time_elapsed.hours": [
"%d 小時前"
],
"time_elapsed.minutes": [
"%d 分鐘前"
],
"time_elapsed.months": [
"%d 個月前"
],
"time_elapsed.not_yet": "未來",
"time_elapsed.now": "剛剛",
"time_elapsed.weeks": [
"%d 週前"
],
"time_elapsed.years": [
"%d 年前"
],
"time_elapsed.yesterday": "昨天",
"tooltip.keyboard_shortcuts": "快捷鍵:%s",
"tooltip.logged_user": "目前登入 %s"
} v2-2.2.13/internal/mediaproxy/ 0000775 0000000 0000000 00000000000 15062123773 0016131 5 ustar 00root root 0000000 0000000 v2-2.2.13/internal/mediaproxy/media_proxy_test.go 0000664 0000000 0000000 00000060666 15062123773 0022055 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package mediaproxy // import "miniflux.app/v2/internal/mediaproxy"
import (
"net/http"
"os"
"testing"
"github.com/gorilla/mux"
"miniflux.app/v2/internal/config"
)
func TestProxyFilterWithHttpDefault(t *testing.T) {
os.Clearenv()
os.Setenv("MEDIA_PROXY_MODE", "http-only")
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test")
var err error
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
r := mux.NewRouter()
r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
input := `












`
expected := `
`
output := RewriteDocumentWithRelativeProxyURL(r, input)
if expected != output {
t.Errorf(`Not expected output: got %s`, output)
}
}
func TestProxyWithImageSourceDataURL(t *testing.T) {
os.Clearenv()
os.Setenv("MEDIA_PROXY_MODE", "all")
os.Setenv("MEDIA_PROXY_RESOURCE_TYPES", "image")
var err error
parser := config.NewConfigParser()
config.Opts, err = parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
r := mux.NewRouter()
r.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
input := `
HTML content
" { t.Errorf("Incorrect entry content, got: %s", feed.Entries[0].Content) } if feed.Entries[0].Author != "Mark Pilgrim" { t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author) } } func TestParseAtom03WithoutSiteURL(t *testing.T) { data := `Some text.
Some text.
Some text.
" { t.Errorf("Incorrect entry content, got: %s", feed.Entries[0].Content) } } func TestParseAtom03WithBase64Content(t *testing.T) { data := `Some text.
" { t.Errorf("Incorrect entry content, got: %s", feed.Entries[0].Content) } } v2-2.2.13/internal/reader/atom/atom_10.go 0000664 0000000 0000000 00000017326 15062123773 0017752 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. // SPDX-License-Identifier: Apache-2.0 package atom // import "miniflux.app/v2/internal/reader/atom" import ( "encoding/xml" "html" "strings" "miniflux.app/v2/internal/reader/media" ) // The "atom:feed" element is the document (i.e., top-level) element of // an Atom Feed Document, acting as a container for metadata and data // associated with the feed. Its element children consist of metadata // elements followed by zero or more atom:entry child elements. // // Specs: // https://tools.ietf.org/html/rfc4287 // https://validator.w3.org/feed/docs/atom.html type atom10Feed struct { XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"` // The "atom:id" element conveys a permanent, universally unique // identifier for an entry or feed. // // Its content MUST be an IRI, as defined by [RFC3987]. Note that the // definition of "IRI" excludes relative references. Though the IRI // might use a dereferencable scheme, Atom Processors MUST NOT assume it // can be dereferenced. // // atom:feed elements MUST contain exactly one atom:id element. ID string `xml:"http://www.w3.org/2005/Atom id"` // The "atom:title" element is a Text construct that conveys a human- // readable title for an entry or feed. // // atom:feed elements MUST contain exactly one atom:title element. Title atom10Text `xml:"http://www.w3.org/2005/Atom title"` // The "atom:subtitle" element is a Text construct that // contains a human-readable description or subtitle for the feed. Subtitle atom10Text `xml:"http://www.w3.org/2005/Atom subtitle"` // The "atom:author" element is a Person construct that indicates the // author of the entry or feed. // // atom:feed elements MUST contain one or more atom:author elements, // unless all of the atom:feed element's child atom:entry elements // contain at least one atom:author element. Authors atomPersons `xml:"http://www.w3.org/2005/Atom author"` // The "atom:icon" element's content is an IRI reference [RFC3987] that // identifies an image that provides iconic visual identification for a // feed. // // atom:feed elements MUST NOT contain more than one atom:icon element. Icon string `xml:"http://www.w3.org/2005/Atom icon"` // The "atom:logo" element's content is an IRI reference [RFC3987] that // identifies an image that provides visual identification for a feed. // // atom:feed elements MUST NOT contain more than one atom:logo element. Logo string `xml:"http://www.w3.org/2005/Atom logo"` // atom:feed elements SHOULD contain one atom:link element with a rel // attribute value of "self". This is the preferred URI for // retrieving Atom Feed Documents representing this Atom feed. // // atom:feed elements MUST NOT contain more than one atom:link // element with a rel attribute value of "alternate" that has the // same combination of type and hreflang attribute values. Links atomLinks `xml:"http://www.w3.org/2005/Atom link"` // The "atom:category" element conveys information about a category // associated with an entry or feed. This specification assigns no // meaning to the content (if any) of this element. // // atom:feed elements MAY contain any number of atom:category // elements. Categories atomCategories `xml:"http://www.w3.org/2005/Atom category"` Entries []atom10Entry `xml:"http://www.w3.org/2005/Atom entry"` } type atom10Entry struct { // The "atom:id" element conveys a permanent, universally unique // identifier for an entry or feed. // // Its content MUST be an IRI, as defined by [RFC3987]. Note that the // definition of "IRI" excludes relative references. Though the IRI // might use a dereferencable scheme, Atom Processors MUST NOT assume it // can be dereferenced. // // atom:entry elements MUST contain exactly one atom:id element. ID string `xml:"http://www.w3.org/2005/Atom id"` // The "atom:title" element is a Text construct that conveys a human- // readable title for an entry or feed. // // atom:entry elements MUST contain exactly one atom:title element. Title atom10Text `xml:"http://www.w3.org/2005/Atom title"` // The "atom:published" element is a Date construct indicating an // instant in time associated with an event early in the life cycle of // the entry. Published string `xml:"http://www.w3.org/2005/Atom published"` // The "atom:updated" element is a Date construct indicating the most // recent instant in time when an entry or feed was modified in a way // the publisher considers significant. Therefore, not all // modifications necessarily result in a changed atom:updated value. // // atom:entry elements MUST contain exactly one atom:updated element. Updated string `xml:"http://www.w3.org/2005/Atom updated"` // atom:entry elements MUST NOT contain more than one atom:link // element with a rel attribute value of "alternate" that has the // same combination of type and hreflang attribute values. Links atomLinks `xml:"http://www.w3.org/2005/Atom link"` // atom:entry elements MUST contain an atom:summary element in either // of the following cases: // * the atom:entry contains an atom:content that has a "src" // attribute (and is thus empty). // * the atom:entry contains content that is encoded in Base64; // i.e., the "type" attribute of atom:content is a MIME media type // [MIMEREG], but is not an XML media type [RFC3023], does not // begin with "text/", and does not end with "/xml" or "+xml". // // atom:entry elements MUST NOT contain more than one atom:summary // element. Summary atom10Text `xml:"http://www.w3.org/2005/Atom summary"` // atom:entry elements MUST NOT contain more than one atom:content // element. Content atom10Text `xml:"http://www.w3.org/2005/Atom content"` // The "atom:author" element is a Person construct that indicates the // author of the entry or feed. // // atom:entry elements MUST contain one or more atom:author elements Authors atomPersons `xml:"http://www.w3.org/2005/Atom author"` // The "atom:category" element conveys information about a category // associated with an entry or feed. This specification assigns no // meaning to the content (if any) of this element. // // atom:entry elements MAY contain any number of atom:category // elements. Categories atomCategories `xml:"http://www.w3.org/2005/Atom category"` media.MediaItemElement } // A Text construct contains human-readable text, usually in small // quantities. The content of Text constructs is Language-Sensitive. // Specs: https://datatracker.ietf.org/doc/html/rfc4287#section-3.1 // Text: https://datatracker.ietf.org/doc/html/rfc4287#section-3.1.1.1 // HTML: https://datatracker.ietf.org/doc/html/rfc4287#section-3.1.1.2 // XHTML: https://datatracker.ietf.org/doc/html/rfc4287#section-3.1.1.3 type atom10Text struct { Type string `xml:"type,attr"` CharData string `xml:",chardata"` InnerXML string `xml:",innerxml"` XHTMLRootElement atomXHTMLRootElement `xml:"http://www.w3.org/1999/xhtml div"` } func (a *atom10Text) body() string { var content string if strings.EqualFold(a.Type, "xhtml") { content = a.xhtmlContent() } else { content = a.CharData } return strings.TrimSpace(content) } func (a *atom10Text) title() string { var content string switch { case strings.EqualFold(a.Type, "xhtml"): content = a.xhtmlContent() case strings.Contains(a.InnerXML, "