Compare commits

...

82 Commits

Author SHA1 Message Date
Athou
b8e254dab6 release 3.2.0 2023-05-05 11:04:41 +02:00
Athou
4059160d90 update changelog 2023-05-05 11:04:04 +02:00
Athou
e0f242fe22 add welcome page 2023-05-05 09:55:53 +02:00
Athou
05453364ff only apply hover effect for unread entries (same as commafeed v2) 2023-05-05 09:36:23 +02:00
Athou
c3aedd935d move notifications out of the way (#1054) 2023-05-05 09:36:23 +02:00
Athou
99a7f72448 use https for sharing urls 2023-05-04 13:04:30 +02:00
Athou
56ae1eadbc enable redis connections with ACLs 2023-05-04 09:12:02 +02:00
Athou
4828c03bbf restore google analytics feature 2023-05-03 20:49:28 +02:00
Athou
cfc07764b4 extract changelog entry when creating a release 2023-05-02 12:05:20 +02:00
Athou
91938cc3b9 create GitHub release after Docker image has been published 2023-05-01 18:37:18 +02:00
Athou
c62a84a9ea update dependency groups that were moved 2023-05-01 18:34:05 +02:00
Athou
0b16b6bb86 release 3.1.0 2023-05-01 18:25:16 +02:00
Athou
6a8f7f0a40 add release script 2023-05-01 18:23:35 +02:00
Athou
42ca0967b6 create release on tag 2023-05-01 17:39:01 +02:00
Athou
deb29f0e88 fix metrics page 2023-05-01 17:07:17 +02:00
Athou
714af986b0 readme update 2023-05-01 17:05:38 +02:00
Athou
4ff26366a5 there's no need to update disabledUntil here anymore because findNextUpdatableFeeds will always be called when the queue is empty 2023-05-01 10:04:43 +02:00
Athou
9c628a8f53 make each step of feed fetching return its own model 2023-05-01 09:58:19 +02:00
Athou
4a40f2b8f7 no need to log if we're not sending any notification 2023-04-30 23:05:48 +02:00
Athou
9a2dda626c changelog update 2023-04-30 23:05:48 +02:00
Athou
a9ff491da0 hide request log in production 2023-04-30 16:21:38 +02:00
Athou
5c5a7d20de in production, no need to see warnings 2023-04-30 16:03:47 +02:00
Athou
05ae4eb529 replace homemade threadpool framework with rxjava 2023-04-30 15:34:32 +02:00
Athou
15f93b198c remove warning 2023-04-29 09:20:34 +02:00
Athou
0a99dacb6b use urlcanon instead of crawler4j because we only used it for url canonization 2023-04-29 09:20:15 +02:00
Athou
00f6c04611 various dependency updates 2023-04-29 09:17:59 +02:00
Athou
d9b899b53f prevent entries from having the hover background color when clicked on mobile 2023-04-28 19:44:26 +02:00
Athou
d96f8da8fd remove deprecation warnings (already done in config.yml.example) 2023-04-28 19:44:26 +02:00
Athou
ababcf7850 remove unnecessary "subscriptions" field on Feed
hopefully removes the error that happens sometimes:
Illegal attempt to associate a collection with two open sessions. Collection : [com.commafeed.backend.model.Feed.subscriptions]
2023-04-28 19:44:26 +02:00
Athou
f23bfaf694 use a different color for hover than read and unread backgrounds 2023-04-28 19:44:26 +02:00
Athou
cac05dee0b store view mode in localStorage (#1051) 2023-04-27 14:42:55 +02:00
Athou
155c93d371 reorder Dockerfile to put changing layers last 2023-04-27 09:38:23 +02:00
Athou
9a61ee7530 create a 'master' docker tag for the latest master version 2023-04-27 09:38:23 +02:00
Athou
4bea1c5e5c restore hover effect from commafeed v2 2023-04-27 07:57:09 +02:00
Athou
9ccc26b0b0 tweak compact layout a little bit more 2023-04-27 07:30:43 +02:00
Athou
5cd3787d6f add i18n placeholders for new label 2023-04-26 23:37:39 +02:00
Athou
807b1f62a1 add an even more compact entry layout 2023-04-26 22:50:43 +02:00
Athou
c15db54d5a bump versions 2023-04-26 09:43:10 +02:00
Athou
aa7b078121 readme update 2023-04-25 15:34:20 +02:00
Athou
99130d0181 combine EnvironmentSubstitutor and SubstitutingSourceProvider (#1050) 2023-04-25 10:40:14 +02:00
Athou
90e2036cbe build for armv7 too 2023-04-25 10:24:22 +02:00
Athou
c2f3e42867 Readme update 2023-04-25 08:57:55 +02:00
Athou
bd33369a41 Merge branch 'develop' 2023-04-25 08:48:49 +02:00
Athou
4f625d8ed5 add docker support 2023-04-25 07:48:52 +02:00
Athou
866fe56dd2 remove warning "HV000254: Missing parameter metadata" 2023-04-25 07:48:11 +02:00
Athou
5f37dbca4c fix env variables support, now works without having to change yml file 2023-04-24 20:28:58 +02:00
Athou
c49e617dfe lombok update 2023-04-24 18:56:47 +02:00
Athou
e763ffd4cf allow access to entries in json format with api key (fixes #1049) 2023-04-22 07:27:59 +02:00
Athou
20ab7dd3e1 readd bitcoin address 2023-03-16 17:09:12 +01:00
Jérémie Panzer
55741c6332 Merge pull request #1046 from dayuer/patch-1
Update zh.js
2023-03-13 16:41:01 +01:00
dayuer
42d85336a8 Update zh.js
Added some entry translations.
2023-03-13 23:33:05 +08:00
Athou
639b82f494 add goal to start typescript in watch mode, helps during big refactorings 2023-03-10 11:37:29 +01:00
Athou
5003c176a2 display parent category name in category selects (fixes #1045) 2023-03-07 08:40:02 +01:00
Athou
10bfbbec17 restore one-click list refresh (#1040) 2023-03-06 14:08:48 +01:00
Athou
3da900db7f add missing parenthesis in feed entry filtering example 2023-03-01 13:50:07 +01:00
Athou
9f421ec3b0 add context menu to navigate to feed if a category is displayed 2023-02-27 19:56:32 +01:00
Athou
69fb11eee0 add context menu item to open link in new tab 2023-02-27 07:41:13 +01:00
Athou
ffbb85df43 add context menu on entry headers 2023-02-24 15:18:04 +01:00
Athou
a4e78c4e0d throttle event 2023-02-24 14:53:30 +01:00
Jérémie Panzer
274c5ae165 Merge pull request #1039 from bartschinski/patch-1
Update german translation in de.js
2023-02-10 10:35:31 +01:00
Phillip Bartschinski
39c4012a1a Update de.js
Add missing translations
2023-02-10 10:28:21 +01:00
Athou
6d4b0cbdef bump all dependencies 2023-02-04 08:34:23 +01:00
Athou
ea4b120a85 prevent full feed fetching next time we fetch it if caching header values changed but content did not (#1037) 2023-01-27 08:49:59 +01:00
Athou
5c2454c331 bring back "refresh all my feeds" (#1036) 2023-01-19 07:29:19 +01:00
Athou
4ff46965c4 add websocket support to immediately refresh tree when new entries are available 2023-01-18 20:58:45 +01:00
Athou
33e3f7ea3c feeds added manually to the queue now refresh immediately instead of waiting up to 15s (#1036) 2023-01-18 18:53:38 +01:00
Athou
347fc4f2c8 allow to force refresh feed anytime (#1036) 2023-01-18 18:53:38 +01:00
Athou
2b4ff4a8a5 on fetch error and not under heavy load, don't increase refresh interval exponentially 2023-01-18 18:53:38 +01:00
Jorengarenar
f7d34983e0 Allow API key for count of unread 2023-01-10 00:34:25 +01:00
Athou
3271d69fcb allow session configuration (#1028) 2023-01-04 11:32:08 +01:00
Jérémie Panzer
7ea24b21f8 Merge pull request #1023 from Jorengarenar/develop
Enable environment variables in config.yml
2023-01-04 07:26:23 +01:00
Jorengarenar
b2b608e8c3 Enable environment variables in config.yml 2023-01-03 19:46:01 +01:00
Athou
e44ea5bc96 re-add dropwizard-migrations to be able to use liquibase from command line 2022-11-15 10:48:55 +01:00
Athou
fa58b1e53f reload entries after marking everything as read, as commafeed 2.x does 2022-11-15 08:24:18 +01:00
Athou
9466bc544c show placeholder when favicon is loading 2022-11-08 11:57:59 +01:00
Athou
9e65f5726c require src and alt for images 2022-11-08 11:52:47 +01:00
Athou
fc2c3740a0 dependencies update 2022-11-08 11:52:47 +01:00
Athou
2095a6512b make compact mode more compact 2022-11-05 14:13:19 +01:00
Athou
a461a72224 enable maven cache to speed up build 2022-11-04 09:08:21 +01:00
Athou
f9e7653901 restore swipe-to-right to toggle read/unread status (#1019) 2022-11-04 08:57:33 +01:00
Jérémie Panzer
754ac166e0 Merge pull request #1017 from tristianc/bugfix/builddir
Determine build directory from variable instead of hardcoding it.
2022-09-29 15:54:14 +02:00
Tristian Celestin
0b18334236 Determine build directory from variable instead of hardcoding it. 2022-09-29 09:49:20 -04:00
120 changed files with 15993 additions and 18686 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
# ignore everything
*
# allow only what we need
!commafeed-server/target/commafeed.jar
!commafeed-server/config.yml.example

View File

@@ -1,27 +1,89 @@
name: Java CI name: Java CI
on: [push] on: [ push ]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
java: ["8", "11", "17"] java: [ "8", "11", "17" ]
steps: steps:
- uses: actions/checkout@v3 - name: Checkout
uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
# Setup
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Set up Java - name: Set up Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
distribution: "temurin" distribution: "temurin"
cache: "maven"
# Build
- name: Build with Maven - name: Build with Maven
run: mvn --batch-mode --update-snapshots verify run: mvn --batch-mode --update-snapshots verify
- uses: actions/upload-artifact@v3
- name: Upload JAR
uses: actions/upload-artifact@v3
if: ${{ matrix.java == '8' }} if: ${{ matrix.java == '8' }}
with: with:
name: commafeed.jar name: commafeed.jar
path: commafeed-server/target/commafeed.jar path: commafeed-server/target/commafeed.jar
# Docker
- name: Login to Container Registry
uses: docker/login-action@v2
if: ${{ matrix.java == '8' }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker build and push tag
uses: docker/build-push-action@v4
if: ${{ matrix.java == '8' && github.ref_type == 'tag' }}
with:
context: .
push: true
platforms: linux/amd64,linux/arm/v7
tags: |
athou/commafeed:latest
athou/commafeed:${{ github.ref_name }}
- name: Docker build and push master
uses: docker/build-push-action@v4
if: ${{ matrix.java == '8' && github.ref_name == 'master' }}
with:
context: .
push: true
platforms: linux/amd64,linux/arm/v7
tags: athou/commafeed:master
# Create GitHub release after Docker image has been published
- name: Extract Changelog Entry
uses: mindsers/changelog-reader-action@v2
if: ${{ matrix.java == '8' && github.ref_type == 'tag' }}
id: changelog_reader
with:
version: ${{ github.ref_name }}
- name: Create GitHub release
uses: softprops/action-gh-release@v1
if: ${{ matrix.java == '8' && github.ref_type == 'tag' }}
with:
name: CommaFeed ${{ github.ref_name }}
body: ${{ steps.changelog_reader.outputs.changes }}
draft: false
prerelease: false
files: |
commafeed-server/target/commafeed.jar
commafeed-server/config.yml.example

View File

@@ -1,80 +0,0 @@
v 3.0.0
- complete overhaul of the UI
- backend and frontend are now in separate maven modules
- no changes to the api or the database
v 2.6.0
- add support for media content as a backup for missing content (useful for youtube feeds)
- correctly follow http error code 308 redirects
- fixed a bug that prevented users from deleting their account
- fixed a bug that made commafeed store entry contents multiple times
- fixed a bug that prevented the app to be used as an installed app on mobile devices if the context path of commafeed was not "/"
- fixed a bug that prevented entries from being "marked as read older than xxx" for a feed that was just added
- removed support for google+ and readability as those services no longer exist
- removed support for deploying on openshift
- removed alphabetical sorting of entries because of really poor performance (title cannot be indexed)
- improve performance for instances with the heavy load setting enabled by preventing CommaFeed from fetching feeds from users that did not log in for a long time
- various dependencies upgrades (notably dropwizard from 1.3 to 2.1)
- add support for mariadb
- add support for java17+ runtime
- various security improvements
v 2.5.0
- unread count is now displayed in a favicon badge when supported
- the user agent string for the bot fetching feeds is now configurable
- feed parsing performance improvements
- support for java9+ runtime
- can now properly start from an empty postgresql database
v 2.4.0
- users were not able to change password or delete account
- fix api key generation
- feed entries can now be sorted alphabetically
- fix facebook sharing
- fix layout on iOS
- postgresql driver update (fix for postgres 9.6)
- various internationalization fixes
- security fixes
v 2.3.0
- dropwizard upgrade 0.9.1
- feed enclosures are hidden if they already displayed in the content
- fix youtube favicons
- various internationalization fixes
v 2.2.0
- fix youtube and instagram favicon fetching
- mark as read filter was lost when a feed was rearranged with drag&drop
- feed entry categories are now displayed if available
- various performance and dependencies upgrades
- java8 is now required
v 2.1.0
- dropwizard upgrade to 0.8.0
- you have to remove the "app.contextPath" setting from your yml file, you can optionally use server.applicationContextPath instead
- new setting app.maxFeedCapacity for deleting old entries
- ability to set filtering expressions for subscriptions to automatically mark new entries as read based on title, content, author or url.
- ability to use !keyword or -keyword to exclude a keyword from a search query
- facebook feeds now show user favicon instead of facebook favicon
- new dark theme 'nightsky'
v 2.0.3
- internet explorer ajax cache workaround
- categories are now deletable again
- openshift support is back
- youtube feeds now show user favicon instead of youtube favicon
v 2.0.2
- api using the api key is now working again
- context path is now configurable in config.yml (see app.contextPath in config.yml.example)
- fix login on firefox when fields are autofilled by the browser
- fix scrolling of subscriptions list on mobile
- user is now logged in after registration
- fix link to documentation on home page and about page
- fields autocomplete is disabled on the profile page
- users are able to delete their account again
- chinese and malaysian translation files are now correctly loaded
- software version in user-agent when fetching feeds is no longer hardcoded
- admin settings page is now read only, settings are configured in config.yml
- added link to metrics on the admin settings page
- Rome (rss library) upgrade to 1.5.0
v 2.0.1
- the redis pool no longer throws an exception when it is unable to aquire a new connection
v2.0.0
- The backend has been completely rewritten using Dropwizard instead of TomEE, resulting in a lot less memory consumption and better overall performances.
See the README on how to build CommaFeed from now on.
- CommaFeed should no longer fetch the same feed multiple times in a row
- Users can use their username or email to log in

134
CHANGELOG.md Normal file
View File

@@ -0,0 +1,134 @@
# Changelog
## [3.2.0]
- restore the welcome page
- only apply hover effect for unread entries (same as commafeed v2)
- move notifications at the bottom of the screen
- always use https for sharing urls
- add support for redis ACLs
- transition to google analytics v4
## [3.1.0]
- add an even more compact layout
- restore hover effect from commafeed 2.x
- view mode (compact, expanded, ...) is now stored on the device so you can have a different view mode on desktop and
mobile
- fix for the "Illegal attempt to associate a collection with two open sessions." error
- feed fetching workflow is now orchestrated with rxjava, removing a lot of code
## [3.0.1]
- allow env variable substitution in config.yml
- e.g. having a custom config.yml file with `app.session.path=${SOME_ENV_VAR}` will substitute `SOME_ENV_VAR` with
its value
- allow env variable prefixed with `CF_` to override config.yml properties
- e.g. setting `CF_APP_ALLOWREGISTRATIONS=true` will set `app.allowRegistrations` to `true`
## [3.0.0]
- complete overhaul of the UI
- backend and frontend are now in separate maven modules
- no changes to the api or the database
- Docker images are now automatically built and available at https://hub.docker.com/r/athou/commafeed
## [2.6.0]
- add support for media content as a backup for missing content (useful for youtube feeds)
- correctly follow http error code 308 redirects
- fixed a bug that prevented users from deleting their account
- fixed a bug that made commafeed store entry contents multiple times
- fixed a bug that prevented the app to be used as an installed app on mobile devices if the context path of commafeed
was not "/"
- fixed a bug that prevented entries from being "marked as read older than xxx" for a feed that was just added
- removed support for google+ and readability as those services no longer exist
- removed support for deploying on openshift
- removed alphabetical sorting of entries because of really poor performance (title cannot be indexed)
- improve performance for instances with the heavy load setting enabled by preventing CommaFeed from fetching feeds from
users that did not log in for a long time
- various dependencies upgrades (notably dropwizard from 1.3 to 2.1)
- add support for mariadb
- add support for java17+ runtime
- various security improvements
## [2.5.0]
- unread count is now displayed in a favicon badge when supported
- the user agent string for the bot fetching feeds is now configurable
- feed parsing performance improvements
- support for java9+ runtime
- can now properly start from an empty postgresql database
## [2.4.0]
- users were not able to change password or delete account
- fix api key generation
- feed entries can now be sorted alphabetically
- fix facebook sharing
- fix layout on iOS
- postgresql driver update (fix for postgres 9.6)
- various internationalization fixes
- security fixes
## [2.3.0]
- dropwizard upgrade 0.9.1
- feed enclosures are hidden if they already displayed in the content
- fix youtube favicons
- various internationalization fixes
## [2.2.0]
- fix youtube and instagram favicon fetching
- mark as read filter was lost when a feed was rearranged with drag&drop
- feed entry categories are now displayed if available
- various performance and dependencies upgrades
- java8 is now required
## [2.1.0]
- dropwizard upgrade to 0.8.0
- you have to remove the "app.contextPath" setting from your yml file, you can optionally use
server.applicationContextPath instead
- new setting app.maxFeedCapacity for deleting old entries
- ability to set filtering expressions for subscriptions to automatically mark new entries as read based on title,
content, author or url.
- ability to use !keyword or -keyword to exclude a keyword from a search query
- facebook feeds now show user favicon instead of facebook favicon
- new dark theme 'nightsky'
## [2.0.3]
- internet explorer ajax cache workaround
- categories are now deletable again
- openshift support is back
- youtube feeds now show user favicon instead of youtube favicon
## [2.0.2]
- api using the api key is now working again
- context path is now configurable in config.yml (see app.contextPath in config.yml.example)
- fix login on firefox when fields are autofilled by the browser
- fix scrolling of subscriptions list on mobile
- user is now logged in after registration
- fix link to documentation on home page and about page
- fields autocomplete is disabled on the profile page
- users are able to delete their account again
- chinese and malaysian translation files are now correctly loaded
- software version in user-agent when fetching feeds is no longer hardcoded
- admin settings page is now read only, settings are configured in config.yml
- added link to metrics on the admin settings page
- Rome (rss library) upgrade to 1.5.0
## [2.0.1]
- the redis pool no longer throws an exception when it is unable to aquire a new connection
## [2.0.0]
- The backend has been completely rewritten using Dropwizard instead of TomEE, resulting in a lot less memory
consumption and better overall performances.
See the README on how to build CommaFeed from now on.
- CommaFeed should no longer fetch the same feed multiple times in a row
- Users can use their username or email to log in

12
Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM eclipse-temurin:17-jre
EXPOSE 8082
RUN mkdir -p /commafeed/data
VOLUME /commafeed/data
ENV CF_SESSION_PATH=/commafeed/data/sessions
COPY commafeed-server/config.yml.example config.yml
COPY commafeed-server/target/commafeed.jar .
CMD ["java", "-Djava.net.preferIPv4Stack=true", "-jar", "commafeed.jar", "server", "config.yml"]

View File

@@ -1,9 +1,20 @@
# CommaFeed # CommaFeed
Sources for [CommaFeed.com](http://www.commafeed.com/). Google Reader inspired self-hosted RSS reader, based on Dropwizard and React/TypeScript.
Google Reader inspired self-hosted RSS reader, based on Dropwizard and AngularJS. ![preview](https://user-images.githubusercontent.com/1256795/184886828-1973f148-58a9-4c6d-9587-ee5e5d3cc2cb.png)
CommaFeed is now considered feature-complete and is in maintenance mode.
## Features
- 4 different layouts
- Dark theme
- Fully responsive
- Keyboard shortcuts for almost everything
- Support for right-to-left feeds
- Translated in 25+ languages
- Supports thousands of users and millions of feeds
- OPML import/export
- REST API
## Related open-source projects ## Related open-source projects
@@ -16,84 +27,53 @@ Browser extensions:
## Deployment on your own server ## Deployment on your own server
### The very short version (download precompiled package) ### Docker
Docker images are built automatically and are available at https://hub.docker.com/r/athou/commafeed
### Download precompiled package
mkdir commafeed && cd commafeed mkdir commafeed && cd commafeed
wget https://github.com/Athou/commafeed/releases/download/3.0.0/commafeed.jar wget https://github.com/Athou/commafeed/releases/latest/download/commafeed.jar
wget https://raw.githubusercontent.com/Athou/commafeed/3.0.0/commafeed-server/config.yml.example -O config.yml wget https://github.com/Athou/commafeed/releases/latest/download/config.yml.example -O config.yml
vi config.yml
java -Djava.net.preferIPv4Stack=true -jar commafeed.jar server config.yml java -Djava.net.preferIPv4Stack=true -jar commafeed.jar server config.yml
### The short version (build from sources) The server will listen on http://localhost:8082. The default
user is `admin` and the default password is `admin`.
### Build from sources
git clone https://github.com/Athou/commafeed.git git clone https://github.com/Athou/commafeed.git
cd commafeed cd commafeed
./mvnw clean package ./mvnw clean package
cp commafeed-server/config.yml.example config.yml cp commafeed-server/config.yml.example config.yml
vi config.yml
java -Djava.net.preferIPv4Stack=true -jar commafeed-server/target/commafeed.jar server config.yml java -Djava.net.preferIPv4Stack=true -jar commafeed-server/target/commafeed.jar server config.yml
### The long version (same as the short version, but more detailed) The server will listen on http://localhost:8082. The default
user is `admin` and the default password is `admin`.
CommaFeed 2.0 has been rewritten to use Dropwizard and gulp instead of using tomee and wro4j. The latest version of the 1.x branch is available [here](https://github.com/Athou/commafeed/tree/1.x). ## Translation
For storage, you can either use an embedded file-based H2 database or an external MySQL, PostgreSQL or SQLServer database. Files for internationalization are
You also need the Java 1.8+ JDK in order to build the application. located [here](https://github.com/Athou/commafeed/tree/master/commafeed-client/src/locales).
To install the required packages to build CommaFeed on Ubuntu, issue the following commands
# if this commands works and returns a version >= 1.8.0 you're good to go and you can skip JDK installation
javac -version
# if openjdk-8-jdk is not available on your ubuntu version (14.04 LTS), add the following repo first
sudo add-apt-repository ppa:openjdk-r/ppa
sudo apt-get update
sudo apt-get install g++ build-essential openjdk-8-jdk
# Make sure java8 is the selected java version
sudo update-alternatives --config java
sudo update-alternatives --config javac
Clone this repository. If you don't have git you can download the sources as a zip file from [here](https://github.com/Athou/commafeed/archive/master.zip)
git clone https://github.com/Athou/commafeed.git
cd commafeed
Now build the application
./mvnw clean package
Copy `commafeed-server/config.yml.example` to `./config.yml` then edit the file to your liking.
Issue the following command to run the app, the server will listen by default on `http://localhost:8082`. The default user is `admin` and the default password is `admin`.
java -Djava.net.preferIPv4Stack=true -jar commafeed-server/target/commafeed.jar server config.yml
You can use a proxy http server such as nginx or apache.
## Translate CommaFeed into your language
Files for internationalization are located [here](https://github.com/Athou/commafeed/tree/master/commafeed-client/src/locales).
To add a new language: To add a new language:
- edit `commafeed-client/src/i18n.ts` - edit `commafeed-client/src/i18n.ts`
- add the new locale to the `locales` array. - add the new locale to the `locales` array.
- import the dayjs locale - import the dayjs locale
- edit `commafeed-client/.linguirc` and add the new locale to the `locales` array. - edit `commafeed-client/.linguirc` and add the new locale to the `locales` array.
- run `npm run i18n` and add translations to the newly created `commafeed-client/src/locales/[locale]/messages.po` file - run `npm run i18n` and add translations to the newly created `commafeed-client/src/locales/[locale]/messages.po` file
The name of the locale should be the two-letters [ISO-639-1 language code](http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). The name of the locale should be the
two-letters [ISO-639-1 language code](http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
## Local development ## Local development
- `git clone https://github.com/Athou/CommaFeed`
### Backend ### Backend
- Open `commafeed-server` in your preferred Java IDE. - Open `commafeed-server` in your preferred Java IDE.
- CommaFeed uses Lombok, you need the Lombok plugin for your IDE. - CommaFeed uses Lombok, you need the Lombok plugin for your IDE.
- If using Eclipse, Go to Window → Preferences → Maven → Annotation Processing and check "Automatically configure JDT APT"
- Start `CommaFeedApplication.java` in debug mode with `server config.dev.yml` as arguments - Start `CommaFeedApplication.java` in debug mode with `server config.dev.yml` as arguments
### Frontend ### Frontend
@@ -101,11 +81,13 @@ The name of the locale should be the two-letters [ISO-639-1 language code](http:
- Open `commafeed-client` in your preferred JavaScript IDE. - Open `commafeed-client` in your preferred JavaScript IDE.
- run `npm install` - run `npm install`
- run `npm run dev` - run `npm run dev`
- the frontend server is now running at http://localhost:8082 and is proxying REST requests to the backend running on port 8083
The frontend server is now running at http://localhost:8082 and is proxying REST requests to the backend running on
port 8083
## Copyright and license ## Copyright and license
Copyright 2013-2022 CommaFeed. Copyright 2013-2023 CommaFeed.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this work except in compliance with the License. you may not use this work except in compliance with the License.

View File

@@ -70,8 +70,6 @@
"groups": [ "groups": [
"useLocation", "useLocation",
"useParams", "useParams",
"useStyles",
"useMantineTheme",
"useState", "useState",
"useAppSelector", "useAppSelector",
"useAppDispatch", "useAppDispatch",

File diff suppressed because it is too large Load Diff

View File

@@ -1,76 +1,83 @@
{ {
"name": "commafeed-client", "name": "commafeed-client",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite --host",
"build": "npm run i18n:compile && tsc && vite build", "dev:typescript": "tsc --watch",
"preview": "vite preview", "build": "npm run i18n:compile && tsc && vite build",
"test": "vitest", "preview": "vite preview",
"test:ci": "vitest run", "test": "vitest",
"eslint": "eslint --ext=.js,.jsx,.ts,.tsx src", "test:ci": "vitest run",
"i18n": "npm run i18n:extract && npm run i18n:compile", "eslint": "eslint --ext=.js,.jsx,.ts,.tsx src",
"i18n:extract": "lingui extract --clean", "i18n": "npm run i18n:extract && npm run i18n:compile",
"i18n:compile": "lingui compile --typescript", "i18n:extract": "lingui extract --clean",
"postinstall": "npm run i18n:compile" "i18n:compile": "lingui compile --typescript",
}, "postinstall": "npm run i18n:compile"
"dependencies": { },
"@emotion/react": "^11.10.5", "dependencies": {
"@fontsource/open-sans": "^4.5.13", "@emotion/react": "^11.10.5",
"@lingui/core": "^3.14.0", "@fontsource/open-sans": "^4.5.14",
"@lingui/macro": "^3.14.0", "@lingui/core": "^3.17.0",
"@lingui/react": "^3.14.0", "@lingui/macro": "^3.17.0",
"@mantine/core": "^5.6.3", "@lingui/react": "^3.17.0",
"@mantine/form": "^5.6.3", "@mantine/core": "^5.10.3",
"@mantine/hooks": "^5.6.3", "@mantine/form": "^5.10.3",
"@mantine/modals": "^5.6.3", "@mantine/hooks": "^5.10.3",
"@mantine/notifications": "^5.6.3", "@mantine/modals": "^5.10.3",
"@mantine/spotlight": "^5.6.3", "@mantine/notifications": "^5.10.3",
"@reduxjs/toolkit": "^1.8.6", "@mantine/spotlight": "^5.10.3",
"axios": "^1.1.3", "@mantine/styles": "^5.10.3",
"dayjs": "^1.11.6", "@reduxjs/toolkit": "^1.9.2",
"interweave": "^13.0.0", "axios": "^1.3.2",
"lodash": "^4.17.21", "dayjs": "^1.11.7",
"make-plural": "^7.1.0", "interweave": "^13.0.0",
"mousetrap": "^1.6.5", "lodash": "^4.17.21",
"react": "^18.2.0", "make-plural": "^7.2.0",
"react-async-hook": "^4.0.0", "mousetrap": "^1.6.5",
"react-dom": "^18.2.0", "react": "^18.2.0",
"react-icons": "^4.6.0", "react-async-hook": "^4.0.0",
"react-infinite-scroller": "^1.2.6", "react-contexify": "^6.0.0",
"react-redux": "^8.0.4", "react-dom": "^18.2.0",
"react-router-dom": "^6.4.2", "react-ga4": "^2.1.0",
"swagger-ui-react": "^4.15.2", "react-icons": "^4.7.1",
"tinycon": "^0.6.8" "react-infinite-scroller": "^1.2.6",
}, "react-redux": "^8.0.5",
"devDependencies": { "react-router-dom": "^6.8.0",
"@lingui/cli": "^3.14.0", "react-swipeable": "^7.0.0",
"@types/eslint": "^8.4.8", "swagger-ui-react": "^4.15.5",
"@types/lodash": "^4.14.186", "tinycon": "^0.6.8",
"@types/mousetrap": "^1.6.10", "use-local-storage": "^3.0.0",
"@types/react": "^18.0.24", "websocket-heartbeat-js": "^1.1.1"
"@types/react-dom": "^18.0.8", },
"@types/react-infinite-scroller": "^1.2.3", "devDependencies": {
"@types/swagger-ui-react": "^4.11.0", "@lingui/cli": "^3.17.0",
"@types/tinycon": "^0.6.3", "@types/eslint": "^8.21.0",
"@typescript-eslint/eslint-plugin": "^5.41.0", "@types/lodash": "^4.14.191",
"@typescript-eslint/parser": "^5.41.0", "@types/mousetrap": "^1.6.11",
"@vitejs/plugin-react": "^2.2.0", "@types/react": "^18.0.27",
"eslint": "^8.26.0", "@types/react-dom": "^18.0.10",
"eslint-config-airbnb": "^19.0.4", "@types/react-infinite-scroller": "^1.2.3",
"eslint-config-airbnb-typescript": "^17.0.0", "@types/swagger-ui-react": "^4.11.0",
"eslint-config-prettier": "^8.5.0", "@types/tinycon": "^0.6.3",
"eslint-config-react-app": "^7.0.1", "@typescript-eslint/eslint-plugin": "^5.50.0",
"eslint-plugin-hooks": "^0.4.3", "@typescript-eslint/parser": "^5.50.0",
"eslint-plugin-prettier": "^4.2.1", "@vitejs/plugin-react": "^3.1.0",
"prettier": "^2.7.1", "eslint": "^8.33.0",
"rollup-plugin-visualizer": "^5.8.3", "eslint-config-airbnb": "^19.0.4",
"typescript": "^4.8.4", "eslint-config-airbnb-typescript": "^17.0.0",
"vite": "^3.2.0", "eslint-config-prettier": "^8.6.0",
"vite-plugin-eslint": "^1.8.1", "eslint-config-react-app": "^7.0.1",
"vite-tsconfig-paths": "^3.5.2", "eslint-plugin-hooks": "^0.4.3",
"vitest": "^0.24.3", "eslint-plugin-prettier": "^4.2.1",
"vitest-mock-extended": "^1.0.3" "prettier": "^2.8.3",
} "rollup-plugin-visualizer": "^5.9.0",
"typescript": "^4.9.5",
"vite": "^4.1.1",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^4.0.5",
"vitest": "^0.28.4",
"vitest-mock-extended": "^1.0.9"
}
} }

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<artifactId>commafeed-client</artifactId> <artifactId>commafeed-client</artifactId>
<name>CommaFeed Client</name> <name>CommaFeed Client</name>

View File

@@ -1,7 +1,7 @@
import { i18n } from "@lingui/core" import { i18n } from "@lingui/core"
import { I18nProvider } from "@lingui/react" import { I18nProvider } from "@lingui/react"
import { ColorScheme, ColorSchemeProvider, MantineProvider } from "@mantine/core" import { ColorScheme, ColorSchemeProvider, MantineProvider } from "@mantine/core"
import { useColorScheme, useLocalStorage } from "@mantine/hooks" import { useColorScheme } from "@mantine/hooks"
import { ModalsProvider } from "@mantine/modals" import { ModalsProvider } from "@mantine/modals"
import { NotificationsProvider } from "@mantine/notifications" import { NotificationsProvider } from "@mantine/notifications"
import { Constants } from "app/constants" import { Constants } from "app/constants"
@@ -27,16 +27,15 @@ import { LoginPage } from "pages/auth/LoginPage"
import { PasswordRecoveryPage } from "pages/auth/PasswordRecoveryPage" import { PasswordRecoveryPage } from "pages/auth/PasswordRecoveryPage"
import { RegistrationPage } from "pages/auth/RegistrationPage" import { RegistrationPage } from "pages/auth/RegistrationPage"
import React, { useEffect } from "react" import React, { useEffect } from "react"
import { HashRouter, Navigate, Route, Routes, useNavigate } from "react-router-dom" import ReactGA from "react-ga4"
import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"
import Tinycon from "tinycon" import Tinycon from "tinycon"
import useLocalStorage from "use-local-storage"
import { WelcomePage } from "./pages/WelcomePage"
function Providers(props: { children: React.ReactNode }) { function Providers(props: { children: React.ReactNode }) {
const preferredColorScheme = useColorScheme() const preferredColorScheme = useColorScheme()
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>({ const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>("color-scheme", preferredColorScheme)
key: "color-scheme",
defaultValue: preferredColorScheme,
getInitialValueInEffect: true,
})
const toggleColorScheme = (value?: ColorScheme) => setColorScheme(value || (colorScheme === "dark" ? "light" : "dark")) const toggleColorScheme = (value?: ColorScheme) => setColorScheme(value || (colorScheme === "dark" ? "light" : "dark"))
return ( return (
@@ -52,7 +51,7 @@ function Providers(props: { children: React.ReactNode }) {
}} }}
> >
<ModalsProvider> <ModalsProvider>
<NotificationsProvider position="top-center" zIndex={9999}> <NotificationsProvider position="bottom-right" zIndex={9999}>
<ErrorBoundary>{props.children}</ErrorBoundary> <ErrorBoundary>{props.children}</ErrorBoundary>
</NotificationsProvider> </NotificationsProvider>
</ModalsProvider> </ModalsProvider>
@@ -69,6 +68,7 @@ function AppRoutes() {
return ( return (
<Routes> <Routes>
<Route path="/" element={<Navigate to={`/app/category/${Constants.categories.all.id}`} replace />} /> <Route path="/" element={<Navigate to={`/app/category/${Constants.categories.all.id}`} replace />} />
<Route path="welcome" element={<WelcomePage />} />
<Route path="login" element={<LoginPage />} /> <Route path="login" element={<LoginPage />} />
<Route path="register" element={<RegistrationPage />} /> <Route path="register" element={<RegistrationPage />} />
<Route path="passwordRecovery" element={<PasswordRecoveryPage />} /> <Route path="passwordRecovery" element={<PasswordRecoveryPage />} />
@@ -114,6 +114,21 @@ function RedirectHandler() {
return null return null
} }
function GoogleAnalyticsHandler() {
const location = useLocation()
const googleAnalyticsCode = useAppSelector(state => state.server.serverInfos?.googleAnalyticsCode)
useEffect(() => {
if (googleAnalyticsCode) ReactGA.initialize(googleAnalyticsCode)
}, [googleAnalyticsCode])
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: location.pathname })
}, [location])
return null
}
function FaviconHandler() { function FaviconHandler() {
const root = useAppSelector(state => state.tree.rootCategory) const root = useAppSelector(state => state.tree.rootCategory)
useEffect(() => { useEffect(() => {
@@ -138,6 +153,7 @@ export function App() {
<> <>
<FaviconHandler /> <FaviconHandler />
<HashRouter> <HashRouter>
<GoogleAnalyticsHandler />
<RedirectHandler /> <RedirectHandler />
<AppRoutes /> <AppRoutes />
</HashRouter> </HashRouter>

View File

@@ -30,7 +30,9 @@ const axiosInstance = axios.create({ baseURL: "./rest", withCredentials: true })
axiosInstance.interceptors.response.use( axiosInstance.interceptors.response.use(
response => response, response => response,
error => { error => {
if (error.response.status === 401) window.location.hash = "/login" if (error.response.status === 401 && error.response.data === "Credentials are required to access this resource.") {
window.location.hash = "/welcome"
}
throw error throw error
} }
) )
@@ -58,6 +60,7 @@ export const client = {
getEntries: (req: GetEntriesPaginatedRequest) => axiosInstance.get<Entries>("feed/entries", { params: req }), getEntries: (req: GetEntriesPaginatedRequest) => axiosInstance.get<Entries>("feed/entries", { params: req }),
markEntries: (req: MarkRequest) => axiosInstance.post("feed/mark", req), markEntries: (req: MarkRequest) => axiosInstance.post("feed/mark", req),
fetchFeed: (req: FeedInfoRequest) => axiosInstance.post<FeedInfo>("feed/fetch", req), fetchFeed: (req: FeedInfoRequest) => axiosInstance.post<FeedInfo>("feed/fetch", req),
refreshAll: () => axiosInstance.get("feed/refreshAll"),
subscribe: (req: SubscribeRequest) => axiosInstance.post<number>("feed/subscribe", req), subscribe: (req: SubscribeRequest) => axiosInstance.post<number>("feed/subscribe", req),
unsubscribe: (req: IDRequest) => axiosInstance.post("feed/unsubscribe", req), unsubscribe: (req: IDRequest) => axiosInstance.post("feed/unsubscribe", req),
importOpml: (req: File) => { importOpml: (req: File) => {

View File

@@ -54,13 +54,13 @@ const sharing: {
label: "Twitter", label: "Twitter",
icon: SiTwitter, icon: SiTwitter,
color: "#1D9BF0", color: "#1D9BF0",
url: (url, desc) => `http://twitter.com/share?text=${desc}&url=${url}`, url: (url, desc) => `https://twitter.com/share?text=${desc}&url=${url}`,
}, },
tumblr: { tumblr: {
label: "Tumblr", label: "Tumblr",
icon: SiTumblr, icon: SiTumblr,
color: "#375672", color: "#375672",
url: (url, desc) => `http://www.tumblr.com/share/link?url=${url}&name=${desc}`, url: (url, desc) => `https://www.tumblr.com/share/link?url=${url}&name=${desc}`,
}, },
pocket: { pocket: {
label: "Pocket", label: "Pocket",
@@ -97,4 +97,5 @@ export const Constants = {
mainScrollAreaId: "main-scroll-area-id", mainScrollAreaId: "main-scroll-area-id",
entryId: (entry: Entry) => `entry-id-${entry.id}`, entryId: (entry: Entry) => `entry-id-${entry.id}`,
}, },
bitcoinWalletAddress: "1dymfUxqCWpyD7a6rQSqNy4rLVDBsAr5e",
} }

View File

@@ -128,6 +128,7 @@ export const markAllEntries = createAsyncThunk<void, { sourceType: EntrySourceTy
async (arg, thunkApi) => { async (arg, thunkApi) => {
const endpoint = arg.sourceType === "category" ? client.category.markEntries : client.feed.markEntries const endpoint = arg.sourceType === "category" ? client.category.markEntries : client.feed.markEntries
await endpoint(arg.req) await endpoint(arg.req)
thunkApi.dispatch(reloadEntries())
thunkApi.dispatch(reloadTree()) thunkApi.dispatch(reloadTree())
} }
) )

View File

@@ -3,7 +3,7 @@ import { showNotification } from "@mantine/notifications"
import { createAsyncThunk, createSlice, isAnyOf } from "@reduxjs/toolkit" import { createAsyncThunk, createSlice, isAnyOf } from "@reduxjs/toolkit"
import { client } from "app/client" import { client } from "app/client"
import { RootState } from "app/store" import { RootState } from "app/store"
import { ReadingMode, ReadingOrder, Settings, SharingSettings, UserModel, ViewMode } from "app/types" import { ReadingMode, ReadingOrder, Settings, SharingSettings, UserModel } from "app/types"
// eslint-disable-next-line import/no-cycle // eslint-disable-next-line import/no-cycle
import { reloadEntries } from "./entries" import { reloadEntries } from "./entries"
@@ -36,46 +36,67 @@ export const changeReadingOrder = createAsyncThunk<void, ReadingOrder, { state:
thunkApi.dispatch(reloadEntries()) thunkApi.dispatch(reloadEntries())
} }
) )
export const changeViewMode = createAsyncThunk<void, ViewMode, { state: RootState }>("settings/viewMode", (viewMode, thunkApi) => { export const changeLanguage = createAsyncThunk<
const { settings } = thunkApi.getState().user void,
if (!settings) return string,
client.user.saveSettings({ ...settings, viewMode }) {
thunkApi.dispatch(reloadEntries()) state: RootState
}) }
export const changeLanguage = createAsyncThunk<void, string, { state: RootState }>("settings/language", (language, thunkApi) => { >("settings/language", (language, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, language }) client.user.saveSettings({ ...settings, language })
}) })
export const changeScrollSpeed = createAsyncThunk<void, boolean, { state: RootState }>("settings/scrollSpeed", (speed, thunkApi) => { export const changeScrollSpeed = createAsyncThunk<
void,
boolean,
{
state: RootState
}
>("settings/scrollSpeed", (speed, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 }) client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 })
}) })
export const changeShowRead = createAsyncThunk<void, boolean, { state: RootState }>("settings/showRead", (showRead, thunkApi) => { export const changeShowRead = createAsyncThunk<
void,
boolean,
{
state: RootState
}
>("settings/showRead", (showRead, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, showRead }) client.user.saveSettings({ ...settings, showRead })
}) })
export const changeScrollMarks = createAsyncThunk<void, boolean, { state: RootState }>("settings/scrollMarks", (scrollMarks, thunkApi) => { export const changeScrollMarks = createAsyncThunk<
void,
boolean,
{
state: RootState
}
>("settings/scrollMarks", (scrollMarks, thunkApi) => {
const { settings } = thunkApi.getState().user const { settings } = thunkApi.getState().user
if (!settings) return if (!settings) return
client.user.saveSettings({ ...settings, scrollMarks }) client.user.saveSettings({ ...settings, scrollMarks })
}) })
export const changeSharingSetting = createAsyncThunk<void, { site: keyof SharingSettings; value: boolean }, { state: RootState }>( export const changeSharingSetting = createAsyncThunk<
"settings/sharingSetting", void,
(sharingSetting, thunkApi) => { { site: keyof SharingSettings; value: boolean },
const { settings } = thunkApi.getState().user {
if (!settings) return state: RootState
client.user.saveSettings({
...settings,
sharingSettings: {
...settings.sharingSettings,
[sharingSetting.site]: sharingSetting.value,
},
})
} }
) >("settings/sharingSetting", (sharingSetting, thunkApi) => {
const { settings } = thunkApi.getState().user
if (!settings) return
client.user.saveSettings({
...settings,
sharingSettings: {
...settings.sharingSettings,
[sharingSetting.site]: sharingSetting.value,
},
})
})
export const userSlice = createSlice({ export const userSlice = createSlice({
name: "user", name: "user",
@@ -99,10 +120,6 @@ export const userSlice = createSlice({
if (!state.settings) return if (!state.settings) return
state.settings.readingOrder = action.meta.arg state.settings.readingOrder = action.meta.arg
}) })
builder.addCase(changeViewMode.pending, (state, action) => {
if (!state.settings) return
state.settings.viewMode = action.meta.arg
})
builder.addCase(changeLanguage.pending, (state, action) => { builder.addCase(changeLanguage.pending, (state, action) => {
if (!state.settings) return if (!state.settings) return
state.settings.language = action.meta.arg state.settings.language = action.meta.arg

View File

@@ -38,6 +38,7 @@ export interface ApplicationSettings {
export interface Category { export interface Category {
id: string id: string
parentId?: string parentId?: string
parentName?: string
name: string name: string
children: Category[] children: Category[]
feeds: Subscription[] feeds: Subscription[]
@@ -227,7 +228,6 @@ export interface Settings {
language: string language: string
readingMode: ReadingMode readingMode: ReadingMode
readingOrder: ReadingOrder readingOrder: ReadingOrder
viewMode: ViewMode
showRead: boolean showRead: boolean
scrollMarks: boolean scrollMarks: boolean
theme?: string theme?: string
@@ -305,4 +305,4 @@ export type ReadingMode = "all" | "unread"
export type ReadingOrder = "asc" | "desc" export type ReadingOrder = "asc" | "desc"
export type ViewMode = "title" | "cozy" | "expanded" export type ViewMode = "title" | "cozy" | "detailed" | "expanded"

View File

@@ -51,3 +51,16 @@ export const scrollToWithCallback = ({
element.scrollTo(options) element.scrollTo(options)
} }
export const openLinkInBackgroundTab = (url: string) => {
// simulate ctrl+click to open tab in background
const a = document.createElement("a")
a.href = url
a.rel = "noreferrer"
a.dispatchEvent(
new MouseEvent("click", {
ctrlKey: true,
metaKey: true,
})
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@@ -1,12 +1,15 @@
import { ActionIcon, Button, useMantineTheme } from "@mantine/core" import { ActionIcon, Button, ButtonVariant, useMantineTheme } from "@mantine/core"
import { ActionIconVariant } from "@mantine/core/lib/ActionIcon/ActionIcon.styles"
import { useMediaQuery } from "@mantine/hooks" import { useMediaQuery } from "@mantine/hooks"
import { forwardRef } from "react" import { forwardRef, MouseEventHandler, ReactNode } from "react"
interface ActionButtonProps { interface ActionButtonProps {
className?: string className?: string
icon?: React.ReactNode icon?: ReactNode
label?: string label?: string
onClick?: React.MouseEventHandler onClick?: MouseEventHandler
variant?: ActionIconVariant & ButtonVariant
showLabelOnMobile?: boolean
} }
/** /**
@@ -14,13 +17,15 @@ interface ActionButtonProps {
*/ */
export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((props: ActionButtonProps, ref) => { export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((props: ActionButtonProps, ref) => {
const theme = useMantineTheme() const theme = useMantineTheme()
const variant = props.variant ?? "subtle"
const mobile = !useMediaQuery(`(min-width: ${theme.breakpoints.lg}px)`) const mobile = !useMediaQuery(`(min-width: ${theme.breakpoints.lg}px)`)
return mobile ? ( const iconOnly = !props.showLabelOnMobile && (mobile || !props.label)
<ActionIcon ref={ref} color={theme.primaryColor} variant="subtle" className={props.className} onClick={props.onClick}> return iconOnly ? (
<ActionIcon ref={ref} color={theme.primaryColor} variant={variant} className={props.className} onClick={props.onClick}>
{props.icon} {props.icon}
</ActionIcon> </ActionIcon>
) : ( ) : (
<Button ref={ref} variant="subtle" size="xs" className={props.className} leftIcon={props.icon} onClick={props.onClick}> <Button ref={ref} variant={variant} size="xs" className={props.className} leftIcon={props.icon} onClick={props.onClick}>
{props.label} {props.label}
</Button> </Button>
) )

View File

@@ -3,13 +3,16 @@ import { useState } from "react"
import { TbPhoto } from "react-icons/tb" import { TbPhoto } from "react-icons/tb"
interface ImageWithPlaceholderWhileLoadingProps { interface ImageWithPlaceholderWhileLoadingProps {
src?: string src: string
alt?: string alt: string
title?: string title?: string
width?: number width?: number
height?: number | "auto" height?: number | "auto"
placeholderWidth?: number placeholderWidth?: number
placeholderHeight?: number placeholderHeight?: number
placeholderBackgroundColor?: string
placeholderIconSize?: number
placeholderIconColor?: string
} }
const useStyles = createStyles((theme, props: ImageWithPlaceholderWhileLoadingProps) => ({ const useStyles = createStyles((theme, props: ImageWithPlaceholderWhileLoadingProps) => ({
@@ -17,8 +20,8 @@ const useStyles = createStyles((theme, props: ImageWithPlaceholderWhileLoadingPr
width: props.placeholderWidth ?? 400, width: props.placeholderWidth ?? 400,
height: props.placeholderHeight ?? 600, height: props.placeholderHeight ?? 600,
maxWidth: "100%", maxWidth: "100%",
color: theme.fn.variant({ color: theme.primaryColor, variant: "subtle" }).color, color: props.placeholderIconColor ?? theme.fn.variant({ color: theme.primaryColor, variant: "subtle" }).color,
backgroundColor: theme.colorScheme === "dark" ? theme.colors.dark[5] : theme.colors.gray[1], backgroundColor: props.placeholderBackgroundColor ?? (theme.colorScheme === "dark" ? theme.colors.dark[5] : theme.colors.gray[1]),
}, },
})) }))
@@ -32,7 +35,7 @@ export function ImageWithPlaceholderWhileLoading(props: ImageWithPlaceholderWhil
<Box> <Box>
<Center className={classes.placeholder}> <Center className={classes.placeholder}>
<div> <div>
<TbPhoto size={48} /> <TbPhoto size={props.placeholderIconSize ?? 48} />
</div> </div>
</Center> </Center>
</Box> </Box>

View File

@@ -95,6 +95,10 @@ export function KeyboardShortcutsHelp() {
</td> </td>
<td> <td>
<Kbd>B</Kbd> <Kbd>B</Kbd>
<span>, </span>
<Kbd>
<Trans>Middle click</Trans>
</Kbd>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -103,6 +107,8 @@ export function KeyboardShortcutsHelp() {
</td> </td>
<td> <td>
<Kbd>M</Kbd> <Kbd>M</Kbd>
<span>, </span>
<Trans>Swipe header to the right</Trans>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -143,6 +149,26 @@ export function KeyboardShortcutsHelp() {
<Kbd>U</Kbd> <Kbd>U</Kbd>
</td> </td>
</tr> </tr>
<tr>
<td>
<Trans>Show entry menu (desktop)</Trans>
</td>
<td>
<Kbd>
<Trans>Right click</Trans>
</Kbd>
</td>
</tr>
<tr>
<td>
<Trans>Show entry menu (mobile)</Trans>
</td>
<td>
<Kbd>
<Trans>Long press</Trans>
</Kbd>
</td>
</tr>
<tr> <tr>
<td> <td>
<Trans>Show keyboard shortcut help</Trans> <Trans>Show keyboard shortcut help</Trans>

View File

@@ -29,7 +29,9 @@ const transform: TransformCallback = node => {
if (node.tagName === "IMG") { if (node.tagName === "IMG") {
// show placeholders for loading img tags, this allows the entry to have its final height immediately // show placeholders for loading img tags, this allows the entry to have its final height immediately
const src = node.getAttribute("src") ?? undefined const src = node.getAttribute("src") ?? undefined
const alt = node.getAttribute("alt") ?? undefined if (!src) return undefined
const alt = node.getAttribute("alt") ?? "image"
const title = node.getAttribute("title") ?? undefined const title = node.getAttribute("title") ?? undefined
const nodeWidth = node.getAttribute("width") const nodeWidth = node.getAttribute("width")
const nodeHeight = node.getAttribute("height") const nodeHeight = node.getAttribute("height")
@@ -40,6 +42,7 @@ const transform: TransformCallback = node => {
height, height,
maxWidth: Constants.layout.entryMaxWidth, maxWidth: Constants.layout.entryMaxWidth,
}) })
return ( return (
<ImageWithPlaceholderWhileLoading <ImageWithPlaceholderWhileLoading
src={src} src={src}

View File

@@ -1,7 +1,7 @@
import { TypographyStylesProvider } from "@mantine/core" import { TypographyStylesProvider } from "@mantine/core"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading" import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
export function Enclosure(props: { enclosureType?: string; enclosureUrl?: string }) { export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) {
const hasVideo = props.enclosureType && props.enclosureType.indexOf("video") === 0 const hasVideo = props.enclosureType && props.enclosureType.indexOf("video") === 0
const hasAudio = props.enclosureType && props.enclosureType.indexOf("audio") === 0 const hasAudio = props.enclosureType && props.enclosureType.indexOf("audio") === 0
const hasImage = props.enclosureType && props.enclosureType.indexOf("image") === 0 const hasImage = props.enclosureType && props.enclosureType.indexOf("image") === 0

View File

@@ -13,6 +13,7 @@ import {
} from "app/slices/entries" } from "app/slices/entries"
import { redirectToRootCategory } from "app/slices/redirect" import { redirectToRootCategory } from "app/slices/redirect"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { openLinkInBackgroundTab } from "app/utils"
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp" import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { useMousetrap } from "hooks/useMousetrap" import { useMousetrap } from "hooks/useMousetrap"
@@ -20,6 +21,7 @@ import throttle from "lodash/throttle"
import { useEffect } from "react" import { useEffect } from "react"
import InfiniteScroll from "react-infinite-scroller" import InfiniteScroll from "react-infinite-scroller"
import { FeedEntry } from "./FeedEntry" import { FeedEntry } from "./FeedEntry"
import { useViewMode } from "../../hooks/useViewMode"
export function FeedEntries() { export function FeedEntries() {
const source = useAppSelector(state => state.entries.source) const source = useAppSelector(state => state.entries.source)
@@ -27,7 +29,7 @@ export function FeedEntries() {
const entriesTimestamp = useAppSelector(state => state.entries.timestamp) const entriesTimestamp = useAppSelector(state => state.entries.timestamp)
const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId) const selectedEntryId = useAppSelector(state => state.entries.selectedEntryId)
const hasMore = useAppSelector(state => state.entries.hasMore) const hasMore = useAppSelector(state => state.entries.hasMore)
const viewMode = useAppSelector(state => state.user.settings?.viewMode) const { viewMode } = useViewMode()
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks) const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
const scrollingToEntry = useAppSelector(state => state.entries.scrollingToEntry) const scrollingToEntry = useAppSelector(state => state.entries.scrollingToEntry)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
@@ -211,15 +213,7 @@ export function FeedEntries() {
useMousetrap("b", () => { useMousetrap("b", () => {
// simulate ctrl+click to open tab in background // simulate ctrl+click to open tab in background
if (!selectedEntry) return if (!selectedEntry) return
const a = document.createElement("a") openLinkInBackgroundTab(selectedEntry.url)
a.href = selectedEntry.url
a.rel = "noreferrer"
a.dispatchEvent(
new MouseEvent("click", {
ctrlKey: true,
metaKey: true,
})
)
}) })
useMousetrap("m", () => { useMousetrap("m", () => {
// toggle read status // toggle read status

View File

@@ -1,10 +1,15 @@
import { Anchor, Box, createStyles, Divider, Paper } from "@mantine/core" import { Anchor, Box, createStyles, Divider, Paper } from "@mantine/core"
import { MantineNumberSize } from "@mantine/styles"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { useAppSelector } from "app/store" import { markEntry } from "app/slices/entries"
import { Entry } from "app/types" import { useAppDispatch } from "app/store"
import { Entry, ViewMode } from "app/types"
import React from "react" import React from "react"
import { useSwipeable } from "react-swipeable"
import { useViewMode } from "../../hooks/useViewMode"
import { FeedEntryBody } from "./FeedEntryBody" import { FeedEntryBody } from "./FeedEntryBody"
import { FeedEntryCompactHeader } from "./FeedEntryCompactHeader" import { FeedEntryCompactHeader } from "./FeedEntryCompactHeader"
import { FeedEntryContextMenu, useFeedEntryContextMenu } from "./FeedEntryContextMenu"
import { FeedEntryFooter } from "./FeedEntryFooter" import { FeedEntryFooter } from "./FeedEntryFooter"
import { FeedEntryHeader } from "./FeedEntryHeader" import { FeedEntryHeader } from "./FeedEntryHeader"
@@ -15,19 +20,37 @@ interface FeedEntryProps {
onHeaderClick: (e: React.MouseEvent) => void onHeaderClick: (e: React.MouseEvent) => void
} }
const useStyles = createStyles((theme, props: FeedEntryProps) => { const useStyles = createStyles((theme, props: FeedEntryProps & { viewMode?: ViewMode }) => {
let backgroundColor let backgroundColor
if (theme.colorScheme === "dark") backgroundColor = props.entry.read ? "inherit" : theme.colors.dark[5] if (theme.colorScheme === "dark") backgroundColor = props.entry.read ? "inherit" : theme.colors.dark[5]
else backgroundColor = props.entry.read && !props.expanded ? theme.colors.gray[0] : "inherit" else backgroundColor = props.entry.read && !props.expanded ? theme.colors.gray[0] : "inherit"
let marginY = theme.spacing.xs
if (props.viewMode === "title") marginY = 2
else if (props.viewMode === "cozy") marginY = 6
let mobileMarginY = 6
if (props.viewMode === "title") mobileMarginY = 2
else if (props.viewMode === "cozy") mobileMarginY = 4
let backgroundHoverColor = backgroundColor
if (!props.expanded && !props.entry.read) {
backgroundHoverColor = theme.colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[1]
}
const styles = { const styles = {
paper: { paper: {
backgroundColor, backgroundColor,
marginTop: theme.spacing.xs, marginTop: marginY,
marginBottom: theme.spacing.xs, marginBottom: marginY,
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
marginTop: "6px", marginTop: mobileMarginY,
marginBottom: "6px", marginBottom: mobileMarginY,
},
"@media (hover: hover)": {
"&:hover": {
backgroundColor: backgroundHoverColor,
},
}, },
}, },
body: { body: {
@@ -43,12 +66,31 @@ const useStyles = createStyles((theme, props: FeedEntryProps) => {
}) })
export function FeedEntry(props: FeedEntryProps) { export function FeedEntry(props: FeedEntryProps) {
const { classes } = useStyles(props) const { viewMode } = useViewMode()
const viewMode = useAppSelector(state => state.user.settings?.viewMode) const { classes } = useStyles({ ...props, viewMode })
const compactHeader = viewMode === "title" && !props.expanded
const dispatch = useAppDispatch()
const swipeHandlers = useSwipeable({
onSwipedRight: () => dispatch(markEntry({ entry: props.entry, read: !props.entry.read })),
})
const { onContextMenu } = useFeedEntryContextMenu(props.entry)
let paddingX: MantineNumberSize = "xs"
if (viewMode === "title" || viewMode === "cozy") paddingX = 6
let paddingY: MantineNumberSize = "xs"
if (viewMode === "title") paddingY = 4
else if (viewMode === "cozy") paddingY = 8
let borderRadius: MantineNumberSize = "sm"
if (viewMode === "title") borderRadius = 0
else if (viewMode === "cozy") borderRadius = "xs"
const compactHeader = !props.expanded && (viewMode === "title" || viewMode === "cozy")
return ( return (
<Paper withBorder className={classes.paper}> <Paper withBorder radius={borderRadius} className={classes.paper}>
<Anchor <Anchor
variant="text" variant="text"
href={props.entry.url} href={props.entry.url}
@@ -56,21 +98,24 @@ export function FeedEntry(props: FeedEntryProps) {
rel="noreferrer" rel="noreferrer"
onClick={props.onHeaderClick} onClick={props.onHeaderClick}
onAuxClick={props.onHeaderClick} onAuxClick={props.onHeaderClick}
onContextMenu={onContextMenu}
> >
<Box p="xs"> <Box px={paddingX} py={paddingY} {...swipeHandlers}>
{compactHeader && <FeedEntryCompactHeader entry={props.entry} />} {compactHeader && <FeedEntryCompactHeader entry={props.entry} />}
{!compactHeader && <FeedEntryHeader entry={props.entry} expanded={props.expanded} />} {!compactHeader && <FeedEntryHeader entry={props.entry} expanded={props.expanded} />}
</Box> </Box>
</Anchor> </Anchor>
{props.expanded && ( {props.expanded && (
<Box px="xs" pb="xs"> <Box px={paddingX} pb={paddingY}>
<Box className={classes.body} sx={{ direction: props.entry.rtl ? "rtl" : "ltr" }}> <Box className={classes.body} sx={{ direction: props.entry.rtl ? "rtl" : "ltr" }}>
<FeedEntryBody entry={props.entry} /> <FeedEntryBody entry={props.entry} />
</Box> </Box>
<Divider variant="dashed" my="xs" /> <Divider variant="dashed" my={paddingY} />
<FeedEntryFooter entry={props.entry} /> <FeedEntryFooter entry={props.entry} />
</Box> </Box>
)} )}
<FeedEntryContextMenu entry={props.entry} />
</Paper> </Paper>
) )
} }

View File

@@ -14,7 +14,7 @@ export function FeedEntryBody(props: FeedEntryBodyProps) {
<Box> <Box>
<Content content={props.entry.content} /> <Content content={props.entry.content} />
</Box> </Box>
{props.entry.enclosureUrl && ( {props.entry.enclosureType && props.entry.enclosureUrl && (
<Box pt="md"> <Box pt="md">
<Enclosure enclosureType={props.entry.enclosureType} enclosureUrl={props.entry.enclosureUrl} /> <Enclosure enclosureType={props.entry.enclosureType} enclosureUrl={props.entry.enclosureUrl} />
</Box> </Box>

View File

@@ -1,8 +1,9 @@
import { Box, createStyles, Image, Text } from "@mantine/core" import { Box, createStyles, Text } from "@mantine/core"
import { Entry } from "app/types" import { Entry } from "app/types"
import { RelativeDate } from "components/RelativeDate" import { RelativeDate } from "components/RelativeDate"
import { OnDesktop } from "components/responsive/OnDesktop" import { OnDesktop } from "components/responsive/OnDesktop"
import { FeedEntryTitle } from "./FeedEntryTitle" import { FeedEntryTitle } from "./FeedEntryTitle"
import { FeedFavicon } from "./FeedFavicon"
export interface FeedEntryHeaderProps { export interface FeedEntryHeaderProps {
entry: Entry entry: Entry
@@ -37,7 +38,7 @@ export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
return ( return (
<Box className={classes.wrapper}> <Box className={classes.wrapper}>
<Box> <Box>
<Image withPlaceholder src={props.entry.iconUrl} alt="feed icon" width={18} height={18} /> <FeedFavicon url={props.entry.iconUrl} />
</Box> </Box>
<OnDesktop> <OnDesktop>
<Text color="dimmed" className={classes.feedName}> <Text color="dimmed" className={classes.feedName}>

View File

@@ -0,0 +1,127 @@
import { t, Trans } from "@lingui/macro"
import { createStyles, Group } from "@mantine/core"
import { Constants } from "app/constants"
import { markEntriesUpToEntry, markEntry, starEntry } from "app/slices/entries"
import { redirectToFeed } from "app/slices/redirect"
import { useAppDispatch, useAppSelector } from "app/store"
import { Entry } from "app/types"
import { openLinkInBackgroundTab } from "app/utils"
import { throttle, truncate } from "lodash"
import { useEffect } from "react"
import { Item, Menu, Separator, useContextMenu } from "react-contexify"
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb"
interface FeedEntryContextMenuProps {
entry: Entry
}
const iconSize = 16
const useStyles = createStyles(theme => ({
menu: {
// apply mantine theme from MenuItem.styles.ts
fontSize: theme.fontSizes.sm,
"--contexify-item-color": `${theme.colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
"--contexify-activeItem-color": `${theme.colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
"--contexify-activeItem-bgColor": `${
theme.colorScheme === "dark" ? theme.fn.rgba(theme.colors.dark[3], 0.35) : theme.colors.gray[1]
} !important`,
},
}))
const menuId = (entry: Entry) => entry.id
export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
const { classes, theme } = useStyles()
const sourceType = useAppSelector(state => state.entries.source.type)
const dispatch = useAppDispatch()
return (
<Menu id={menuId(props.entry)} theme={theme.colorScheme} animation={false} className={classes.menu}>
<Item
onClick={() => {
window.open(props.entry.url, "_blank", "noreferrer")
dispatch(markEntry({ entry: props.entry, read: true }))
}}
>
<Group>
<TbExternalLink size={iconSize} />
<Trans>Open link in new tab</Trans>
</Group>
</Item>
<Item
onClick={() => {
openLinkInBackgroundTab(props.entry.url)
dispatch(markEntry({ entry: props.entry, read: true }))
}}
>
<Group>
<TbExternalLink size={iconSize} />
<Trans>Open link in new background tab</Trans>
</Group>
</Item>
<Separator />
<Item onClick={() => dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}>
<Group>
{props.entry.starred ? <TbStarOff size={iconSize} /> : <TbStar size={iconSize} />}
{props.entry.starred ? t`Unstar` : t`Star`}
</Group>
</Item>
<Item onClick={() => dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))}>
<Group>
{props.entry.read ? <TbEyeOff size={iconSize} /> : <TbEyeCheck size={iconSize} />}
{props.entry.read ? t`Keep unread` : t`Mark as read`}
</Group>
</Item>
<Item onClick={() => dispatch(markEntriesUpToEntry(props.entry))}>
<Group>
<TbArrowBarToDown size={iconSize} />
<Trans>Mark as read up to here</Trans>
</Group>
</Item>
{sourceType === "category" && (
<>
<Separator />
<Item
onClick={() => {
dispatch(redirectToFeed(props.entry.feedId))
}}
>
<Group>
<TbRss size={iconSize} />
<Trans>Go to {truncate(props.entry.feedName)}</Trans>
</Group>
</Item>
</>
)}
</Menu>
)
}
export function useFeedEntryContextMenu(entry: Entry) {
const contextMenu = useContextMenu({
id: menuId(entry),
})
const onContextMenu = (event: React.MouseEvent) => {
event.preventDefault()
contextMenu.show({
event,
})
}
// close context menu on scroll
useEffect(() => {
const scrollArea = document.getElementById(Constants.dom.mainScrollAreaId)
const listener = () => contextMenu.hideAll()
const throttledListener = throttle(listener, 100)
scrollArea?.addEventListener("scroll", throttledListener)
return () => scrollArea?.removeEventListener("scroll", throttledListener)
}, [contextMenu])
return { onContextMenu }
}

View File

@@ -7,6 +7,7 @@ import { useAppDispatch, useAppSelector } from "app/store"
import { Entry } from "app/types" import { Entry } from "app/types"
import { ActionButton } from "components/ActionButtton" import { ActionButton } from "components/ActionButtton"
import { ButtonToolbar } from "components/ButtonToolbar" import { ButtonToolbar } from "components/ButtonToolbar"
import { throttle } from "lodash"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbShare, TbStar, TbStarOff, TbTag } from "react-icons/tb" import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbShare, TbStar, TbStarOff, TbTag } from "react-icons/tb"
import { ShareButtons } from "./ShareButtons" import { ShareButtons } from "./ShareButtons"
@@ -22,8 +23,7 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
const mobile = !useMediaQuery(`(min-width: ${Constants.layout.mobileBreakpoint}px)`) const mobile = !useMediaQuery(`(min-width: ${Constants.layout.mobileBreakpoint}px)`)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const showSharingButtons = const showSharingButtons = sharingSettings && Object.values(sharingSettings).some(v => v)
sharingSettings && (Object.values(sharingSettings) as Array<typeof sharingSettings[keyof typeof sharingSettings]>).some(v => v)
const readStatusButtonClicked = () => dispatch(markEntry({ entry: props.entry, read: !props.entry.read })) const readStatusButtonClicked = () => dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))
const onTagsChange = (values: string[]) => const onTagsChange = (values: string[]) =>
@@ -38,8 +38,10 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
const scrollArea = document.getElementById(Constants.dom.mainScrollAreaId) const scrollArea = document.getElementById(Constants.dom.mainScrollAreaId)
const listener = () => setScrollPosition(scrollArea ? scrollArea.scrollTop : 0) const listener = () => setScrollPosition(scrollArea ? scrollArea.scrollTop : 0)
scrollArea?.addEventListener("scroll", listener) const throttledListener = throttle(listener, 100)
return () => scrollArea?.removeEventListener("scroll", listener)
scrollArea?.addEventListener("scroll", throttledListener)
return () => scrollArea?.removeEventListener("scroll", throttledListener)
}, []) }, [])
return ( return (

View File

@@ -1,7 +1,8 @@
import { Box, createStyles, Image, Text } from "@mantine/core" import { Box, createStyles, Text } from "@mantine/core"
import { Entry } from "app/types" import { Entry } from "app/types"
import { RelativeDate } from "components/RelativeDate" import { RelativeDate } from "components/RelativeDate"
import { FeedEntryTitle } from "./FeedEntryTitle" import { FeedEntryTitle } from "./FeedEntryTitle"
import { FeedFavicon } from "./FeedFavicon"
export interface FeedEntryHeaderProps { export interface FeedEntryHeaderProps {
entry: Entry entry: Entry
@@ -33,7 +34,7 @@ export function FeedEntryHeader(props: FeedEntryHeaderProps) {
</Box> </Box>
<Box className={classes.headerSubtext}> <Box className={classes.headerSubtext}>
<Box mr={6}> <Box mr={6}>
<Image withPlaceholder src={props.entry.iconUrl} alt="feed icon" width={18} height={18} /> <FeedFavicon url={props.entry.iconUrl} />
</Box> </Box>
<Box> <Box>
<Text color="dimmed">{props.entry.feedName}</Text> <Text color="dimmed">{props.entry.feedName}</Text>

View File

@@ -0,0 +1,22 @@
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
export interface FeedFaviconProps {
url: string
size?: number
}
export function FeedFavicon({ url, size = 18 }: FeedFaviconProps) {
return (
<ImageWithPlaceholderWhileLoading
src={url}
alt="feed favicon"
width={size}
height={size}
placeholderWidth={size}
placeholderHeight={size}
placeholderBackgroundColor="inherit"
placeholderIconSize={size}
placeholderIconColor="inherit"
/>
)
}

View File

@@ -4,16 +4,20 @@ import { Constants } from "app/constants"
import { useAppSelector } from "app/store" import { useAppSelector } from "app/store"
import { flattenCategoryTree } from "app/utils" import { flattenCategoryTree } from "app/utils"
type CategorySelectProps = Partial<SelectProps> & { withAll?: boolean } type CategorySelectProps = Partial<SelectProps> & {
withAll?: boolean
withoutCategoryIds?: string[]
}
export function CategorySelect(props: CategorySelectProps) { export function CategorySelect(props: CategorySelectProps) {
const rootCategory = useAppSelector(state => state.tree.rootCategory) const rootCategory = useAppSelector(state => state.tree.rootCategory)
const categories = rootCategory && flattenCategoryTree(rootCategory) const categories = rootCategory && flattenCategoryTree(rootCategory)
const selectData: SelectItem[] | undefined = categories const selectData: SelectItem[] | undefined = categories
?.filter(c => c.id !== Constants.categories.all.id) ?.filter(c => c.id !== Constants.categories.all.id)
.filter(c => !props.withoutCategoryIds || !props.withoutCategoryIds.includes(c.id))
.sort((c1, c2) => c1.name.localeCompare(c2.name)) .sort((c1, c2) => c1.name.localeCompare(c2.name))
.map(c => ({ .map(c => ({
label: c.name, label: c.parentName ? t`${c.name} (in ${c.parentName})` : c.name,
value: c.id, value: c.id,
})) }))
if (props.withAll) { if (props.withAll) {

View File

@@ -1,11 +1,26 @@
import { Trans } from "@lingui/macro" import { t, Trans } from "@lingui/macro"
import { Box, Divider, Group, Menu, SegmentedControl, SegmentedControlItem, useMantineColorScheme } from "@mantine/core" import { Box, Divider, Group, Menu, SegmentedControl, SegmentedControlItem, useMantineColorScheme } from "@mantine/core"
import { showNotification } from "@mantine/notifications"
import { client } from "app/client"
import { redirectToAbout, redirectToAdminUsers, redirectToMetrics, redirectToSettings } from "app/slices/redirect" import { redirectToAbout, redirectToAdminUsers, redirectToMetrics, redirectToSettings } from "app/slices/redirect"
import { changeViewMode } from "app/slices/user"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { ViewMode } from "app/types" import { ViewMode } from "app/types"
import { useState } from "react" import { useState } from "react"
import { TbChartLine, TbHelp, TbLayoutList, TbList, TbMoon, TbNotes, TbPower, TbSettings, TbSun, TbUsers } from "react-icons/tb" import {
TbChartLine,
TbHelp,
TbLayoutList,
TbList,
TbListDetails,
TbMoon,
TbNotes,
TbPower,
TbSettings,
TbSun,
TbUsers,
TbWorldDownload,
} from "react-icons/tb"
import { useViewMode } from "../../hooks/useViewMode"
interface ProfileMenuProps { interface ProfileMenuProps {
control: React.ReactElement control: React.ReactElement
@@ -40,6 +55,17 @@ const viewModeData: ViewModeControlItem[] = [
</Group> </Group>
), ),
}, },
{
value: "detailed",
label: (
<Group>
<TbListDetails size={iconSize} />
<Box ml={6}>
<Trans>Detailed</Trans>
</Box>
</Group>
),
},
{ {
value: "expanded", value: "expanded",
label: ( label: (
@@ -55,7 +81,8 @@ const viewModeData: ViewModeControlItem[] = [
export function ProfileMenu(props: ProfileMenuProps) { export function ProfileMenu(props: ProfileMenuProps) {
const [opened, setOpened] = useState(false) const [opened, setOpened] = useState(false)
const viewMode = useAppSelector(state => state.user.settings?.viewMode) const { viewMode, setViewMode } = useViewMode()
const profile = useAppSelector(state => state.user.profile)
const admin = useAppSelector(state => state.user.profile?.admin) const admin = useAppSelector(state => state.user.profile?.admin)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { colorScheme, toggleColorScheme } = useMantineColorScheme() const { colorScheme, toggleColorScheme } = useMantineColorScheme()
@@ -69,6 +96,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
<Menu position="bottom-end" closeOnItemClick={false} opened={opened} onChange={setOpened}> <Menu position="bottom-end" closeOnItemClick={false} opened={opened} onChange={setOpened}>
<Menu.Target>{props.control}</Menu.Target> <Menu.Target>{props.control}</Menu.Target>
<Menu.Dropdown> <Menu.Dropdown>
{profile && <Menu.Label>{profile.name}</Menu.Label>}
<Menu.Item <Menu.Item
icon={<TbSettings size={iconSize} />} icon={<TbSettings size={iconSize} />}
onClick={() => { onClick={() => {
@@ -78,6 +106,21 @@ export function ProfileMenu(props: ProfileMenuProps) {
> >
<Trans>Settings</Trans> <Trans>Settings</Trans>
</Menu.Item> </Menu.Item>
<Menu.Item
icon={<TbWorldDownload size={iconSize} />}
onClick={() =>
client.feed.refreshAll().then(() => {
showNotification({
message: t`Your feeds have been queued for refresh.`,
color: "green",
autoClose: 1000,
})
setOpened(false)
})
}
>
<Trans>Fetch all my feeds now</Trans>
</Menu.Item>
<Divider /> <Divider />
<Menu.Label> <Menu.Label>
@@ -96,7 +139,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
orientation="vertical" orientation="vertical"
data={viewModeData} data={viewModeData}
value={viewMode} value={viewMode}
onChange={e => dispatch(changeViewMode(e as ViewMode))} onChange={e => setViewMode(e as ViewMode)}
mb="xs" mb="xs"
/> />

View File

@@ -22,7 +22,7 @@ export function ProfileSettings() {
const form = useForm<FormData>({ const form = useForm<FormData>({
validate: { validate: {
newPasswordConfirmation: (value: string, values: FormData) => (value !== values.newPassword ? t`Passwords do not match` : null), newPasswordConfirmation: (value, values) => (value !== values.newPassword ? t`Passwords do not match` : null),
}, },
}) })
const { setValues } = form const { setValues } = form

View File

@@ -1,4 +1,5 @@
import { Box, createStyles, Image } from "@mantine/core" import { Box, createStyles } from "@mantine/core"
import { FeedFavicon } from "components/content/FeedFavicon"
import React, { ReactNode } from "react" import React, { ReactNode } from "react"
import { UnreadCount } from "./UnreadCount" import { UnreadCount } from "./UnreadCount"
@@ -49,11 +50,7 @@ export function TreeNode(props: TreeNodeProps) {
return ( return (
<Box py={1} pl={props.level * 20} className={classes.node} onClick={(e: React.MouseEvent) => props.onClick(e, props.id)}> <Box py={1} pl={props.level * 20} className={classes.node} onClick={(e: React.MouseEvent) => props.onClick(e, props.id)}>
<Box mr={6} onClick={(e: React.MouseEvent) => props.onIconClick && props.onIconClick(e, props.id)}> <Box mr={6} onClick={(e: React.MouseEvent) => props.onIconClick && props.onIconClick(e, props.id)}>
{typeof props.icon === "string" ? ( {typeof props.icon === "string" ? <FeedFavicon url={props.icon} /> : props.icon}
<Image withPlaceholder src={props.icon} alt="favicon" width={18} height={18} />
) : (
props.icon
)}
</Box> </Box>
<Box className={classes.nodeText}>{props.name}</Box> <Box className={classes.nodeText}>{props.name}</Box>
{!props.expanded && ( {!props.expanded && (

View File

@@ -1,9 +1,10 @@
import { t } from "@lingui/macro" import { t } from "@lingui/macro"
import { Box, Center, Image, Kbd, TextInput } from "@mantine/core" import { Box, Center, Kbd, TextInput } from "@mantine/core"
import { openSpotlight, SpotlightAction, SpotlightProvider } from "@mantine/spotlight" import { openSpotlight, SpotlightAction, SpotlightProvider } from "@mantine/spotlight"
import { redirectToFeed } from "app/slices/redirect" import { redirectToFeed } from "app/slices/redirect"
import { useAppDispatch } from "app/store" import { useAppDispatch } from "app/store"
import { Subscription } from "app/types" import { Subscription } from "app/types"
import { FeedFavicon } from "components/content/FeedFavicon"
import { useMousetrap } from "hooks/useMousetrap" import { useMousetrap } from "hooks/useMousetrap"
import { TbSearch } from "react-icons/tb" import { TbSearch } from "react-icons/tb"
@@ -17,7 +18,7 @@ export function TreeSearch(props: TreeSearchProps) {
.sort((f1, f2) => f1.name.localeCompare(f2.name)) .sort((f1, f2) => f1.name.localeCompare(f2.name))
.map(f => ({ .map(f => ({
title: f.name, title: f.name,
icon: <Image withPlaceholder src={f.iconUrl} alt="favicon" width={18} height={18} />, icon: <FeedFavicon url={f.iconUrl} />,
onTrigger: () => dispatch(redirectToFeed(f.id)), onTrigger: () => dispatch(redirectToFeed(f.id)),
})) }))

View File

@@ -0,0 +1,7 @@
import useLocalStorage from "use-local-storage"
import { ViewMode } from "../app/types"
export function useViewMode() {
const [viewMode, setViewMode] = useLocalStorage<ViewMode>("view-mode", "detailed")
return { viewMode, setViewMode }
}

View File

@@ -0,0 +1,24 @@
import { reloadTree } from "app/slices/tree"
import { useAppDispatch } from "app/store"
import { useEffect } from "react"
import WebsocketHeartbeatJs from "websocket-heartbeat-js"
export const useWebSocket = () => {
const dispatch = useAppDispatch()
useEffect(() => {
const currentUrl = new URL(window.location.href)
const wsProtocol = currentUrl.protocol === "http:" ? "ws" : "wss"
const wsUrl = `${wsProtocol}://${currentUrl.hostname}:${currentUrl.port}/ws`
const ws = new WebsocketHeartbeatJs({ url: wsUrl, pingMsg: "ping" })
ws.onmessage = event => {
const { data } = event
if (typeof data === "string") {
if (data.startsWith("new-feed-entries:")) dispatch(reloadTree())
}
}
return () => ws.close()
}, [dispatch])
}

View File

@@ -29,7 +29,37 @@ import "dayjs/locale/sk"
import "dayjs/locale/sv" import "dayjs/locale/sv"
import "dayjs/locale/tr" import "dayjs/locale/tr"
import "dayjs/locale/zh" import "dayjs/locale/zh"
import { ar, ca, cs, cy, da, de, en, es, fa, fi, fr, gl, hu, id, it, ja, ko, ms, nb, nl, nn, pl, pt, ru, sk, sv, tr, zh } from "make-plural" import {
ar,
ca,
cs,
cy,
da,
de,
en,
es,
fa,
fi,
fr,
gl,
hu,
id,
it,
ja,
ko,
ms,
nb,
nl,
nn,
pl,
PluralCategory,
pt,
ru,
sk,
sv,
tr,
zh,
} from "make-plural"
import { useEffect } from "react" import { useEffect } from "react"
import { messages as arMessages } from "./locales/ar/messages" import { messages as arMessages } from "./locales/ar/messages"
import { messages as caMessages } from "./locales/ca/messages" import { messages as caMessages } from "./locales/ca/messages"
@@ -64,7 +94,7 @@ interface Locale {
key: string key: string
label: string label: string
messages: Messages messages: Messages
plurals?: (n: number | string, ord?: boolean) => string plurals?: (n: number | string, ord?: boolean) => PluralCategory
} }
// add an object to the array to add a new locale // add an object to the array to add a new locale

View File

@@ -219,6 +219,10 @@ msgstr "حذف المستخدم"
msgid "Desc" msgid "Desc"
msgstr "تنازلي" msgstr "تنازلي"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "موجز URL"
msgid "Feed name" msgid "Feed name"
msgstr "اسم الخلاصة" msgstr "اسم الخلاصة"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "تصفية التعبير" msgstr "تصفية التعبير"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "هل نسيت كلمة المرور؟" msgstr "هل نسيت كلمة المرور؟"
@@ -320,6 +332,10 @@ msgstr "انتقل إلى وثائق API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "اذهب إلى طريقة العرض \"الكل\"" msgstr "اذهب إلى طريقة العرض \"الكل\""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "الأشياء الجيدة" msgstr "الأشياء الجيدة"
@@ -348,6 +364,7 @@ msgstr "استيراد"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "في العرض الموسع ، التمرير عبر الإدخالات وضع علامة عليها كمقروءة" msgstr "في العرض الموسع ، التمرير عبر الإدخالات وضع علامة عليها كمقروءة"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "إبقاء غير مقروءة" msgstr "إبقاء غير مقروءة"
@@ -397,6 +414,7 @@ msgstr "تحميل العلامات ..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "تسجيل الدخول" msgstr "تسجيل الدخول"
@@ -404,6 +422,10 @@ msgstr "تسجيل الدخول"
msgid "Logout" msgid "Logout"
msgstr "تسجيل الخروج" msgstr "تسجيل الخروج"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "تعليم الكل كمقروء"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "تعليم كافة الإدخالات كمقروءة" msgstr "تعليم كافة الإدخالات كمقروءة"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "وضع علامة كمقروء" msgstr "وضع علامة كمقروء"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "وضع علامة كمقروءة حتى هنا" msgstr "وضع علامة كمقروءة حتى هنا"
@@ -430,6 +454,10 @@ msgstr "وضع علامة كمقروءة حتى هنا"
msgid "Metrics" msgid "Metrics"
msgstr "المقاييس" msgstr "المقاييس"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "تحريك الصفحة لأسفل" msgstr "تحريك الصفحة لأسفل"
@@ -515,6 +543,14 @@ msgstr "فتح الإدخال الحالي في علامة تبويب جديدة
msgid "Open link" msgid "Open link"
msgstr "افتح الرابط" msgstr "افتح الرابط"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "فتح الإدخال التالي" msgstr "فتح الإدخال التالي"
@@ -581,6 +617,10 @@ msgstr "تحديث"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "تم إغلاق التسجيلات في مثيل CommaFeed هذا" msgstr "تم إغلاق التسجيلات في مثيل CommaFeed هذا"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "مشاركة المواقع"
msgid "Shift" msgid "Shift"
msgstr "الحلقة" msgstr "الحلقة"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "إظهار موجز ويب والفئات التي لا تحتوي على إدخالات غير مقروءة" msgstr "إظهار موجز ويب والفئات التي لا تحتوي على إدخالات غير مقروءة"
@@ -642,6 +690,7 @@ msgstr "إظهار تعليمات اختصار لوحة المفاتيح"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "قم بالتسجيل" msgstr "قم بالتسجيل"
@@ -654,6 +703,7 @@ msgstr "شيء سيء حدث للتو ..."
msgid "Space" msgid "Space"
msgstr "فضاء" msgstr "فضاء"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "النجم" msgstr "النجم"
@@ -681,6 +731,10 @@ msgstr "الاشتراك في موجز ويب"
msgid "Success" msgid "Success"
msgstr "النجاح" msgstr "النجاح"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "التبديل إلى النسق الداكن" msgstr "التبديل إلى النسق الداكن"
@@ -709,10 +763,15 @@ msgstr "تبديل قراءة حالة الإدخال الحالي"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "جرب CommaFeed باستخدام الحساب التجريبي: تجريبي / تجريبي" msgstr "جرب CommaFeed باستخدام الحساب التجريبي: تجريبي / تجريبي"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "غير مقروءة" msgstr "غير مقروءة"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "إلغاء النجم" msgstr "إلغاء النجم"
@@ -743,6 +802,14 @@ msgstr "موقع الكتروني"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "ليس لديك أي اشتراكات حتى الآن. " msgstr "ليس لديك أي اشتراكات حتى الآن. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "الملف مطلوب" msgstr "الملف مطلوب"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Suprimeix l'usuari"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL del canal"
msgid "Feed name" msgid "Feed name"
msgstr "Nom del canal" msgstr "Nom del canal"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Expressió de filtratge" msgstr "Expressió de filtratge"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Heu oblidat la contrasenya?" msgstr "Heu oblidat la contrasenya?"
@@ -320,6 +332,10 @@ msgstr "Vés a la documentació de l'API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Vés a la vista Tot" msgstr "Vés a la vista Tot"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Bones" msgstr "Bones"
@@ -348,6 +364,7 @@ msgstr "Importació"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "a la vista ampliada, desplaçant-se per les entrades les marqueu com a llegides" msgstr "a la vista ampliada, desplaçant-se per les entrades les marqueu com a llegides"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Mantenir sense llegir" msgstr "Mantenir sense llegir"
@@ -397,6 +414,7 @@ msgstr "Carregant les etiquetes..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Inicia sessió" msgstr "Inicia sessió"
@@ -404,6 +422,10 @@ msgstr "Inicia sessió"
msgid "Logout" msgid "Logout"
msgstr "Tanca sessió" msgstr "Tanca sessió"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Marca-ho tot com a llegit"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Marqueu totes les entrades com a llegides" msgstr "Marqueu totes les entrades com a llegides"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Marca com a llegit" msgstr "Marca com a llegit"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Marca com a llegit fins aquí" msgstr "Marca com a llegit fins aquí"
@@ -430,6 +454,10 @@ msgstr "Marca com a llegit fins aquí"
msgid "Metrics" msgid "Metrics"
msgstr "mètriques" msgstr "mètriques"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Mou la pàgina cap avall" msgstr "Mou la pàgina cap avall"
@@ -515,6 +543,14 @@ msgstr "Obre l'entrada actual en una pestanya nova al fons"
msgid "Open link" msgid "Open link"
msgstr "Enllaç obert" msgstr "Enllaç obert"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Obre la següent entrada" msgstr "Obre la següent entrada"
@@ -581,6 +617,10 @@ msgstr "Actualitzar"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Els registres estan tancats en aquesta instància de CommaFeed" msgstr "Els registres estan tancats en aquesta instància de CommaFeed"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Compartir llocs"
msgid "Shift" msgid "Shift"
msgstr "canvi" msgstr "canvi"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Mostra feeds i categories sense entrades no llegides" msgstr "Mostra feeds i categories sense entrades no llegides"
@@ -642,6 +690,7 @@ msgstr "Mostra l'ajuda de la drecera del teclat"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Inscriu-te" msgstr "Inscriu-te"
@@ -654,6 +703,7 @@ msgstr "Acaba de passar una cosa dolenta..."
msgid "Space" msgid "Space"
msgstr "Espai" msgstr "Espai"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Estrella" msgstr "Estrella"
@@ -681,6 +731,10 @@ msgstr "Subscriu-te al canal"
msgid "Success" msgid "Success"
msgstr "Éxit" msgstr "Éxit"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Canvia al tema fosc" msgstr "Canvia al tema fosc"
@@ -709,10 +763,15 @@ msgstr "Canvia l'estat de lectura de l'entrada actual"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Proveu CommaFeed amb el compte de demostració: demo/demo" msgstr "Proveu CommaFeed amb el compte de demostració: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Sense llegir" msgstr "Sense llegir"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Desestrellar" msgstr "Desestrellar"
@@ -743,6 +802,14 @@ msgstr "Lloc web"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Encara no teniu cap subscripció. " msgstr "Encara no teniu cap subscripció. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "el fitxer és necessari" msgstr "el fitxer és necessari"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Smazat uživatele"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL zdroje"
msgid "Feed name" msgid "Feed name"
msgstr "Název zdroje" msgstr "Název zdroje"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtrování výrazu" msgstr "Filtrování výrazu"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Zapomněli jste heslo?" msgstr "Zapomněli jste heslo?"
@@ -320,6 +332,10 @@ msgstr "Přejděte na dokumentaci API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Přejděte do zobrazení Vše" msgstr "Přejděte do zobrazení Vše"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Dobroty" msgstr "Dobroty"
@@ -348,6 +364,7 @@ msgstr ""
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "V rozšířeném zobrazení je procházením označíte jako přečtené" msgstr "V rozšířeném zobrazení je procházením označíte jako přečtené"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Ponechat nepřečtené" msgstr "Ponechat nepřečtené"
@@ -397,6 +414,7 @@ msgstr "Načítání značek..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Přihlaste se" msgstr "Přihlaste se"
@@ -404,6 +422,10 @@ msgstr "Přihlaste se"
msgid "Logout" msgid "Logout"
msgstr "Odhlášení" msgstr "Odhlášení"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Označit vše jako přečtené"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Označte všechny položky jako přečtené" msgstr "Označte všechny položky jako přečtené"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Označit jako přečtené" msgstr "Označit jako přečtené"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Označit jako přečtené až sem" msgstr "Označit jako přečtené až sem"
@@ -430,6 +454,10 @@ msgstr "Označit jako přečtené až sem"
msgid "Metrics" msgid "Metrics"
msgstr "Metriky" msgstr "Metriky"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Přesuňte stránku dolů" msgstr "Přesuňte stránku dolů"
@@ -515,6 +543,14 @@ msgstr "Otevřít aktuální položku na nové kartě na pozadí"
msgid "Open link" msgid "Open link"
msgstr "Otevřít odkaz" msgstr "Otevřít odkaz"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Otevřete další položku" msgstr "Otevřete další položku"
@@ -581,6 +617,10 @@ msgstr "Obnovit"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "V této instanci CommaFeed jsou registrace uzavřeny" msgstr "V této instanci CommaFeed jsou registrace uzavřeny"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Stránky pro sdílení"
msgid "Shift" msgid "Shift"
msgstr "Směna" msgstr "Směna"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Zobrazit kanály a kategorie bez nepřečtených položek" msgstr "Zobrazit kanály a kategorie bez nepřečtených položek"
@@ -642,6 +690,7 @@ msgstr "Zobrazit nápovědu ke klávesovým zkratkám"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Zaregistrujte se" msgstr "Zaregistrujte se"
@@ -654,6 +703,7 @@ msgstr "Právě se stalo něco špatného..."
msgid "Space" msgid "Space"
msgstr "Vesmír" msgstr "Vesmír"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Hvězda" msgstr "Hvězda"
@@ -681,6 +731,10 @@ msgstr "Přihlaste se k odběru kanálu"
msgid "Success" msgid "Success"
msgstr "Úspěch" msgstr "Úspěch"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Přepněte na tmavý motiv" msgstr "Přepněte na tmavý motiv"
@@ -709,10 +763,15 @@ msgstr "Přepne stav čtení aktuálního záznamu"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Vyzkoušejte CommaFeed s demo účtem: demo/demo" msgstr "Vyzkoušejte CommaFeed s demo účtem: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Nepřečteno" msgstr "Nepřečteno"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Odstranit hvězdu" msgstr "Odstranit hvězdu"
@@ -743,6 +802,14 @@ msgstr "Webové stránky"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Zatím nemáte žádné předplatné. " msgstr "Zatím nemáte žádné předplatné. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "" msgstr ""
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Dileu defnyddiwr"
msgid "Desc" msgid "Desc"
msgstr "Rhag" msgstr "Rhag"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL porthiant"
msgid "Feed name" msgid "Feed name"
msgstr "Enw porthiant" msgstr "Enw porthiant"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Hidlo mynegiant" msgstr "Hidlo mynegiant"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Wedi anghofio cyfrinair?" msgstr "Wedi anghofio cyfrinair?"
@@ -320,6 +332,10 @@ msgstr "Ewch i'r ddogfennaeth API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Ewch i'r golwg Pawb" msgstr "Ewch i'r golwg Pawb"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "nwyddau" msgstr "nwyddau"
@@ -348,6 +364,7 @@ msgstr "Mewnforio"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "Mewn gwedd estynedig, mae sgrolio trwy gofnodion yn nodi eu bod wedi'u darllen" msgstr "Mewn gwedd estynedig, mae sgrolio trwy gofnodion yn nodi eu bod wedi'u darllen"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Cadwch heb ei ddarllen" msgstr "Cadwch heb ei ddarllen"
@@ -397,6 +414,7 @@ msgstr "Wrthi'n llwytho tagiau..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Mewngofnodi" msgstr "Mewngofnodi"
@@ -404,6 +422,10 @@ msgstr "Mewngofnodi"
msgid "Logout" msgid "Logout"
msgstr "Allgofnodi" msgstr "Allgofnodi"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Marciwch y cyfan wedi'i ddarllen"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Marciwch bob cofnod wedi'i ddarllen" msgstr "Marciwch bob cofnod wedi'i ddarllen"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Marciwch ei fod wedi'i ddarllen" msgstr "Marciwch ei fod wedi'i ddarllen"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Marciwch fel y darllenwyd hyd yma" msgstr "Marciwch fel y darllenwyd hyd yma"
@@ -430,6 +454,10 @@ msgstr "Marciwch fel y darllenwyd hyd yma"
msgid "Metrics" msgid "Metrics"
msgstr "metrigau" msgstr "metrigau"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Symudwch y dudalen i lawr" msgstr "Symudwch y dudalen i lawr"
@@ -515,6 +543,14 @@ msgstr "Agorwch y cofnod cyfredol mewn tab newydd yn y cefndir"
msgid "Open link" msgid "Open link"
msgstr "Dolen agored" msgstr "Dolen agored"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Agor y cofnod nesaf" msgstr "Agor y cofnod nesaf"
@@ -581,6 +617,10 @@ msgstr "Adnewyddu"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Mae cofrestriadau ar gau ar yr achos CommaFeed hwn" msgstr "Mae cofrestriadau ar gau ar yr achos CommaFeed hwn"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Rhannu gwefannau"
msgid "Shift" msgid "Shift"
msgstr "shifft" msgstr "shifft"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Dangos ffrydiau a chategorïau heb unrhyw gofnodion heb eu darllen" msgstr "Dangos ffrydiau a chategorïau heb unrhyw gofnodion heb eu darllen"
@@ -642,6 +690,7 @@ msgstr "Dangos cymorth llwybr byr bysellfwrdd"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Cofrestrwch" msgstr "Cofrestrwch"
@@ -654,6 +703,7 @@ msgstr "Mae rhywbeth drwg newydd ddigwydd ..."
msgid "Space" msgid "Space"
msgstr "Gofod" msgstr "Gofod"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "seren" msgstr "seren"
@@ -681,6 +731,10 @@ msgstr "Tanysgrifio i'r porthiant"
msgid "Success" msgid "Success"
msgstr "Llwyddiant" msgstr "Llwyddiant"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Newid i thema dywyll" msgstr "Newid i thema dywyll"
@@ -709,10 +763,15 @@ msgstr "Toglo statws darllen y cofnod cyfredol"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Rhowch gynnig ar CommaFeed gyda'r cyfrif demo: demo / demo" msgstr "Rhowch gynnig ar CommaFeed gyda'r cyfrif demo: demo / demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Heb ei ddarllen" msgstr "Heb ei ddarllen"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "dad-seren" msgstr "dad-seren"
@@ -743,6 +802,14 @@ msgstr "Gwefan"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Nid oes gennych unrhyw danysgrifiadau eto. " msgstr "Nid oes gennych unrhyw danysgrifiadau eto. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "mae angen y ffeil" msgstr "mae angen y ffeil"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Slet bruger"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr ""
msgid "Feed name" msgid "Feed name"
msgstr "Feednavn" msgstr "Feednavn"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtrerende udtryk" msgstr "Filtrerende udtryk"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Glemt adgangskode?" msgstr "Glemt adgangskode?"
@@ -320,6 +332,10 @@ msgstr "Gå til API-dokumentationen."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Gå til visningen Alle" msgstr "Gå til visningen Alle"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Godbidder" msgstr "Godbidder"
@@ -348,6 +364,7 @@ msgstr ""
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "I udvidet visning markerer du dem som læst, når du ruller gennem poster" msgstr "I udvidet visning markerer du dem som læst, når du ruller gennem poster"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Forbehold ulæst" msgstr "Forbehold ulæst"
@@ -397,6 +414,7 @@ msgstr "Indlæser tags..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Log ind" msgstr "Log ind"
@@ -404,6 +422,10 @@ msgstr "Log ind"
msgid "Logout" msgid "Logout"
msgstr "Log ud" msgstr "Log ud"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Marker alle som læst"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Marker alle poster som læst" msgstr "Marker alle poster som læst"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Markér som læst" msgstr "Markér som læst"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Markér som læst indtil her" msgstr "Markér som læst indtil her"
@@ -430,6 +454,10 @@ msgstr "Markér som læst indtil her"
msgid "Metrics" msgid "Metrics"
msgstr "" msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Flyt siden ned" msgstr "Flyt siden ned"
@@ -515,6 +543,14 @@ msgstr "Åbn den aktuelle post i en ny fane i baggrunden"
msgid "Open link" msgid "Open link"
msgstr "Åbent link" msgstr "Åbent link"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Åbn næste post" msgstr "Åbn næste post"
@@ -581,6 +617,10 @@ msgstr "Opdater"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Registreringer er lukket på denne CommaFeed-instans" msgstr "Registreringer er lukket på denne CommaFeed-instans"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Delingssider"
msgid "Shift" msgid "Shift"
msgstr "Skift" msgstr "Skift"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Vis feeds og kategorier uden ulæste poster" msgstr "Vis feeds og kategorier uden ulæste poster"
@@ -642,6 +690,7 @@ msgstr "Vis hjælp til tastaturgenveje"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Tilmeld dig" msgstr "Tilmeld dig"
@@ -654,6 +703,7 @@ msgstr "Der er lige sket noget slemt..."
msgid "Space" msgid "Space"
msgstr "Rum" msgstr "Rum"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Stjerne" msgstr "Stjerne"
@@ -681,6 +731,10 @@ msgstr "Abonner på feedet"
msgid "Success" msgid "Success"
msgstr "Succes" msgstr "Succes"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Skift til mørkt tema" msgstr "Skift til mørkt tema"
@@ -709,10 +763,15 @@ msgstr "Skift læsestatus for den aktuelle post"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Prøv CommaFeed med demokontoen: demo/demo" msgstr "Prøv CommaFeed med demokontoen: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Ulæst" msgstr "Ulæst"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "" msgstr ""
@@ -743,6 +802,14 @@ msgstr "Hjemmeside"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Du har ingen abonnementer endnu. " msgstr "Du har ingen abonnementer endnu. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "fil er påkrævet" msgstr "fil er påkrævet"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Benutzer löschen"
msgid "Desc" msgid "Desc"
msgstr "Beschr" msgstr "Beschr"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "Feed-URL"
msgid "Feed name" msgid "Feed name"
msgstr "Feedname" msgstr "Feedname"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filterausdruck" msgstr "Filterausdruck"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Passwort vergessen?" msgstr "Passwort vergessen?"
@@ -320,6 +332,10 @@ msgstr "Gehen Sie zur API-Dokumentation."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Zur Ansicht Alle wechseln" msgstr "Zur Ansicht Alle wechseln"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Gutes" msgstr "Gutes"
@@ -348,6 +364,7 @@ msgstr "Importieren"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "In der erweiterten Ansicht werden Einträge beim Scrollen als gelesen markiert" msgstr "In der erweiterten Ansicht werden Einträge beim Scrollen als gelesen markiert"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Ungelesen lassen" msgstr "Ungelesen lassen"
@@ -397,6 +414,7 @@ msgstr "Tags werden geladen..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Einloggen" msgstr "Einloggen"
@@ -404,6 +422,10 @@ msgstr "Einloggen"
msgid "Logout" msgid "Logout"
msgstr "Abmelden" msgstr "Abmelden"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Alle als gelesen markieren"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Alle Einträge als gelesen markieren" msgstr "Alle Einträge als gelesen markieren"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Als gelesen markieren" msgstr "Als gelesen markieren"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Bis hierhin als gelesen markieren" msgstr "Bis hierhin als gelesen markieren"
@@ -430,6 +454,10 @@ msgstr "Bis hierhin als gelesen markieren"
msgid "Metrics" msgid "Metrics"
msgstr "Metriken" msgstr "Metriken"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Seite nach unten verschieben" msgstr "Seite nach unten verschieben"
@@ -515,6 +543,14 @@ msgstr "Aktuellen Eintrag in neuem Tab im Hintergrund öffnen"
msgid "Open link" msgid "Open link"
msgstr "Link öffnen" msgstr "Link öffnen"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Nächsten Eintrag öffnen" msgstr "Nächsten Eintrag öffnen"
@@ -581,6 +617,10 @@ msgstr "Aktualisieren"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Registrierungen sind für diese CommaFeed-Instanz geschlossen" msgstr "Registrierungen sind für diese CommaFeed-Instanz geschlossen"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Seiten teilen"
msgid "Shift" msgid "Shift"
msgstr "Verschiebung" msgstr "Verschiebung"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Feeds und Kategorien ohne ungelesene Einträge anzeigen" msgstr "Feeds und Kategorien ohne ungelesene Einträge anzeigen"
@@ -642,6 +690,7 @@ msgstr "Tastenkürzel-Hilfe anzeigen"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Melden Sie sich an" msgstr "Melden Sie sich an"
@@ -654,6 +703,7 @@ msgstr "Etwas Schlimmes ist gerade passiert..."
msgid "Space" msgid "Space"
msgstr "Raum" msgstr "Raum"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Stern" msgstr "Stern"
@@ -681,6 +731,10 @@ msgstr "Feed abonnieren"
msgid "Success" msgid "Success"
msgstr "Erfolg" msgstr "Erfolg"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Zum dunklen Design wechseln" msgstr "Zum dunklen Design wechseln"
@@ -709,10 +763,15 @@ msgstr "Lesestatus des aktuellen Eintrags umschalten"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Testen Sie CommaFeed mit dem Demokonto: demo/demo" msgstr "Testen Sie CommaFeed mit dem Demokonto: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Ungelesen" msgstr "Ungelesen"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Stern entfernen" msgstr "Stern entfernen"
@@ -743,6 +802,14 @@ msgstr "Webseite"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Sie haben noch keine Abonnements. " msgstr "Sie haben noch keine Abonnements. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "Datei ist erforderlich" msgstr "Datei ist erforderlich"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Delete user"
msgid "Desc" msgid "Desc"
msgstr "Desc" msgstr "Desc"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr "Detailed"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "Feed URL"
msgid "Feed name" msgid "Feed name"
msgstr "Feed name" msgstr "Feed name"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr "Fetch all my feeds now"
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtering expression" msgstr "Filtering expression"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Forgot password?" msgstr "Forgot password?"
@@ -320,6 +332,10 @@ msgstr "Go to the API documentation."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Go to the All view" msgstr "Go to the All view"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr "Go to {0}"
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Goodies" msgstr "Goodies"
@@ -348,6 +364,7 @@ msgstr "Import"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "In expanded view, scrolling through entries mark them as read" msgstr "In expanded view, scrolling through entries mark them as read"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Keep unread" msgstr "Keep unread"
@@ -397,6 +414,7 @@ msgstr "Loading tags..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Log in" msgstr "Log in"
@@ -404,6 +422,10 @@ msgstr "Log in"
msgid "Logout" msgid "Logout"
msgstr "Logout" msgstr "Logout"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr "Long press"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Mark all as read"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Mark all entries as read" msgstr "Mark all entries as read"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Mark as read" msgstr "Mark as read"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Mark as read up to here" msgstr "Mark as read up to here"
@@ -430,6 +454,10 @@ msgstr "Mark as read up to here"
msgid "Metrics" msgid "Metrics"
msgstr "Metrics" msgstr "Metrics"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr "Middle click"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Move the page down" msgstr "Move the page down"
@@ -515,6 +543,14 @@ msgstr "Open current entry in a new tab in the background"
msgid "Open link" msgid "Open link"
msgstr "Open link" msgstr "Open link"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr "Open link in new background tab"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr "Open link in new tab"
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Open next entry" msgstr "Open next entry"
@@ -581,6 +617,10 @@ msgstr "Refresh"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Registrations are closed on this CommaFeed instance" msgstr "Registrations are closed on this CommaFeed instance"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr "Right click"
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Sharing sites"
msgid "Shift" msgid "Shift"
msgstr "Shift" msgstr "Shift"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr "Show entry menu (desktop)"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr "Show entry menu (mobile)"
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Show feeds and categories with no unread entries" msgstr "Show feeds and categories with no unread entries"
@@ -642,6 +690,7 @@ msgstr "Show keyboard shortcut help"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Sign up" msgstr "Sign up"
@@ -654,6 +703,7 @@ msgstr "Something bad just happened..."
msgid "Space" msgid "Space"
msgstr "Space" msgstr "Space"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Star" msgstr "Star"
@@ -681,6 +731,10 @@ msgstr "Subscribe to the feed"
msgid "Success" msgid "Success"
msgstr "Success" msgstr "Success"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr "Swipe header to the right"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Switch to dark theme" msgstr "Switch to dark theme"
@@ -709,10 +763,15 @@ msgstr "Toggle read status of current entry"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Try out CommaFeed with the demo account: demo/demo" msgstr "Try out CommaFeed with the demo account: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr "Try the demo!"
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Unread" msgstr "Unread"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Unstar" msgstr "Unstar"
@@ -743,6 +802,14 @@ msgstr "Website"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgstr "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr "Your feeds have been queued for refresh."
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "file is required" msgstr "file is required"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr "{0} (in {1})"

View File

@@ -219,6 +219,10 @@ msgstr "Borrar usuario"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL de fuente"
msgid "Feed name" msgid "Feed name"
msgstr "Nombre de alimentación" msgstr "Nombre de alimentación"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Expresión de filtrado" msgstr "Expresión de filtrado"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "¿Olvidaste la contraseña?" msgstr "¿Olvidaste la contraseña?"
@@ -320,6 +332,10 @@ msgstr "Ir a la documentación de la API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Ir a la vista Todo" msgstr "Ir a la vista Todo"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "golosinas" msgstr "golosinas"
@@ -348,6 +364,7 @@ msgstr "Importar"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "En la vista ampliada, al desplazarse por las entradas, márquelas como leídas" msgstr "En la vista ampliada, al desplazarse por las entradas, márquelas como leídas"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Mantener sin leer" msgstr "Mantener sin leer"
@@ -397,6 +414,7 @@ msgstr "Cargando etiquetas..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Iniciar sesión" msgstr "Iniciar sesión"
@@ -404,6 +422,10 @@ msgstr "Iniciar sesión"
msgid "Logout" msgid "Logout"
msgstr "Salir" msgstr "Salir"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Marcar todo como leído"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Marcar todas las entradas como leídas" msgstr "Marcar todas las entradas como leídas"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Marcar como leído" msgstr "Marcar como leído"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Marcar como leído hasta aquí" msgstr "Marcar como leído hasta aquí"
@@ -430,6 +454,10 @@ msgstr "Marcar como leído hasta aquí"
msgid "Metrics" msgid "Metrics"
msgstr "Métricas" msgstr "Métricas"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Mover la página hacia abajo" msgstr "Mover la página hacia abajo"
@@ -515,6 +543,14 @@ msgstr "Abrir la entrada actual en una nueva pestaña en segundo plano"
msgid "Open link" msgid "Open link"
msgstr "Abrir enlace" msgstr "Abrir enlace"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Abrir siguiente entrada" msgstr "Abrir siguiente entrada"
@@ -581,6 +617,10 @@ msgstr "Actualizar"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Los registros están cerrados en esta instancia de CommaFeed" msgstr "Los registros están cerrados en esta instancia de CommaFeed"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Compartir sitios"
msgid "Shift" msgid "Shift"
msgstr "Cambio" msgstr "Cambio"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Mostrar feeds y categorías sin entradas no leídas" msgstr "Mostrar feeds y categorías sin entradas no leídas"
@@ -642,6 +690,7 @@ msgstr "Mostrar ayuda de atajo de teclado"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Registrarse" msgstr "Registrarse"
@@ -654,6 +703,7 @@ msgstr "Algo malo acaba de pasar..."
msgid "Space" msgid "Space"
msgstr "Espacio" msgstr "Espacio"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "estrella" msgstr "estrella"
@@ -681,6 +731,10 @@ msgstr "Suscríbete a la fuente"
msgid "Success" msgid "Success"
msgstr "Éxito" msgstr "Éxito"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Cambiar a tema oscuro" msgstr "Cambiar a tema oscuro"
@@ -709,10 +763,15 @@ msgstr "Alternar estado de lectura de la entrada actual"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Pruebe CommaFeed con la cuenta demo: demo/demo" msgstr "Pruebe CommaFeed con la cuenta demo: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "No leído" msgstr "No leído"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Desmarcar" msgstr "Desmarcar"
@@ -743,6 +802,14 @@ msgstr "Sitio web"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Todavía no tienes ninguna suscripción. " msgstr "Todavía no tienes ninguna suscripción. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "archivo requerido" msgstr "archivo requerido"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "حذف کاربر"
msgid "Desc" msgid "Desc"
msgstr "توصیف" msgstr "توصیف"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL فید"
msgid "Feed name" msgid "Feed name"
msgstr "نام فید" msgstr "نام فید"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "بیان فیلتر" msgstr "بیان فیلتر"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "رمز عبور را فراموش کرده اید؟" msgstr "رمز عبور را فراموش کرده اید؟"
@@ -320,6 +332,10 @@ msgstr "به مستندات API بروید."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "به نمای All بروید" msgstr "به نمای All بروید"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "خوبی ها" msgstr "خوبی ها"
@@ -348,6 +364,7 @@ msgstr "واردات"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "در نمای بازشده، پیمایش در ورودی‌ها، آنها را به عنوان خوانده شده علامت‌گذاری می‌کند" msgstr "در نمای بازشده، پیمایش در ورودی‌ها، آنها را به عنوان خوانده شده علامت‌گذاری می‌کند"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "خوانده نشده نگه دارید" msgstr "خوانده نشده نگه دارید"
@@ -397,6 +414,7 @@ msgstr "بارگیری برچسب ها..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "وارد شوید" msgstr "وارد شوید"
@@ -404,6 +422,10 @@ msgstr "وارد شوید"
msgid "Logout" msgid "Logout"
msgstr "خروج" msgstr "خروج"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "همه را به عنوان خوانده شده علامت گذاری ک
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "همه ورودی ها را به عنوان خوانده شده علامت گذاری کنید" msgstr "همه ورودی ها را به عنوان خوانده شده علامت گذاری کنید"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "علامت گذاری به عنوان خوانده شده" msgstr "علامت گذاری به عنوان خوانده شده"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "تا اینجا به عنوان خوانده شده علامت بزنید" msgstr "تا اینجا به عنوان خوانده شده علامت بزنید"
@@ -430,6 +454,10 @@ msgstr "تا اینجا به عنوان خوانده شده علامت بزنی
msgid "Metrics" msgid "Metrics"
msgstr "متریک" msgstr "متریک"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "صفحه را به پایین ببرید" msgstr "صفحه را به پایین ببرید"
@@ -515,6 +543,14 @@ msgstr "ورودی فعلی را در یک برگه جدید در پس زمین
msgid "Open link" msgid "Open link"
msgstr "پیوند را باز کنید" msgstr "پیوند را باز کنید"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "ورودی بعدی را باز کنید" msgstr "ورودی بعدی را باز کنید"
@@ -581,6 +617,10 @@ msgstr "تازه کردن"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "ثبت نام در این نمونه CommaFeed بسته شده است" msgstr "ثبت نام در این نمونه CommaFeed بسته شده است"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "اشتراک گذاری سایت ها"
msgid "Shift" msgid "Shift"
msgstr "شیفت" msgstr "شیفت"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "فیدها و دسته ها را بدون ورودی خوانده نشده نشان دهید" msgstr "فیدها و دسته ها را بدون ورودی خوانده نشده نشان دهید"
@@ -642,6 +690,7 @@ msgstr "نمایش راهنمایی میانبر صفحه کلید"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "ثبت نام کنید" msgstr "ثبت نام کنید"
@@ -654,6 +703,7 @@ msgstr "اتفاق بدی افتاد..."
msgid "Space" msgid "Space"
msgstr "فضا" msgstr "فضا"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "ستاره" msgstr "ستاره"
@@ -681,6 +731,10 @@ msgstr "در فید مشترک شوید"
msgid "Success" msgid "Success"
msgstr "موفقیت" msgstr "موفقیت"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "تغییر به تم تیره" msgstr "تغییر به تم تیره"
@@ -709,10 +763,15 @@ msgstr "وضعیت خواندن ورودی فعلی را تغییر دهید"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "CommaFeed را با حساب آزمایشی امتحان کنید: دمو/دمو" msgstr "CommaFeed را با حساب آزمایشی امتحان کنید: دمو/دمو"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "خوانده نشده" msgstr "خوانده نشده"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "" msgstr ""
@@ -743,6 +802,14 @@ msgstr "وب سایت"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "شما هنوز هیچ اشتراکی ندارید. " msgstr "شما هنوز هیچ اشتراکی ندارید. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "فایل مورد نیاز است" msgstr "فایل مورد نیاز است"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Poista käyttäjä"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "Syötteen URL-osoite"
msgid "Feed name" msgid "Feed name"
msgstr "Syötteen nimi" msgstr "Syötteen nimi"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Suodattava lauseke" msgstr "Suodattava lauseke"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Unohditko salasanan?" msgstr "Unohditko salasanan?"
@@ -320,6 +332,10 @@ msgstr "Siirry API-dokumentaatioon."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Siirry Kaikki-näkymään" msgstr "Siirry Kaikki-näkymään"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Hyvää" msgstr "Hyvää"
@@ -348,6 +364,7 @@ msgstr "Tuo"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "Merkitse ne luetuiksi laajennetussa näkymässä vierittämällä merkintöjä" msgstr "Merkitse ne luetuiksi laajennetussa näkymässä vierittämällä merkintöjä"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Pidä lukematta" msgstr "Pidä lukematta"
@@ -397,6 +414,7 @@ msgstr "Ladataan tunnisteita..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Kirjaudu sisään" msgstr "Kirjaudu sisään"
@@ -404,6 +422,10 @@ msgstr "Kirjaudu sisään"
msgid "Logout" msgid "Logout"
msgstr "Uloskirjautuminen" msgstr "Uloskirjautuminen"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Merkitse kaikki luetuiksi"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Merkitse kaikki merkinnät luetuiksi" msgstr "Merkitse kaikki merkinnät luetuiksi"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Merkitse luetuksi" msgstr "Merkitse luetuksi"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Merkitse luetuksi tähän asti" msgstr "Merkitse luetuksi tähän asti"
@@ -430,6 +454,10 @@ msgstr "Merkitse luetuksi tähän asti"
msgid "Metrics" msgid "Metrics"
msgstr "" msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Siirrä sivua alaspäin" msgstr "Siirrä sivua alaspäin"
@@ -515,6 +543,14 @@ msgstr "Avaa nykyinen merkintä uudella välilehdellä taustalla"
msgid "Open link" msgid "Open link"
msgstr "Avaa linkki" msgstr "Avaa linkki"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Avaa seuraava merkintä" msgstr "Avaa seuraava merkintä"
@@ -581,6 +617,10 @@ msgstr "Päivitä"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Tämän CommaFeed-esiintymän rekisteröinnit on suljettu" msgstr "Tämän CommaFeed-esiintymän rekisteröinnit on suljettu"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Sivustojen jakaminen"
msgid "Shift" msgid "Shift"
msgstr "Vaihto" msgstr "Vaihto"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Näytä syötteet ja luokat ilman lukemattomia merkintöjä" msgstr "Näytä syötteet ja luokat ilman lukemattomia merkintöjä"
@@ -642,6 +690,7 @@ msgstr "Näytä pikanäppäimen ohje"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Rekisteröidy" msgstr "Rekisteröidy"
@@ -654,6 +703,7 @@ msgstr "Jotain pahaa tapahtui juuri..."
msgid "Space" msgid "Space"
msgstr "Avaruus" msgstr "Avaruus"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Tähti" msgstr "Tähti"
@@ -681,6 +731,10 @@ msgstr "Tilaa syöte"
msgid "Success" msgid "Success"
msgstr "Onnistui" msgstr "Onnistui"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Vaihda tummaan teemaan" msgstr "Vaihda tummaan teemaan"
@@ -709,10 +763,15 @@ msgstr "Vaihda nykyisen merkinnän lukutila"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Kokeile CommaFeediä demotilillä: demo/demo" msgstr "Kokeile CommaFeediä demotilillä: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Lukematon" msgstr "Lukematon"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Poista tähti" msgstr "Poista tähti"
@@ -743,6 +802,14 @@ msgstr "Verkkosivusto"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Sinulla ei ole vielä tilauksia. " msgstr "Sinulla ei ole vielä tilauksia. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "tiedosto vaaditaan" msgstr "tiedosto vaaditaan"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Effacer l'utilisateur"
msgid "Desc" msgid "Desc"
msgstr "Descendant" msgstr "Descendant"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr "Vue détaillée"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL du flux"
msgid "Feed name" msgid "Feed name"
msgstr "Nom du flux" msgstr "Nom du flux"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Expression de filtrage" msgstr "Expression de filtrage"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Mot de passe oublié ?" msgstr "Mot de passe oublié ?"
@@ -320,6 +332,10 @@ msgstr "Aller à la documentation de l'API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Aller à la catégorie Tout" msgstr "Aller à la catégorie Tout"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Extensions" msgstr "Extensions"
@@ -348,6 +364,7 @@ msgstr "Importer"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "En mode de lecture étendu, marquer les éléments comme lus lorsque la fenêtre descend." msgstr "En mode de lecture étendu, marquer les éléments comme lus lorsque la fenêtre descend."
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Garder non lu" msgstr "Garder non lu"
@@ -397,6 +414,7 @@ msgstr "Chargement des tags ..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Connexion" msgstr "Connexion"
@@ -404,6 +422,10 @@ msgstr "Connexion"
msgid "Logout" msgid "Logout"
msgstr "Déconnexion" msgstr "Déconnexion"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Tout marquer comme lu"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Marquer toutes les entrées comme lues" msgstr "Marquer toutes les entrées comme lues"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Marquer comme lu" msgstr "Marquer comme lu"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Marquer comme lu jusqu'ici" msgstr "Marquer comme lu jusqu'ici"
@@ -430,6 +454,10 @@ msgstr "Marquer comme lu jusqu'ici"
msgid "Metrics" msgid "Metrics"
msgstr "Métriques" msgstr "Métriques"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Faites défiler la page vers le bas" msgstr "Faites défiler la page vers le bas"
@@ -515,6 +543,14 @@ msgstr "Ouvrir l'entrée actuelle dans un nouvel onglet en arrière-plan"
msgid "Open link" msgid "Open link"
msgstr "Ouvrir le lien" msgstr "Ouvrir le lien"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Ouvrir l'entrée suivante" msgstr "Ouvrir l'entrée suivante"
@@ -581,6 +617,10 @@ msgstr "Rafraîchir"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Les inscriptions sont fermées sur cette instance de CommaFeed" msgstr "Les inscriptions sont fermées sur cette instance de CommaFeed"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Sites de partage"
msgid "Shift" msgid "Shift"
msgstr "Maj" msgstr "Maj"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Afficher les flux et les catégories pour lesquels tout est déjà lu" msgstr "Afficher les flux et les catégories pour lesquels tout est déjà lu"
@@ -642,6 +690,7 @@ msgstr "Montrer les raccourcis clavier"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Créer un compte" msgstr "Créer un compte"
@@ -654,6 +703,7 @@ msgstr "Quelque chose s'est mal passé..."
msgid "Space" msgid "Space"
msgstr "Espace" msgstr "Espace"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Ajouter aux favoris" msgstr "Ajouter aux favoris"
@@ -681,6 +731,10 @@ msgstr "S'abonner au flux"
msgid "Success" msgid "Success"
msgstr "Succès" msgstr "Succès"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr "Faire glisser le titre vers la droite"
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Activer le mode sombre" msgstr "Activer le mode sombre"
@@ -709,10 +763,15 @@ msgstr "Marquer l'entrée actuelle comme lue/non lue"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Essayez CommaFeed avec le compte de démonstration : demo/demo" msgstr "Essayez CommaFeed avec le compte de démonstration : demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Non lu" msgstr "Non lu"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Retirer des favoris" msgstr "Retirer des favoris"
@@ -743,6 +802,14 @@ msgstr "Site web"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Vous n'avez pas encore d'abonnements. Pourquoi ne pas essayer d'en ajouter un en cliquant sur le signe + en haut de la page ?" msgstr "Vous n'avez pas encore d'abonnements. Pourquoi ne pas essayer d'en ajouter un en cliquant sur le signe + en haut de la page ?"
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "fichier requis" msgstr "fichier requis"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Eliminar usuario"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL da fonte"
msgid "Feed name" msgid "Feed name"
msgstr "Nome do feed" msgstr "Nome do feed"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Expresión de filtrado" msgstr "Expresión de filtrado"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Esqueceches o contrasinal?" msgstr "Esqueceches o contrasinal?"
@@ -320,6 +332,10 @@ msgstr "Ir á documentación da API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Ir á vista Todos" msgstr "Ir á vista Todos"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "agasallos" msgstr "agasallos"
@@ -348,6 +364,7 @@ msgstr "Importación"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "Na vista ampliada, ao desprazarse polas entradas márcaas como lidas" msgstr "Na vista ampliada, ao desprazarse polas entradas márcaas como lidas"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Manter sen ler" msgstr "Manter sen ler"
@@ -397,6 +414,7 @@ msgstr "Cargando etiquetas..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Iniciar sesión" msgstr "Iniciar sesión"
@@ -404,6 +422,10 @@ msgstr "Iniciar sesión"
msgid "Logout" msgid "Logout"
msgstr "Pechar sesión" msgstr "Pechar sesión"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Marcar todo como lido"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Marcar todas as entradas como lidas" msgstr "Marcar todas as entradas como lidas"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Marcar como lido" msgstr "Marcar como lido"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Marcar como lido ata aquí" msgstr "Marcar como lido ata aquí"
@@ -430,6 +454,10 @@ msgstr "Marcar como lido ata aquí"
msgid "Metrics" msgid "Metrics"
msgstr "Métricas" msgstr "Métricas"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Move a páxina cara abaixo" msgstr "Move a páxina cara abaixo"
@@ -515,6 +543,14 @@ msgstr "Abre a entrada actual nunha nova pestana en segundo plano"
msgid "Open link" msgid "Open link"
msgstr "ligazón aberta" msgstr "ligazón aberta"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Abrir a seguinte entrada" msgstr "Abrir a seguinte entrada"
@@ -581,6 +617,10 @@ msgstr "Actualizar"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Os rexistros están pechados nesta instancia de CommaFeed" msgstr "Os rexistros están pechados nesta instancia de CommaFeed"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Compartir sitios"
msgid "Shift" msgid "Shift"
msgstr "quendas" msgstr "quendas"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Mostrar fontes e categorías sen entradas sen ler" msgstr "Mostrar fontes e categorías sen entradas sen ler"
@@ -642,6 +690,7 @@ msgstr "Mostrar axuda do atallo do teclado"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Rexístrese" msgstr "Rexístrese"
@@ -654,6 +703,7 @@ msgstr "Algo malo pasou..."
msgid "Space" msgid "Space"
msgstr "Espazo" msgstr "Espazo"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "estrela" msgstr "estrela"
@@ -681,6 +731,10 @@ msgstr "Subscríbete ao feed"
msgid "Success" msgid "Success"
msgstr "Éxito" msgstr "Éxito"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Cambiar ao tema escuro" msgstr "Cambiar ao tema escuro"
@@ -709,10 +763,15 @@ msgstr "alternar o estado de lectura da entrada actual"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Proba CommaFeed coa conta de demostración: demo/demo" msgstr "Proba CommaFeed coa conta de demostración: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Sen ler" msgstr "Sen ler"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Desestrela" msgstr "Desestrela"
@@ -743,6 +802,14 @@ msgstr "Páxina web"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Aínda non tes ningunha subscrición. " msgstr "Aínda non tes ningunha subscrición. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "é necesario o ficheiro" msgstr "é necesario o ficheiro"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Felhasználó törlése"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr ""
msgid "Feed name" msgid "Feed name"
msgstr "Hírcsatorna neve" msgstr "Hírcsatorna neve"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Szűrő kifejezés" msgstr "Szűrő kifejezés"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Elfelejtette a jelszavát?" msgstr "Elfelejtette a jelszavát?"
@@ -320,6 +332,10 @@ msgstr "Nyissa meg az API dokumentációját."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Lépjen az Összes nézetre" msgstr "Lépjen az Összes nézetre"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Jók" msgstr "Jók"
@@ -348,6 +364,7 @@ msgstr "Importálás"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "Kibontott nézetben a bejegyzések görgetése olvasottként jelöli meg őket" msgstr "Kibontott nézetben a bejegyzések görgetése olvasottként jelöli meg őket"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Olvasatlan marad" msgstr "Olvasatlan marad"
@@ -397,6 +414,7 @@ msgstr "Címkék betöltése..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Jelentkezzen be" msgstr "Jelentkezzen be"
@@ -404,6 +422,10 @@ msgstr "Jelentkezzen be"
msgid "Logout" msgid "Logout"
msgstr "Kijelentkezés" msgstr "Kijelentkezés"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Minden megjelölése olvasottként"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Minden bejegyzés megjelölése olvasottként" msgstr "Minden bejegyzés megjelölése olvasottként"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Megjelölés olvasottként" msgstr "Megjelölés olvasottként"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Megjelölés idáig olvasottként" msgstr "Megjelölés idáig olvasottként"
@@ -430,6 +454,10 @@ msgstr "Megjelölés idáig olvasottként"
msgid "Metrics" msgid "Metrics"
msgstr "" msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Mozgassa le az oldalt" msgstr "Mozgassa le az oldalt"
@@ -515,6 +543,14 @@ msgstr "Az aktuális bejegyzés megnyitása egy új lapon a háttérben"
msgid "Open link" msgid "Open link"
msgstr "Link megnyitása" msgstr "Link megnyitása"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Következő bejegyzés megnyitása" msgstr "Következő bejegyzés megnyitása"
@@ -581,6 +617,10 @@ msgstr "Frissítés"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "A regisztrációk le vannak zárva ezen a CommaFeed példányon" msgstr "A regisztrációk le vannak zárva ezen a CommaFeed példányon"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Webhelyek megosztása"
msgid "Shift" msgid "Shift"
msgstr "" msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Hírcsatornák és kategóriák megjelenítése olvasatlan bejegyzések nélkül" msgstr "Hírcsatornák és kategóriák megjelenítése olvasatlan bejegyzések nélkül"
@@ -642,6 +690,7 @@ msgstr "A billentyűparancsok súgójának megjelenítése"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Regisztráljon" msgstr "Regisztráljon"
@@ -654,6 +703,7 @@ msgstr "Valami rossz történt..."
msgid "Space" msgid "Space"
msgstr "" msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Csillag" msgstr "Csillag"
@@ -681,6 +731,10 @@ msgstr "Feliratkozás a hírfolyamra"
msgid "Success" msgid "Success"
msgstr "Siker" msgstr "Siker"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Váltás sötét témára" msgstr "Váltás sötét témára"
@@ -709,10 +763,15 @@ msgstr "Az aktuális bejegyzés olvasási állapotának váltása"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Próbálja ki a CommaFeed-et a demo fiókkal: demo/demo" msgstr "Próbálja ki a CommaFeed-et a demo fiókkal: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Olvasatlan" msgstr "Olvasatlan"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "" msgstr ""
@@ -743,6 +802,14 @@ msgstr "Webhely"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Még nincs előfizetése. " msgstr "Még nincs előfizetése. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "fájl szükséges" msgstr "fájl szükséges"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Hapus pengguna"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL Umpan"
msgid "Feed name" msgid "Feed name"
msgstr "Nama umpan" msgstr "Nama umpan"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Memfilter ekspresi" msgstr "Memfilter ekspresi"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Lupa kata sandi?" msgstr "Lupa kata sandi?"
@@ -320,6 +332,10 @@ msgstr "Buka dokumentasi API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Pergi ke tampilan Semua" msgstr "Pergi ke tampilan Semua"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Pernak-pernik" msgstr "Pernak-pernik"
@@ -348,6 +364,7 @@ msgstr "Impor"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "Dalam tampilan yang diperluas, menggulir entri menandainya sebagai telah dibaca" msgstr "Dalam tampilan yang diperluas, menggulir entri menandainya sebagai telah dibaca"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Tetap belum dibaca" msgstr "Tetap belum dibaca"
@@ -397,6 +414,7 @@ msgstr "Memuat tag..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Masuk" msgstr "Masuk"
@@ -404,6 +422,10 @@ msgstr "Masuk"
msgid "Logout" msgid "Logout"
msgstr "Keluar" msgstr "Keluar"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Tandai semua sebagai telah dibaca"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Tandai semua entri sebagai telah dibaca" msgstr "Tandai semua entri sebagai telah dibaca"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Tandai sebagai telah dibaca" msgstr "Tandai sebagai telah dibaca"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Tandai sebagai telah dibaca sampai di sini" msgstr "Tandai sebagai telah dibaca sampai di sini"
@@ -430,6 +454,10 @@ msgstr "Tandai sebagai telah dibaca sampai di sini"
msgid "Metrics" msgid "Metrics"
msgstr "Metrik" msgstr "Metrik"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Pindahkan halaman ke bawah" msgstr "Pindahkan halaman ke bawah"
@@ -515,6 +543,14 @@ msgstr "Buka entri saat ini di tab baru di latar belakang"
msgid "Open link" msgid "Open link"
msgstr "Buka tautan" msgstr "Buka tautan"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Buka entri berikutnya" msgstr "Buka entri berikutnya"
@@ -581,6 +617,10 @@ msgstr "Segarkan"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Pendaftaran ditutup pada instans CommaFeed ini" msgstr "Pendaftaran ditutup pada instans CommaFeed ini"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Berbagi situs"
msgid "Shift" msgid "Shift"
msgstr "Pergeseran" msgstr "Pergeseran"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Tampilkan umpan dan kategori tanpa entri yang belum dibaca" msgstr "Tampilkan umpan dan kategori tanpa entri yang belum dibaca"
@@ -642,6 +690,7 @@ msgstr "Tampilkan bantuan pintasan keyboard"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Daftar" msgstr "Daftar"
@@ -654,6 +703,7 @@ msgstr "Sesuatu yang buruk baru saja terjadi..."
msgid "Space" msgid "Space"
msgstr "Luar Angkasa" msgstr "Luar Angkasa"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Bintang" msgstr "Bintang"
@@ -681,6 +731,10 @@ msgstr "Berlangganan umpan"
msgid "Success" msgid "Success"
msgstr "Sukses" msgstr "Sukses"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Beralih ke tema gelap" msgstr "Beralih ke tema gelap"
@@ -709,10 +763,15 @@ msgstr "Beralih status baca entri saat ini"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Cobalah CommaFeed dengan akun demo: demo/demo" msgstr "Cobalah CommaFeed dengan akun demo: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Belum Dibaca" msgstr "Belum Dibaca"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Hapus bintang" msgstr "Hapus bintang"
@@ -743,6 +802,14 @@ msgstr "Situs Web"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Anda belum memiliki langganan. " msgstr "Anda belum memiliki langganan. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "file diperlukan" msgstr "file diperlukan"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Elimina utente"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL feed"
msgid "Feed name" msgid "Feed name"
msgstr "Nome del feed" msgstr "Nome del feed"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Espressione filtrante" msgstr "Espressione filtrante"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Password dimenticata?" msgstr "Password dimenticata?"
@@ -320,6 +332,10 @@ msgstr "Vai alla documentazione dell'API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Vai alla vista Tutto" msgstr "Vai alla vista Tutto"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Chicche" msgstr "Chicche"
@@ -348,6 +364,7 @@ msgstr "Importa"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "Nella vista espansa, scorrendo le voci contrassegnale come lette" msgstr "Nella vista espansa, scorrendo le voci contrassegnale come lette"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Mantieni non letto" msgstr "Mantieni non letto"
@@ -397,6 +414,7 @@ msgstr "Caricamento tag..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Accedi" msgstr "Accedi"
@@ -404,6 +422,10 @@ msgstr "Accedi"
msgid "Logout" msgid "Logout"
msgstr "Disconnessione" msgstr "Disconnessione"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Contrassegna tutto come letto"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Contrassegna tutte le voci come lette" msgstr "Contrassegna tutte le voci come lette"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Contrassegna come letto" msgstr "Contrassegna come letto"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Contrassegna come letto fino a qui" msgstr "Contrassegna come letto fino a qui"
@@ -430,6 +454,10 @@ msgstr "Contrassegna come letto fino a qui"
msgid "Metrics" msgid "Metrics"
msgstr "Metriche" msgstr "Metriche"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Sposta la pagina in basso" msgstr "Sposta la pagina in basso"
@@ -515,6 +543,14 @@ msgstr "Apri la voce corrente in una nuova scheda in background"
msgid "Open link" msgid "Open link"
msgstr "Apri collegamento" msgstr "Apri collegamento"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Apri voce successiva" msgstr "Apri voce successiva"
@@ -581,6 +617,10 @@ msgstr "Aggiorna"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Le registrazioni sono chiuse su questa istanza CommaFeed" msgstr "Le registrazioni sono chiuse su questa istanza CommaFeed"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Condivisione di siti"
msgid "Shift" msgid "Shift"
msgstr "Cambio" msgstr "Cambio"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Mostra feed e categorie senza voci non lette" msgstr "Mostra feed e categorie senza voci non lette"
@@ -642,6 +690,7 @@ msgstr "Mostra la guida alle scorciatoie da tastiera"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Iscriviti" msgstr "Iscriviti"
@@ -654,6 +703,7 @@ msgstr "È appena successo qualcosa di brutto..."
msgid "Space" msgid "Space"
msgstr "Spazio" msgstr "Spazio"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Stella" msgstr "Stella"
@@ -681,6 +731,10 @@ msgstr "Iscriviti al feed"
msgid "Success" msgid "Success"
msgstr "Successo" msgstr "Successo"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Passa al tema scuro" msgstr "Passa al tema scuro"
@@ -709,10 +763,15 @@ msgstr "Commuta lo stato di lettura della voce corrente"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Prova CommaFeed con il conto demo: demo/demo" msgstr "Prova CommaFeed con il conto demo: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Non letto" msgstr "Non letto"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Elimina le stelle" msgstr "Elimina le stelle"
@@ -743,6 +802,14 @@ msgstr "Sito web"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Non hai ancora abbonamenti. " msgstr "Non hai ancora abbonamenti. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "è richiesto il file" msgstr "è richiesto il file"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "ユーザーの削除"
msgid "Desc" msgid "Desc"
msgstr "説明" msgstr "説明"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "フィード URL"
msgid "Feed name" msgid "Feed name"
msgstr "フィード名" msgstr "フィード名"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "フィルタリング式" msgstr "フィルタリング式"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "パスワードをお忘れですか?" msgstr "パスワードをお忘れですか?"
@@ -320,6 +332,10 @@ msgstr "API ドキュメントに移動します。"
msgid "Go to the All view" msgid "Go to the All view"
msgstr "すべてのビューに移動" msgstr "すべてのビューに移動"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "グッディーズ" msgstr "グッディーズ"
@@ -348,6 +364,7 @@ msgstr "インポート"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "展開ビューでエントリをスクロールすると、それらが既読としてマークされます" msgstr "展開ビューでエントリをスクロールすると、それらが既読としてマークされます"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "未読のままにする" msgstr "未読のままにする"
@@ -397,6 +414,7 @@ msgstr "タグを読み込んでいます..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "ログイン" msgstr "ログイン"
@@ -404,6 +422,10 @@ msgstr "ログイン"
msgid "Logout" msgid "Logout"
msgstr "ログアウト" msgstr "ログアウト"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "すべて既読にする"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "すべてのエントリを既読にする" msgstr "すべてのエントリを既読にする"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "既読にする" msgstr "既読にする"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "ここまで既読にする" msgstr "ここまで既読にする"
@@ -430,6 +454,10 @@ msgstr "ここまで既読にする"
msgid "Metrics" msgid "Metrics"
msgstr "メトリクス" msgstr "メトリクス"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "ページを下に移動" msgstr "ページを下に移動"
@@ -515,6 +543,14 @@ msgstr "現在のエントリをバックグラウンドで新しいタブで開
msgid "Open link" msgid "Open link"
msgstr "リンクを開く" msgstr "リンクを開く"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "次のエントリを開く" msgstr "次のエントリを開く"
@@ -581,6 +617,10 @@ msgstr "リフレッシュ"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "この CommaFeed インスタンスの登録は終了しています" msgstr "この CommaFeed インスタンスの登録は終了しています"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "共有サイト"
msgid "Shift" msgid "Shift"
msgstr "シフト" msgstr "シフト"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "未読エントリのないフィードとカテゴリを表示する" msgstr "未読エントリのないフィードとカテゴリを表示する"
@@ -642,6 +690,7 @@ msgstr "キーボード ショートカットのヘルプを表示"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "サインアップ" msgstr "サインアップ"
@@ -654,6 +703,7 @@ msgstr "何か悪いことが起こった..."
msgid "Space" msgid "Space"
msgstr "スペース" msgstr "スペース"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "スター" msgstr "スター"
@@ -681,6 +731,10 @@ msgstr "フィードを購読する"
msgid "Success" msgid "Success"
msgstr "成功" msgstr "成功"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "ダークテーマに切り替え" msgstr "ダークテーマに切り替え"
@@ -709,10 +763,15 @@ msgstr "現在のエントリの読み取りステータスを切り替えます
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "デモアカウントで CommaFeed を試す: demo/demo" msgstr "デモアカウントで CommaFeed を試す: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "未読" msgstr "未読"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "スターを外す" msgstr "スターを外す"
@@ -743,6 +802,14 @@ msgstr "ウェブサイト"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "まだサブスクリプションがありません。" msgstr "まだサブスクリプションがありません。"
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "ファイルが必要です" msgstr "ファイルが必要です"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "사용자 삭제"
msgid "Desc" msgid "Desc"
msgstr "설명" msgstr "설명"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "피드 URL"
msgid "Feed name" msgid "Feed name"
msgstr "피드 이름" msgstr "피드 이름"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "필터링 표현식" msgstr "필터링 표현식"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "비밀번호를 잊으셨나요?" msgstr "비밀번호를 잊으셨나요?"
@@ -320,6 +332,10 @@ msgstr "API 문서로 이동합니다."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "전체 보기로 이동" msgstr "전체 보기로 이동"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "굿즈" msgstr "굿즈"
@@ -348,6 +364,7 @@ msgstr "가져오기"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "확장 보기에서 항목을 스크롤하면 읽은 것으로 표시됩니다." msgstr "확장 보기에서 항목을 스크롤하면 읽은 것으로 표시됩니다."
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "읽지 않은 상태로 유지" msgstr "읽지 않은 상태로 유지"
@@ -397,6 +414,7 @@ msgstr "태그 로드 중..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "로그인" msgstr "로그인"
@@ -404,6 +422,10 @@ msgstr "로그인"
msgid "Logout" msgid "Logout"
msgstr "로그아웃" msgstr "로그아웃"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "모두 읽은 상태로 표시"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "모든 항목을 읽은 상태로 표시" msgstr "모든 항목을 읽은 상태로 표시"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "읽은 상태로 표시" msgstr "읽은 상태로 표시"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "여기까지 읽은 것으로 표시" msgstr "여기까지 읽은 것으로 표시"
@@ -430,6 +454,10 @@ msgstr "여기까지 읽은 것으로 표시"
msgid "Metrics" msgid "Metrics"
msgstr "메트릭스" msgstr "메트릭스"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "페이지를 아래로 이동" msgstr "페이지를 아래로 이동"
@@ -515,6 +543,14 @@ msgstr "배경의 새 탭에서 현재 항목 열기"
msgid "Open link" msgid "Open link"
msgstr "링크 열기" msgstr "링크 열기"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "다음 항목 열기" msgstr "다음 항목 열기"
@@ -581,6 +617,10 @@ msgstr "새로 고침"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "이 CommaFeed 인스턴스에 대한 등록이 마감되었습니다." msgstr "이 CommaFeed 인스턴스에 대한 등록이 마감되었습니다."
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "사이트 공유"
msgid "Shift" msgid "Shift"
msgstr "시프트" msgstr "시프트"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "읽지 않은 항목이 없는 피드 및 카테고리 표시" msgstr "읽지 않은 항목이 없는 피드 및 카테고리 표시"
@@ -642,6 +690,7 @@ msgstr "키보드 단축키 도움말 표시"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "가입" msgstr "가입"
@@ -654,6 +703,7 @@ msgstr "뭔가 안 좋은 일이 일어났어..."
msgid "Space" msgid "Space"
msgstr "우주" msgstr "우주"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "스타" msgstr "스타"
@@ -681,6 +731,10 @@ msgstr "피드 구독"
msgid "Success" msgid "Success"
msgstr "성공" msgstr "성공"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "어두운 테마로 전환" msgstr "어두운 테마로 전환"
@@ -709,10 +763,15 @@ msgstr "현재 항목의 읽기 상태 전환"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "데모 계정으로 CommaFeed를 사용해 보세요: demo/demo" msgstr "데모 계정으로 CommaFeed를 사용해 보세요: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "읽지 않음" msgstr "읽지 않음"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "별표 제거" msgstr "별표 제거"
@@ -743,6 +802,14 @@ msgstr "웹사이트"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "아직 구독이 없습니다. " msgstr "아직 구독이 없습니다. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "파일이 필요합니다" msgstr "파일이 필요합니다"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Padam pengguna"
msgid "Desc" msgid "Desc"
msgstr "Dec" msgstr "Dec"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL Suapan"
msgid "Feed name" msgid "Feed name"
msgstr "Nama suapan" msgstr "Nama suapan"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Ungkapan penapisan" msgstr "Ungkapan penapisan"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Lupa kata laluan?" msgstr "Lupa kata laluan?"
@@ -320,6 +332,10 @@ msgstr "Pergi ke dokumentasi API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Pergi ke paparan Semua" msgstr "Pergi ke paparan Semua"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "" msgstr ""
@@ -348,6 +364,7 @@ msgstr ""
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "Dalam paparan yang diperluas, menatal melalui entri menandakannya sebagai dibaca" msgstr "Dalam paparan yang diperluas, menatal melalui entri menandakannya sebagai dibaca"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Teruskan tidak dibaca" msgstr "Teruskan tidak dibaca"
@@ -397,6 +414,7 @@ msgstr "Memuatkan tag..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Log masuk" msgstr "Log masuk"
@@ -404,6 +422,10 @@ msgstr "Log masuk"
msgid "Logout" msgid "Logout"
msgstr "Log Keluar" msgstr "Log Keluar"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Tandai semua sebagai dibaca"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Tandai semua entri sebagai dibaca" msgstr "Tandai semua entri sebagai dibaca"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Tandakan sebagai dibaca" msgstr "Tandakan sebagai dibaca"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Tandai sebagai dibaca sehingga di sini" msgstr "Tandai sebagai dibaca sehingga di sini"
@@ -430,6 +454,10 @@ msgstr "Tandai sebagai dibaca sehingga di sini"
msgid "Metrics" msgid "Metrics"
msgstr "Metrik" msgstr "Metrik"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Gerakkan halaman ke bawah" msgstr "Gerakkan halaman ke bawah"
@@ -515,6 +543,14 @@ msgstr "Buka entri semasa dalam tab baharu di latar belakang"
msgid "Open link" msgid "Open link"
msgstr "Buka pautan" msgstr "Buka pautan"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Buka entri seterusnya" msgstr "Buka entri seterusnya"
@@ -581,6 +617,10 @@ msgstr "Muat semula"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Pendaftaran ditutup pada contoh CommaFeed ini" msgstr "Pendaftaran ditutup pada contoh CommaFeed ini"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Berkongsi tapak"
msgid "Shift" msgid "Shift"
msgstr "Anjakan" msgstr "Anjakan"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Tunjukkan suapan dan kategori tanpa entri yang belum dibaca" msgstr "Tunjukkan suapan dan kategori tanpa entri yang belum dibaca"
@@ -642,6 +690,7 @@ msgstr "Tunjukkan bantuan pintasan papan kekunci"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Daftar" msgstr "Daftar"
@@ -654,6 +703,7 @@ msgstr "Sesuatu yang buruk baru saja berlaku..."
msgid "Space" msgid "Space"
msgstr "Angkasa" msgstr "Angkasa"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Bintang" msgstr "Bintang"
@@ -681,6 +731,10 @@ msgstr "Langgan suapan"
msgid "Success" msgid "Success"
msgstr "Kejayaan" msgstr "Kejayaan"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Tukar kepada tema gelap" msgstr "Tukar kepada tema gelap"
@@ -709,10 +763,15 @@ msgstr "Togol status bacaan entri semasa"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Cuba CommaFeed dengan akaun demo: demo/demo" msgstr "Cuba CommaFeed dengan akaun demo: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Belum dibaca" msgstr "Belum dibaca"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Nyahbintang" msgstr "Nyahbintang"
@@ -743,6 +802,14 @@ msgstr "Laman web"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Anda belum mempunyai sebarang langganan lagi. " msgstr "Anda belum mempunyai sebarang langganan lagi. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "fail diperlukan" msgstr "fail diperlukan"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Slett bruker"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "Feed-URL"
msgid "Feed name" msgid "Feed name"
msgstr "Feednavn" msgstr "Feednavn"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtrerende uttrykk" msgstr "Filtrerende uttrykk"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Glemt passord?" msgstr "Glemt passord?"
@@ -320,6 +332,10 @@ msgstr "Gå til API-dokumentasjonen."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Gå til visningen Alle" msgstr "Gå til visningen Alle"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Godbiter" msgstr "Godbiter"
@@ -348,6 +364,7 @@ msgstr ""
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "I utvidet visning merker du dem som lest ved å rulle gjennom oppføringer" msgstr "I utvidet visning merker du dem som lest ved å rulle gjennom oppføringer"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Behold ulest" msgstr "Behold ulest"
@@ -397,6 +414,7 @@ msgstr "Laster tagger..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Logg inn" msgstr "Logg inn"
@@ -404,6 +422,10 @@ msgstr "Logg inn"
msgid "Logout" msgid "Logout"
msgstr "Logg ut" msgstr "Logg ut"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Merk alle som lest"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Merk alle oppføringer som lest" msgstr "Merk alle oppføringer som lest"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Merk som lest" msgstr "Merk som lest"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Merk som lest frem til her" msgstr "Merk som lest frem til her"
@@ -430,6 +454,10 @@ msgstr "Merk som lest frem til her"
msgid "Metrics" msgid "Metrics"
msgstr "Beregninger" msgstr "Beregninger"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Flytt siden ned" msgstr "Flytt siden ned"
@@ -515,6 +543,14 @@ msgstr "Åpne gjeldende oppføring i en ny fane i bakgrunnen"
msgid "Open link" msgid "Open link"
msgstr "Åpen lenke" msgstr "Åpen lenke"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Åpne neste oppføring" msgstr "Åpne neste oppføring"
@@ -581,6 +617,10 @@ msgstr "Oppdater"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Registreringer er stengt på denne CommaFeed-forekomsten" msgstr "Registreringer er stengt på denne CommaFeed-forekomsten"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Delingssider"
msgid "Shift" msgid "Shift"
msgstr "Skift" msgstr "Skift"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Vis feeder og kategorier uten uleste oppføringer" msgstr "Vis feeder og kategorier uten uleste oppføringer"
@@ -642,6 +690,7 @@ msgstr "Vis hurtigtasthjelp"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Meld deg på" msgstr "Meld deg på"
@@ -654,6 +703,7 @@ msgstr "Noe ille skjedde akkurat..."
msgid "Space" msgid "Space"
msgstr "" msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Stjerne" msgstr "Stjerne"
@@ -681,6 +731,10 @@ msgstr "Abonner på feeden"
msgid "Success" msgid "Success"
msgstr "Suksess" msgstr "Suksess"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Bytt til mørkt tema" msgstr "Bytt til mørkt tema"
@@ -709,10 +763,15 @@ msgstr "Veksle lesestatus for gjeldende oppføring"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Prøv CommaFeed med demokontoen: demo/demo" msgstr "Prøv CommaFeed med demokontoen: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Ulest" msgstr "Ulest"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Fjern stjerne" msgstr "Fjern stjerne"
@@ -743,6 +802,14 @@ msgstr "Nettsted"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Du har ingen abonnementer ennå. " msgstr "Du har ingen abonnementer ennå. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "fil kreves" msgstr "fil kreves"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Gebruiker verwijderen"
msgid "Desc" msgid "Desc"
msgstr "Beschrijving" msgstr "Beschrijving"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "Feed-URL"
msgid "Feed name" msgid "Feed name"
msgstr "Feednaam" msgstr "Feednaam"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Uitdrukking filteren" msgstr "Uitdrukking filteren"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Wachtwoord vergeten?" msgstr "Wachtwoord vergeten?"
@@ -320,6 +332,10 @@ msgstr "Ga naar de API-documentatie."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Ga naar de weergave Alles" msgstr "Ga naar de weergave Alles"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Goederen" msgstr "Goederen"
@@ -348,6 +364,7 @@ msgstr ""
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "In de uitgevouwen weergave markeert het scrollen door items ze als gelezen" msgstr "In de uitgevouwen weergave markeert het scrollen door items ze als gelezen"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Ongelezen houden" msgstr "Ongelezen houden"
@@ -397,6 +414,7 @@ msgstr "Tags laden..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Inloggen" msgstr "Inloggen"
@@ -404,6 +422,10 @@ msgstr "Inloggen"
msgid "Logout" msgid "Logout"
msgstr "Uitloggen" msgstr "Uitloggen"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Alles markeren als gelezen"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Markeer alle vermeldingen als gelezen" msgstr "Markeer alle vermeldingen als gelezen"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Markeren als gelezen" msgstr "Markeren als gelezen"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Markeer als gelezen tot hier" msgstr "Markeer als gelezen tot hier"
@@ -430,6 +454,10 @@ msgstr "Markeer als gelezen tot hier"
msgid "Metrics" msgid "Metrics"
msgstr "Metrieken" msgstr "Metrieken"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Verplaats de pagina naar beneden" msgstr "Verplaats de pagina naar beneden"
@@ -515,6 +543,14 @@ msgstr "Open huidig item in een nieuw tabblad op de achtergrond"
msgid "Open link" msgid "Open link"
msgstr "Link openen" msgstr "Link openen"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Volgende invoer openen" msgstr "Volgende invoer openen"
@@ -581,6 +617,10 @@ msgstr "Vernieuwen"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Registraties zijn gesloten op deze CommaFeed-instantie" msgstr "Registraties zijn gesloten op deze CommaFeed-instantie"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Sites delen"
msgid "Shift" msgid "Shift"
msgstr "" msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Toon feeds en categorieën zonder ongelezen items" msgstr "Toon feeds en categorieën zonder ongelezen items"
@@ -642,6 +690,7 @@ msgstr "Toon hulp bij sneltoetsen"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Aanmelden" msgstr "Aanmelden"
@@ -654,6 +703,7 @@ msgstr "Er is net iets ergs gebeurd..."
msgid "Space" msgid "Space"
msgstr "Ruimte" msgstr "Ruimte"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Ster" msgstr "Ster"
@@ -681,6 +731,10 @@ msgstr "Abonneer je op de feed"
msgid "Success" msgid "Success"
msgstr "Succes" msgstr "Succes"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Overschakelen naar donker thema" msgstr "Overschakelen naar donker thema"
@@ -709,10 +763,15 @@ msgstr "Toggle leesstatus van huidige invoer"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Probeer CommaFeed uit met het demo-account: demo/demo" msgstr "Probeer CommaFeed uit met het demo-account: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Ongelezen" msgstr "Ongelezen"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Sterren uit" msgstr "Sterren uit"
@@ -743,6 +802,14 @@ msgstr ""
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Je hebt nog geen abonnementen. " msgstr "Je hebt nog geen abonnementen. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "bestand is vereist" msgstr "bestand is vereist"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Slett bruker"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "Feed-URL"
msgid "Feed name" msgid "Feed name"
msgstr "Feednavn" msgstr "Feednavn"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtrerende uttrykk" msgstr "Filtrerende uttrykk"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Glemt passord?" msgstr "Glemt passord?"
@@ -320,6 +332,10 @@ msgstr "Gå til API-dokumentasjonen."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Gå til visningen Alle" msgstr "Gå til visningen Alle"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Godbiter" msgstr "Godbiter"
@@ -348,6 +364,7 @@ msgstr ""
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "I utvidet visning merker du dem som lest ved å rulle gjennom oppføringer" msgstr "I utvidet visning merker du dem som lest ved å rulle gjennom oppføringer"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Behold ulest" msgstr "Behold ulest"
@@ -397,6 +414,7 @@ msgstr "Laster tagger..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Logg inn" msgstr "Logg inn"
@@ -404,6 +422,10 @@ msgstr "Logg inn"
msgid "Logout" msgid "Logout"
msgstr "Logg ut" msgstr "Logg ut"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Merk alle som lest"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Merk alle oppføringer som lest" msgstr "Merk alle oppføringer som lest"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Merk som lest" msgstr "Merk som lest"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Merk som lest frem til her" msgstr "Merk som lest frem til her"
@@ -430,6 +454,10 @@ msgstr "Merk som lest frem til her"
msgid "Metrics" msgid "Metrics"
msgstr "Beregninger" msgstr "Beregninger"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Flytt siden ned" msgstr "Flytt siden ned"
@@ -515,6 +543,14 @@ msgstr "Åpne gjeldende oppføring i en ny fane i bakgrunnen"
msgid "Open link" msgid "Open link"
msgstr "Åpen lenke" msgstr "Åpen lenke"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Åpne neste oppføring" msgstr "Åpne neste oppføring"
@@ -581,6 +617,10 @@ msgstr "Oppdater"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Registreringer er stengt på denne CommaFeed-forekomsten" msgstr "Registreringer er stengt på denne CommaFeed-forekomsten"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Delingssider"
msgid "Shift" msgid "Shift"
msgstr "Skift" msgstr "Skift"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Vis feeder og kategorier uten uleste oppføringer" msgstr "Vis feeder og kategorier uten uleste oppføringer"
@@ -642,6 +690,7 @@ msgstr "Vis hurtigtasthjelp"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Meld deg på" msgstr "Meld deg på"
@@ -654,6 +703,7 @@ msgstr "Noe ille skjedde akkurat..."
msgid "Space" msgid "Space"
msgstr "" msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Stjerne" msgstr "Stjerne"
@@ -681,6 +731,10 @@ msgstr "Abonner på feeden"
msgid "Success" msgid "Success"
msgstr "Suksess" msgstr "Suksess"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Bytt til mørkt tema" msgstr "Bytt til mørkt tema"
@@ -709,10 +763,15 @@ msgstr "Veksle lesestatus for gjeldende oppføring"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Prøv CommaFeed med demokontoen: demo/demo" msgstr "Prøv CommaFeed med demokontoen: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Ulest" msgstr "Ulest"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Fjern stjerne" msgstr "Fjern stjerne"
@@ -743,6 +802,14 @@ msgstr "Nettsted"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Du har ingen abonnementer ennå. " msgstr "Du har ingen abonnementer ennå. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "fil kreves" msgstr "fil kreves"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Usuń użytkownika"
msgid "Desc" msgid "Desc"
msgstr "Opis" msgstr "Opis"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL kanału"
msgid "Feed name" msgid "Feed name"
msgstr "nazwa kanału" msgstr "nazwa kanału"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Wyrażenie filtrujące" msgstr "Wyrażenie filtrujące"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Zapomniałeś hasła?" msgstr "Zapomniałeś hasła?"
@@ -320,6 +332,10 @@ msgstr "Przejdź do dokumentacji API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Przejdź do widoku Wszystkie" msgstr "Przejdź do widoku Wszystkie"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Gadżety" msgstr "Gadżety"
@@ -348,6 +364,7 @@ msgstr ""
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "W widoku rozszerzonym przewijanie wpisów oznacza je jako przeczytane" msgstr "W widoku rozszerzonym przewijanie wpisów oznacza je jako przeczytane"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Nie przeczytaj" msgstr "Nie przeczytaj"
@@ -397,6 +414,7 @@ msgstr "Ładowanie tagów..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Zaloguj się" msgstr "Zaloguj się"
@@ -404,6 +422,10 @@ msgstr "Zaloguj się"
msgid "Logout" msgid "Logout"
msgstr "Wyloguj" msgstr "Wyloguj"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Oznacz wszystko jako przeczytane"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Oznacz wszystkie wpisy jako przeczytane" msgstr "Oznacz wszystkie wpisy jako przeczytane"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Oznacz jako przeczytane" msgstr "Oznacz jako przeczytane"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Oznacz jako przeczytane do tej pory" msgstr "Oznacz jako przeczytane do tej pory"
@@ -430,6 +454,10 @@ msgstr "Oznacz jako przeczytane do tej pory"
msgid "Metrics" msgid "Metrics"
msgstr "Metryki" msgstr "Metryki"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Przesuń stronę w dół" msgstr "Przesuń stronę w dół"
@@ -515,6 +543,14 @@ msgstr "Otwórz bieżący wpis w nowej karcie w tle"
msgid "Open link" msgid "Open link"
msgstr "Otwórz link" msgstr "Otwórz link"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Otwórz następny wpis" msgstr "Otwórz następny wpis"
@@ -581,6 +617,10 @@ msgstr "Odśwież"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Rejestracje są zamknięte w tej instancji CommaFeed" msgstr "Rejestracje są zamknięte w tej instancji CommaFeed"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Udostępnianie witryn"
msgid "Shift" msgid "Shift"
msgstr "zmiana" msgstr "zmiana"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Pokaż kanały i kategorie bez nieprzeczytanych wpisów" msgstr "Pokaż kanały i kategorie bez nieprzeczytanych wpisów"
@@ -642,6 +690,7 @@ msgstr "Pokaż pomoc dotyczącą skrótów klawiaturowych"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Zarejestruj się" msgstr "Zarejestruj się"
@@ -654,6 +703,7 @@ msgstr "Coś złego właśnie się stało..."
msgid "Space" msgid "Space"
msgstr "Przestrzeń" msgstr "Przestrzeń"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Gwiazda" msgstr "Gwiazda"
@@ -681,6 +731,10 @@ msgstr "Subskrybuj kanał"
msgid "Success" msgid "Success"
msgstr "Sukces" msgstr "Sukces"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Przełącz na ciemny motyw" msgstr "Przełącz na ciemny motyw"
@@ -709,10 +763,15 @@ msgstr "Przełącz stan odczytu bieżącego wpisu"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Wypróbuj CommaFeed z kontem demo: demo/demo" msgstr "Wypróbuj CommaFeed z kontem demo: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Nieprzeczytane" msgstr "Nieprzeczytane"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "" msgstr ""
@@ -743,6 +802,14 @@ msgstr "Strona internetowa"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Nie masz jeszcze żadnych subskrypcji. " msgstr "Nie masz jeszcze żadnych subskrypcji. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "plik jest wymagany" msgstr "plik jest wymagany"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Excluir usuário"
msgid "Desc" msgid "Desc"
msgstr "Descrição" msgstr "Descrição"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL do feed"
msgid "Feed name" msgid "Feed name"
msgstr "Nome do feed" msgstr "Nome do feed"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtrando expressão" msgstr "Filtrando expressão"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Esqueceu a senha?" msgstr "Esqueceu a senha?"
@@ -320,6 +332,10 @@ msgstr "Vá para a documentação da API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Ir para a visualização Tudo" msgstr "Ir para a visualização Tudo"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Brindes" msgstr "Brindes"
@@ -348,6 +364,7 @@ msgstr "Importar"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "Na visualização expandida, rolar pelas entradas marca-as como lidas" msgstr "Na visualização expandida, rolar pelas entradas marca-as como lidas"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Manter não lido" msgstr "Manter não lido"
@@ -397,6 +414,7 @@ msgstr "Carregando tags..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Entrar" msgstr "Entrar"
@@ -404,6 +422,10 @@ msgstr "Entrar"
msgid "Logout" msgid "Logout"
msgstr "Sair" msgstr "Sair"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Marcar todos como lidos"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Marcar todas as entradas como lidas" msgstr "Marcar todas as entradas como lidas"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Marcar como lido" msgstr "Marcar como lido"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Marcar como lido até aqui" msgstr "Marcar como lido até aqui"
@@ -430,6 +454,10 @@ msgstr "Marcar como lido até aqui"
msgid "Metrics" msgid "Metrics"
msgstr "Métricas" msgstr "Métricas"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Mova a página para baixo" msgstr "Mova a página para baixo"
@@ -515,6 +543,14 @@ msgstr "Abrir a entrada atual em uma nova aba em segundo plano"
msgid "Open link" msgid "Open link"
msgstr "Abrir link" msgstr "Abrir link"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Abrir próxima entrada" msgstr "Abrir próxima entrada"
@@ -581,6 +617,10 @@ msgstr "Atualizar"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Os registros estão fechados nesta instância do CommaFeed" msgstr "Os registros estão fechados nesta instância do CommaFeed"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Compartilhando sites"
msgid "Shift" msgid "Shift"
msgstr "Mudar" msgstr "Mudar"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Mostrar feeds e categorias sem entradas não lidas" msgstr "Mostrar feeds e categorias sem entradas não lidas"
@@ -642,6 +690,7 @@ msgstr "Mostrar ajuda de atalho de teclado"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Inscreva-se" msgstr "Inscreva-se"
@@ -654,6 +703,7 @@ msgstr "Algo ruim acabou de acontecer..."
msgid "Space" msgid "Space"
msgstr "Espaço" msgstr "Espaço"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Estrela" msgstr "Estrela"
@@ -681,6 +731,10 @@ msgstr "Inscrever-se no feed"
msgid "Success" msgid "Success"
msgstr "Sucesso" msgstr "Sucesso"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Mudar para tema escuro" msgstr "Mudar para tema escuro"
@@ -709,10 +763,15 @@ msgstr "Alternar o status de leitura da entrada atual"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Experimente o CommaFeed com a conta demo: demo/demo" msgstr "Experimente o CommaFeed com a conta demo: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Não lido" msgstr "Não lido"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Desestrelar" msgstr "Desestrelar"
@@ -743,6 +802,14 @@ msgstr "Site"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Você ainda não tem nenhuma assinatura. " msgstr "Você ainda não tem nenhuma assinatura. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "o arquivo é obrigatório" msgstr "o arquivo é obrigatório"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Удалить пользователя"
msgid "Desc" msgid "Desc"
msgstr "По убыванию" msgstr "По убыванию"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL-адрес фида"
msgid "Feed name" msgid "Feed name"
msgstr "Имя фида" msgstr "Имя фида"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Выражение фильтрации" msgstr "Выражение фильтрации"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Забыли пароль?" msgstr "Забыли пароль?"
@@ -320,6 +332,10 @@ msgstr "Перейдите к документации по API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Перейти к представлению «Все»" msgstr "Перейти к представлению «Все»"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Сладости" msgstr "Сладости"
@@ -348,6 +364,7 @@ msgstr "Импорт"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "В развернутом виде прокрутка записей помечает их как прочитанные." msgstr "В развернутом виде прокрутка записей помечает их как прочитанные."
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Не читать" msgstr "Не читать"
@@ -397,6 +414,7 @@ msgstr "Загрузка тегов..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Войти" msgstr "Войти"
@@ -404,6 +422,10 @@ msgstr "Войти"
msgid "Logout" msgid "Logout"
msgstr "Выйти" msgstr "Выйти"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Отметить все как прочитанное"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Отметить все записи как прочитанные" msgstr "Отметить все записи как прочитанные"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Отметить как прочитанное" msgstr "Отметить как прочитанное"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Отметить как прочитанное до этого места" msgstr "Отметить как прочитанное до этого места"
@@ -430,6 +454,10 @@ msgstr "Отметить как прочитанное до этого мест
msgid "Metrics" msgid "Metrics"
msgstr "Метрики" msgstr "Метрики"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Переместить страницу вниз" msgstr "Переместить страницу вниз"
@@ -515,6 +543,14 @@ msgstr "Открыть текущую запись в новой вкладке
msgid "Open link" msgid "Open link"
msgstr "Открыть ссылку" msgstr "Открыть ссылку"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Открыть следующую запись" msgstr "Открыть следующую запись"
@@ -581,6 +617,10 @@ msgstr "Обновить"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Регистрация закрыта для этого экземпляра CommaFeed." msgstr "Регистрация закрыта для этого экземпляра CommaFeed."
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Обмен сайтами"
msgid "Shift" msgid "Shift"
msgstr "Сдвиг" msgstr "Сдвиг"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Показать каналы и категории без непрочитанных записей" msgstr "Показать каналы и категории без непрочитанных записей"
@@ -642,6 +690,7 @@ msgstr "Показать справку по сочетаниям клавиш."
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Зарегистрироваться" msgstr "Зарегистрироваться"
@@ -654,6 +703,7 @@ msgstr "Только что случилось что-то плохое..."
msgid "Space" msgid "Space"
msgstr "Пробел" msgstr "Пробел"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Звезда" msgstr "Звезда"
@@ -681,6 +731,10 @@ msgstr "Подписаться на ленту"
msgid "Success" msgid "Success"
msgstr "Успех" msgstr "Успех"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Переключиться на темную тему" msgstr "Переключиться на темную тему"
@@ -709,10 +763,15 @@ msgstr "Переключить статус чтения текущей запи
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Попробуйте CommaFeed на демо-счете: demo/demo" msgstr "Попробуйте CommaFeed на демо-счете: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "непрочитано" msgstr "непрочитано"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Снять пометку" msgstr "Снять пометку"
@@ -743,6 +802,14 @@ msgstr "Веб-сайт"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "У вас пока нет подписок. " msgstr "У вас пока нет подписок. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "требуется файл" msgstr "требуется файл"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Vymažte používateľa"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "URL informačného kanála"
msgid "Feed name" msgid "Feed name"
msgstr "Názov informačného kanála" msgstr "Názov informačného kanála"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtrovanie výrazu" msgstr "Filtrovanie výrazu"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Zabudli ste heslo?" msgstr "Zabudli ste heslo?"
@@ -320,6 +332,10 @@ msgstr "Prejdite na dokumentáciu API."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Prejdite na zobrazenie Všetky" msgstr "Prejdite na zobrazenie Všetky"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Dobrôtky" msgstr "Dobrôtky"
@@ -348,6 +364,7 @@ msgstr ""
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "V rozšírenom zobrazení ich rolovanie cez položky označí ako prečítané" msgstr "V rozšírenom zobrazení ich rolovanie cez položky označí ako prečítané"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Ponechať neprečítané" msgstr "Ponechať neprečítané"
@@ -397,6 +414,7 @@ msgstr "Načítavam značky..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Prihláste sa" msgstr "Prihláste sa"
@@ -404,6 +422,10 @@ msgstr "Prihláste sa"
msgid "Logout" msgid "Logout"
msgstr "Odhlásenie" msgstr "Odhlásenie"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Označiť všetko ako prečítané"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Označte všetky položky ako prečítané" msgstr "Označte všetky položky ako prečítané"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Označiť ako prečítané" msgstr "Označiť ako prečítané"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Označiť ako prečítané až sem" msgstr "Označiť ako prečítané až sem"
@@ -430,6 +454,10 @@ msgstr "Označiť ako prečítané až sem"
msgid "Metrics" msgid "Metrics"
msgstr "Metriky" msgstr "Metriky"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Posuňte stránku nadol" msgstr "Posuňte stránku nadol"
@@ -515,6 +543,14 @@ msgstr "Otvorte aktuálny záznam na novej karte na pozadí"
msgid "Open link" msgid "Open link"
msgstr "Otvoriť odkaz" msgstr "Otvoriť odkaz"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Otvor ďalší záznam" msgstr "Otvor ďalší záznam"
@@ -581,6 +617,10 @@ msgstr "Obnoviť"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "V tejto inštancii CommaFeed sú registrácie uzavreté" msgstr "V tejto inštancii CommaFeed sú registrácie uzavreté"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Zdieľanie stránok"
msgid "Shift" msgid "Shift"
msgstr "Smena" msgstr "Smena"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Zobraziť kanály a kategórie bez neprečítaných záznamov" msgstr "Zobraziť kanály a kategórie bez neprečítaných záznamov"
@@ -642,6 +690,7 @@ msgstr "Zobraziť pomoc s klávesovými skratkami"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Zaregistrujte sa" msgstr "Zaregistrujte sa"
@@ -654,6 +703,7 @@ msgstr "Práve sa stalo niečo zlé..."
msgid "Space" msgid "Space"
msgstr "Vesmír" msgstr "Vesmír"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Hviezda" msgstr "Hviezda"
@@ -681,6 +731,10 @@ msgstr "Prihláste sa na odber kanála"
msgid "Success" msgid "Success"
msgstr "Úspech" msgstr "Úspech"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Prepnúť na tmavú tému" msgstr "Prepnúť na tmavú tému"
@@ -709,10 +763,15 @@ msgstr "Prepne stav čítania aktuálneho záznamu"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Vyskúšajte CommaFeed s demo účtom: demo/demo" msgstr "Vyskúšajte CommaFeed s demo účtom: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Neprečítané" msgstr "Neprečítané"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Odobrať hviezdičku" msgstr "Odobrať hviezdičku"
@@ -743,6 +802,14 @@ msgstr "Webová stránka"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Zatiaľ nemáte žiadne odbery. " msgstr "Zatiaľ nemáte žiadne odbery. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "" msgstr ""
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Ta bort användare"
msgid "Desc" msgid "Desc"
msgstr "" msgstr ""
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "Flödes-URL"
msgid "Feed name" msgid "Feed name"
msgstr "Flödesnamn" msgstr "Flödesnamn"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtrerande uttryck" msgstr "Filtrerande uttryck"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Glömt lösenord?" msgstr "Glömt lösenord?"
@@ -320,6 +332,10 @@ msgstr "Gå till API-dokumentationen."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Gå till vyn Alla" msgstr "Gå till vyn Alla"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "Godsaker" msgstr "Godsaker"
@@ -348,6 +364,7 @@ msgstr ""
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "I utökad vy, rullning genom poster markerar dem som lästa" msgstr "I utökad vy, rullning genom poster markerar dem som lästa"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Behåll oläst" msgstr "Behåll oläst"
@@ -397,6 +414,7 @@ msgstr "Laddar taggar..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Logga in" msgstr "Logga in"
@@ -404,6 +422,10 @@ msgstr "Logga in"
msgid "Logout" msgid "Logout"
msgstr "Logga ut" msgstr "Logga ut"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Markera alla som lästa"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Markera alla poster som lästa" msgstr "Markera alla poster som lästa"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Markera som läst" msgstr "Markera som läst"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Markera som läst hit" msgstr "Markera som läst hit"
@@ -430,6 +454,10 @@ msgstr "Markera som läst hit"
msgid "Metrics" msgid "Metrics"
msgstr "Mätverk" msgstr "Mätverk"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Flytta sidan nedåt" msgstr "Flytta sidan nedåt"
@@ -515,6 +543,14 @@ msgstr "Öppna aktuell post i en ny flik i bakgrunden"
msgid "Open link" msgid "Open link"
msgstr "Öppen länk" msgstr "Öppen länk"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Öppna nästa post" msgstr "Öppna nästa post"
@@ -581,6 +617,10 @@ msgstr "Uppdatera"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Registreringar är stängda på denna CommaFeed-instans" msgstr "Registreringar är stängda på denna CommaFeed-instans"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Delningssajter"
msgid "Shift" msgid "Shift"
msgstr "Skift" msgstr "Skift"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Visa flöden och kategorier utan olästa poster" msgstr "Visa flöden och kategorier utan olästa poster"
@@ -642,6 +690,7 @@ msgstr "Visa kortkommandohjälp"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Anmäl dig" msgstr "Anmäl dig"
@@ -654,6 +703,7 @@ msgstr "Något dåligt hände precis..."
msgid "Space" msgid "Space"
msgstr "Rymden" msgstr "Rymden"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Stjärna" msgstr "Stjärna"
@@ -681,6 +731,10 @@ msgstr "Prenumerera på flödet"
msgid "Success" msgid "Success"
msgstr "Framgång" msgstr "Framgång"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Byt till mörkt tema" msgstr "Byt till mörkt tema"
@@ -709,10 +763,15 @@ msgstr "Växla lässtatus för aktuell post"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "Prova CommaFeed med demokontot: demo/demo" msgstr "Prova CommaFeed med demokontot: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Oläst" msgstr "Oläst"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "" msgstr ""
@@ -743,6 +802,14 @@ msgstr "Webbplats"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Du har inga prenumerationer än. " msgstr "Du har inga prenumerationer än. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "fil krävs" msgstr "fil krävs"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "Kullanıcıyı sil"
msgid "Desc" msgid "Desc"
msgstr "Açılış" msgstr "Açılış"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "Feed URL'si"
msgid "Feed name" msgid "Feed name"
msgstr "Yayın adı" msgstr "Yayın adı"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "Filtreleme ifadesi" msgstr "Filtreleme ifadesi"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Parolanızı mı unuttunuz?" msgstr "Parolanızı mı unuttunuz?"
@@ -320,6 +332,10 @@ msgstr "API belgelerine gidin."
msgid "Go to the All view" msgid "Go to the All view"
msgstr "Tümü görünümüne git" msgstr "Tümü görünümüne git"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "İyilikler" msgstr "İyilikler"
@@ -348,6 +364,7 @@ msgstr "İçe Aktar"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "Genişletilmiş görünümde, girişler arasında gezinmek onları okundu olarak işaretler" msgstr "Genişletilmiş görünümde, girişler arasında gezinmek onları okundu olarak işaretler"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "Okunmadan sakla" msgstr "Okunmadan sakla"
@@ -397,6 +414,7 @@ msgstr "Etiketler yükleniyor..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "Giriş" msgstr "Giriş"
@@ -404,6 +422,10 @@ msgstr "Giriş"
msgid "Logout" msgid "Logout"
msgstr "Çıkış" msgstr "Çıkış"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "Tümünü okundu olarak işaretle"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "Tüm girişleri okundu olarak işaretle" msgstr "Tüm girişleri okundu olarak işaretle"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "Okundu olarak işaretle" msgstr "Okundu olarak işaretle"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "Buraya kadar okundu olarak işaretle" msgstr "Buraya kadar okundu olarak işaretle"
@@ -430,6 +454,10 @@ msgstr "Buraya kadar okundu olarak işaretle"
msgid "Metrics" msgid "Metrics"
msgstr "Metrikler" msgstr "Metrikler"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "Sayfayı aşağı taşı" msgstr "Sayfayı aşağı taşı"
@@ -515,6 +543,14 @@ msgstr "Geçerli girişi arka planda yeni bir sekmede aç"
msgid "Open link" msgid "Open link"
msgstr "Bağlantıyı aç" msgstr "Bağlantıyı aç"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "Sonraki girişi aç" msgstr "Sonraki girişi aç"
@@ -581,6 +617,10 @@ msgstr "Yenile"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "Bu CommaFeed örneğinde kayıtlar kapalı" msgstr "Bu CommaFeed örneğinde kayıtlar kapalı"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "Siteleri paylaşma"
msgid "Shift" msgid "Shift"
msgstr "Vardiya" msgstr "Vardiya"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "Okunmamış girişi olmayan beslemeleri ve kategorileri göster" msgstr "Okunmamış girişi olmayan beslemeleri ve kategorileri göster"
@@ -642,6 +690,7 @@ msgstr "Klavye kısayolu yardımını göster"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "Kaydolun" msgstr "Kaydolun"
@@ -654,6 +703,7 @@ msgstr "Az önce kötü bir şey oldu..."
msgid "Space" msgid "Space"
msgstr "Uzay" msgstr "Uzay"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "Yıldız" msgstr "Yıldız"
@@ -681,6 +731,10 @@ msgstr "beslemeye abone olun"
msgid "Success" msgid "Success"
msgstr "Başarı" msgstr "Başarı"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "Karanlık temaya geç" msgstr "Karanlık temaya geç"
@@ -709,10 +763,15 @@ msgstr "Geçerli girişin okuma durumunu değiştir"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "CommaFeed'i demo hesabıyla deneyin: demo/demo" msgstr "CommaFeed'i demo hesabıyla deneyin: demo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "Okunmadı" msgstr "Okunmadı"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "Yıldızı kaldır" msgstr "Yıldızı kaldır"
@@ -743,6 +802,14 @@ msgstr "Web sitesi"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "Henüz aboneliğiniz yok. " msgstr "Henüz aboneliğiniz yok. "
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "dosya gerekli" msgstr "dosya gerekli"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -219,6 +219,10 @@ msgstr "删除用户"
msgid "Desc" msgid "Desc"
msgstr "描述" msgstr "描述"
#: src/components/header/ProfileMenu.tsx
msgid "Detailed"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/app/SettingsPage.tsx #: src/pages/app/SettingsPage.tsx
msgid "Display" msgid "Display"
@@ -288,10 +292,18 @@ msgstr "供稿网址"
msgid "Feed name" msgid "Feed name"
msgstr "提要名称" msgstr "提要名称"
#: src/components/header/ProfileMenu.tsx
msgid "Fetch all my feeds now"
msgstr ""
#: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx
msgid "Filtering expression" msgid "Filtering expression"
msgstr "过滤表达式" msgstr "过滤表达式"
#: src/pages/app/AboutPage.tsx
msgid "For those of you who prefer bitcoin, here is the address: {bitcoinAddress}"
msgstr ""
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "忘记密码?" msgstr "忘记密码?"
@@ -320,6 +332,10 @@ msgstr "转到 API 文档。"
msgid "Go to the All view" msgid "Go to the All view"
msgstr "转到全部视图" msgstr "转到全部视图"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Go to {0}"
msgstr ""
#: src/pages/app/AboutPage.tsx #: src/pages/app/AboutPage.tsx
msgid "Goodies" msgid "Goodies"
msgstr "好东西" msgstr "好东西"
@@ -348,6 +364,7 @@ msgstr "进口"
msgid "In expanded view, scrolling through entries mark them as read" msgid "In expanded view, scrolling through entries mark them as read"
msgstr "在展开视图中,滚动条目将它们标记为已读" msgstr "在展开视图中,滚动条目将它们标记为已读"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Keep unread" msgid "Keep unread"
msgstr "保持未读状态" msgstr "保持未读状态"
@@ -397,6 +414,7 @@ msgstr "正在加载标签..."
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/auth/LoginPage.tsx #: src/pages/auth/LoginPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Log in" msgid "Log in"
msgstr "登录" msgstr "登录"
@@ -404,6 +422,10 @@ msgstr "登录"
msgid "Logout" msgid "Logout"
msgstr "注销" msgstr "注销"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Long press"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
#: src/pages/admin/AdminUsersPage.tsx #: src/pages/admin/AdminUsersPage.tsx
msgid "Manage users" msgid "Manage users"
@@ -418,10 +440,12 @@ msgstr "全部标记为已读"
msgid "Mark all entries as read" msgid "Mark all entries as read"
msgstr "将所有条目标记为已读" msgstr "将所有条目标记为已读"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read" msgid "Mark as read"
msgstr "标记为已读" msgstr "标记为已读"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Mark as read up to here" msgid "Mark as read up to here"
msgstr "标记为已读到这里" msgstr "标记为已读到这里"
@@ -430,6 +454,10 @@ msgstr "标记为已读到这里"
msgid "Metrics" msgid "Metrics"
msgstr "指标" msgstr "指标"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Middle click"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Move the page down" msgid "Move the page down"
msgstr "页面下移" msgstr "页面下移"
@@ -515,6 +543,14 @@ msgstr "在后台的新选项卡中打开当前条目"
msgid "Open link" msgid "Open link"
msgstr "打开链接" msgstr "打开链接"
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new background tab"
msgstr ""
#: src/components/content/FeedEntryContextMenu.tsx
msgid "Open link in new tab"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx #: src/components/KeyboardShortcutsHelp.tsx
msgid "Open next entry" msgid "Open next entry"
msgstr "打开下一个条目" msgstr "打开下一个条目"
@@ -581,6 +617,10 @@ msgstr "刷新"
msgid "Registrations are closed on this CommaFeed instance" msgid "Registrations are closed on this CommaFeed instance"
msgstr "此 CommaFeed 实例上的注册已关闭" msgstr "此 CommaFeed 实例上的注册已关闭"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Right click"
msgstr ""
#: src/components/admin/UserEdit.tsx #: src/components/admin/UserEdit.tsx
#: src/components/settings/ProfileSettings.tsx #: src/components/settings/ProfileSettings.tsx
#: src/pages/app/CategoryDetailsPage.tsx #: src/pages/app/CategoryDetailsPage.tsx
@@ -632,6 +672,14 @@ msgstr "共享站点"
msgid "Shift" msgid "Shift"
msgstr "换档" msgstr "换档"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (desktop)"
msgstr ""
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Show entry menu (mobile)"
msgstr ""
#: src/components/settings/DisplaySettings.tsx #: src/components/settings/DisplaySettings.tsx
msgid "Show feeds and categories with no unread entries" msgid "Show feeds and categories with no unread entries"
msgstr "显示没有未读条目的提要和类别" msgstr "显示没有未读条目的提要和类别"
@@ -642,6 +690,7 @@ msgstr "显示键盘快捷键帮助"
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/auth/RegistrationPage.tsx #: src/pages/auth/RegistrationPage.tsx
#: src/pages/WelcomePage.tsx
msgid "Sign up" msgid "Sign up"
msgstr "注册" msgstr "注册"
@@ -654,6 +703,7 @@ msgstr "刚刚发生了不好的事情……"
msgid "Space" msgid "Space"
msgstr "空间" msgstr "空间"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Star" msgid "Star"
msgstr "星星" msgstr "星星"
@@ -681,6 +731,10 @@ msgstr "订阅订阅源"
msgid "Success" msgid "Success"
msgstr "成功" msgstr "成功"
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Swipe header to the right"
msgstr ""
#: src/components/header/ProfileMenu.tsx #: src/components/header/ProfileMenu.tsx
msgid "Switch to dark theme" msgid "Switch to dark theme"
msgstr "切换到深色主题" msgstr "切换到深色主题"
@@ -709,10 +763,15 @@ msgstr "切换当前条目的读取状态"
msgid "Try out CommaFeed with the demo account: demo/demo" msgid "Try out CommaFeed with the demo account: demo/demo"
msgstr "使用演示帐户试用 CommaFeeddemo/demo" msgstr "使用演示帐户试用 CommaFeeddemo/demo"
#: src/pages/WelcomePage.tsx
msgid "Try the demo!"
msgstr ""
#: src/components/header/Header.tsx #: src/components/header/Header.tsx
msgid "Unread" msgid "Unread"
msgstr "未读" msgstr "未读"
#: src/components/content/FeedEntryContextMenu.tsx
#: src/components/content/FeedEntryFooter.tsx #: src/components/content/FeedEntryFooter.tsx
msgid "Unstar" msgid "Unstar"
msgstr "解星" msgstr "解星"
@@ -743,6 +802,14 @@ msgstr "网站"
msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?" msgid "You don't have any subscriptions yet. Why not try adding one by clicking on the + sign at the top of the page?"
msgstr "您还没有任何订阅。" msgstr "您还没有任何订阅。"
#: src/components/header/ProfileMenu.tsx
msgid "Your feeds have been queued for refresh."
msgstr ""
#: src/components/content/add/ImportOpml.tsx #: src/components/content/add/ImportOpml.tsx
msgid "file is required" msgid "file is required"
msgstr "文件是必需的" msgstr "文件是必需的"
#: src/components/content/add/CategorySelect.tsx
msgid "{0} (in {1})"
msgstr ""

View File

@@ -2,6 +2,7 @@ import "@fontsource/open-sans"
import { store } from "app/store" import { store } from "app/store"
import dayjs from "dayjs" import dayjs from "dayjs"
import relativeTime from "dayjs/plugin/relativeTime" import relativeTime from "dayjs/plugin/relativeTime"
import "react-contexify/ReactContexify.css"
import ReactDOM from "react-dom/client" import ReactDOM from "react-dom/client"
import { Provider } from "react-redux" import { Provider } from "react-redux"
import { App } from "./App" import { App } from "./App"

View File

@@ -0,0 +1,126 @@
import { t } from "@lingui/macro"
import { Anchor, Box, Center, Container, Divider, Group, Image, Title, useMantineColorScheme } from "@mantine/core"
import { useMediaQuery } from "@mantine/hooks"
import welcome_page_dark from "assets/welcome_page_dark.png"
import welcome_page_light from "assets/welcome_page_light.png"
import { useAsyncCallback } from "react-async-hook"
import { SiGithub, TbKey, TbUserPlus } from "react-icons/all"
import { SiTwitter } from "react-icons/si"
import { TbClock, TbMoon, TbSun } from "react-icons/tb"
import { client } from "../app/client"
import { Constants } from "../app/constants"
import { redirectToLogin, redirectToRegistration, redirectToRootCategory } from "../app/slices/redirect"
import { useAppDispatch, useAppSelector } from "../app/store"
import { ActionButton } from "../components/ActionButtton"
import { ButtonToolbar } from "../components/ButtonToolbar"
import { PageTitle } from "./PageTitle"
export function WelcomePage() {
const { colorScheme } = useMantineColorScheme()
const image = colorScheme === "light" ? welcome_page_light : welcome_page_dark
return (
<Container>
<Header />
<Center my="xl">
<Title order={3}>Bloat-free feed reader</Title>
</Center>
<Divider my="xl" />
<Image src={image} />
<Divider my="xl" />
<Footer />
</Container>
)
}
function Header() {
const mobile = !useMediaQuery(`(min-width: ${Constants.layout.mobileBreakpoint}px)`)
if (mobile) {
return (
<>
<PageTitle />
<Center>
<Buttons />
</Center>
</>
)
}
return (
<Group position="apart">
<PageTitle />
<Buttons />
</Group>
)
}
function Buttons() {
const iconSize = 18
const serverInfos = useAppSelector(state => state.server.serverInfos)
const { colorScheme, toggleColorScheme } = useMantineColorScheme()
const dispatch = useAppDispatch()
const login = useAsyncCallback(client.user.login, {
onSuccess: () => {
dispatch(redirectToRootCategory())
},
})
return (
<ButtonToolbar>
{serverInfos?.demoAccountEnabled && (
<ActionButton
label={t`Try the demo!`}
icon={<TbClock size={iconSize} />}
variant="outline"
onClick={() => login.execute({ name: "demo", password: "demo" })}
showLabelOnMobile
/>
)}
<ActionButton
label={t`Log in`}
icon={<TbKey size={iconSize} />}
variant="outline"
onClick={() => dispatch(redirectToLogin())}
showLabelOnMobile
/>
{serverInfos?.allowRegistrations && (
<ActionButton
label={t`Sign up`}
icon={<TbUserPlus size={iconSize} />}
variant="filled"
onClick={() => dispatch(redirectToRegistration())}
showLabelOnMobile
/>
)}
<ActionButton
icon={colorScheme === "dark" ? <TbSun size={18} /> : <TbMoon size={iconSize} />}
onClick={() => toggleColorScheme()}
/>
</ButtonToolbar>
)
}
function Footer() {
return (
<Box>
<Group>
<span>© CommaFeed</span>
<span> - </span>
<Anchor variant="text" href="https://github.com/Athou/commafeed/" target="_blank" rel="noreferrer">
<SiGithub />
</Anchor>
<Anchor variant="text" href="https://twitter.com/CommaFeed" target="_blank" rel="noreferrer">
<SiTwitter />
</Anchor>
</Group>
</Box>
)
}

View File

@@ -1,7 +1,6 @@
import { Accordion, Box, Tabs } from "@mantine/core" import { Accordion, Tabs } from "@mantine/core"
import { client } from "app/client" import { client } from "app/client"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { Gauge } from "components/metrics/Gauge"
import { Meter } from "components/metrics/Meter" import { Meter } from "components/metrics/Meter"
import { MetricAccordionItem } from "components/metrics/MetricAccordionItem" import { MetricAccordionItem } from "components/metrics/MetricAccordionItem"
import { Timer } from "components/metrics/Timer" import { Timer } from "components/metrics/Timer"
@@ -9,28 +8,18 @@ import { useAsync } from "react-async-hook"
import { TbChartAreaLine, TbClock } from "react-icons/tb" import { TbChartAreaLine, TbClock } from "react-icons/tb"
const shownMeters: { [key: string]: string } = { const shownMeters: { [key: string]: string } = {
"com.commafeed.backend.feed.FeedQueues.refill": "Refresh queue refill rate", "com.commafeed.backend.feed.FeedRefreshEngine.refill": "Feed queue refill rate",
"com.commafeed.backend.feed.FeedRefreshTaskGiver.feedRefreshed": "Feed refreshed", "com.commafeed.backend.feed.FeedRefreshWorker.feedFetched": "Feed fetching rate",
"com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated": "Feed updated", "com.commafeed.backend.feed.FeedRefreshUpdater.feedUpdated": "Feed update rate",
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit": "Entry cache hit", "com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheHit": "Entry cache hit rate",
"com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss", "com.commafeed.backend.feed.FeedRefreshUpdater.entryCacheMiss": "Entry cache miss rate",
}
const shownGauges: { [key: string]: string } = {
"com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-updater.active": "Feed Updater active",
"com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-updater.pending": "Feed Updater queued",
"com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-worker.active": "Feed Worker active",
"com.commafeed.backend.feed.FeedRefreshExecutor.feed-refresh-worker.pending": "Feed Worker queued",
"com.commafeed.backend.feed.FeedQueues.addQueue": "Task Giver Add Queue",
"com.commafeed.backend.feed.FeedQueues.takeQueue": "Task Giver Take Queue",
"com.commafeed.backend.feed.FeedQueues.giveBackQueue": "Task Giver Give Back Queue",
} }
export function MetricsPage() { export function MetricsPage() {
const query = useAsync(() => client.admin.getMetrics(), []) const query = useAsync(() => client.admin.getMetrics(), [])
if (!query.result) return <Loader /> if (!query.result) return <Loader />
const { meters, gauges, timers } = query.result.data const { meters, timers } = query.result.data
return ( return (
<Tabs defaultValue="stats"> <Tabs defaultValue="stats">
<Tabs.List> <Tabs.List>
@@ -50,15 +39,6 @@ export function MetricsPage() {
</MetricAccordionItem> </MetricAccordionItem>
))} ))}
</Accordion> </Accordion>
<Box pt="xs">
{Object.keys(shownGauges).map(g => (
<Box key={g}>
<span>{shownGauges[g]}&nbsp;</span>
<Gauge gauge={gauges[g]} />
</Box>
))}
</Box>
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="timers" pt="xs"> <Tabs.Panel value="timers" pt="xs">

View File

@@ -1,5 +1,5 @@
import { t, Trans } from "@lingui/macro" import { t, Trans } from "@lingui/macro"
import { Anchor, Box, Center, Container, createStyles, List, NativeSelect, SimpleGrid, Title } from "@mantine/core" import { Anchor, Box, Center, Code, Container, createStyles, List, NativeSelect, SimpleGrid, Title } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { redirectToApiDocumentation } from "app/slices/redirect" import { redirectToApiDocumentation } from "app/slices/redirect"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
@@ -57,6 +57,7 @@ function NextUnreadBookmarklet() {
) )
} }
const bitcoinAddress = <Code>{Constants.bitcoinWalletAddress}</Code>
export function AboutPage() { export function AboutPage() {
const version = useAppSelector(state => state.server.serverInfos?.version) const version = useAppSelector(state => state.server.serverInfos?.version)
const revision = useAppSelector(state => state.server.serverInfos?.gitCommit) const revision = useAppSelector(state => state.server.serverInfos?.gitCommit)
@@ -70,7 +71,7 @@ export function AboutPage() {
CommaFeed version {version} ({revision}) CommaFeed version {version} ({revision})
</Trans> </Trans>
</Box> </Box>
<Box> <Box mt="md">
<Trans> <Trans>
CommaFeed is an open-source project. Sources are hosted on&nbsp; CommaFeed is an open-source project. Sources are hosted on&nbsp;
<Anchor href="https://github.com/Athou/commafeed" target="_blank" rel="noreferrer"> <Anchor href="https://github.com/Athou/commafeed" target="_blank" rel="noreferrer">
@@ -114,6 +115,9 @@ export function AboutPage() {
</Center> </Center>
</form> </form>
</Box> </Box>
<Box mt="xs">
<Trans>For those of you who prefer bitcoin, here is the address: {bitcoinAddress}</Trans>
</Box>
</Section> </Section>
<Section title={t`Goodies`} icon={<TbPuzzle size={24} />}> <Section title={t`Goodies`} icon={<TbPuzzle size={24} />}>
<List> <List>

View File

@@ -109,7 +109,12 @@ export function CategoryDetailsPage() {
{editable && ( {editable && (
<> <>
<TextInput label={t`Name`} {...form.getInputProps("name")} required /> <TextInput label={t`Name`} {...form.getInputProps("name")} required />
<CategorySelect label={t`Parent Category`} {...form.getInputProps("parentId")} clearable /> <CategorySelect
label={t`Parent Category`}
{...form.getInputProps("parentId")}
clearable
withoutCategoryIds={[id]}
/>
<NumberInput label={t`Position`} {...form.getInputProps("position")} required min={0} /> <NumberInput label={t`Position`} {...form.getInputProps("position")} required min={0} />
</> </>
)} )}

View File

@@ -17,7 +17,7 @@ import { TbDeviceFloppy, TbTrash } from "react-icons/tb"
import { useParams } from "react-router-dom" import { useParams } from "react-router-dom"
function FilteringExpressionDescription() { function FilteringExpressionDescription() {
const example = <Code>url.contains('youtube') or (author eq 'athou' and title.contains('github')</Code> const example = <Code>url.contains('youtube') or (author eq 'athou' and title.contains('github'))</Code>
return ( return (
<div> <div>
<div> <div>

View File

@@ -25,6 +25,7 @@ import { Logo } from "components/Logo"
import { OnDesktop } from "components/responsive/OnDesktop" import { OnDesktop } from "components/responsive/OnDesktop"
import { OnMobile } from "components/responsive/OnMobile" import { OnMobile } from "components/responsive/OnMobile"
import { useAppLoading } from "hooks/useAppLoading" import { useAppLoading } from "hooks/useAppLoading"
import { useWebSocket } from "hooks/useWebSocket"
import { LoadingPage } from "pages/LoadingPage" import { LoadingPage } from "pages/LoadingPage"
import { ReactNode, Suspense, useEffect } from "react" import { ReactNode, Suspense, useEffect } from "react"
import { TbPlus } from "react-icons/tb" import { TbPlus } from "react-icons/tb"
@@ -85,6 +86,7 @@ export default function Layout({ sidebar, header }: LayoutProps) {
const { loading } = useAppLoading() const { loading } = useAppLoading()
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen) const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
useWebSocket()
useEffect(() => { useEffect(() => {
dispatch(reloadSettings()) dispatch(reloadSettings())

View File

@@ -22,6 +22,7 @@ export default defineConfig({
port: 8082, port: 8082,
proxy: { proxy: {
"/rest": "http://localhost:8083", "/rest": "http://localhost:8083",
"/ws": "ws://localhost:8083",
"/swagger": "http://localhost:8083", "/swagger": "http://localhost:8083",
}, },
}, },

View File

@@ -69,7 +69,7 @@ app:
# user-agent string that will be used by the http client, leave empty for the default one # user-agent string that will be used by the http client, leave empty for the default one
userAgent: userAgent:
# Database connection # Database connection
# ------------------- # -------------------
# for MySQL # for MySQL
@@ -92,7 +92,7 @@ database:
properties: properties:
charSet: UTF-8 charSet: UTF-8
validationQuery: "/* CommaFeed Health Check */ SELECT 1" validationQuery: "/* CommaFeed Health Check */ SELECT 1"
server: server:
applicationConnectors: applicationConnectors:
- type: http - type: http
@@ -100,7 +100,7 @@ server:
adminConnectors: adminConnectors:
- type: http - type: http
port: 8084 port: 8084
logging: logging:
level: INFO level: INFO
loggers: loggers:
@@ -108,6 +108,7 @@ logging:
liquibase: INFO liquibase: INFO
org.hibernate.SQL: INFO # or ALL for sql debugging org.hibernate.SQL: INFO # or ALL for sql debugging
org.hibernate.engine.internal.StatisticalLoggingSessionEventListener: WARN org.hibernate.engine.internal.StatisticalLoggingSessionEventListener: WARN
org.hibernate.orm.deprecation: "OFF"
appenders: appenders:
- type: console - type: console
- type: file - type: file
@@ -117,14 +118,16 @@ logging:
archivedLogFilenamePattern: log/commafeed-%d.log archivedLogFilenamePattern: log/commafeed-%d.log
archivedFileCount: 5 archivedFileCount: 5
timeZone: UTC timeZone: UTC
# Redis pool configuration # Redis pool configuration
# (only used if app.cache is 'redis') # (only used if app.cache is 'redis')
# ----------------------------------- # -----------------------------------
redis: redis:
host: localhost host: localhost
port: 6379 port: 6379
password: # username is only required when using ACLs
username:
password:
timeout: 2000 timeout: 2000
database: 0 database: 0
maxTotal: 500 maxTotal: 500

View File

@@ -87,7 +87,7 @@ app:
database: database:
driverClass: org.h2.Driver driverClass: org.h2.Driver
url: jdbc:h2:/home/commafeed/db url: jdbc:h2:/commafeed/data/db
user: sa user: sa
password: sa password: sa
properties: properties:
@@ -104,9 +104,11 @@ server:
adminConnectors: adminConnectors:
- type: http - type: http
port: 8084 port: 8084
requestLog:
appenders: [ ]
logging: logging:
level: WARN level: ERROR
loggers: loggers:
com.commafeed: INFO com.commafeed: INFO
liquibase: INFO liquibase: INFO
@@ -128,6 +130,8 @@ logging:
redis: redis:
host: localhost host: localhost
port: 6379 port: 6379
# username is only required when using ACLs
username:
password: password:
timeout: 2000 timeout: 2000
database: 0 database: 0

View File

@@ -1,19 +1,20 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed</artifactId> <artifactId>commafeed</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</parent> </parent>
<artifactId>commafeed-server</artifactId> <artifactId>commafeed-server</artifactId>
<name>CommaFeed Server</name> <name>CommaFeed Server</name>
<properties> <properties>
<guice.version>5.1.0</guice.version> <guice.version>5.1.0</guice.version>
<querydsl.version>4.2.1</querydsl.version> <querydsl.version>4.4.0</querydsl.version>
<rome.version>1.18.0</rome.version> <rome.version>2.1.0</rome.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -21,7 +22,7 @@
<dependency> <dependency>
<groupId>io.dropwizard</groupId> <groupId>io.dropwizard</groupId>
<artifactId>dropwizard-dependencies</artifactId> <artifactId>dropwizard-dependencies</artifactId>
<version>2.1.1</version> <version>2.1.6</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@@ -41,7 +42,9 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version> <configuration>
<parameters>true</parameters>
</configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@@ -110,8 +113,10 @@
</goals> </goals>
<configuration> <configuration>
<transformers> <transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" /> <transformer
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.commafeed.CommaFeedApplication</mainClass> <mainClass>com.commafeed.CommaFeedApplication</mainClass>
</transformer> </transformer>
<transformer implementation="org.kordamp.shade.resources.PropertiesFileTransformer"> <transformer implementation="org.kordamp.shade.resources.PropertiesFileTransformer">
@@ -228,13 +233,13 @@
<dependency> <dependency>
<groupId>com.commafeed</groupId> <groupId>com.commafeed</groupId>
<artifactId>commafeed-client</artifactId> <artifactId>commafeed-client</artifactId>
<version>3.0.0</version> <version>3.2.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.22</version> <version>1.18.26</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -261,8 +266,8 @@
<artifactId>dropwizard-hibernate</artifactId> <artifactId>dropwizard-hibernate</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.liquibase</groupId> <groupId>io.dropwizard</groupId>
<artifactId>liquibase-core</artifactId> <artifactId>dropwizard-migrations</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.dropwizard</groupId> <groupId>io.dropwizard</groupId>
@@ -285,6 +290,22 @@
<artifactId>dropwizard-web</artifactId> <artifactId>dropwizard-web</artifactId>
<version>1.5.0</version> <version>1.5.0</version>
</dependency> </dependency>
<dependency>
<groupId>be.tomcools</groupId>
<artifactId>dropwizard-websocket-jee7-bundle</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>io.whitfin</groupId>
<artifactId>dropwizard-environment-substitutor</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<version>3.1.6</version>
</dependency>
<dependency> <dependency>
<groupId>javax.xml.bind</groupId> <groupId>javax.xml.bind</groupId>
@@ -366,7 +387,7 @@
<dependency> <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>
<artifactId>jedis</artifactId> <artifactId>jedis</artifactId>
<version>2.7.2</version> <version>4.3.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.sun.mail</groupId> <groupId>com.sun.mail</groupId>
@@ -398,50 +419,38 @@
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>
<version>1.14.3</version> <version>1.15.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.ibm.icu</groupId> <groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId> <artifactId>icu4j</artifactId>
<version>70.1</version> <version>73.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.sourceforge.cssparser</groupId> <groupId>net.sourceforge.cssparser</groupId>
<artifactId>cssparser</artifactId> <artifactId>cssparser</artifactId>
<version>0.9.29</version> <version>0.9.30</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>edu.uci.ics</groupId> <groupId>org.netpreserve</groupId>
<artifactId>crawler4j</artifactId> <artifactId>urlcanon</artifactId>
<version>3.5</version> <version>0.4.0</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.gwt</groupId> <groupId>org.gwtproject</groupId>
<artifactId>gwt-servlet</artifactId> <artifactId>gwt-servlet</artifactId>
<version>2.9.0</version> <version>2.10.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.github.hakky54</groupId> <groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart</artifactId> <artifactId>sslcontext-kickstart</artifactId>
<version>7.2.0</version> <version>7.4.11</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.apis</groupId> <groupId>com.google.apis</groupId>
<artifactId>google-api-services-youtube</artifactId> <artifactId>google-api-services-youtube</artifactId>
<version>v3-rev139-1.20.0</version> <version>v3-rev222-1.25.0</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava-jdk5</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
@@ -449,14 +458,14 @@
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-j</artifactId>
<version>8.0.28</version> <version>8.0.33</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.postgresql</groupId> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<version>42.4.1</version> <version>42.6.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.sourceforge.jtds</groupId> <groupId>net.sourceforge.jtds</groupId>
@@ -474,10 +483,15 @@
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.mock-server</groupId> <groupId>org.mock-server</groupId>
<artifactId>mockserver-junit-jupiter</artifactId> <artifactId>mockserver-junit-jupiter</artifactId>
<version>5.13.2</version> <version>5.15.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -493,7 +507,7 @@
<dependency> <dependency>
<groupId>com.microsoft.playwright</groupId> <groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId> <artifactId>playwright</artifactId>
<version>1.24.1</version> <version>1.32.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>

View File

@@ -13,13 +13,12 @@ import javax.servlet.ServletException;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.websocket.server.ServerEndpointConfig;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import com.codahale.metrics.json.MetricsModule; import com.codahale.metrics.json.MetricsModule;
import com.commafeed.backend.feed.FeedRefreshTaskGiver; import com.commafeed.backend.feed.FeedRefreshEngine;
import com.commafeed.backend.feed.FeedRefreshUpdater;
import com.commafeed.backend.feed.FeedRefreshWorker;
import com.commafeed.backend.model.AbstractModel; import com.commafeed.backend.model.AbstractModel;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedCategory; import com.commafeed.backend.model.FeedCategory;
@@ -31,7 +30,7 @@ import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole; import com.commafeed.backend.model.UserRole;
import com.commafeed.backend.model.UserSettings; import com.commafeed.backend.model.UserSettings;
import com.commafeed.backend.service.StartupService; import com.commafeed.backend.service.DatabaseStartupService;
import com.commafeed.backend.service.UserService; import com.commafeed.backend.service.UserService;
import com.commafeed.backend.task.ScheduledTask; import com.commafeed.backend.task.ScheduledTask;
import com.commafeed.frontend.auth.SecurityCheckFactoryProvider; import com.commafeed.frontend.auth.SecurityCheckFactoryProvider;
@@ -47,22 +46,31 @@ import com.commafeed.frontend.servlet.CustomCssServlet;
import com.commafeed.frontend.servlet.LogoutServlet; import com.commafeed.frontend.servlet.LogoutServlet;
import com.commafeed.frontend.servlet.NextUnreadServlet; import com.commafeed.frontend.servlet.NextUnreadServlet;
import com.commafeed.frontend.session.SessionHelperFactoryProvider; import com.commafeed.frontend.session.SessionHelperFactoryProvider;
import com.commafeed.frontend.ws.WebSocketConfigurator;
import com.commafeed.frontend.ws.WebSocketEndpoint;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import be.tomcools.dropwizard.websocket.WebsocketBundle;
import io.dropwizard.Application; import io.dropwizard.Application;
import io.dropwizard.assets.AssetsBundle; import io.dropwizard.assets.AssetsBundle;
import io.dropwizard.configuration.DefaultConfigurationFactoryFactory;
import io.dropwizard.configuration.EnvironmentVariableSubstitutor;
import io.dropwizard.configuration.SubstitutingSourceProvider;
import io.dropwizard.db.DataSourceFactory; import io.dropwizard.db.DataSourceFactory;
import io.dropwizard.forms.MultiPartBundle; import io.dropwizard.forms.MultiPartBundle;
import io.dropwizard.hibernate.HibernateBundle; import io.dropwizard.hibernate.HibernateBundle;
import io.dropwizard.server.DefaultServerFactory; import io.dropwizard.migrations.MigrationsBundle;
import io.dropwizard.servlets.CacheBustingFilter; import io.dropwizard.servlets.CacheBustingFilter;
import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment; import io.dropwizard.setup.Environment;
import io.dropwizard.web.WebBundle; import io.dropwizard.web.WebBundle;
import io.dropwizard.web.conf.WebConfiguration; import io.dropwizard.web.conf.WebConfiguration;
import io.whitfin.dropwizard.configuration.EnvironmentSubstitutor;
public class CommaFeedApplication extends Application<CommaFeedConfiguration> { public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
@@ -72,6 +80,7 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
public static final Date STARTUP_TIME = new Date(); public static final Date STARTUP_TIME = new Date();
private HibernateBundle<CommaFeedConfiguration> hibernateBundle; private HibernateBundle<CommaFeedConfiguration> hibernateBundle;
private WebsocketBundle<CommaFeedConfiguration> websocketBundle;
@Override @Override
public String getName() { public String getName() {
@@ -80,8 +89,27 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
@Override @Override
public void initialize(Bootstrap<CommaFeedConfiguration> bootstrap) { public void initialize(Bootstrap<CommaFeedConfiguration> bootstrap) {
bootstrap.setConfigurationFactoryFactory(new DefaultConfigurationFactoryFactory<CommaFeedConfiguration>() {
@Override
protected ObjectMapper configureObjectMapper(ObjectMapper objectMapper) {
// disable case sensitivity because EnvironmentSubstitutor maps MYPROPERTY to myproperty and not to myProperty
return objectMapper
.setConfig(objectMapper.getDeserializationConfig().with(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
}
});
// enable config.yml string substitution
// e.g. having a custom config.yml file with app.session.path=${SOME_ENV_VAR} will substitute SOME_ENV_VAR
SubstitutingSourceProvider substitutingSourceProvider = new SubstitutingSourceProvider(bootstrap.getConfigurationSourceProvider(),
new EnvironmentVariableSubstitutor(false));
// enable config.yml properties override with env variables prefixed with CF_
// e.g. setting CF_APP_ALLOWREGISTRATIONS=true will set app.allowRegistrations to true
EnvironmentSubstitutor environmentSubstitutor = new EnvironmentSubstitutor("CF", substitutingSourceProvider);
bootstrap.setConfigurationSourceProvider(environmentSubstitutor);
bootstrap.getObjectMapper().registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false)); bootstrap.getObjectMapper().registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false));
bootstrap.addBundle(websocketBundle = new WebsocketBundle<>());
bootstrap.addBundle(hibernateBundle = new HibernateBundle<CommaFeedConfiguration>(AbstractModel.class, Feed.class, bootstrap.addBundle(hibernateBundle = new HibernateBundle<CommaFeedConfiguration>(AbstractModel.class, Feed.class,
FeedCategory.class, FeedEntry.class, FeedEntryContent.class, FeedEntryStatus.class, FeedEntryTag.class, FeedCategory.class, FeedEntry.class, FeedEntryContent.class, FeedEntryStatus.class, FeedEntryTag.class,
FeedSubscription.class, User.class, UserRole.class, UserSettings.class) { FeedSubscription.class, User.class, UserRole.class, UserSettings.class) {
@@ -107,6 +135,13 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
} }
}); });
bootstrap.addBundle(new MigrationsBundle<CommaFeedConfiguration>() {
@Override
public DataSourceFactory getDataSourceFactory(CommaFeedConfiguration configuration) {
return configuration.getDataSourceFactory();
}
});
bootstrap.addBundle(new AssetsBundle("/assets/", "/", "index.html")); bootstrap.addBundle(new AssetsBundle("/assets/", "/", "index.html"));
bootstrap.addBundle(new MultiPartBundle()); bootstrap.addBundle(new MultiPartBundle());
} }
@@ -126,7 +161,6 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
// REST resources // REST resources
environment.jersey().setUrlPattern("/rest/*"); environment.jersey().setUrlPattern("/rest/*");
((DefaultServerFactory) config.getServerFactory()).setJerseyRootPath("/rest/*");
environment.jersey().register(injector.getInstance(AdminREST.class)); environment.jersey().register(injector.getInstance(AdminREST.class));
environment.jersey().register(injector.getInstance(CategoryREST.class)); environment.jersey().register(injector.getInstance(CategoryREST.class));
environment.jersey().register(injector.getInstance(EntryREST.class)); environment.jersey().register(injector.getInstance(EntryREST.class));
@@ -141,6 +175,12 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
environment.servlets().addServlet("customCss", injector.getInstance(CustomCssServlet.class)).addMapping("/custom_css.css"); environment.servlets().addServlet("customCss", injector.getInstance(CustomCssServlet.class)).addMapping("/custom_css.css");
environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js"); environment.servlets().addServlet("analytics.js", injector.getInstance(AnalyticsServlet.class)).addMapping("/analytics.js");
// WebSocket endpoint
ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder.create(WebSocketEndpoint.class, "/ws")
.configurator(injector.getInstance(WebSocketConfigurator.class))
.build();
websocketBundle.addEndpoint(serverEndpointConfig);
// Scheduled tasks // Scheduled tasks
Set<ScheduledTask> tasks = injector.getInstance(Key.get(new TypeLiteral<Set<ScheduledTask>>() { Set<ScheduledTask> tasks = injector.getInstance(Key.get(new TypeLiteral<Set<ScheduledTask>>() {
})); }));
@@ -153,12 +193,10 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
} }
// database init/changelogs // database init/changelogs
environment.lifecycle().manage(injector.getInstance(StartupService.class)); environment.lifecycle().manage(injector.getInstance(DatabaseStartupService.class));
// background feed fetching // start feed fetching engine
environment.lifecycle().manage(injector.getInstance(FeedRefreshTaskGiver.class)); environment.lifecycle().manage(injector.getInstance(FeedRefreshEngine.class));
environment.lifecycle().manage(injector.getInstance(FeedRefreshWorker.class));
environment.lifecycle().manage(injector.getInstance(FeedRefreshUpdater.class));
// prevent caching index.html, so that the webapp is always up to date // prevent caching index.html, so that the webapp is always up to date
environment.servlets() environment.servlets()

View File

@@ -45,18 +45,14 @@ public class CommaFeedConfiguration extends Configuration {
@JsonProperty("app") @JsonProperty("app")
private ApplicationSettings applicationSettings; private ApplicationSettings applicationSettings;
private final ResourceBundle bundle; private final String version;
private final String gitCommit;
public CommaFeedConfiguration() { public CommaFeedConfiguration() {
bundle = ResourceBundle.getBundle("application"); ResourceBundle bundle = ResourceBundle.getBundle("application");
}
public String getVersion() { this.version = bundle.getString("version");
return bundle.getString("version"); this.gitCommit = bundle.getString("git.commit");
}
public String getGitCommit() {
return bundle.getString("git.commit");
} }
@Getter @Getter

View File

@@ -14,7 +14,6 @@ import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.HttpResponseInterceptor; import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException; import org.apache.http.client.HttpResponseException;
import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig;
@@ -59,7 +58,7 @@ public class HttpGetter {
} }
} }
public HttpResult getBinary(String url, int timeout) throws ClientProtocolException, IOException, NotModifiedException { public HttpResult getBinary(String url, int timeout) throws IOException, NotModifiedException {
return getBinary(url, null, null, timeout); return getBinary(url, null, null, timeout);
} }
@@ -71,14 +70,10 @@ public class HttpGetter {
* header we got last time we queried that url, or null * header we got last time we queried that url, or null
* @param eTag * @param eTag
* header we got last time we queried that url, or null * header we got last time we queried that url, or null
* @return
* @throws ClientProtocolException
* @throws IOException
* @throws NotModifiedException * @throws NotModifiedException
* if the url hasn't changed since we asked for it last time * if the url hasn't changed since we asked for it last time
*/ */
public HttpResult getBinary(String url, String lastModified, String eTag, int timeout) public HttpResult getBinary(String url, String lastModified, String eTag, int timeout) throws IOException, NotModifiedException {
throws ClientProtocolException, IOException, NotModifiedException {
HttpResult result = null; HttpResult result = null;
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
@@ -175,18 +170,28 @@ public class HttpGetter {
return builder.build(); return builder.build();
} }
public static void main(String[] args) throws Exception { @Getter
CommaFeedConfiguration config = new CommaFeedConfiguration();
HttpGetter getter = new HttpGetter(config);
HttpResult result = getter.getBinary("https://sourceforge.net/projects/mpv-player-windows/rss", 30000);
System.out.println(new String(result.content));
}
public static class NotModifiedException extends Exception { public static class NotModifiedException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* if the value of this header changed, this is its new value
*/
private final String newLastModifiedHeader;
/**
* if the value of this header changed, this is its new value
*/
private final String newEtagHeader;
public NotModifiedException(String message) { public NotModifiedException(String message) {
this(message, null, null);
}
public NotModifiedException(String message, String newLastModifiedHeader, String newEtagHeader) {
super(message); super(message);
this.newLastModifiedHeader = newLastModifiedHeader;
this.newEtagHeader = newEtagHeader;
} }
} }

View File

@@ -1,27 +1,53 @@
package com.commafeed.backend.cache; package com.commafeed.backend.cache;
import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol; import redis.clients.jedis.Protocol;
@Slf4j
@Getter @Getter
public class RedisPoolFactory { public class RedisPoolFactory {
private final String host = "localhost";
private final int port = Protocol.DEFAULT_PORT;
private String password;
private final int timeout = Protocol.DEFAULT_TIMEOUT;
private final int database = Protocol.DEFAULT_DATABASE;
private final int maxTotal = 500; @JsonProperty
private String host = "localhost";
@JsonProperty
private int port = Protocol.DEFAULT_PORT;
@JsonProperty
private String username;
@JsonProperty
private String password;
@JsonProperty
private int timeout = Protocol.DEFAULT_TIMEOUT;
@JsonProperty
private int database = Protocol.DEFAULT_DATABASE;
@JsonProperty
private int maxTotal = 500;
public JedisPool build() { public JedisPool build() {
JedisPoolConfig config = new JedisPoolConfig(); JedisPoolConfig poolConfig = new JedisPoolConfig();
config.setMaxTotal(maxTotal); poolConfig.setMaxTotal(maxTotal);
return new JedisPool(config, host, port, timeout, StringUtils.trimToNull(password), database); JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()
.user(username)
.password(password)
.timeoutMillis(timeout)
.database(database)
.build();
return new JedisPool(poolConfig, new HostAndPort(host, port), clientConfig);
} }
} }

View File

@@ -36,7 +36,7 @@ public class FeedDAO extends GenericDAO<Feed> {
QFeedSubscription subs = QFeedSubscription.feedSubscription; QFeedSubscription subs = QFeedSubscription.feedSubscription;
QUser user = QUser.user; QUser user = QUser.user;
query.join(feed.subscriptions, subs).join(subs.user, user).where(user.lastLogin.gt(lastLoginThreshold)); query.join(subs).on(subs.feed.id.eq(feed.id)).join(subs.user, user).where(user.lastLogin.gt(lastLoginThreshold));
} }
return query.orderBy(feed.disabledUntil.asc()).limit(count).fetch(); return query.orderBy(feed.disabledUntil.asc()).limit(count).fetch();

View File

@@ -2,6 +2,7 @@ package com.commafeed.backend.feed;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Set; import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
@@ -13,13 +14,19 @@ import org.apache.commons.codec.digest.DigestUtils;
import com.commafeed.backend.HttpGetter; import com.commafeed.backend.HttpGetter;
import com.commafeed.backend.HttpGetter.HttpResult; import com.commafeed.backend.HttpGetter.HttpResult;
import com.commafeed.backend.HttpGetter.NotModifiedException; import com.commafeed.backend.HttpGetter.NotModifiedException;
import com.commafeed.backend.feed.FeedParser.FeedParserResult;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.urlprovider.FeedURLProvider; import com.commafeed.backend.urlprovider.FeedURLProvider;
import com.rometools.rome.io.FeedException; import com.rometools.rome.io.FeedException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/**
* Fetches a feed then parses it
*/
@Slf4j @Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject })) @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton @Singleton
@@ -29,18 +36,18 @@ public class FeedFetcher {
private final HttpGetter getter; private final HttpGetter getter;
private final Set<FeedURLProvider> urlProviders; private final Set<FeedURLProvider> urlProviders;
public FetchedFeed fetch(String feedUrl, boolean extractFeedUrlFromHtml, String lastModified, String eTag, Date lastPublishedDate, public FeedFetcherResult fetch(String feedUrl, boolean extractFeedUrlFromHtml, String lastModified, String eTag, Date lastPublishedDate,
String lastContentHash) throws FeedException, IOException, NotModifiedException { String lastContentHash) throws FeedException, IOException, NotModifiedException {
log.debug("Fetching feed {}", feedUrl); log.debug("Fetching feed {}", feedUrl);
FetchedFeed fetchedFeed = null;
int timeout = 20000; int timeout = 20000;
HttpResult result = getter.getBinary(feedUrl, lastModified, eTag, timeout); HttpResult result = getter.getBinary(feedUrl, lastModified, eTag, timeout);
byte[] content = result.getContent(); byte[] content = result.getContent();
FeedParserResult parserResult;
try { try {
fetchedFeed = parser.parse(result.getUrlAfterRedirect(), content); parserResult = parser.parse(result.getUrlAfterRedirect(), content);
} catch (FeedException e) { } catch (FeedException e) {
if (extractFeedUrlFromHtml) { if (extractFeedUrlFromHtml) {
String extractedUrl = extractFeedUrl(urlProviders, feedUrl, StringUtils.newStringUtf8(result.getContent())); String extractedUrl = extractFeedUrl(urlProviders, feedUrl, StringUtils.newStringUtf8(result.getContent()));
@@ -49,7 +56,7 @@ public class FeedFetcher {
result = getter.getBinary(extractedUrl, lastModified, eTag, timeout); result = getter.getBinary(extractedUrl, lastModified, eTag, timeout);
content = result.getContent(); content = result.getContent();
fetchedFeed = parser.parse(result.getUrlAfterRedirect(), content); parserResult = parser.parse(result.getUrlAfterRedirect(), content);
} else { } else {
throw e; throw e;
} }
@@ -62,25 +69,31 @@ public class FeedFetcher {
throw new IOException("Feed content is empty."); throw new IOException("Feed content is empty.");
} }
boolean lastModifiedHeaderValueChanged = !StringUtils.equals(lastModified, result.getLastModifiedSince());
boolean etagHeaderValueChanged = !StringUtils.equals(eTag, result.getETag());
String hash = DigestUtils.sha1Hex(content); String hash = DigestUtils.sha1Hex(content);
if (lastContentHash != null && hash != null && lastContentHash.equals(hash)) { if (lastContentHash != null && hash != null && lastContentHash.equals(hash)) {
log.debug("content hash not modified: {}", feedUrl); log.debug("content hash not modified: {}", feedUrl);
throw new NotModifiedException("content hash not modified"); throw new NotModifiedException("content hash not modified",
lastModifiedHeaderValueChanged ? result.getLastModifiedSince() : null,
etagHeaderValueChanged ? result.getETag() : null);
} }
if (lastPublishedDate != null && fetchedFeed.getFeed().getLastPublishedDate() != null if (lastPublishedDate != null && parserResult.getFeed().getLastPublishedDate() != null
&& lastPublishedDate.getTime() == fetchedFeed.getFeed().getLastPublishedDate().getTime()) { && lastPublishedDate.getTime() == parserResult.getFeed().getLastPublishedDate().getTime()) {
log.debug("publishedDate not modified: {}", feedUrl); log.debug("publishedDate not modified: {}", feedUrl);
throw new NotModifiedException("publishedDate not modified"); throw new NotModifiedException("publishedDate not modified",
lastModifiedHeaderValueChanged ? result.getLastModifiedSince() : null,
etagHeaderValueChanged ? result.getETag() : null);
} }
Feed feed = fetchedFeed.getFeed(); Feed feed = parserResult.getFeed();
feed.setLastModifiedHeader(result.getLastModifiedSince()); feed.setLastModifiedHeader(result.getLastModifiedSince());
feed.setEtagHeader(FeedUtils.truncate(result.getETag(), 255)); feed.setEtagHeader(FeedUtils.truncate(result.getETag(), 255));
feed.setLastContentHash(hash); feed.setLastContentHash(hash);
fetchedFeed.setFetchDuration(result.getDuration()); return new FeedFetcherResult(parserResult.getFeed(), parserResult.getEntries(), parserResult.getTitle(),
fetchedFeed.setUrlAfterRedirect(result.getUrlAfterRedirect()); result.getUrlAfterRedirect(), result.getDuration());
return fetchedFeed;
} }
private static String extractFeedUrl(Set<FeedURLProvider> urlProviders, String url, String urlContent) { private static String extractFeedUrl(Set<FeedURLProvider> urlProviders, String url, String urlContent) {
@@ -93,4 +106,14 @@ public class FeedFetcher {
return null; return null;
} }
@Value
public static class FeedFetcherResult {
Feed feed;
List<FeedEntry> entries;
String title;
String urlAfterRedirect;
long fetchDuration;
}
} }

View File

@@ -3,6 +3,7 @@ package com.commafeed.backend.feed;
import java.io.StringReader; import java.io.StringReader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -37,8 +38,12 @@ import com.rometools.rome.io.SyndFeedInput;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/**
* Parses raw xml as a Feed object
*/
@Slf4j @Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject })) @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton @Singleton
@@ -50,10 +55,7 @@ public class FeedParser {
private static final Date START = new Date(86400000); private static final Date START = new Date(86400000);
private static final Date END = new Date(1000L * Integer.MAX_VALUE - 86400000); private static final Date END = new Date(1000L * Integer.MAX_VALUE - 86400000);
public FetchedFeed parse(String feedUrl, byte[] xml) throws FeedException { public FeedParserResult parse(String feedUrl, byte[] xml) throws FeedException {
FetchedFeed fetchedFeed = new FetchedFeed();
Feed feed = fetchedFeed.getFeed();
List<FeedEntry> entries = fetchedFeed.getEntries();
try { try {
Charset encoding = FeedUtils.guessEncoding(xml); Charset encoding = FeedUtils.guessEncoding(xml);
@@ -63,17 +65,19 @@ public class FeedParser {
} }
xmlString = FeedUtils.replaceHtmlEntitiesWithNumericEntities(xmlString); xmlString = FeedUtils.replaceHtmlEntitiesWithNumericEntities(xmlString);
InputSource source = new InputSource(new StringReader(xmlString)); InputSource source = new InputSource(new StringReader(xmlString));
SyndFeed rss = new SyndFeedInput().build(source); SyndFeed rss = new SyndFeedInput().build(source);
handleForeignMarkup(rss); handleForeignMarkup(rss);
fetchedFeed.setTitle(rss.getTitle()); String title = rss.getTitle();
Feed feed = new Feed();
feed.setPushHub(findHub(rss)); feed.setPushHub(findHub(rss));
feed.setPushTopic(findSelf(rss)); feed.setPushTopic(findSelf(rss));
feed.setUrl(feedUrl); feed.setUrl(feedUrl);
feed.setLink(rss.getLink()); feed.setLink(rss.getLink());
List<SyndEntry> items = rss.getEntries();
for (SyndEntry item : items) { List<FeedEntry> entries = new ArrayList<>();
for (SyndEntry item : rss.getEntries()) {
FeedEntry entry = new FeedEntry(); FeedEntry entry = new FeedEntry();
String guid = item.getUri(); String guid = item.getUri();
@@ -121,6 +125,7 @@ public class FeedParser {
entries.add(entry); entries.add(entry);
} }
Date lastEntryDate = null; Date lastEntryDate = null;
Date publishedDate = validateDate(rss.getPublishedDate(), false); Date publishedDate = validateDate(rss.getPublishedDate(), false);
if (!entries.isEmpty()) { if (!entries.isEmpty()) {
@@ -133,10 +138,10 @@ public class FeedParser {
feed.setAverageEntryInterval(FeedUtils.averageTimeBetweenEntries(entries)); feed.setAverageEntryInterval(FeedUtils.averageTimeBetweenEntries(entries));
feed.setLastEntryDate(lastEntryDate); feed.setLastEntryDate(lastEntryDate);
return new FeedParserResult(feed, entries, title);
} catch (Exception e) { } catch (Exception e) {
throw new FeedException(String.format("Could not parse feed from %s : %s", feedUrl, e.getMessage()), e); throw new FeedException(String.format("Could not parse feed from %s : %s", feedUrl, e.getMessage()), e);
} }
return fetchedFeed;
} }
/** /**
@@ -273,4 +278,11 @@ public class FeedParser {
} }
} }
@Value
public static class FeedParserResult {
Feed feed;
List<FeedEntry> entries;
String title;
}
} }

View File

@@ -1,160 +0,0 @@
package com.commafeed.backend.feed;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.SessionFactory;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.model.Feed;
@Singleton
public class FeedQueues {
private SessionFactory sessionFactory;
private final FeedDAO feedDAO;
private final CommaFeedConfiguration config;
private Queue<FeedRefreshContext> addQueue = new ConcurrentLinkedQueue<>();
private Queue<FeedRefreshContext> takeQueue = new ConcurrentLinkedQueue<>();
private Queue<Feed> giveBackQueue = new ConcurrentLinkedQueue<>();
private Meter refill;
@Inject
public FeedQueues(SessionFactory sessionFactory, FeedDAO feedDAO, CommaFeedConfiguration config, MetricRegistry metrics) {
this.sessionFactory = sessionFactory;
this.config = config;
this.feedDAO = feedDAO;
refill = metrics.meter(MetricRegistry.name(getClass(), "refill"));
metrics.register(MetricRegistry.name(getClass(), "addQueue"), new Gauge<Integer>() {
@Override
public Integer getValue() {
return addQueue.size();
}
});
metrics.register(MetricRegistry.name(getClass(), "takeQueue"), new Gauge<Integer>() {
@Override
public Integer getValue() {
return takeQueue.size();
}
});
metrics.register(MetricRegistry.name(getClass(), "giveBackQueue"), new Gauge<Integer>() {
@Override
public Integer getValue() {
return giveBackQueue.size();
}
});
}
/**
* take a feed from the refresh queue
*/
public synchronized FeedRefreshContext take() {
FeedRefreshContext context = takeQueue.poll();
if (context == null) {
refill();
context = takeQueue.poll();
}
return context;
}
/**
* add a feed to the refresh queue
*/
public void add(Feed feed, boolean urgent) {
int refreshInterval = config.getApplicationSettings().getRefreshIntervalMinutes();
if (feed.getLastUpdated() == null || feed.getLastUpdated().before(DateUtils.addMinutes(new Date(), -1 * refreshInterval))) {
boolean alreadyQueued = addQueue.stream().anyMatch(c -> c.getFeed().getId().equals(feed.getId()));
if (!alreadyQueued) {
addQueue.add(new FeedRefreshContext(feed, urgent));
}
}
}
/**
* refills the refresh queue and empties the giveBack queue while at it
*/
private void refill() {
refill.mark();
List<FeedRefreshContext> contexts = new ArrayList<>();
int batchSize = Math.min(100, 3 * config.getApplicationSettings().getBackgroundThreads());
// add feeds we got from the add() method
int addQueueSize = addQueue.size();
for (int i = 0; i < Math.min(batchSize, addQueueSize); i++) {
contexts.add(addQueue.poll());
}
// add feeds that are up to refresh from the database
int count = batchSize - contexts.size();
if (count > 0) {
List<Feed> feeds = UnitOfWork.call(sessionFactory, () -> feedDAO.findNextUpdatable(count, getLastLoginThreshold()));
for (Feed feed : feeds) {
contexts.add(new FeedRefreshContext(feed, false));
}
}
// set the disabledDate as we use it in feedDAO to decide what to refresh next. We also use a map to remove
// duplicates.
Map<Long, FeedRefreshContext> map = new LinkedHashMap<>();
for (FeedRefreshContext context : contexts) {
Feed feed = context.getFeed();
feed.setDisabledUntil(DateUtils.addMinutes(new Date(), config.getApplicationSettings().getRefreshIntervalMinutes()));
map.put(feed.getId(), context);
}
// refill the queue
takeQueue.addAll(map.values());
// add feeds from the giveBack queue to the map, overriding duplicates
int giveBackQueueSize = giveBackQueue.size();
for (int i = 0; i < giveBackQueueSize; i++) {
Feed feed = giveBackQueue.poll();
map.put(feed.getId(), new FeedRefreshContext(feed, false));
}
// update all feeds in the database
List<Feed> feeds = map.values().stream().map(c -> c.getFeed()).collect(Collectors.toList());
UnitOfWork.run(sessionFactory, () -> feedDAO.saveOrUpdate(feeds));
}
/**
* give a feed back, updating it to the database during the next refill()
*/
public void giveBack(Feed feed) {
String normalized = FeedUtils.normalizeURL(feed.getUrl());
feed.setNormalizedUrl(normalized);
feed.setNormalizedUrlHash(DigestUtils.sha1Hex(normalized));
feed.setLastUpdated(new Date());
giveBackQueue.add(feed);
}
private Date getLastLoginThreshold() {
if (config.getApplicationSettings().getHeavyLoad()) {
return DateUtils.addDays(new Date(), -30);
} else {
return null;
}
}
}

View File

@@ -1,22 +0,0 @@
package com.commafeed.backend.feed;
import java.util.List;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class FeedRefreshContext {
private Feed feed;
private List<FeedEntry> entries;
private boolean urgent;
public FeedRefreshContext(Feed feed, boolean isUrgent) {
this.feed = feed;
this.urgent = isUrgent;
}
}

View File

@@ -0,0 +1,108 @@
package com.commafeed.backend.feed;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.SessionFactory;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.FeedDAO;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.model.Feed;
import io.dropwizard.lifecycle.Managed;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.processors.PublishProcessor;
import io.reactivex.rxjava3.schedulers.Schedulers;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Singleton
public class FeedRefreshEngine implements Managed {
private final SessionFactory sessionFactory;
private final FeedDAO feedDAO;
private final FeedRefreshWorker worker;
private final FeedRefreshUpdater updater;
private final CommaFeedConfiguration config;
private final Meter refill;
private final PublishProcessor<Feed> priorityQueue;
private Disposable flow;
@Inject
public FeedRefreshEngine(SessionFactory sessionFactory, FeedDAO feedDAO, FeedRefreshWorker worker, FeedRefreshUpdater updater,
CommaFeedConfiguration config, MetricRegistry metrics) {
this.sessionFactory = sessionFactory;
this.feedDAO = feedDAO;
this.worker = worker;
this.updater = updater;
this.config = config;
this.refill = metrics.meter(MetricRegistry.name(getClass(), "refill"));
this.priorityQueue = PublishProcessor.create();
}
@Override
public void start() {
Flowable<Feed> database = Flowable.fromCallable(() -> findNextUpdatableFeeds(getBatchSize(), getLastLoginThreshold()))
.onErrorResumeNext(e -> {
log.error("error while fetching next updatable feeds", e);
return Flowable.empty();
})
// repeat query 15s after the flowable has been emptied
// https://github.com/ReactiveX/RxJava/issues/448#issuecomment-233244964
.repeatWhen(o -> o.concatMap(v -> Flowable.timer(15, TimeUnit.SECONDS)))
.flatMap(Flowable::fromIterable);
Flowable<Feed> source = Flowable.merge(priorityQueue, database);
this.flow = source.subscribeOn(Schedulers.io())
// feed fetching
.parallel(config.getApplicationSettings().getBackgroundThreads())
.runOn(Schedulers.io())
.flatMap(f -> Flowable.fromCallable(() -> worker.update(f)).onErrorResumeNext(e -> {
log.error("error while fetching feed", e);
return Flowable.empty();
}))
.sequential()
// database updating
.parallel(config.getApplicationSettings().getDatabaseUpdateThreads())
.runOn(Schedulers.io())
.flatMap(fae -> Flowable.fromCallable(() -> updater.update(fae.getFeed(), fae.getEntries())).onErrorResumeNext(e -> {
log.error("error while updating database", e);
return Flowable.empty();
}))
.sequential()
// end flow
.subscribe();
}
public void refreshImmediately(Feed feed) {
priorityQueue.onNext(feed);
}
private List<Feed> findNextUpdatableFeeds(int max, Date lastLoginThreshold) {
refill.mark();
return UnitOfWork.call(sessionFactory, () -> feedDAO.findNextUpdatable(max, lastLoginThreshold));
}
private int getBatchSize() {
return Math.min(Flowable.bufferSize(), 3 * config.getApplicationSettings().getBackgroundThreads());
}
private Date getLastLoginThreshold() {
return Boolean.TRUE.equals(config.getApplicationSettings().getHeavyLoad()) ? DateUtils.addDays(new Date(), -30) : null;
}
@Override
public void stop() {
flow.dispose();
}
}

View File

@@ -1,99 +0,0 @@
package com.commafeed.backend.feed;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import lombok.extern.slf4j.Slf4j;
/**
* Wraps a {@link ThreadPoolExecutor} instance. Blocks when queue is full instead of rejecting the task. Allow priority queueing by using
* {@link Task} instead of {@link Runnable}
*
*/
@Slf4j
public class FeedRefreshExecutor {
private String poolName;
private ThreadPoolExecutor pool;
private LinkedBlockingDeque<Runnable> queue;
public FeedRefreshExecutor(final String poolName, int threads, int queueCapacity, MetricRegistry metrics) {
log.info("Creating pool {} with {} threads", poolName, threads);
this.poolName = poolName;
pool = new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, queue = new LinkedBlockingDeque<Runnable>(queueCapacity) {
private static final long serialVersionUID = 1L;
@Override
public boolean offer(Runnable r) {
Task task = (Task) r;
if (task.isUrgent()) {
return offerFirst(r);
} else {
return offerLast(r);
}
}
}) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (t != null) {
log.error("thread from pool {} threw a runtime exception", poolName, t);
}
}
};
pool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
log.debug("{} thread queue full, waiting...", poolName);
try {
Task task = (Task) r;
if (task.isUrgent()) {
queue.putFirst(r);
} else {
queue.put(r);
}
} catch (InterruptedException e1) {
log.error(poolName + " interrupted while waiting for queue.", e1);
}
}
});
metrics.register(MetricRegistry.name(getClass(), poolName, "active"), new Gauge<Integer>() {
@Override
public Integer getValue() {
return pool.getActiveCount();
}
});
metrics.register(MetricRegistry.name(getClass(), poolName, "pending"), new Gauge<Integer>() {
@Override
public Integer getValue() {
return queue.size();
}
});
}
public void execute(Task task) {
pool.execute(task);
}
public void shutdown() {
pool.shutdownNow();
while (!pool.isTerminated()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
log.error("{} interrupted while waiting for threads to finish.", poolName);
}
}
}
public interface Task extends Runnable {
boolean isUrgent();
}
}

View File

@@ -0,0 +1,84 @@
package com.commafeed.backend.feed;
import java.util.Date;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.time.DateUtils;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.model.Feed;
@Singleton
public class FeedRefreshIntervalCalculator {
private boolean heavyLoad;
private int refreshIntervalMinutes;
@Inject
public FeedRefreshIntervalCalculator(CommaFeedConfiguration config) {
this.heavyLoad = config.getApplicationSettings().getHeavyLoad();
this.refreshIntervalMinutes = config.getApplicationSettings().getRefreshIntervalMinutes();
}
public Date onFetchSuccess(Feed feed) {
Date defaultRefreshInterval = getDefaultRefreshInterval();
return heavyLoad ? computeRefreshIntervalForHeavyLoad(feed, defaultRefreshInterval) : defaultRefreshInterval;
}
public Date onFeedNotModified(Feed feed) {
Date defaultRefreshInterval = getDefaultRefreshInterval();
return heavyLoad ? computeRefreshIntervalForHeavyLoad(feed, defaultRefreshInterval) : defaultRefreshInterval;
}
public Date onFetchError(Feed feed) {
int errorCount = feed.getErrorCount();
int retriesBeforeDisable = 3;
if (errorCount < retriesBeforeDisable || !heavyLoad) {
return getDefaultRefreshInterval();
}
int disabledHours = Math.min(24 * 7, errorCount - retriesBeforeDisable + 1);
return DateUtils.addHours(new Date(), disabledHours);
}
private Date getDefaultRefreshInterval() {
return DateUtils.addMinutes(new Date(), refreshIntervalMinutes);
}
private Date computeRefreshIntervalForHeavyLoad(Feed feed, Date defaultRefreshInterval) {
Date now = new Date();
Date publishedDate = feed.getLastEntryDate();
Long averageEntryInterval = feed.getAverageEntryInterval();
if (publishedDate == null) {
// feed with no entries, recheck in 24 hours
return DateUtils.addHours(now, 24);
} else if (publishedDate.before(DateUtils.addMonths(now, -1))) {
// older than a month, recheck in 24 hours
return DateUtils.addHours(now, 24);
} else if (publishedDate.before(DateUtils.addDays(now, -14))) {
// older than two weeks, recheck in 12 hours
return DateUtils.addHours(now, 12);
} else if (publishedDate.before(DateUtils.addDays(now, -7))) {
// older than a week, recheck in 6 hours
return DateUtils.addHours(now, 6);
} else if (averageEntryInterval != null) {
// use average time between entries to decide when to refresh next, divided by factor
int factor = 2;
// not more than 6 hours
long date = Math.min(DateUtils.addHours(now, 6).getTime(), now.getTime() + averageEntryInterval / factor);
// not less than default refresh interval
date = Math.max(defaultRefreshInterval.getTime(), date);
return new Date(date);
} else {
// unknown case, recheck in 24 hours
return DateUtils.addHours(now, 24);
}
}
}

View File

@@ -1,85 +0,0 @@
package com.commafeed.backend.feed;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.FeedDAO;
import io.dropwizard.lifecycle.Managed;
import lombok.extern.slf4j.Slf4j;
/**
* Infinite loop fetching feeds from @FeedQueues and queuing them to the {@link FeedRefreshWorker} pool.
*
*/
@Slf4j
@Singleton
public class FeedRefreshTaskGiver implements Managed {
private final FeedQueues queues;
private final FeedRefreshWorker worker;
private final ExecutorService executor;
private final Meter feedRefreshed;
private final Meter threadWaited;
@Inject
public FeedRefreshTaskGiver(FeedQueues queues, FeedDAO feedDAO, FeedRefreshWorker worker, CommaFeedConfiguration config,
MetricRegistry metrics) {
this.queues = queues;
this.worker = worker;
executor = Executors.newFixedThreadPool(1);
feedRefreshed = metrics.meter(MetricRegistry.name(getClass(), "feedRefreshed"));
threadWaited = metrics.meter(MetricRegistry.name(getClass(), "threadWaited"));
}
@Override
public void stop() {
log.info("shutting down feed refresh task giver");
executor.shutdownNow();
while (!executor.isTerminated()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
log.error("interrupted while waiting for threads to finish.");
}
}
}
@Override
public void start() {
log.info("starting feed refresh task giver");
executor.execute(new Runnable() {
@Override
public void run() {
while (!executor.isShutdown()) {
try {
FeedRefreshContext context = queues.take();
if (context != null) {
feedRefreshed.mark();
worker.updateFeed(context);
} else {
log.debug("nothing to do, sleeping for 15s");
threadWaited.mark();
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
log.debug("interrupted while sleeping");
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
});
}
}

View File

@@ -20,36 +20,41 @@ import org.hibernate.SessionFactory;
import com.codahale.metrics.Meter; import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import com.commafeed.CommaFeedConfiguration; import com.commafeed.CommaFeedConfiguration;
import com.commafeed.CommaFeedConfiguration.ApplicationSettings;
import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.dao.UnitOfWork; import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.feed.FeedRefreshExecutor.Task;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.FeedEntryContent;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.service.FeedUpdateService; import com.commafeed.backend.service.FeedEntryService;
import com.commafeed.backend.service.FeedService;
import com.commafeed.backend.service.PubSubService; import com.commafeed.backend.service.PubSubService;
import com.commafeed.frontend.ws.WebSocketMessageBuilder;
import com.commafeed.frontend.ws.WebSocketSessions;
import com.google.common.util.concurrent.Striped; import com.google.common.util.concurrent.Striped;
import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.Managed;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/**
* Updates the feed in the database and inserts new entries
*/
@Slf4j @Slf4j
@Singleton @Singleton
public class FeedRefreshUpdater implements Managed { public class FeedRefreshUpdater implements Managed {
private final SessionFactory sessionFactory; private final SessionFactory sessionFactory;
private final FeedUpdateService feedUpdateService; private final FeedService feedService;
private final FeedEntryService feedEntryService;
private final PubSubService pubSubService; private final PubSubService pubSubService;
private final FeedQueues queues;
private final CommaFeedConfiguration config; private final CommaFeedConfiguration config;
private final FeedSubscriptionDAO feedSubscriptionDAO; private final FeedSubscriptionDAO feedSubscriptionDAO;
private final CacheService cache; private final CacheService cache;
private final WebSocketSessions webSocketSessions;
private final FeedRefreshExecutor pool;
private final Striped<Lock> locks; private final Striped<Lock> locks;
private final Meter entryCacheMiss; private final Meter entryCacheMiss;
@@ -58,21 +63,19 @@ public class FeedRefreshUpdater implements Managed {
private final Meter entryInserted; private final Meter entryInserted;
@Inject @Inject
public FeedRefreshUpdater(SessionFactory sessionFactory, FeedUpdateService feedUpdateService, PubSubService pubSubService, public FeedRefreshUpdater(SessionFactory sessionFactory, FeedService feedService, FeedEntryService feedEntryService,
FeedQueues queues, CommaFeedConfiguration config, MetricRegistry metrics, FeedSubscriptionDAO feedSubscriptionDAO, PubSubService pubSubService, CommaFeedConfiguration config, MetricRegistry metrics, FeedSubscriptionDAO feedSubscriptionDAO,
CacheService cache) { CacheService cache, WebSocketSessions webSocketSessions) {
this.sessionFactory = sessionFactory; this.sessionFactory = sessionFactory;
this.feedUpdateService = feedUpdateService; this.feedService = feedService;
this.feedEntryService = feedEntryService;
this.pubSubService = pubSubService; this.pubSubService = pubSubService;
this.queues = queues;
this.config = config; this.config = config;
this.feedSubscriptionDAO = feedSubscriptionDAO; this.feedSubscriptionDAO = feedSubscriptionDAO;
this.cache = cache; this.cache = cache;
this.webSocketSessions = webSocketSessions;
ApplicationSettings settings = config.getApplicationSettings(); locks = Striped.lazyWeakLock(100000);
int threads = Math.max(settings.getDatabaseUpdateThreads(), 1);
pool = new FeedRefreshExecutor("feed-refresh-updater", threads, Math.min(50 * threads, 1000), metrics);
locks = Striped.lazyWeakLock(threads * 100000);
entryCacheMiss = metrics.meter(MetricRegistry.name(getClass(), "entryCacheMiss")); entryCacheMiss = metrics.meter(MetricRegistry.name(getClass(), "entryCacheMiss"));
entryCacheHit = metrics.meter(MetricRegistry.name(getClass(), "entryCacheHit")); entryCacheHit = metrics.meter(MetricRegistry.name(getClass(), "entryCacheHit"));
@@ -80,22 +83,9 @@ public class FeedRefreshUpdater implements Managed {
entryInserted = metrics.meter(MetricRegistry.name(getClass(), "entryInserted")); entryInserted = metrics.meter(MetricRegistry.name(getClass(), "entryInserted"));
} }
@Override private AddEntryResult addEntry(final Feed feed, final FeedEntry entry, final List<FeedSubscription> subscriptions) {
public void start() throws Exception { boolean processed = false;
} boolean inserted = false;
@Override
public void stop() throws Exception {
log.info("shutting down feed refresh updater");
pool.shutdown();
}
public void updateFeed(FeedRefreshContext context) {
pool.execute(new EntryTask(context));
}
private boolean addEntry(final Feed feed, final FeedEntry entry, final List<FeedSubscription> subscriptions) {
boolean success = false;
// lock on feed, make sure we are not updating the same feed twice at // lock on feed, make sure we are not updating the same feed twice at
// the same time // the same time
@@ -112,14 +102,15 @@ public class FeedRefreshUpdater implements Managed {
boolean locked1 = false; boolean locked1 = false;
boolean locked2 = false; boolean locked2 = false;
try { try {
// try to lock, give up after 1 minute
locked1 = lock1.tryLock(1, TimeUnit.MINUTES); locked1 = lock1.tryLock(1, TimeUnit.MINUTES);
locked2 = lock2.tryLock(1, TimeUnit.MINUTES); locked2 = lock2.tryLock(1, TimeUnit.MINUTES);
if (locked1 && locked2) { if (locked1 && locked2) {
boolean inserted = UnitOfWork.call(sessionFactory, () -> feedUpdateService.addEntry(feed, entry, subscriptions)); processed = true;
inserted = UnitOfWork.call(sessionFactory, () -> feedEntryService.addEntry(feed, entry, subscriptions));
if (inserted) { if (inserted) {
entryInserted.mark(); entryInserted.mark();
} }
success = true;
} else { } else {
log.error("lock timeout for " + feed.getUrl() + " - " + key1); log.error("lock timeout for " + feed.getUrl() + " - " + key1);
} }
@@ -133,7 +124,7 @@ public class FeedRefreshUpdater implements Managed {
lock2.unlock(); lock2.unlock();
} }
} }
return success; return new AddEntryResult(processed, inserted);
} }
private void handlePubSub(final Feed feed) { private void handlePubSub(final Feed feed) {
@@ -159,68 +150,69 @@ public class FeedRefreshUpdater implements Managed {
} }
} }
private class EntryTask implements Task { public boolean update(Feed feed, List<FeedEntry> entries) {
boolean processed = true;
boolean insertedAtLeastOneEntry = false;
private final FeedRefreshContext context; if (!entries.isEmpty()) {
List<String> lastEntries = cache.getLastEntries(feed);
List<String> currentEntries = new ArrayList<>();
public EntryTask(FeedRefreshContext context) { List<FeedSubscription> subscriptions = null;
this.context = context; for (FeedEntry entry : entries) {
} String cacheKey = cache.buildUniqueEntryKey(feed, entry);
if (!lastEntries.contains(cacheKey)) {
@Override log.debug("cache miss for {}", entry.getUrl());
public void run() { if (subscriptions == null) {
boolean ok = true; subscriptions = UnitOfWork.call(sessionFactory, () -> feedSubscriptionDAO.findByFeed(feed));
final Feed feed = context.getFeed();
List<FeedEntry> entries = context.getEntries();
if (entries.isEmpty()) {
feed.setMessage("Feed has no entries");
} else {
List<String> lastEntries = cache.getLastEntries(feed);
List<String> currentEntries = new ArrayList<>();
List<FeedSubscription> subscriptions = null;
for (FeedEntry entry : entries) {
String cacheKey = cache.buildUniqueEntryKey(feed, entry);
if (!lastEntries.contains(cacheKey)) {
log.debug("cache miss for {}", entry.getUrl());
if (subscriptions == null) {
subscriptions = UnitOfWork.call(sessionFactory, () -> feedSubscriptionDAO.findByFeed(feed));
}
ok &= addEntry(feed, entry, subscriptions);
entryCacheMiss.mark();
} else {
log.debug("cache hit for {}", entry.getUrl());
entryCacheHit.mark();
} }
AddEntryResult addEntryResult = addEntry(feed, entry, subscriptions);
processed &= addEntryResult.processed;
insertedAtLeastOneEntry |= addEntryResult.inserted;
currentEntries.add(cacheKey); entryCacheMiss.mark();
} else {
log.debug("cache hit for {}", entry.getUrl());
entryCacheHit.mark();
} }
cache.setLastEntries(feed, currentEntries);
if (subscriptions == null) { currentEntries.add(cacheKey);
feed.setMessage("No new entries found");
} else if (!subscriptions.isEmpty()) {
List<User> users = subscriptions.stream().map(FeedSubscription::getUser).collect(Collectors.toList());
cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
cache.invalidateUserRootCategory(users.toArray(new User[0]));
}
} }
cache.setLastEntries(feed, currentEntries);
if (config.getApplicationSettings().getPubsubhubbub()) { if (subscriptions == null) {
handlePubSub(feed); feed.setMessage("No new entries found");
} } else if (insertedAtLeastOneEntry) {
if (!ok) { List<User> users = subscriptions.stream().map(FeedSubscription::getUser).collect(Collectors.toList());
// requeue asap cache.invalidateUnreadCount(subscriptions.toArray(new FeedSubscription[0]));
feed.setDisabledUntil(new Date(0)); cache.invalidateUserRootCategory(users.toArray(new User[0]));
// notify over websocket
subscriptions.forEach(sub -> webSocketSessions.sendMessage(sub.getUser(), WebSocketMessageBuilder.newFeedEntries(sub)));
} }
}
if (Boolean.TRUE.equals(config.getApplicationSettings().getPubsubhubbub())) {
handlePubSub(feed);
}
if (!processed) {
// requeue asap
feed.setDisabledUntil(new Date(0));
}
if (insertedAtLeastOneEntry) {
feedUpdated.mark(); feedUpdated.mark();
queues.giveBack(feed);
} }
@Override UnitOfWork.run(sessionFactory, () -> feedService.save(feed));
public boolean isUrgent() {
return context.isUrgent(); return processed;
} }
@AllArgsConstructor
private static class AddEntryResult {
private final boolean processed;
private final boolean inserted;
} }
} }

View File

@@ -1,6 +1,6 @@
package com.commafeed.backend.feed; package com.commafeed.backend.feed;
import java.util.Date; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -10,117 +10,100 @@ import javax.inject.Singleton;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import com.commafeed.CommaFeedConfiguration; import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.HttpGetter.NotModifiedException; import com.commafeed.backend.HttpGetter.NotModifiedException;
import com.commafeed.backend.feed.FeedRefreshExecutor.Task; import com.commafeed.backend.feed.FeedFetcher.FeedFetcherResult;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import io.dropwizard.lifecycle.Managed; import lombok.Value;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
* Calls {@link FeedFetcher} and handles its outcome * Calls {@link FeedFetcher} and updates the Feed object, but does not update the database ({@link FeedRefreshUpdater} does that)
*
*/ */
@Slf4j @Slf4j
@Singleton @Singleton
public class FeedRefreshWorker implements Managed { public class FeedRefreshWorker {
private final FeedRefreshUpdater feedRefreshUpdater; private final FeedRefreshIntervalCalculator refreshIntervalCalculator;
private final FeedFetcher fetcher; private final FeedFetcher fetcher;
private final FeedQueues queues;
private final CommaFeedConfiguration config; private final CommaFeedConfiguration config;
private final FeedRefreshExecutor pool; private final Meter feedFetched;
@Inject @Inject
public FeedRefreshWorker(FeedRefreshUpdater feedRefreshUpdater, FeedFetcher fetcher, FeedQueues queues, CommaFeedConfiguration config, public FeedRefreshWorker(FeedRefreshIntervalCalculator refreshIntervalCalculator, FeedFetcher fetcher, CommaFeedConfiguration config,
MetricRegistry metrics) { MetricRegistry metrics) {
this.feedRefreshUpdater = feedRefreshUpdater; this.refreshIntervalCalculator = refreshIntervalCalculator;
this.fetcher = fetcher; this.fetcher = fetcher;
this.config = config; this.config = config;
this.queues = queues; this.feedFetched = metrics.meter(MetricRegistry.name(getClass(), "feedFetched"));
int threads = config.getApplicationSettings().getBackgroundThreads();
pool = new FeedRefreshExecutor("feed-refresh-worker", threads, Math.min(20 * threads, 1000), metrics);
} }
@Override public FeedRefreshWorkerResult update(Feed feed) {
public void start() throws Exception {
}
@Override
public void stop() throws Exception {
pool.shutdown();
}
public void updateFeed(FeedRefreshContext context) {
pool.execute(new FeedTask(context));
}
private void update(FeedRefreshContext context) {
Feed feed = context.getFeed();
int refreshInterval = config.getApplicationSettings().getRefreshIntervalMinutes();
Date disabledUntil = DateUtils.addMinutes(new Date(), refreshInterval);
try { try {
String url = Optional.ofNullable(feed.getUrlAfterRedirect()).orElse(feed.getUrl()); String url = Optional.ofNullable(feed.getUrlAfterRedirect()).orElse(feed.getUrl());
FetchedFeed fetchedFeed = fetcher.fetch(url, false, feed.getLastModifiedHeader(), feed.getEtagHeader(), FeedFetcherResult feedFetcherResult = fetcher.fetch(url, false, feed.getLastModifiedHeader(), feed.getEtagHeader(),
feed.getLastPublishedDate(), feed.getLastContentHash()); feed.getLastPublishedDate(), feed.getLastContentHash());
// stops here if NotModifiedException or any other exception is thrown // stops here if NotModifiedException or any other exception is thrown
List<FeedEntry> entries = fetchedFeed.getEntries(); List<FeedEntry> entries = feedFetcherResult.getEntries();
Integer maxFeedCapacity = config.getApplicationSettings().getMaxFeedCapacity(); Integer maxFeedCapacity = config.getApplicationSettings().getMaxFeedCapacity();
if (maxFeedCapacity > 0) { if (maxFeedCapacity > 0) {
entries = entries.stream().limit(maxFeedCapacity).collect(Collectors.toList()); entries = entries.stream().limit(maxFeedCapacity).collect(Collectors.toList());
} }
if (config.getApplicationSettings().getHeavyLoad()) { String urlAfterRedirect = feedFetcherResult.getUrlAfterRedirect();
disabledUntil = FeedUtils.buildDisabledUntil(fetchedFeed.getFeed().getLastEntryDate(),
fetchedFeed.getFeed().getAverageEntryInterval(), disabledUntil);
}
String urlAfterRedirect = fetchedFeed.getUrlAfterRedirect();
if (StringUtils.equals(url, urlAfterRedirect)) { if (StringUtils.equals(url, urlAfterRedirect)) {
urlAfterRedirect = null; urlAfterRedirect = null;
} }
feed.setUrlAfterRedirect(urlAfterRedirect); feed.setUrlAfterRedirect(urlAfterRedirect);
feed.setLink(fetchedFeed.getFeed().getLink()); feed.setLink(feedFetcherResult.getFeed().getLink());
feed.setLastModifiedHeader(fetchedFeed.getFeed().getLastModifiedHeader()); feed.setLastModifiedHeader(feedFetcherResult.getFeed().getLastModifiedHeader());
feed.setEtagHeader(fetchedFeed.getFeed().getEtagHeader()); feed.setEtagHeader(feedFetcherResult.getFeed().getEtagHeader());
feed.setLastContentHash(fetchedFeed.getFeed().getLastContentHash()); feed.setLastContentHash(feedFetcherResult.getFeed().getLastContentHash());
feed.setLastPublishedDate(fetchedFeed.getFeed().getLastPublishedDate()); feed.setLastPublishedDate(feedFetcherResult.getFeed().getLastPublishedDate());
feed.setAverageEntryInterval(fetchedFeed.getFeed().getAverageEntryInterval()); feed.setAverageEntryInterval(feedFetcherResult.getFeed().getAverageEntryInterval());
feed.setLastEntryDate(fetchedFeed.getFeed().getLastEntryDate()); feed.setLastEntryDate(feedFetcherResult.getFeed().getLastEntryDate());
feed.setErrorCount(0); feed.setErrorCount(0);
feed.setMessage(null); feed.setMessage(null);
feed.setDisabledUntil(disabledUntil); feed.setDisabledUntil(refreshIntervalCalculator.onFetchSuccess(feedFetcherResult.getFeed()));
handlePubSub(feed, fetchedFeed.getFeed()); handlePubSub(feed, feedFetcherResult.getFeed());
context.setEntries(entries);
feedRefreshUpdater.updateFeed(context);
return new FeedRefreshWorkerResult(feed, entries);
} catch (NotModifiedException e) { } catch (NotModifiedException e) {
log.debug("Feed not modified : {} - {}", feed.getUrl(), e.getMessage()); log.debug("Feed not modified : {} - {}", feed.getUrl(), e.getMessage());
if (config.getApplicationSettings().getHeavyLoad()) {
disabledUntil = FeedUtils.buildDisabledUntil(feed.getLastEntryDate(), feed.getAverageEntryInterval(), disabledUntil);
}
feed.setErrorCount(0); feed.setErrorCount(0);
feed.setMessage(e.getMessage()); feed.setMessage(e.getMessage());
feed.setDisabledUntil(disabledUntil); feed.setDisabledUntil(refreshIntervalCalculator.onFeedNotModified(feed));
queues.giveBack(feed); if (e.getNewLastModifiedHeader() != null) {
feed.setLastModifiedHeader(e.getNewLastModifiedHeader());
}
if (e.getNewEtagHeader() != null) {
feed.setEtagHeader(e.getNewEtagHeader());
}
return new FeedRefreshWorkerResult(feed, Collections.emptyList());
} catch (Exception e) { } catch (Exception e) {
String message = "Unable to refresh feed " + feed.getUrl() + " : " + e.getMessage(); String message = "Unable to refresh feed " + feed.getUrl() + " : " + e.getMessage();
log.debug(e.getClass().getName() + " " + message, e); log.debug(e.getClass().getName() + " " + message, e);
feed.setErrorCount(feed.getErrorCount() + 1); feed.setErrorCount(feed.getErrorCount() + 1);
feed.setMessage(message); feed.setMessage(message);
feed.setDisabledUntil(FeedUtils.buildDisabledUntil(feed.getErrorCount())); feed.setDisabledUntil(refreshIntervalCalculator.onFetchError(feed));
queues.giveBack(feed); return new FeedRefreshWorkerResult(feed, Collections.emptyList());
} finally {
feedFetched.mark();
} }
} }
@@ -146,22 +129,10 @@ public class FeedRefreshWorker implements Managed {
} }
} }
private class FeedTask implements Task { @Value
public static class FeedRefreshWorkerResult {
private final FeedRefreshContext context; Feed feed;
List<FeedEntry> entries;
public FeedTask(FeedRefreshContext context) {
this.context = context;
}
@Override
public void run() {
update(context);
}
@Override
public boolean isUrgent() {
return context.isUrgent();
}
} }
} }

View File

@@ -8,7 +8,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -20,7 +19,6 @@ import org.ahocorasick.trie.Trie.TrieBuilder;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics; import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
@@ -30,6 +28,8 @@ import org.jsoup.nodes.Entities.EscapeMode;
import org.jsoup.safety.Cleaner; import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Safelist; import org.jsoup.safety.Safelist;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import org.netpreserve.urlcanon.Canonicalizer;
import org.netpreserve.urlcanon.ParsedUrl;
import org.w3c.css.sac.InputSource; import org.w3c.css.sac.InputSource;
import org.w3c.dom.css.CSSStyleDeclaration; import org.w3c.dom.css.CSSStyleDeclaration;
@@ -43,7 +43,6 @@ import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch; import com.ibm.icu.text.CharsetMatch;
import com.steadystate.css.parser.CSSOMParser; import com.steadystate.css.parser.CSSOMParser;
import edu.uci.ics.crawler4j.url.URLCanonicalizer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
@@ -181,7 +180,10 @@ public class FeedUtils {
if (url == null) { if (url == null) {
return null; return null;
} }
String normalized = URLCanonicalizer.getCanonicalURL(url);
ParsedUrl parsedUrl = ParsedUrl.parseUrl(url);
Canonicalizer.AGGRESSIVE.canonicalize(parsedUrl);
String normalized = parsedUrl.toString();
if (normalized == null) { if (normalized == null) {
normalized = url; normalized = url;
} }
@@ -359,57 +361,6 @@ public class FeedUtils {
return sb.toString(); return sb.toString();
} }
/**
* When there was an error fetching the feed
*
*/
public static Date buildDisabledUntil(int errorCount) {
Date now = new Date();
int retriesBeforeDisable = 3;
if (errorCount >= retriesBeforeDisable) {
int disabledHours = errorCount - retriesBeforeDisable + 1;
disabledHours = Math.min(24 * 7, disabledHours);
return DateUtils.addHours(now, disabledHours);
}
return now;
}
/**
* When the feed was refreshed successfully
*/
public static Date buildDisabledUntil(Date publishedDate, Long averageEntryInterval, Date defaultRefreshInterval) {
Date now = new Date();
if (publishedDate == null) {
// feed with no entries, recheck in 24 hours
return DateUtils.addHours(now, 24);
} else if (publishedDate.before(DateUtils.addMonths(now, -1))) {
// older than a month, recheck in 24 hours
return DateUtils.addHours(now, 24);
} else if (publishedDate.before(DateUtils.addDays(now, -14))) {
// older than two weeks, recheck in 12 hours
return DateUtils.addHours(now, 12);
} else if (publishedDate.before(DateUtils.addDays(now, -7))) {
// older than a week, recheck in 6 hours
return DateUtils.addHours(now, 6);
} else if (averageEntryInterval != null) {
// use average time between entries to decide when to refresh next, divided by factor
int factor = 2;
// not more than 6 hours
long date = Math.min(DateUtils.addHours(now, 6).getTime(), now.getTime() + averageEntryInterval / factor);
// not less than default refresh interval
date = Math.max(defaultRefreshInterval.getTime(), date);
return new Date(date);
} else {
// unknown case, recheck in 24 hours
return DateUtils.addHours(now, 24);
}
}
public static Long averageTimeBetweenEntries(List<FeedEntry> entries) { public static Long averageTimeBetweenEntries(List<FeedEntry> entries) {
if (entries.isEmpty() || entries.size() == 1) { if (entries.isEmpty() || entries.size() == 1) {
return null; return null;

View File

@@ -1,23 +0,0 @@
package com.commafeed.backend.feed;
import java.util.ArrayList;
import java.util.List;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class FetchedFeed {
private Feed feed = new Feed();
private List<FeedEntry> entries = new ArrayList<>();
private String title;
private String urlAfterRedirect;
private long fetchDuration;
}

View File

@@ -1,11 +1,9 @@
package com.commafeed.backend.model; package com.commafeed.backend.model;
import java.util.Date; import java.util.Date;
import java.util.Set;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
@@ -123,7 +121,4 @@ public class Feed extends AbstractModel {
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date pushLastPing; private Date pushLastPing;
@OneToMany(mappedBy = "feed")
private Set<FeedSubscription> subscriptions;
} }

View File

@@ -31,7 +31,7 @@ public class UserSettings extends AbstractModel {
} }
public enum ViewMode { public enum ViewMode {
title, cozy, expanded title, cozy, detailed, expanded
} }
@OneToOne(fetch = FetchType.LAZY) @OneToOne(fetch = FetchType.LAZY)

View File

@@ -25,7 +25,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject })) @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton @Singleton
public class StartupService implements Managed { public class DatabaseStartupService implements Managed {
private final SessionFactory sessionFactory; private final SessionFactory sessionFactory;
private final UserDAO userDAO; private final UserDAO userDAO;

View File

@@ -7,18 +7,24 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.apache.commons.codec.digest.DigestUtils;
import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedEntryDAO; import com.commafeed.backend.dao.FeedEntryDAO;
import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.feed.FeedEntryKeyword; import com.commafeed.backend.feed.FeedEntryKeyword;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent;
import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject })) @RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton @Singleton
public class FeedEntryService { public class FeedEntryService {
@@ -26,8 +32,45 @@ public class FeedEntryService {
private final FeedSubscriptionDAO feedSubscriptionDAO; private final FeedSubscriptionDAO feedSubscriptionDAO;
private final FeedEntryDAO feedEntryDAO; private final FeedEntryDAO feedEntryDAO;
private final FeedEntryStatusDAO feedEntryStatusDAO; private final FeedEntryStatusDAO feedEntryStatusDAO;
private final FeedEntryContentService feedEntryContentService;
private final FeedEntryFilteringService feedEntryFilteringService;
private final CacheService cache; private final CacheService cache;
/**
* this is NOT thread-safe
*/
public boolean addEntry(Feed feed, FeedEntry entry, List<FeedSubscription> subscriptions) {
Long existing = feedEntryDAO.findExisting(entry.getGuid(), feed);
if (existing != null) {
return false;
}
FeedEntryContent content = feedEntryContentService.findOrCreate(entry.getContent(), feed.getLink());
entry.setGuidHash(DigestUtils.sha1Hex(entry.getGuid()));
entry.setContent(content);
entry.setInserted(new Date());
entry.setFeed(feed);
feedEntryDAO.saveOrUpdate(entry);
// if filter does not match the entry, mark it as read
for (FeedSubscription sub : subscriptions) {
boolean matches = true;
try {
matches = feedEntryFilteringService.filterMatchesEntry(sub.getFilter(), entry);
} catch (FeedEntryFilteringService.FeedEntryFilterException e) {
log.error("could not evaluate filter {}", sub.getFilter(), e);
}
if (!matches) {
FeedEntryStatus status = new FeedEntryStatus(sub.getUser(), sub, entry);
status.setRead(true);
feedEntryStatusDAO.saveOrUpdate(status);
}
}
return true;
}
public void markEntry(User user, Long entryId, boolean read) { public void markEntry(User user, Long entryId, boolean read) {
FeedEntry entry = feedEntryDAO.findById(entryId); FeedEntry entry = feedEntryDAO.findById(entryId);

Some files were not shown because too many files have changed in this diff Show More