mirror of
https://github.com/Athou/commafeed.git
synced 2026-10-24 14:09:23 +00:00
Compare commits
473 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d90ccf856 | ||
|
|
7f77cf0268 | ||
|
|
f3c390e0e0 | ||
|
|
1fdc471197 | ||
|
|
ecf5d9e50e | ||
|
|
97a49e8faa | ||
|
|
f5a020cdc2 | ||
|
|
1f0507ee65 | ||
|
|
393c90f334 | ||
|
|
03ecc2f49b | ||
|
|
17743adce6 | ||
|
|
126dde103a | ||
|
|
14a25581b4 | ||
|
|
c7c95efb3a | ||
|
|
4c1293d357 | ||
|
|
76a92ea44c | ||
|
|
ba454322db | ||
|
|
a3b9f471e1 | ||
|
|
d58ddc888e | ||
|
|
fe9837c45c | ||
|
|
332d126bd7 | ||
|
|
0c66bf2695 | ||
|
|
4d6d07c98b | ||
|
|
8f7d4bfa10 | ||
|
|
7b3f39001f | ||
|
|
b9630809a0 | ||
|
|
ce3244da5e | ||
|
|
4067297d2a | ||
|
|
03c11fa181 | ||
|
|
3676e78eb9 | ||
|
|
1e08c4486f | ||
|
|
fd8bb60daa | ||
|
|
557d56ffc5 | ||
|
|
30f76a2349 | ||
|
|
cf0fa5e22e | ||
|
|
f16becbd82 | ||
|
|
4855450ddc | ||
|
|
0733aedce5 | ||
|
|
399ad14d9b | ||
|
|
7881ea3c5c | ||
|
|
7e9de622da | ||
|
|
c6655078f2 | ||
|
|
b86cef9499 | ||
|
|
5375ab6e97 | ||
|
|
75920a6473 | ||
|
|
f474e76866 | ||
|
|
d0ae27c4ce | ||
|
|
ab191dded6 | ||
|
|
950b571a9f | ||
|
|
7a153ca69a | ||
|
|
effd8946de | ||
|
|
ae3ad3745c | ||
|
|
2e1e5257b4 | ||
|
|
bc6e767953 | ||
|
|
b9eceaae73 | ||
|
|
8ad9ba7d74 | ||
|
|
10bb4b7936 | ||
|
|
ab3127cef9 | ||
|
|
5d64e2f21e | ||
|
|
e404c9ce3b | ||
|
|
0c5a71650c | ||
|
|
8ce9eb8537 | ||
|
|
1ce0e4af97 | ||
|
|
44ad42a8a5 | ||
|
|
f213726c40 | ||
|
|
972d424846 | ||
|
|
6049263137 | ||
|
|
ae2dc7f066 | ||
|
|
503d129f22 | ||
|
|
543ec3bbb1 | ||
|
|
bed45127c4 | ||
|
|
71f10b0a62 | ||
|
|
cb235289c4 | ||
|
|
0b07c65320 | ||
|
|
868f65d7c9 | ||
|
|
07715383f4 | ||
|
|
29d10debdb | ||
|
|
cd640da960 | ||
|
|
56ecb4a2f4 | ||
|
|
00161fd549 | ||
|
|
a602ce17cd | ||
|
|
672588d08b | ||
|
|
5fa2579275 | ||
|
|
09bc317797 | ||
|
|
440c1ada50 | ||
|
|
911a5ca099 | ||
|
|
65017f6b76 | ||
|
|
64fb5ef3a2 | ||
|
|
dda389a50f | ||
|
|
419ad4fb6a | ||
|
|
ebae46f06b | ||
|
|
ac25accb31 | ||
|
|
3c7eb90e50 | ||
|
|
4b3973f3fa | ||
|
|
6ddbe40c3c | ||
|
|
d5164750e4 | ||
|
|
822fbd8add | ||
|
|
3a341d0707 | ||
|
|
23884fcbda | ||
|
|
3b16674f98 | ||
|
|
d5eb3e1bb1 | ||
|
|
8d47b38fd4 | ||
|
|
04fb3282fa | ||
|
|
bcce130b82 | ||
|
|
e5d336f6ba | ||
|
|
2d741f6e1e | ||
|
|
2968dff118 | ||
|
|
0c244ec154 | ||
|
|
34cd337992 | ||
|
|
1703cb7b93 | ||
|
|
dc89faa4a1 | ||
|
|
3eccfe8ccc | ||
|
|
6e34f6831c | ||
|
|
b7ebcd6a23 | ||
|
|
8b7efe056d | ||
|
|
87cb392516 | ||
|
|
aa11cb48f2 | ||
|
|
36a29a393c | ||
|
|
99f1b45866 | ||
|
|
9e8d529af3 | ||
|
|
bb80641b40 | ||
|
|
a2ed16c6fd | ||
|
|
4f10f57837 | ||
|
|
28f40cd6fe | ||
|
|
89a84a0c83 | ||
|
|
ebcda50a34 | ||
|
|
8640e62b77 | ||
|
|
d0418eea89 | ||
|
|
41ba067daf | ||
|
|
53346997b6 | ||
|
|
87a632761e | ||
|
|
998ebc43a3 | ||
|
|
945e312c41 | ||
|
|
963a562100 | ||
|
|
4be961b68b | ||
|
|
02a9140c13 | ||
|
|
5ea60ce6d6 | ||
|
|
e9e7be0fc6 | ||
|
|
60c30f2683 | ||
|
|
a17e69d37d | ||
|
|
157f75d33e | ||
|
|
30a14c73ff | ||
|
|
27cece3afb | ||
|
|
8124ecb51f | ||
|
|
e67c4c8670 | ||
|
|
fcc6798d63 | ||
|
|
d313ebfc69 | ||
|
|
6edb8f00f6 | ||
|
|
98a261d457 | ||
|
|
e97a69f475 | ||
|
|
e9dc284b26 | ||
|
|
856c52c64b | ||
|
|
386a4a32ef | ||
|
|
dbdb40ff01 | ||
|
|
1bd9b71215 | ||
|
|
f4dc279ec9 | ||
|
|
ca8438623d | ||
|
|
b81aac71f5 | ||
|
|
a4e0d28fa2 | ||
|
|
56d4e4bbb1 | ||
|
|
af1d10529b | ||
|
|
a5f84a3f6d | ||
|
|
69dfbc81cc | ||
|
|
34408ccdb0 | ||
|
|
b0522a6e5d | ||
|
|
2a559a07c0 | ||
|
|
bdf2a6a915 | ||
|
|
e814f75872 | ||
|
|
c0e0791854 | ||
|
|
17dbdb66bf | ||
|
|
6e3e9add36 | ||
|
|
5b25011853 | ||
|
|
804b24b2d6 | ||
|
|
de98b0f7c2 | ||
|
|
709de70fa9 | ||
|
|
1b7bc11f9d | ||
|
|
bbcaacf3dd | ||
|
|
e816b1262c | ||
|
|
fc019333dc | ||
|
|
56b0ddbc34 | ||
|
|
bb74d27d52 | ||
|
|
7b1d19ca71 | ||
|
|
af961678bd | ||
|
|
1579242644 | ||
|
|
2a45f5c596 | ||
|
|
c4560c49b0 | ||
|
|
2b7f75d9ef | ||
|
|
5f68bcbd46 | ||
|
|
c93c072259 | ||
|
|
516344a993 | ||
|
|
4d42fe5a0b | ||
|
|
47bf132fff | ||
|
|
04a9d48505 | ||
|
|
3716ddcf15 | ||
|
|
6c3ab40cb6 | ||
|
|
b7270974e5 | ||
|
|
9bfc53e77a | ||
|
|
f34fac8d17 | ||
|
|
555c11649e | ||
|
|
c308c5d606 | ||
|
|
54e9615994 | ||
|
|
b0b3b323fd | ||
|
|
f740a0b8ba | ||
|
|
e154675e72 | ||
|
|
2b3d3261de | ||
|
|
fab525ca56 | ||
|
|
1c273d1c1a | ||
|
|
90d684367c | ||
|
|
1ffc544ab0 | ||
|
|
cde0cceee1 | ||
|
|
1604fbc55c | ||
|
|
6f586e8866 | ||
|
|
c53455ad61 | ||
|
|
77b3c609f3 | ||
|
|
3b84598d99 | ||
|
|
6f536c0981 | ||
|
|
1f881ea252 | ||
|
|
b14df1b3cb | ||
|
|
e13763baa3 | ||
|
|
502ef67a71 | ||
|
|
05cab626df | ||
|
|
c7d54af7ad | ||
|
|
ad195fcd80 | ||
|
|
b87c68d598 | ||
|
|
33af37ed2e | ||
|
|
c4113fc92b | ||
|
|
ed40025d26 | ||
|
|
752008a551 | ||
|
|
a8bf970dca | ||
|
|
5b94bcdfdd | ||
|
|
a66a0e9485 | ||
|
|
80755e15b8 | ||
|
|
8cede0c6e0 | ||
|
|
9556f0f7d2 | ||
|
|
1b80f3060c | ||
|
|
00811f0be3 | ||
|
|
0fdfc2c59c | ||
|
|
8d25562a76 | ||
|
|
1d0d9d4be6 | ||
|
|
9ed4559625 | ||
|
|
2a53e8f15d | ||
|
|
15fbd18f31 | ||
|
|
2c248a7ca1 | ||
|
|
94d8fdb4ab | ||
|
|
3596439ed4 | ||
|
|
bc732a52ce | ||
|
|
ef096899a2 | ||
|
|
bf4bcf740e | ||
|
|
679281ac26 | ||
|
|
bde3d1408b | ||
|
|
3621a96d3f | ||
|
|
6583ad114f | ||
|
|
f4e42165bf | ||
|
|
00d7be735a | ||
|
|
1388fba8e4 | ||
|
|
c43644861f | ||
|
|
831ca599b8 | ||
|
|
43fbd03292 | ||
|
|
1efd04f6c5 | ||
|
|
e89ccfd74b | ||
|
|
622486160c | ||
|
|
6d172cae9e | ||
|
|
91b60fb147 | ||
|
|
3f9b7f48d5 | ||
|
|
3599fdec65 | ||
|
|
00e86974b3 | ||
|
|
2aa897eb80 | ||
|
|
ce9706b860 | ||
|
|
e140dfe556 | ||
|
|
4a1b845ac2 | ||
|
|
573f8af57e | ||
|
|
33445c93e8 | ||
|
|
f7fcb659f7 | ||
|
|
b0aa723c2f | ||
|
|
48dca41738 | ||
|
|
873d6f487f | ||
|
|
c8d73b129e | ||
|
|
c63e9e1cd2 | ||
|
|
def2ad84bb | ||
|
|
52510e9817 | ||
|
|
69a95488ce | ||
|
|
636841f647 | ||
|
|
1ea521236c | ||
|
|
6966efeda5 | ||
|
|
66ad0c3548 | ||
|
|
e061ccbf79 | ||
|
|
30cc6a111d | ||
|
|
8416ce5124 | ||
|
|
0cacfe391b | ||
|
|
c0eaab48cd | ||
|
|
c696bc3545 | ||
|
|
72505954a9 | ||
|
|
49159dba5c | ||
|
|
41e2d89abc | ||
|
|
6e7de97d93 | ||
|
|
2f20e6b498 | ||
|
|
ecf9158fd7 | ||
|
|
dc7e7321f3 | ||
|
|
0f087ad2a0 | ||
|
|
ff19a6e9eb | ||
|
|
8cec0c0779 | ||
|
|
314384f8e7 | ||
|
|
6c75ad95ad | ||
|
|
76e3b3caff | ||
|
|
c8f71480c9 | ||
|
|
9172a0cf9c | ||
|
|
eb476c6023 | ||
|
|
da4f3bd38e | ||
|
|
c65d628a36 | ||
|
|
589b8c3afc | ||
|
|
2684051da0 | ||
|
|
200db68d05 | ||
|
|
f6d8d32a01 | ||
|
|
72d5af5bb0 | ||
|
|
aabbc54cbb | ||
|
|
09b523bf56 | ||
|
|
5dc7f522bf | ||
|
|
fd58a42824 | ||
|
|
0a6d1c85ed | ||
|
|
13f84a260c | ||
|
|
4132ad9a97 | ||
|
|
1faba92ecc | ||
|
|
b93fc86663 | ||
|
|
7123036e8d | ||
|
|
cce4ea4dc6 | ||
|
|
b8f7df1555 | ||
|
|
ba8cc0477c | ||
|
|
010026bc88 | ||
|
|
a1ba34caf2 | ||
|
|
1214380bee | ||
|
|
03eb09c68c | ||
|
|
055d503862 | ||
|
|
5755ff762b | ||
|
|
88e24787e8 | ||
|
|
ed32d07ac4 | ||
|
|
3a917fcebe | ||
|
|
3535068ca8 | ||
|
|
4c39d4b96b | ||
|
|
8e864c6793 | ||
|
|
aeffecced3 | ||
|
|
4be42dc1e2 | ||
|
|
bdf6c62c29 | ||
|
|
a4592e5199 | ||
|
|
a5268f082d | ||
|
|
b556e72922 | ||
|
|
3602d2533a | ||
|
|
681f2c766a | ||
|
|
6e0a136cd1 | ||
|
|
9e9945a1cc | ||
|
|
3368cead8b | ||
|
|
a7b5126b4a | ||
|
|
a9ef1a03dc | ||
|
|
bc0ec8dbfa | ||
|
|
7bef009922 | ||
|
|
9a467bc27b | ||
|
|
e3934c9403 | ||
|
|
af337aa4d0 | ||
|
|
313d3a01ec | ||
|
|
feb9876cf5 | ||
|
|
9c3a5cb820 | ||
|
|
e219f781bf | ||
|
|
21ecd6ae05 | ||
|
|
2449902c3d | ||
|
|
c1a55037ba | ||
|
|
646a8122c0 | ||
|
|
edc4e6c508 | ||
|
|
5ddeb7b156 | ||
|
|
306624e55f | ||
|
|
6a9ca65cbc | ||
|
|
7db0026a0c | ||
|
|
7358705248 | ||
|
|
0b9ee2eec9 | ||
|
|
42eaf7fc39 | ||
|
|
6783cac9b4 | ||
|
|
628106ce88 | ||
|
|
1ce69b31a7 | ||
|
|
912ac11842 | ||
|
|
5f2fad560c | ||
|
|
b300f364a7 | ||
|
|
049ec41d13 | ||
|
|
f0d17bc84e | ||
|
|
cc442d7409 | ||
|
|
80bac26f21 | ||
|
|
015c39e6f4 | ||
|
|
e0ec3a4ebd | ||
|
|
a022de5cc3 | ||
|
|
a7b5e1e18c | ||
|
|
b8695df1c8 | ||
|
|
5e4382d1d2 | ||
|
|
ce983f4906 | ||
|
|
4a8be29616 | ||
|
|
1622eb642a | ||
|
|
422d992484 | ||
|
|
1065af1233 | ||
|
|
dde39d83be | ||
|
|
94fe7617bf | ||
|
|
e781d85b7c | ||
|
|
c30c9ca380 | ||
|
|
c395916af9 | ||
|
|
1a4037e360 | ||
|
|
f207bfaddd | ||
|
|
3dd298c0c8 | ||
|
|
8e2e4da7c3 | ||
|
|
418e3c109c | ||
|
|
cfda63a609 | ||
|
|
ba68276bfb | ||
|
|
97c700024a | ||
|
|
d4f67d726c | ||
|
|
ea87fa3e5a | ||
|
|
d12d247b3e | ||
|
|
c4a23915bd | ||
|
|
8645c57d13 | ||
|
|
b170e0bc17 | ||
|
|
4199f514f9 | ||
|
|
c96efb3bb4 | ||
|
|
d5571e0196 | ||
|
|
9f7f87b4ae | ||
|
|
4b03fce85d | ||
|
|
4b28d1c635 | ||
|
|
a5e803177f | ||
|
|
9462f284a4 | ||
|
|
4b0f6bd61b | ||
|
|
d6c85214ef | ||
|
|
de770cf801 | ||
|
|
dae0f740c2 | ||
|
|
9452c3e74c | ||
|
|
f826f1d543 | ||
|
|
fd13c80e5a | ||
|
|
75871348d5 | ||
|
|
0140e88595 | ||
|
|
e620b1254b | ||
|
|
77cfcf6c58 | ||
|
|
04e9df3d0a | ||
|
|
a953134089 | ||
|
|
88b9c0d783 | ||
|
|
ff7e0ba95b | ||
|
|
53dfbcaf00 | ||
|
|
d8fca22c00 | ||
|
|
4fb5963bd9 | ||
|
|
862d81f446 | ||
|
|
8ae7943b94 | ||
|
|
46e947d779 | ||
|
|
7780d6020f | ||
|
|
e5803b46f2 | ||
|
|
d776090298 | ||
|
|
f2f3111d49 | ||
|
|
dff1b957cd | ||
|
|
b1ec50c3cb | ||
|
|
85aa188287 | ||
|
|
e7d6526da9 | ||
|
|
d67c6fcc00 | ||
|
|
d9bf084300 | ||
|
|
da3977b0e2 | ||
|
|
7df930bccc | ||
|
|
11f73a1a53 | ||
|
|
2bbcd55e48 | ||
|
|
43890b8d5c | ||
|
|
b6b8e3741b | ||
|
|
c39f00c336 | ||
|
|
e48eaacfef | ||
|
|
6d97691388 | ||
|
|
d64cc996bc | ||
|
|
617d66c9b9 | ||
|
|
0fc50d2105 | ||
|
|
2bb9f28569 | ||
|
|
c1580b8359 | ||
|
|
dde235825d | ||
|
|
eac13b4025 | ||
|
|
59cc9c2706 | ||
|
|
40e20a0d33 | ||
|
|
a42679acaa | ||
|
|
fd2e8fcdb8 | ||
|
|
ebd5a15152 |
52
.github/workflows/ci.yml
vendored
52
.github/workflows/ci.yml
vendored
@@ -23,18 +23,24 @@ jobs:
|
||||
steps:
|
||||
# Checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Setup
|
||||
- name: Set up GraalVM
|
||||
uses: graalvm/setup-graalvm@60c26726de13f8b90771df4bc1641a52a3159994 # v1
|
||||
uses: graalvm/setup-graalvm@5298d94fb55a4f185c602eeac5de1b553882abe2 # v1.6.4
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
distribution: "graalvm"
|
||||
cache: "maven"
|
||||
|
||||
- name: Set up NodeJS cache
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: 'commafeed-client/package-lock.json'
|
||||
|
||||
- name: Install Playwright dependencies
|
||||
run: sudo apt-get install -y libgbm1
|
||||
if: matrix.os != 'windows-latest'
|
||||
@@ -67,20 +73,20 @@ jobs:
|
||||
|
||||
# Upload artifacts
|
||||
- name: Upload cross-platform app
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: matrix.os == 'ubuntu-latest' # we only need to upload the cross-platform artifact once per database
|
||||
with:
|
||||
name: commafeed-${{ matrix.database }}-jvm
|
||||
path: commafeed-server/target/commafeed-*.zip
|
||||
|
||||
- name: Upload native executable
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: commafeed-${{ matrix.database }}-${{ runner.os }}-${{ runner.arch }}
|
||||
path: commafeed-server/target/commafeed-*-runner*
|
||||
|
||||
- name: Upload pages
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.database == 'h2' # we only need to upload the pages once
|
||||
with:
|
||||
path: target/pages
|
||||
@@ -98,23 +104,23 @@ jobs:
|
||||
steps:
|
||||
# Checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Setup
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
|
||||
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Install required packages
|
||||
run: sudo apt-get install -y rename unzip
|
||||
|
||||
# Prepare artifacts
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: commafeed-${{ matrix.database }}-*
|
||||
path: ./artifacts
|
||||
@@ -135,7 +141,7 @@ jobs:
|
||||
|
||||
# Docker
|
||||
- name: Login to Container Registry
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
if: ${{ env.DOCKERHUB_USERNAME != '' }}
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -143,7 +149,7 @@ jobs:
|
||||
|
||||
## build but don't push for PRs and renovate
|
||||
- name: Docker build - native
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
file: commafeed-server/src/main/docker/Dockerfile.native
|
||||
@@ -151,7 +157,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
|
||||
- name: Docker build - jvm
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
with:
|
||||
context: .
|
||||
file: commafeed-server/src/main/docker/Dockerfile.jvm
|
||||
@@ -160,7 +166,7 @@ jobs:
|
||||
|
||||
## build and push tag
|
||||
- name: Docker build and push tag - native
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -172,7 +178,7 @@ jobs:
|
||||
athou/commafeed:${{ github.ref_name }}-${{ matrix.database }}
|
||||
|
||||
- name: Docker build and push tag - jvm
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -185,7 +191,7 @@ jobs:
|
||||
|
||||
## build and push master
|
||||
- name: Docker build and push master - native
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -195,7 +201,7 @@ jobs:
|
||||
tags: athou/commafeed:master-${{ matrix.database }}
|
||||
|
||||
- name: Docker build and push master - jvm
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
if: ${{ github.ref_name == 'master' }}
|
||||
with:
|
||||
context: .
|
||||
@@ -215,12 +221,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: commafeed-*
|
||||
path: ./artifacts
|
||||
@@ -230,13 +236,13 @@ jobs:
|
||||
run: chmod +x artifacts/*-runner
|
||||
|
||||
- name: Extract Changelog Entry
|
||||
uses: mindsers/changelog-reader-action@32aa5b4c155d76c94e4ec883a223c947b2f02656 # v2
|
||||
uses: mindsers/changelog-reader-action@1faaf50aa09d5793d9a100819973df801febfb31 # v2.4.0
|
||||
id: changelog_reader
|
||||
with:
|
||||
version: ${{ github.ref_name }}
|
||||
|
||||
- name: Create GitHub release
|
||||
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1
|
||||
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
|
||||
with:
|
||||
name: CommaFeed ${{ github.ref_name }}
|
||||
body: ${{ steps.changelog_reader.outputs.changes }}
|
||||
@@ -249,12 +255,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Update Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5
|
||||
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -274,5 +280,5 @@ jobs:
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
|
||||
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
id: deployment
|
||||
|
||||
2
.mvn/wrapper/maven-wrapper.properties
vendored
2
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1,3 +1,3 @@
|
||||
wrapperVersion=3.3.4
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
|
||||
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,5 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
## [7.3.1]
|
||||
|
||||
- Prevent Host header injection attacks on the password recovery endpoint. A new setting
|
||||
`commafeed.password-recovery-public-base-url` has been added to specify the base URL to use in password recovery
|
||||
emails. This setting is only required when the password recovery feature is enabled, which is not the default.
|
||||
|
||||
## [7.3.0]
|
||||
|
||||
- Add support for the Google Reader API for native mobile apps that don't support the Fever API
|
||||
- The profile menu now works again on mobile when action buttons are at the bottom of the screen
|
||||
- More SSRF protection regarding IPv6
|
||||
- Changed the default value of `blockLocalAddresses` to make CommaFeed more secure out-of-the-box. See the "Access to
|
||||
local address blocked" section of the README if you subscribe to feeds that are only available on your local network.
|
||||
|
||||
## [7.2.1]
|
||||
|
||||
- correctly handle favicons hosted at the root of the domain for feeds with non-default ports
|
||||
- prevent XXS injections in third party apps. The CommaFeed React client was already stripping malicious "javascript:" URLs, they are now filtered during feed parsing and no longer stored in the database/served through the REST API.
|
||||
|
||||
## [7.2.0]
|
||||
|
||||
- Add support for icons declared in feeds. This is useful for feeds exposed by RSS bridges (#2048)
|
||||
- Search now also works for starred entries (#2217)
|
||||
- The error message when subscribing to a feed is now properly displayed again
|
||||
- Small performance improvements to the feed refresh engine
|
||||
- Removed the Pocket sharing feature because the Pocket service has been discontinued
|
||||
- Prevent the image proxy feature to access any URL to avoid SSRF attacks
|
||||
- Strengthened the SSRF protection by also blocking ULA and CGNAT ranges
|
||||
- Prevent DDOS attacks by filtering OPML files during imports
|
||||
|
||||
## [7.1.0]
|
||||
|
||||
- Display the unread count in the header on mobile, since the tree is hidden by default and the unread count is not visible otherwise (#2055)
|
||||
|
||||
14
README.md
14
README.md
@@ -15,7 +15,7 @@ Google Reader inspired self-hosted RSS reader, based on Quarkus and React/TypeSc
|
||||
- Supports thousands of users and millions of feeds
|
||||
- OPML import/export
|
||||
- REST API
|
||||
- Fever-compatible API for native mobile apps
|
||||
- Fever and Google Reader API for native mobile apps
|
||||
- Can automatically mark articles as read based on user-defined rules
|
||||
- Push notifications when new articles are published
|
||||
- Highly customizable with [custom CSS](https://athou.github.io/commafeed/documentation/custom-css) and JavaScript
|
||||
@@ -70,8 +70,8 @@ memory usage.
|
||||
./mvnw clean package [-P<database> [-Pnative]] [-DskipTests]
|
||||
|
||||
- `<database>` can be one of `h2`, `postgresql`, `mysql` or `mariadb`. The default is `h2`.
|
||||
- `-Pnative` compiles the application to native code. This requires GraalVM to be installed (`GRAALVM_HOME` environment
|
||||
variable pointing to a GraalVM installation).
|
||||
- `-Pnative` compiles the application to native code. This requires either GraalVM to be installed (`GRAALVM_HOME` environment
|
||||
variable pointing to a GraalVM installation) or a container environment to be available (docker/podman/...).
|
||||
- `-DskipTests` to speed up the build process by skipping tests.
|
||||
|
||||
When the build is complete:
|
||||
@@ -159,6 +159,14 @@ named [Semeru](https://developer.ibm.com/languages/java/semeru-runtimes/download
|
||||
This is the JVM used in
|
||||
the [Docker image](https://github.com/Athou/commafeed/blob/master/commafeed-server/src/main/docker/Dockerfile.jvm).
|
||||
|
||||
## FAQ
|
||||
|
||||
### Getting "Access to local address blocked" when adding a feed
|
||||
|
||||
CommaFeed blocks access to local resources by default to prevent [SSRF](https://en.wikipedia.org/wiki/Server-side_request_forgery) attacks.
|
||||
If you want to subscribe to feeds that are only available on your local network, you can disable this security measure by setting the `commafeed.http-client.block-local-addresses` variable to `false`.
|
||||
Do this only if you trust all users of your CommaFeed instance not to access private resources.
|
||||
|
||||
## Translation
|
||||
|
||||
Files for internationalization are
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
cd commafeed-client && npx lint-staged
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.11/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.5.8/schema.json",
|
||||
"formatter": {
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 4,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { LinguiConfig } from "@lingui/conf"
|
||||
import { formatter } from "@lingui/format-po"
|
||||
|
||||
const config: LinguiConfig = {
|
||||
locales: [
|
||||
@@ -38,11 +39,9 @@ const config: LinguiConfig = {
|
||||
exclude: ["src/locales/**"],
|
||||
},
|
||||
],
|
||||
format: "po",
|
||||
formatOptions: {
|
||||
origins: true,
|
||||
format: formatter({
|
||||
lineNumbers: false,
|
||||
},
|
||||
}),
|
||||
sourceLocale: "en",
|
||||
fallbackLocales: {
|
||||
default: "en",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export default {
|
||||
"src/**/*.{js,jsx,ts,tsx}": () => ["npm run i18n:extract", "git diff --exit-code commafeed-client/src/locales/en/messages.po"],
|
||||
}
|
||||
4274
commafeed-client/package-lock.json
generated
4274
commafeed-client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,75 +12,74 @@
|
||||
"test:ci": "vitest run",
|
||||
"lint": "biome check",
|
||||
"lint:fix": "biome check --write",
|
||||
"i18n:extract": "lingui extract --clean",
|
||||
"prepare": "cd .. && husky ./commafeed-client/.husky"
|
||||
"i18n:extract": "lingui extract --clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@fontsource/open-sans": "^5.2.7",
|
||||
"@lingui/core": "^5.9.5",
|
||||
"@lingui/react": "^5.9.5",
|
||||
"@mantine/core": "^9.0.1",
|
||||
"@mantine/form": "^9.0.1",
|
||||
"@mantine/hooks": "^9.0.1",
|
||||
"@mantine/modals": "^9.0.1",
|
||||
"@mantine/notifications": "^9.0.1",
|
||||
"@mantine/spotlight": "^9.0.1",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@react-querybuilder/mantine": "^8.14.4",
|
||||
"@reduxjs/toolkit": "^2.11.2",
|
||||
"@rolldown/plugin-babel": "^0.2.2",
|
||||
"axios": "^1.15.0",
|
||||
"dayjs": "^1.11.20",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"interweave": "^13.1.1",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"react": "^19.2.5",
|
||||
"react-async-hook": "^4.0.0",
|
||||
"react-contexify": "^6.0.0",
|
||||
"react-dom": "^19.2.5",
|
||||
"react-draggable": "^4.5.0",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-infinite-scroller": "^1.2.6",
|
||||
"react-querybuilder": "^8.14.4",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.14.0",
|
||||
"react-swipeable": "^7.0.2",
|
||||
"style-to-object": "^1.0.14",
|
||||
"throttle-debounce": "^5.0.2",
|
||||
"tinycon": "^0.6.8",
|
||||
"tss-react": "^4.9.20",
|
||||
"websocket-heartbeat-js": "^1.1.3"
|
||||
"@emotion/react": "11.14.0",
|
||||
"@fontsource/open-sans": "5.3.0",
|
||||
"@lingui/core": "6.6.0",
|
||||
"@lingui/react": "6.6.0",
|
||||
"@mantine/core": "9.5.1",
|
||||
"@mantine/dates": "9.5.1",
|
||||
"@mantine/form": "9.5.1",
|
||||
"@mantine/hooks": "9.5.1",
|
||||
"@mantine/modals": "9.5.1",
|
||||
"@mantine/notifications": "9.5.1",
|
||||
"@mantine/spotlight": "9.5.1",
|
||||
"@monaco-editor/react": "4.7.0",
|
||||
"@react-querybuilder/mantine": "8.22.5",
|
||||
"@reduxjs/toolkit": "2.12.0",
|
||||
"@rolldown/plugin-babel": "0.2.3",
|
||||
"axios": "1.19.0",
|
||||
"dayjs": "1.11.21",
|
||||
"escape-string-regexp": "5.0.0",
|
||||
"interweave": "13.1.1",
|
||||
"monaco-editor": "0.56.0",
|
||||
"mousetrap": "1.6.5",
|
||||
"react": "19.2.8",
|
||||
"react-async-hook": "4.0.0",
|
||||
"react-contexify": "6.0.0",
|
||||
"react-dom": "19.2.8",
|
||||
"react-draggable": "4.7.1",
|
||||
"react-icons": "5.7.0",
|
||||
"react-infinite-scroller": "1.2.6",
|
||||
"react-querybuilder": "8.22.5",
|
||||
"react-redux": "9.3.0",
|
||||
"react-router-dom": "7.18.2",
|
||||
"react-swipeable": "7.0.2",
|
||||
"style-to-object": "2.0.2",
|
||||
"throttle-debounce": "5.0.2",
|
||||
"tinycon": "0.6.8",
|
||||
"tss-react": "4.9.21",
|
||||
"websocket-heartbeat-js": "1.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.11",
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.9.5",
|
||||
"@lingui/cli": "^5.9.5",
|
||||
"@lingui/vite-plugin": "^5.9.5",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-infinite-scroller": "^1.2.5",
|
||||
"@types/throttle-debounce": "^5.0.2",
|
||||
"@types/tinycon": "^0.6.7",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@babel/types": "^8.0.4",
|
||||
"@biomejs/biome": "2.5.8",
|
||||
"@lingui/babel-plugin-lingui-macro": "6.6.0",
|
||||
"@lingui/cli": "6.6.0",
|
||||
"@lingui/vite-plugin": "6.6.0",
|
||||
"@testing-library/jest-dom": "7.0.1",
|
||||
"@testing-library/react": "16.3.2",
|
||||
"@testing-library/user-event": "14.6.4",
|
||||
"@types/mousetrap": "1.6.15",
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.4",
|
||||
"@types/react-infinite-scroller": "1.2.5",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycon": "0.6.7",
|
||||
"@vitejs/plugin-react": "6.0.5",
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^29.0.2",
|
||||
"lint-staged": "^16.4.0",
|
||||
"typescript": "^6.0.2",
|
||||
"vite": "^8.0.8",
|
||||
"vite-plugin-checker": "^0.12.0",
|
||||
"vitest": "^4.1.4",
|
||||
"yaml": "^2.8.3"
|
||||
"jsdom": "30.0.1",
|
||||
"typescript": "7.0.2",
|
||||
"vite": "8.2.1",
|
||||
"vite-plugin-checker": "0.14.5",
|
||||
"vitest": "4.1.10",
|
||||
"yaml": "2.9.0"
|
||||
},
|
||||
"overrides": {
|
||||
"react-infinite-scroller": {
|
||||
"react": "^19.2.5"
|
||||
"react": "19.2.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
<parent>
|
||||
<groupId>com.commafeed</groupId>
|
||||
<artifactId>commafeed</artifactId>
|
||||
<version>7.1.0</version>
|
||||
<version>7.3.1</version>
|
||||
</parent>
|
||||
<artifactId>commafeed-client</artifactId>
|
||||
<name>CommaFeed Client</name>
|
||||
|
||||
<properties>
|
||||
<!-- renovate: datasource=node-version depName=node -->
|
||||
<node.version>v24.14.1</node.version>
|
||||
<node.version>v24.19.0</node.version>
|
||||
<!-- renovate: datasource=npm depName=npm -->
|
||||
<npm.version>11.12.1</npm.version>
|
||||
<npm.version>12.0.2</npm.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@@ -23,7 +23,7 @@
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>2.0.2</version>
|
||||
<?m2e ignore?>
|
||||
<executions>
|
||||
<execution>
|
||||
|
||||
@@ -1,62 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="393.84613"
|
||||
width="393.84613"
|
||||
viewBox="0 0 5.0480766 5.0480766"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
sodipodi:docname="favicon.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<sodipodi:namedview
|
||||
id="namedview3"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.21875"
|
||||
inkscape:cx="207.17949"
|
||||
inkscape:cy="187.07692"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="855"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3" />
|
||||
<rect
|
||||
fill="#f88a14"
|
||||
rx="0.53846151"
|
||||
ry="0.53846151"
|
||||
height="5.0480766"
|
||||
width="5.0480766"
|
||||
id="rect1"
|
||||
x="0"
|
||||
y="0"
|
||||
style="stroke-width:0.769231" />
|
||||
<path
|
||||
d="m 1.3450904,0.64548657 c 2.9002,0 2.9002,2.91010003 2.9002,2.91010003"
|
||||
fill="none"
|
||||
stroke="#ffffff"
|
||||
stroke-linecap="round"
|
||||
stroke-width="0.78125"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 1.3377904,1.9915866 c 1.5705,-0.00908 1.5705,1.5639 1.5705,1.5639"
|
||||
fill="none"
|
||||
stroke="#ffffff"
|
||||
stroke-linecap="round"
|
||||
stroke-width="0.78125"
|
||||
id="path2" />
|
||||
<path
|
||||
d="m 2.0192904,3.5227866 c 0,0.23366 -0.10712,0.47418 -0.24663,0.6537 -0.1814,0.2333 -0.5705,0.5618 -0.6913,0.5653 0.0402,-0.0662 0.263,-0.5654 0.2563,-0.5654 -0.36423004,0 -0.65950004,-0.29265 -0.65950004,-0.65365 0,-0.361 0.29527,-0.65365 0.65950004,-0.65365 0.36423,0 0.68159,0.29265 0.68159,0.65365 z"
|
||||
fill="#ffffff"
|
||||
id="path3" />
|
||||
height="393.84613"
|
||||
width="393.84613"
|
||||
viewBox="0 0 5.0480766 5.0480766"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<title>CommaFeed favicon</title>
|
||||
<defs
|
||||
id="defs3" />
|
||||
<rect
|
||||
fill="#f88a14"
|
||||
rx="0.53846151"
|
||||
ry="0.53846151"
|
||||
height="5.0480766"
|
||||
width="5.0480766"
|
||||
id="rect1"
|
||||
x="0"
|
||||
y="0"
|
||||
style="stroke-width:0.769231" />
|
||||
<path
|
||||
d="m 1.3450904,0.64548657 c 2.9002,0 2.9002,2.91010003 2.9002,2.91010003"
|
||||
fill="none"
|
||||
stroke="#ffffff"
|
||||
stroke-linecap="round"
|
||||
stroke-width="0.78125"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 1.3377904,1.9915866 c 1.5705,-0.00908 1.5705,1.5639 1.5705,1.5639"
|
||||
fill="none"
|
||||
stroke="#ffffff"
|
||||
stroke-linecap="round"
|
||||
stroke-width="0.78125"
|
||||
id="path2" />
|
||||
<path
|
||||
d="m 2.0192904,3.5227866 c 0,0.23366 -0.10712,0.47418 -0.24663,0.6537 -0.1814,0.2333 -0.5705,0.5618 -0.6913,0.5653 0.0402,-0.0662 0.263,-0.5654 0.2563,-0.5654 -0.36423004,0 -0.65950004,-0.29265 -0.65950004,-0.65365 0,-0.361 0.29527,-0.65365 0.65950004,-0.65365 0.36423,0 0.68159,0.29265 0.68159,0.65365 z"
|
||||
fill="#ffffff"
|
||||
id="path3" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -4,21 +4,16 @@ import { MantineProvider, v8CssVariablesResolver } from "@mantine/core"
|
||||
import { ModalsProvider } from "@mantine/modals"
|
||||
import { Notifications } from "@mantine/notifications"
|
||||
import type React from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useEffect } from "react"
|
||||
import { HashRouter, Navigate, Route, Routes, useNavigate } from "react-router-dom"
|
||||
import Tinycon from "tinycon"
|
||||
import { Constants } from "@/app/constants"
|
||||
import { redirectTo } from "@/app/redirect/slice"
|
||||
import { redirectToInitialSetup } from "@/app/redirect/thunks"
|
||||
import { reloadServerInfos } from "@/app/server/thunks"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
import { categoryUnreadCount } from "@/app/utils"
|
||||
import { DisablePullToRefresh } from "@/components/DisablePullToRefresh"
|
||||
import { ErrorBoundary } from "@/components/ErrorBoundary"
|
||||
import { Header } from "@/components/header/Header"
|
||||
import { Tree } from "@/components/sidebar/Tree"
|
||||
import { useAppLoading } from "@/hooks/useAppLoading"
|
||||
import { useBrowserExtension } from "@/hooks/useBrowserExtension"
|
||||
import { useI18n } from "@/i18n"
|
||||
import { AdminUsersPage } from "@/pages/admin/AdminUsersPage"
|
||||
import { MetricsPage } from "@/pages/admin/MetricsPage"
|
||||
@@ -146,83 +141,8 @@ function RedirectHandler() {
|
||||
return null
|
||||
}
|
||||
|
||||
function UnreadCountTitleHandler({
|
||||
enabled,
|
||||
}: Readonly<{
|
||||
enabled?: boolean
|
||||
}>) {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
return <title>{enabled && unreadCount > 0 ? `(${unreadCount}) CommaFeed` : "CommaFeed"}</title>
|
||||
}
|
||||
|
||||
function UnreadCountFaviconHandler({ enabled }: { enabled?: boolean }) {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
useEffect(() => {
|
||||
if (enabled && unreadCount > 0) {
|
||||
Tinycon.setBubble(unreadCount)
|
||||
} else {
|
||||
Tinycon.reset()
|
||||
}
|
||||
}, [unreadCount, enabled])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function BrowserExtensionBadgeUnreadCountHandler() {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const { setBadgeUnreadCount } = useBrowserExtension()
|
||||
useEffect(() => {
|
||||
if (!root) return
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
setBadgeUnreadCount(unreadCount)
|
||||
}, [root, setBadgeUnreadCount])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function CustomJsHandler() {
|
||||
const [scriptLoaded, setScriptLoaded] = useState(false)
|
||||
const { loading } = useAppLoading()
|
||||
|
||||
useEffect(() => {
|
||||
if (scriptLoaded || loading) {
|
||||
return
|
||||
}
|
||||
|
||||
const script = document.createElement("script")
|
||||
script.src = "custom_js.js"
|
||||
script.async = true
|
||||
document.body.appendChild(script)
|
||||
|
||||
setScriptLoaded(true)
|
||||
|
||||
return () => script.remove()
|
||||
}, [scriptLoaded, loading])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function CustomCssHandler() {
|
||||
useEffect(() => {
|
||||
const link = document.createElement("link")
|
||||
link.rel = "stylesheet"
|
||||
link.type = "text/css"
|
||||
link.href = "custom_css.css"
|
||||
document.head.appendChild(link)
|
||||
|
||||
return () => link.remove()
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function App() {
|
||||
useI18n()
|
||||
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
|
||||
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
|
||||
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -231,13 +151,6 @@ export function App() {
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<UnreadCountTitleHandler enabled={unreadCountTitle} />
|
||||
<UnreadCountFaviconHandler enabled={unreadCountFavicon} />
|
||||
<BrowserExtensionBadgeUnreadCountHandler />
|
||||
<CustomJsHandler />
|
||||
<CustomCssHandler />
|
||||
<DisablePullToRefresh enabled={disablePullToRefresh} />
|
||||
|
||||
<HashRouter>
|
||||
<InitialSetupHandler />
|
||||
<RedirectHandler />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IconType } from "react-icons"
|
||||
import { FaAt } from "react-icons/fa"
|
||||
import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiPocket, SiTumblr, SiX } from "react-icons/si"
|
||||
import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiTumblr, SiX } from "react-icons/si"
|
||||
import type { Category, Entry, SharingSettings } from "./types"
|
||||
|
||||
const categories: Record<string, Omit<Category, "name">> = {
|
||||
@@ -58,12 +58,6 @@ const sharing: {
|
||||
color: "#375672",
|
||||
url: (url, desc) => `https://www.tumblr.com/share/link?url=${url}&name=${desc}`,
|
||||
},
|
||||
pocket: {
|
||||
label: "Pocket",
|
||||
icon: SiPocket,
|
||||
color: "#EF4154",
|
||||
url: (url, desc) => `https://getpocket.com/save?url=${url}&title=${desc}`,
|
||||
},
|
||||
instapaper: {
|
||||
label: "Instapaper",
|
||||
icon: SiInstapaper,
|
||||
|
||||
@@ -249,7 +249,6 @@ export interface SharingSettings {
|
||||
facebook: boolean
|
||||
twitter: boolean
|
||||
tumblr: boolean
|
||||
pocket: boolean
|
||||
instapaper: boolean
|
||||
buffer: boolean
|
||||
}
|
||||
|
||||
@@ -1,32 +1,8 @@
|
||||
import { t } from "@lingui/core/macro"
|
||||
import { showNotification } from "@mantine/notifications"
|
||||
import { createSlice, isAnyOf, type PayloadAction } from "@reduxjs/toolkit"
|
||||
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
|
||||
import type { LocalSettings, Settings, UserModel, ViewMode } from "@/app/types"
|
||||
import {
|
||||
changeCustomContextMenu,
|
||||
changeDisablePullToRefresh,
|
||||
changeEntriesToKeepOnTopWhenScrolling,
|
||||
changeExternalLinkIconDisplayMode,
|
||||
changeLanguage,
|
||||
changeMarkAllAsReadConfirmation,
|
||||
changeMarkAllAsReadNavigateToUnread,
|
||||
changeMobileFooter,
|
||||
changePrimaryColor,
|
||||
changePushNotificationSettings,
|
||||
changeReadingMode,
|
||||
changeReadingOrder,
|
||||
changeScrollMarks,
|
||||
changeScrollMode,
|
||||
changeScrollSpeed,
|
||||
changeSharingSetting,
|
||||
changeShowRead,
|
||||
changeStarIconDisplayMode,
|
||||
changeUnreadCountFavicon,
|
||||
changeUnreadCountTitle,
|
||||
reloadProfile,
|
||||
reloadSettings,
|
||||
reloadTags,
|
||||
} from "./thunks"
|
||||
import { changeSettings, reloadProfile, reloadSettings, reloadTags } from "./thunks"
|
||||
|
||||
interface UserState {
|
||||
settings?: Settings
|
||||
@@ -73,115 +49,16 @@ export const userSlice = createSlice({
|
||||
builder.addCase(reloadTags.fulfilled, (state, action) => {
|
||||
state.tags = action.payload
|
||||
})
|
||||
builder.addCase(changeReadingMode.pending, (state, action) => {
|
||||
builder.addCase(changeSettings.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.readingMode = action.meta.arg
|
||||
state.settings = { ...state.settings, ...action.meta.arg }
|
||||
})
|
||||
builder.addCase(changeReadingOrder.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.readingOrder = action.meta.arg
|
||||
builder.addCase(changeSettings.fulfilled, () => {
|
||||
showNotification({
|
||||
message: t`Settings saved.`,
|
||||
color: "green",
|
||||
})
|
||||
})
|
||||
builder.addCase(changeLanguage.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.language = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeScrollSpeed.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.scrollSpeed = action.meta.arg ? 400 : 0
|
||||
})
|
||||
builder.addCase(changeShowRead.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.showRead = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeScrollMarks.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.scrollMarks = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeScrollMode.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.scrollMode = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeEntriesToKeepOnTopWhenScrolling.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.entriesToKeepOnTopWhenScrolling = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeStarIconDisplayMode.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.starIconDisplayMode = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeExternalLinkIconDisplayMode.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.externalLinkIconDisplayMode = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.markAllAsReadConfirmation = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeMarkAllAsReadNavigateToUnread.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.markAllAsReadNavigateToNextUnread = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeCustomContextMenu.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.customContextMenu = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeMobileFooter.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.mobileFooter = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeUnreadCountTitle.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.unreadCountTitle = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeUnreadCountFavicon.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.unreadCountFavicon = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeDisablePullToRefresh.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.disablePullToRefresh = action.meta.arg
|
||||
})
|
||||
builder.addCase(changePrimaryColor.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.primaryColor = action.meta.arg
|
||||
})
|
||||
builder.addCase(changeSharingSetting.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
|
||||
})
|
||||
builder.addCase(changePushNotificationSettings.pending, (state, action) => {
|
||||
if (!state.settings) return
|
||||
state.settings.pushNotificationSettings = action.meta.arg
|
||||
})
|
||||
|
||||
builder.addMatcher(
|
||||
isAnyOf(
|
||||
changeLanguage.fulfilled,
|
||||
changeScrollSpeed.fulfilled,
|
||||
changeShowRead.fulfilled,
|
||||
changeScrollMarks.fulfilled,
|
||||
changeScrollMode.fulfilled,
|
||||
changeEntriesToKeepOnTopWhenScrolling.fulfilled,
|
||||
changeStarIconDisplayMode.fulfilled,
|
||||
changeExternalLinkIconDisplayMode.fulfilled,
|
||||
changeMarkAllAsReadConfirmation.fulfilled,
|
||||
changeMarkAllAsReadNavigateToUnread.fulfilled,
|
||||
changeCustomContextMenu.fulfilled,
|
||||
changeMobileFooter.fulfilled,
|
||||
changeUnreadCountTitle.fulfilled,
|
||||
changeUnreadCountFavicon.fulfilled,
|
||||
changeDisablePullToRefresh.fulfilled,
|
||||
changePrimaryColor.fulfilled,
|
||||
changeSharingSetting.fulfilled,
|
||||
changePushNotificationSettings.fulfilled
|
||||
),
|
||||
() => {
|
||||
showNotification({
|
||||
message: t`Settings saved.`,
|
||||
color: "green",
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createAppAsyncThunk } from "@/app/async-thunk"
|
||||
import { client } from "@/app/client"
|
||||
import { reloadEntries } from "@/app/entries/thunks"
|
||||
import type { IconDisplayMode, PushNotificationSettings, ReadingMode, ReadingOrder, ScrollMode, SharingSettings } from "@/app/types"
|
||||
import type { Settings } from "@/app/types"
|
||||
|
||||
export const reloadSettings = createAppAsyncThunk("settings/reload", async () => await client.user.getSettings().then(r => r.data))
|
||||
|
||||
@@ -9,163 +8,8 @@ export const reloadProfile = createAppAsyncThunk("profile/reload", async () => a
|
||||
|
||||
export const reloadTags = createAppAsyncThunk("entries/tags", async () => await client.entry.getTags().then(r => r.data))
|
||||
|
||||
export const changeReadingMode = createAppAsyncThunk("settings/readingMode", (readingMode: ReadingMode, thunkApi) => {
|
||||
export const changeSettings = createAppAsyncThunk("settings/change", async (newSettings: Partial<Settings>, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, readingMode })
|
||||
thunkApi.dispatch(reloadEntries())
|
||||
await client.user.saveSettings({ ...settings, ...newSettings })
|
||||
})
|
||||
|
||||
export const changeReadingOrder = createAppAsyncThunk("settings/readingOrder", (readingOrder: ReadingOrder, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, readingOrder })
|
||||
thunkApi.dispatch(reloadEntries())
|
||||
})
|
||||
|
||||
export const changeLanguage = createAppAsyncThunk("settings/language", (language: string, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, language })
|
||||
})
|
||||
|
||||
export const changeScrollSpeed = createAppAsyncThunk("settings/scrollSpeed", (speed: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 })
|
||||
})
|
||||
|
||||
export const changeShowRead = createAppAsyncThunk("settings/showRead", (showRead: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, showRead })
|
||||
})
|
||||
|
||||
export const changeScrollMarks = createAppAsyncThunk("settings/scrollMarks", (scrollMarks: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, scrollMarks })
|
||||
})
|
||||
|
||||
export const changeScrollMode = createAppAsyncThunk("settings/scrollMode", (scrollMode: ScrollMode, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, scrollMode })
|
||||
})
|
||||
|
||||
export const changeEntriesToKeepOnTopWhenScrolling = createAppAsyncThunk(
|
||||
"settings/entriesToKeepOnTopWhenScrolling",
|
||||
(entriesToKeepOnTopWhenScrolling: number, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, entriesToKeepOnTopWhenScrolling })
|
||||
}
|
||||
)
|
||||
|
||||
export const changeStarIconDisplayMode = createAppAsyncThunk(
|
||||
"settings/starIconDisplayMode",
|
||||
(starIconDisplayMode: IconDisplayMode, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, starIconDisplayMode })
|
||||
}
|
||||
)
|
||||
|
||||
export const changeExternalLinkIconDisplayMode = createAppAsyncThunk(
|
||||
"settings/externalLinkIconDisplayMode",
|
||||
(externalLinkIconDisplayMode: IconDisplayMode, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, externalLinkIconDisplayMode })
|
||||
}
|
||||
)
|
||||
|
||||
export const changeMarkAllAsReadConfirmation = createAppAsyncThunk(
|
||||
"settings/markAllAsReadConfirmation",
|
||||
(markAllAsReadConfirmation: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, markAllAsReadConfirmation })
|
||||
}
|
||||
)
|
||||
|
||||
export const changeMarkAllAsReadNavigateToUnread = createAppAsyncThunk(
|
||||
"settings/markAllAsReadNavigateToUnread",
|
||||
(markAllAsReadNavigateToNextUnread: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, markAllAsReadNavigateToNextUnread })
|
||||
}
|
||||
)
|
||||
|
||||
export const changeCustomContextMenu = createAppAsyncThunk("settings/customContextMenu", (customContextMenu: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, customContextMenu })
|
||||
})
|
||||
|
||||
export const changeMobileFooter = createAppAsyncThunk("settings/mobileFooter", (mobileFooter: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, mobileFooter })
|
||||
})
|
||||
|
||||
export const changeUnreadCountTitle = createAppAsyncThunk("settings/unreadCountTitle", (unreadCountTitle: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, unreadCountTitle })
|
||||
})
|
||||
|
||||
export const changeUnreadCountFavicon = createAppAsyncThunk("settings/unreadCountFavicon", (unreadCountFavicon: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, unreadCountFavicon })
|
||||
})
|
||||
|
||||
export const changeDisablePullToRefresh = createAppAsyncThunk(
|
||||
"settings/disablePullToRefresh",
|
||||
(disablePullToRefresh: boolean, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, disablePullToRefresh })
|
||||
}
|
||||
)
|
||||
|
||||
export const changePrimaryColor = createAppAsyncThunk("settings/primaryColor", (primaryColor: string, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({ ...settings, primaryColor })
|
||||
})
|
||||
|
||||
export const changeSharingSetting = createAppAsyncThunk(
|
||||
"settings/sharingSetting",
|
||||
(
|
||||
sharingSetting: {
|
||||
site: keyof SharingSettings
|
||||
value: boolean
|
||||
},
|
||||
thunkApi
|
||||
) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({
|
||||
...settings,
|
||||
sharingSettings: {
|
||||
...settings.sharingSettings,
|
||||
[sharingSetting.site]: sharingSetting.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
export const changePushNotificationSettings = createAppAsyncThunk(
|
||||
"settings/pushNotificationSettings",
|
||||
(pushNotificationSettings: PushNotificationSettings, thunkApi) => {
|
||||
const { settings } = thunkApi.getState().user
|
||||
if (!settings) return
|
||||
client.user.saveSettings({
|
||||
...settings,
|
||||
pushNotificationSettings,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg height="512" width="512" viewBox="0 0 6.5625 6.5625" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>CommaFeed logo</title>
|
||||
<rect fill="#f88a14" rx="0.7" ry="0.7" height="6.5625" width="6.5625" />
|
||||
<path d="m1.9761,1.5289c2.9002,0,2.9002,2.9101,2.9002,2.9101" fill="none" stroke="#FFF" stroke-linecap="round"
|
||||
stroke-width="0.78125" />
|
||||
stroke-width="0.78125" />
|
||||
<path d="m1.9688,2.875c1.5705-0.00908,1.5705,1.5639,1.5705,1.5639" fill="none" stroke="#FFF" stroke-linecap="round"
|
||||
stroke-width="0.78125" />
|
||||
stroke-width="0.78125" />
|
||||
<path d="m2.6503,4.4062c0,0.23366-0.10712,0.47418-0.24663,0.6537-0.1814,0.2333-0.5705,0.5618-0.6913,0.5653,0.0402-0.0662,0.263-0.5654,0.2563-0.5654-0.36423,0-0.6595-0.29265-0.6595-0.65365s0.29527-0.65365,0.6595-0.65365,0.68159,0.29265,0.68159,0.65365z"
|
||||
fill="#FFF" />
|
||||
fill="#FFF" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 791 B After Width: | Height: | Size: 831 B |
@@ -14,6 +14,7 @@ interface ImageWithPlaceholderWhileLoadingProps {
|
||||
placeholderHeight?: number
|
||||
placeholderBackgroundColor?: string
|
||||
placeholderIconSize?: number
|
||||
onError?: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void
|
||||
}
|
||||
|
||||
const useStyles = tss
|
||||
@@ -44,6 +45,7 @@ export function ImageWithPlaceholderWhileLoading({
|
||||
title,
|
||||
width,
|
||||
style,
|
||||
onError,
|
||||
}: Readonly<ImageWithPlaceholderWhileLoadingProps>) {
|
||||
const { classes } = useStyles({
|
||||
placeholderWidth,
|
||||
@@ -70,6 +72,7 @@ export function ImageWithPlaceholderWhileLoading({
|
||||
width={width}
|
||||
height={height}
|
||||
onLoad={() => setLoading(false)}
|
||||
onError={onError}
|
||||
style={{
|
||||
...style,
|
||||
display: loading ? "none" : (style?.display ?? "initial"),
|
||||
|
||||
@@ -7,11 +7,11 @@ const init = async () => {
|
||||
async getWorker(_, label) {
|
||||
let worker: typeof import("*?worker")
|
||||
if (label === "css") {
|
||||
worker = await import("monaco-editor/esm/vs/language/css/css.worker?worker")
|
||||
worker = await import("monaco-editor/language/css/css.worker?worker")
|
||||
} else if (label === "javascript") {
|
||||
worker = await import("monaco-editor/esm/vs/language/typescript/ts.worker?worker")
|
||||
worker = await import("monaco-editor/language/typescript/ts.worker?worker")
|
||||
} else {
|
||||
worker = await import("monaco-editor/esm/vs/editor/editor.worker?worker")
|
||||
worker = await import("monaco-editor/editor/editor.worker?worker")
|
||||
}
|
||||
return new worker.default()
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useTimeout } from "@mantine/hooks"
|
||||
import { useState } from "react"
|
||||
import { ImageWithPlaceholderWhileLoading } from "@/components/ImageWithPlaceholderWhileLoading"
|
||||
|
||||
export interface FeedFaviconProps {
|
||||
@@ -6,9 +8,23 @@ export interface FeedFaviconProps {
|
||||
}
|
||||
|
||||
export function FeedFavicon({ url, size = 18 }: Readonly<FeedFaviconProps>) {
|
||||
// the backend always returns a favicon except when the feed has never been fetched
|
||||
// this can happen when the user subscribes to a feed, the feed is added to the tree but the feed has not been fetched yet
|
||||
// in this case we retry every second up to 3 times until the feed is fetched and the favicon is available
|
||||
const [timestamp, setTimestamp] = useState(0)
|
||||
const [retryCount, setRetryCount] = useState(0)
|
||||
const { start: retry } = useTimeout(() => setTimestamp(Date.now()), 1000)
|
||||
|
||||
const urlWithTimestamp = url + (timestamp === 0 ? "" : `?t=${timestamp}`)
|
||||
const handleError = () => {
|
||||
if (retryCount < 3) {
|
||||
setRetryCount(c => c + 1)
|
||||
retry()
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ImageWithPlaceholderWhileLoading
|
||||
src={url}
|
||||
src={urlWithTimestamp}
|
||||
alt="feed favicon"
|
||||
width={size}
|
||||
height={size}
|
||||
@@ -16,6 +32,7 @@ export function FeedFavicon({ url, size = 18 }: Readonly<FeedFaviconProps>) {
|
||||
placeholderHeight={size}
|
||||
placeholderBackgroundColor="inherit"
|
||||
placeholderIconSize={size}
|
||||
onError={handleError}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
import { Highlight } from "@mantine/core"
|
||||
import { useAppSelector } from "@/app/store"
|
||||
import type { Entry } from "@/app/types"
|
||||
import { tss } from "@/tss"
|
||||
|
||||
export interface FeedEntryTitleProps {
|
||||
entry: Entry
|
||||
}
|
||||
|
||||
const useStyles = tss.create(() => ({
|
||||
content: {
|
||||
textWrap: "inherit",
|
||||
},
|
||||
}))
|
||||
|
||||
export function FeedEntryTitle(props: Readonly<FeedEntryTitleProps>) {
|
||||
const search = useAppSelector(state => state.entries.search)
|
||||
const keywords = search?.split(" ")
|
||||
|
||||
const { classes } = useStyles()
|
||||
return (
|
||||
<Highlight
|
||||
inherit
|
||||
highlight={keywords ?? ""}
|
||||
// make sure ellipsis is shown when title is too long
|
||||
span
|
||||
className={classes.content}
|
||||
>
|
||||
{props.entry.title}
|
||||
</Highlight>
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "react-icons/tb"
|
||||
import { markAllAsReadWithConfirmationIfRequired, reloadEntries, search, selectNextEntry, selectPreviousEntry } from "@/app/entries/thunks"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
import { changeReadingMode, changeReadingOrder } from "@/app/user/thunks"
|
||||
import { changeSettings } from "@/app/user/thunks"
|
||||
import { ActionButton } from "@/components/ActionButton"
|
||||
import { Loader } from "@/components/Loader"
|
||||
import { useActionButton } from "@/hooks/useActionButton"
|
||||
@@ -73,6 +73,25 @@ export function Header() {
|
||||
}, [setValues, searchFromStore])
|
||||
|
||||
if (!settings) return <Loader />
|
||||
|
||||
const toggleReadingMode = async () => {
|
||||
await dispatch(
|
||||
changeSettings({
|
||||
readingMode: settings.readingMode === "all" ? "unread" : "all",
|
||||
})
|
||||
)
|
||||
dispatch(reloadEntries())
|
||||
}
|
||||
|
||||
const toggleReadingOrder = async () => {
|
||||
await dispatch(
|
||||
changeSettings({
|
||||
readingOrder: settings.readingOrder === "asc" ? "desc" : "asc",
|
||||
})
|
||||
)
|
||||
dispatch(reloadEntries())
|
||||
}
|
||||
|
||||
return (
|
||||
<Center className="cf-toolbar-wrapper">
|
||||
<HeaderToolbar>
|
||||
@@ -121,12 +140,12 @@ export function Header() {
|
||||
<ActionButton
|
||||
icon={settings.readingMode === "all" ? <TbEye size={iconSize} /> : <TbEyeOff size={iconSize} />}
|
||||
label={settings.readingMode === "all" ? msg`All` : msg`Unread`}
|
||||
onClick={async () => await dispatch(changeReadingMode(settings.readingMode === "all" ? "unread" : "all"))}
|
||||
onClick={toggleReadingMode}
|
||||
/>
|
||||
<ActionButton
|
||||
icon={settings.readingOrder === "asc" ? <TbSortAscending size={iconSize} /> : <TbSortDescending size={iconSize} />}
|
||||
label={settings.readingOrder === "asc" ? msg`Asc` : msg`Desc`}
|
||||
onClick={async () => await dispatch(changeReadingOrder(settings.readingOrder === "asc" ? "desc" : "asc"))}
|
||||
onClick={toggleReadingOrder}
|
||||
/>
|
||||
|
||||
<Popover>
|
||||
|
||||
@@ -124,7 +124,7 @@ export function ProfileMenu(props: Readonly<ProfileMenuProps>) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu position="bottom-end" closeOnItemClick={false} opened={opened} onChange={setOpened}>
|
||||
<Menu position="bottom-end" closeOnItemClick={false} opened={opened} onChange={setOpened} preventPositionChangeWhenVisible={false}>
|
||||
<Menu.Target>{props.control}</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
{profile && <Menu.Label>{profile.name}</Menu.Label>}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type ComboboxData,
|
||||
Divider,
|
||||
Group,
|
||||
Loader,
|
||||
NumberInput,
|
||||
Radio,
|
||||
Select,
|
||||
@@ -18,45 +19,11 @@ import type { ReactNode } from "react"
|
||||
import { Constants } from "@/app/constants"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
import type { IconDisplayMode, ScrollMode, SharingSettings } from "@/app/types"
|
||||
import {
|
||||
changeCustomContextMenu,
|
||||
changeDisablePullToRefresh,
|
||||
changeEntriesToKeepOnTopWhenScrolling,
|
||||
changeExternalLinkIconDisplayMode,
|
||||
changeLanguage,
|
||||
changeMarkAllAsReadConfirmation,
|
||||
changeMarkAllAsReadNavigateToUnread,
|
||||
changeMobileFooter,
|
||||
changePrimaryColor,
|
||||
changeScrollMarks,
|
||||
changeScrollMode,
|
||||
changeScrollSpeed,
|
||||
changeSharingSetting,
|
||||
changeShowRead,
|
||||
changeStarIconDisplayMode,
|
||||
changeUnreadCountFavicon,
|
||||
changeUnreadCountTitle,
|
||||
} from "@/app/user/thunks"
|
||||
import { changeSettings } from "@/app/user/thunks"
|
||||
import { locales } from "@/i18n"
|
||||
|
||||
export function DisplaySettings() {
|
||||
const language = useAppSelector(state => state.user.settings?.language)
|
||||
const scrollSpeed = useAppSelector(state => state.user.settings?.scrollSpeed)
|
||||
const showRead = useAppSelector(state => state.user.settings?.showRead)
|
||||
const scrollMarks = useAppSelector(state => state.user.settings?.scrollMarks)
|
||||
const scrollMode = useAppSelector(state => state.user.settings?.scrollMode)
|
||||
const entriesToKeepOnTop = useAppSelector(state => state.user.settings?.entriesToKeepOnTopWhenScrolling)
|
||||
const starIconDisplayMode = useAppSelector(state => state.user.settings?.starIconDisplayMode)
|
||||
const externalLinkIconDisplayMode = useAppSelector(state => state.user.settings?.externalLinkIconDisplayMode)
|
||||
const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation)
|
||||
const markAllAsReadNavigateToNextUnread = useAppSelector(state => state.user.settings?.markAllAsReadNavigateToNextUnread)
|
||||
const customContextMenu = useAppSelector(state => state.user.settings?.customContextMenu)
|
||||
const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter)
|
||||
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
|
||||
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
|
||||
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
|
||||
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
|
||||
const primaryColor = useAppSelector(state => state.user.settings?.primaryColor) || Constants.theme.defaultPrimaryColor
|
||||
const settings = useAppSelector(state => state.user.settings)
|
||||
const { _ } = useLingui()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
@@ -108,50 +75,51 @@ export function DisplaySettings() {
|
||||
</Group>
|
||||
)
|
||||
|
||||
if (!settings) return <Loader />
|
||||
return (
|
||||
<Stack>
|
||||
<Divider label={<Trans>Display</Trans>} labelPosition="center" />
|
||||
|
||||
<Select
|
||||
label={<Trans>Language</Trans>}
|
||||
value={language}
|
||||
value={settings.language}
|
||||
data={locales.map(l => ({
|
||||
value: l.key,
|
||||
label: l.label,
|
||||
}))}
|
||||
onChange={async s => await (s && dispatch(changeLanguage(s)))}
|
||||
onChange={language => language && dispatch(changeSettings({ language }))}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label={<Trans>Primary color</Trans>}
|
||||
data={colorData}
|
||||
value={primaryColor}
|
||||
onChange={async value => value && (await dispatch(changePrimaryColor(value)))}
|
||||
value={settings.primaryColor}
|
||||
onChange={primaryColor => primaryColor && dispatch(changeSettings({ primaryColor }))}
|
||||
renderOption={colorRenderer}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Show feeds and categories with no unread entries</Trans>}
|
||||
checked={showRead}
|
||||
onChange={async e => await dispatch(changeShowRead(e.currentTarget.checked))}
|
||||
checked={settings.showRead}
|
||||
onChange={e => dispatch(changeSettings({ showRead: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Show confirmation when marking all entries as read</Trans>}
|
||||
checked={markAllAsReadConfirmation}
|
||||
onChange={async e => await dispatch(changeMarkAllAsReadConfirmation(e.currentTarget.checked))}
|
||||
checked={settings.markAllAsReadConfirmation}
|
||||
onChange={e => dispatch(changeSettings({ markAllAsReadConfirmation: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Navigate to the next category/feed with unread entries when marking all entries as read</Trans>}
|
||||
checked={markAllAsReadNavigateToNextUnread}
|
||||
onChange={async e => await dispatch(changeMarkAllAsReadNavigateToUnread(e.currentTarget.checked))}
|
||||
checked={settings.markAllAsReadNavigateToNextUnread}
|
||||
onChange={e => dispatch(changeSettings({ markAllAsReadNavigateToNextUnread: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>On mobile, show action buttons at the bottom of the screen</Trans>}
|
||||
checked={mobileFooter}
|
||||
onChange={async e => await dispatch(changeMobileFooter(e.currentTarget.checked))}
|
||||
checked={settings.mobileFooter}
|
||||
onChange={e => dispatch(changeSettings({ mobileFooter: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Divider label={<Trans>Scrolling</Trans>} labelPosition="center" />
|
||||
@@ -159,14 +127,14 @@ export function DisplaySettings() {
|
||||
<Switch
|
||||
label={<Trans>Disable "Pull to refresh" browser behavior</Trans>}
|
||||
description={<Trans>This setting can cause scrolling issues on some browsers (e.g. Safari)</Trans>}
|
||||
checked={disablePullToRefresh}
|
||||
onChange={async e => await dispatch(changeDisablePullToRefresh(e.currentTarget.checked))}
|
||||
checked={settings.disablePullToRefresh}
|
||||
onChange={e => dispatch(changeSettings({ disablePullToRefresh: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Radio.Group
|
||||
label={<Trans>Scroll selected entry to the top of the page</Trans>}
|
||||
value={scrollMode}
|
||||
onChange={async value => await dispatch(changeScrollMode(value as ScrollMode))}
|
||||
value={settings.scrollMode}
|
||||
onChange={value => dispatch(changeSettings({ scrollMode: value }))}
|
||||
>
|
||||
<Group mt="xs">
|
||||
{Object.entries(scrollModeOptions).map(e => (
|
||||
@@ -179,56 +147,56 @@ export function DisplaySettings() {
|
||||
label={<Trans>Entries to keep above the selected entry when scrolling</Trans>}
|
||||
description={<Trans>Only applies to compact, cozy and detailed modes</Trans>}
|
||||
min={0}
|
||||
value={entriesToKeepOnTop}
|
||||
onChange={async value => await dispatch(changeEntriesToKeepOnTopWhenScrolling(+value))}
|
||||
value={settings.entriesToKeepOnTopWhenScrolling}
|
||||
onChange={value => dispatch(changeSettings({ entriesToKeepOnTopWhenScrolling: +value }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Scroll smoothly when navigating between entries</Trans>}
|
||||
checked={scrollSpeed ? scrollSpeed > 0 : false}
|
||||
onChange={async e => await dispatch(changeScrollSpeed(e.currentTarget.checked))}
|
||||
checked={settings.scrollSpeed ? settings.scrollSpeed > 0 : false}
|
||||
onChange={e => dispatch(changeSettings({ scrollSpeed: e.currentTarget.checked ? 400 : 0 }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>In expanded view, scrolling through entries mark them as read</Trans>}
|
||||
checked={scrollMarks}
|
||||
onChange={async e => await dispatch(changeScrollMarks(e.currentTarget.checked))}
|
||||
checked={settings.scrollMarks}
|
||||
onChange={e => dispatch(changeSettings({ scrollMarks: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Divider label={<Trans>Browser tab</Trans>} labelPosition="center" />
|
||||
|
||||
<Switch
|
||||
label={<Trans>Show unread count in tab title</Trans>}
|
||||
checked={unreadCountTitle}
|
||||
onChange={async e => await dispatch(changeUnreadCountTitle(e.currentTarget.checked))}
|
||||
checked={settings.unreadCountTitle}
|
||||
onChange={e => dispatch(changeSettings({ unreadCountTitle: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Show unread count in tab favicon</Trans>}
|
||||
checked={unreadCountFavicon}
|
||||
onChange={async e => await dispatch(changeUnreadCountFavicon(e.currentTarget.checked))}
|
||||
checked={settings.unreadCountFavicon}
|
||||
onChange={e => dispatch(changeSettings({ unreadCountFavicon: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Divider label={<Trans>Entry headers</Trans>} labelPosition="center" />
|
||||
|
||||
<Select
|
||||
label={<Trans>Show star icon</Trans>}
|
||||
value={starIconDisplayMode}
|
||||
value={settings.starIconDisplayMode}
|
||||
data={displayModeData}
|
||||
onChange={async s => await dispatch(changeStarIconDisplayMode(s as IconDisplayMode))}
|
||||
onChange={value => dispatch(changeSettings({ starIconDisplayMode: value as IconDisplayMode }))}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label={<Trans>Show external link icon</Trans>}
|
||||
value={externalLinkIconDisplayMode}
|
||||
value={settings.externalLinkIconDisplayMode}
|
||||
data={displayModeData}
|
||||
onChange={async s => await dispatch(changeExternalLinkIconDisplayMode(s as IconDisplayMode))}
|
||||
onChange={value => dispatch(changeSettings({ externalLinkIconDisplayMode: value as IconDisplayMode }))}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label={<Trans>Show CommaFeed's own context menu on right click</Trans>}
|
||||
checked={customContextMenu}
|
||||
onChange={async e => await dispatch(changeCustomContextMenu(e.currentTarget.checked))}
|
||||
checked={settings.customContextMenu}
|
||||
onChange={e => dispatch(changeSettings({ customContextMenu: e.currentTarget.checked }))}
|
||||
/>
|
||||
|
||||
<Divider label={<Trans>Sharing sites</Trans>} labelPosition="center" />
|
||||
@@ -238,12 +206,14 @@ export function DisplaySettings() {
|
||||
<Switch
|
||||
key={site}
|
||||
label={Constants.sharing[site].label}
|
||||
checked={sharingSettings?.[site]}
|
||||
onChange={async e =>
|
||||
await dispatch(
|
||||
changeSharingSetting({
|
||||
site,
|
||||
value: e.currentTarget.checked,
|
||||
checked={settings.sharingSettings[site]}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
changeSettings({
|
||||
sharingSettings: {
|
||||
...settings.sharingSettings,
|
||||
[site]: e.currentTarget.checked,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ export function ProfileSettings() {
|
||||
label={<Trans>API key</Trans>}
|
||||
description={
|
||||
<Trans>
|
||||
This is your API key. It can be used for some read-only API operations and grants access to the Fever API.
|
||||
Use the form at the bottom of the page to generate a new API key
|
||||
This is your API key. It can be used for some read-only API operations and grants access to the Fever API
|
||||
and the Google Reader API. Use the form at the bottom of the page to generate a new API key
|
||||
</Trans>
|
||||
}
|
||||
readOnly
|
||||
@@ -130,6 +130,22 @@ export function ProfileSettings() {
|
||||
</Box>
|
||||
</Input.Wrapper>
|
||||
|
||||
<Input.Wrapper
|
||||
label={<Trans>Google Reader API</Trans>}
|
||||
description={
|
||||
<Trans>
|
||||
CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible
|
||||
mobile client. Login with your username and your <u>API key</u>.
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<Box>
|
||||
<Anchor href="rest/googlereader" target="_blank">
|
||||
<Trans>Google Reader API URL</Trans>
|
||||
</Anchor>
|
||||
</Box>
|
||||
</Input.Wrapper>
|
||||
|
||||
<Divider />
|
||||
|
||||
<PasswordInput
|
||||
|
||||
@@ -10,7 +10,7 @@ import { client, errorToStrings } from "@/app/client"
|
||||
import { redirectToSelectedSource } from "@/app/redirect/thunks"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
import type { PushNotificationSettings as PushNotificationSettingsModel } from "@/app/types"
|
||||
import { changePushNotificationSettings } from "@/app/user/thunks"
|
||||
import { changeSettings } from "@/app/user/thunks"
|
||||
import { Alert } from "@/components/Alert"
|
||||
|
||||
export function PushNotificationSettings() {
|
||||
@@ -25,7 +25,7 @@ export function PushNotificationSettings() {
|
||||
}, [form.initialize, notificationSettings])
|
||||
|
||||
const handleSubmit = (values: PushNotificationSettingsModel) => {
|
||||
dispatch(changePushNotificationSettings(values))
|
||||
dispatch(changeSettings({ pushNotificationSettings: values }))
|
||||
}
|
||||
|
||||
const sendTestPushNotification = useAsyncCallback(client.user.sendTestPushNotification)
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "إضافة مستخدم"
|
||||
msgid "Admin"
|
||||
msgstr "إداري"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "اسم مستخدم المسؤول"
|
||||
@@ -73,7 +72,6 @@ msgstr "اسم مستخدم المسؤول"
|
||||
msgid "All"
|
||||
msgstr "الكل"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "دائمًا"
|
||||
@@ -173,18 +171,14 @@ msgstr "قم ببناء تعبير تصفية لتحديد ما تريد قرا
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "إلغاء"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "إصدار إضافة متصفح CommaFeed هو {browserExtensionVersion
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed متوافق مع Fever API. استخدم عنوان URL التالي في تطبيق الهاتف المتوافق مع Fever. قم بتسجيل الدخول باستخدام اسم المستخدم و <0>مفتاح API</0> الخاص بك."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed متوافق مع Google Reader API. استخدم عنوان URL التالي في تطبيق الهاتف المتوافق مع Google Reader. قم بتسجيل الدخول باستخدام اسم المستخدم و <0>مفتاح API</0> الخاص بك."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed التالي العنصر غير المقروء"
|
||||
@@ -240,7 +238,6 @@ msgstr "تأكيد"
|
||||
msgid "Confirm password"
|
||||
msgstr "تأكيد كلمة المرور"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "تأكيد كلمة المرور"
|
||||
@@ -294,7 +291,6 @@ msgstr "أيام"
|
||||
msgid "Delete"
|
||||
msgstr "حذف"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "حذف الحساب"
|
||||
@@ -342,13 +338,10 @@ msgstr "اسحب الرابط إلى شريط الإشارات"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "البريد الإلكتروني"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "عنوان البريد الإلكتروني"
|
||||
@@ -399,7 +392,6 @@ msgstr "خيارات الإضافة"
|
||||
msgid "Feed name"
|
||||
msgstr "اسم الخلاصة"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "انتقل إلى وثائق API."
|
||||
msgid "Goodies"
|
||||
msgstr "الأشياء الجيدة"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "عنبي"
|
||||
@@ -570,7 +570,6 @@ msgstr "تحميل الاشتراكات ..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "تحميل العلامات ..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "انتقل إلى اشتراك بإدخال اسمه"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "انتقل إلى الفئة/الخلاصة التالية التي تحتوي على إدخالات غير مقروءة عند تمييز الكل كمقروء"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "أبدا"
|
||||
@@ -657,7 +655,6 @@ msgstr "أبدا"
|
||||
msgid "New password"
|
||||
msgstr "كلمة مرور جديدة"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "كلمة مرور جديدة"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "تصدير OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "ملف OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "الفئة الأصل"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "كلمة المرور"
|
||||
@@ -874,7 +867,6 @@ msgstr "تحديث"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "تم إغلاق التسجيلات في مثيل CommaFeed هذا"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "إعادة تعيين كلمة المرور"
|
||||
@@ -883,7 +875,6 @@ msgstr "إعادة تعيين كلمة المرور"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "نقرة بالزر الأيمن"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "التمرير"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "بحث"
|
||||
@@ -924,7 +913,6 @@ msgstr "تحديد الخلاصة/الفئة التالية غير المقرو
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "تحديد الخلاصة/الفئة السابقة غير المقروءة"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "رابط الخادم"
|
||||
@@ -953,8 +941,6 @@ msgstr "مشاركة"
|
||||
msgid "Sharing sites"
|
||||
msgstr "مشاركة المواقع"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "الحلقة"
|
||||
@@ -1003,7 +989,6 @@ msgstr "إظهار عدد غير المقروء في أيقونة التبويب
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "إظهار عدد غير المقروء في عنوان التبويب"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "قم بالتسجيل"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "شيء سيء حدث للتو ..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "فضاء"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "النجم"
|
||||
msgid "Starred"
|
||||
msgstr "مميز بنجمة"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "قم بالتبديل إلى النسق الفاتح"
|
||||
msgid "System"
|
||||
msgstr "النظام"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "الكلمات"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "تحتوي هذه الخلاصة على فلتر قديم لا يمكن تحريره ولا يتم تطبيقه. يرجى إعادة إنشاء الفلتر باستخدام محرر التعبيرات الجديد. كان تعبير الفلتر القديم هو: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "هذا هو مفتاح API الخاص بك. يمكن استخدامه لبعض عمليات API للقراءة فقط ويمنح الوصول إلى Fever API. استخدم النموذج الموجود في أسفل الصفحة لإنشاء مفتاح API جديد"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "هذا هو مفتاح API الخاص بك. يمكن استخدامه لبعض عمليات API للقراءة فقط ويمنح حق الوصول إلى Fever API و Google Reader API. استخدم النموذج الموجود أسفل الصفحة لإنشاء مفتاح API جديد"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "غير مقروءة"
|
||||
msgid "Unstar"
|
||||
msgstr "إلغاء النجم"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "إلغاء الاشتراك"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "مفتاح المستخدم"
|
||||
msgid "User name"
|
||||
msgstr "اسم المستخدم"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "اسم المستخدم أو البريد الإلكتروني"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Afegeix usuari"
|
||||
msgid "Admin"
|
||||
msgstr "Administrador"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nom d'usuari de l'administrador"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nom d'usuari de l'administrador"
|
||||
msgid "All"
|
||||
msgstr "Tot"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Sempre"
|
||||
@@ -173,18 +171,14 @@ msgstr "Creeu una expressió de filtratge per indicar què voleu llegir. Les ent
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel·la"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Versió de l'extensió del navegador CommaFeed {browserExtensionVersion}
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed és compatible amb l'API Fever. Utilitzeu l'URL següent al vostre client mòbil compatible amb Fever. Inicieu sessió amb el vostre nom d'usuari i la vostra <0>clau API</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed és compatible amb l'API de Google Reader. Utilitzeu l'URL següent al vostre client mòbil compatible amb Google Reader. Inicieu sessió amb el vostre nom d'usuari i la vostra <0>clau API</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed següent element no llegit"
|
||||
@@ -240,7 +238,6 @@ msgstr "Confirma"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirmeu la contrasenya"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirma la contrasenya"
|
||||
@@ -294,7 +291,6 @@ msgstr "dies"
|
||||
msgid "Delete"
|
||||
msgstr "Eliminar"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Suprimeix el compte"
|
||||
@@ -342,13 +338,10 @@ msgstr "Arrossegueu l'enllaç a la barra d'adreces d'interès"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Correu electrònic"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Adreça de correu electrònic"
|
||||
@@ -399,7 +392,6 @@ msgstr "Opcions de l'extensió"
|
||||
msgid "Feed name"
|
||||
msgstr "Nom del canal"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Vés a la documentació de l'API."
|
||||
msgid "Goodies"
|
||||
msgstr "Extres"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Raïm"
|
||||
@@ -570,7 +570,6 @@ msgstr "S'estan carregant les subscripcions..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Carregant les etiquetes..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Navegueu a una subscripció introduint-ne el nom"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navega a la següent categoria/canal amb entrades no llegides quan es marquen totes les entrades com a llegides"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Mai"
|
||||
@@ -657,7 +655,6 @@ msgstr "Mai"
|
||||
msgid "New password"
|
||||
msgstr "Contrasenya nova"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Contrasenya nova"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Exportació OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Fitxer OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "Categoria pare"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Contrasenya"
|
||||
@@ -874,7 +867,6 @@ msgstr "Actualitzar"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Els registres estan tancats en aquesta instància de CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Restableix la contrasenya"
|
||||
@@ -883,7 +875,6 @@ msgstr "Restableix la contrasenya"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clic dret"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Desplaçament"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
@@ -924,7 +913,6 @@ msgstr "Selecciona el següent canal/categoria no llegit"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Selecciona el canal/categoria anterior sense llegir"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL del servidor"
|
||||
@@ -953,8 +941,6 @@ msgstr "Comparteix"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Compartir a altres llocs web"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "canvi"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Mostra el recompte de no llegits a la icona de favorits de la pestanya"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Mostra el recompte de no llegits al títol de la pestanya"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Registra't"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Acaba de passar una cosa dolenta..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Espai"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Estrella"
|
||||
msgid "Starred"
|
||||
msgstr "Estrellat"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Canvia al tema clar"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetes"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Aquest feed té un filtre heretat que no es pot editar i no s'aplica. Torneu a crear el filtre amb el nou editor d'expressions. L'expressió del filtre heretat era: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Aquesta és la vostra clau de l'API. Es pot utilitzar per a algunes operacions de l'API de només lectura i permet accedir a l'API Fever. Utilitzeu el formulari de la part inferior de la pàgina per generar una nova clau d'API."
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Aquesta és la vostra clau API. Es pot utilitzar per a algunes operacions de l'API de només lectura i dona accés a l'API Fever i a l'API de Google Reader. Utilitzeu el formulari a la part inferior de la pàgina per generar una nova clau API"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Sense llegir"
|
||||
msgid "Unstar"
|
||||
msgstr "Desestrellar"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Donar-se de baixa"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Clau d'usuari"
|
||||
msgid "User name"
|
||||
msgstr "Nom d'usuari"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nom d'usuari o correu electrònic"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Přidat uživatele"
|
||||
msgid "Admin"
|
||||
msgstr "Správce"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Uživatelské jméno správce"
|
||||
@@ -73,7 +72,6 @@ msgstr "Uživatelské jméno správce"
|
||||
msgid "All"
|
||||
msgstr "Všechny"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Vždy"
|
||||
@@ -173,18 +171,14 @@ msgstr "Vytvořte výraz filtru pro určení toho, co chcete číst. Položky, k
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Zrušit"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Verze rozšíření prohlížeče CommaFeed {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed je kompatibilní s Fever API. Použijte následující URL ve svém mobilním klientovi kompatibilním s Fever. Přihlaste se pomocí svého uživatelského jména a svého <0>klíče API</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed je kompatibilní s Google Reader API. Použijte následující URL ve svém mobilním klientovi kompatibilním s Google Reader. Přihlaste se pomocí svého uživatelského jména a svého <0>klíče API</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed další nepřečtená položka"
|
||||
@@ -240,7 +238,6 @@ msgstr "Potvrdit"
|
||||
msgid "Confirm password"
|
||||
msgstr "Potvrďte heslo"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Potvrdit heslo"
|
||||
@@ -294,7 +291,6 @@ msgstr "dní"
|
||||
msgid "Delete"
|
||||
msgstr "Smazat"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Smazat účet"
|
||||
@@ -342,13 +338,10 @@ msgstr "Přetáhněte odkaz na lištu záložek"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mailová adresa"
|
||||
@@ -399,7 +392,6 @@ msgstr "Možnosti rozšíření"
|
||||
msgid "Feed name"
|
||||
msgstr "Název zdroje"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Přejděte na dokumentaci API."
|
||||
msgid "Goodies"
|
||||
msgstr "Dobroty"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Hroznová"
|
||||
@@ -570,7 +570,6 @@ msgstr "Načítání odběrů..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Načítání značek..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Přejděte na předplatné zadáním jeho názvu"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Po označení všech položek jako přečtených přejít na další kategorii/zdroj s nepřečtenými položkami"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nikdy"
|
||||
@@ -657,7 +655,6 @@ msgstr "Nikdy"
|
||||
msgid "New password"
|
||||
msgstr "Nové heslo"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nové heslo"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Export OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "soubor OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "Rodičovská kategorie"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Heslo"
|
||||
@@ -874,7 +867,6 @@ msgstr "Obnovit"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "V této instanci CommaFeed jsou registrace uzavřeny"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Obnovit heslo"
|
||||
@@ -883,7 +875,6 @@ msgstr "Obnovit heslo"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Kliknutí pravým tlačítkem"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Posouvání"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Hledej"
|
||||
@@ -924,7 +913,6 @@ msgstr "Vybrat další nepřečtený zdroj/kategorii"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Vybrat předchozí nepřečtený zdroj/kategorii"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL serveru"
|
||||
@@ -953,8 +941,6 @@ msgstr "Sdílejte"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Stránky pro sdílení"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Směna"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Zobrazovat počet nepřečtených v ikoně karty"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Zobrazovat počet nepřečtených v názvu karty"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Zaregistrovat se"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Právě se stalo něco špatného..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Vesmír"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Hvězda"
|
||||
msgid "Starred"
|
||||
msgstr "S hvězdičkou"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Přepnout na světlý motiv"
|
||||
msgid "System"
|
||||
msgstr "Systémový"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Štítky"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Tento zdroj má zastaralý filtr, který nelze upravovat a není aplikován. Znovu vytvořte filtr pomocí nového editoru výrazů. Původní výraz filtru byl: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Toto je váš klíč API. Lze jej použít pro některé operace API pouze pro čtení a poskytuje přístup k Fever API. Pro vygenerování nového klíče API použijte formulář v dolní části stránky."
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Toto je váš klíč API. Lze jej použít pro některé operace API pouze pro čtení a uděluje přístup k Fever API a Google Reader API. Použijte formulář v dolní části stránky k vygenerování nového klíče API"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Nepřečteno"
|
||||
msgid "Unstar"
|
||||
msgstr "Odstranit hvězdu"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Odhlásit odběr"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Uživatelský klíč"
|
||||
msgid "User name"
|
||||
msgstr "Uživatelské jméno"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Uživatelské jméno nebo e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Ychwanegu defnyddiwr"
|
||||
msgid "Admin"
|
||||
msgstr "Gweinyddol"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Enw defnyddiwr gweinyddwr"
|
||||
@@ -73,7 +72,6 @@ msgstr "Enw defnyddiwr gweinyddwr"
|
||||
msgid "All"
|
||||
msgstr "Pawb"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Bob amser"
|
||||
@@ -173,18 +171,14 @@ msgstr "Adeiladwch fynegiant hidlo i nodi'r hyn rydych chi am ei ddarllen. Bydd
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Diddymu"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Fersiwn estyniad porwr CommaFeed {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "Mae CommaFeed yn gydnaws â'r Fever API. Defnyddiwch y URL canlynol yn eich cleient symudol sy'n gydnaws â Fever. Mewngofnodwch gyda'ch enw defnyddiwr a'ch <0>allwedd API</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "Mae CommaFeed yn gydnaws â Google Reader API. Defnyddiwch y URL canlynol yn eich cleient symudol sy'n gydnaws â Google Reader. Mewngofnodwch gyda'ch enw defnyddiwr a'ch <0>allwedd API</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed eitem nesaf heb ei darllen"
|
||||
@@ -240,7 +238,6 @@ msgstr "Cadarnhau"
|
||||
msgid "Confirm password"
|
||||
msgstr "Cadarnhau'r cyfrinair"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Cadarnhau Cyfrinair"
|
||||
@@ -294,7 +291,6 @@ msgstr "diwrnodau"
|
||||
msgid "Delete"
|
||||
msgstr "Dileu"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Dileu cyfrif"
|
||||
@@ -342,13 +338,10 @@ msgstr "Llusgwch y ddolen i'r bar nod tudalen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-bost"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "cyfeiriad e-bost"
|
||||
@@ -399,7 +392,6 @@ msgstr "Opsiynau estyniad"
|
||||
msgid "Feed name"
|
||||
msgstr "Enw porthiant"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Ewch i'r ddogfennaeth API."
|
||||
msgid "Goodies"
|
||||
msgstr "nwyddau"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Grawnwin"
|
||||
@@ -570,7 +570,6 @@ msgstr "Yn llwytho tanysgrifiadau..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Wrthi'n llwytho tagiau..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Llywiwch i danysgrifiad trwy nodi ei enw"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Llywiwch i'r categori/porthiant nesaf gyda chofnodion heb eu darllen wrth farcio pob cofnod fel un wedi'i ddarllen"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Byth"
|
||||
@@ -657,7 +655,6 @@ msgstr "Byth"
|
||||
msgid "New password"
|
||||
msgstr "Cyfrinair newydd"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Cyfrinair Newydd"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Allforio OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Ffeil OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "Categori Rhiant"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "cyfrinair"
|
||||
@@ -874,7 +867,6 @@ msgstr "Adnewyddu"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Mae cofrestriadau ar gau ar yr achos CommaFeed hwn"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Ailosod Cyfrinair"
|
||||
@@ -883,7 +875,6 @@ msgstr "Ailosod Cyfrinair"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clic dde"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Sgrolio"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Chwilio"
|
||||
@@ -924,7 +913,6 @@ msgstr "Dewiswch y porthiant/categori nesaf heb ei ddarllen"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Dewiswch y porthiant/categori blaenorol heb ei ddarllen"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL y gweinydd"
|
||||
@@ -953,8 +941,6 @@ msgstr "Rhannu"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Rhannu gwefannau"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "shifft"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Dangos nifer heb ei ddarllen yn ffaficon y tab"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Dangos nifer heb ei ddarllen yn nheitl y tab"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Cofrestrwch"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Mae rhywbeth drwg newydd ddigwydd ..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Gofod"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "seren"
|
||||
msgid "Starred"
|
||||
msgstr "serennog"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Newid i thema golau"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tagiau"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Mae gan y porthiant hwn hen hidlydd na ellir ei olygu ac nid yw'n cael ei gymhwyso. Crëwch yr hidlydd eto gan ddefnyddio'r golygydd mynegiant newydd. Yr hen fynegiant hidlo oedd: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dyma'ch allwedd API. Gellir ei ddefnyddio ar gyfer rhai gweithrediadau API darllen-yn-unig ac mae'n caniatáu mynediad i'r Fever API. Defnyddiwch y ffurflen ar waelod y dudalen i gynhyrchu allwedd API newydd"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dyma eich allwedd API. Gellir ei defnyddio ar gyfer rhai gweithrediadau API darllen-yn-unig ac mae'n rhoi mynediad i'r Fever API a'r Google Reader API. Defnyddiwch y ffurflen ar waelod y dudalen i gynhyrchu allwedd API newydd"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Heb ei ddarllen"
|
||||
msgid "Unstar"
|
||||
msgstr "dad-seren"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Dad-danysgrifio"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Allwedd defnyddiwr"
|
||||
msgid "User name"
|
||||
msgstr "Enw defnyddiwr"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Enw Defnyddiwr neu E-bost"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Tilføj bruger"
|
||||
msgid "Admin"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Administrator brugernavn"
|
||||
@@ -73,7 +72,6 @@ msgstr "Administrator brugernavn"
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Altid"
|
||||
@@ -173,18 +171,14 @@ msgstr "Byg et filterudtryk for at angive, hvad du vil læse. Indlæg, der ikke
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Annuller"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed browserudvidelse version {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed er kompatibel med Fever API. Brug følgende URL i din Fever-kompatible mobilklient. Log ind med dit brugernavn og din <0>API-nøgle</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed er kompatibel med Google Reader API. Brug følgende URL i din Google Reader-kompatible mobilklient. Log ind med dit brugernavn og din <0>API-nøgle</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed næste ulæste element"
|
||||
@@ -240,7 +238,6 @@ msgstr "Bekræft"
|
||||
msgid "Confirm password"
|
||||
msgstr "Bekræft adgangskode"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Bekræft adgangskode"
|
||||
@@ -294,7 +291,6 @@ msgstr "dage"
|
||||
msgid "Delete"
|
||||
msgstr "Slet"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Slet konto"
|
||||
@@ -342,13 +338,10 @@ msgstr "Træk linket til bogmærkelinjen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mailadresse"
|
||||
@@ -399,7 +392,6 @@ msgstr "Indstillinger for udvidelse"
|
||||
msgid "Feed name"
|
||||
msgstr "Feednavn"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Gå til API-dokumentationen."
|
||||
msgid "Goodies"
|
||||
msgstr "Godbidder"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Grape"
|
||||
@@ -570,7 +570,6 @@ msgstr "Indlæser abonnementer..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Indlæser tags..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Naviger til et abonnement ved at indtaste dets navn"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Naviger til næste kategori/feed med ulæste indlæg, når alle indlæg markeres som læst"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Aldrig"
|
||||
@@ -657,7 +655,6 @@ msgstr "Aldrig"
|
||||
msgid "New password"
|
||||
msgstr "Ny adgangskode"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Ny adgangskode"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML eksport"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML fil"
|
||||
@@ -791,10 +787,7 @@ msgstr "Forældrekategori"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Adgangskode"
|
||||
@@ -874,7 +867,6 @@ msgstr "Opdater"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registreringer er lukket på denne CommaFeed-instans"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Nulstil adgangskode"
|
||||
@@ -883,7 +875,6 @@ msgstr "Nulstil adgangskode"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Højreklik"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Rulning"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Søg"
|
||||
@@ -924,7 +913,6 @@ msgstr "Vælg næste ulæste feed/kategori"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Vælg forrige ulæste feed/kategori"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +941,6 @@ msgstr "Del"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Delingssider"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Skift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Vis ulæst antal i fanens favicon"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Vis ulæst antal i fanens titel"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Tilmeld dig"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Der er lige sket noget slemt..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Rum"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Stjerne"
|
||||
msgid "Starred"
|
||||
msgstr "Medvirkende"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Skift til lystema"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Dette feed har et ældre filter, der ikke kan redigeres og ikke anvendes. Genopret venligst filteret ved hjælp af den nye udtrykseditor. Det ældre filterudtryk var: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dette er din API-nøgle. Den kan bruges til visse skrivebeskyttede API-operationer og giver adgang til Fever API. Brug formularen nederst på siden til at generere en ny API-nøgle"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dette er din API-nøgle. Den kan bruges til nogle skrivebeskyttede API-operationer og giver adgang til Fever API og Google Reader API. Brug formularen nederst på siden for at generere en ny API-nøgle"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Ulæst"
|
||||
msgid "Unstar"
|
||||
msgstr "Fjern stjerne"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Afmeld"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Brugernøgle"
|
||||
msgid "User name"
|
||||
msgstr "Brugernavn"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Brugernavn eller e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Benutzer hinzufügen"
|
||||
msgid "Admin"
|
||||
msgstr "Verwaltung"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Administrator-Benutzername"
|
||||
@@ -73,7 +72,6 @@ msgstr "Administrator-Benutzername"
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Immer"
|
||||
@@ -173,18 +171,14 @@ msgstr "Erstellen Sie einen Filterausdruck, um anzugeben, was Sie lesen möchten
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed Browser Erweiterung Version {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed ist mit der Fever-API kompatibel. Verwenden Sie die folgende URL in Ihrem Fever-kompatiblen mobilen Client. Melden Sie sich mit Ihrem Benutzernamen und Ihrem <0>API-Schlüssel</0> an."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed ist mit der Google-Reader-API kompatibel. Verwenden Sie die folgende URL in Ihrem Google-Reader-kompatiblen mobilen Client. Melden Sie sich mit Ihrem Benutzernamen und Ihrem <0>API-Schlüssel</0> an."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed nächstes ungelesenes Element"
|
||||
@@ -240,7 +238,6 @@ msgstr "Bestätigen"
|
||||
msgid "Confirm password"
|
||||
msgstr "Passwort bestätigen"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Passwort bestätigen"
|
||||
@@ -294,7 +291,6 @@ msgstr "Tage"
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Konto löschen"
|
||||
@@ -342,13 +338,10 @@ msgstr "Link in Lesezeichenleiste ziehen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-Mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-Mail-Adresse"
|
||||
@@ -399,7 +392,6 @@ msgstr "Erweiterungsoptionen"
|
||||
msgid "Feed name"
|
||||
msgstr "Feedname"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Gehen Sie zur API-Dokumentation."
|
||||
msgid "Goodies"
|
||||
msgstr "Goodies"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Traube"
|
||||
@@ -570,7 +570,6 @@ msgstr "Abonnements werden geladen..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Tags werden geladen..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Navigieren Sie zu einem Abonnement, indem Sie seinen Namen eingeben"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigieren Sie zum nächsten Feed/Kategorie mit ungelesenen Einträgen, wenn Sie alle Einträge als gelesen markieren"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Niemals"
|
||||
@@ -657,7 +655,6 @@ msgstr "Niemals"
|
||||
msgid "New password"
|
||||
msgstr "Neues Passwort"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Neues Passwort"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-Export"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-Datei"
|
||||
@@ -791,10 +787,7 @@ msgstr "Übergeordnete Kategorie"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
@@ -874,7 +867,6 @@ msgstr "Aktualisieren"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registrierungen sind für diese CommaFeed-Instanz geschlossen"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Passwort zurücksetzen"
|
||||
@@ -883,7 +875,6 @@ msgstr "Passwort zurücksetzen"
|
||||
msgid "REST API"
|
||||
msgstr "REST-API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Rechtsklick"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Scrollen"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Suche"
|
||||
@@ -924,7 +913,6 @@ msgstr "Nächsten ungelesenen Feed/Kategorie auswählen"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Vorherigen ungelesenen Feed/Kategorie auswählen"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +941,6 @@ msgstr "Teilen"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Seiten teilen"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Verschiebung"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Anzahl ungelesener Nachrichten im Tab-Favicon anzeigen"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Anzahl ungelesener Nachrichten im Tab-Titel anzeigen"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Melden Sie sich an"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Etwas Schlimmes ist gerade passiert..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Raum"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Stern"
|
||||
msgid "Starred"
|
||||
msgstr "Markiert"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Zum Lightmode wechseln"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Dieser Feed verfügt über einen veralteten Filter, der nicht bearbeitet werden kann und nicht angewendet wird. Bitte erstellen Sie den Filter mit dem neuen Ausdruckseditor neu. Der veraltete Filterausdruck war: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dies ist Ihr API-Schlüssel. Er kann für einige schreibgeschützte API-Vorgänge verwendet werden und ermöglicht den Zugriff auf die Fever-API. Verwenden Sie das Formular unten auf der Seite, um einen neuen API-Schlüssel zu generieren"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dies ist Ihr API-Schlüssel. Er kann für einige schreibgeschützte API-Vorgänge verwendet werden und gewährt Zugriff auf die Fever-API und die Google-Reader-API. Verwenden Sie das Formular am Ende der Seite, um einen neuen API-Schlüssel zu generieren"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Ungelesen"
|
||||
msgid "Unstar"
|
||||
msgstr "Stern entfernen"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Abbestellen"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Benutzerschlüssel"
|
||||
msgid "User name"
|
||||
msgstr "Benutzername"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Benutzername oder E-Mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Add user"
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Admin user name"
|
||||
@@ -73,7 +72,6 @@ msgstr "Admin user name"
|
||||
msgid "All"
|
||||
msgstr "All"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Always"
|
||||
@@ -173,18 +171,14 @@ msgstr "Build a filter expression to indicate what you want to read. Entries tha
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed browser extension version {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed next unread item"
|
||||
@@ -240,7 +238,6 @@ msgstr "Confirm"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirm password"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirm Password"
|
||||
@@ -294,7 +291,6 @@ msgstr "days"
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Delete account"
|
||||
@@ -342,13 +338,10 @@ msgstr "Drag link to bookmark bar"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mail address"
|
||||
@@ -399,7 +392,6 @@ msgstr "Extension options"
|
||||
msgid "Feed name"
|
||||
msgstr "Feed name"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Go to the API documentation."
|
||||
msgid "Goodies"
|
||||
msgstr "Goodies"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr "Google Reader API"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr "Google Reader API URL"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Grape"
|
||||
@@ -570,7 +570,6 @@ msgstr "Loading subscriptions..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Loading tags..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Navigate to a subscription by entering its name"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Never"
|
||||
@@ -657,7 +655,6 @@ msgstr "Never"
|
||||
msgid "New password"
|
||||
msgstr "New password"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "New Password"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML export"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML file"
|
||||
@@ -791,10 +787,7 @@ msgstr "Parent Category"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
@@ -874,7 +867,6 @@ msgstr "Refresh"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registrations are closed on this CommaFeed instance"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Reset Password"
|
||||
@@ -883,7 +875,6 @@ msgstr "Reset Password"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Right click"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Scrolling"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Search"
|
||||
@@ -924,7 +913,6 @@ msgstr "Select next unread feed/category"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Select previous unread feed/category"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server URL"
|
||||
@@ -953,8 +941,6 @@ msgstr "Share"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Sharing sites"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Show unread count in tab favicon"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Show unread count in tab title"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Sign up"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Something bad just happened..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Space"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Star"
|
||||
msgid "Starred"
|
||||
msgstr "Starred"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Switch to light theme"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "This feed has a legacy filter that cannot be edited and is not applied. Please recreate the filter using the new expression editor. The legacy filter expression was: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Unread"
|
||||
msgid "Unstar"
|
||||
msgstr "Unstar"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Unsubscribe"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "User key"
|
||||
msgid "User name"
|
||||
msgstr "User name"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "User Name or E-mail"
|
||||
|
||||
@@ -61,7 +61,6 @@ msgstr "Añadir usuario"
|
||||
msgid "Admin"
|
||||
msgstr "Administrador"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nombre de usuario administrador"
|
||||
@@ -74,7 +73,6 @@ msgstr "Nombre de usuario administrador"
|
||||
msgid "All"
|
||||
msgstr "Todo"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Siempre"
|
||||
@@ -174,18 +172,14 @@ msgstr "Crea una expresión de filtrado para indicar lo que quieres leer. Las en
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -217,6 +211,10 @@ msgstr "Versión de la extensión del navegador CommaFeed {browserExtensionVersi
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed es compatible con Fever API. Utilice la siguiente URL en su cliente móvil compatible con Fever. Inicie sesión con su nombre de usuario y su <0>clave API</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed es compatible con Google Reader API. Utilice la siguiente URL en su cliente móvil compatible con Google Reader. Inicie sesión con su nombre de usuario y su <0>clave API</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed siguiente elemento no leído"
|
||||
@@ -241,7 +239,6 @@ msgstr "Confirmar"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirmar contraseña"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirmar contraseña"
|
||||
@@ -295,7 +292,6 @@ msgstr "días"
|
||||
msgid "Delete"
|
||||
msgstr "Borrar"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Borrar cuenta"
|
||||
@@ -343,13 +339,10 @@ msgstr "Arrastra el enlace a la barra de marcadores"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Correo electrónico"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Dirección de correo electrónico"
|
||||
@@ -400,7 +393,6 @@ msgstr "Opciones de la extensión"
|
||||
msgid "Feed name"
|
||||
msgstr "Nombre del feed"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -467,6 +459,14 @@ msgstr "Ir a la documentación de la API."
|
||||
msgid "Goodies"
|
||||
msgstr "Golosinas"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Uva"
|
||||
@@ -571,7 +571,6 @@ msgstr "Cargando suscripciones..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Cargando etiquetas..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -649,7 +648,6 @@ msgstr "Navegar a una suscripción introduciendo su nombre"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navegar a la siguiente categoría/feed con entradas no leídas al marcar todas como leídas"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nunca"
|
||||
@@ -658,7 +656,6 @@ msgstr "Nunca"
|
||||
msgid "New password"
|
||||
msgstr "Nueva contraseña"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nueva contraseña"
|
||||
@@ -765,7 +762,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Exportar OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Archivo OPML"
|
||||
@@ -792,10 +788,7 @@ msgstr "Categoría principal"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Contraseña"
|
||||
@@ -875,7 +868,6 @@ msgstr "Actualizar"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Los registros están cerrados en esta instancia de CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Restablecer contraseña"
|
||||
@@ -884,7 +876,6 @@ msgstr "Restablecer contraseña"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clic derecho"
|
||||
@@ -911,8 +902,6 @@ msgid "Scrolling"
|
||||
msgstr "Desplazarse"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Buscar"
|
||||
@@ -925,7 +914,6 @@ msgstr "Seleccionar el siguiente feed/categoría no leído"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Seleccionar el anterior feed/categoría no leído"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL del servidor"
|
||||
@@ -954,8 +942,6 @@ msgstr "Compartir"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Compartir sitios"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1004,7 +990,6 @@ msgstr "Mostrar recuento de no leídos en la pestaña favicon"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Mostrar recuento de no leídos en el título de la pestaña"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1014,7 +999,6 @@ msgstr "Registrarse"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Algo malo acaba de pasar..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Espacio"
|
||||
@@ -1031,7 +1015,6 @@ msgstr "Estrella"
|
||||
msgid "Starred"
|
||||
msgstr "Destacado"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1065,7 +1048,6 @@ msgstr "Cambiar a tema claro"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetas"
|
||||
@@ -1096,8 +1078,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Este feed tiene un filtro heredado que no se puede editar y no se aplica. Por favor, vuelve a crear el filtro usando el nuevo editor de expresiones. La expresión del filtro heredado era: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Esta es su clave API. Se puede utilizar para algunas operaciones API de solo lectura y otorga acceso a Fever API. Utilice el formulario en la parte inferior de la página para generar una nueva clave API"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Esta es su clave API. Se puede utilizar para algunas operaciones API de solo lectura y otorga acceso a la API Fever y a la API de Google Reader. Utilice el formulario en la parte inferior de la página para generar una nueva clave API"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1137,7 +1119,6 @@ msgstr "No leído"
|
||||
msgid "Unstar"
|
||||
msgstr "Desmarcar"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Cancelar suscripción"
|
||||
@@ -1150,7 +1131,6 @@ msgstr "Clave de usuario"
|
||||
msgid "User name"
|
||||
msgstr "Nombre de usuario"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nombre de usuario o correo electrónico"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "افزودن کاربر"
|
||||
msgid "Admin"
|
||||
msgstr "مدیر"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "نام کاربری مدیر"
|
||||
@@ -73,7 +72,6 @@ msgstr "نام کاربری مدیر"
|
||||
msgid "All"
|
||||
msgstr "همه"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "همیشه"
|
||||
@@ -173,18 +171,14 @@ msgstr "یک عبارت فیلتر بسازید تا نشان دهید چه چی
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "لغو"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "نسخه افزونه مرورگر CommaFeed {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed با Fever API سازگار است. از آدرس زیر در کلاینت موبایل سازگار با Fever استفاده کنید. با نام کاربری و <0>کلید API</0> خود وارد شوید."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed با Google Reader API سازگار است. از آدرس زیر در کلاینت موبایل سازگار با Google Reader استفاده کنید. با نام کاربری و <0>کلید API</0> خود وارد شوید."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "مورد خوانده نشده بعدی CommaFeed"
|
||||
@@ -240,7 +238,6 @@ msgstr "تأیید کنید"
|
||||
msgid "Confirm password"
|
||||
msgstr "رمز عبور را تأیید کنید"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "تأیید رمز عبور"
|
||||
@@ -294,7 +291,6 @@ msgstr "روز"
|
||||
msgid "Delete"
|
||||
msgstr "حذف کنید"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "حذف حساب"
|
||||
@@ -342,13 +338,10 @@ msgstr "پیوند را به نوار نشانک بکشید"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "ایمیل"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "آدرس ایمیل"
|
||||
@@ -399,7 +392,6 @@ msgstr "گزینه های افزونه"
|
||||
msgid "Feed name"
|
||||
msgstr "نام فید"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "به مستندات API بروید."
|
||||
msgid "Goodies"
|
||||
msgstr "خوبی ها"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "عنبی"
|
||||
@@ -570,7 +570,6 @@ msgstr "بارگیری اشتراک ها..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "بارگیری برچسب ها..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "با وارد کردن نام اشتراک، به آن بروید"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "هنگام علامتگذاری همه ورودیها بهعنوان خواندهشده، به دستهبندی/فید بعدی با ورودیهای خواندهنشده بروید"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "هرگز"
|
||||
@@ -657,7 +655,6 @@ msgstr "هرگز"
|
||||
msgid "New password"
|
||||
msgstr "رمز عبور جدید"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "رمز عبور جدید"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "خروجی OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "فایل OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "دسته والد"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "رمز عبور"
|
||||
@@ -874,7 +867,6 @@ msgstr "تازه کردن"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "ثبت نام در این نمونه CommaFeed بسته شده است"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "بازنشانی رمز عبور"
|
||||
@@ -883,7 +875,6 @@ msgstr "بازنشانی رمز عبور"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "راست کلیک"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "پیمایش"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "جستجو"
|
||||
@@ -924,7 +913,6 @@ msgstr "انتخاب فید/دسته بعدی خوانده نشده"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "انتخاب فید/دسته قبلی خوانده نشده"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "آدرس سرور"
|
||||
@@ -953,8 +941,6 @@ msgstr "به اشتراک بگذارید"
|
||||
msgid "Sharing sites"
|
||||
msgstr "اشتراک گذاری سایت ها"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "شیفت"
|
||||
@@ -1003,7 +989,6 @@ msgstr "نمایش تعداد خوانده نشده در فاویکون زبان
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "نمایش تعداد خوانده نشده در عنوان زبانه"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "ثبت نام کنید"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "اتفاق بدی افتاد..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "فضا"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "ستاره"
|
||||
msgid "Starred"
|
||||
msgstr "ستاره دار"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "تغییر به تم روشن"
|
||||
msgid "System"
|
||||
msgstr "سیستم"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "برچسبها"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "این فید دارای یک فیلتر قدیمی است که قابل ویرایش نیست و اعمال نمیشود. لطفاً فیلتر را با استفاده از ویرایشگر جدید بازسازی کنید. عبارت فیلتر قدیمی: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "این کلید API شماست. میتوان از آن برای برخی عملیات API فقط خواندنی استفاده کرد و دسترسی به Fever API را فراهم میکند. از فرم پایین صفحه برای تولید کلید جدید استفاده کنید"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "این کلید API شماست. میتوان از آن برای برخی عملیات فقط خواندنی API استفاده کرد و دسترسی به Fever API و Google Reader API را میدهد. از فرم پایین صفحه برای ایجاد کلید API جدید استفاده کنید"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "خوانده نشده"
|
||||
msgid "Unstar"
|
||||
msgstr "برداشتن ستاره"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "لغو اشتراک"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "کلید کاربر"
|
||||
msgid "User name"
|
||||
msgstr "نام کاربری"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "نام کاربری یا ایمیل"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Lisää käyttäjä"
|
||||
msgid "Admin"
|
||||
msgstr "Järjestelmänvalvoja"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Järjestelmänvalvojan käyttäjänimi"
|
||||
@@ -73,7 +72,6 @@ msgstr "Järjestelmänvalvojan käyttäjänimi"
|
||||
msgid "All"
|
||||
msgstr "Kaikki"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Aina"
|
||||
@@ -173,18 +171,14 @@ msgstr "Rakenna suodatinlauseke määrittääksesi, mitä haluat lukea. Merkinn
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Peruuta"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed-selainlaajennuksen versio {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed on yhteensopiva Fever-API:n kanssa. Käytä seuraavaa URL-osoitetta Fever-yhteensopivassa mobiiliohjelmassasi. Kirjaudu sisään käyttäjätunnuksellasi ja <0>API-avaimellasi</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed on yhteensopiva Google Reader -API:n kanssa. Käytä seuraavaa URL-osoitetta Google Reader -yhteensopivassa mobiiliohjelmassasi. Kirjaudu sisään käyttäjätunnuksellasi ja <0>API-avaimellasi</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed seuraava lukematon kohde"
|
||||
@@ -240,7 +238,6 @@ msgstr "Vahvista"
|
||||
msgid "Confirm password"
|
||||
msgstr "Vahvista salasana"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Vahvista salasana"
|
||||
@@ -294,7 +291,6 @@ msgstr "päivää"
|
||||
msgid "Delete"
|
||||
msgstr "Poista"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Poista tili"
|
||||
@@ -342,13 +338,10 @@ msgstr "Vedä linkki kirjanmerkkipalkkiin"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Sähköposti"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Sähköpostiosoite"
|
||||
@@ -399,7 +392,6 @@ msgstr "Laajennuksen asetukset"
|
||||
msgid "Feed name"
|
||||
msgstr "Syötteen nimi"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Siirry API-dokumentaatioon."
|
||||
msgid "Goodies"
|
||||
msgstr "Hyvää"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Viinirypäle"
|
||||
@@ -570,7 +570,6 @@ msgstr "Ladataan tilauksia..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Ladataan tunnisteita..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Siirry tilaukseen kirjoittamalla sen nimi"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Siirry seuraavaan luokkaan/syötteeseen, jossa on lukemattomia merkintöjä, kun kaikki merkinnät merkitään luetuiksi"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Ei koskaan"
|
||||
@@ -657,7 +655,6 @@ msgstr "Ei koskaan"
|
||||
msgid "New password"
|
||||
msgstr "Uusi salasana"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Uusi salasana"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-vienti"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-tiedosto"
|
||||
@@ -791,10 +787,7 @@ msgstr "Pääluokka"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Salasana"
|
||||
@@ -874,7 +867,6 @@ msgstr "Päivitä"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Tämän CommaFeed-esiintymän rekisteröinnit on suljettu"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Nollaa salasana"
|
||||
@@ -883,7 +875,6 @@ msgstr "Nollaa salasana"
|
||||
msgid "REST API"
|
||||
msgstr "REST-API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Oikea napsautus"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Vieritys"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Etsi"
|
||||
@@ -924,7 +913,6 @@ msgstr "Valitse seuraava lukematon syöte/luokka"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Valitse edellinen lukematon syöte/luokka"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Palvelimen URL-osoite"
|
||||
@@ -953,8 +941,6 @@ msgstr "Jaa"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Sivustojen jakaminen"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Vaihto"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Näytä lukemattomien määrä välilehden kuvakkeessa"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Näytä lukemattomien määrä välilehden otsikossa"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Rekisteröidy"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Jotain pahaa tapahtui juuri..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Avaruus"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Tähti"
|
||||
msgid "Starred"
|
||||
msgstr "Tähdellä merkitty"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Vaihda vaaleaan teemaan"
|
||||
msgid "System"
|
||||
msgstr "Järjestelmä"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tunnisteet"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Tässä syötteessä on vanha suodatin, jota ei voi muokata ja jota ei sovelleta. Luo suodatin uudelleen käyttämällä uutta lauseke-editoria. Vanha suodatinlauseke oli: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Tämä on API-avaimesi. Sitä voidaan käyttää joihinkin vain luku -API-toimintoihin ja se antaa pääsyn Fever-API:iin. Käytä sivun alareunassa olevaa lomaketta uuden API-avaimen luomiseen"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Tämä on API-avaimesi. Sitä voidaan käyttää joihinkin vain luku -API-toimintoihin, ja se myöntää pääsyn Fever-API:hin ja Google Reader -API:hin. Käytä sivun alareunassa olevaa lomaketta luodaksesi uuden API-avaimen"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Lukematon"
|
||||
msgid "Unstar"
|
||||
msgstr "Poista tähti"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Peruuta tilaus"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Käyttäjäavain"
|
||||
msgid "User name"
|
||||
msgstr "Käyttäjänimi"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Käyttäjänimi tai sähköpostiosoite"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Ajouter un utilisateur"
|
||||
msgid "Admin"
|
||||
msgstr "Administrateur"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nom de l'administrateur"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nom de l'administrateur"
|
||||
msgid "All"
|
||||
msgstr "Tout"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Toujours"
|
||||
@@ -173,18 +171,14 @@ msgstr "Créez une expression régulière pour filtrer ce que vous voulez lire.
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Extension CommaFeed pour navigateur version {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "Commafeed est compatible avec l'API Fever, en inscrivant l'URL suivante dans votre client mobile compatible. Entrez votre nom d'utilisateur habituel, et votre <0>clef API</0> comme mot de passe."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "Commafeed est compatible avec l'API Google Reader, en inscrivant l'URL suivante dans votre client mobile compatible. Entrez votre nom d'utilisateur habituel, et votre <0>clef API</0> comme mot de passe."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed prochain article non lu"
|
||||
@@ -240,7 +238,6 @@ msgstr "Confirmer"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirmer le mot de passe"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirmer le mot de passe"
|
||||
@@ -294,7 +291,6 @@ msgstr "jours"
|
||||
msgid "Delete"
|
||||
msgstr "Effacer"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Effacer le compte"
|
||||
@@ -342,13 +338,10 @@ msgstr "Déplacez le lien vers la barre de favoris"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Adresse e-mail"
|
||||
@@ -399,7 +392,6 @@ msgstr "Options de l'extension"
|
||||
msgid "Feed name"
|
||||
msgstr "Nom du flux"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Aller à la documentation de l'API."
|
||||
msgid "Goodies"
|
||||
msgstr "Extensions"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Bordeaux"
|
||||
@@ -570,7 +570,6 @@ msgstr "Chargement des abonnements..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Chargement des marqueurs..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Naviguer vers un abonnement en entrant son nom"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Aller vers la catégorie/le flux comportant des entrées non lues suivant après avoir marqué toutes les entrées lues"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Jamais"
|
||||
@@ -657,7 +655,6 @@ msgstr "Jamais"
|
||||
msgid "New password"
|
||||
msgstr "Nouveau mot de passe"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nouveau mot de passe"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Export du fichier OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Fichier OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "Catégorie parente"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
@@ -874,7 +867,6 @@ msgstr "Rafraîchir"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Les inscriptions sont fermées sur cette instance de CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Réinitialiser le mot de passe"
|
||||
@@ -883,7 +875,6 @@ msgstr "Réinitialiser le mot de passe"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clic droit"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Défilement"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Rechercher"
|
||||
@@ -924,7 +913,6 @@ msgstr "Sélectionner l'article non lu suivant/la catégorie non lue suivante"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Sélectionner l'article non lu précédent/la catégorie non lue précédente"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL du serveur"
|
||||
@@ -953,8 +941,6 @@ msgstr "Partager"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Sites de partage"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Maj"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Afficher le nombre d'entrées non lues dans la favicône de l'onglet"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Afficher le nombre d'entrées non lues dans le titre de l'onglet"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Créer un compte"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Quelque chose s'est mal passé..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Espace"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Ajouter aux favoris"
|
||||
msgid "Starred"
|
||||
msgstr "Favoris"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Activer le mode clair"
|
||||
msgid "System"
|
||||
msgstr "Système"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Marqueurs"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Ce flux est configuré avec un filtre obsolète, qui ne peut être ni modifié ni appliqué. Vous devez recréer ce filtre à l'aide du nouvel éditeur d'expressions régulières. Le filtre configuré était : <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Ceci est votre clef API. Elle peut être utilisée pour certaines opérations en lecture seule et donne accès à l'API Fever. Utilisez le formulaire en bas de la page pour générer une nouvelle clef API"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Ceci est votre clef API. Elle peut être utilisée pour certaines opérations en lecture seule et donne accès à l'API Fever et à l'API Google Reader. Utilisez le formulaire en bas de la page pour générer une nouvelle clef API"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Non lu"
|
||||
msgid "Unstar"
|
||||
msgstr "Retirer des favoris"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Se désabonner"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Clé utilisateur"
|
||||
msgid "User name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nom d'utilisateur ou e-mail"
|
||||
|
||||
@@ -61,7 +61,6 @@ msgstr "Engadir persoa usuaria"
|
||||
msgid "Admin"
|
||||
msgstr "Administración"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Identificador de admin"
|
||||
@@ -74,7 +73,6 @@ msgstr "Identificador de admin"
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Sempre"
|
||||
@@ -174,18 +172,14 @@ msgstr "Crea unha expresión de filtrado para indicar o que queres ler. Os artig
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -217,6 +211,10 @@ msgstr "Versión do complemento do navegador para CommaFeed {browserExtensionVer
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed é compatible coa API de Fever. Usa o seguinte URL no teu cliente móbil compatible con Fever. Accede co identificador e a túa <0>clave API</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed é compatible coa API de Google Reader. Usa o seguinte URL no teu cliente móbil compatible con Google Reader. Accede co identificador e a túa <0>clave API</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "Seguinte elemento non lido en CommaFeed"
|
||||
@@ -241,7 +239,6 @@ msgstr "Confirmar"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirmar contrasinal"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirmar contrasinal"
|
||||
@@ -295,7 +292,6 @@ msgstr "días"
|
||||
msgid "Delete"
|
||||
msgstr "Eliminar"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Eliminar conta"
|
||||
@@ -343,13 +339,10 @@ msgstr "Arrastra a ligazón á barra de marcadores"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Correo electrónico"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Enderezo de correo electrónico"
|
||||
@@ -400,7 +393,6 @@ msgstr "Opcións do complemento"
|
||||
msgid "Feed name"
|
||||
msgstr "Nome da canle"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -467,6 +459,14 @@ msgstr "Ir á documentación da API."
|
||||
msgid "Goodies"
|
||||
msgstr "Agasallos"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Bago"
|
||||
@@ -571,7 +571,6 @@ msgstr "Cargando subscricións..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Cargando etiquetas..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -649,7 +648,6 @@ msgstr "Vai a unha subscrición ao escribir o seu nome"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Vai á seguinte categoría/canle con artigos sen ler cando marques todos artigos como lidos"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nunca"
|
||||
@@ -658,7 +656,6 @@ msgstr "Nunca"
|
||||
msgid "New password"
|
||||
msgstr "Novo contrasinal"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Novo contrasinal"
|
||||
@@ -765,7 +762,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Exportación OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Ficheiro OPML"
|
||||
@@ -792,10 +788,7 @@ msgstr "Categoría superior"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Contrasinal"
|
||||
@@ -875,7 +868,6 @@ msgstr "Actualizar"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Non se admiten novas contas nesta instancia de CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Restablecer contrasinal"
|
||||
@@ -884,7 +876,6 @@ msgstr "Restablecer contrasinal"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Botón dereito"
|
||||
@@ -911,8 +902,6 @@ msgid "Scrolling"
|
||||
msgstr "Desprazamento"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Busca"
|
||||
@@ -925,7 +914,6 @@ msgstr "Seleccionar a seguinte canle/categoría sen ler"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Seleccionar a canle/categoría anterior sen ler"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL do servidor"
|
||||
@@ -954,8 +942,6 @@ msgstr "Compartir"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Servizos para compartir"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Maiúsculas"
|
||||
@@ -1004,7 +990,6 @@ msgstr "Mostrar a conta de artigos sen ler na icona da pestana"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Mostar a conta de artigos sen ler no título da pestana"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1014,7 +999,6 @@ msgstr "Crear conta"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Algo malo pasou..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Espazo"
|
||||
@@ -1031,7 +1015,6 @@ msgstr "Pór estrela"
|
||||
msgid "Starred"
|
||||
msgstr "Con estrela"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1065,7 +1048,6 @@ msgstr "Cambiar ao decorado claro"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetas"
|
||||
@@ -1096,8 +1078,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Esta canle ten un filtro antigo que non se pode editar e que non se aplica. Volve crear o filtro usando o novo editor de expresións. A expresión do filtro antigo era: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Esta é a túa clave da API. Pode usarse para algunhas operacións da API de só-lectura e concede acceso á API Fever. Usa o formulario da parte inferior da páxina para crear unha nova clave da API"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Esta é a túa clave da API. Pode usarse para algunhas operacións da API de só-lectura e concede acceso á API Fever e á API de Google Reader. Usa o formulario da parte inferior da páxina para crear unha nova clave da API"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1137,7 +1119,6 @@ msgstr "Sen ler"
|
||||
msgid "Unstar"
|
||||
msgstr "Retirar estrela"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Cancelar a subscrición"
|
||||
@@ -1150,7 +1131,6 @@ msgstr "Clave de usuario"
|
||||
msgid "User name"
|
||||
msgstr "Identificador"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Identificador ou correo electrónico"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Felhasználó hozzáadása"
|
||||
msgid "Admin"
|
||||
msgstr "Adminisztráció"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Adminisztrátor felhasználóneve"
|
||||
@@ -73,7 +72,6 @@ msgstr "Adminisztrátor felhasználóneve"
|
||||
msgid "All"
|
||||
msgstr "Mind"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Mindig"
|
||||
@@ -173,18 +171,14 @@ msgstr "Hozzon létre egy szűrőkifejezést annak megjelölésére, hogy mit sz
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Mégse"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed böngészőbővítmény {browserExtensionVersion} verzió."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "A CommaFeed kompatibilis a Fever API-val. Használja a következő URL-t Fever-kompatibilis mobilkliensében. Jelentkezzen be felhasználónevével és <0>API-kulcsával</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "A CommaFeed kompatibilis a Google Reader API-val. Használja a következő URL-t Google Reader-kompatibilis mobilkliensében. Jelentkezzen be felhasználónevével és <0>API-kulcsával</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed következő olvasatlan elem"
|
||||
@@ -240,7 +238,6 @@ msgstr "Erősítse meg"
|
||||
msgid "Confirm password"
|
||||
msgstr "Erősítse meg a jelszót"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Jelszó megerősítése"
|
||||
@@ -294,7 +291,6 @@ msgstr "nap"
|
||||
msgid "Delete"
|
||||
msgstr "Törlés"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Fiók törlése"
|
||||
@@ -342,13 +338,10 @@ msgstr "Húzza a hivatkozást a könyvjelzősávra"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mail cím"
|
||||
@@ -399,7 +392,6 @@ msgstr "Bővítmény beállításai"
|
||||
msgid "Feed name"
|
||||
msgstr "Hírcsatorna neve"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Nyissa meg az API dokumentációját."
|
||||
msgid "Goodies"
|
||||
msgstr "Jók"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Szőlő"
|
||||
@@ -570,7 +570,6 @@ msgstr "Előfizetések betöltése..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Címkék betöltése..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Navigáljon egy előfizetéshez a nevének megadásával"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigálás a következő olvasatlan bejegyzéseket tartalmazó kategóriára/hírcsatornára, ha minden bejegyzést olvasottnak jelöl"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Soha"
|
||||
@@ -657,7 +655,6 @@ msgstr "Soha"
|
||||
msgid "New password"
|
||||
msgstr "Új jelszó"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Új jelszó"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML exportálás"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML fájl"
|
||||
@@ -791,10 +787,7 @@ msgstr "Szülő kategória"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Jelszó"
|
||||
@@ -874,7 +867,6 @@ msgstr "Frissítés"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "A regisztrációk le vannak zárva ezen a CommaFeed példányon"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Jelszó visszaállítása"
|
||||
@@ -883,7 +875,6 @@ msgstr "Jelszó visszaállítása"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Jobb klikk"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Görgetés"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Keresés"
|
||||
@@ -924,7 +913,6 @@ msgstr "Következő olvasatlan feed/kategória kiválasztása"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Előző olvasatlan feed/kategória kiválasztása"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Szerver URL"
|
||||
@@ -953,8 +941,6 @@ msgstr "Oszd meg"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Webhelyek megosztása"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Olvasatlan darabszám megjelenítése a tab faviconján"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Olvasatlan darabszám megjelenítése a tab címében"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Regisztráció"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Valami rossz történt..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Szóköz"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Csillag"
|
||||
msgid "Starred"
|
||||
msgstr "Csillaggal megjelölve"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Váltás világos témára"
|
||||
msgid "System"
|
||||
msgstr "Rendszer"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Címkék"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Ez a feed egy régi szűrővel rendelkezik, amely nem szerkeszthető és nem kerül alkalmazásra. Kérjük, hozza létre újra a szűrőt az új kifejezésszerkesztővel. A régi szűrőkifejezés a következő volt: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Ez az Ön API-kulcsa. Felhasználható néhány írásvédett API-művelethez, és hozzáférést biztosít a Fever API-hoz. Használja az oldal alján található űrlapot egy új API-kulcs létrehozásához"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Ez az Ön API-kulcsa. Néhány csak olvasható API-művelethez használható, és hozzáférést biztosít a Fever API-hoz és a Google Reader API-hoz. Használja az oldal alján található űrlapot új API-kulcs létrehozásához"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Olvasatlan"
|
||||
msgid "Unstar"
|
||||
msgstr "Csillag eltávolítása"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Leiratkozás"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Felhasználói kulcs"
|
||||
msgid "User name"
|
||||
msgstr "Felhasználónév"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Felhasználói név vagy e-mail cím"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Tambahkan pengguna"
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nama pengguna admin"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nama pengguna admin"
|
||||
msgid "All"
|
||||
msgstr "Semua"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Selalu"
|
||||
@@ -173,18 +171,14 @@ msgstr "Bangun ekspresi filter untuk menunjukkan apa yang ingin Anda baca. Entri
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Batal"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Ekstensi browser CommaFeed versi {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed kompatibel dengan Fever API. Gunakan URL berikut di klien seluler Anda yang kompatibel dengan Fever. Login dengan nama pengguna dan <0>kunci API</0> Anda."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed kompatibel dengan Google Reader API. Gunakan URL berikut di klien seluler Anda yang kompatibel dengan Google Reader. Login dengan nama pengguna dan <0>kunci API</0> Anda."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed item yang belum dibaca berikutnya"
|
||||
@@ -240,7 +238,6 @@ msgstr "Konfirmasi"
|
||||
msgid "Confirm password"
|
||||
msgstr "Konfirmasi kata sandi"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Konfirmasi Kata Sandi"
|
||||
@@ -294,7 +291,6 @@ msgstr "hari"
|
||||
msgid "Delete"
|
||||
msgstr "Hapus"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Hapus akun"
|
||||
@@ -342,13 +338,10 @@ msgstr "Seret tautan ke bilah bookmark"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Email"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Alamat email"
|
||||
@@ -399,7 +392,6 @@ msgstr "Opsi ekstensi"
|
||||
msgid "Feed name"
|
||||
msgstr "Nama umpan"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Buka dokumentasi API."
|
||||
msgid "Goodies"
|
||||
msgstr "Pernak-pernik"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Anggur"
|
||||
@@ -570,7 +570,6 @@ msgstr "Memuat langganan..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Memuat tag..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Navigasikan ke langganan dengan memasukkan namanya"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigasikan ke kategori/umpan berikutnya dengan entri yang belum dibaca saat menandai semua entri sebagai telah dibaca"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Tidak pernah"
|
||||
@@ -657,7 +655,6 @@ msgstr "Tidak pernah"
|
||||
msgid "New password"
|
||||
msgstr "Kata sandi baru"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Kata Sandi Baru"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "ekspor OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "file OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "Kategori Induk"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Kata Sandi"
|
||||
@@ -874,7 +867,6 @@ msgstr "Segarkan"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Pendaftaran ditutup pada instans CommaFeed ini"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Atur Ulang Kata Sandi"
|
||||
@@ -883,7 +875,6 @@ msgstr "Atur Ulang Kata Sandi"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Klik kanan"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Menggulir"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Pencarian"
|
||||
@@ -924,7 +913,6 @@ msgstr "Pilih umpan/kategori belum dibaca berikutnya"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Pilih umpan/kategori belum dibaca sebelumnya"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL Server"
|
||||
@@ -953,8 +941,6 @@ msgstr "Bagikan"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Berbagi situs"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Pergeseran"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Tampilkan jumlah yang belum dibaca di favicon tab"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Tampilkan jumlah yang belum dibaca di judul tab"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Daftar"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Sesuatu yang buruk baru saja terjadi..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Spasi"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Bintang"
|
||||
msgid "Starred"
|
||||
msgstr "Berbintang"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Beralih ke tema terang"
|
||||
msgid "System"
|
||||
msgstr "Sistem"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tag"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Umpan ini memiliki filter lama yang tidak dapat diedit dan tidak diterapkan. Silakan buat ulang filter menggunakan editor ekspresi baru. Ekspresi filter lama adalah: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Ini adalah kunci API Anda. Ini dapat digunakan untuk beberapa operasi API baca-saja dan memberikan akses ke Fever API. Gunakan formulir di bagian bawah halaman untuk membuat kunci API baru"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Ini adalah kunci API Anda. Ini dapat digunakan untuk beberapa operasi API yang hanya-baca dan memberikan akses ke Fever API dan Google Reader API. Gunakan formulir di bagian bawah halaman untuk membuat kunci API baru"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Belum Dibaca"
|
||||
msgid "Unstar"
|
||||
msgstr "Hapus bintang"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Berhenti berlangganan"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Kunci pengguna"
|
||||
msgid "User name"
|
||||
msgstr "Nama pengguna"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nama Pengguna atau Email"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Aggiungi utente"
|
||||
msgid "Admin"
|
||||
msgstr "Ammin"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nome utente amministratore"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nome utente amministratore"
|
||||
msgid "All"
|
||||
msgstr "Tutto"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Sempre"
|
||||
@@ -173,18 +171,14 @@ msgstr "Crea un'espressione di filtro per indicare cosa vuoi leggere. Le voci ch
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Annulla"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Versione dell'estensione del browser CommaFeed {browserExtensionVersion}
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed è compatibile con l'API Fever. Utilizza il seguente URL nel tuo client mobile compatibile con Fever. Accedi con il tuo nome utente e la tua <0>chiave API</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed è compatibile con l'API Google Reader. Utilizza il seguente URL nel tuo client mobile compatibile con Google Reader. Accedi con il tuo nome utente e la tua <0>chiave API</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed successivo elemento non letto"
|
||||
@@ -240,7 +238,6 @@ msgstr "Conferma"
|
||||
msgid "Confirm password"
|
||||
msgstr "Conferma password"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Conferma password"
|
||||
@@ -294,7 +291,6 @@ msgstr "giorni"
|
||||
msgid "Delete"
|
||||
msgstr "Elimina"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Elimina account"
|
||||
@@ -342,13 +338,10 @@ msgstr "Trascina il collegamento sulla barra dei preferiti"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Indirizzo e-mail"
|
||||
@@ -399,7 +392,6 @@ msgstr "Opzioni estensione"
|
||||
msgid "Feed name"
|
||||
msgstr "Nome del feed"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Vai alla documentazione dell'API."
|
||||
msgid "Goodies"
|
||||
msgstr "Chicche"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Uva"
|
||||
@@ -570,7 +570,6 @@ msgstr "Caricamento abbonamenti..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Caricamento tag..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Navigare verso un abbonamento inserendo il suo nome"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Passa alla categoria/feed successivo con voci non lette quando contrassegni tutte le voci come lette"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Mai"
|
||||
@@ -657,7 +655,6 @@ msgstr "Mai"
|
||||
msgid "New password"
|
||||
msgstr "Nuova password"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nuova password"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Esportazione OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "File OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "Categoria padre"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
@@ -874,7 +867,6 @@ msgstr "Aggiorna"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Le registrazioni sono chiuse su questa istanza CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Reimposta password"
|
||||
@@ -883,7 +875,6 @@ msgstr "Reimposta password"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clic destro"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Scorrimento"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Cerca"
|
||||
@@ -924,7 +913,6 @@ msgstr "Seleziona il feed/categoria non letto successivo"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Seleziona il feed/categoria non letto precedente"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL del server"
|
||||
@@ -953,8 +941,6 @@ msgstr "Condividi"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Condivisione di siti"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Cambio"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Mostra il numero di non letti nella favicon della scheda"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Mostra il numero di non letti nel titolo della scheda"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Iscriviti"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "È appena successo qualcosa di brutto..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Spazio"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Stella"
|
||||
msgid "Starred"
|
||||
msgstr "Speciali"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Passa al tema chiaro"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tag"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Questo feed ha un filtro legacy che non può essere modificato e non viene applicato. Ricrea il filtro utilizzando il nuovo editor di espressioni. L'espressione del filtro legacy era: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Questa è la tua chiave API. Può essere utilizzata per alcune operazioni API di sola lettura e garantisce l'accesso all'API Fever. Utilizza il modulo in fondo alla pagina per generare una nuova chiave API"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Questa è la tua chiave API. Può essere utilizzata per alcune operazioni API di sola lettura e concede l'accesso all'API Fever e all'API Google Reader. Usa il modulo in fondo alla pagina per generare una nuova chiave API"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Non letto"
|
||||
msgid "Unstar"
|
||||
msgstr "Elimina le stelle"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Annulla iscrizione"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Chiave utente"
|
||||
msgid "User name"
|
||||
msgstr "Nome utente"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nome utente o e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "ユーザー追加"
|
||||
msgid "Admin"
|
||||
msgstr "管理者"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "管理者ユーザー名"
|
||||
@@ -73,7 +72,6 @@ msgstr "管理者ユーザー名"
|
||||
msgid "All"
|
||||
msgstr "すべて"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "常に"
|
||||
@@ -173,18 +171,14 @@ msgstr "読みたい内容を指定するためのフィルタ式を作成しま
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "キャンセル"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed ブラウザー拡張機能のバージョンは {browserExten
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeedはFever APIと互換性があります。Fever互換のモバイルクライアントで次のURLを使用してください。ユーザー名と <0>API キー</0> でログインしてください。"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeedはGoogle Reader APIと互換性があります。Google Reader互換のモバイルクライアントで次のURLを使用してください。ユーザー名と <0>API キー</0> でログインしてください。"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed 次の未読アイテム"
|
||||
@@ -240,7 +238,6 @@ msgstr "確認"
|
||||
msgid "Confirm password"
|
||||
msgstr "パスワード確認"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "パスワードの確認"
|
||||
@@ -294,7 +291,6 @@ msgstr "日"
|
||||
msgid "Delete"
|
||||
msgstr "削除"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "アカウント削除"
|
||||
@@ -342,13 +338,10 @@ msgstr "リンクをブックマークバーにドラッグ"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "メール"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "メールアドレス"
|
||||
@@ -399,7 +392,6 @@ msgstr "拡張機能オプション"
|
||||
msgid "Feed name"
|
||||
msgstr "フィード名"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "APIドキュメントに移動します。"
|
||||
msgid "Goodies"
|
||||
msgstr "グッズ"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "グレープ"
|
||||
@@ -570,7 +570,6 @@ msgstr "サブスクリプションを読み込んでいます..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "タグを読み込んでいます..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "名前を入力してサブスクリプションに移動します"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "すべてのエントリーを既読にするとき、未読エントリーのある次のカテゴリ/フィードに移動する"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "しない"
|
||||
@@ -657,7 +655,6 @@ msgstr "しない"
|
||||
msgid "New password"
|
||||
msgstr "新しいパスワード"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "新しいパスワード"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML エクスポート"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPMLファイル"
|
||||
@@ -791,10 +787,7 @@ msgstr "親カテゴリ"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "パスワード"
|
||||
@@ -874,7 +867,6 @@ msgstr "リフレッシュ"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "このCommaFeedインスタンスの登録は終了しています"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "パスワードをリセット"
|
||||
@@ -883,7 +875,6 @@ msgstr "パスワードをリセット"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "右クリック"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "スクロール"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "検索"
|
||||
@@ -924,7 +913,6 @@ msgstr "次の未読フィード/カテゴリを選択"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "前の未読フィード/カテゴリを選択"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "サーバーURL"
|
||||
@@ -953,8 +941,6 @@ msgstr "シェア"
|
||||
msgid "Sharing sites"
|
||||
msgstr "共有サイト"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "未読数をタブのアイコンに表示する"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "未読数をタブのタイトルに表示する"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "サインアップ"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "何か悪いことが起きました..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Space"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "スター"
|
||||
msgid "Starred"
|
||||
msgstr "スター付き"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "ライトテーマに切り替え"
|
||||
msgid "System"
|
||||
msgstr "システム"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "タグ"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "このフィードには、編集できず適用もされない古い形式のフィルタがあります。新しい式エディタを使用してフィルタを再作成してください。古いフィルタ式は次のとおりでした: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "これはあなたのAPIキーです。いくつかの読み取り専用API操作に使用できます。これにより、Fever APIへのアクセスが可能になります。ページの下部のフォームを使用して新しいAPIキーを生成します。"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "これはあなたのAPIキーです。一部の読み取り専用API操作に使用でき、Fever APIおよびGoogle Reader APIへのアクセスを許可します。新しいAPIキーを生成するには、ページ下部のフォームを使用してください"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "未読"
|
||||
msgid "Unstar"
|
||||
msgstr "スターを外す"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "退会"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "ユーザーキー"
|
||||
msgid "User name"
|
||||
msgstr "ユーザー名"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "ユーザー名またはメールアドレス"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "사용자 추가"
|
||||
msgid "Admin"
|
||||
msgstr "관리자"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "관리자 사용자 이름"
|
||||
@@ -73,7 +72,6 @@ msgstr "관리자 사용자 이름"
|
||||
msgid "All"
|
||||
msgstr "전체"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "항상"
|
||||
@@ -173,18 +171,14 @@ msgstr "읽고 싶은 항목을 지정하기 위한 필터 표현식을 작성
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "취소"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed 브라우저 확장 프로그램 버전 {browserExtensionVersi
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed는 Fever API와 호환됩니다. Fever 호환 모바일 클라이언트에서 다음 URL을 사용하십시오. 사용자 이름과 <0>API 키</0>로 로그인하십시오."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed는 Google Reader API와 호환됩니다. Google Reader 호환 모바일 클라이언트에서 다음 URL을 사용하십시오. 사용자 이름과 <0>API 키</0>로 로그인하십시오."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "다음 읽지 않은 항목을 쉼표로 피드"
|
||||
@@ -240,7 +238,6 @@ msgstr "확인"
|
||||
msgid "Confirm password"
|
||||
msgstr "비밀번호 확인"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "비밀번호 확인"
|
||||
@@ -294,7 +291,6 @@ msgstr "일"
|
||||
msgid "Delete"
|
||||
msgstr "삭제"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "계정 삭제"
|
||||
@@ -342,13 +338,10 @@ msgstr "링크를 북마크바로 드래그"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "이메일"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "이메일 주소"
|
||||
@@ -399,7 +392,6 @@ msgstr "확장 프로그램 옵션"
|
||||
msgid "Feed name"
|
||||
msgstr "피드 이름"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "API 문서로 이동합니다."
|
||||
msgid "Goodies"
|
||||
msgstr "굿즈"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "그레이프"
|
||||
@@ -570,7 +570,6 @@ msgstr "구독 로드 중..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "태그 로드 중..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "이름을 입력하여 구독으로 이동"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "모든 항목을 읽음으로 표시할 때 읽지 않은 항목이 있는 다음 카테고리/피드로 이동"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "안 함"
|
||||
@@ -657,7 +655,6 @@ msgstr "안 함"
|
||||
msgid "New password"
|
||||
msgstr "새 비밀번호"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "새 비밀번호"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML 내보내기"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML 파일"
|
||||
@@ -791,10 +787,7 @@ msgstr "부모 카테고리"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "비밀번호"
|
||||
@@ -874,7 +867,6 @@ msgstr "새로 고침"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "이 CommaFeed 인스턴스에 대한 등록이 마감되었습니다."
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "비밀번호 재설정"
|
||||
@@ -883,7 +875,6 @@ msgstr "비밀번호 재설정"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "오른쪽 클릭"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "스크롤"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "검색"
|
||||
@@ -924,7 +913,6 @@ msgstr "다음 읽지 않은 피드/카테고리 선택"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "이전 읽지 않은 피드/카테고리 선택"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "서버 URL"
|
||||
@@ -953,8 +941,6 @@ msgstr "공유"
|
||||
msgid "Sharing sites"
|
||||
msgstr "사이트 공유"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "시프트"
|
||||
@@ -1003,7 +989,6 @@ msgstr "탭 파비콘에 읽지 않은 개수 표시"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "탭 제목에 읽지 않은 개수 표시"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "가입"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "뭔가 안 좋은 일이 일어났어..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "우주"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "스타"
|
||||
msgid "Starred"
|
||||
msgstr "별표"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "밝은 테마로 전환"
|
||||
msgid "System"
|
||||
msgstr "시스템"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "태그"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "이 피드에는 편집할 수 없고 적용되지 않는 기존 필터가 있습니다. 새 표현식 편집기를 사용하여 필터를 다시 만드십시오. 기존 필터 표현식은 <0>{0}</0>이었습니다."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "이것은 귀하의 API 키입니다. 일부 읽기 전용 API 작업에 사용할 수 있으며 Fever API에 대한 액세스 권한을 부여합니다. 새 API 키를 생성하려면 페이지 하단의 양식을 사용하십시오."
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "이것은 회원님의 API 키입니다. 일부 읽기 전용 API 작업에 사용할 수 있으며 Fever API 및 Google Reader API에 대한 액세스 권한을 부여합니다. 새 API 키를 생성하려면 페이지 하단의 양식을 사용하십시오"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "읽지 않음"
|
||||
msgid "Unstar"
|
||||
msgstr "별표 제거"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "구독 취소"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "사용자 키"
|
||||
msgid "User name"
|
||||
msgstr "사용자 이름"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "사용자 이름 또는 이메일"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Tambah pengguna"
|
||||
msgid "Admin"
|
||||
msgstr "Pentadbir"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nama pengguna pentadbir"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nama pengguna pentadbir"
|
||||
msgid "All"
|
||||
msgstr "Semua"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Sentiasa"
|
||||
@@ -173,18 +171,14 @@ msgstr "Bina ungkapan penapis untuk menunjukkan perkara yang anda mahu baca. Ent
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Batal"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Ekstensi penyemak imbas CommaFeed versi {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed serasi dengan Fever API. Gunakan URL berikut dalam klien mudah alih serasi Fever anda. Log masuk dengan nama pengguna anda dan <0>kunci API</0> anda."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed serasi dengan Google Reader API. Gunakan URL berikut dalam klien mudah alih serasi Google Reader anda. Log masuk dengan nama pengguna anda dan <0>kunci API</0> anda."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed item belum dibaca seterusnya"
|
||||
@@ -240,7 +238,6 @@ msgstr "Sahkan"
|
||||
msgid "Confirm password"
|
||||
msgstr "Sahkan kata laluan"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Sahkan Kata Laluan"
|
||||
@@ -294,7 +291,6 @@ msgstr "hari"
|
||||
msgid "Delete"
|
||||
msgstr "Padam"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Padam akaun"
|
||||
@@ -342,13 +338,10 @@ msgstr "Seret pautan ke bar penanda halaman"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mel"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Alamat e-mel"
|
||||
@@ -399,7 +392,6 @@ msgstr "Pilihan ekstensi"
|
||||
msgid "Feed name"
|
||||
msgstr "Nama suapan"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Pergi ke dokumentasi API."
|
||||
msgid "Goodies"
|
||||
msgstr "Barangan"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Anggur"
|
||||
@@ -570,7 +570,6 @@ msgstr "Memuatkan langganan..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Memuatkan tag..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Navigasi ke langganan dengan memasukkan namanya"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigasi ke kategori/suapan seterusnya dengan entri yang belum dibaca apabila menandakan semua entri sebagai dibaca"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Tidak pernah"
|
||||
@@ -657,7 +655,6 @@ msgstr "Tidak pernah"
|
||||
msgid "New password"
|
||||
msgstr "Kata laluan baharu"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Kata Laluan Baharu"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML eksport"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Fail OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "Kategori Induk"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Kata Laluan"
|
||||
@@ -874,7 +867,6 @@ msgstr "Muat semula"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Pendaftaran ditutup pada contoh CommaFeed ini"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Tetap Semula Kata Laluan"
|
||||
@@ -883,7 +875,6 @@ msgstr "Tetap Semula Kata Laluan"
|
||||
msgid "REST API"
|
||||
msgstr "REHAT API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Klik kanan"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Menatal"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Cari"
|
||||
@@ -924,7 +913,6 @@ msgstr "Pilih suapan/kategori belum dibaca seterusnya"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Pilih suapan/kategori belum dibaca sebelumnya"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL Pelayan"
|
||||
@@ -953,8 +941,6 @@ msgstr "Kongsi"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Berkongsi tapak"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Anjakan"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Tunjukkan kiraan belum dibaca dalam favicon tab"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Tunjukkan kiraan belum dibaca dalam tajuk tab"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Daftar"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Sesuatu yang buruk baru saja berlaku..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Angkasa"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Bintang"
|
||||
msgid "Starred"
|
||||
msgstr "Dibintangi"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Tukar kepada tema cahaya"
|
||||
msgid "System"
|
||||
msgstr "Sistem"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tag"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Suapan ini mempunyai penapis warisan yang tidak boleh diedit dan tidak digunakan. Sila buat semula penapis menggunakan editor ungkapan baharu. Ungkapan penapis warisan ialah: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Ini ialah kunci API anda. Ia boleh digunakan untuk beberapa operasi API baca sahaja dan memberikan akses kepada Fever API. Gunakan borang di bahagian bawah halaman untuk menjana kunci API baharu"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Ini ialah kunci API anda. Ia boleh digunakan untuk sesetengah operasi API baca sahaja dan memberikan akses kepada Fever API dan Google Reader API. Gunakan borang di bahagian bawah halaman untuk menjana kunci API baharu"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Belum dibaca"
|
||||
msgid "Unstar"
|
||||
msgstr "Nyahbintang"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Nyahlanggan"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Kunci pengguna"
|
||||
msgid "User name"
|
||||
msgstr "Nama pengguna"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nama Pengguna atau E-mel"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Legg til bruker"
|
||||
msgid "Admin"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Administrator brukernavn"
|
||||
@@ -73,7 +72,6 @@ msgstr "Administrator brukernavn"
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Alltid"
|
||||
@@ -173,18 +171,14 @@ msgstr "Bygg et filteruttrykk for å angi hva du vil lese. Oppføringer som ikke
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Avbryt"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed nettleserutvidelse versjon {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed er kompatibel med Fever API. Bruk følgende URL i din Fever-kompatible mobilklient. Logg inn med brukernavn og din <0>API-nøkkel</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed er kompatibel med Google Reader API. Bruk følgende URL i din Google Reader-kompatible mobilklient. Logg inn med brukernavn og din <0>API-nøkkel</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed neste uleste element"
|
||||
@@ -240,7 +238,6 @@ msgstr "Bekreft"
|
||||
msgid "Confirm password"
|
||||
msgstr "Bekreft passord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Bekreft passord"
|
||||
@@ -294,7 +291,6 @@ msgstr "dager"
|
||||
msgid "Delete"
|
||||
msgstr "Slett"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Slett konto"
|
||||
@@ -342,13 +338,10 @@ msgstr "Dra lenken til bokmerkelinjen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-post"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-postadresse"
|
||||
@@ -399,7 +392,6 @@ msgstr "Utvidelsesvalg"
|
||||
msgid "Feed name"
|
||||
msgstr "Feednavn"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Gå til API-dokumentasjonen."
|
||||
msgid "Goodies"
|
||||
msgstr "Godbiter"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Drue"
|
||||
@@ -570,7 +570,6 @@ msgstr "Laster abonnementer..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Laster tagger..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Naviger til et abonnement ved å skrive inn navnet"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Naviger til neste kategori/feed med uleste oppføringer når du markerer alle oppføringer som lest"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Aldri"
|
||||
@@ -657,7 +655,6 @@ msgstr "Aldri"
|
||||
msgid "New password"
|
||||
msgstr "Nytt passord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nytt passord"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-eksport"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-fil"
|
||||
@@ -791,10 +787,7 @@ msgstr "Overordnet kategori"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Passord"
|
||||
@@ -874,7 +867,6 @@ msgstr "Oppdater"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registreringer er stengt på denne CommaFeed-forekomsten"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Tilbakestill passord"
|
||||
@@ -883,7 +875,6 @@ msgstr "Tilbakestill passord"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Høyreklikk"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Rulling"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Søk"
|
||||
@@ -924,7 +913,6 @@ msgstr "Velg neste uleste feed/kategori"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Velg forrige uleste feed/kategori"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +941,6 @@ msgstr "Del"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Delingssider"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Skift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Vis antall uleste i fanens favicon"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Vis antall uleste i fanetittelen"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Meld deg på"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Noe ille skjedde akkurat..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Mellomrom"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Stjerne"
|
||||
msgid "Starred"
|
||||
msgstr "Stjerne"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Bytt til lystema"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tagger"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Denne feeden har et utdatert filter som ikke kan redigeres og ikke blir brukt. Vennligst opprett filteret på nytt med den nye uttrykkseditoren. Det utdaterte filteruttrykket var: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dette er API-nøkkelen din. Den kan brukes til enkelte skrivebeskyttede API-operasjoner og gir tilgang til Fever API. Bruk skjemaet nederst på siden for å generere en ny API-nøkkel"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dette er din API-nøkkel. Den kan brukes til noen skrivebeskyttede API-operasjoner og gir tilgang til Fever API og Google Reader API. Bruk skjemaet nederst på siden for å generere en ny API-nøkkel"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Ulest"
|
||||
msgid "Unstar"
|
||||
msgstr "Fjern stjerne"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Avslutt abonnement"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Brukernøkkel"
|
||||
msgid "User name"
|
||||
msgstr "Brukernavn"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Brukernavn eller e-post"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Gebruiker toevoegen"
|
||||
msgid "Admin"
|
||||
msgstr "Beheerder"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Gebruikersnaam beheerder"
|
||||
@@ -73,7 +72,6 @@ msgstr "Gebruikersnaam beheerder"
|
||||
msgid "All"
|
||||
msgstr "Alles"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Altijd"
|
||||
@@ -173,18 +171,14 @@ msgstr "Maak een filterexpressie om aan te geven wat u wilt lezen. Items die nie
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Annuleren"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed browser extensie versie {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed is compatibel met de Fever API. Gebruik de volgende URL in uw Fever-compatibele mobiele client. Log in met uw gebruikersnaam en uw <0>API-sleutel</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed is compatibel met de Google Reader API. Gebruik de volgende URL in uw Google Reader-compatibele mobiele client. Log in met uw gebruikersnaam en uw <0>API-sleutel</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed volgende ongelezen item"
|
||||
@@ -240,7 +238,6 @@ msgstr "Bevestigen"
|
||||
msgid "Confirm password"
|
||||
msgstr "Bevestig wachtwoord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Bevestig wachtwoord"
|
||||
@@ -294,7 +291,6 @@ msgstr "dagen"
|
||||
msgid "Delete"
|
||||
msgstr "Verwijderen"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Account verwijderen"
|
||||
@@ -342,13 +338,10 @@ msgstr "Link naar bladwijzerbalk slepen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mailadres"
|
||||
@@ -399,7 +392,6 @@ msgstr "Extensie opties"
|
||||
msgid "Feed name"
|
||||
msgstr "Feednaam"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Ga naar de API-documentatie."
|
||||
msgid "Goodies"
|
||||
msgstr "Goederen"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Druif"
|
||||
@@ -570,7 +570,6 @@ msgstr "Abonnementen laden..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Tags laden..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Navigeer naar een abonnement door de naam in te voeren"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigeer naar de volgende categorie/feed met ongelezen items wanneer alle items als gelezen worden gemarkeerd"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nooit"
|
||||
@@ -657,7 +655,6 @@ msgstr "Nooit"
|
||||
msgid "New password"
|
||||
msgstr "Nieuw wachtwoord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nieuw wachtwoord"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-export"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-bestand"
|
||||
@@ -791,10 +787,7 @@ msgstr "Oudercategorie"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Wachtwoord"
|
||||
@@ -874,7 +867,6 @@ msgstr "Vernieuwen"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registraties zijn gesloten op deze CommaFeed-instantie"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Wachtwoord herstellen"
|
||||
@@ -883,7 +875,6 @@ msgstr "Wachtwoord herstellen"
|
||||
msgid "REST API"
|
||||
msgstr "REST-API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Rechtermuisklik"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Scrollen"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Zoeken"
|
||||
@@ -924,7 +913,6 @@ msgstr "Selecteer volgende ongelezen feed/categorie"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Selecteer vorige ongelezen feed/categorie"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +941,6 @@ msgstr "Delen"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Sites delen"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Toon aantal ongelezen in tabblad-favicon"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Toon aantal ongelezen in tabbladtitel"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Aanmelden"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Er is net iets ergs gebeurd..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Ruimte"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Ster"
|
||||
msgid "Starred"
|
||||
msgstr "Met ster"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Schakel over naar licht thema"
|
||||
msgid "System"
|
||||
msgstr "Systeem"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Deze feed heeft een legacy-filter dat niet kan worden bewerkt en niet wordt toegepast. Maak het filter opnieuw met de nieuwe expressie-editor. De legacy-filterexpressie was: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dit is je API-sleutel. Het kan worden gebruikt voor sommige alleen-lezen API-bewerkingen en geeft toegang tot de Fever API. Gebruik het formulier onderaan de pagina om een nieuwe API-sleutel te genereren"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dit is uw API-sleutel. Deze kan worden gebruikt voor sommige alleen-lezen API-bewerkingen en geeft toegang tot de Fever API en de Google Reader API. Gebruik het formulier onderaan de pagina om een nieuwe API-sleutel te genereren"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Ongelezen"
|
||||
msgid "Unstar"
|
||||
msgstr "Sterren uit"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Afmelden"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Gebruikerssleutel"
|
||||
msgid "User name"
|
||||
msgstr "Gebruikersnaam"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Gebruikersnaam of e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Legg til brukar"
|
||||
msgid "Admin"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Brukarnamn for administrator"
|
||||
@@ -73,7 +72,6 @@ msgstr "Brukarnamn for administrator"
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Alltid"
|
||||
@@ -173,18 +171,14 @@ msgstr "Bygg eit filteruttrykk for å angis kva du vil lesa. Innlegg som ikkje s
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Avbryt"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed nettlesarutviding versjon {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed er kompatibel med Fever API. Bruk følgjande URL i din Fever-kompatible mobilklient. Logg inn med brukarnamn og din <0>API-nøkkel</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed er kompatibel med Google Reader API. Bruk følgjande URL i din Google Reader-kompatible mobilklient. Logg inn med brukarnamn og din <0>API-nøkkel</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed neste ulesne element"
|
||||
@@ -240,7 +238,6 @@ msgstr "Bekreft"
|
||||
msgid "Confirm password"
|
||||
msgstr "Bekreft passord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Stadfest passord"
|
||||
@@ -294,7 +291,6 @@ msgstr "dagar"
|
||||
msgid "Delete"
|
||||
msgstr "Slett"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Slett konto"
|
||||
@@ -342,13 +338,10 @@ msgstr "Dra lenken til bokmerkelinjen"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-post"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-postadresse"
|
||||
@@ -399,7 +392,6 @@ msgstr "Val for utviding"
|
||||
msgid "Feed name"
|
||||
msgstr "Feednavn"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Gå til API-dokumentasjonen."
|
||||
msgid "Goodies"
|
||||
msgstr "Godbitar"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Drue"
|
||||
@@ -570,7 +570,6 @@ msgstr "Laster abonnementer..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Laster tagger..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Naviger til et abonnement ved å skrive inn navnet"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Naviger til neste kategori/suap med ulesne innlegg når du markerer alle innlegg som lesne"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Aldri"
|
||||
@@ -657,7 +655,6 @@ msgstr "Aldri"
|
||||
msgid "New password"
|
||||
msgstr "Nytt passord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nytt passord"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-eksport"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-fil"
|
||||
@@ -791,10 +787,7 @@ msgstr "Overordnet kategori"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Passord"
|
||||
@@ -874,7 +867,6 @@ msgstr "Oppdater"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registreringer er stengt på denne CommaFeed-forekomsten"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Tilbakestill passord"
|
||||
@@ -883,7 +875,6 @@ msgstr "Tilbakestill passord"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Høgreklikk"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Rulling"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Søk"
|
||||
@@ -924,7 +913,6 @@ msgstr "Vel neste ulesne suap/kategori"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Vel førre ulesne suap/kategori"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +941,6 @@ msgstr "Del"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Delingssider"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Skift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Vis talet på ulesne i favicon for fana"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Vis talet på ulesne i tittelen på fana"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Meld deg på"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Noe ille skjedde akkurat..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Mellomrom"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Stjerne"
|
||||
msgid "Starred"
|
||||
msgstr "Stjernemerka"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Bytt til lyst tema"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tagger"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Denne suapen har eit gamalt filter som ikkje kan redigerast og som ikkje vert brukt. Ver venleg og lag filteret på nytt med den nye uttrykkseditoren. Det gamle filteruttrykket var: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dette er API-nøkkelen din. Han kan brukast til enkelte skrivebeskytta API-handlingar og gjev tilgang til Fever API. Bruk skjemaet nedst på sida for å laga ein ny API-nøkkel"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Dette er din API-nøkkel. Ho kan brukast til nokre skrivebeskytta API-operasjonar og gir tilgang til Fever API og Google Reader API. Bruk skjemaet nedst på sida for å generere ein ny API-nøkkel"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Ulesen"
|
||||
msgid "Unstar"
|
||||
msgstr "Fjern stjerne"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Avslutt abonnement"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Brukarnøkkel"
|
||||
msgid "User name"
|
||||
msgstr "Brukarnamn"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Brukarnamn eller e-post"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Dodaj użytkownika"
|
||||
msgid "Admin"
|
||||
msgstr "Administracja"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nazwa użytkownika administratora"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nazwa użytkownika administratora"
|
||||
msgid "All"
|
||||
msgstr "Wszystkie"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Zawsze"
|
||||
@@ -173,18 +171,14 @@ msgstr "Zbuduj wyrażenie filtrujące, aby wskazać, co chcesz czytać. Wpisy, k
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Anuluj"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Wersja rozszerzenia przeglądarki CommaFeed {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed jest kompatybilny z API Fever. Użyj następującego adresu URL w swoim kliencie mobilnym zgodnym z Fever. Zaloguj się przy użyciu swojej nazwy użytkownika i <0>klucza API</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed jest kompatybilny z API Google Reader. Użyj następującego adresu URL w swoim kliencie mobilnym zgodnym z Google Reader. Zaloguj się przy użyciu swojej nazwy użytkownika i <0>klucza API</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "Przecinek następny nieprzeczytany element"
|
||||
@@ -240,7 +238,6 @@ msgstr "Potwierdź"
|
||||
msgid "Confirm password"
|
||||
msgstr "Potwierdź hasło"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Potwierdź hasło"
|
||||
@@ -294,7 +291,6 @@ msgstr "dni"
|
||||
msgid "Delete"
|
||||
msgstr "Usuń"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Usuń konto"
|
||||
@@ -342,13 +338,10 @@ msgstr "Przeciągnij link do paska zakładek"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Adres e-mail"
|
||||
@@ -399,7 +392,6 @@ msgstr "Opcje rozszerzenia"
|
||||
msgid "Feed name"
|
||||
msgstr "nazwa kanału"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Przejdź do dokumentacji API."
|
||||
msgid "Goodies"
|
||||
msgstr "Gadżety"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Winogronowy"
|
||||
@@ -570,7 +570,6 @@ msgstr "Ładowanie subskrypcji..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Ładowanie tagów..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Przejdź do subskrypcji, wpisując jej nazwę"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Przejdź do następnej kategorii/kanału z nieprzeczytanymi wpisami po oznaczeniu wszystkich wpisów jako przeczytane"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nigdy"
|
||||
@@ -657,7 +655,6 @@ msgstr "Nigdy"
|
||||
msgid "New password"
|
||||
msgstr "Nowe hasło"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nowe hasło"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Eksport OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Plik OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "Kategoria nadrzędna"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Hasło"
|
||||
@@ -874,7 +867,6 @@ msgstr "Odśwież"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Rejestracje są zamknięte w tej instancji CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Zresetuj hasło"
|
||||
@@ -883,7 +875,6 @@ msgstr "Zresetuj hasło"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Prawe kliknięcie"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Przewijanie"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Szukaj"
|
||||
@@ -924,7 +913,6 @@ msgstr "Wybierz następny nieprzeczytany kanał/kategorię"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Wybierz poprzedni nieprzeczytany kanał/kategorię"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL serwera"
|
||||
@@ -953,8 +941,6 @@ msgstr "Udostępnij"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Udostępnianie witryn"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "zmiana"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Pokazuj liczbę nieprzeczytanych na ikonie karty"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Pokazuj liczbę nieprzeczytanych w tytule karty"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Zarejestruj się"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Coś złego właśnie się stało..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Przestrzeń"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Gwiazda"
|
||||
msgid "Starred"
|
||||
msgstr "Oznaczone gwiazdką"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Przełącz na jasny motyw"
|
||||
msgid "System"
|
||||
msgstr "Systemowy"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Tagi"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Ten kanał ma stary filtr, którego nie można edytować i który nie jest stosowany. Proszę utworzyć filtr ponownie, korzystając z nowego edytora wyrażeń. Stare wyrażenie filtra to: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "To jest Twój klucz API. Może być używany do niektórych operacji API tylko do odczytu i zapewnia dostęp do API Fever. Użyj formularza na dole strony, aby wygenerować nowy klucz API"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "To jest Twój klucz API. Może być używany do niektórych operacji API tylko do odczytu i zapewnia dostęp do API Fever i API Google Reader. Użyj formularza na dole strony, aby wygenerować nowy klucz API"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Nieprzeczytane"
|
||||
msgid "Unstar"
|
||||
msgstr "Usuń gwiazdkę"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Anuluj subskrypcję"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Klucz użytkownika"
|
||||
msgid "User name"
|
||||
msgstr "Nazwa użytkownika"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nazwa użytkownika lub e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Adicionar usuário"
|
||||
msgid "Admin"
|
||||
msgstr "Administrador"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Nome de usuário do administrador"
|
||||
@@ -73,7 +72,6 @@ msgstr "Nome de usuário do administrador"
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Sempre"
|
||||
@@ -173,18 +171,14 @@ msgstr "Crie uma expressão de filtro para indicar o que você deseja ler. As en
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Extensão de navegador CommaFeed versão {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed é compatível com a API Fever. Use a URL a seguir no seu aplicativo móvel compatível com Fever. Entre com seu nome de usuário e sua <0>chave de API</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed é compatível com a API Google Reader. Use a URL a seguir no seu aplicativo móvel compatível com Google Reader. Entre com seu nome de usuário e sua <0>chave de API</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed próximo item não lido"
|
||||
@@ -240,7 +238,6 @@ msgstr "Confirmar"
|
||||
msgid "Confirm password"
|
||||
msgstr "Confirmar senha"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirmar senha"
|
||||
@@ -294,7 +291,6 @@ msgstr "dias"
|
||||
msgid "Delete"
|
||||
msgstr "Excluir"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Excluir conta"
|
||||
@@ -342,13 +338,10 @@ msgstr "Arraste o link para a barra de favoritos"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Endereço de e-mail"
|
||||
@@ -399,7 +392,6 @@ msgstr "Opções da extensão"
|
||||
msgid "Feed name"
|
||||
msgstr "Nome do feed"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Vá para a documentação da API."
|
||||
msgid "Goodies"
|
||||
msgstr "Brindes"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Uva"
|
||||
@@ -570,7 +570,6 @@ msgstr "Carregando assinaturas..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Carregando tags..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Navegue até uma assinatura digitando seu nome"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navegar para a próxima categoria/feed com entradas não lidas quando todas as entrafas forem lidas"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nunca"
|
||||
@@ -657,7 +655,6 @@ msgstr "Nunca"
|
||||
msgid "New password"
|
||||
msgstr "Nova senha"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nova senha"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Exportação OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Arquivo OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "Categoria Pai"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Senha"
|
||||
@@ -874,7 +867,6 @@ msgstr "Atualizar"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Os registros estão fechados nesta instância do CommaFeed"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Redefinir senha"
|
||||
@@ -883,7 +875,6 @@ msgstr "Redefinir senha"
|
||||
msgid "REST API"
|
||||
msgstr "API REST"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Clique com o botão direito"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Deslizar"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Pesquisar"
|
||||
@@ -924,7 +913,6 @@ msgstr "Selecionar próximo feed/categoria não lido"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Selecionar feed/categoria não lido anterior"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL do servidor"
|
||||
@@ -953,8 +941,6 @@ msgstr "Compartilhar"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Compartilhando sites"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Mostrar contagem de não lidos no favion da aba"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Mostrar contagem de não lidos no título da aba"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Inscreva-se"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Algo ruim acabou de acontecer..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Espaço"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Estrela"
|
||||
msgid "Starred"
|
||||
msgstr "Com estrela"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Mudar para tema claro"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Etiquetas"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Este feed tem um filtro legado que não pode ser editado e não é aplicado. Recrie o filtro usando o novo editor de expressões. A expressão do filtro legado era: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Esta é sua chave de API. Ela pode ser usada para algumas operações somente leitura da API e concede acesso à API do Fever. Use o formulário abaixo para gerar uma nova chave de API"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Esta é sua chave de API. Ela pode ser usada para algumas operações de API somente leitura e concede acesso à API Fever e à API Google Reader. Use o formulário na parte inferior da página para gerar uma nova chave de API"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Não lido"
|
||||
msgid "Unstar"
|
||||
msgstr "Desestrelar"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Cancelar inscrição"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Chave do usuário"
|
||||
msgid "User name"
|
||||
msgstr "Nome de usuário"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Nome de usuário ou e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Добавить пользователя"
|
||||
msgid "Admin"
|
||||
msgstr "Админ"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Имя администратора"
|
||||
@@ -73,7 +72,6 @@ msgstr "Имя администратора"
|
||||
msgid "All"
|
||||
msgstr "Все"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Всегда"
|
||||
@@ -173,18 +171,14 @@ msgstr "Создайте выражение фильтра, чтобы указ
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Версия расширения браузера CommaFeed {browserExt
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed совместим с API Fever. Используйте следующий URL-адрес в мобильном клиенте, совместимом с Fever. Войдите в систему, указав свое имя пользователя и <0>API-ключ</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed совместим с API Google Reader. Используйте следующий URL-адрес в мобильном клиенте, совместимом с Google Reader. Войдите в систему, указав свое имя пользователя и <0>API-ключ</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed следующий непрочитанный элемент"
|
||||
@@ -240,7 +238,6 @@ msgstr "Подтвердить"
|
||||
msgid "Confirm password"
|
||||
msgstr "Подтвердить пароль"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Подтвердите пароль"
|
||||
@@ -294,7 +291,6 @@ msgstr "дней"
|
||||
msgid "Delete"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Удалить учетную запись"
|
||||
@@ -342,13 +338,10 @@ msgstr "Перетащите ссылку на панель закладок"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "Электронная почта"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "Адрес электронной почты"
|
||||
@@ -399,7 +392,6 @@ msgstr "Параметры расширения"
|
||||
msgid "Feed name"
|
||||
msgstr "Имя фида"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Перейдите к документации по API."
|
||||
msgid "Goodies"
|
||||
msgstr "Сладости"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Виноградный"
|
||||
@@ -570,7 +570,6 @@ msgstr "Загрузка подписок..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Загрузка тегов..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Перейдите к подписке, введя ее имя."
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Переходить к следующей категории/фиду с непрочитанными записями при пометке всех записей как прочитанных"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Никогда"
|
||||
@@ -657,7 +655,6 @@ msgstr "Никогда"
|
||||
msgid "New password"
|
||||
msgstr "Новый пароль"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Новый пароль"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Экспорт OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML файл"
|
||||
@@ -791,10 +787,7 @@ msgstr "Родительская категория"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Пароль"
|
||||
@@ -874,7 +867,6 @@ msgstr "Обновить"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Регистрация закрыта для этого экземпляра CommaFeed."
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Сброс пароля"
|
||||
@@ -883,7 +875,6 @@ msgstr "Сброс пароля"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Правый клик"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Прокрутка"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
@@ -924,7 +913,6 @@ msgstr "Выбрать следующий непрочитанный фид/ка
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Выбрать предыдущий непрочитанный фид/категорию"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL сервера"
|
||||
@@ -953,8 +941,6 @@ msgstr "Поделиться"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Обмен сайтами"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Показывать количество непрочитанных в
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Показывать количество непрочитанных в заголовке вкладки"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Зарегистрироваться"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Только что случилось что-то плохое..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Пробел"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "В избранное"
|
||||
msgid "Starred"
|
||||
msgstr "Избранное"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Переключиться на светлую тему"
|
||||
msgid "System"
|
||||
msgstr "Системная"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Теги"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Этот фид имеет устаревший фильтр, который нельзя редактировать и который не применяется. Пожалуйста, создайте фильтр заново, используя новый редактор выражений. Устаревшее выражение фильтра было: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Это ваш ключ API. Он может использоваться для некоторых операций API только для чтения и предоставляет доступ к API Fever. Чтобы сгенерировать новый ключ API, воспользуйтесь формой в нижней части страницы"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Это ваш ключ API. Он может использоваться для некоторых операций API только для чтения и предоставляет доступ к API Fever и API Google Reader. Используйте форму внизу страницы, чтобы сгенерировать новый ключ API"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Не прочитано"
|
||||
msgid "Unstar"
|
||||
msgstr "Удалить из избранного"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Отписаться"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Ключ пользователя"
|
||||
msgid "User name"
|
||||
msgstr "Имя пользователя"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Имя пользователя или адрес электронной почты"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Pridať užívateľa"
|
||||
msgid "Admin"
|
||||
msgstr "Správca"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Meno správcu"
|
||||
@@ -73,7 +72,6 @@ msgstr "Meno správcu"
|
||||
msgid "All"
|
||||
msgstr "Všetky"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Vždy"
|
||||
@@ -173,18 +171,14 @@ msgstr "Vytvorte výraz filtra na určenie toho, čo chcete čítať. Záznamy,
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Zrušiť"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Rozšírenie prehliadača CommaFeed verzie {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed je kompatibilný s Fever API. Vo svojom mobilnom klientovi kompatibilnom s Fever použite nasledujúcu adresu URL. Prihláste sa pomocou svojho používateľského mena a <0>kľúča API</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed je kompatibilný s Google Reader API. Vo svojom mobilnom klientovi kompatibilnom s Google Reader použite nasledujúcu adresu URL. Prihláste sa pomocou svojho používateľského mena a <0>kľúča API</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed ďalšia neprečítaná položka"
|
||||
@@ -240,7 +238,6 @@ msgstr "Potvrdiť"
|
||||
msgid "Confirm password"
|
||||
msgstr "Potvrďte heslo"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Potvrdiť heslo"
|
||||
@@ -294,7 +291,6 @@ msgstr "dni"
|
||||
msgid "Delete"
|
||||
msgstr "Vymazať"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Vymažte účet"
|
||||
@@ -342,13 +338,10 @@ msgstr "Presuňte odkaz na lištu so záložkami"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-mailová adresa"
|
||||
@@ -399,7 +392,6 @@ msgstr "Možnosti rozšírenia"
|
||||
msgid "Feed name"
|
||||
msgstr "Názov informačného kanála"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Prejdite na dokumentáciu API."
|
||||
msgid "Goodies"
|
||||
msgstr "Dobrôtky"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Hroznová"
|
||||
@@ -570,7 +570,6 @@ msgstr "Načítavam odbery..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Načítavam značky..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Prejdite na predplatné zadaním jeho názvu"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Pri označovaní všetkých položiek ako prečítaných prejdite na ďalšiu kategóriu/kanál s neprečítanými položkami"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Nikdy"
|
||||
@@ -657,7 +655,6 @@ msgstr "Nikdy"
|
||||
msgid "New password"
|
||||
msgstr "Nové heslo"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nové heslo"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "Export OPML"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "Súbor OPML"
|
||||
@@ -791,10 +787,7 @@ msgstr "Rodičovská kategória"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Heslo"
|
||||
@@ -874,7 +867,6 @@ msgstr "Obnoviť"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "V tejto inštancii CommaFeed sú registrácie uzavreté"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Obnoviť heslo"
|
||||
@@ -883,7 +875,6 @@ msgstr "Obnoviť heslo"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Kliknutie pravým tlačidlom"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Rolovanie"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Hľadaj"
|
||||
@@ -924,7 +913,6 @@ msgstr "Vybrať ďalší neprečítaný kanál/kategóriu"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Vybrať predchádzajúci neprečítaný kanál/kategóriu"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "URL servera"
|
||||
@@ -953,8 +941,6 @@ msgstr "Zdieľať"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Zdieľanie stránok"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Smena"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Zobraziť počet neprečítaných v ikone karty"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Zobraziť počet neprečítaných v názve karty"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Zaregistrujte sa"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Práve sa stalo niečo zlé..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Medzera"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Hviezda"
|
||||
msgid "Starred"
|
||||
msgstr "S hviezdičkou"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Prepnite na svetlú tému"
|
||||
msgid "System"
|
||||
msgstr "Systém"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Značky"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Tento kanál má starší filter, ktorý sa nedá upravovať a nepoužíva sa. Znova vytvorte filter pomocou nového editora výrazov. Pôvodný výraz filtra bol: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Toto je váš kľúč API. Môže sa použiť na niektoré operácie rozhrania API len na čítanie a poskytuje prístup k rozhraniu Fever API. Na vygenerovanie nového kľúča API použite formulár v dolnej časti stránky."
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Toto je váš kľúč API. Môže sa použiť pre niektoré operácie API iba na čítanie a udeľuje prístup k Fever API a Google Reader API. Na vygenerovanie nového kľúča API použite formulár v spodnej časti stránky"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Neprečítané"
|
||||
msgid "Unstar"
|
||||
msgstr "Odobrať hviezdičku"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Zrušte odber"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Používateľský kľúč"
|
||||
msgid "User name"
|
||||
msgstr "Meno používateľa"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Meno používateľa alebo e-mail"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Lägg till användare"
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Administratörsanvändarnamn"
|
||||
@@ -73,7 +72,6 @@ msgstr "Administratörsanvändarnamn"
|
||||
msgid "All"
|
||||
msgstr "Alla"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Alltid"
|
||||
@@ -173,18 +171,14 @@ msgstr "Skapa ett filteruttryck för att ange vad du vill läsa. Poster som inte
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "Avbryt"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "Webbläsartillägget CommaFeed version {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed är kompatibelt med Fever API. Använd följande URL i din Fever-kompatibla mobilklient. Logga in med ditt användarnamn och din <0>API-nyckel</0>."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed är kompatibelt med Google Reader API. Använd följande URL i din Google Reader-kompatibla mobilklient. Logga in med ditt användarnamn och din <0>API-nyckel</0>."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed nästa olästa objekt"
|
||||
@@ -240,7 +238,6 @@ msgstr "Bekräfta"
|
||||
msgid "Confirm password"
|
||||
msgstr "Bekräfta lösenord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Bekräfta lösenord"
|
||||
@@ -294,7 +291,6 @@ msgstr "dagar"
|
||||
msgid "Delete"
|
||||
msgstr "Ta bort"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Radera konto"
|
||||
@@ -342,13 +338,10 @@ msgstr "Dra länken till bokmärkesfältet"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-post"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-postadress"
|
||||
@@ -399,7 +392,6 @@ msgstr "Tilläggsalternativ"
|
||||
msgid "Feed name"
|
||||
msgstr "Flödesnamn"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "Gå till API-dokumentationen."
|
||||
msgid "Goodies"
|
||||
msgstr "Godsaker"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Lila"
|
||||
@@ -570,7 +570,6 @@ msgstr "Laddar prenumerationer..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Laddar taggar..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Navigera till ett abonnemang genom att ange dess namn"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Navigera till nästa kategori/flöde med olästa poster när alla poster markeras som lästa"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Aldrig"
|
||||
@@ -657,7 +655,6 @@ msgstr "Aldrig"
|
||||
msgid "New password"
|
||||
msgstr "Nytt lösenord"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Nytt lösenord"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML-export"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML-fil"
|
||||
@@ -791,10 +787,7 @@ msgstr "Föräldrakategori"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Lösenord"
|
||||
@@ -874,7 +867,6 @@ msgstr "Uppdatera"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Registreringar är stängda på denna CommaFeed-instans"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Återställ lösenord"
|
||||
@@ -883,7 +875,6 @@ msgstr "Återställ lösenord"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Högerklick"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Rullning"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Sök"
|
||||
@@ -924,7 +913,6 @@ msgstr "Välj nästa olästa flöde/kategori"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Välj föregående olästa flöde/kategori"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Server-URL"
|
||||
@@ -953,8 +941,6 @@ msgstr "Dela"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Delningssajter"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Skift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Visa antal olästa i flikikonen"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Visa antal olästa i fliktiteln"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Anmäl dig"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Något dåligt hände precis..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Rymden"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Stjärna"
|
||||
msgid "Starred"
|
||||
msgstr "Starmed"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Byt till ljustema"
|
||||
msgid "System"
|
||||
msgstr "System"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Taggar"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Detta flöde har ett gammalt filter som inte kan redigeras och inte tillämpas. Skapa filtret på nytt med den nya uttryckseditorern. Det gamla filteruttrycket var: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Detta är din API-nyckel. Den kan användas för vissa skrivskyddade API-operationer och ger åtkomst till Fever API. Använd formuläret längst ner på sidan för att generera en ny API-nyckel"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Det här är din API-nyckel. Den kan användas för vissa skrivskyddade API-åtgärder och ger åtkomst till Fever API och Google Reader API. Använd formuläret längst ner på sidan för att generera en ny API-nyckel"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Oläst"
|
||||
msgid "Unstar"
|
||||
msgstr "Ta bort stjärna"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Avsluta prenumeration"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Användarnyckel"
|
||||
msgid "User name"
|
||||
msgstr "Användarnamn"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Användarnamn eller e-post"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "Kullanıcı ekle"
|
||||
msgid "Admin"
|
||||
msgstr "Yönetici"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "Yönetici kullanıcı adı"
|
||||
@@ -73,7 +72,6 @@ msgstr "Yönetici kullanıcı adı"
|
||||
msgid "All"
|
||||
msgstr "Tümü"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "Her zaman"
|
||||
@@ -173,18 +171,14 @@ msgstr "Ne okumak istediğinizi belirtmek için bir filtre ifadesi oluşturun. E
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "İptal"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed tarayıcı eklentisi sürüm {browserExtensionVersion}."
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed, Fever API ile uyumludur. Fever uyumlu mobil istemcinizde aşağıdaki URL'yi kullanın. Kullanıcı adınız ve <0>API anahtarınız</0> ile giriş yapın."
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed, Google Reader API ile uyumludur. Google Reader uyumlu mobil istemcinizde aşağıdaki URL'yi kullanın. Kullanıcı adınız ve <0>API anahtarınız</0> ile giriş yapın."
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed sonraki okunmamış öğe"
|
||||
@@ -240,7 +238,6 @@ msgstr "Onayla"
|
||||
msgid "Confirm password"
|
||||
msgstr "Şifreyi onayla"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "Şifreyi Onayla"
|
||||
@@ -294,7 +291,6 @@ msgstr "gün"
|
||||
msgid "Delete"
|
||||
msgstr "Sil"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "Hesabı sil"
|
||||
@@ -342,13 +338,10 @@ msgstr "Bağlantıyı yer işareti çubuğuna sürükleyin"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "E-posta"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "E-posta adresi"
|
||||
@@ -399,7 +392,6 @@ msgstr "Eklenti ayarları"
|
||||
msgid "Feed name"
|
||||
msgstr "Yayın adı"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "API belgelerine gidin."
|
||||
msgid "Goodies"
|
||||
msgstr "İyilikler"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "Üzüm"
|
||||
@@ -570,7 +570,6 @@ msgstr "Abonelikler yükleniyor..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "Etiketler yükleniyor..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "Adını girerek bir aboneliğe gidin"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "Tüm girişleri okundu olarak işaretlerken okunmamış girişleri olan bir sonraki kategoriye/beslemeye git"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "Asla"
|
||||
@@ -657,7 +655,6 @@ msgstr "Asla"
|
||||
msgid "New password"
|
||||
msgstr "Yeni şifre"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "Yeni Şifre"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML dışa aktarma"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML dosyası"
|
||||
@@ -791,10 +787,7 @@ msgstr "Üst Kategori"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "Şifre"
|
||||
@@ -874,7 +867,6 @@ msgstr "Yenile"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "Bu CommaFeed örneğinde kayıtlar kapalı"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "Şifreyi Sıfırla"
|
||||
@@ -883,7 +875,6 @@ msgstr "Şifreyi Sıfırla"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "Sağ tık"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "Kaydırma"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "Ara"
|
||||
@@ -924,7 +913,6 @@ msgstr "Sonraki okunmamış beslemeyi/kategoriyi seç"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "Önceki okunmamış beslemeyi/kategoriyi seç"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "Sunucu URL'si"
|
||||
@@ -953,8 +941,6 @@ msgstr "Paylaş"
|
||||
msgid "Sharing sites"
|
||||
msgstr "Siteleri paylaşma"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Vardiya"
|
||||
@@ -1003,7 +989,6 @@ msgstr "Sekme simgesinde okunmamış sayısını göster"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "Sekme başlığında okunmamış sayısını göster"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "Kaydolun"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "Az önce kötü bir şey oldu..."
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "Uzay"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "Yıldız"
|
||||
msgid "Starred"
|
||||
msgstr "Yıldızlı"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "Aydınlık temaya geç"
|
||||
msgid "System"
|
||||
msgstr "Sistem"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "Etiketler"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "Bu beslemenin düzenlenemeyen ve uygulanmayan eski bir filtresi var. Lütfen yeni ifade düzenleyiciyi kullanarak filtreyi yeniden oluşturun. Eski filtre ifadesi şuydu: <0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Bu sizin API anahtarınızdır. Bazı salt okunur API işlemleri için kullanılabilir ve Fever API'sine erişim sağlar. Yeni bir API anahtarı oluşturmak için sayfanın altındaki formu kullanın"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "Bu, API anahtarınızdır. Bazı salt okunur API işlemleri için kullanılabilir ve Fever API ile Google Reader API'ye erişim sağlar. Yeni bir API anahtarı oluşturmak için sayfanın altındaki formu kullanın"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "Okunmamış"
|
||||
msgid "Unstar"
|
||||
msgstr "Yıldızı kaldır"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "Aboneliği iptal et"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "Kullanıcı anahtarı"
|
||||
msgid "User name"
|
||||
msgstr "Kullanıcı adı"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "Kullanıcı Adı veya E-posta"
|
||||
|
||||
@@ -60,7 +60,6 @@ msgstr "添加用户"
|
||||
msgid "Admin"
|
||||
msgstr "管理员"
|
||||
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
msgid "Admin user name"
|
||||
msgstr "管理员用户名"
|
||||
@@ -73,7 +72,6 @@ msgstr "管理员用户名"
|
||||
msgid "All"
|
||||
msgstr "全部"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Always"
|
||||
msgstr "总是"
|
||||
@@ -173,18 +171,14 @@ msgstr "构建过滤表达式以指示您想阅读的内容。不匹配的条目
|
||||
#: src/components/MarkAllAsReadConfirmationDialog.tsx
|
||||
#: src/components/settings/CustomCodeSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/CategoryDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/TagDetailsPage.tsx
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/AddCategory.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
@@ -216,6 +210,10 @@ msgstr "CommaFeed的浏览器扩展版本:{browserExtensionVersion}。"
|
||||
msgid "CommaFeed is compatible with the Fever API. Use the following URL in your Fever-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed 和 Fever API 兼容,可以在您Fever兼容的移动客户端中使用以下网址,通过用户名和<0>API密钥</0>登录。"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "CommaFeed is compatible with the Google Reader API. Use the following URL in your Google Reader-compatible mobile client. Login with your username and your <0>API key</0>."
|
||||
msgstr "CommaFeed 和 Google Reader API 兼容,可以在您Google Reader兼容的移动客户端中使用以下网址,通过用户名和<0>API密钥</0>登录。"
|
||||
|
||||
#: src/pages/app/AboutPage.tsx
|
||||
msgid "CommaFeed next unread item"
|
||||
msgstr "CommaFeed 下一个未读项目"
|
||||
@@ -240,7 +238,6 @@ msgstr "确认"
|
||||
msgid "Confirm password"
|
||||
msgstr "确认密码"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Confirm Password"
|
||||
msgstr "确认密码"
|
||||
@@ -294,7 +291,6 @@ msgstr "天"
|
||||
msgid "Delete"
|
||||
msgstr "删除"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Delete account"
|
||||
msgstr "删除帐号"
|
||||
@@ -342,13 +338,10 @@ msgstr "拖动链接到书签栏"
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
#: src/pages/admin/AdminUsersPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
#: src/pages/auth/PasswordRecoveryPage.tsx
|
||||
msgid "E-mail"
|
||||
msgstr "电子邮件"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "E-mail address"
|
||||
msgstr "电子邮件地址"
|
||||
@@ -399,7 +392,6 @@ msgstr "扩展选项"
|
||||
msgid "Feed name"
|
||||
msgstr "信息流名称"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Feed URL"
|
||||
@@ -466,6 +458,14 @@ msgstr "API 文档"
|
||||
msgid "Goodies"
|
||||
msgstr "好东西"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "Google Reader API URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Grape"
|
||||
msgstr "深紫"
|
||||
@@ -570,7 +570,6 @@ msgstr "正在加载订阅..."
|
||||
msgid "Loading tags..."
|
||||
msgstr "正在加载标签..."
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Log in"
|
||||
@@ -648,7 +647,6 @@ msgstr "通过输入订阅名称导航到订阅"
|
||||
msgid "Navigate to the next category/feed with unread entries when marking all entries as read"
|
||||
msgstr "将所有条目标记为已读时导航到下一个包含未读条目的类别/信息流"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "Never"
|
||||
msgstr "从不"
|
||||
@@ -657,7 +655,6 @@ msgstr "从不"
|
||||
msgid "New password"
|
||||
msgstr "新密码"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "New Password"
|
||||
msgstr "新密码"
|
||||
@@ -764,7 +761,6 @@ msgstr "OPML"
|
||||
msgid "OPML export"
|
||||
msgstr "OPML 导出"
|
||||
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
#: src/components/content/add/ImportOpml.tsx
|
||||
msgid "OPML file"
|
||||
msgstr "OPML 文件"
|
||||
@@ -791,10 +787,7 @@ msgstr "父类别"
|
||||
|
||||
#: src/components/admin/UserEdit.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/InitialSetupPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
@@ -874,7 +867,6 @@ msgstr "刷新"
|
||||
msgid "Registrations are closed on this CommaFeed instance"
|
||||
msgstr "此 CommaFeed 实例上的注册已关闭"
|
||||
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
#: src/pages/auth/PasswordResetPage.tsx
|
||||
msgid "Reset Password"
|
||||
msgstr "重置密码"
|
||||
@@ -883,7 +875,6 @@ msgstr "重置密码"
|
||||
msgid "REST API"
|
||||
msgstr "REST API"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Right click"
|
||||
msgstr "右键单击"
|
||||
@@ -910,8 +901,6 @@ msgid "Scrolling"
|
||||
msgstr "滚动"
|
||||
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/header/Header.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
#: src/components/sidebar/TreeSearch.tsx
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
@@ -924,7 +913,6 @@ msgstr "选择下一个未读信息流/类别"
|
||||
msgid "Select previous unread feed/category"
|
||||
msgstr "选择上一个未读信息流/类别"
|
||||
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
#: src/components/settings/PushNotificationSettings.tsx
|
||||
msgid "Server URL"
|
||||
msgstr "服务器网址"
|
||||
@@ -953,8 +941,6 @@ msgstr "分享"
|
||||
msgid "Sharing sites"
|
||||
msgstr "共享站点"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Shift"
|
||||
msgstr "Shift"
|
||||
@@ -1003,7 +989,6 @@ msgstr "在标签页图标上显示未读数量"
|
||||
msgid "Show unread count in tab title"
|
||||
msgstr "在标签页标题中显示未读数量"
|
||||
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/auth/RegistrationPage.tsx
|
||||
#: src/pages/WelcomePage.tsx
|
||||
msgid "Sign up"
|
||||
@@ -1013,7 +998,6 @@ msgstr "注册"
|
||||
msgid "Something bad just happened..."
|
||||
msgstr "刚刚发生了不好的事情……"
|
||||
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
#: src/components/KeyboardShortcutsHelp.tsx
|
||||
msgid "Space"
|
||||
msgstr "空格"
|
||||
@@ -1030,7 +1014,6 @@ msgstr "星标"
|
||||
msgid "Starred"
|
||||
msgstr "已加星标"
|
||||
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/components/content/add/Subscribe.tsx
|
||||
#: src/pages/app/AddPage.tsx
|
||||
msgid "Subscribe"
|
||||
@@ -1064,7 +1047,6 @@ msgstr "切换到浅色主题"
|
||||
msgid "System"
|
||||
msgstr "跟随系统"
|
||||
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
#: src/components/content/FeedEntryFooter.tsx
|
||||
msgid "Tags"
|
||||
msgstr "标签"
|
||||
@@ -1095,8 +1077,8 @@ msgid "This feed has a legacy filter that cannot be edited and is not applied. P
|
||||
msgstr "此信息流具有无法编辑且未应用的旧版过滤器。请使用新的表达式编辑器重新创建过滤器。旧版过滤器表达式为:<0>{0}</0>"
|
||||
|
||||
#: src/components/settings/ProfileSettings.tsx
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "这是您的API 密钥,它可以被用于Fever API的只读操作及访问授权。使用页面底部的表单生成一个新的API密钥。"
|
||||
msgid "This is your API key. It can be used for some read-only API operations and grants access to the Fever API and the Google Reader API. Use the form at the bottom of the page to generate a new API key"
|
||||
msgstr "这是您的 API 密钥。它可用于某些只读 API 操作,并授予对 Fever API 和 Google Reader API 的访问权限。使用页面底部的表单生成新的 API 密钥"
|
||||
|
||||
#: src/components/settings/DisplaySettings.tsx
|
||||
msgid "This setting can cause scrolling issues on some browsers (e.g. Safari)"
|
||||
@@ -1136,7 +1118,6 @@ msgstr "未读"
|
||||
msgid "Unstar"
|
||||
msgstr "取消星标"
|
||||
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
#: src/pages/app/FeedDetailsPage.tsx
|
||||
msgid "Unsubscribe"
|
||||
msgstr "取消订阅"
|
||||
@@ -1149,7 +1130,6 @@ msgstr "用户密钥"
|
||||
msgid "User name"
|
||||
msgstr "用户名"
|
||||
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
#: src/pages/auth/LoginPage.tsx
|
||||
msgid "User Name or E-mail"
|
||||
msgstr "用户名或电子邮件"
|
||||
|
||||
@@ -15,9 +15,10 @@ dayjs.extend(relativeTime)
|
||||
dayjs.extend(duration)
|
||||
|
||||
const root = document.getElementById("root")
|
||||
root &&
|
||||
ReactDOM.createRoot(root).render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
)
|
||||
if (!root) throw new Error("root element not found")
|
||||
|
||||
ReactDOM.createRoot(root).render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { msg } from "@lingui/core/macro"
|
||||
import { ActionIcon, AppShell, Box, Center, Group, ScrollArea, Title, useMantineTheme } from "@mantine/core"
|
||||
import { type ReactNode, type RefObject, Suspense, useEffect, useRef } from "react"
|
||||
import { type ReactNode, type RefObject, Suspense, useEffect, useRef, useState } from "react"
|
||||
import Draggable from "react-draggable"
|
||||
import { TbMenu2, TbPlus, TbX } from "react-icons/tb"
|
||||
import { Outlet } from "react-router-dom"
|
||||
import { useSwipeable } from "react-swipeable"
|
||||
import Tinycon from "tinycon"
|
||||
import { Constants } from "@/app/constants"
|
||||
import { redirectToAdd, redirectToRootCategory } from "@/app/redirect/thunks"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/store"
|
||||
@@ -12,8 +13,10 @@ import { setMobileMenuOpen } from "@/app/tree/slice"
|
||||
import { reloadTree } from "@/app/tree/thunks"
|
||||
import { setSidebarWidth } from "@/app/user/slice"
|
||||
import { reloadProfile, reloadSettings, reloadTags } from "@/app/user/thunks"
|
||||
import { categoryUnreadCount } from "@/app/utils"
|
||||
import { ActionButton } from "@/components/ActionButton"
|
||||
import { AnnouncementDialog } from "@/components/AnnouncementDialog"
|
||||
import { DisablePullToRefresh } from "@/components/DisablePullToRefresh"
|
||||
import { Loader } from "@/components/Loader"
|
||||
import { Logo } from "@/components/Logo"
|
||||
import { MarkAllAsReadConfirmationDialog } from "@/components/MarkAllAsReadConfirmationDialog"
|
||||
@@ -51,6 +54,78 @@ function LogoAndTitle() {
|
||||
)
|
||||
}
|
||||
|
||||
function UnreadCountTitleHandler({
|
||||
enabled,
|
||||
}: Readonly<{
|
||||
enabled?: boolean
|
||||
}>) {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
return <title>{enabled && unreadCount > 0 ? `(${unreadCount}) CommaFeed` : "CommaFeed"}</title>
|
||||
}
|
||||
|
||||
function UnreadCountFaviconHandler({ enabled }: { enabled?: boolean }) {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
useEffect(() => {
|
||||
if (enabled && unreadCount > 0) {
|
||||
Tinycon.setBubble(unreadCount)
|
||||
} else {
|
||||
Tinycon.reset()
|
||||
}
|
||||
}, [unreadCount, enabled])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function BrowserExtensionBadgeUnreadCountHandler() {
|
||||
const root = useAppSelector(state => state.tree.rootCategory)
|
||||
const { setBadgeUnreadCount } = useBrowserExtension()
|
||||
useEffect(() => {
|
||||
if (!root) return
|
||||
const unreadCount = categoryUnreadCount(root)
|
||||
setBadgeUnreadCount(unreadCount)
|
||||
}, [root, setBadgeUnreadCount])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function CustomJsHandler() {
|
||||
const [scriptLoaded, setScriptLoaded] = useState(false)
|
||||
const { loading } = useAppLoading()
|
||||
|
||||
useEffect(() => {
|
||||
if (scriptLoaded || loading) {
|
||||
return
|
||||
}
|
||||
|
||||
const script = document.createElement("script")
|
||||
script.src = "custom_js.js"
|
||||
script.async = true
|
||||
document.body.appendChild(script)
|
||||
|
||||
setScriptLoaded(true)
|
||||
|
||||
return () => script.remove()
|
||||
}, [scriptLoaded, loading])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function CustomCssHandler() {
|
||||
useEffect(() => {
|
||||
const link = document.createElement("link")
|
||||
link.rel = "stylesheet"
|
||||
link.type = "text/css"
|
||||
link.href = "custom_css.css"
|
||||
document.head.appendChild(link)
|
||||
|
||||
return () => link.remove()
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const useStyles = tss
|
||||
.withParams<{
|
||||
sidebarWidth: number
|
||||
@@ -75,6 +150,9 @@ export default function Layout(props: Readonly<LayoutProps>) {
|
||||
const draggableSeparator = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { loading } = useAppLoading()
|
||||
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
|
||||
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
|
||||
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
|
||||
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
|
||||
const webSocketConnected = useAppSelector(state => state.server.webSocketConnected)
|
||||
const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval)
|
||||
@@ -173,61 +251,69 @@ export default function Layout(props: Readonly<LayoutProps>) {
|
||||
|
||||
if (loading) return <LoadingPage />
|
||||
return (
|
||||
<Box {...swipeHandlers}>
|
||||
<AppShell
|
||||
header={{ height: Constants.layout.headerHeight, collapsed: headerInFooter }}
|
||||
footer={{ height: Constants.layout.headerHeight, collapsed: !headerInFooter }}
|
||||
navbar={{
|
||||
width: sidebarWidth,
|
||||
breakpoint: Constants.layout.mobileBreakpoint,
|
||||
collapsed: { mobile: !mobileMenuOpen, desktop: !props.sidebarVisible },
|
||||
}}
|
||||
padding={{ base: 6, [Constants.layout.mobileBreakpointName]: "md" }}
|
||||
>
|
||||
<AppShell.Header>{!headerInFooter && header}</AppShell.Header>
|
||||
<AppShell.Footer>{headerInFooter && header}</AppShell.Footer>
|
||||
<AppShell.Navbar p={sidebarPadding}>
|
||||
<AppShell.Section grow component={ScrollArea} mx="-sm" px="sm">
|
||||
<Box className={classes.sidebarContent}>{props.sidebar}</Box>
|
||||
</AppShell.Section>
|
||||
</AppShell.Navbar>
|
||||
<OnDesktop>
|
||||
<Draggable
|
||||
nodeRef={draggableSeparator as RefObject<HTMLElement>}
|
||||
axis="x"
|
||||
defaultPosition={{
|
||||
x: sidebarWidth,
|
||||
y: 0,
|
||||
}}
|
||||
bounds={{
|
||||
left: 120,
|
||||
right: 1000,
|
||||
}}
|
||||
grid={[30, 30]}
|
||||
onDrag={(_e, data) => {
|
||||
dispatch(setSidebarWidth(data.x))
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
ref={draggableSeparator}
|
||||
style={{
|
||||
position: "fixed",
|
||||
height: "100%",
|
||||
width: "10px",
|
||||
cursor: "ew-resize",
|
||||
<>
|
||||
<UnreadCountTitleHandler enabled={unreadCountTitle} />
|
||||
<UnreadCountFaviconHandler enabled={unreadCountFavicon} />
|
||||
<BrowserExtensionBadgeUnreadCountHandler />
|
||||
<CustomJsHandler />
|
||||
<CustomCssHandler />
|
||||
<DisablePullToRefresh enabled={disablePullToRefresh} />
|
||||
<Box {...swipeHandlers}>
|
||||
<AppShell
|
||||
header={{ height: Constants.layout.headerHeight, collapsed: headerInFooter }}
|
||||
footer={{ height: Constants.layout.headerHeight, collapsed: !headerInFooter }}
|
||||
navbar={{
|
||||
width: sidebarWidth,
|
||||
breakpoint: Constants.layout.mobileBreakpoint,
|
||||
collapsed: { mobile: !mobileMenuOpen, desktop: !props.sidebarVisible },
|
||||
}}
|
||||
padding={{ base: 6, [Constants.layout.mobileBreakpointName]: "md" }}
|
||||
>
|
||||
<AppShell.Header>{!headerInFooter && header}</AppShell.Header>
|
||||
<AppShell.Footer>{headerInFooter && header}</AppShell.Footer>
|
||||
<AppShell.Navbar p={sidebarPadding}>
|
||||
<AppShell.Section grow component={ScrollArea} mx="-sm" px="sm">
|
||||
<Box className={classes.sidebarContent}>{props.sidebar}</Box>
|
||||
</AppShell.Section>
|
||||
</AppShell.Navbar>
|
||||
<OnDesktop>
|
||||
<Draggable
|
||||
nodeRef={draggableSeparator as RefObject<HTMLElement>}
|
||||
axis="x"
|
||||
defaultPosition={{
|
||||
x: sidebarWidth,
|
||||
y: 0,
|
||||
}}
|
||||
/>
|
||||
</Draggable>
|
||||
</OnDesktop>
|
||||
bounds={{
|
||||
left: 120,
|
||||
right: 1000,
|
||||
}}
|
||||
grid={[30, 30]}
|
||||
onDrag={(_e, data) => {
|
||||
dispatch(setSidebarWidth(data.x))
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
ref={draggableSeparator}
|
||||
style={{
|
||||
position: "fixed",
|
||||
height: "100%",
|
||||
width: "10px",
|
||||
cursor: "ew-resize",
|
||||
}}
|
||||
/>
|
||||
</Draggable>
|
||||
</OnDesktop>
|
||||
|
||||
<AppShell.Main>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<AnnouncementDialog />
|
||||
<MarkAllAsReadConfirmationDialog />
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
</Box>
|
||||
<AppShell.Main>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<AnnouncementDialog />
|
||||
<MarkAllAsReadConfirmationDialog />
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ export default defineConfig(() => ({
|
||||
}),
|
||||
lingui(),
|
||||
checker({
|
||||
typescript: true,
|
||||
// temporary disabled until TypeScript 7 exposes a stable api
|
||||
// typescript: true,
|
||||
biome: {
|
||||
command: "check",
|
||||
flags: "--error-on-warnings",
|
||||
|
||||
@@ -1,401 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="23">
|
||||
<profile kind="CodeFormatterProfile" name="CommaFeed" version="23">
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="140"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="140"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
<module name="Checker">
|
||||
<property name="charset" value="UTF-8" />
|
||||
<property name="fileExtensions" value="java" />
|
||||
@@ -40,11 +40,6 @@
|
||||
<module name="AvoidStarImport" />
|
||||
<module name="AvoidStaticImport" />
|
||||
<module name="IllegalImport" />
|
||||
<module name="ImportOrder">
|
||||
<property name="groups" value="/^java\./,javax,jakarta,org,com" />
|
||||
<property name="ordered" value="true" />
|
||||
<property name="separated" value="true" />
|
||||
</module>
|
||||
<module name="RedundantImport" />
|
||||
<module name="UnusedImports" />
|
||||
|
||||
@@ -60,7 +55,7 @@
|
||||
<module name="EmptyCatchBlock">
|
||||
<property name="exceptionVariableName" value="ignore|ignored" />
|
||||
<message key="catch.block.empty"
|
||||
value="Empty catch block. You can use the name 'ignore' or 'ignored' for the exception variable if you really want an empty catch block, but you should strongly consider at the very least logging something." />
|
||||
value="Empty catch block. You can use the name 'ignore' or 'ignored' for the exception variable if you really want an empty catch block, but you should strongly consider at the very least logging something." />
|
||||
</module>
|
||||
<module name="LeftCurly" />
|
||||
<module name="NeedBraces" />
|
||||
@@ -76,7 +71,7 @@
|
||||
<module name="FallThrough" />
|
||||
<module name="IllegalInstantiation">
|
||||
<property name="classes"
|
||||
value="java.lang.Boolean, java.lang.Byte, java.lang.Character, java.lang.Double, java.lang.Float, java.lang.Integer, java.lang.Long, java.lang.Short" />
|
||||
value="java.lang.Boolean, java.lang.Byte, java.lang.Character, java.lang.Double, java.lang.Float, java.lang.Integer, java.lang.Long, java.lang.Short" />
|
||||
</module>
|
||||
<module name="IllegalType" />
|
||||
<module name="ModifiedControlVariable">
|
||||
@@ -113,12 +108,6 @@
|
||||
<module name="NoLineWrap" />
|
||||
<module name="NoWhitespaceBefore" />
|
||||
<module name="ParenPad" />
|
||||
<module name="RegexpSinglelineJava">
|
||||
<property name="format" value="^\t* +\t*\S" />
|
||||
<property name="message" value="Line has leading space characters; indentation should be performed with tabs only." />
|
||||
<property name="ignoreComments" value="true" />
|
||||
</module>
|
||||
<module name="WhitespaceAround" />
|
||||
|
||||
</module>
|
||||
</module>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#Organize Import Order
|
||||
#Wed Jan 29 15:15:04 CET 2025
|
||||
0=java
|
||||
1=javax
|
||||
2=jakarta
|
||||
3=org
|
||||
4=com
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
FROM ibm-semeru-runtimes:open-jdk-25.0.2_10-jre@sha256:b02e4cd184d9ece59b01129af1d0a069fa01e4f0f798f0bc4f3ff1a8391ba694
|
||||
FROM ibm-semeru-runtimes:open-jdk-25.0.4.0-jre@sha256:02888a2306c2cb8af22a05b2c41be90fdc9df5f4611861ddf6ea90c874de65e6
|
||||
EXPOSE 8082
|
||||
|
||||
RUN mkdir -p /commafeed/data
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:13.4@sha256:3352c2e13876c8a5c5873ef20870e1939e73cb9a3c1aeba5e3e72172a85ce9ed
|
||||
FROM debian:13.6@sha256:34cd9e9fd437c0a095ec39cb2e73422c9f30821b0d0848ed74fd0d43bae4d958
|
||||
ARG TARGETARCH
|
||||
|
||||
EXPOSE 8082
|
||||
|
||||
@@ -93,3 +93,12 @@ Tags are of the form `<version>-<database>[-jvm]` where:
|
||||
- `master` (always points to the latest git commit)
|
||||
- `<database>` is the database to use (`h2`, `postgresql`, `mysql` or `mariadb`)
|
||||
- `-jvm` is optional and indicates that CommaFeed is running on a JVM, and not compiled natively.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Getting "Access to local address blocked" when adding a feed
|
||||
|
||||
CommaFeed blocks access to local resources by default to prevent [SSRF](https://en.wikipedia.org/wiki/Server-side_request_forgery) attacks.
|
||||
If you want to subscribe to feeds that are only available on your local network, you can disable this security measure by setting the `COMMAFEED_HTTP_CLIENT_BLOCK_LOCAL_ADDRESSES` variable to `false`.
|
||||
Do this only if you trust all users of your CommaFeed instance not to access private resources.
|
||||
|
||||
|
||||
@@ -1,40 +1,59 @@
|
||||
package com.commafeed;
|
||||
|
||||
import jakarta.enterprise.event.Observes;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import com.commafeed.backend.feed.FeedRefreshEngine;
|
||||
import com.commafeed.backend.feed.ImageProxyUrl;
|
||||
import com.commafeed.backend.task.TaskScheduler;
|
||||
import com.commafeed.security.password.PasswordConstraintValidator;
|
||||
|
||||
import io.quarkus.runtime.ShutdownEvent;
|
||||
import io.quarkus.runtime.StartupEvent;
|
||||
|
||||
import jakarta.enterprise.event.Observes;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang3.SystemProperties;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
@RequiredArgsConstructor
|
||||
public class CommaFeedApplication {
|
||||
|
||||
private final FeedRefreshEngine feedRefreshEngine;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final FeedRefreshEngine feedRefreshEngine;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
public void start(@Observes StartupEvent ev) {
|
||||
log.info("starting up...");
|
||||
public void start(@Observes StartupEvent ev) {
|
||||
log.info("starting up...");
|
||||
|
||||
PasswordConstraintValidator.setMinimumPasswordLength(config.users().minimumPasswordLength());
|
||||
// disable entity expansion limits added in JDK24+ (#1961)
|
||||
// we already strip doctype declarations in XMLCleaner to prevent xxe attacks
|
||||
// we also already limit the size of feeds we download in HttpGetter
|
||||
System.setProperty(SystemProperties.JDK_XML_MAX_GENERAL_ENTITY_SIZE_LIMIT, "0");
|
||||
System.setProperty(SystemProperties.JDK_XML_TOTAL_ENTITY_SIZE_LIMIT, "0");
|
||||
|
||||
feedRefreshEngine.start();
|
||||
taskScheduler.start();
|
||||
}
|
||||
PasswordConstraintValidator.setMinimumPasswordLength(
|
||||
config.users().minimumPasswordLength());
|
||||
|
||||
public void stop(@Observes ShutdownEvent ev) {
|
||||
log.info("shutting down...");
|
||||
if (config.imageProxyEnabled()) {
|
||||
ImageProxyUrl.generateKey();
|
||||
}
|
||||
|
||||
feedRefreshEngine.stop();
|
||||
taskScheduler.stop();
|
||||
}
|
||||
if (config.passwordRecoveryEnabled() && config.passwordRecoveryPublicBaseUrl().isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
"password recovery is enabled but no public base url is set");
|
||||
}
|
||||
|
||||
feedRefreshEngine.start();
|
||||
taskScheduler.start();
|
||||
}
|
||||
|
||||
public void stop(@Observes ShutdownEvent ev) {
|
||||
log.info("shutting down...");
|
||||
|
||||
feedRefreshEngine.stop();
|
||||
taskScheduler.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package com.commafeed;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
|
||||
import com.commafeed.backend.feed.FeedRefreshIntervalCalculator;
|
||||
|
||||
import io.quarkus.runtime.annotations.ConfigDocSection;
|
||||
@@ -16,391 +9,362 @@ import io.quarkus.runtime.configuration.MemorySize;
|
||||
import io.smallrye.config.ConfigMapping;
|
||||
import io.smallrye.config.WithDefault;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* CommaFeed configuration
|
||||
*
|
||||
* Default values are for production, they can be overridden in application.properties for other profiles
|
||||
* <p>Default values are for production, they can be overridden in application.properties for other
|
||||
* profiles
|
||||
*/
|
||||
@ConfigMapping(prefix = "commafeed")
|
||||
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
|
||||
public interface CommaFeedConfiguration {
|
||||
/**
|
||||
* Whether to expose a robots.txt file that disallows web crawlers and search engine indexers.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean hideFromWebCrawlers();
|
||||
/**
|
||||
* Whether to expose a robots.txt file that disallows web crawlers and search engine indexers.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean hideFromWebCrawlers();
|
||||
|
||||
/**
|
||||
* If enabled, images in feed entries will be proxied through the server instead of accessed directly by the browser.
|
||||
*
|
||||
* This is useful if commafeed is accessed through a restricting proxy that blocks some feeds that are followed.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean imageProxyEnabled();
|
||||
/**
|
||||
* If enabled, images in feed entries will be proxied through the server instead of accessed
|
||||
* directly by the browser.
|
||||
*
|
||||
* <p>This is useful if commafeed is accessed through a restricting proxy that blocks some feeds
|
||||
* that are followed.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean imageProxyEnabled();
|
||||
|
||||
/**
|
||||
* Enable password recovery via email.
|
||||
*
|
||||
* Quarkus mailer will need to be configured.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean passwordRecoveryEnabled();
|
||||
/**
|
||||
* Enable password recovery via email.
|
||||
*
|
||||
* <p>Quarkus mailer will need to be configured.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean passwordRecoveryEnabled();
|
||||
|
||||
/**
|
||||
* Message displayed in a notification at the bottom of the page.
|
||||
*/
|
||||
Optional<String> announcement();
|
||||
/**
|
||||
* The base URL of the application to use in the password recovery link in the email. We can't
|
||||
* use the URL sent by the browser of the user because of malicious Host header injection
|
||||
* attacks.
|
||||
*/
|
||||
Optional<String> passwordRecoveryPublicBaseUrl();
|
||||
|
||||
/**
|
||||
* Google Auth key for fetching Youtube channel favicons.
|
||||
*/
|
||||
Optional<String> googleAuthKey();
|
||||
/** Message displayed in a notification at the bottom of the page. */
|
||||
Optional<String> announcement();
|
||||
|
||||
/**
|
||||
* HTTP client configuration
|
||||
*/
|
||||
@ConfigDocSection
|
||||
HttpClient httpClient();
|
||||
/** Google Auth key for fetching Youtube channel favicons. */
|
||||
Optional<String> googleAuthKey();
|
||||
|
||||
/**
|
||||
* Feed refresh engine settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
FeedRefresh feedRefresh();
|
||||
/** HTTP client configuration */
|
||||
@ConfigDocSection
|
||||
HttpClient httpClient();
|
||||
|
||||
/**
|
||||
* Push notification settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
PushNotifications pushNotifications();
|
||||
/** Feed refresh engine settings. */
|
||||
@ConfigDocSection
|
||||
FeedRefresh feedRefresh();
|
||||
|
||||
/**
|
||||
* Database settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
Database database();
|
||||
/** Push notification settings. */
|
||||
@ConfigDocSection
|
||||
PushNotifications pushNotifications();
|
||||
|
||||
/**
|
||||
* Users settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
Users users();
|
||||
/** Database settings. */
|
||||
@ConfigDocSection
|
||||
Database database();
|
||||
|
||||
/**
|
||||
* Websocket settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
Websocket websocket();
|
||||
/** Users settings. */
|
||||
@ConfigDocSection
|
||||
Users users();
|
||||
|
||||
/**
|
||||
* Duration to wait for the feed refresh engine and the task scheduler to stop when the application is shutting down.
|
||||
*/
|
||||
@WithDefault("2s")
|
||||
Duration shutdownTimeout();
|
||||
/** Websocket settings. */
|
||||
@ConfigDocSection
|
||||
Websocket websocket();
|
||||
|
||||
interface HttpClient {
|
||||
/**
|
||||
* User-Agent string that will be used by the http client, leave empty for the default one.
|
||||
*/
|
||||
Optional<String> userAgent();
|
||||
/**
|
||||
* Duration to wait for the feed refresh engine and the task scheduler to stop when the
|
||||
* application is shutting down.
|
||||
*/
|
||||
@WithDefault("2s")
|
||||
Duration shutdownTimeout();
|
||||
|
||||
/**
|
||||
* Time to wait for a connection to be established.
|
||||
*/
|
||||
@WithDefault("5s")
|
||||
Duration connectTimeout();
|
||||
interface HttpClient {
|
||||
/**
|
||||
* User-Agent string that will be used by the http client, leave empty for the default one.
|
||||
*/
|
||||
Optional<String> userAgent();
|
||||
|
||||
/**
|
||||
* Time to wait for SSL handshake to complete.
|
||||
*/
|
||||
@WithDefault("5s")
|
||||
Duration sslHandshakeTimeout();
|
||||
/** Time to wait for a connection to be established. */
|
||||
@WithDefault("5s")
|
||||
Duration connectTimeout();
|
||||
|
||||
/**
|
||||
* Time to wait between two packets before timeout.
|
||||
*/
|
||||
@WithDefault("10s")
|
||||
Duration socketTimeout();
|
||||
/** Time to wait for SSL handshake to complete. */
|
||||
@WithDefault("5s")
|
||||
Duration sslHandshakeTimeout();
|
||||
|
||||
/**
|
||||
* Time to wait for the full response to be received.
|
||||
*/
|
||||
@WithDefault("10s")
|
||||
Duration responseTimeout();
|
||||
/** Time to wait between two packets before timeout. */
|
||||
@WithDefault("10s")
|
||||
Duration socketTimeout();
|
||||
|
||||
/**
|
||||
* Time to live for a connection in the pool.
|
||||
*/
|
||||
@WithDefault("30s")
|
||||
Duration connectionTimeToLive();
|
||||
/** Time to wait for the full response to be received. */
|
||||
@WithDefault("10s")
|
||||
Duration responseTimeout();
|
||||
|
||||
/**
|
||||
* Time between eviction runs for idle connections.
|
||||
*/
|
||||
@WithDefault("1m")
|
||||
Duration idleConnectionsEvictionInterval();
|
||||
/** Time to live for a connection in the pool. */
|
||||
@WithDefault("30s")
|
||||
Duration connectionTimeToLive();
|
||||
|
||||
/**
|
||||
* If a feed is larger than this, it will be discarded to prevent memory issues while parsing the feed.
|
||||
*/
|
||||
@WithDefault("5M")
|
||||
MemorySize maxResponseSize();
|
||||
/** Time between eviction runs for idle connections. */
|
||||
@WithDefault("1m")
|
||||
Duration idleConnectionsEvictionInterval();
|
||||
|
||||
/**
|
||||
* Prevent access to local addresses to mitigate server-side request forgery (SSRF) attacks, which could potentially expose internal
|
||||
* resources.
|
||||
*
|
||||
* You may want to enable this if you host a public instance of CommaFeed with registrations open.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean blockLocalAddresses();
|
||||
/**
|
||||
* If a feed is larger than this, it will be discarded to prevent memory issues while
|
||||
* parsing the feed.
|
||||
*/
|
||||
@WithDefault("5M")
|
||||
MemorySize maxResponseSize();
|
||||
|
||||
/**
|
||||
* HTTP client cache configuration
|
||||
*/
|
||||
@ConfigDocSection
|
||||
HttpClientCache cache();
|
||||
}
|
||||
/**
|
||||
* Prevent access to local addresses to mitigate server-side request forgery (SSRF) attacks,
|
||||
* which could potentially expose internal resources.
|
||||
*
|
||||
* <p>You may want to enable this if you host a public instance of CommaFeed with
|
||||
* registrations open.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean blockLocalAddresses();
|
||||
|
||||
interface HttpClientCache {
|
||||
/**
|
||||
* Whether to enable the cache. This cache is used to avoid spamming feeds in short bursts (e.g. when subscribing to a feed for the
|
||||
* first time or when clicking "fetch all my feeds now").
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
/** HTTP client cache configuration */
|
||||
@ConfigDocSection
|
||||
HttpClientCache cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum amount of memory the cache can use.
|
||||
*/
|
||||
@WithDefault("10M")
|
||||
MemorySize maximumMemorySize();
|
||||
interface HttpClientCache {
|
||||
/**
|
||||
* Whether to enable the cache. This cache is used to avoid spamming feeds in short bursts
|
||||
* (e.g. when subscribing to a feed for the first time or when clicking "fetch all my feeds
|
||||
* now").
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
|
||||
/**
|
||||
* Duration after which an entry is removed from the cache.
|
||||
*/
|
||||
@WithDefault("1m")
|
||||
Duration expiration();
|
||||
}
|
||||
/** Maximum amount of memory the cache can use. */
|
||||
@WithDefault("10M")
|
||||
MemorySize maximumMemorySize();
|
||||
|
||||
interface FeedRefresh {
|
||||
/**
|
||||
* Default amount of time CommaFeed will wait before refreshing a feed.
|
||||
*/
|
||||
@WithDefault("5m")
|
||||
Duration interval();
|
||||
/** Duration after which an entry is removed from the cache. */
|
||||
@WithDefault("1m")
|
||||
Duration expiration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum amount of time CommaFeed will wait before refreshing a feed. This is used as an upper bound when:
|
||||
*
|
||||
* <ul>
|
||||
* <li>an error occurs while refreshing a feed and we're backing off exponentially</li>
|
||||
* <li>we receive a Cache-Control header from the feed</li>
|
||||
* <li>we receive a Retry-After header from the feed</li>
|
||||
* </ul>
|
||||
*/
|
||||
@WithDefault("4h")
|
||||
Duration maxInterval();
|
||||
interface FeedRefresh {
|
||||
/** Default amount of time CommaFeed will wait before refreshing a feed. */
|
||||
@WithDefault("5m")
|
||||
Duration interval();
|
||||
|
||||
/**
|
||||
* If enabled, CommaFeed will calculate the next refresh time based on the feed's average time between entries and the time since
|
||||
* the last entry was published. The interval will be sometimes between the default refresh interval
|
||||
* (`commafeed.feed-refresh.interval`) and the maximum refresh interval (`commafeed.feed-refresh.max-interval`).
|
||||
*
|
||||
* See {@link FeedRefreshIntervalCalculator} for details.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean intervalEmpirical();
|
||||
/**
|
||||
* Maximum amount of time CommaFeed will wait before refreshing a feed. This is used as an
|
||||
* upper bound when:
|
||||
*
|
||||
* <ul>
|
||||
* <li>an error occurs while refreshing a feed and we're backing off exponentially
|
||||
* <li>we receive a Cache-Control header from the feed
|
||||
* <li>we receive a Retry-After header from the feed
|
||||
* </ul>
|
||||
*/
|
||||
@WithDefault("4h")
|
||||
Duration maxInterval();
|
||||
|
||||
/**
|
||||
* Feed refresh engine error handling settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
FeedRefreshErrorHandling errors();
|
||||
/**
|
||||
* If enabled, CommaFeed will calculate the next refresh time based on the feed's average
|
||||
* time between entries and the time since the last entry was published. The interval will
|
||||
* be sometimes between the default refresh interval (`commafeed.feed-refresh.interval`) and
|
||||
* the maximum refresh interval (`commafeed.feed-refresh.max-interval`).
|
||||
*
|
||||
* <p>See {@link FeedRefreshIntervalCalculator} for details.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean intervalEmpirical();
|
||||
|
||||
/**
|
||||
* Amount of http threads used to fetch feeds.
|
||||
*/
|
||||
@Min(1)
|
||||
@WithDefault("3")
|
||||
int httpThreads();
|
||||
/** Feed refresh engine error handling settings. */
|
||||
@ConfigDocSection
|
||||
FeedRefreshErrorHandling errors();
|
||||
|
||||
/**
|
||||
* Amount of threads used to insert new entries in the database.
|
||||
*/
|
||||
@Min(1)
|
||||
@WithDefault("1")
|
||||
int databaseThreads();
|
||||
/** Amount of http threads used to fetch feeds. */
|
||||
@Min(1)
|
||||
@WithDefault("3")
|
||||
int httpThreads();
|
||||
|
||||
/**
|
||||
* Duration after which a user is considered inactive. Feeds for inactive users are not refreshed until they log in again.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration userInactivityPeriod();
|
||||
/** Amount of threads used to insert new entries in the database. */
|
||||
@Min(1)
|
||||
@WithDefault("1")
|
||||
int databaseThreads();
|
||||
|
||||
/**
|
||||
* Duration after which the evaluation of a filtering expresion to mark an entry as read is considered to have timed out.
|
||||
*/
|
||||
@WithDefault("500ms")
|
||||
Duration filteringExpressionEvaluationTimeout();
|
||||
/**
|
||||
* Duration after which a user is considered inactive. Feeds for inactive users are not
|
||||
* refreshed until they log in again.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration userInactivityPeriod();
|
||||
|
||||
/**
|
||||
* Duration after which the "Fetch all my feeds now" action is available again after use to avoid spamming feeds.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration forceRefreshCooldownDuration();
|
||||
}
|
||||
/**
|
||||
* Duration after which the evaluation of a filtering expresion to mark an entry as read is
|
||||
* considered to have timed out.
|
||||
*/
|
||||
@WithDefault("500ms")
|
||||
Duration filteringExpressionEvaluationTimeout();
|
||||
|
||||
interface PushNotifications {
|
||||
/**
|
||||
* Whether to enable push notifications to notify users of new entries in their feeds.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
/**
|
||||
* Duration after which the "Fetch all my feeds now" action is available again after use to
|
||||
* avoid spamming feeds.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration forceRefreshCooldownDuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Amount of threads used to send external notifications about new entries.
|
||||
*/
|
||||
@Min(1)
|
||||
@WithDefault("5")
|
||||
int threads();
|
||||
interface PushNotifications {
|
||||
/** Whether to enable push notifications to notify users of new entries in their feeds. */
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
|
||||
/**
|
||||
* Maximum amount of notifications that can be queued before new notifications are discarded.
|
||||
*/
|
||||
@Min(1)
|
||||
@WithDefault("100")
|
||||
int queueCapacity();
|
||||
}
|
||||
/** Amount of threads used to send external notifications about new entries. */
|
||||
@Min(1)
|
||||
@WithDefault("5")
|
||||
int threads();
|
||||
|
||||
interface FeedRefreshErrorHandling {
|
||||
/**
|
||||
* Number of retries before backoff is applied.
|
||||
*/
|
||||
@Min(0)
|
||||
@WithDefault("3")
|
||||
int retriesBeforeBackoff();
|
||||
/**
|
||||
* Maximum amount of notifications that can be queued before new notifications are
|
||||
* discarded.
|
||||
*/
|
||||
@Min(1)
|
||||
@WithDefault("100")
|
||||
int queueCapacity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Duration to wait before retrying after an error. Will be multiplied by the number of errors since the last successful fetch.
|
||||
*/
|
||||
@WithDefault("1h")
|
||||
Duration backoffInterval();
|
||||
}
|
||||
interface FeedRefreshErrorHandling {
|
||||
/** Number of retries before backoff is applied. */
|
||||
@Min(0)
|
||||
@WithDefault("3")
|
||||
int retriesBeforeBackoff();
|
||||
|
||||
interface Database {
|
||||
/**
|
||||
* Timeout applied to all database queries.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration queryTimeout();
|
||||
/**
|
||||
* Duration to wait before retrying after an error. Will be multiplied by the number of
|
||||
* errors since the last successful fetch.
|
||||
*/
|
||||
@WithDefault("1h")
|
||||
Duration backoffInterval();
|
||||
}
|
||||
|
||||
/**
|
||||
* Database cleanup settings.
|
||||
*/
|
||||
@ConfigDocSection
|
||||
Cleanup cleanup();
|
||||
interface Database {
|
||||
/**
|
||||
* Timeout applied to all database queries.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration queryTimeout();
|
||||
|
||||
interface Cleanup {
|
||||
/**
|
||||
* Maximum age of feed entries in the database. Older entries will be deleted.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("365d")
|
||||
Duration entriesMaxAge();
|
||||
/** Database cleanup settings. */
|
||||
@ConfigDocSection
|
||||
Cleanup cleanup();
|
||||
|
||||
/**
|
||||
* Maximum age of feed entry statuses (read/unread) in the database. Older statuses will be deleted.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration statusesMaxAge();
|
||||
interface Cleanup {
|
||||
/**
|
||||
* Maximum age of feed entries in the database. Older entries will be deleted.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("365d")
|
||||
Duration entriesMaxAge();
|
||||
|
||||
/**
|
||||
* Maximum number of entries per feed to keep in the database.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("500")
|
||||
int maxFeedCapacity();
|
||||
/**
|
||||
* Maximum age of feed entry statuses (read/unread) in the database. Older statuses will
|
||||
* be deleted.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
Duration statusesMaxAge();
|
||||
|
||||
/**
|
||||
* Limit the number of feeds a user can subscribe to.
|
||||
*
|
||||
* 0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
int maxFeedsPerUser();
|
||||
/**
|
||||
* Maximum number of entries per feed to keep in the database.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("500")
|
||||
int maxFeedCapacity();
|
||||
|
||||
/**
|
||||
* Rows to delete per query while cleaning up old entries.
|
||||
*/
|
||||
@Positive
|
||||
@WithDefault("100")
|
||||
int batchSize();
|
||||
/**
|
||||
* Limit the number of feeds a user can subscribe to.
|
||||
*
|
||||
* <p>0 to disable.
|
||||
*/
|
||||
@WithDefault("0")
|
||||
int maxFeedsPerUser();
|
||||
|
||||
/**
|
||||
* Whether to keep starred entries when cleaning up old entries.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean keepStarredEntries();
|
||||
/** Rows to delete per query while cleaning up old entries. */
|
||||
@Positive
|
||||
@WithDefault("100")
|
||||
int batchSize();
|
||||
|
||||
default Instant statusesInstantThreshold() {
|
||||
return statusesMaxAge().toMillis() > 0 ? Instant.now().minus(statusesMaxAge()) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Whether to keep starred entries when cleaning up old entries. */
|
||||
@WithDefault("true")
|
||||
boolean keepStarredEntries();
|
||||
|
||||
interface Users {
|
||||
/**
|
||||
* Whether to let users create accounts for themselves.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean allowRegistrations();
|
||||
default Instant statusesInstantThreshold() {
|
||||
return statusesMaxAge().toMillis() > 0
|
||||
? Instant.now().minus(statusesMaxAge())
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum password length for user accounts.
|
||||
*/
|
||||
@WithDefault("4")
|
||||
int minimumPasswordLength();
|
||||
interface Users {
|
||||
/** Whether to let users create accounts for themselves. */
|
||||
@WithDefault("false")
|
||||
boolean allowRegistrations();
|
||||
|
||||
/**
|
||||
* Whether an email address is required when creating a user account.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean emailAddressRequired();
|
||||
/** Minimum password length for user accounts. */
|
||||
@WithDefault("4")
|
||||
int minimumPasswordLength();
|
||||
|
||||
/**
|
||||
* Whether to create a demo account the first time the app starts.
|
||||
*/
|
||||
@WithDefault("false")
|
||||
boolean createDemoAccount();
|
||||
}
|
||||
/** Whether an email address is required when creating a user account. */
|
||||
@WithDefault("false")
|
||||
boolean emailAddressRequired();
|
||||
|
||||
interface Websocket {
|
||||
/**
|
||||
* Enable websocket connection so the server can notify web clients that there are new entries for feeds.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
/** Whether to create a demo account the first time the app starts. */
|
||||
@WithDefault("false")
|
||||
boolean createDemoAccount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interval at which the client will send a ping message on the websocket to keep the connection alive.
|
||||
*/
|
||||
@WithDefault("15m")
|
||||
Duration pingInterval();
|
||||
interface Websocket {
|
||||
/**
|
||||
* Enable websocket connection so the server can notify web clients that there are new
|
||||
* entries for feeds.
|
||||
*/
|
||||
@WithDefault("true")
|
||||
boolean enabled();
|
||||
|
||||
/**
|
||||
* If the websocket connection is disabled or the connection is lost, the client will reload the feed tree at this interval.
|
||||
*/
|
||||
@WithDefault("30s")
|
||||
Duration treeReloadInterval();
|
||||
}
|
||||
/**
|
||||
* Interval at which the client will send a ping message on the websocket to keep the
|
||||
* connection alive.
|
||||
*/
|
||||
@WithDefault("15m")
|
||||
Duration pingInterval();
|
||||
|
||||
/**
|
||||
* If the websocket connection is disabled or the connection is lost, the client will reload
|
||||
* the feed tree at this interval.
|
||||
*/
|
||||
@WithDefault("30s")
|
||||
Duration treeReloadInterval();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class CommaFeedConstants {
|
||||
public static final String USERNAME_DEMO = "demo";
|
||||
public static final String USERNAME_DEMO = "demo";
|
||||
}
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
package com.commafeed;
|
||||
|
||||
import java.time.InstantSource;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
|
||||
import jakarta.enterprise.inject.Produces;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import java.time.InstantSource;
|
||||
|
||||
@Singleton
|
||||
public class CommaFeedProducers {
|
||||
|
||||
@Produces
|
||||
@Singleton
|
||||
public InstantSource instantSource() {
|
||||
return InstantSource.system();
|
||||
}
|
||||
|
||||
@Produces
|
||||
@Singleton
|
||||
public MetricRegistry metricRegistry() {
|
||||
return new MetricRegistry();
|
||||
}
|
||||
@Produces
|
||||
@Singleton
|
||||
public InstantSource instantSource() {
|
||||
return InstantSource.system();
|
||||
}
|
||||
|
||||
@Produces
|
||||
@Singleton
|
||||
public MetricRegistry metricRegistry() {
|
||||
return new MetricRegistry();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
package com.commafeed;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
@Singleton
|
||||
@Getter
|
||||
public class CommaFeedVersion {
|
||||
|
||||
private final String version;
|
||||
private final String gitCommit;
|
||||
private final String version;
|
||||
private final String gitCommit;
|
||||
|
||||
public CommaFeedVersion() throws IOException {
|
||||
Properties properties = new Properties();
|
||||
try (InputStream stream = getClass().getResourceAsStream("/git.properties")) {
|
||||
if (stream != null) {
|
||||
properties.load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
this.version = properties.getProperty("git.build.version", "unknown");
|
||||
this.gitCommit = properties.getProperty("git.commit.id.abbrev", "unknown");
|
||||
}
|
||||
public CommaFeedVersion() throws IOException {
|
||||
Properties properties = new Properties();
|
||||
try (InputStream stream = getClass().getResourceAsStream("/git.properties")) {
|
||||
if (stream != null) {
|
||||
properties.load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
this.version = properties.getProperty("git.build.version", "unknown");
|
||||
this.gitCommit = properties.getProperty("git.commit.id.abbrev", "unknown");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,58 @@
|
||||
package com.commafeed;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.validation.ValidationException;
|
||||
import jakarta.ws.rs.core.NewCookie;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
|
||||
import org.jboss.resteasy.reactive.RestResponse;
|
||||
import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder;
|
||||
import org.jboss.resteasy.reactive.RestResponse.Status;
|
||||
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
|
||||
|
||||
import com.commafeed.security.CookieService;
|
||||
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import io.quarkus.security.AuthenticationFailedException;
|
||||
import io.quarkus.security.UnauthorizedException;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.validation.ValidationException;
|
||||
import jakarta.ws.rs.core.NewCookie;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.jboss.resteasy.reactive.RestResponse;
|
||||
import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder;
|
||||
import org.jboss.resteasy.reactive.RestResponse.Status;
|
||||
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Provider
|
||||
@Priority(1)
|
||||
public class ExceptionMappers {
|
||||
|
||||
private final CookieService cookieService;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final CookieService cookieService;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
@ServerExceptionMapper(UnauthorizedException.class)
|
||||
public RestResponse<UnauthorizedResponse> unauthorized(UnauthorizedException e) {
|
||||
return RestResponse.status(Status.UNAUTHORIZED, new UnauthorizedResponse(e.getMessage(), config.users().allowRegistrations()));
|
||||
}
|
||||
@ServerExceptionMapper(UnauthorizedException.class)
|
||||
public RestResponse<UnauthorizedResponse> unauthorized(UnauthorizedException e) {
|
||||
return RestResponse.status(
|
||||
Status.UNAUTHORIZED,
|
||||
new UnauthorizedResponse(e.getMessage(), config.users().allowRegistrations()));
|
||||
}
|
||||
|
||||
@ServerExceptionMapper(AuthenticationFailedException.class)
|
||||
public RestResponse<AuthenticationFailed> authenticationFailed(AuthenticationFailedException e) {
|
||||
NewCookie logoutCookie = cookieService.buildLogoutCookie();
|
||||
return ResponseBuilder.create(Status.UNAUTHORIZED, new AuthenticationFailed(e.getMessage())).cookie(logoutCookie).build();
|
||||
}
|
||||
@ServerExceptionMapper(AuthenticationFailedException.class)
|
||||
public RestResponse<AuthenticationFailed> authenticationFailed(
|
||||
AuthenticationFailedException e) {
|
||||
NewCookie logoutCookie = cookieService.buildLogoutCookie();
|
||||
return ResponseBuilder.create(Status.UNAUTHORIZED, new AuthenticationFailed(e.getMessage()))
|
||||
.cookie(logoutCookie)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ServerExceptionMapper(ValidationException.class)
|
||||
public RestResponse<ValidationFailed> validationFailed(ValidationException e) {
|
||||
return RestResponse.status(Status.BAD_REQUEST, new ValidationFailed(e.getMessage()));
|
||||
}
|
||||
@ServerExceptionMapper(ValidationException.class)
|
||||
public RestResponse<ValidationFailed> validationFailed(ValidationException e) {
|
||||
return RestResponse.status(Status.BAD_REQUEST, new ValidationFailed(e.getMessage()));
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record UnauthorizedResponse(String message, boolean allowRegistrations) {
|
||||
}
|
||||
@RegisterForReflection
|
||||
public record UnauthorizedResponse(String message, boolean allowRegistrations) {}
|
||||
|
||||
@RegisterForReflection
|
||||
public record AuthenticationFailed(String message) {
|
||||
}
|
||||
@RegisterForReflection
|
||||
public record AuthenticationFailed(String message) {}
|
||||
|
||||
@RegisterForReflection
|
||||
public record ValidationFailed(String message) {
|
||||
}
|
||||
@RegisterForReflection
|
||||
public record ValidationFailed(String message) {}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package com.commafeed;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import com.codahale.metrics.json.MetricsModule;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -12,18 +8,23 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
import io.quarkus.jackson.ObjectMapperCustomizer;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Singleton
|
||||
public class JacksonCustomizer implements ObjectMapperCustomizer {
|
||||
@Override
|
||||
public void customize(ObjectMapper objectMapper) {
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
@Override
|
||||
public void customize(ObjectMapper objectMapper) {
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
|
||||
// read and write instants as milliseconds instead of nanoseconds
|
||||
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true)
|
||||
.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
|
||||
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
|
||||
// read and write instants as milliseconds instead of nanoseconds
|
||||
objectMapper
|
||||
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true)
|
||||
.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
|
||||
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
|
||||
|
||||
// add support for serializing metrics
|
||||
objectMapper.registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false));
|
||||
}
|
||||
// add support for serializing metrics
|
||||
objectMapper.registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,217 +10,340 @@ import com.codahale.metrics.Timer;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
|
||||
@RegisterForReflection(
|
||||
targets = {
|
||||
// metrics
|
||||
MetricRegistry.class, Meter.class, Gauge.class, Counter.class, Timer.class, Histogram.class,
|
||||
targets = {
|
||||
// metrics
|
||||
MetricRegistry.class,
|
||||
Meter.class,
|
||||
Gauge.class,
|
||||
Counter.class,
|
||||
Timer.class,
|
||||
Histogram.class,
|
||||
|
||||
// rome
|
||||
java.util.Date.class, com.rometools.opml.feed.synd.impl.TreeCategoryImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndFeedImpl.class, com.rometools.rome.feed.module.DCSubjectImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndEntryImpl.class, com.rometools.modules.psc.types.SimpleChapter.class,
|
||||
com.rometools.rome.feed.synd.SyndCategoryImpl.class, com.rometools.rome.feed.synd.SyndImageImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndContentImpl.class, com.rometools.rome.feed.synd.SyndEnclosureImpl.class,
|
||||
// rome
|
||||
java.util.Date.class,
|
||||
com.rometools.opml.feed.synd.impl.TreeCategoryImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndFeedImpl.class,
|
||||
com.rometools.rome.feed.module.DCSubjectImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndEntryImpl.class,
|
||||
com.rometools.modules.psc.types.SimpleChapter.class,
|
||||
com.rometools.rome.feed.synd.SyndCategoryImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndImageImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndContentImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndEnclosureImpl.class,
|
||||
|
||||
// rome cloneable
|
||||
com.rometools.modules.activitystreams.types.Article.class, com.rometools.modules.activitystreams.types.Audio.class,
|
||||
com.rometools.modules.activitystreams.types.Bookmark.class, com.rometools.modules.activitystreams.types.Comment.class,
|
||||
com.rometools.modules.activitystreams.types.Event.class, com.rometools.modules.activitystreams.types.File.class,
|
||||
com.rometools.modules.activitystreams.types.Folder.class, com.rometools.modules.activitystreams.types.List.class,
|
||||
com.rometools.modules.activitystreams.types.Note.class, com.rometools.modules.activitystreams.types.Person.class,
|
||||
com.rometools.modules.activitystreams.types.Photo.class, com.rometools.modules.activitystreams.types.PhotoAlbum.class,
|
||||
com.rometools.modules.activitystreams.types.Place.class, com.rometools.modules.activitystreams.types.Playlist.class,
|
||||
com.rometools.modules.activitystreams.types.Product.class, com.rometools.modules.activitystreams.types.Review.class,
|
||||
com.rometools.modules.activitystreams.types.Service.class, com.rometools.modules.activitystreams.types.Song.class,
|
||||
com.rometools.modules.activitystreams.types.Status.class, com.rometools.modules.base.types.DateTimeRange.class,
|
||||
com.rometools.modules.base.types.FloatUnit.class, com.rometools.modules.base.types.GenderEnumeration.class,
|
||||
com.rometools.modules.base.types.IntUnit.class, com.rometools.modules.base.types.PriceTypeEnumeration.class,
|
||||
com.rometools.modules.base.types.ShippingType.class, com.rometools.modules.base.types.ShortDate.class,
|
||||
com.rometools.modules.base.types.Size.class, com.rometools.modules.base.types.YearType.class,
|
||||
com.rometools.modules.content.ContentItem.class, com.rometools.modules.georss.GeoRSSPoint.class,
|
||||
com.rometools.modules.georss.geometries.Envelope.class, com.rometools.modules.georss.geometries.LineString.class,
|
||||
com.rometools.modules.georss.geometries.LinearRing.class, com.rometools.modules.georss.geometries.Point.class,
|
||||
com.rometools.modules.georss.geometries.Polygon.class, com.rometools.modules.georss.geometries.Position.class,
|
||||
com.rometools.modules.georss.geometries.PositionList.class, com.rometools.modules.mediarss.types.MediaGroup.class,
|
||||
com.rometools.modules.mediarss.types.Metadata.class, com.rometools.modules.mediarss.types.Thumbnail.class,
|
||||
com.rometools.modules.opensearch.entity.OSQuery.class, com.rometools.modules.photocast.types.PhotoDate.class,
|
||||
com.rometools.modules.sle.types.DateValue.class, com.rometools.modules.sle.types.Group.class,
|
||||
com.rometools.modules.sle.types.NumberValue.class, com.rometools.modules.sle.types.Sort.class,
|
||||
com.rometools.modules.sle.types.StringValue.class, com.rometools.modules.yahooweather.types.Astronomy.class,
|
||||
com.rometools.modules.yahooweather.types.Atmosphere.class, com.rometools.modules.yahooweather.types.Condition.class,
|
||||
com.rometools.modules.yahooweather.types.Forecast.class, com.rometools.modules.yahooweather.types.Location.class,
|
||||
com.rometools.modules.yahooweather.types.Units.class, com.rometools.modules.yahooweather.types.Wind.class,
|
||||
com.rometools.opml.feed.opml.Attribute.class, com.rometools.opml.feed.opml.Opml.class,
|
||||
com.rometools.opml.feed.opml.Outline.class, com.rometools.rome.feed.atom.Category.class,
|
||||
com.rometools.rome.feed.atom.Content.class, com.rometools.rome.feed.atom.Entry.class,
|
||||
com.rometools.rome.feed.atom.Feed.class, com.rometools.rome.feed.atom.Generator.class,
|
||||
com.rometools.rome.feed.atom.Link.class, com.rometools.rome.feed.atom.Person.class,
|
||||
com.rometools.rome.feed.rss.Category.class, com.rometools.rome.feed.rss.Channel.class,
|
||||
com.rometools.rome.feed.rss.Cloud.class, com.rometools.rome.feed.rss.Content.class,
|
||||
com.rometools.rome.feed.rss.Description.class, com.rometools.rome.feed.rss.Enclosure.class,
|
||||
com.rometools.rome.feed.rss.Guid.class, com.rometools.rome.feed.rss.Image.class, com.rometools.rome.feed.rss.Item.class,
|
||||
com.rometools.rome.feed.rss.Source.class, com.rometools.rome.feed.rss.TextInput.class,
|
||||
com.rometools.rome.feed.synd.SyndLinkImpl.class, com.rometools.rome.feed.synd.SyndPersonImpl.class,
|
||||
java.util.ArrayList.class,
|
||||
// rome cloneable
|
||||
com.rometools.modules.activitystreams.types.Article.class,
|
||||
com.rometools.modules.activitystreams.types.Audio.class,
|
||||
com.rometools.modules.activitystreams.types.Bookmark.class,
|
||||
com.rometools.modules.activitystreams.types.Comment.class,
|
||||
com.rometools.modules.activitystreams.types.Event.class,
|
||||
com.rometools.modules.activitystreams.types.File.class,
|
||||
com.rometools.modules.activitystreams.types.Folder.class,
|
||||
com.rometools.modules.activitystreams.types.List.class,
|
||||
com.rometools.modules.activitystreams.types.Note.class,
|
||||
com.rometools.modules.activitystreams.types.Person.class,
|
||||
com.rometools.modules.activitystreams.types.Photo.class,
|
||||
com.rometools.modules.activitystreams.types.PhotoAlbum.class,
|
||||
com.rometools.modules.activitystreams.types.Place.class,
|
||||
com.rometools.modules.activitystreams.types.Playlist.class,
|
||||
com.rometools.modules.activitystreams.types.Product.class,
|
||||
com.rometools.modules.activitystreams.types.Review.class,
|
||||
com.rometools.modules.activitystreams.types.Service.class,
|
||||
com.rometools.modules.activitystreams.types.Song.class,
|
||||
com.rometools.modules.activitystreams.types.Status.class,
|
||||
com.rometools.modules.base.types.DateTimeRange.class,
|
||||
com.rometools.modules.base.types.FloatUnit.class,
|
||||
com.rometools.modules.base.types.GenderEnumeration.class,
|
||||
com.rometools.modules.base.types.IntUnit.class,
|
||||
com.rometools.modules.base.types.PriceTypeEnumeration.class,
|
||||
com.rometools.modules.base.types.ShippingType.class,
|
||||
com.rometools.modules.base.types.ShortDate.class,
|
||||
com.rometools.modules.base.types.Size.class,
|
||||
com.rometools.modules.base.types.YearType.class,
|
||||
com.rometools.modules.content.ContentItem.class,
|
||||
com.rometools.modules.georss.GeoRSSPoint.class,
|
||||
com.rometools.modules.georss.geometries.Envelope.class,
|
||||
com.rometools.modules.georss.geometries.LineString.class,
|
||||
com.rometools.modules.georss.geometries.LinearRing.class,
|
||||
com.rometools.modules.georss.geometries.Point.class,
|
||||
com.rometools.modules.georss.geometries.Polygon.class,
|
||||
com.rometools.modules.georss.geometries.Position.class,
|
||||
com.rometools.modules.georss.geometries.PositionList.class,
|
||||
com.rometools.modules.mediarss.types.MediaGroup.class,
|
||||
com.rometools.modules.mediarss.types.Metadata.class,
|
||||
com.rometools.modules.mediarss.types.Thumbnail.class,
|
||||
com.rometools.modules.opensearch.entity.OSQuery.class,
|
||||
com.rometools.modules.photocast.types.PhotoDate.class,
|
||||
com.rometools.modules.sle.types.DateValue.class,
|
||||
com.rometools.modules.sle.types.Group.class,
|
||||
com.rometools.modules.sle.types.NumberValue.class,
|
||||
com.rometools.modules.sle.types.Sort.class,
|
||||
com.rometools.modules.sle.types.StringValue.class,
|
||||
com.rometools.modules.yahooweather.types.Astronomy.class,
|
||||
com.rometools.modules.yahooweather.types.Atmosphere.class,
|
||||
com.rometools.modules.yahooweather.types.Condition.class,
|
||||
com.rometools.modules.yahooweather.types.Forecast.class,
|
||||
com.rometools.modules.yahooweather.types.Location.class,
|
||||
com.rometools.modules.yahooweather.types.Units.class,
|
||||
com.rometools.modules.yahooweather.types.Wind.class,
|
||||
com.rometools.opml.feed.opml.Attribute.class,
|
||||
com.rometools.opml.feed.opml.Opml.class,
|
||||
com.rometools.opml.feed.opml.Outline.class,
|
||||
com.rometools.rome.feed.atom.Category.class,
|
||||
com.rometools.rome.feed.atom.Content.class,
|
||||
com.rometools.rome.feed.atom.Entry.class,
|
||||
com.rometools.rome.feed.atom.Feed.class,
|
||||
com.rometools.rome.feed.atom.Generator.class,
|
||||
com.rometools.rome.feed.atom.Link.class,
|
||||
com.rometools.rome.feed.atom.Person.class,
|
||||
com.rometools.rome.feed.rss.Category.class,
|
||||
com.rometools.rome.feed.rss.Channel.class,
|
||||
com.rometools.rome.feed.rss.Cloud.class,
|
||||
com.rometools.rome.feed.rss.Content.class,
|
||||
com.rometools.rome.feed.rss.Description.class,
|
||||
com.rometools.rome.feed.rss.Enclosure.class,
|
||||
com.rometools.rome.feed.rss.Guid.class,
|
||||
com.rometools.rome.feed.rss.Image.class,
|
||||
com.rometools.rome.feed.rss.Item.class,
|
||||
com.rometools.rome.feed.rss.Source.class,
|
||||
com.rometools.rome.feed.rss.TextInput.class,
|
||||
com.rometools.rome.feed.synd.SyndLinkImpl.class,
|
||||
com.rometools.rome.feed.synd.SyndPersonImpl.class,
|
||||
java.util.ArrayList.class,
|
||||
|
||||
// rome modules
|
||||
com.rometools.modules.sse.modules.Conflict.class, com.rometools.modules.sse.modules.Conflicts.class,
|
||||
com.rometools.modules.cc.CreativeCommonsImpl.class, com.rometools.modules.feedpress.modules.FeedpressModuleImpl.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleImpl.class, com.rometools.modules.sse.modules.Sharing.class,
|
||||
com.rometools.modules.georss.SimpleModuleImpl.class, com.rometools.modules.atom.modules.AtomLinkModuleImpl.class,
|
||||
com.rometools.modules.itunes.EntryInformationImpl.class, com.rometools.modules.sse.modules.Update.class,
|
||||
com.rometools.modules.photocast.PhotocastModuleImpl.class, com.rometools.modules.itunes.FeedInformationImpl.class,
|
||||
com.rometools.modules.yahooweather.YWeatherModuleImpl.class, com.rometools.modules.feedburner.FeedBurnerImpl.class,
|
||||
com.rometools.modules.sse.modules.Related.class, com.rometools.modules.fyyd.modules.FyydModuleImpl.class,
|
||||
com.rometools.modules.psc.modules.PodloveSimpleChapterModuleImpl.class, com.rometools.modules.thr.ThreadingModuleImpl.class,
|
||||
com.rometools.modules.sse.modules.Sync.class, com.rometools.modules.sle.SimpleListExtensionImpl.class,
|
||||
com.rometools.modules.slash.SlashImpl.class, com.rometools.modules.sse.modules.History.class,
|
||||
com.rometools.modules.georss.GMLModuleImpl.class, com.rometools.modules.base.CustomTagsImpl.class,
|
||||
com.rometools.modules.base.GoogleBaseImpl.class, com.rometools.modules.sle.SleEntryImpl.class,
|
||||
com.rometools.modules.mediarss.MediaEntryModuleImpl.class, com.rometools.modules.content.ContentModuleImpl.class,
|
||||
com.rometools.modules.georss.W3CGeoModuleImpl.class, com.rometools.rome.feed.module.DCModuleImpl.class,
|
||||
com.rometools.modules.mediarss.MediaModuleImpl.class, com.rometools.rome.feed.module.SyModuleImpl.class,
|
||||
// rome modules
|
||||
com.rometools.modules.sse.modules.Conflict.class,
|
||||
com.rometools.modules.sse.modules.Conflicts.class,
|
||||
com.rometools.modules.cc.CreativeCommonsImpl.class,
|
||||
com.rometools.modules.feedpress.modules.FeedpressModuleImpl.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleImpl.class,
|
||||
com.rometools.modules.sse.modules.Sharing.class,
|
||||
com.rometools.modules.georss.SimpleModuleImpl.class,
|
||||
com.rometools.modules.atom.modules.AtomLinkModuleImpl.class,
|
||||
com.rometools.modules.itunes.EntryInformationImpl.class,
|
||||
com.rometools.modules.sse.modules.Update.class,
|
||||
com.rometools.modules.photocast.PhotocastModuleImpl.class,
|
||||
com.rometools.modules.itunes.FeedInformationImpl.class,
|
||||
com.rometools.modules.yahooweather.YWeatherModuleImpl.class,
|
||||
com.rometools.modules.feedburner.FeedBurnerImpl.class,
|
||||
com.rometools.modules.sse.modules.Related.class,
|
||||
com.rometools.modules.fyyd.modules.FyydModuleImpl.class,
|
||||
com.rometools.modules.psc.modules.PodloveSimpleChapterModuleImpl.class,
|
||||
com.rometools.modules.thr.ThreadingModuleImpl.class,
|
||||
com.rometools.modules.sse.modules.Sync.class,
|
||||
com.rometools.modules.sle.SimpleListExtensionImpl.class,
|
||||
com.rometools.modules.slash.SlashImpl.class,
|
||||
com.rometools.modules.sse.modules.History.class,
|
||||
com.rometools.modules.georss.GMLModuleImpl.class,
|
||||
com.rometools.modules.base.CustomTagsImpl.class,
|
||||
com.rometools.modules.base.GoogleBaseImpl.class,
|
||||
com.rometools.modules.sle.SleEntryImpl.class,
|
||||
com.rometools.modules.mediarss.MediaEntryModuleImpl.class,
|
||||
com.rometools.modules.content.ContentModuleImpl.class,
|
||||
com.rometools.modules.georss.W3CGeoModuleImpl.class,
|
||||
com.rometools.rome.feed.module.DCModuleImpl.class,
|
||||
com.rometools.modules.mediarss.MediaModuleImpl.class,
|
||||
com.rometools.rome.feed.module.SyModuleImpl.class,
|
||||
|
||||
// extracted from all 3 rome.properties files of rome library
|
||||
com.rometools.rome.io.impl.RSS090Parser.class, com.rometools.rome.io.impl.RSS091NetscapeParser.class,
|
||||
com.rometools.rome.io.impl.RSS091UserlandParser.class, com.rometools.rome.io.impl.RSS092Parser.class,
|
||||
com.rometools.rome.io.impl.RSS093Parser.class, com.rometools.rome.io.impl.RSS094Parser.class,
|
||||
com.rometools.rome.io.impl.RSS10Parser.class, com.rometools.rome.io.impl.RSS20wNSParser.class,
|
||||
com.rometools.rome.io.impl.RSS20Parser.class, com.rometools.rome.io.impl.Atom10Parser.class,
|
||||
com.rometools.rome.io.impl.Atom03Parser.class,
|
||||
|
||||
com.rometools.rome.io.impl.SyModuleParser.class, com.rometools.rome.io.impl.DCModuleParser.class,
|
||||
|
||||
com.rometools.rome.io.impl.RSS090Generator.class, com.rometools.rome.io.impl.RSS091NetscapeGenerator.class,
|
||||
com.rometools.rome.io.impl.RSS091UserlandGenerator.class, com.rometools.rome.io.impl.RSS092Generator.class,
|
||||
com.rometools.rome.io.impl.RSS093Generator.class, com.rometools.rome.io.impl.RSS094Generator.class,
|
||||
com.rometools.rome.io.impl.RSS10Generator.class, com.rometools.rome.io.impl.RSS20Generator.class,
|
||||
com.rometools.rome.io.impl.Atom10Generator.class, com.rometools.rome.io.impl.Atom03Generator.class,
|
||||
|
||||
com.rometools.rome.feed.synd.impl.ConverterForAtom10.class, com.rometools.rome.feed.synd.impl.ConverterForAtom03.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS090.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS091Netscape.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS091Userland.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS092.class, com.rometools.rome.feed.synd.impl.ConverterForRSS093.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS094.class, com.rometools.rome.feed.synd.impl.ConverterForRSS10.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS20.class,
|
||||
|
||||
com.rometools.modules.mediarss.io.RSS20YahooParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class, com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class, com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class, com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class, com.rometools.modules.atom.io.AtomModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class, com.rometools.modules.sle.io.ModuleParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class, com.rometools.modules.feedpress.io.FeedpressParser.class,
|
||||
com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class, com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class, com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class, com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class, com.rometools.modules.atom.io.AtomModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class, com.rometools.modules.sle.io.ModuleParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class, com.rometools.modules.feedpress.io.FeedpressParser.class,
|
||||
com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS1.class, com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class, com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class, com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class, com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class, com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressParser.class, com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class, com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class, com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.atom.io.AtomModuleParser.class, com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class, com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class, com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class, com.rometools.modules.sle.io.ItemParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS1.class, com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class, com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class, com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class, com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class, com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class, com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class, com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class, com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class, com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.thr.io.ThreadingModuleParser.class, com.rometools.modules.psc.io.PodloveSimpleChapterParser.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.itunes.io.ITunesGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class, com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class, com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class, com.rometools.modules.atom.io.AtomModuleGenerator.class,
|
||||
com.rometools.modules.sle.io.ModuleGenerator.class, com.rometools.modules.yahooweather.io.WeatherModuleGenerator.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressGenerator.class, com.rometools.modules.fyyd.io.FyydGenerator.class,
|
||||
|
||||
com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class, com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class, com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class, com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class, com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressGenerator.class, com.rometools.modules.fyyd.io.FyydGenerator.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.base.io.GoogleBaseGenerator.class,
|
||||
com.rometools.modules.base.io.CustomTagGenerator.class, com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class, com.rometools.modules.itunes.io.ITunesGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class, com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class, com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class, com.rometools.modules.atom.io.AtomModuleGenerator.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleGenerator.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterGenerator.class,
|
||||
|
||||
com.rometools.modules.base.io.GoogleBaseGenerator.class, com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.base.io.GoogleBaseGenerator.class,
|
||||
com.rometools.modules.base.io.CustomTagGenerator.class, com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class, com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class, com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class, com.rometools.modules.base.io.CustomTagGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class, com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class, com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class, com.rometools.modules.thr.io.ThreadingModuleGenerator.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterGenerator.class,
|
||||
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
|
||||
com.rometools.opml.io.impl.OPML10Generator.class, com.rometools.opml.io.impl.OPML20Generator.class,
|
||||
|
||||
com.rometools.opml.io.impl.OPML10Parser.class, com.rometools.opml.io.impl.OPML20Parser.class,
|
||||
|
||||
com.rometools.opml.feed.synd.impl.ConverterForOPML10.class, com.rometools.opml.feed.synd.impl.ConverterForOPML20.class, })
|
||||
|
||||
public class NativeImageClasses {
|
||||
}
|
||||
// extracted from all 3 rome.properties files of rome library
|
||||
com.rometools.rome.io.impl.RSS090Parser.class,
|
||||
com.rometools.rome.io.impl.RSS091NetscapeParser.class,
|
||||
com.rometools.rome.io.impl.RSS091UserlandParser.class,
|
||||
com.rometools.rome.io.impl.RSS092Parser.class,
|
||||
com.rometools.rome.io.impl.RSS093Parser.class,
|
||||
com.rometools.rome.io.impl.RSS094Parser.class,
|
||||
com.rometools.rome.io.impl.RSS10Parser.class,
|
||||
com.rometools.rome.io.impl.RSS20wNSParser.class,
|
||||
com.rometools.rome.io.impl.RSS20Parser.class,
|
||||
com.rometools.rome.io.impl.Atom10Parser.class,
|
||||
com.rometools.rome.io.impl.Atom03Parser.class,
|
||||
com.rometools.rome.io.impl.SyModuleParser.class,
|
||||
com.rometools.rome.io.impl.DCModuleParser.class,
|
||||
com.rometools.rome.io.impl.RSS090Generator.class,
|
||||
com.rometools.rome.io.impl.RSS091NetscapeGenerator.class,
|
||||
com.rometools.rome.io.impl.RSS091UserlandGenerator.class,
|
||||
com.rometools.rome.io.impl.RSS092Generator.class,
|
||||
com.rometools.rome.io.impl.RSS093Generator.class,
|
||||
com.rometools.rome.io.impl.RSS094Generator.class,
|
||||
com.rometools.rome.io.impl.RSS10Generator.class,
|
||||
com.rometools.rome.io.impl.RSS20Generator.class,
|
||||
com.rometools.rome.io.impl.Atom10Generator.class,
|
||||
com.rometools.rome.io.impl.Atom03Generator.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForAtom10.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForAtom03.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS090.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS091Netscape.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS091Userland.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS092.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS093.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS094.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS10.class,
|
||||
com.rometools.rome.feed.synd.impl.ConverterForRSS20.class,
|
||||
com.rometools.modules.mediarss.io.RSS20YahooParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.atom.io.AtomModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.sle.io.ModuleParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressParser.class,
|
||||
com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.atom.io.AtomModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.sle.io.ModuleParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressParser.class,
|
||||
com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS1.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressParser.class,
|
||||
com.rometools.modules.fyyd.io.FyydParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.atom.io.AtomModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.itunes.io.ITunesParserOldNamespace.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.sle.io.ItemParser.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleParser.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS1.class,
|
||||
com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class,
|
||||
com.rometools.modules.content.io.ContentModuleParser.class,
|
||||
com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class,
|
||||
com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.cc.io.ModuleParserRSS2.class,
|
||||
com.rometools.modules.base.io.GoogleBaseParser.class,
|
||||
com.rometools.modules.base.io.CustomTagParser.class,
|
||||
com.rometools.modules.slash.io.SlashModuleParser.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleParser.class,
|
||||
com.rometools.modules.georss.SimpleParser.class,
|
||||
com.rometools.modules.georss.W3CGeoParser.class,
|
||||
com.rometools.modules.photocast.io.Parser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.AlternateMediaModuleParser.class,
|
||||
com.rometools.modules.thr.io.ThreadingModuleParser.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterParser.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.itunes.io.ITunesGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.atom.io.AtomModuleGenerator.class,
|
||||
com.rometools.modules.sle.io.ModuleGenerator.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleGenerator.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressGenerator.class,
|
||||
com.rometools.modules.fyyd.io.FyydGenerator.class,
|
||||
com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.feedpress.io.FeedpressGenerator.class,
|
||||
com.rometools.modules.fyyd.io.FyydGenerator.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.base.io.GoogleBaseGenerator.class,
|
||||
com.rometools.modules.base.io.CustomTagGenerator.class,
|
||||
com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.itunes.io.ITunesGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.atom.io.AtomModuleGenerator.class,
|
||||
com.rometools.modules.yahooweather.io.WeatherModuleGenerator.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterGenerator.class,
|
||||
com.rometools.modules.base.io.GoogleBaseGenerator.class,
|
||||
com.rometools.modules.content.io.ContentModuleGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.base.io.GoogleBaseGenerator.class,
|
||||
com.rometools.modules.base.io.CustomTagGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.cc.io.CCModuleGenerator.class,
|
||||
com.rometools.modules.base.io.CustomTagGenerator.class,
|
||||
com.rometools.modules.slash.io.SlashModuleGenerator.class,
|
||||
com.rometools.modules.opensearch.impl.OpenSearchModuleGenerator.class,
|
||||
com.rometools.modules.georss.SimpleGenerator.class,
|
||||
com.rometools.modules.georss.W3CGeoGenerator.class,
|
||||
com.rometools.modules.photocast.io.Generator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.modules.thr.io.ThreadingModuleGenerator.class,
|
||||
com.rometools.modules.psc.io.PodloveSimpleChapterGenerator.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleParser.class,
|
||||
com.rometools.modules.mediarss.io.MediaModuleGenerator.class,
|
||||
com.rometools.opml.io.impl.OPML10Generator.class,
|
||||
com.rometools.opml.io.impl.OPML20Generator.class,
|
||||
com.rometools.opml.io.impl.OPML10Parser.class,
|
||||
com.rometools.opml.io.impl.OPML20Parser.class,
|
||||
com.rometools.opml.feed.synd.impl.ConverterForOPML10.class,
|
||||
com.rometools.opml.feed.synd.impl.ConverterForOPML20.class,
|
||||
})
|
||||
public class NativeImageClasses {}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
package com.commafeed.backend;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import com.google.common.hash.HashFunction;
|
||||
import com.google.common.hash.Hashing;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@UtilityClass
|
||||
@SuppressWarnings("deprecation")
|
||||
public class Digests {
|
||||
|
||||
public static String sha1Hex(byte[] input) {
|
||||
return hashBytesToHex(Hashing.sha1(), input);
|
||||
}
|
||||
public static String sha1Hex(byte[] input) {
|
||||
return hashBytesToHex(Hashing.sha1(), input);
|
||||
}
|
||||
|
||||
public static String sha1Hex(String input) {
|
||||
return hashBytesToHex(Hashing.sha1(), input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
public static String sha1Hex(String input) {
|
||||
return hashBytesToHex(Hashing.sha1(), input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String md5Hex(String input) {
|
||||
return hashBytesToHex(Hashing.md5(), input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
public static String md5Hex(String input) {
|
||||
return hashBytesToHex(Hashing.md5(), input.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private static String hashBytesToHex(HashFunction function, byte[] input) {
|
||||
return function.hashBytes(input).toString();
|
||||
}
|
||||
private static String hashBytesToHex(HashFunction function, byte[] input) {
|
||||
return function.hashBytes(input).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package com.commafeed.backend;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.SequencedMap;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedVersion;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
import inet.ipaddr.IPAddress;
|
||||
import inet.ipaddr.IPAddressNetwork;
|
||||
import inet.ipaddr.IPAddressString;
|
||||
import inet.ipaddr.ipv4.IPv4Address;
|
||||
import inet.ipaddr.ipv6.IPv6Address;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import nl.altindag.ssl.SSLFactory;
|
||||
import nl.altindag.ssl.apache5.util.Apache5SslUtils;
|
||||
|
||||
import org.apache.hc.client5.http.DnsResolver;
|
||||
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
||||
import org.apache.hc.client5.http.config.ConnectionConfig;
|
||||
@@ -35,132 +41,187 @@ import org.apache.hc.core5.util.TimeValue;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.brotli.dec.BrotliInputStream;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedVersion;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import nl.altindag.ssl.SSLFactory;
|
||||
import nl.altindag.ssl.apache5.util.Apache5SslUtils;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.SequencedMap;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
@Singleton
|
||||
@RequiredArgsConstructor
|
||||
public class HttpClientFactory {
|
||||
|
||||
private static final DnsResolver DNS_RESOLVER = SystemDefaultDnsResolver.INSTANCE;
|
||||
private static final DnsResolver DNS_RESOLVER = SystemDefaultDnsResolver.INSTANCE;
|
||||
private static final IPAddress CGNAT_RANGE = new IPAddressString("100.64.0.0/10").getAddress();
|
||||
|
||||
private final CommaFeedConfiguration config;
|
||||
private final CommaFeedVersion version;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final CommaFeedVersion version;
|
||||
|
||||
public CloseableHttpClient newClient(int poolSize) {
|
||||
PoolingHttpClientConnectionManager connectionManager = newConnectionManager(config, poolSize);
|
||||
String userAgent = config.httpClient()
|
||||
.userAgent()
|
||||
.orElseGet(() -> String.format("CommaFeed/%s (https://github.com/Athou/commafeed)", version.getVersion()));
|
||||
return newClient(config, connectionManager, userAgent);
|
||||
}
|
||||
public CloseableHttpClient newClient(int poolSize) {
|
||||
PoolingHttpClientConnectionManager connectionManager =
|
||||
newConnectionManager(config, poolSize);
|
||||
String userAgent =
|
||||
config.httpClient()
|
||||
.userAgent()
|
||||
.orElseGet(
|
||||
() ->
|
||||
String.format(
|
||||
"CommaFeed/%s (https://github.com/Athou/commafeed)",
|
||||
version.getVersion()));
|
||||
return newClient(config, connectionManager, userAgent);
|
||||
}
|
||||
|
||||
private CloseableHttpClient newClient(CommaFeedConfiguration config, HttpClientConnectionManager connectionManager, String userAgent) {
|
||||
List<Header> headers = new ArrayList<>();
|
||||
headers.add(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en"));
|
||||
headers.add(new BasicHeader(HttpHeaders.PRAGMA, "No-cache"));
|
||||
headers.add(new BasicHeader(HttpHeaders.CACHE_CONTROL, "no-cache"));
|
||||
private CloseableHttpClient newClient(
|
||||
CommaFeedConfiguration config,
|
||||
HttpClientConnectionManager connectionManager,
|
||||
String userAgent) {
|
||||
List<Header> headers = new ArrayList<>();
|
||||
headers.add(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en"));
|
||||
headers.add(new BasicHeader(HttpHeaders.PRAGMA, "No-cache"));
|
||||
headers.add(new BasicHeader(HttpHeaders.CACHE_CONTROL, "no-cache"));
|
||||
|
||||
SequencedMap<String, InputStreamFactory> contentDecoderMap = new LinkedHashMap<>();
|
||||
contentDecoderMap.put(ContentCoding.GZIP.token(), GZIPInputStream::new);
|
||||
contentDecoderMap.put(ContentCoding.DEFLATE.token(), DeflateInputStream::new);
|
||||
contentDecoderMap.put(ContentCoding.BROTLI.token(), BrotliInputStream::new);
|
||||
SequencedMap<String, InputStreamFactory> contentDecoderMap = new LinkedHashMap<>();
|
||||
contentDecoderMap.put(ContentCoding.GZIP.token(), GZIPInputStream::new);
|
||||
contentDecoderMap.put(ContentCoding.DEFLATE.token(), DeflateInputStream::new);
|
||||
contentDecoderMap.put(ContentCoding.BROTLI.token(), BrotliInputStream::new);
|
||||
|
||||
RedirectStrategy redirectStrategy = config.httpClient().blockLocalAddresses()
|
||||
? new BlockLocalAddressesRedirectStrategy(DNS_RESOLVER)
|
||||
: new DefaultRedirectStrategy();
|
||||
RedirectStrategy redirectStrategy =
|
||||
config.httpClient().blockLocalAddresses()
|
||||
? new BlockLocalAddressesRedirectStrategy(DNS_RESOLVER)
|
||||
: new DefaultRedirectStrategy();
|
||||
|
||||
return HttpClientBuilder.create()
|
||||
.useSystemProperties()
|
||||
.disableAutomaticRetries()
|
||||
.disableCookieManagement()
|
||||
.setUserAgent(userAgent)
|
||||
.setDefaultHeaders(headers)
|
||||
.setConnectionManager(connectionManager)
|
||||
.evictExpiredConnections()
|
||||
.evictIdleConnections(TimeValue.of(config.httpClient().idleConnectionsEvictionInterval()))
|
||||
.setContentDecoderRegistry(new LinkedHashMap<>(contentDecoderMap))
|
||||
.setRedirectStrategy(redirectStrategy)
|
||||
.build();
|
||||
}
|
||||
return HttpClientBuilder.create()
|
||||
.disableConnectionState()
|
||||
.useSystemProperties()
|
||||
.disableAutomaticRetries()
|
||||
.disableCookieManagement()
|
||||
.setUserAgent(userAgent)
|
||||
.setDefaultHeaders(headers)
|
||||
.setConnectionManager(connectionManager)
|
||||
.evictExpiredConnections()
|
||||
.evictIdleConnections(
|
||||
TimeValue.of(config.httpClient().idleConnectionsEvictionInterval()))
|
||||
.setContentDecoderRegistry(new LinkedHashMap<>(contentDecoderMap))
|
||||
.setRedirectStrategy(redirectStrategy)
|
||||
.build();
|
||||
}
|
||||
|
||||
private PoolingHttpClientConnectionManager newConnectionManager(CommaFeedConfiguration config, int poolSize) {
|
||||
SSLFactory sslFactory = SSLFactory.builder().withUnsafeTrustMaterial().withUnsafeHostnameVerifier().build();
|
||||
DnsResolver dnsResolver = config.httpClient().blockLocalAddresses() ? new BlockLocalAddressesDnsResolver(DNS_RESOLVER)
|
||||
: DNS_RESOLVER;
|
||||
private PoolingHttpClientConnectionManager newConnectionManager(
|
||||
CommaFeedConfiguration config, int poolSize) {
|
||||
SSLFactory sslFactory =
|
||||
SSLFactory.builder().withUnsafeTrustMaterial().withUnsafeHostnameVerifier().build();
|
||||
DnsResolver dnsResolver =
|
||||
config.httpClient().blockLocalAddresses()
|
||||
? new BlockLocalAddressesDnsResolver(DNS_RESOLVER)
|
||||
: DNS_RESOLVER;
|
||||
|
||||
return PoolingHttpClientConnectionManagerBuilder.create()
|
||||
.setTlsSocketStrategy(Apache5SslUtils.toTlsSocketStrategy(sslFactory))
|
||||
.setDefaultConnectionConfig(ConnectionConfig.custom()
|
||||
.setConnectTimeout(Timeout.of(config.httpClient().connectTimeout()))
|
||||
.setSocketTimeout(Timeout.of(config.httpClient().socketTimeout()))
|
||||
.setTimeToLive(Timeout.of(config.httpClient().connectionTimeToLive()))
|
||||
.build())
|
||||
.setDefaultTlsConfig(TlsConfig.custom().setHandshakeTimeout(Timeout.of(config.httpClient().sslHandshakeTimeout())).build())
|
||||
.setMaxConnPerRoute(poolSize)
|
||||
.setMaxConnTotal(poolSize)
|
||||
.setDnsResolver(dnsResolver)
|
||||
.build();
|
||||
return PoolingHttpClientConnectionManagerBuilder.create()
|
||||
.setTlsSocketStrategy(Apache5SslUtils.toTlsSocketStrategy(sslFactory))
|
||||
.setDefaultConnectionConfig(
|
||||
ConnectionConfig.custom()
|
||||
.setConnectTimeout(Timeout.of(config.httpClient().connectTimeout()))
|
||||
.setSocketTimeout(Timeout.of(config.httpClient().socketTimeout()))
|
||||
.setTimeToLive(
|
||||
Timeout.of(config.httpClient().connectionTimeToLive()))
|
||||
.build())
|
||||
.setDefaultTlsConfig(
|
||||
TlsConfig.custom()
|
||||
.setHandshakeTimeout(
|
||||
Timeout.of(config.httpClient().sslHandshakeTimeout()))
|
||||
.build())
|
||||
.setMaxConnPerRoute(poolSize)
|
||||
.setMaxConnTotal(poolSize)
|
||||
.setDnsResolver(dnsResolver)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
private static boolean isLocalAddress(InetAddress address) {
|
||||
return isLocalAddress(new IPAddressNetwork.IPAddressGenerator().from(address));
|
||||
}
|
||||
|
||||
private static boolean isLocalAddress(InetAddress address) {
|
||||
return address.isSiteLocalAddress() || address.isAnyLocalAddress() || address.isLinkLocalAddress() || address.isLoopbackAddress()
|
||||
|| address.isMulticastAddress();
|
||||
}
|
||||
private static boolean isLocalAddress(IPAddress ip) {
|
||||
if (ip.isLocal() || ip.isLoopback() || ip.isMulticast() || CGNAT_RANGE.contains(ip)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private record BlockLocalAddressesDnsResolver(DnsResolver delegate) implements DnsResolver {
|
||||
@Override
|
||||
public InetAddress[] resolve(String host) throws UnknownHostException {
|
||||
InetAddress[] addresses = delegate.resolve(host);
|
||||
for (InetAddress addr : addresses) {
|
||||
if (isLocalAddress(addr)) {
|
||||
throw new UnknownHostException("Access to local address blocked: " + addr.getHostAddress());
|
||||
}
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
if (!ip.isIPv6()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveCanonicalHostname(String host) throws UnknownHostException {
|
||||
return delegate.resolveCanonicalHostname(host);
|
||||
}
|
||||
}
|
||||
// IPv6 transition mechanisms embed an IPv4 address that must be validated too, otherwise
|
||||
// they could be used to smuggle a blocked IPv4 target past the IPv6-only checks above
|
||||
IPv6Address ipv6 = ip.toIPv6();
|
||||
if (ipv6.isIPv4Mapped() || ipv6.isIPv4Compatible() || ipv6.isWellKnownIPv4Translatable()) {
|
||||
// IPv4-mapped (::ffff:x.x.x.x), IPv4-compatible (::x.x.x.x) and NAT64
|
||||
// (64:ff9b::/96, RFC 6052) addresses all embed the IPv4 address in the lowest 32 bits
|
||||
return isLocalAddress(ipv6.getEmbeddedIPv4Address());
|
||||
}
|
||||
if (ipv6.is6To4()) {
|
||||
// 6to4 (2002::/16, RFC 3056) embeds the IPv4 address in bits 16-47
|
||||
return isLocalAddress(ipv6.get6To4IPv4Address());
|
||||
}
|
||||
if (ipv6.isTeredo()) {
|
||||
// Teredo (2001::/32, RFC 4380) embeds the IPv4 address in the lowest 32 bits,
|
||||
// obfuscated with a bitwise complement
|
||||
IPv4Address obfuscated = ipv6.getEmbeddedIPv4Address();
|
||||
return isLocalAddress(new IPv4Address(~obfuscated.intValue()));
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class BlockLocalAddressesRedirectStrategy extends DefaultRedirectStrategy {
|
||||
return false;
|
||||
}
|
||||
|
||||
private final DnsResolver delegate;
|
||||
private record BlockLocalAddressesDnsResolver(DnsResolver delegate) implements DnsResolver {
|
||||
@Override
|
||||
public InetAddress[] resolve(String host) throws UnknownHostException {
|
||||
InetAddress[] addresses = delegate.resolve(host);
|
||||
for (InetAddress addr : addresses) {
|
||||
if (isLocalAddress(addr)) {
|
||||
throw new UnknownHostException(
|
||||
"Access to local address blocked: " + addr.getHostAddress());
|
||||
}
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getLocationURI(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException {
|
||||
URI redirectUri = super.getLocationURI(request, response, context);
|
||||
@Override
|
||||
public String resolveCanonicalHostname(String host) throws UnknownHostException {
|
||||
return delegate.resolveCanonicalHostname(host);
|
||||
}
|
||||
}
|
||||
|
||||
String host = redirectUri.getHost();
|
||||
if (host == null) {
|
||||
throw new HttpException("Redirect URI does not have a host: " + redirectUri);
|
||||
}
|
||||
@RequiredArgsConstructor
|
||||
private static class BlockLocalAddressesRedirectStrategy extends DefaultRedirectStrategy {
|
||||
|
||||
InetAddress[] addresses;
|
||||
try {
|
||||
addresses = delegate.resolve(host);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new HttpException("Unknown host: " + host);
|
||||
}
|
||||
private final DnsResolver delegate;
|
||||
|
||||
for (InetAddress addr : addresses) {
|
||||
if (isLocalAddress(addr)) {
|
||||
throw new HttpException("Access to local address blocked: " + addr.getHostAddress());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public URI getLocationURI(HttpRequest request, HttpResponse response, HttpContext context)
|
||||
throws HttpException {
|
||||
URI redirectUri = super.getLocationURI(request, response, context);
|
||||
|
||||
return redirectUri;
|
||||
}
|
||||
}
|
||||
String host = redirectUri.getHost();
|
||||
if (host == null) {
|
||||
throw new HttpException("Redirect URI does not have a host: " + redirectUri);
|
||||
}
|
||||
|
||||
InetAddress[] addresses;
|
||||
try {
|
||||
addresses = delegate.resolve(host);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new HttpException("Unknown host: " + host);
|
||||
}
|
||||
|
||||
for (InetAddress addr : addresses) {
|
||||
if (isLocalAddress(addr)) {
|
||||
throw new HttpException(
|
||||
"Access to local address blocked: " + addr.getHostAddress());
|
||||
}
|
||||
}
|
||||
|
||||
return redirectUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
package com.commafeed.backend;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.InstantSource;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedConfiguration.HttpClientCache;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.ws.rs.core.CacheControl;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Lombok;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
@@ -27,284 +34,335 @@ import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.jboss.resteasy.reactive.common.headers.CacheControlDelegate;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedConfiguration.HttpClientCache;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.InstantSource;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Lombok;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Smart HTTP getter: handles gzip, ssl, last modified and etag headers
|
||||
*/
|
||||
/** Smart HTTP getter: handles gzip, ssl, last modified and etag headers */
|
||||
@Singleton
|
||||
@Slf4j
|
||||
public class HttpGetter {
|
||||
private final CommaFeedConfiguration config;
|
||||
private final InstantSource instantSource;
|
||||
private final CloseableHttpClient client;
|
||||
private final Cache<HttpRequest, HttpResponse> cache;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final InstantSource instantSource;
|
||||
private final CloseableHttpClient client;
|
||||
private final Cache<HttpRequest, HttpResponse> cache;
|
||||
|
||||
public HttpGetter(CommaFeedConfiguration config, InstantSource instantSource, HttpClientFactory httpClientFactory,
|
||||
MetricRegistry metrics) {
|
||||
this.config = config;
|
||||
this.instantSource = instantSource;
|
||||
this.client = httpClientFactory.newClient(config.feedRefresh().httpThreads());
|
||||
this.cache = newCache(config);
|
||||
public HttpGetter(
|
||||
CommaFeedConfiguration config,
|
||||
InstantSource instantSource,
|
||||
HttpClientFactory httpClientFactory,
|
||||
MetricRegistry metrics) {
|
||||
this.config = config;
|
||||
this.instantSource = instantSource;
|
||||
this.client = httpClientFactory.newClient(config.feedRefresh().httpThreads());
|
||||
this.cache = newCache(config);
|
||||
|
||||
metrics.registerGauge(MetricRegistry.name(getClass(), "cache", "size"), () -> cache == null ? 0 : cache.size());
|
||||
metrics.registerGauge(MetricRegistry.name(getClass(), "cache", "memoryUsage"),
|
||||
() -> cache == null ? 0 : cache.asMap().values().stream().mapToInt(e -> ArrayUtils.getLength(e.content)).sum());
|
||||
}
|
||||
metrics.registerGauge(
|
||||
MetricRegistry.name(getClass(), "cache", "size"),
|
||||
() -> cache == null ? 0 : cache.size());
|
||||
metrics.registerGauge(
|
||||
MetricRegistry.name(getClass(), "cache", "memoryUsage"),
|
||||
() ->
|
||||
cache == null
|
||||
? 0
|
||||
: cache.asMap().values().stream()
|
||||
.mapToInt(e -> ArrayUtils.getLength(e.content))
|
||||
.sum());
|
||||
}
|
||||
|
||||
public HttpResult get(String url) throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||
return get(HttpRequest.builder(url).build());
|
||||
}
|
||||
public HttpResult get(String url)
|
||||
throws IOException,
|
||||
NotModifiedException,
|
||||
TooManyRequestsException,
|
||||
SchemeNotAllowedException {
|
||||
return get(HttpRequest.builder(url).build());
|
||||
}
|
||||
|
||||
public HttpResult get(HttpRequest request)
|
||||
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||
URI uri = URI.create(request.getUrl());
|
||||
ensureHttpScheme(uri.getScheme());
|
||||
public HttpResult get(HttpRequest request)
|
||||
throws IOException,
|
||||
NotModifiedException,
|
||||
TooManyRequestsException,
|
||||
SchemeNotAllowedException {
|
||||
URI uri = URI.create(request.getUrl());
|
||||
ensureHttpScheme(uri.getScheme());
|
||||
|
||||
final HttpResponse response;
|
||||
if (cache == null) {
|
||||
response = invoke(request);
|
||||
} else {
|
||||
try {
|
||||
response = cache.get(request, () -> invoke(request));
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof IOException ioe) {
|
||||
throw ioe;
|
||||
} else {
|
||||
throw Lombok.sneakyThrow(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
final HttpResponse response;
|
||||
if (cache == null) {
|
||||
response = invoke(request);
|
||||
} else {
|
||||
try {
|
||||
response = cache.get(request, () -> invoke(request));
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof IOException ioe) {
|
||||
throw ioe;
|
||||
} else {
|
||||
throw Lombok.sneakyThrow(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int code = response.code();
|
||||
if (code == HttpStatus.SC_TOO_MANY_REQUESTS || code == HttpStatus.SC_SERVICE_UNAVAILABLE && response.retryAfter() != null) {
|
||||
throw new TooManyRequestsException(response.retryAfter());
|
||||
}
|
||||
int code = response.code();
|
||||
if (code == HttpStatus.SC_TOO_MANY_REQUESTS
|
||||
|| code == HttpStatus.SC_SERVICE_UNAVAILABLE && response.retryAfter() != null) {
|
||||
throw new TooManyRequestsException(response.retryAfter());
|
||||
}
|
||||
|
||||
if (code == HttpStatus.SC_NOT_MODIFIED) {
|
||||
throw new NotModifiedException("'304 - not modified' http code received");
|
||||
}
|
||||
if (code == HttpStatus.SC_NOT_MODIFIED) {
|
||||
throw new NotModifiedException("'304 - not modified' http code received");
|
||||
}
|
||||
|
||||
if (code >= 300) {
|
||||
throw new HttpResponseException(code, "Server returned HTTP error code " + code);
|
||||
}
|
||||
if (code >= 300) {
|
||||
throw new HttpResponseException(code, "Server returned HTTP error code " + code);
|
||||
}
|
||||
|
||||
String lastModifiedHeader = response.lastModifiedHeader();
|
||||
String eTagHeader = response.eTagHeader();
|
||||
String lastModifiedHeader = response.lastModifiedHeader();
|
||||
String eTagHeader = response.eTagHeader();
|
||||
|
||||
Duration validFor = Optional.ofNullable(response.cacheControl())
|
||||
.filter(cc -> cc.getMaxAge() >= 0)
|
||||
.map(cc -> Duration.ofSeconds(cc.getMaxAge()))
|
||||
.orElse(Duration.ZERO);
|
||||
Duration validFor =
|
||||
Optional.ofNullable(response.cacheControl())
|
||||
.filter(cc -> cc.getMaxAge() >= 0)
|
||||
.map(cc -> Duration.ofSeconds(cc.getMaxAge()))
|
||||
.orElse(Duration.ZERO);
|
||||
|
||||
return new HttpResult(response.content(), response.contentType(), lastModifiedHeader, eTagHeader, response.urlAfterRedirect(),
|
||||
validFor);
|
||||
}
|
||||
return new HttpResult(
|
||||
response.content(),
|
||||
response.contentType(),
|
||||
lastModifiedHeader,
|
||||
eTagHeader,
|
||||
response.urlAfterRedirect(),
|
||||
validFor);
|
||||
}
|
||||
|
||||
private void ensureHttpScheme(String scheme) throws SchemeNotAllowedException {
|
||||
if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
|
||||
throw new SchemeNotAllowedException(scheme);
|
||||
}
|
||||
}
|
||||
private void ensureHttpScheme(String scheme) throws SchemeNotAllowedException {
|
||||
if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
|
||||
throw new SchemeNotAllowedException(scheme);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpResponse invoke(HttpRequest request) throws IOException {
|
||||
log.debug("fetching {}", request.getUrl());
|
||||
private HttpResponse invoke(HttpRequest request) throws IOException {
|
||||
log.debug("fetching {}", request.getUrl());
|
||||
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setRequestConfig(RequestConfig.custom()
|
||||
.setResponseTimeout(Timeout.of(config.httpClient().responseTimeout()))
|
||||
// causes issues with some feeds
|
||||
// see https://github.com/Athou/commafeed/issues/1572
|
||||
// and https://issues.apache.org/jira/browse/HTTPCLIENT-2344
|
||||
.setProtocolUpgradeEnabled(false)
|
||||
.build());
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setRequestConfig(
|
||||
RequestConfig.custom()
|
||||
.setResponseTimeout(Timeout.of(config.httpClient().responseTimeout()))
|
||||
// causes issues with some feeds
|
||||
// see https://github.com/Athou/commafeed/issues/1572
|
||||
// and https://issues.apache.org/jira/browse/HTTPCLIENT-2344
|
||||
.setProtocolUpgradeEnabled(false)
|
||||
.build());
|
||||
|
||||
return client.execute(request.toClassicHttpRequest(), context, resp -> {
|
||||
byte[] content = resp.getEntity() == null ? null
|
||||
: toByteArray(resp.getEntity(), config.httpClient().maxResponseSize().asLongValue());
|
||||
int code = resp.getCode();
|
||||
String lastModifiedHeader = Optional.ofNullable(resp.getFirstHeader(HttpHeaders.LAST_MODIFIED))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.orElse(null);
|
||||
String eTagHeader = Optional.ofNullable(resp.getFirstHeader(HttpHeaders.ETAG))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.orElse(null);
|
||||
return client.execute(
|
||||
request.toClassicHttpRequest(),
|
||||
context,
|
||||
resp -> {
|
||||
byte[] content =
|
||||
resp.getEntity() == null
|
||||
? null
|
||||
: toByteArray(
|
||||
resp.getEntity(),
|
||||
config.httpClient().maxResponseSize().asLongValue());
|
||||
int code = resp.getCode();
|
||||
String lastModifiedHeader =
|
||||
Optional.ofNullable(resp.getFirstHeader(HttpHeaders.LAST_MODIFIED))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.orElse(null);
|
||||
String eTagHeader =
|
||||
Optional.ofNullable(resp.getFirstHeader(HttpHeaders.ETAG))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.orElse(null);
|
||||
|
||||
CacheControl cacheControl = Optional.ofNullable(resp.getFirstHeader(HttpHeaders.CACHE_CONTROL))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.map(HttpGetter::toCacheControl)
|
||||
.orElse(null);
|
||||
CacheControl cacheControl =
|
||||
Optional.ofNullable(resp.getFirstHeader(HttpHeaders.CACHE_CONTROL))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.map(HttpGetter::toCacheControl)
|
||||
.orElse(null);
|
||||
|
||||
Instant retryAfter = Optional.ofNullable(resp.getFirstHeader(HttpHeaders.RETRY_AFTER))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.map(this::toInstant)
|
||||
.orElse(null);
|
||||
Instant retryAfter =
|
||||
Optional.ofNullable(resp.getFirstHeader(HttpHeaders.RETRY_AFTER))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(StringUtils::trimToNull)
|
||||
.map(this::toInstant)
|
||||
.orElse(null);
|
||||
|
||||
String contentType = Optional.ofNullable(resp.getEntity()).map(HttpEntity::getContentType).orElse(null);
|
||||
String urlAfterRedirect = Optional.ofNullable(context.getRedirectLocations())
|
||||
.map(RedirectLocations::getAll)
|
||||
.map(l -> Iterables.getLast(l, null))
|
||||
.map(URI::toString)
|
||||
.orElse(request.getUrl());
|
||||
String contentType =
|
||||
Optional.ofNullable(resp.getEntity())
|
||||
.map(HttpEntity::getContentType)
|
||||
.orElse(null);
|
||||
String urlAfterRedirect =
|
||||
Optional.ofNullable(context.getRedirectLocations())
|
||||
.map(RedirectLocations::getAll)
|
||||
.map(l -> Iterables.getLast(l, null))
|
||||
.map(URI::toString)
|
||||
.orElse(request.getUrl());
|
||||
|
||||
return new HttpResponse(code, lastModifiedHeader, eTagHeader, cacheControl, retryAfter, content, contentType, urlAfterRedirect);
|
||||
});
|
||||
}
|
||||
return new HttpResponse(
|
||||
code,
|
||||
lastModifiedHeader,
|
||||
eTagHeader,
|
||||
cacheControl,
|
||||
retryAfter,
|
||||
content,
|
||||
contentType,
|
||||
urlAfterRedirect);
|
||||
});
|
||||
}
|
||||
|
||||
private static CacheControl toCacheControl(String headerValue) {
|
||||
try {
|
||||
return CacheControlDelegate.INSTANCE.fromString(headerValue);
|
||||
} catch (Exception e) {
|
||||
log.debug("Invalid Cache-Control header: {}", headerValue);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private static CacheControl toCacheControl(String headerValue) {
|
||||
try {
|
||||
return CacheControlDelegate.INSTANCE.fromString(headerValue);
|
||||
} catch (Exception e) {
|
||||
log.debug("Invalid Cache-Control header: {}", headerValue);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Instant toInstant(String headerValue) {
|
||||
if (headerValue == null) {
|
||||
return null;
|
||||
}
|
||||
private Instant toInstant(String headerValue) {
|
||||
if (headerValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (StringUtils.isNumeric(headerValue)) {
|
||||
return instantSource.instant().plusSeconds(Long.parseLong(headerValue));
|
||||
}
|
||||
if (StringUtils.isNumeric(headerValue)) {
|
||||
return instantSource.instant().plusSeconds(Long.parseLong(headerValue));
|
||||
}
|
||||
|
||||
return DateUtils.parseStandardDate(headerValue);
|
||||
}
|
||||
return DateUtils.parseStandardDate(headerValue);
|
||||
}
|
||||
|
||||
private static byte[] toByteArray(HttpEntity entity, long maxBytes) throws IOException {
|
||||
if (entity.getContentLength() > maxBytes) {
|
||||
throw new IOException(
|
||||
"Response size (%s bytes) exceeds the maximum allowed size (%s bytes)".formatted(entity.getContentLength(), maxBytes));
|
||||
}
|
||||
private static byte[] toByteArray(HttpEntity entity, long maxBytes) throws IOException {
|
||||
if (entity.getContentLength() > maxBytes) {
|
||||
throw new IOException(
|
||||
"Response size (%s bytes) exceeds the maximum allowed size (%s bytes)"
|
||||
.formatted(entity.getContentLength(), maxBytes));
|
||||
}
|
||||
|
||||
try (InputStream input = entity.getContent()) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream input = entity.getContent()) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] bytes = ByteStreams.limit(input, maxBytes + 1).readAllBytes();
|
||||
if (bytes.length > maxBytes) {
|
||||
throw new IOException("Response size exceeds the maximum allowed size (%s bytes)".formatted(maxBytes));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
byte[] bytes = ByteStreams.limit(input, maxBytes + 1).readAllBytes();
|
||||
if (bytes.length > maxBytes) {
|
||||
throw new IOException(
|
||||
"Response size exceeds the maximum allowed size (%s bytes)"
|
||||
.formatted(maxBytes));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
private static Cache<HttpRequest, HttpResponse> newCache(CommaFeedConfiguration config) {
|
||||
HttpClientCache cacheConfig = config.httpClient().cache();
|
||||
if (!cacheConfig.enabled()) {
|
||||
return null;
|
||||
}
|
||||
private static Cache<HttpRequest, HttpResponse> newCache(CommaFeedConfiguration config) {
|
||||
HttpClientCache cacheConfig = config.httpClient().cache();
|
||||
if (!cacheConfig.enabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return CacheBuilder.newBuilder()
|
||||
.weigher((HttpRequest key, HttpResponse value) -> value.content() != null ? value.content().length : 0)
|
||||
.maximumWeight(cacheConfig.maximumMemorySize().asLongValue())
|
||||
.expireAfterWrite(cacheConfig.expiration())
|
||||
.build();
|
||||
}
|
||||
return CacheBuilder.newBuilder()
|
||||
.weigher(
|
||||
(HttpRequest key, HttpResponse value) ->
|
||||
value.content() != null ? value.content().length : 0)
|
||||
.maximumWeight(cacheConfig.maximumMemorySize().asLongValue())
|
||||
.expireAfterWrite(cacheConfig.expiration())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static class SchemeNotAllowedException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public static class SchemeNotAllowedException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SchemeNotAllowedException(String scheme) {
|
||||
super("Scheme not allowed: " + scheme);
|
||||
}
|
||||
}
|
||||
public SchemeNotAllowedException(String scheme) {
|
||||
super("Scheme not allowed: " + scheme);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class NotModifiedException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Getter
|
||||
public static class NotModifiedException extends Exception {
|
||||
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 newLastModifiedHeader;
|
||||
|
||||
/**
|
||||
* if the value of this header changed, this is its new value
|
||||
*/
|
||||
private final String newEtagHeader;
|
||||
/** if the value of this header changed, this is its new value */
|
||||
private final String newEtagHeader;
|
||||
|
||||
public NotModifiedException(String message) {
|
||||
this(message, null, null);
|
||||
}
|
||||
public NotModifiedException(String message) {
|
||||
this(message, null, null);
|
||||
}
|
||||
|
||||
public NotModifiedException(String message, String newLastModifiedHeader, String newEtagHeader) {
|
||||
super(message);
|
||||
this.newLastModifiedHeader = newLastModifiedHeader;
|
||||
this.newEtagHeader = newEtagHeader;
|
||||
}
|
||||
}
|
||||
public NotModifiedException(
|
||||
String message, String newLastModifiedHeader, String newEtagHeader) {
|
||||
super(message);
|
||||
this.newLastModifiedHeader = newLastModifiedHeader;
|
||||
this.newEtagHeader = newEtagHeader;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public static class TooManyRequestsException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public static class TooManyRequestsException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Instant retryAfter;
|
||||
}
|
||||
private final Instant retryAfter;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class HttpResponseException extends IOException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Getter
|
||||
public static class HttpResponseException extends IOException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int code;
|
||||
private final int code;
|
||||
|
||||
public HttpResponseException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
public HttpResponseException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
@Builder(builderMethodName = "")
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
public static class HttpRequest {
|
||||
private String url;
|
||||
private String lastModified;
|
||||
private String eTag;
|
||||
@Builder(builderMethodName = "")
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
public static class HttpRequest {
|
||||
private String url;
|
||||
private String lastModified;
|
||||
private String eTag;
|
||||
|
||||
public static HttpRequestBuilder builder(String url) {
|
||||
return new HttpRequestBuilder().url(url);
|
||||
}
|
||||
public static HttpRequestBuilder builder(String url) {
|
||||
return new HttpRequestBuilder().url(url);
|
||||
}
|
||||
|
||||
public ClassicHttpRequest toClassicHttpRequest() {
|
||||
ClassicHttpRequest req = ClassicRequestBuilder.get(url).build();
|
||||
if (lastModified != null) {
|
||||
req.addHeader(HttpHeaders.IF_MODIFIED_SINCE, lastModified);
|
||||
}
|
||||
if (eTag != null) {
|
||||
req.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
|
||||
}
|
||||
return req;
|
||||
}
|
||||
}
|
||||
public ClassicHttpRequest toClassicHttpRequest() {
|
||||
ClassicHttpRequest req = ClassicRequestBuilder.get(url).build();
|
||||
if (lastModified != null) {
|
||||
req.addHeader(HttpHeaders.IF_MODIFIED_SINCE, lastModified);
|
||||
}
|
||||
if (eTag != null) {
|
||||
req.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
|
||||
}
|
||||
return req;
|
||||
}
|
||||
}
|
||||
|
||||
private record HttpResponse(int code, String lastModifiedHeader, String eTagHeader, CacheControl cacheControl, Instant retryAfter,
|
||||
byte[] content, String contentType, String urlAfterRedirect) {
|
||||
}
|
||||
|
||||
public record HttpResult(byte[] content, String contentType, String lastModifiedSince, String eTag, String urlAfterRedirect,
|
||||
Duration validFor) {
|
||||
}
|
||||
private record HttpResponse(
|
||||
int code,
|
||||
String lastModifiedHeader,
|
||||
String eTagHeader,
|
||||
CacheControl cacheControl,
|
||||
Instant retryAfter,
|
||||
byte[] content,
|
||||
String contentType,
|
||||
String urlAfterRedirect) {}
|
||||
|
||||
public record HttpResult(
|
||||
byte[] content,
|
||||
String contentType,
|
||||
String lastModifiedSince,
|
||||
String eTag,
|
||||
String urlAfterRedirect,
|
||||
Duration validFor) {}
|
||||
}
|
||||
|
||||
@@ -1,106 +1,129 @@
|
||||
package com.commafeed.backend;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.netpreserve.urlcanon.Canonicalizer;
|
||||
import org.netpreserve.urlcanon.ParsedUrl;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.net.URI;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@UtilityClass
|
||||
@Slf4j
|
||||
public class Urls {
|
||||
|
||||
private static final String ESCAPED_QUESTION_MARK = Pattern.quote("?");
|
||||
private static final Pattern QUESTION_MARK = Pattern.compile(Pattern.quote("?"));
|
||||
|
||||
public static boolean isHttp(String url) {
|
||||
return url.startsWith("http://");
|
||||
}
|
||||
public static boolean isHttp(String url) {
|
||||
if (url == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isHttps(String url) {
|
||||
return url.startsWith("https://");
|
||||
}
|
||||
return url.toLowerCase(Locale.ROOT).startsWith("http://");
|
||||
}
|
||||
|
||||
public static boolean isAbsolute(String url) {
|
||||
return isHttp(url) || isHttps(url);
|
||||
}
|
||||
public static boolean isHttps(String url) {
|
||||
if (url == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param relativeUrl
|
||||
* the url of the entry
|
||||
* @param feedLink
|
||||
* the url of the feed as described in the feed
|
||||
* @param feedUrl
|
||||
* the url of the feed that we used to fetch the feed
|
||||
* @return an absolute url pointing to the entry
|
||||
*/
|
||||
public static String toAbsolute(String relativeUrl, String feedLink, String feedUrl) {
|
||||
String baseUrl = (feedLink != null && isAbsolute(feedLink)) ? feedLink : feedUrl;
|
||||
if (baseUrl == null) {
|
||||
return null;
|
||||
}
|
||||
return url.toLowerCase(Locale.ROOT).startsWith("https://");
|
||||
}
|
||||
|
||||
try {
|
||||
return URI.create(baseUrl).resolve(relativeUrl).toString();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.debug("Unable to create absolute url from relative url: {} base: {}", relativeUrl, baseUrl, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static boolean isAbsolute(String url) {
|
||||
return isHttp(url) || isHttps(url);
|
||||
}
|
||||
|
||||
public static String removeTrailingSlash(String url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
/** remove malicious 'javascript: 'URLs * */
|
||||
public static String sanitize(String url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (url.endsWith("/")) {
|
||||
url = url.substring(0, url.length() - 1);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
if (!isHttp(url) && !isHttps(url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the url. The resulting url is not meant to be fetched but rather used as a mean to identify a feed and avoid duplicates
|
||||
*/
|
||||
public static String normalize(String url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
ParsedUrl parsedUrl = ParsedUrl.parseUrl(url);
|
||||
Canonicalizer.AGGRESSIVE.canonicalize(parsedUrl);
|
||||
String normalized = parsedUrl.toString();
|
||||
if (normalized == null) {
|
||||
normalized = url;
|
||||
}
|
||||
/**
|
||||
* @param relativeUrl the url of the entry
|
||||
* @param feedLink the url of the feed as described in the feed
|
||||
* @param feedUrl the url of the feed that we used to fetch the feed
|
||||
* @return an absolute url pointing to the entry
|
||||
*/
|
||||
public static String toAbsolute(String relativeUrl, String feedLink, String feedUrl) {
|
||||
String baseUrl = (feedLink != null && isAbsolute(feedLink)) ? feedLink : feedUrl;
|
||||
if (baseUrl == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// convert to lower case, the url probably won't work in some cases
|
||||
// after that but we don't care we just want to compare urls to avoid
|
||||
// duplicates
|
||||
normalized = normalized.toLowerCase();
|
||||
try {
|
||||
return URI.create(baseUrl).resolve(relativeUrl).toString();
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.debug(
|
||||
"Unable to create absolute url from relative url: {} base: {}",
|
||||
relativeUrl,
|
||||
baseUrl,
|
||||
e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// store all urls as http
|
||||
if (normalized.startsWith("https")) {
|
||||
normalized = "http" + normalized.substring(5);
|
||||
}
|
||||
public static String removeTrailingSlash(String url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// remove the www. part
|
||||
normalized = normalized.replace("//www.", "//");
|
||||
if (url.endsWith("/")) {
|
||||
url = url.substring(0, url.length() - 1);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// feedproxy redirects to feedburner
|
||||
normalized = normalized.replace("feedproxy.google.com", "feeds.feedburner.com");
|
||||
/**
|
||||
* Normalize the url. The resulting url is not meant to be fetched but rather used as a mean to
|
||||
* identify a feed and avoid duplicates
|
||||
*/
|
||||
public static String normalize(String url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// feedburner feeds have a special treatment
|
||||
if (normalized.split(ESCAPED_QUESTION_MARK)[0].contains("feedburner.com")) {
|
||||
normalized = normalized.replace("feeds2.feedburner.com", "feeds.feedburner.com");
|
||||
normalized = normalized.split(ESCAPED_QUESTION_MARK)[0];
|
||||
normalized = Strings.CS.removeEnd(normalized, "/");
|
||||
}
|
||||
ParsedUrl parsedUrl = ParsedUrl.parseUrl(url);
|
||||
Canonicalizer.AGGRESSIVE.canonicalize(parsedUrl);
|
||||
String normalized = parsedUrl.toString();
|
||||
if (normalized == null) {
|
||||
normalized = url;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
// convert to lower case, the url probably won't work in some cases
|
||||
// after that but we don't care we just want to compare urls to avoid
|
||||
// duplicates
|
||||
normalized = normalized.toLowerCase();
|
||||
|
||||
// store all urls as http
|
||||
if (normalized.startsWith("https")) {
|
||||
normalized = "http" + normalized.substring(5);
|
||||
}
|
||||
|
||||
// remove the www. part
|
||||
normalized = normalized.replace("//www.", "//");
|
||||
|
||||
// feedproxy redirects to feedburner
|
||||
normalized = normalized.replace("feedproxy.google.com", "feeds.feedburner.com");
|
||||
|
||||
// feedburner feeds have a special treatment
|
||||
if (QUESTION_MARK.split(normalized)[0].contains("feedburner.com")) {
|
||||
normalized = normalized.replace("feeds2.feedburner.com", "feeds.feedburner.com");
|
||||
normalized = QUESTION_MARK.split(normalized)[0];
|
||||
normalized = Strings.CS.removeEnd(normalized, "/");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,78 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.QFeedCategory;
|
||||
import com.commafeed.backend.model.QUser;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Singleton
|
||||
public class FeedCategoryDAO extends GenericDAO<FeedCategory> {
|
||||
|
||||
private static final QFeedCategory CATEGORY = QFeedCategory.feedCategory;
|
||||
private static final QFeedCategory CATEGORY = QFeedCategory.feedCategory;
|
||||
|
||||
public FeedCategoryDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedCategory.class);
|
||||
}
|
||||
public FeedCategoryDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedCategory.class);
|
||||
}
|
||||
|
||||
public List<FeedCategory> findAll(User user) {
|
||||
return query().selectFrom(CATEGORY).where(CATEGORY.user.eq(user)).join(CATEGORY.user, QUser.user).fetchJoin().fetch();
|
||||
}
|
||||
public List<FeedCategory> findAll(User user) {
|
||||
return query().selectFrom(CATEGORY)
|
||||
.where(CATEGORY.user.eq(user))
|
||||
.join(CATEGORY.user, QUser.user)
|
||||
.fetchJoin()
|
||||
.fetch();
|
||||
}
|
||||
|
||||
public FeedCategory findById(User user, Long id) {
|
||||
return query().selectFrom(CATEGORY).where(CATEGORY.user.eq(user), CATEGORY.id.eq(id)).fetchOne();
|
||||
}
|
||||
public FeedCategory findById(User user, Long id) {
|
||||
return query().selectFrom(CATEGORY)
|
||||
.where(CATEGORY.user.eq(user), CATEGORY.id.eq(id))
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
public FeedCategory findByName(User user, String name, FeedCategory parent) {
|
||||
Predicate parentPredicate;
|
||||
if (parent == null) {
|
||||
parentPredicate = CATEGORY.parent.isNull();
|
||||
} else {
|
||||
parentPredicate = CATEGORY.parent.eq(parent);
|
||||
}
|
||||
return query().selectFrom(CATEGORY).where(CATEGORY.user.eq(user), CATEGORY.name.eq(name), parentPredicate).fetchOne();
|
||||
}
|
||||
public FeedCategory findByName(User user, String name, FeedCategory parent) {
|
||||
Predicate parentPredicate;
|
||||
if (parent == null) {
|
||||
parentPredicate = CATEGORY.parent.isNull();
|
||||
} else {
|
||||
parentPredicate = CATEGORY.parent.eq(parent);
|
||||
}
|
||||
return query().selectFrom(CATEGORY)
|
||||
.where(CATEGORY.user.eq(user), CATEGORY.name.eq(name), parentPredicate)
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
public List<FeedCategory> findByParent(User user, FeedCategory parent) {
|
||||
Predicate parentPredicate;
|
||||
if (parent == null) {
|
||||
parentPredicate = CATEGORY.parent.isNull();
|
||||
} else {
|
||||
parentPredicate = CATEGORY.parent.eq(parent);
|
||||
}
|
||||
return query().selectFrom(CATEGORY).where(CATEGORY.user.eq(user), parentPredicate).fetch();
|
||||
}
|
||||
public List<FeedCategory> findByParent(User user, FeedCategory parent) {
|
||||
Predicate parentPredicate;
|
||||
if (parent == null) {
|
||||
parentPredicate = CATEGORY.parent.isNull();
|
||||
} else {
|
||||
parentPredicate = CATEGORY.parent.eq(parent);
|
||||
}
|
||||
return query().selectFrom(CATEGORY).where(CATEGORY.user.eq(user), parentPredicate).fetch();
|
||||
}
|
||||
|
||||
public List<FeedCategory> findAllChildrenCategories(User user, FeedCategory parent) {
|
||||
return findAll(user).stream().filter(c -> isChild(c, parent)).toList();
|
||||
}
|
||||
|
||||
private boolean isChild(FeedCategory child, FeedCategory parent) {
|
||||
if (parent == null) {
|
||||
return true;
|
||||
}
|
||||
boolean isChild = false;
|
||||
while (child != null) {
|
||||
if (Objects.equals(child.getId(), parent.getId())) {
|
||||
isChild = true;
|
||||
break;
|
||||
}
|
||||
child = child.getParent();
|
||||
}
|
||||
return isChild;
|
||||
}
|
||||
public List<FeedCategory> findAllChildrenCategories(User user, FeedCategory parent) {
|
||||
return findAll(user).stream().filter(c -> isChild(c, parent)).toList();
|
||||
}
|
||||
|
||||
private boolean isChild(FeedCategory child, FeedCategory parent) {
|
||||
if (parent == null) {
|
||||
return true;
|
||||
}
|
||||
boolean isChild = false;
|
||||
while (child != null) {
|
||||
if (Objects.equals(child.getId(), parent.getId())) {
|
||||
isChild = true;
|
||||
break;
|
||||
}
|
||||
child = child.getParent();
|
||||
}
|
||||
return isChild;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +1,72 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.QFeed;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.querydsl.jpa.JPAExpressions;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
public class FeedDAO extends GenericDAO<Feed> {
|
||||
|
||||
private static final QFeed FEED = QFeed.feed;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
private static final QFeed FEED = QFeed.feed;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
|
||||
public FeedDAO(EntityManager entityManager) {
|
||||
super(entityManager, Feed.class);
|
||||
}
|
||||
public FeedDAO(EntityManager entityManager) {
|
||||
super(entityManager, Feed.class);
|
||||
}
|
||||
|
||||
public List<Feed> findByIds(List<Long> id) {
|
||||
return query().selectFrom(FEED).where(FEED.id.in(id)).fetch();
|
||||
}
|
||||
public List<Feed> findByIds(List<Long> id) {
|
||||
return query().selectFrom(FEED).where(FEED.id.in(id)).fetch();
|
||||
}
|
||||
|
||||
public List<Feed> findNextUpdatable(int count, Instant lastLoginThreshold) {
|
||||
JPAQuery<Feed> query = query().selectFrom(FEED)
|
||||
.distinct()
|
||||
// join on subscriptions to only refresh feeds that have subscribers
|
||||
.join(SUBSCRIPTION)
|
||||
.on(SUBSCRIPTION.feed.eq(FEED))
|
||||
.where(FEED.disabledUntil.isNull().or(FEED.disabledUntil.lt(Instant.now())));
|
||||
public List<Feed> findNextUpdatable(int count, Instant lastLoginThreshold) {
|
||||
JPAQuery<Feed> query =
|
||||
query().selectFrom(FEED)
|
||||
.distinct()
|
||||
// join on subscriptions to only refresh feeds that have subscribers
|
||||
.join(SUBSCRIPTION)
|
||||
.on(SUBSCRIPTION.feed.eq(FEED))
|
||||
.where(
|
||||
FEED.disabledUntil
|
||||
.isNull()
|
||||
.or(FEED.disabledUntil.lt(Instant.now())));
|
||||
|
||||
if (lastLoginThreshold != null) {
|
||||
query.join(SUBSCRIPTION.user).where(SUBSCRIPTION.user.lastLogin.gt(lastLoginThreshold));
|
||||
}
|
||||
if (lastLoginThreshold != null) {
|
||||
query.join(SUBSCRIPTION.user).where(SUBSCRIPTION.user.lastLogin.gt(lastLoginThreshold));
|
||||
}
|
||||
|
||||
return query.orderBy(FEED.disabledUntil.asc()).limit(count).fetch();
|
||||
}
|
||||
return query.orderBy(FEED.disabledUntil.asc()).limit(count).fetch();
|
||||
}
|
||||
|
||||
public void setDisabledUntil(List<Long> feedIds, Instant date) {
|
||||
updateQuery(FEED).set(FEED.disabledUntil, date).where(FEED.id.in(feedIds)).execute();
|
||||
}
|
||||
public void setDisabledUntil(List<Long> feedIds, Instant date) {
|
||||
updateQuery(FEED).set(FEED.disabledUntil, date).where(FEED.id.in(feedIds)).execute();
|
||||
}
|
||||
|
||||
public Feed findByUrl(String normalizedUrl, String normalizedUrlHash) {
|
||||
return query().selectFrom(FEED)
|
||||
.where(FEED.normalizedUrlHash.eq(normalizedUrlHash))
|
||||
.fetch()
|
||||
.stream()
|
||||
.filter(f -> Strings.CS.equals(normalizedUrl, f.getNormalizedUrl()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
public Feed findByUrl(String normalizedUrl, String normalizedUrlHash) {
|
||||
return query()
|
||||
.selectFrom(FEED)
|
||||
.where(FEED.normalizedUrlHash.eq(normalizedUrlHash))
|
||||
.fetch()
|
||||
.stream()
|
||||
.filter(f -> Strings.CS.equals(normalizedUrl, f.getNormalizedUrl()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public List<Feed> findWithoutSubscriptions(int max) {
|
||||
QFeedSubscription sub = QFeedSubscription.feedSubscription;
|
||||
return query().selectFrom(FEED).where(JPAExpressions.selectOne().from(sub).where(sub.feed.eq(FEED)).notExists()).limit(max).fetch();
|
||||
}
|
||||
public List<Feed> findWithoutSubscriptions(int max) {
|
||||
QFeedSubscription sub = QFeedSubscription.feedSubscription;
|
||||
return query().selectFrom(FEED)
|
||||
.where(JPAExpressions.selectOne().from(sub).where(sub.feed.eq(FEED)).notExists())
|
||||
.limit(max)
|
||||
.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,40 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.commafeed.backend.model.QFeedEntry;
|
||||
import com.commafeed.backend.model.QFeedEntryContent;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryContentDAO extends GenericDAO<FeedEntryContent> {
|
||||
|
||||
private static final QFeedEntryContent CONTENT = QFeedEntryContent.feedEntryContent;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final QFeedEntryContent CONTENT = QFeedEntryContent.feedEntryContent;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
|
||||
public FeedEntryContentDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntryContent.class);
|
||||
}
|
||||
public FeedEntryContentDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntryContent.class);
|
||||
}
|
||||
|
||||
public List<FeedEntryContent> findExisting(String contentHash, String titleHash) {
|
||||
return query().select(CONTENT).from(CONTENT).where(CONTENT.contentHash.eq(contentHash), CONTENT.titleHash.eq(titleHash)).fetch();
|
||||
}
|
||||
public List<FeedEntryContent> findExisting(String contentHash, String titleHash) {
|
||||
return query().select(CONTENT)
|
||||
.from(CONTENT)
|
||||
.where(CONTENT.contentHash.eq(contentHash), CONTENT.titleHash.eq(titleHash))
|
||||
.fetch();
|
||||
}
|
||||
|
||||
public long deleteWithoutEntries(int max) {
|
||||
List<Long> ids = query().select(CONTENT.id)
|
||||
.from(CONTENT)
|
||||
.leftJoin(ENTRY)
|
||||
.on(ENTRY.content.id.eq(CONTENT.id))
|
||||
.where(ENTRY.id.isNull())
|
||||
.limit(max)
|
||||
.fetch();
|
||||
return deleteQuery(CONTENT).where(CONTENT.id.in(ids)).execute();
|
||||
}
|
||||
public long deleteWithoutEntries(int max) {
|
||||
List<Long> ids =
|
||||
query().select(CONTENT.id)
|
||||
.from(CONTENT)
|
||||
.leftJoin(ENTRY)
|
||||
.on(ENTRY.content.id.eq(CONTENT.id))
|
||||
.where(ENTRY.id.isNull())
|
||||
.limit(max)
|
||||
.fetch();
|
||||
return deleteQuery(CONTENT).where(CONTENT.id.in(ids)).execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.QFeedEntry;
|
||||
@@ -17,83 +8,99 @@ import com.querydsl.core.Tuple;
|
||||
import com.querydsl.core.types.dsl.NumberExpression;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryDAO extends GenericDAO<FeedEntry> {
|
||||
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final int IN_CLAUSE_BATCH_SIZE = 1000;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final int IN_CLAUSE_BATCH_SIZE = 1000;
|
||||
|
||||
public FeedEntryDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntry.class);
|
||||
}
|
||||
public FeedEntryDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntry.class);
|
||||
}
|
||||
|
||||
public FeedEntry findExisting(String guidHash, Feed feed) {
|
||||
return query().select(ENTRY).from(ENTRY).where(ENTRY.guidHash.eq(guidHash), ENTRY.feed.eq(feed)).limit(1).fetchOne();
|
||||
}
|
||||
public FeedEntry findExisting(String guidHash, Feed feed) {
|
||||
return query().select(ENTRY)
|
||||
.from(ENTRY)
|
||||
.where(ENTRY.guidHash.eq(guidHash), ENTRY.feed.eq(feed))
|
||||
.limit(1)
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
public Set<String> findExistingGuidHashes(Set<String> guidHashes, Feed feed) {
|
||||
if (guidHashes.isEmpty()) {
|
||||
return Set.of();
|
||||
}
|
||||
public Set<String> findExistingGuidHashes(Set<String> guidHashes, Feed feed) {
|
||||
if (guidHashes.isEmpty()) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
Set<String> result = new HashSet<>();
|
||||
for (List<String> batch : Lists.partition(new ArrayList<>(guidHashes), IN_CLAUSE_BATCH_SIZE)) {
|
||||
result.addAll(query().select(ENTRY.guidHash).from(ENTRY).where(ENTRY.feed.eq(feed), ENTRY.guidHash.in(batch)).fetch());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Set<String> result = new HashSet<>();
|
||||
for (List<String> batch :
|
||||
Lists.partition(new ArrayList<>(guidHashes), IN_CLAUSE_BATCH_SIZE)) {
|
||||
result.addAll(
|
||||
query().select(ENTRY.guidHash)
|
||||
.from(ENTRY)
|
||||
.where(ENTRY.feed.eq(feed), ENTRY.guidHash.in(batch))
|
||||
.fetch());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<FeedCapacity> findFeedsExceedingCapacity(long maxCapacity, long max, boolean keepStarredEntries) {
|
||||
NumberExpression<Long> count = ENTRY.id.count();
|
||||
JPAQuery<Tuple> query = query().select(ENTRY.feed.id, count).from(ENTRY);
|
||||
public List<FeedCapacity> findFeedsExceedingCapacity(
|
||||
long maxCapacity, long max, boolean keepStarredEntries) {
|
||||
NumberExpression<Long> count = ENTRY.id.count();
|
||||
JPAQuery<Tuple> query = query().select(ENTRY.feed.id, count).from(ENTRY);
|
||||
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
|
||||
return query.groupBy(ENTRY.feed)
|
||||
.having(count.gt(maxCapacity))
|
||||
.limit(max)
|
||||
.fetch()
|
||||
.stream()
|
||||
.map(t -> new FeedCapacity(t.get(ENTRY.feed.id), t.get(count)))
|
||||
.toList();
|
||||
}
|
||||
return query.groupBy(ENTRY.feed).having(count.gt(maxCapacity)).limit(max).fetch().stream()
|
||||
.map(t -> new FeedCapacity(t.get(ENTRY.feed.id), t.get(count)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public int delete(Long feedId, long max) {
|
||||
List<FeedEntry> list = query().selectFrom(ENTRY).where(ENTRY.feed.id.eq(feedId)).limit(max).fetch();
|
||||
return delete(list);
|
||||
}
|
||||
public int delete(Long feedId, long max) {
|
||||
List<FeedEntry> list =
|
||||
query().selectFrom(ENTRY).where(ENTRY.feed.id.eq(feedId)).limit(max).fetch();
|
||||
return delete(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete entries older than a certain date
|
||||
*/
|
||||
public int deleteEntriesOlderThan(Instant olderThan, long max, boolean keepStarredEntries) {
|
||||
JPAQuery<FeedEntry> query = query().selectFrom(ENTRY)
|
||||
.where(ENTRY.published.lt(olderThan))
|
||||
.orderBy(ENTRY.published.asc())
|
||||
.limit(max);
|
||||
/** Delete entries older than a certain date */
|
||||
public int deleteEntriesOlderThan(Instant olderThan, long max, boolean keepStarredEntries) {
|
||||
JPAQuery<FeedEntry> query =
|
||||
query().selectFrom(ENTRY)
|
||||
.where(ENTRY.published.lt(olderThan))
|
||||
.orderBy(ENTRY.published.asc())
|
||||
.limit(max);
|
||||
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
|
||||
return delete(query.fetch());
|
||||
}
|
||||
return delete(query.fetch());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the oldest entries of a feed
|
||||
*/
|
||||
public int deleteOldEntries(Long feedId, long max, boolean keepStarredEntries) {
|
||||
JPAQuery<FeedEntry> query = query().selectFrom(ENTRY).where(ENTRY.feed.id.eq(feedId)).orderBy(ENTRY.published.asc()).limit(max);
|
||||
/** Delete the oldest entries of a feed */
|
||||
public int deleteOldEntries(Long feedId, long max, boolean keepStarredEntries) {
|
||||
JPAQuery<FeedEntry> query =
|
||||
query().selectFrom(ENTRY)
|
||||
.where(ENTRY.feed.id.eq(feedId))
|
||||
.orderBy(ENTRY.published.asc())
|
||||
.limit(max);
|
||||
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
if (keepStarredEntries) {
|
||||
query.where(Predicates.isNotStarred(ENTRY));
|
||||
}
|
||||
|
||||
return delete(query.fetch());
|
||||
}
|
||||
return delete(query.fetch());
|
||||
}
|
||||
|
||||
public record FeedCapacity(Long id, Long capacity) {
|
||||
}
|
||||
public record FeedCapacity(Long id, Long capacity) {}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.feed.FeedEntryKeyword;
|
||||
import com.commafeed.backend.feed.FeedEntryKeyword.Mode;
|
||||
@@ -33,263 +22,318 @@ import com.querydsl.core.types.dsl.Expressions;
|
||||
import com.querydsl.core.types.dsl.NumberExpression;
|
||||
import com.querydsl.jpa.impl.JPAQuery;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
|
||||
|
||||
private static final QFeedEntryStatus STATUS = QFeedEntryStatus.feedEntryStatus;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final QFeed FEED = QFeed.feed;
|
||||
private static final QFeedEntryContent CONTENT = QFeedEntryContent.feedEntryContent;
|
||||
private static final QFeedEntryTag TAG = QFeedEntryTag.feedEntryTag;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
private static final QFeedEntryStatus STATUS = QFeedEntryStatus.feedEntryStatus;
|
||||
private static final QFeedEntry ENTRY = QFeedEntry.feedEntry;
|
||||
private static final QFeed FEED = QFeed.feed;
|
||||
private static final QFeedEntryContent CONTENT = QFeedEntryContent.feedEntryContent;
|
||||
private static final QFeedEntryTag TAG = QFeedEntryTag.feedEntryTag;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
|
||||
private final FeedEntryTagDAO feedEntryTagDAO;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final FeedEntryTagDAO feedEntryTagDAO;
|
||||
private final CommaFeedConfiguration config;
|
||||
|
||||
public FeedEntryStatusDAO(EntityManager entityManager, FeedEntryTagDAO feedEntryTagDAO, CommaFeedConfiguration config) {
|
||||
super(entityManager, FeedEntryStatus.class);
|
||||
this.feedEntryTagDAO = feedEntryTagDAO;
|
||||
this.config = config;
|
||||
}
|
||||
public FeedEntryStatusDAO(
|
||||
EntityManager entityManager,
|
||||
FeedEntryTagDAO feedEntryTagDAO,
|
||||
CommaFeedConfiguration config) {
|
||||
super(entityManager, FeedEntryStatus.class);
|
||||
this.feedEntryTagDAO = feedEntryTagDAO;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public FeedEntryStatus getStatus(User user, FeedSubscription sub, FeedEntry entry) {
|
||||
List<FeedEntryStatus> statuses = query().selectFrom(STATUS).where(STATUS.entry.eq(entry), STATUS.subscription.eq(sub)).fetch();
|
||||
FeedEntryStatus status = statuses.stream().findFirst().orElse(null);
|
||||
return handleStatus(user, status, sub, entry);
|
||||
}
|
||||
public FeedEntryStatus getStatus(User user, FeedSubscription sub, FeedEntry entry) {
|
||||
List<FeedEntryStatus> statuses =
|
||||
query().selectFrom(STATUS)
|
||||
.where(STATUS.entry.eq(entry), STATUS.subscription.eq(sub))
|
||||
.fetch();
|
||||
FeedEntryStatus status = statuses.stream().findFirst().orElse(null);
|
||||
return handleStatus(user, status, sub, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates an artificial "unread" status if status is null
|
||||
*/
|
||||
private FeedEntryStatus handleStatus(User user, FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
|
||||
if (status == null) {
|
||||
Instant statusesInstantThreshold = config.database().cleanup().statusesInstantThreshold();
|
||||
boolean read = statusesInstantThreshold != null && entry.getPublished().isBefore(statusesInstantThreshold);
|
||||
status = new FeedEntryStatus(user, sub, entry);
|
||||
status.setRead(read);
|
||||
status.setMarkable(!read);
|
||||
} else {
|
||||
status.setMarkable(true);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
/** creates an artificial "unread" status if status is null */
|
||||
private FeedEntryStatus handleStatus(
|
||||
User user, FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
|
||||
if (status == null) {
|
||||
Instant statusesInstantThreshold =
|
||||
config.database().cleanup().statusesInstantThreshold();
|
||||
boolean read =
|
||||
statusesInstantThreshold != null
|
||||
&& entry.getPublished().isBefore(statusesInstantThreshold);
|
||||
status = new FeedEntryStatus(user, sub, entry);
|
||||
status.setRead(read);
|
||||
status.setMarkable(!read);
|
||||
} else {
|
||||
status.setMarkable(true);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
private void fetchTags(User user, List<FeedEntryStatus> statuses) {
|
||||
Map<Long, List<FeedEntryTag>> tagsByEntryIds = feedEntryTagDAO.findByEntries(user,
|
||||
statuses.stream().map(FeedEntryStatus::getEntry).toList());
|
||||
for (FeedEntryStatus status : statuses) {
|
||||
List<FeedEntryTag> tags = tagsByEntryIds.get(status.getEntry().getId());
|
||||
status.setTags(tags == null ? List.of() : tags);
|
||||
}
|
||||
}
|
||||
private void fetchTags(User user, List<FeedEntryStatus> statuses) {
|
||||
Map<Long, List<FeedEntryTag>> tagsByEntryIds =
|
||||
feedEntryTagDAO.findByEntries(
|
||||
user, statuses.stream().map(FeedEntryStatus::getEntry).toList());
|
||||
for (FeedEntryStatus status : statuses) {
|
||||
List<FeedEntryTag> tags = tagsByEntryIds.get(status.getEntry().getId());
|
||||
status.setTags(tags == null ? List.of() : tags);
|
||||
}
|
||||
}
|
||||
|
||||
public List<FeedEntryStatus> findStarred(User user, Instant newerThan, int offset, int limit, ReadingOrder order,
|
||||
boolean includeContent) {
|
||||
JPAQuery<FeedEntryStatus> query = query().selectFrom(STATUS).where(STATUS.user.eq(user), STATUS.starred.isTrue());
|
||||
if (includeContent) {
|
||||
query.join(STATUS.entry).fetchJoin();
|
||||
query.join(STATUS.entry.content).fetchJoin();
|
||||
}
|
||||
public List<FeedEntryStatus> findStarred(
|
||||
User user,
|
||||
List<FeedEntryKeyword> keywords,
|
||||
Instant newerThan,
|
||||
int offset,
|
||||
int limit,
|
||||
ReadingOrder order,
|
||||
boolean includeContent) {
|
||||
JPAQuery<FeedEntryStatus> query =
|
||||
query().selectFrom(STATUS).where(STATUS.user.eq(user), STATUS.starred.isTrue());
|
||||
if (includeContent || CollectionUtils.isNotEmpty(keywords)) {
|
||||
query.join(STATUS.entry).fetchJoin();
|
||||
query.join(STATUS.entry.content, CONTENT).fetchJoin();
|
||||
}
|
||||
|
||||
if (newerThan != null) {
|
||||
query.where(STATUS.entryInserted.gt(newerThan));
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(keywords)) {
|
||||
applyKeywordsFilter(query, keywords);
|
||||
}
|
||||
|
||||
if (order == ReadingOrder.ASC) {
|
||||
query.orderBy(STATUS.entryPublished.asc(), STATUS.id.asc());
|
||||
} else {
|
||||
query.orderBy(STATUS.entryPublished.desc(), STATUS.id.desc());
|
||||
}
|
||||
if (newerThan != null) {
|
||||
query.where(STATUS.entryInserted.gt(newerThan));
|
||||
}
|
||||
|
||||
if (offset > -1) {
|
||||
query.offset(offset);
|
||||
}
|
||||
if (order == ReadingOrder.ASC) {
|
||||
query.orderBy(STATUS.entryPublished.asc(), STATUS.id.asc());
|
||||
} else {
|
||||
query.orderBy(STATUS.entryPublished.desc(), STATUS.id.desc());
|
||||
}
|
||||
|
||||
if (limit > -1) {
|
||||
query.limit(limit);
|
||||
}
|
||||
if (offset > -1) {
|
||||
query.offset(offset);
|
||||
}
|
||||
|
||||
setTimeout(query, config.database().queryTimeout());
|
||||
if (limit > -1) {
|
||||
query.limit(limit);
|
||||
}
|
||||
|
||||
List<FeedEntryStatus> statuses = query.fetch();
|
||||
statuses.forEach(s -> s.setMarkable(true));
|
||||
if (includeContent) {
|
||||
fetchTags(user, statuses);
|
||||
}
|
||||
setTimeout(query, config.database().queryTimeout());
|
||||
|
||||
return statuses;
|
||||
}
|
||||
List<FeedEntryStatus> statuses = query.fetch();
|
||||
statuses.forEach(s -> s.setMarkable(true));
|
||||
if (includeContent) {
|
||||
fetchTags(user, statuses);
|
||||
}
|
||||
|
||||
public List<FeedEntryStatus> findBySubscriptions(User user, List<FeedSubscription> subs, boolean unreadOnly,
|
||||
List<FeedEntryKeyword> keywords, Instant newerThan, int offset, int limit, ReadingOrder order, boolean includeContent,
|
||||
String tag, Long minEntryId, Long maxEntryId) {
|
||||
Map<Long, List<FeedSubscription>> subsByFeedId = subs.stream().collect(Collectors.groupingBy(s -> s.getFeed().getId()));
|
||||
return statuses;
|
||||
}
|
||||
|
||||
JPAQuery<Tuple> query = query().select(ENTRY, STATUS).from(ENTRY);
|
||||
query.leftJoin(ENTRY.statuses, STATUS).on(STATUS.subscription.in(subs));
|
||||
query.where(ENTRY.feed.id.in(subsByFeedId.keySet()));
|
||||
public List<FeedEntryStatus> findBySubscriptions(
|
||||
User user,
|
||||
List<FeedSubscription> subs,
|
||||
boolean unreadOnly,
|
||||
List<FeedEntryKeyword> keywords,
|
||||
Instant newerThan,
|
||||
int offset,
|
||||
int limit,
|
||||
ReadingOrder order,
|
||||
boolean includeContent,
|
||||
String tag,
|
||||
Long minEntryId,
|
||||
Long maxEntryId) {
|
||||
Map<Long, List<FeedSubscription>> subsByFeedId =
|
||||
subs.stream().collect(Collectors.groupingBy(s -> s.getFeed().getId()));
|
||||
|
||||
if (includeContent || CollectionUtils.isNotEmpty(keywords)) {
|
||||
query.join(ENTRY.content, CONTENT).fetchJoin();
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(keywords)) {
|
||||
for (FeedEntryKeyword keyword : keywords) {
|
||||
BooleanBuilder or = new BooleanBuilder();
|
||||
or.or(CONTENT.content.containsIgnoreCase(keyword.keyword()));
|
||||
or.or(CONTENT.title.containsIgnoreCase(keyword.keyword()));
|
||||
if (keyword.mode() == Mode.EXCLUDE) {
|
||||
or.not();
|
||||
}
|
||||
query.where(or);
|
||||
}
|
||||
}
|
||||
JPAQuery<Tuple> query = query().select(ENTRY, STATUS).from(ENTRY);
|
||||
query.leftJoin(ENTRY.statuses, STATUS).on(STATUS.subscription.in(subs));
|
||||
query.where(ENTRY.feed.id.in(subsByFeedId.keySet()));
|
||||
|
||||
if (unreadOnly && tag == null) {
|
||||
query.where(buildUnreadPredicate());
|
||||
}
|
||||
if (includeContent || CollectionUtils.isNotEmpty(keywords)) {
|
||||
query.join(ENTRY.content, CONTENT).fetchJoin();
|
||||
}
|
||||
|
||||
if (tag != null) {
|
||||
BooleanBuilder and = new BooleanBuilder();
|
||||
and.and(TAG.user.id.eq(user.getId()));
|
||||
and.and(TAG.name.eq(tag));
|
||||
query.join(ENTRY.tags, TAG).on(and);
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(keywords)) {
|
||||
applyKeywordsFilter(query, keywords);
|
||||
}
|
||||
|
||||
if (newerThan != null) {
|
||||
query.where(ENTRY.inserted.goe(newerThan));
|
||||
}
|
||||
if (unreadOnly && tag == null) {
|
||||
query.where(buildUnreadPredicate());
|
||||
}
|
||||
|
||||
if (minEntryId != null) {
|
||||
query.where(ENTRY.id.gt(minEntryId));
|
||||
}
|
||||
if (tag != null) {
|
||||
BooleanBuilder and = new BooleanBuilder();
|
||||
and.and(TAG.user.id.eq(user.getId()));
|
||||
and.and(TAG.name.eq(tag));
|
||||
query.join(ENTRY.tags, TAG).on(and);
|
||||
}
|
||||
|
||||
if (maxEntryId != null) {
|
||||
query.where(ENTRY.id.lt(maxEntryId));
|
||||
}
|
||||
if (newerThan != null) {
|
||||
query.where(ENTRY.inserted.goe(newerThan));
|
||||
}
|
||||
|
||||
if (order != null) {
|
||||
if (order == ReadingOrder.ASC) {
|
||||
query.orderBy(ENTRY.published.asc(), ENTRY.id.asc());
|
||||
} else {
|
||||
query.orderBy(ENTRY.published.desc(), ENTRY.id.desc());
|
||||
}
|
||||
}
|
||||
if (minEntryId != null) {
|
||||
query.where(ENTRY.id.gt(minEntryId));
|
||||
}
|
||||
|
||||
if (offset > -1) {
|
||||
query.offset(offset);
|
||||
}
|
||||
if (maxEntryId != null) {
|
||||
query.where(ENTRY.id.lt(maxEntryId));
|
||||
}
|
||||
|
||||
if (limit > -1) {
|
||||
query.limit(limit);
|
||||
}
|
||||
if (order != null) {
|
||||
if (order == ReadingOrder.ASC) {
|
||||
query.orderBy(ENTRY.published.asc(), ENTRY.id.asc());
|
||||
} else {
|
||||
query.orderBy(ENTRY.published.desc(), ENTRY.id.desc());
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(query, config.database().queryTimeout());
|
||||
if (offset > -1) {
|
||||
query.offset(offset);
|
||||
}
|
||||
|
||||
List<FeedEntryStatus> statuses = new ArrayList<>();
|
||||
List<Tuple> tuples = query.fetch();
|
||||
for (Tuple tuple : tuples) {
|
||||
FeedEntry e = tuple.get(ENTRY);
|
||||
FeedEntryStatus s = tuple.get(STATUS);
|
||||
for (FeedSubscription sub : subsByFeedId.get(e.getFeed().getId())) {
|
||||
statuses.add(handleStatus(user, s, sub, e));
|
||||
}
|
||||
}
|
||||
if (limit > -1) {
|
||||
query.limit(limit);
|
||||
}
|
||||
|
||||
if (includeContent) {
|
||||
fetchTags(user, statuses);
|
||||
}
|
||||
setTimeout(query, config.database().queryTimeout());
|
||||
|
||||
return statuses;
|
||||
}
|
||||
List<FeedEntryStatus> statuses = new ArrayList<>();
|
||||
List<Tuple> tuples = query.fetch();
|
||||
for (Tuple tuple : tuples) {
|
||||
FeedEntry e = tuple.get(ENTRY);
|
||||
FeedEntryStatus s = tuple.get(STATUS);
|
||||
for (FeedSubscription sub : subsByFeedId.get(e.getFeed().getId())) {
|
||||
statuses.add(handleStatus(user, s, sub, e));
|
||||
}
|
||||
}
|
||||
|
||||
public UnreadCount getUnreadCount(FeedSubscription sub) {
|
||||
JPAQuery<Tuple> query = query().select(ENTRY.count(), ENTRY.published.max())
|
||||
.from(ENTRY)
|
||||
.leftJoin(ENTRY.statuses, STATUS)
|
||||
.on(STATUS.subscription.eq(sub))
|
||||
.where(ENTRY.feed.eq(sub.getFeed()))
|
||||
.where(buildUnreadPredicate());
|
||||
if (includeContent) {
|
||||
fetchTags(user, statuses);
|
||||
}
|
||||
|
||||
Tuple tuple = query.fetchOne();
|
||||
Long count = tuple.get(ENTRY.count());
|
||||
Instant published = tuple.get(ENTRY.published.max());
|
||||
return new UnreadCount(sub.getId(), count == null ? 0 : count, published);
|
||||
}
|
||||
return statuses;
|
||||
}
|
||||
|
||||
private BooleanBuilder buildUnreadPredicate() {
|
||||
BooleanBuilder or = new BooleanBuilder();
|
||||
or.or(STATUS.read.isNull());
|
||||
or.or(STATUS.read.isFalse());
|
||||
private void applyKeywordsFilter(JPAQuery<?> query, List<FeedEntryKeyword> keywords) {
|
||||
for (FeedEntryKeyword keyword : keywords) {
|
||||
BooleanBuilder or = new BooleanBuilder();
|
||||
or.or(CONTENT.content.containsIgnoreCase(keyword.keyword()));
|
||||
or.or(CONTENT.title.containsIgnoreCase(keyword.keyword()));
|
||||
if (keyword.mode() == Mode.EXCLUDE) {
|
||||
or.not();
|
||||
}
|
||||
query.where(or);
|
||||
}
|
||||
}
|
||||
|
||||
Instant statusesInstantThreshold = config.database().cleanup().statusesInstantThreshold();
|
||||
if (statusesInstantThreshold != null) {
|
||||
return or.and(ENTRY.published.goe(statusesInstantThreshold));
|
||||
} else {
|
||||
return or;
|
||||
}
|
||||
}
|
||||
public UnreadCount getUnreadCount(FeedSubscription sub) {
|
||||
JPAQuery<Tuple> query =
|
||||
query().select(ENTRY.count(), ENTRY.published.max())
|
||||
.from(ENTRY)
|
||||
.leftJoin(ENTRY.statuses, STATUS)
|
||||
.on(STATUS.subscription.eq(sub))
|
||||
.where(ENTRY.feed.eq(sub.getFeed()))
|
||||
.where(buildUnreadPredicate());
|
||||
|
||||
public long deleteOldStatuses(Instant olderThan, int limit) {
|
||||
List<Long> ids = query().select(STATUS.id)
|
||||
.from(STATUS)
|
||||
.where(STATUS.entryInserted.lt(olderThan), STATUS.starred.isFalse())
|
||||
.limit(limit)
|
||||
.fetch();
|
||||
return deleteQuery(STATUS).where(STATUS.id.in(ids)).execute();
|
||||
}
|
||||
Tuple tuple = query.fetchOne();
|
||||
Long count = tuple.get(ENTRY.count());
|
||||
Instant published = tuple.get(ENTRY.published.max());
|
||||
return new UnreadCount(sub.getId(), count == null ? 0 : count, published);
|
||||
}
|
||||
|
||||
public long autoMarkAsRead(int limit) {
|
||||
Instant now = Instant.now();
|
||||
private BooleanBuilder buildUnreadPredicate() {
|
||||
BooleanBuilder or = new BooleanBuilder();
|
||||
or.or(STATUS.read.isNull());
|
||||
or.or(STATUS.read.isFalse());
|
||||
|
||||
BooleanBuilder where = new BooleanBuilder();
|
||||
where.and(SUBSCRIPTION.autoMarkAsReadAfterDays.isNotNull());
|
||||
where.and(SUBSCRIPTION.autoMarkAsReadAfterDays.gt(0));
|
||||
Instant statusesInstantThreshold = config.database().cleanup().statusesInstantThreshold();
|
||||
if (statusesInstantThreshold != null) {
|
||||
return or.and(ENTRY.published.goe(statusesInstantThreshold));
|
||||
} else {
|
||||
return or;
|
||||
}
|
||||
}
|
||||
|
||||
NumberExpression<Integer> daysDiff = Expressions.numberTemplate(Integer.class, "TIMESTAMPDIFF(DAY, {0}, {1})", ENTRY.published,
|
||||
now);
|
||||
where.and(daysDiff.goe(SUBSCRIPTION.autoMarkAsReadAfterDays));
|
||||
public long deleteOldStatuses(Instant olderThan, int limit) {
|
||||
List<Long> ids =
|
||||
query().select(STATUS.id)
|
||||
.from(STATUS)
|
||||
.where(STATUS.entryInserted.lt(olderThan), STATUS.starred.isFalse())
|
||||
.limit(limit)
|
||||
.fetch();
|
||||
return deleteQuery(STATUS).where(STATUS.id.in(ids)).execute();
|
||||
}
|
||||
|
||||
where.and(buildUnreadPredicate());
|
||||
public long autoMarkAsRead(int limit) {
|
||||
Instant now = Instant.now();
|
||||
|
||||
List<Tuple> tuples = query().select(ENTRY, STATUS, SUBSCRIPTION)
|
||||
.from(ENTRY)
|
||||
.join(ENTRY.feed, FEED)
|
||||
.join(SUBSCRIPTION)
|
||||
.on(SUBSCRIPTION.feed.eq(FEED))
|
||||
.leftJoin(ENTRY.statuses, STATUS)
|
||||
.on(STATUS.subscription.eq(SUBSCRIPTION))
|
||||
.where(where)
|
||||
.limit(limit)
|
||||
.fetch();
|
||||
BooleanBuilder where = new BooleanBuilder();
|
||||
where.and(SUBSCRIPTION.autoMarkAsReadAfterDays.isNotNull());
|
||||
where.and(SUBSCRIPTION.autoMarkAsReadAfterDays.gt(0));
|
||||
|
||||
long updated = 0;
|
||||
NumberExpression<Integer> daysDiff =
|
||||
Expressions.numberTemplate(
|
||||
Integer.class, "TIMESTAMPDIFF(DAY, {0}, {1})", ENTRY.published, now);
|
||||
where.and(daysDiff.goe(SUBSCRIPTION.autoMarkAsReadAfterDays));
|
||||
|
||||
// Update existing statuses
|
||||
List<Long> statusIdsToUpdate = tuples.stream()
|
||||
.map(t -> t.get(STATUS))
|
||||
.filter(s -> s != null && s.getId() != null)
|
||||
.map(FeedEntryStatus::getId)
|
||||
.distinct()
|
||||
.toList();
|
||||
where.and(buildUnreadPredicate());
|
||||
|
||||
if (!statusIdsToUpdate.isEmpty()) {
|
||||
updated += updateQuery(STATUS).where(STATUS.id.in(statusIdsToUpdate)).set(STATUS.read, true).execute();
|
||||
}
|
||||
List<Tuple> tuples =
|
||||
query().select(ENTRY, STATUS, SUBSCRIPTION)
|
||||
.from(ENTRY)
|
||||
.join(ENTRY.feed, FEED)
|
||||
.join(SUBSCRIPTION)
|
||||
.on(SUBSCRIPTION.feed.eq(FEED))
|
||||
.leftJoin(ENTRY.statuses, STATUS)
|
||||
.on(STATUS.subscription.eq(SUBSCRIPTION))
|
||||
.where(where)
|
||||
.limit(limit)
|
||||
.fetch();
|
||||
|
||||
// Insert new statuses for entries without existing status
|
||||
for (Tuple tuple : tuples) {
|
||||
FeedEntryStatus status = tuple.get(STATUS);
|
||||
if (status == null || status.getId() == null) {
|
||||
FeedEntry entry = tuple.get(ENTRY);
|
||||
FeedSubscription sub = tuple.get(SUBSCRIPTION);
|
||||
FeedEntryStatus newStatus = new FeedEntryStatus(sub.getUser(), sub, entry);
|
||||
newStatus.setRead(true);
|
||||
persist(newStatus);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
long updated = 0;
|
||||
|
||||
return updated;
|
||||
}
|
||||
// Update existing statuses
|
||||
List<Long> statusIdsToUpdate =
|
||||
tuples.stream()
|
||||
.map(t -> t.get(STATUS))
|
||||
.filter(s -> s != null && s.getId() != null)
|
||||
.map(FeedEntryStatus::getId)
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
if (!statusIdsToUpdate.isEmpty()) {
|
||||
updated +=
|
||||
updateQuery(STATUS)
|
||||
.where(STATUS.id.in(statusIdsToUpdate))
|
||||
.set(STATUS.read, true)
|
||||
.execute();
|
||||
}
|
||||
|
||||
// Insert new statuses for entries without existing status
|
||||
for (Tuple tuple : tuples) {
|
||||
FeedEntryStatus status = tuple.get(STATUS);
|
||||
if (status == null || status.getId() == null) {
|
||||
FeedEntry entry = tuple.get(ENTRY);
|
||||
FeedSubscription sub = tuple.get(SUBSCRIPTION);
|
||||
FeedEntryStatus newStatus = new FeedEntryStatus(sub.getUser(), sub, entry);
|
||||
newStatus.setRead(true);
|
||||
persist(newStatus);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryTag;
|
||||
import com.commafeed.backend.model.QFeedEntryTag;
|
||||
import com.commafeed.backend.model.User;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class FeedEntryTagDAO extends GenericDAO<FeedEntryTag> {
|
||||
|
||||
private static final QFeedEntryTag TAG = QFeedEntryTag.feedEntryTag;
|
||||
private static final QFeedEntryTag TAG = QFeedEntryTag.feedEntryTag;
|
||||
|
||||
public FeedEntryTagDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntryTag.class);
|
||||
}
|
||||
public FeedEntryTagDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedEntryTag.class);
|
||||
}
|
||||
|
||||
public List<String> findByUser(User user) {
|
||||
return query().selectDistinct(TAG.name).from(TAG).where(TAG.user.eq(user)).fetch();
|
||||
}
|
||||
public List<String> findByUser(User user) {
|
||||
return query().selectDistinct(TAG.name).from(TAG).where(TAG.user.eq(user)).fetch();
|
||||
}
|
||||
|
||||
public List<FeedEntryTag> findByEntry(User user, FeedEntry entry) {
|
||||
return query().selectFrom(TAG).where(TAG.user.eq(user), TAG.entry.eq(entry)).fetch();
|
||||
}
|
||||
public List<FeedEntryTag> findByEntry(User user, FeedEntry entry) {
|
||||
return query().selectFrom(TAG).where(TAG.user.eq(user), TAG.entry.eq(entry)).fetch();
|
||||
}
|
||||
|
||||
public Map<Long, List<FeedEntryTag>> findByEntries(User user, List<FeedEntry> entries) {
|
||||
return query().selectFrom(TAG)
|
||||
.where(TAG.user.eq(user), TAG.entry.in(entries))
|
||||
.fetch()
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(t -> t.getEntry().getId()));
|
||||
}
|
||||
public Map<Long, List<FeedEntryTag>> findByEntries(User user, List<FeedEntry> entries) {
|
||||
return query()
|
||||
.selectFrom(TAG)
|
||||
.where(TAG.user.eq(user), TAG.entry.in(entries))
|
||||
.fetch()
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(t -> t.getEntry().getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import com.commafeed.backend.model.AbstractModel;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.Models;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.querydsl.jpa.JPQLQuery;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
@@ -15,117 +19,128 @@ import org.hibernate.event.spi.PostCommitInsertEventListener;
|
||||
import org.hibernate.event.spi.PostInsertEvent;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
import com.commafeed.backend.model.AbstractModel;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.Models;
|
||||
import com.commafeed.backend.model.QFeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.querydsl.jpa.JPQLQuery;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
|
||||
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
private static final QFeedSubscription SUBSCRIPTION = QFeedSubscription.feedSubscription;
|
||||
|
||||
private final EntityManager entityManager;
|
||||
private final EntityManager entityManager;
|
||||
|
||||
public FeedSubscriptionDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedSubscription.class);
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
public FeedSubscriptionDAO(EntityManager entityManager) {
|
||||
super(entityManager, FeedSubscription.class);
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
public void onPostCommitInsert(Consumer<FeedSubscription> consumer) {
|
||||
entityManager.unwrap(SharedSessionContractImplementor.class)
|
||||
.getFactory()
|
||||
.getServiceRegistry()
|
||||
.getService(EventListenerRegistry.class)
|
||||
.getEventListenerGroup(EventType.POST_COMMIT_INSERT)
|
||||
.appendListener(new PostCommitInsertEventListener() {
|
||||
@Override
|
||||
public void onPostInsert(PostInsertEvent event) {
|
||||
if (event.getEntity() instanceof FeedSubscription s) {
|
||||
consumer.accept(s);
|
||||
}
|
||||
}
|
||||
public void onPostCommitInsert(Consumer<FeedSubscription> consumer) {
|
||||
entityManager
|
||||
.unwrap(SharedSessionContractImplementor.class)
|
||||
.getFactory()
|
||||
.getServiceRegistry()
|
||||
.getService(EventListenerRegistry.class)
|
||||
.getEventListenerGroup(EventType.POST_COMMIT_INSERT)
|
||||
.appendListener(
|
||||
new PostCommitInsertEventListener() {
|
||||
@Override
|
||||
public void onPostInsert(PostInsertEvent event) {
|
||||
if (event.getEntity() instanceof FeedSubscription s) {
|
||||
consumer.accept(s);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresPostCommitHandling(EntityPersister persister) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean requiresPostCommitHandling(EntityPersister persister) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostInsertCommitFailed(PostInsertEvent event) {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onPostInsertCommitFailed(PostInsertEvent event) {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public FeedSubscription findById(User user, Long id) {
|
||||
List<FeedSubscription> subs = query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user), SUBSCRIPTION.id.eq(id))
|
||||
.leftJoin(SUBSCRIPTION.feed)
|
||||
.fetchJoin()
|
||||
.leftJoin(SUBSCRIPTION.category)
|
||||
.fetchJoin()
|
||||
.fetch();
|
||||
FeedSubscription sub = subs.stream().findFirst().orElse(null);
|
||||
return initRelations(sub);
|
||||
}
|
||||
public FeedSubscription findById(User user, Long id) {
|
||||
List<FeedSubscription> subs =
|
||||
query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user), SUBSCRIPTION.id.eq(id))
|
||||
.leftJoin(SUBSCRIPTION.feed)
|
||||
.fetchJoin()
|
||||
.leftJoin(SUBSCRIPTION.category)
|
||||
.fetchJoin()
|
||||
.fetch();
|
||||
FeedSubscription sub = subs.stream().findFirst().orElse(null);
|
||||
return initRelations(sub);
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findByFeed(Feed feed) {
|
||||
return query().selectFrom(SUBSCRIPTION).where(SUBSCRIPTION.feed.eq(feed)).fetch();
|
||||
}
|
||||
public List<FeedSubscription> findByFeed(Feed feed) {
|
||||
return query().selectFrom(SUBSCRIPTION).where(SUBSCRIPTION.feed.eq(feed)).fetch();
|
||||
}
|
||||
|
||||
public FeedSubscription findByFeed(User user, Feed feed) {
|
||||
List<FeedSubscription> subs = query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user), SUBSCRIPTION.feed.eq(feed))
|
||||
.fetch();
|
||||
FeedSubscription sub = subs.stream().findFirst().orElse(null);
|
||||
return initRelations(sub);
|
||||
}
|
||||
public FeedSubscription findByFeed(User user, Feed feed) {
|
||||
List<FeedSubscription> subs =
|
||||
query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user), SUBSCRIPTION.feed.eq(feed))
|
||||
.fetch();
|
||||
FeedSubscription sub = subs.stream().findFirst().orElse(null);
|
||||
return initRelations(sub);
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findAll(User user) {
|
||||
List<FeedSubscription> subs = query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user))
|
||||
.leftJoin(SUBSCRIPTION.feed)
|
||||
.fetchJoin()
|
||||
.leftJoin(SUBSCRIPTION.category)
|
||||
.fetchJoin()
|
||||
.fetch();
|
||||
return initRelations(subs);
|
||||
}
|
||||
public List<FeedSubscription> findAll(User user) {
|
||||
List<FeedSubscription> subs =
|
||||
query().selectFrom(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user))
|
||||
.leftJoin(SUBSCRIPTION.feed)
|
||||
.fetchJoin()
|
||||
.leftJoin(SUBSCRIPTION.category)
|
||||
.fetchJoin()
|
||||
.fetch();
|
||||
return initRelations(subs);
|
||||
}
|
||||
|
||||
public Long count(User user) {
|
||||
return query().select(SUBSCRIPTION.count()).from(SUBSCRIPTION).where(SUBSCRIPTION.user.eq(user)).fetchOne();
|
||||
}
|
||||
public Long count(User user) {
|
||||
return query().select(SUBSCRIPTION.count())
|
||||
.from(SUBSCRIPTION)
|
||||
.where(SUBSCRIPTION.user.eq(user))
|
||||
.fetchOne();
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findByCategory(User user, FeedCategory category) {
|
||||
JPQLQuery<FeedSubscription> query = query().selectFrom(SUBSCRIPTION).where(SUBSCRIPTION.user.eq(user));
|
||||
if (category == null) {
|
||||
query.where(SUBSCRIPTION.category.isNull());
|
||||
} else {
|
||||
query.where(SUBSCRIPTION.category.eq(category));
|
||||
}
|
||||
return initRelations(query.fetch());
|
||||
}
|
||||
public List<FeedSubscription> findByCategory(User user, FeedCategory category) {
|
||||
JPQLQuery<FeedSubscription> query =
|
||||
query().selectFrom(SUBSCRIPTION).where(SUBSCRIPTION.user.eq(user));
|
||||
if (category == null) {
|
||||
query.where(SUBSCRIPTION.category.isNull());
|
||||
} else {
|
||||
query.where(SUBSCRIPTION.category.eq(category));
|
||||
}
|
||||
return initRelations(query.fetch());
|
||||
}
|
||||
|
||||
public List<FeedSubscription> findByCategories(User user, List<FeedCategory> categories) {
|
||||
Set<Long> categoryIds = categories.stream().map(AbstractModel::getId).collect(Collectors.toSet());
|
||||
return findAll(user).stream().filter(s -> s.getCategory() != null && categoryIds.contains(s.getCategory().getId())).toList();
|
||||
}
|
||||
public List<FeedSubscription> findByCategories(User user, List<FeedCategory> categories) {
|
||||
Set<Long> categoryIds =
|
||||
categories.stream().map(AbstractModel::getId).collect(Collectors.toSet());
|
||||
return findAll(user).stream()
|
||||
.filter(
|
||||
s ->
|
||||
s.getCategory() != null
|
||||
&& categoryIds.contains(s.getCategory().getId()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<FeedSubscription> initRelations(List<FeedSubscription> list) {
|
||||
list.forEach(this::initRelations);
|
||||
return list;
|
||||
}
|
||||
private List<FeedSubscription> initRelations(List<FeedSubscription> list) {
|
||||
list.forEach(this::initRelations);
|
||||
return list;
|
||||
}
|
||||
|
||||
private FeedSubscription initRelations(FeedSubscription sub) {
|
||||
if (sub != null) {
|
||||
Models.initialize(sub.getFeed());
|
||||
Models.initialize(sub.getCategory());
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
private FeedSubscription initRelations(FeedSubscription sub) {
|
||||
if (sub != null) {
|
||||
Models.initialize(sub.getFeed());
|
||||
Models.initialize(sub.getCategory());
|
||||
}
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.hibernate.jpa.SpecHints;
|
||||
|
||||
import com.commafeed.backend.model.AbstractModel;
|
||||
import com.querydsl.core.types.EntityPath;
|
||||
import com.querydsl.jpa.impl.JPADeleteClause;
|
||||
@@ -14,53 +7,59 @@ import com.querydsl.jpa.impl.JPAQuery;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import com.querydsl.jpa.impl.JPAUpdateClause;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.hibernate.jpa.SpecHints;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public abstract class GenericDAO<T extends AbstractModel> {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
private final Class<T> entityClass;
|
||||
private final EntityManager entityManager;
|
||||
private final Class<T> entityClass;
|
||||
|
||||
protected JPAQueryFactory query() {
|
||||
return new JPAQueryFactory(entityManager);
|
||||
}
|
||||
protected JPAQueryFactory query() {
|
||||
return new JPAQueryFactory(entityManager);
|
||||
}
|
||||
|
||||
protected JPAUpdateClause updateQuery(EntityPath<T> entityPath) {
|
||||
return new JPAUpdateClause(entityManager, entityPath);
|
||||
}
|
||||
protected JPAUpdateClause updateQuery(EntityPath<T> entityPath) {
|
||||
return new JPAUpdateClause(entityManager, entityPath);
|
||||
}
|
||||
|
||||
protected JPADeleteClause deleteQuery(EntityPath<T> entityPath) {
|
||||
return new JPADeleteClause(entityManager, entityPath);
|
||||
}
|
||||
protected JPADeleteClause deleteQuery(EntityPath<T> entityPath) {
|
||||
return new JPADeleteClause(entityManager, entityPath);
|
||||
}
|
||||
|
||||
public void persist(T model) {
|
||||
entityManager.persist(model);
|
||||
}
|
||||
public void persist(T model) {
|
||||
entityManager.persist(model);
|
||||
}
|
||||
|
||||
public T merge(T model) {
|
||||
return entityManager.merge(model);
|
||||
}
|
||||
public T merge(T model) {
|
||||
return entityManager.merge(model);
|
||||
}
|
||||
|
||||
public T findById(Long id) {
|
||||
return entityManager.find(entityClass, id);
|
||||
}
|
||||
public T findById(Long id) {
|
||||
return entityManager.find(entityClass, id);
|
||||
}
|
||||
|
||||
public void delete(T object) {
|
||||
if (object != null) {
|
||||
entityManager.remove(object);
|
||||
}
|
||||
}
|
||||
public void delete(T object) {
|
||||
if (object != null) {
|
||||
entityManager.remove(object);
|
||||
}
|
||||
}
|
||||
|
||||
public int delete(Collection<T> objects) {
|
||||
objects.forEach(this::delete);
|
||||
return objects.size();
|
||||
}
|
||||
|
||||
protected void setTimeout(JPAQuery<?> query, Duration timeout) {
|
||||
if (!timeout.isZero()) {
|
||||
query.setHint(SpecHints.HINT_SPEC_QUERY_TIMEOUT, Math.toIntExact(timeout.toMillis()));
|
||||
}
|
||||
}
|
||||
public int delete(Collection<T> objects) {
|
||||
objects.forEach(this::delete);
|
||||
return objects.size();
|
||||
}
|
||||
|
||||
protected void setTimeout(JPAQuery<?> query, Duration timeout) {
|
||||
if (!timeout.isZero()) {
|
||||
query.setHint(SpecHints.HINT_SPEC_QUERY_TIMEOUT, Math.toIntExact(timeout.toMillis()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,12 @@ import lombok.experimental.UtilityClass;
|
||||
@UtilityClass
|
||||
public class Predicates {
|
||||
|
||||
private static final QFeedEntryStatus STATUS = QFeedEntryStatus.feedEntryStatus;
|
||||
private static final QFeedEntryStatus STATUS = QFeedEntryStatus.feedEntryStatus;
|
||||
|
||||
public static BooleanExpression isNotStarred(QFeedEntry entry) {
|
||||
return JPAExpressions.selectOne().from(STATUS).where(STATUS.entry.eq(entry).and(STATUS.starred.isTrue())).notExists();
|
||||
}
|
||||
public static BooleanExpression isNotStarred(QFeedEntry entry) {
|
||||
return JPAExpressions.selectOne()
|
||||
.from(STATUS)
|
||||
.where(STATUS.entry.eq(entry).and(STATUS.starred.isTrue()))
|
||||
.notExists();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import io.quarkus.narayana.jta.QuarkusTransaction;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import io.quarkus.narayana.jta.QuarkusTransaction;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@Singleton
|
||||
public class UnitOfWork {
|
||||
|
||||
public void run(Runnable runnable) {
|
||||
QuarkusTransaction.joiningExisting().run(runnable);
|
||||
}
|
||||
public void run(Runnable runnable) {
|
||||
QuarkusTransaction.joiningExisting().run(runnable);
|
||||
}
|
||||
|
||||
public <T> T call(Callable<T> callable) {
|
||||
return QuarkusTransaction.joiningExisting().call(callable);
|
||||
}
|
||||
public <T> T call(Callable<T> callable) {
|
||||
return QuarkusTransaction.joiningExisting().call(callable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.QUser;
|
||||
import com.commafeed.backend.model.User;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
@Singleton
|
||||
public class UserDAO extends GenericDAO<User> {
|
||||
|
||||
private static final QUser USER = QUser.user;
|
||||
private static final QUser USER = QUser.user;
|
||||
|
||||
public UserDAO(EntityManager entityManager) {
|
||||
super(entityManager, User.class);
|
||||
}
|
||||
public UserDAO(EntityManager entityManager) {
|
||||
super(entityManager, User.class);
|
||||
}
|
||||
|
||||
public User findByName(String name) {
|
||||
return query().selectFrom(USER).where(USER.name.equalsIgnoreCase(name)).fetchOne();
|
||||
}
|
||||
public User findByName(String name) {
|
||||
return query().selectFrom(USER).where(USER.name.equalsIgnoreCase(name)).fetchOne();
|
||||
}
|
||||
|
||||
public User findByApiKey(String key) {
|
||||
return query().selectFrom(USER).where(USER.apiKey.equalsIgnoreCase(key)).fetchOne();
|
||||
}
|
||||
public User findByApiKey(String key) {
|
||||
return query().selectFrom(USER).where(USER.apiKey.equalsIgnoreCase(key)).fetchOne();
|
||||
}
|
||||
|
||||
public User findByEmail(String email) {
|
||||
return query().selectFrom(USER).where(USER.email.equalsIgnoreCase(email)).fetchOne();
|
||||
}
|
||||
public User findByEmail(String email) {
|
||||
return query().selectFrom(USER).where(USER.email.equalsIgnoreCase(email)).fetchOne();
|
||||
}
|
||||
|
||||
public long count() {
|
||||
return query().select(USER.count()).from(USER).fetchOne();
|
||||
}
|
||||
public long count() {
|
||||
return query().select(USER.count()).from(USER).fetchOne();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.QUserRole;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserRole;
|
||||
import com.commafeed.backend.model.UserRole.Role;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class UserRoleDAO extends GenericDAO<UserRole> {
|
||||
|
||||
private static final QUserRole ROLE = QUserRole.userRole;
|
||||
private static final QUserRole ROLE = QUserRole.userRole;
|
||||
|
||||
public UserRoleDAO(EntityManager entityManager) {
|
||||
super(entityManager, UserRole.class);
|
||||
}
|
||||
public UserRoleDAO(EntityManager entityManager) {
|
||||
super(entityManager, UserRole.class);
|
||||
}
|
||||
|
||||
public List<UserRole> findAll() {
|
||||
return query().selectFrom(ROLE).leftJoin(ROLE.user).fetchJoin().distinct().fetch();
|
||||
}
|
||||
public List<UserRole> findAll() {
|
||||
return query().selectFrom(ROLE).leftJoin(ROLE.user).fetchJoin().distinct().fetch();
|
||||
}
|
||||
|
||||
public List<UserRole> findAll(User user) {
|
||||
return query().selectFrom(ROLE).where(ROLE.user.eq(user)).distinct().fetch();
|
||||
}
|
||||
public List<UserRole> findAll(User user) {
|
||||
return query().selectFrom(ROLE).where(ROLE.user.eq(user)).distinct().fetch();
|
||||
}
|
||||
|
||||
public Set<Role> findRoles(User user) {
|
||||
return findAll(user).stream().map(UserRole::getRole).collect(Collectors.toSet());
|
||||
}
|
||||
public Set<Role> findRoles(User user) {
|
||||
return findAll(user).stream().map(UserRole::getRole).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public long countAdmins() {
|
||||
return query().select(ROLE.count()).from(ROLE).where(ROLE.role.eq(Role.ADMIN)).fetchOne();
|
||||
}
|
||||
public long countAdmins() {
|
||||
return query().select(ROLE.count()).from(ROLE).where(ROLE.role.eq(Role.ADMIN)).fetchOne();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import com.commafeed.backend.model.QUserSettings;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserSettings;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
@Singleton
|
||||
public class UserSettingsDAO extends GenericDAO<UserSettings> {
|
||||
|
||||
private static final QUserSettings SETTINGS = QUserSettings.userSettings;
|
||||
private static final QUserSettings SETTINGS = QUserSettings.userSettings;
|
||||
|
||||
public UserSettingsDAO(EntityManager entityManager) {
|
||||
super(entityManager, UserSettings.class);
|
||||
}
|
||||
public UserSettingsDAO(EntityManager entityManager) {
|
||||
super(entityManager, UserSettings.class);
|
||||
}
|
||||
|
||||
public UserSettings findByUser(User user) {
|
||||
return query().selectFrom(SETTINGS).where(SETTINGS.user.eq(user)).fetchFirst();
|
||||
}
|
||||
public UserSettings findByUser(User user) {
|
||||
return query().selectFrom(SETTINGS).where(SETTINGS.user.eq(user)).fetchFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractFaviconFetcher {
|
||||
|
||||
private static final List<String> ICON_MIMETYPE_BLACKLIST = Arrays.asList("application/xml", "text/html");
|
||||
private static final long MIN_ICON_LENGTH = 100;
|
||||
private static final long MAX_ICON_LENGTH = 100000;
|
||||
|
||||
public abstract Favicon fetch(Feed feed);
|
||||
|
||||
protected boolean isValidIconResponse(byte[] content, String contentType) {
|
||||
if (content == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long length = content.length;
|
||||
|
||||
if (StringUtils.isNotBlank(contentType)) {
|
||||
contentType = contentType.split(";")[0];
|
||||
}
|
||||
|
||||
if (ICON_MIMETYPE_BLACKLIST.contains(contentType)) {
|
||||
log.debug("Content-Type {} is blacklisted", contentType);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length < MIN_ICON_LENGTH) {
|
||||
log.debug("Length {} below MIN_ICON_LENGTH {}", length, MIN_ICON_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length > MAX_ICON_LENGTH) {
|
||||
log.debug("Length {} greater than MAX_ICON_LENGTH {}", length, MAX_ICON_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.Urls;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Inspired/Ported from https://github.com/potatolondon/getfavicon
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
@Priority(Integer.MIN_VALUE)
|
||||
public class DefaultFaviconFetcher extends AbstractFaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
Favicon icon = fetch(feed.getLink());
|
||||
if (icon == null) {
|
||||
icon = fetch(feed.getUrl());
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
private Favicon fetch(String url) {
|
||||
if (url == null) {
|
||||
log.debug("url is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
int doubleSlash = url.indexOf("//");
|
||||
if (doubleSlash == -1) {
|
||||
doubleSlash = 0;
|
||||
} else {
|
||||
doubleSlash += 2;
|
||||
}
|
||||
int firstSlash = url.indexOf('/', doubleSlash);
|
||||
if (firstSlash != -1) {
|
||||
url = url.substring(0, firstSlash);
|
||||
}
|
||||
|
||||
Favicon icon = getIconAtRoot(url);
|
||||
|
||||
if (icon == null) {
|
||||
icon = getIconInPage(url);
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private Favicon getIconAtRoot(String url) {
|
||||
byte[] bytes = null;
|
||||
String contentType = null;
|
||||
|
||||
try {
|
||||
url = Urls.removeTrailingSlash(url) + "/favicon.ico";
|
||||
log.debug("getting root icon at {}", url);
|
||||
HttpResult result = getter.get(url);
|
||||
bytes = result.content();
|
||||
contentType = result.contentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e);
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
return null;
|
||||
}
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
|
||||
private Favicon getIconInPage(String url) {
|
||||
|
||||
Document doc;
|
||||
try {
|
||||
HttpResult result = getter.get(url);
|
||||
doc = Jsoup.parse(new String(result.content()), url);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve page to find icon", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
Elements icons = doc.select("link[rel~=(?i)^(shortcut|icon|shortcut icon)$]");
|
||||
|
||||
if (icons.isEmpty()) {
|
||||
log.debug("No icon found in page {}", url);
|
||||
return null;
|
||||
}
|
||||
|
||||
String href = icons.getFirst().attr("abs:href");
|
||||
if (StringUtils.isBlank(href)) {
|
||||
log.debug("No icon found in page");
|
||||
return null;
|
||||
}
|
||||
|
||||
log.debug("Found unconfirmed iconInPage at {}", href);
|
||||
|
||||
byte[] bytes;
|
||||
String contentType;
|
||||
try {
|
||||
HttpResult result = getter.get(href);
|
||||
bytes = result.content();
|
||||
contentType = result.contentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve icon found in page {}", href, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
log.debug("Invalid icon found for {}", href);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,71 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import org.apache.hc.core5.http.NameValuePair;
|
||||
import org.apache.hc.core5.net.URIBuilder;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.hc.core5.http.NameValuePair;
|
||||
import org.apache.hc.core5.net.URIBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
public class FacebookFaviconFetcher extends AbstractFaviconFetcher {
|
||||
@Priority(3)
|
||||
public class FacebookFaviconFetcher implements FaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getUrl();
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getUrl();
|
||||
if (!url.toLowerCase().contains("www.facebook.com")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!url.toLowerCase().contains("www.facebook.com")) {
|
||||
return null;
|
||||
}
|
||||
String userName = extractUserName(url);
|
||||
if (userName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String userName = extractUserName(url);
|
||||
if (userName == null) {
|
||||
return null;
|
||||
}
|
||||
String iconUrl =
|
||||
String.format(
|
||||
"https://graph.facebook.com/%s/picture?type=square&height=16", userName);
|
||||
|
||||
String iconUrl = String.format("https://graph.facebook.com/%s/picture?type=square&height=16", userName);
|
||||
try {
|
||||
log.debug("Getting Facebook user's icon, {}", url);
|
||||
|
||||
byte[] bytes = null;
|
||||
String contentType = null;
|
||||
HttpResult iconResult = getter.get(iconUrl);
|
||||
return new Favicon(iconResult.content(), iconResult.contentType());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve Facebook icon", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
log.debug("Getting Facebook user's icon, {}", url);
|
||||
|
||||
HttpResult iconResult = getter.get(iconUrl);
|
||||
bytes = iconResult.content();
|
||||
contentType = iconResult.contentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve Facebook icon", e);
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
return null;
|
||||
}
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
|
||||
private String extractUserName(String url) {
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(url);
|
||||
} catch (URISyntaxException e) {
|
||||
log.debug("could not parse url", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NameValuePair> params = new URIBuilder(uri).getQueryParams();
|
||||
return params.stream().filter(p -> "id".equals(p.getName())).map(NameValuePair::getValue).findFirst().orElse(null);
|
||||
}
|
||||
private String extractUserName(String url) {
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(url);
|
||||
} catch (URISyntaxException e) {
|
||||
log.debug("could not parse url", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NameValuePair> params = new URIBuilder(uri).getQueryParams();
|
||||
return params.stream()
|
||||
.filter(p -> "id".equals(p.getName()))
|
||||
.map(NameValuePair::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,19 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
public record Favicon(byte[] icon, MediaType mediaType) {
|
||||
|
||||
private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.valueOf("image/x-icon");
|
||||
private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.valueOf("image/x-icon");
|
||||
|
||||
public Favicon(byte[] icon, String contentType) {
|
||||
this(icon, parseMediaType(contentType));
|
||||
}
|
||||
public Favicon(byte[] icon, String contentType) {
|
||||
this(icon, parseMediaType(contentType));
|
||||
}
|
||||
|
||||
private static MediaType parseMediaType(String contentType) {
|
||||
try {
|
||||
return MediaType.valueOf(contentType);
|
||||
} catch (Exception e) {
|
||||
log.debug("invalid content type '{}' received, returning default value", contentType, e);
|
||||
return DEFAULT_MEDIA_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
private static MediaType parseMediaType(String contentType) {
|
||||
try {
|
||||
return MediaType.valueOf(contentType);
|
||||
} catch (Exception e) {
|
||||
log.debug(
|
||||
"invalid content type '{}' received, returning default value", contentType, e);
|
||||
return DEFAULT_MEDIA_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
public interface FaviconFetcher {
|
||||
|
||||
Favicon fetch(Feed feed);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/** Fetch favicon from the url declared in the feed. */
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
@Priority(2)
|
||||
public class FeedFaviconFetcher implements FaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getIconUrl();
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
HttpResult result = getter.get(url);
|
||||
return new Favicon(result.content(), result.contentType());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve icon declared in the feed {}", url, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
/** Extracts favicon url from html page. */
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
@Priority(1)
|
||||
public class HtmlFaviconFetcher implements FaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getLink();
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Document doc;
|
||||
try {
|
||||
HttpResult result = getter.get(url);
|
||||
doc = Jsoup.parse(new String(result.content()), url);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve page to find icon", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
Elements icons = doc.select("link[rel~=(?i)^(shortcut|icon|shortcut icon)$]");
|
||||
if (icons.isEmpty()) {
|
||||
log.debug("No icon found in page {}", url);
|
||||
return null;
|
||||
}
|
||||
|
||||
String href = icons.getFirst().attr("abs:href");
|
||||
if (StringUtils.isBlank(href)) {
|
||||
log.debug("No icon found in page");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
HttpResult result = getter.get(href);
|
||||
return new Favicon(result.content(), result.contentType());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve icon found in page {}", href, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/** Fetches favicon from root of the domain (e.g. https://example.com/favicon.ico) */
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
@Priority(0)
|
||||
public class RootFaviconFetcher implements FaviconFetcher {
|
||||
|
||||
private final HttpGetter getter;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getLink();
|
||||
if (url == null) {
|
||||
url = feed.getUrl();
|
||||
}
|
||||
|
||||
try {
|
||||
URI uri = URI.create(url.trim());
|
||||
String faviconUrl =
|
||||
"%s://%s%s/favicon.ico"
|
||||
.formatted(
|
||||
uri.getScheme(),
|
||||
uri.getHost(),
|
||||
uri.getPort() > 0 ? ":" + uri.getPort() : "");
|
||||
|
||||
log.debug("getting root icon at {}", faviconUrl);
|
||||
HttpResult result = getter.get(faviconUrl);
|
||||
return new Favicon(result.content(), result.contentType());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve iconAtRoot for url {}: ", url, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,5 @@
|
||||
package com.commafeed.backend.favicon;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.hc.core5.http.NameValuePair;
|
||||
import org.apache.hc.core5.net.URIBuilder;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
@@ -23,114 +11,143 @@ import com.fasterxml.jackson.core.JsonPointer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
import jakarta.inject.Singleton;
|
||||
import jakarta.ws.rs.core.UriBuilder;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.hc.core5.http.NameValuePair;
|
||||
import org.apache.hc.core5.net.URIBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Singleton
|
||||
public class YoutubeFaviconFetcher extends AbstractFaviconFetcher {
|
||||
@Priority(3)
|
||||
public class YoutubeFaviconFetcher implements FaviconFetcher {
|
||||
|
||||
private static final String PART_SNIPPET = "snippet";
|
||||
private static final String PART_SNIPPET = "snippet";
|
||||
|
||||
private static final JsonPointer CHANNEL_THUMBNAIL_URL = JsonPointer.compile("/items/0/snippet/thumbnails/default/url");
|
||||
private static final JsonPointer PLAYLIST_CHANNEL_ID = JsonPointer.compile("/items/0/snippet/channelId");
|
||||
private static final JsonPointer CHANNEL_THUMBNAIL_URL =
|
||||
JsonPointer.compile("/items/0/snippet/thumbnails/default/url");
|
||||
private static final JsonPointer PLAYLIST_CHANNEL_ID =
|
||||
JsonPointer.compile("/items/0/snippet/channelId");
|
||||
|
||||
private final HttpGetter getter;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final HttpGetter getter;
|
||||
private final CommaFeedConfiguration config;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getUrl();
|
||||
if (!url.toLowerCase().contains("youtube.com/feeds/videos.xml")) {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public Favicon fetch(Feed feed) {
|
||||
String url = feed.getUrl();
|
||||
if (!url.toLowerCase().contains("youtube.com/feeds/videos.xml")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Optional<String> googleAuthKey = config.googleAuthKey();
|
||||
if (googleAuthKey.isEmpty()) {
|
||||
log.debug("no google auth key configured");
|
||||
return null;
|
||||
}
|
||||
Optional<String> googleAuthKey = config.googleAuthKey();
|
||||
if (googleAuthKey.isEmpty()) {
|
||||
log.debug("no google auth key configured");
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] bytes = null;
|
||||
String contentType = null;
|
||||
try {
|
||||
List<NameValuePair> params = new URIBuilder(url).getQueryParams();
|
||||
Optional<NameValuePair> userId = params.stream().filter(nvp -> nvp.getName().equalsIgnoreCase("user")).findFirst();
|
||||
Optional<NameValuePair> channelId = params.stream().filter(nvp -> nvp.getName().equalsIgnoreCase("channel_id")).findFirst();
|
||||
Optional<NameValuePair> playlistId = params.stream().filter(nvp -> nvp.getName().equalsIgnoreCase("playlist_id")).findFirst();
|
||||
try {
|
||||
List<NameValuePair> params = new URIBuilder(url).getQueryParams();
|
||||
Optional<NameValuePair> userId =
|
||||
params.stream()
|
||||
.filter(nvp -> nvp.getName().equalsIgnoreCase("user"))
|
||||
.findFirst();
|
||||
Optional<NameValuePair> channelId =
|
||||
params.stream()
|
||||
.filter(nvp -> nvp.getName().equalsIgnoreCase("channel_id"))
|
||||
.findFirst();
|
||||
Optional<NameValuePair> playlistId =
|
||||
params.stream()
|
||||
.filter(nvp -> nvp.getName().equalsIgnoreCase("playlist_id"))
|
||||
.findFirst();
|
||||
|
||||
byte[] response = null;
|
||||
if (userId.isPresent()) {
|
||||
log.debug("contacting youtube api for user {}", userId.get().getValue());
|
||||
response = fetchForUser(googleAuthKey.get(), userId.get().getValue());
|
||||
} else if (channelId.isPresent()) {
|
||||
log.debug("contacting youtube api for channel {}", channelId.get().getValue());
|
||||
response = fetchForChannel(googleAuthKey.get(), channelId.get().getValue());
|
||||
} else if (playlistId.isPresent()) {
|
||||
log.debug("contacting youtube api for playlist {}", playlistId.get().getValue());
|
||||
response = fetchForPlaylist(googleAuthKey.get(), playlistId.get().getValue());
|
||||
}
|
||||
if (ArrayUtils.isEmpty(response)) {
|
||||
log.debug("youtube api returned empty response");
|
||||
return null;
|
||||
}
|
||||
byte[] response = null;
|
||||
if (userId.isPresent()) {
|
||||
log.debug("contacting youtube api for user {}", userId.get().getValue());
|
||||
response = fetchForUser(googleAuthKey.get(), userId.get().getValue());
|
||||
} else if (channelId.isPresent()) {
|
||||
log.debug("contacting youtube api for channel {}", channelId.get().getValue());
|
||||
response = fetchForChannel(googleAuthKey.get(), channelId.get().getValue());
|
||||
} else if (playlistId.isPresent()) {
|
||||
log.debug("contacting youtube api for playlist {}", playlistId.get().getValue());
|
||||
response = fetchForPlaylist(googleAuthKey.get(), playlistId.get().getValue());
|
||||
}
|
||||
if (ArrayUtils.isEmpty(response)) {
|
||||
log.debug("youtube api returned empty response");
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonNode thumbnailUrl = objectMapper.readTree(response).at(CHANNEL_THUMBNAIL_URL);
|
||||
if (thumbnailUrl.isMissingNode()) {
|
||||
log.debug("youtube api returned invalid response");
|
||||
return null;
|
||||
}
|
||||
JsonNode thumbnailUrl = objectMapper.readTree(response).at(CHANNEL_THUMBNAIL_URL);
|
||||
if (thumbnailUrl.isMissingNode()) {
|
||||
log.debug("youtube api returned invalid response");
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpResult iconResult = getter.get(thumbnailUrl.asText());
|
||||
bytes = iconResult.content();
|
||||
contentType = iconResult.contentType();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve YouTube icon", e);
|
||||
}
|
||||
HttpResult iconResult = getter.get(thumbnailUrl.asText());
|
||||
return new Favicon(iconResult.content(), iconResult.contentType());
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve YouTube icon", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValidIconResponse(bytes, contentType)) {
|
||||
return null;
|
||||
}
|
||||
return new Favicon(bytes, contentType);
|
||||
}
|
||||
private byte[] fetchForUser(String googleAuthKey, String userId)
|
||||
throws IOException,
|
||||
NotModifiedException,
|
||||
TooManyRequestsException,
|
||||
SchemeNotAllowedException {
|
||||
URI uri =
|
||||
UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/channels")
|
||||
.queryParam("part", PART_SNIPPET)
|
||||
.queryParam("key", googleAuthKey)
|
||||
.queryParam("forUsername", userId)
|
||||
.build();
|
||||
return getter.get(uri.toString()).content();
|
||||
}
|
||||
|
||||
private byte[] fetchForUser(String googleAuthKey, String userId)
|
||||
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||
URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/channels")
|
||||
.queryParam("part", PART_SNIPPET)
|
||||
.queryParam("key", googleAuthKey)
|
||||
.queryParam("forUsername", userId)
|
||||
.build();
|
||||
return getter.get(uri.toString()).content();
|
||||
}
|
||||
private byte[] fetchForChannel(String googleAuthKey, String channelId)
|
||||
throws IOException,
|
||||
NotModifiedException,
|
||||
TooManyRequestsException,
|
||||
SchemeNotAllowedException {
|
||||
URI uri =
|
||||
UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/channels")
|
||||
.queryParam("part", PART_SNIPPET)
|
||||
.queryParam("key", googleAuthKey)
|
||||
.queryParam("id", channelId)
|
||||
.build();
|
||||
return getter.get(uri.toString()).content();
|
||||
}
|
||||
|
||||
private byte[] fetchForChannel(String googleAuthKey, String channelId)
|
||||
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||
URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/channels")
|
||||
.queryParam("part", PART_SNIPPET)
|
||||
.queryParam("key", googleAuthKey)
|
||||
.queryParam("id", channelId)
|
||||
.build();
|
||||
return getter.get(uri.toString()).content();
|
||||
}
|
||||
private byte[] fetchForPlaylist(String googleAuthKey, String playlistId)
|
||||
throws IOException,
|
||||
NotModifiedException,
|
||||
TooManyRequestsException,
|
||||
SchemeNotAllowedException {
|
||||
URI uri =
|
||||
UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/playlists")
|
||||
.queryParam("part", PART_SNIPPET)
|
||||
.queryParam("key", googleAuthKey)
|
||||
.queryParam("id", playlistId)
|
||||
.build();
|
||||
byte[] playlistBytes = getter.get(uri.toString()).content();
|
||||
|
||||
private byte[] fetchForPlaylist(String googleAuthKey, String playlistId)
|
||||
throws IOException, NotModifiedException, TooManyRequestsException, SchemeNotAllowedException {
|
||||
URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/playlists")
|
||||
.queryParam("part", PART_SNIPPET)
|
||||
.queryParam("key", googleAuthKey)
|
||||
.queryParam("id", playlistId)
|
||||
.build();
|
||||
byte[] playlistBytes = getter.get(uri.toString()).content();
|
||||
|
||||
JsonNode channelId = objectMapper.readTree(playlistBytes).at(PLAYLIST_CHANNEL_ID);
|
||||
if (channelId.isMissingNode()) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
return fetchForChannel(googleAuthKey, channelId.asText());
|
||||
}
|
||||
JsonNode channelId = objectMapper.readTree(playlistBytes).at(PLAYLIST_CHANNEL_ID);
|
||||
if (channelId.isMissingNode()) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
return fetchForChannel(googleAuthKey, channelId.asText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* A keyword used in a search query
|
||||
*/
|
||||
/** A keyword used in a search query */
|
||||
public record FeedEntryKeyword(String keyword, Mode mode) {
|
||||
|
||||
public enum Mode {
|
||||
INCLUDE, EXCLUDE
|
||||
}
|
||||
public enum Mode {
|
||||
INCLUDE,
|
||||
EXCLUDE
|
||||
}
|
||||
|
||||
public static List<FeedEntryKeyword> fromQueryString(String keywords) {
|
||||
List<FeedEntryKeyword> list = new ArrayList<>();
|
||||
if (keywords != null) {
|
||||
for (String keyword : StringUtils.split(keywords)) {
|
||||
boolean not = false;
|
||||
if (keyword.startsWith("-") || keyword.startsWith("!")) {
|
||||
not = true;
|
||||
keyword = keyword.substring(1);
|
||||
}
|
||||
list.add(new FeedEntryKeyword(keyword, not ? Mode.EXCLUDE : Mode.INCLUDE));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
public static List<FeedEntryKeyword> fromQueryString(String keywords) {
|
||||
List<FeedEntryKeyword> list = new ArrayList<>();
|
||||
if (keywords != null) {
|
||||
for (String keyword : StringUtils.split(keywords)) {
|
||||
boolean not = false;
|
||||
if (keyword.startsWith("-") || keyword.startsWith("!")) {
|
||||
not = true;
|
||||
keyword = keyword.substring(1);
|
||||
}
|
||||
list.add(new FeedEntryKeyword(keyword, not ? Mode.EXCLUDE : Mode.INCLUDE));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user