forked from akai/readest
Compare commits
823 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56d6aceb0d | |||
| 598eb77237 | |||
| d326e1c73d | |||
| 1705006b6b | |||
| 952e436514 | |||
| 772bb73b46 | |||
| 5774e00c09 | |||
| f6f446e8a0 | |||
| 1eae2af23e | |||
| 295a588988 | |||
| 4110911011 | |||
| e0b3a6fb0c | |||
| c30a59a9ed | |||
| ae42dcb53a | |||
| 302363a9fd | |||
| cc8f917cdd | |||
| 51a553dd89 | |||
| 6e7c9d1395 | |||
| 2d5590ec1f | |||
| dc58d985e7 | |||
| 712d564e9d | |||
| 6bfeb295d2 | |||
| aea3fda086 | |||
| 35227ecd61 | |||
| cb30716830 | |||
| 4625e47a6d | |||
| 77a85cee09 | |||
| 53936ad17c | |||
| de6529523f | |||
| ca674bb5e7 | |||
| 981579c255 | |||
| cbdc3b8f52 | |||
| 3b348c8f35 | |||
| dd0ff6ae9d | |||
| 30dee7b909 | |||
| a272ba892a | |||
| e7f370453e | |||
| 5dc2528455 | |||
| c27245e980 | |||
| 9b0072173c | |||
| 06aec0b597 | |||
| 15c0a7a2f2 | |||
| d66fedcab7 | |||
| 43f72720f2 | |||
| 6d29814551 | |||
| ed8956e9ed | |||
| cead0f42e0 | |||
| c59097b0ac | |||
| 7bb1133706 | |||
| 8e7b2192d5 | |||
| e7564ffc27 | |||
| e0fa6230ab | |||
| f5657fb3a0 | |||
| 19f2414f42 | |||
| 19f3e65b62 | |||
| d1e7b4902c | |||
| 176e5df771 | |||
| 579e950756 | |||
| eadb355396 | |||
| 8ba052dc81 | |||
| 293d5b5f5d | |||
| fb37406b31 | |||
| 486659a1ca | |||
| 5a0a70a30a | |||
| a9684ab357 | |||
| a43845b4c5 | |||
| 1d8ed3fc92 | |||
| d609de58f0 | |||
| f9a7117253 | |||
| 234ecc3113 | |||
| dab92c8a46 | |||
| ad55375f89 | |||
| 34f19fd148 | |||
| 920627ae59 | |||
| 4b0720a3e3 | |||
| 3b03b2c8d5 | |||
| 6fbf9ef68b | |||
| ca8f0fe9f6 | |||
| 6d5e59c79a | |||
| 6fcda66b60 | |||
| 94ede10f6e | |||
| 4f55920b71 | |||
| 63b0b87028 | |||
| 17f2a17adc | |||
| e18bfd6810 | |||
| 6d798542f6 | |||
| ebbbf104b2 | |||
| fa61f40262 | |||
| b251e537d3 | |||
| aa60123d38 | |||
| 1527dd9b3a | |||
| 38d7ba80fe | |||
| 8a8fcb2611 | |||
| d488f65447 | |||
| 528a13e36a | |||
| 71371130e0 | |||
| be8b946683 | |||
| 82ece3332a | |||
| 957b7d5f3e | |||
| 5190bbcafc | |||
| 3f531d9046 | |||
| a2244e28b8 | |||
| 3bbc2071c8 | |||
| 9c273d79fc | |||
| 09b19bd3c5 | |||
| c58153e942 | |||
| ff94dc76c6 | |||
| 293cc545db | |||
| e1dad98e56 | |||
| e8f6db96e4 | |||
| b223ccaee9 | |||
| 5c97b2e9d8 | |||
| 31e44d2e4d | |||
| 976bbcc152 | |||
| 802212c423 | |||
| e858f9b23f | |||
| 3e292af990 | |||
| b0cc5461af | |||
| 7d852518a3 | |||
| 73d30c103f | |||
| 8cdf378b47 | |||
| 1088af023b | |||
| 011ad18a02 | |||
| e9d71b2936 | |||
| ec32614539 | |||
| 96678d85ec | |||
| 41b5e92563 | |||
| ef97a8ed02 | |||
| 8df8bc8b4a | |||
| f0e23a1503 | |||
| 4e1464ef17 | |||
| 7b60b1bb0c | |||
| cc780712b9 | |||
| 95ff526140 | |||
| f86bbbcc22 | |||
| 20940105fb | |||
| 2a49e93cf7 | |||
| 030a7c0823 | |||
| 7bf4822b27 | |||
| de11511c30 | |||
| ab7da981da | |||
| 3df75a67f9 | |||
| 07e3248780 | |||
| 23d5f3363d | |||
| c6daf72da9 | |||
| bd866cb049 | |||
| a5690e9a84 | |||
| ed7cfc31fc | |||
| a07bf23e18 | |||
| 41d014914b | |||
| baee85e7c8 | |||
| 1e259e87b2 | |||
| 4abbc17f66 | |||
| d7fd06ca82 | |||
| e43e533aca | |||
| dc788283ad | |||
| 373420bb0c | |||
| 6072b0dcbd | |||
| 13ff96db85 | |||
| bfbe92f355 | |||
| 799db40763 | |||
| 932c82aa49 | |||
| 184de9210d | |||
| 50a2957e35 | |||
| 637b7462c4 | |||
| 82deb85c64 | |||
| db35a4e203 | |||
| 017a9338b3 | |||
| cf21a752c6 | |||
| 16adf11258 | |||
| ae2c421938 | |||
| 3174e341a3 | |||
| 298d4872a0 | |||
| b679817fce | |||
| b9a2b10fac | |||
| a9e33ca50a | |||
| 09d1e0c040 | |||
| 8b10e7fb17 | |||
| a2d17e6a79 | |||
| 7e62516b5d | |||
| d53f3b42e2 | |||
| d682fcbb44 | |||
| b3fe33221d | |||
| fbfd7fd6c6 | |||
| 531dbe5f16 | |||
| 70b94d8986 | |||
| 21795e5cd3 | |||
| 52242d6886 | |||
| f361698e05 | |||
| 9595aa56e0 | |||
| caa0d719c5 | |||
| 45bd355981 | |||
| 52ac74bbad | |||
| 6a44f609ba | |||
| ca5c860594 | |||
| 80cab8e56d | |||
| 888f4afde9 | |||
| 05afaab5fd | |||
| ff962a1f02 | |||
| 62df631dd1 | |||
| c9647276b1 | |||
| 9ecb9b24d2 | |||
| f0ab05bbde | |||
| f1a08565e3 | |||
| b8ddb5475e | |||
| 74401fc1bb | |||
| 29df8522fa | |||
| 9f958a44e2 | |||
| 0e516f6e56 | |||
| b71b246601 | |||
| 94843902ac | |||
| f67930feb1 | |||
| 5e048ddab1 | |||
| e68dedd10d | |||
| d22b8bec1e | |||
| e9c5ebb696 | |||
| 956c71cd7b | |||
| b4207bd742 | |||
| ec26ef4f29 | |||
| b872868136 | |||
| 797fe9c604 | |||
| 8ed9290659 | |||
| 8e6451863f | |||
| c688612888 | |||
| b3333c384c | |||
| 84349ab12d | |||
| c4e3315642 | |||
| bbfc82e50d | |||
| 966f5e2acd | |||
| 45e5f0fa61 | |||
| 3d4d1482aa | |||
| 5f897f648f | |||
| 26fec924fc | |||
| af9cf33936 | |||
| 52df478f21 | |||
| 97555a7e88 | |||
| c7e82825f5 | |||
| bb82ab6c8a | |||
| e2faa9ad75 | |||
| f310305834 | |||
| 5a072e7d1f | |||
| 64675820f1 | |||
| 1e942a23b4 | |||
| a92c357982 | |||
| 1ebf5e7b52 | |||
| c11f79e843 | |||
| 87f0240b0a | |||
| 2905506017 | |||
| 1936136596 | |||
| e0cf7e8d9f | |||
| 0177a03a90 | |||
| f5df80f154 | |||
| 76b239f382 | |||
| 2f430bf2ff | |||
| 0e8605d298 | |||
| 7445d5befe | |||
| e41bcfbac9 | |||
| 91bc4ddec7 | |||
| e8f70b896e | |||
| 1f4f5f8a6d | |||
| 764acf59de | |||
| eab83c6d3f | |||
| 7f62cdcab9 | |||
| e4ab06abcd | |||
| fef0aa6947 | |||
| 2935eacb1c | |||
| ad9bb58cd4 | |||
| 871d6467c3 | |||
| 3f19ce24de | |||
| 8280585e70 | |||
| 1f7483b911 | |||
| b2bb92036b | |||
| 3831f24ff7 | |||
| 3bec89c85d | |||
| 3dfe6bd65e | |||
| b00d321817 | |||
| 1af55e24da | |||
| f7b2a2432c | |||
| db3dee025b | |||
| 25352efece | |||
| 1297e33c62 | |||
| 8274c6bc4f | |||
| ed5bbd25d6 | |||
| d06e1849cc | |||
| 34ad6e6749 | |||
| b163012488 | |||
| f9a3559086 | |||
| 674a0bf16d | |||
| 9b53ccc9de | |||
| 7a605a357a | |||
| af649edd0d | |||
| 64a71e25e4 | |||
| 51a3767940 | |||
| a8572ab7c6 | |||
| 907b672313 | |||
| 8df9f909b4 | |||
| bcd7a90f27 | |||
| 5646e0cfb5 | |||
| fad27d74a0 | |||
| 5fbbfa523b | |||
| 569c6707a4 | |||
| 99f8a29326 | |||
| 04737d6f35 | |||
| 8850e6c00f | |||
| 93b96d64eb | |||
| 9f8894c1e0 | |||
| dac4f2e8ec | |||
| 54bc1514df | |||
| 97cab2d70b | |||
| 13588b4a65 | |||
| 0f5bd5ca1c | |||
| a74c1a56a9 | |||
| 1e9bd1d821 | |||
| 02cc0a9ebb | |||
| 5273ef75dc | |||
| bd957a4eb8 | |||
| 38552a0c2e | |||
| 0609e828b1 | |||
| 35ade2c855 | |||
| b68c14da1f | |||
| 5c41394961 | |||
| 5685944599 | |||
| 94e761f681 | |||
| 7f636a2072 | |||
| 480b8b98a3 | |||
| d1fb67316f | |||
| f0e9ddd2ae | |||
| b16a4445ae | |||
| 515d47e64d | |||
| 3fd78c4ed8 | |||
| 152a95941a | |||
| bf5805910d | |||
| 431d14f4a4 | |||
| 0d2e5b7c76 | |||
| e949476d27 | |||
| 0afff573a1 | |||
| d4bb61f12b | |||
| 09c45b4615 | |||
| d8eef87bf0 | |||
| 66d2fdf999 | |||
| d8e0ceeff1 | |||
| f3ad97b989 | |||
| 1bb49ab023 | |||
| f9a0b39586 | |||
| c533da498d | |||
| b50bc0b854 | |||
| 96c465931c | |||
| 2bd54ac236 | |||
| 6ad549d13c | |||
| 67249370c9 | |||
| fe3ab011ca | |||
| 6cb4278b98 | |||
| 51468862a2 | |||
| b3a44d066f | |||
| 68d4538d40 | |||
| 80105af839 | |||
| 3904c1e8e7 | |||
| 99be6c58e2 | |||
| 664cc772d0 | |||
| 21208adbcf | |||
| 70eb59e2d6 | |||
| 3e7b57282e | |||
| 534582b125 | |||
| 4465e6986e | |||
| a535f6419e | |||
| 65da9c1d47 | |||
| 5f71fd9e47 | |||
| dcf75e07d1 | |||
| 79ba9b3818 | |||
| 128b238bcb | |||
| e26f7e6a2c | |||
| 4c5ff59bcf | |||
| 99b2a34bd2 | |||
| 40f3268ef3 | |||
| 4dac0850c5 | |||
| 118538ba35 | |||
| 4a92cacd84 | |||
| ce53cd2b47 | |||
| b99c1bc19a | |||
| eec2c39f19 | |||
| c6ae85484e | |||
| 7a9f46e93c | |||
| b9f6578127 | |||
| e75a3d254e | |||
| ea15906acf | |||
| 15d2784725 | |||
| ae3bb9da9a | |||
| 239b32fcc2 | |||
| f64739419b | |||
| af8f036ca3 | |||
| 24a87508c6 | |||
| 548d50a882 | |||
| 1e81ab5205 | |||
| a47a480aa2 | |||
| 411f9e236d | |||
| 950bbd0821 | |||
| 2824e50b51 | |||
| 129720c916 | |||
| fd3533dba1 | |||
| 920032155b | |||
| 226bf7033e | |||
| 9444be7fcc | |||
| c9a69a922b | |||
| 9a11b05833 | |||
| 6e603ee38f | |||
| 03fd6e2e6f | |||
| 7dd11c0fb0 | |||
| 968597e52c | |||
| a534050b19 | |||
| 0be828fd66 | |||
| c6d4e2bdd6 | |||
| bf72ab86cd | |||
| fb49ddf484 | |||
| d8817a88b9 | |||
| 475becafe3 | |||
| df165576e6 | |||
| 051f2e5b13 | |||
| 83f0d135c5 | |||
| 1ed84a3256 | |||
| 9d6394fe2b | |||
| fe9603ffb8 | |||
| 681e065ac4 | |||
| f64fc5723e | |||
| a9a1dc8e70 | |||
| b1a1e35790 | |||
| 3c538c3746 | |||
| 9834bd57cf | |||
| b89171a4d8 | |||
| 92116e7455 | |||
| 8dfab2c963 | |||
| 9f261f12e9 | |||
| e74615ac1d | |||
| 3b350d6945 | |||
| 9e0e3fde7d | |||
| 52c49ddef1 | |||
| e3d52891fb | |||
| 9cd88fe839 | |||
| fca3917a12 | |||
| c90de6967b | |||
| bbbd378f9b | |||
| 9f7147f8f8 | |||
| 8a468a6d1f | |||
| c848a319ff | |||
| 5cc80db438 | |||
| 94930baa1b | |||
| 1294ef90c1 | |||
| 570598520f | |||
| 7b4fc91994 | |||
| fb387c2384 | |||
| 4ce1ebe477 | |||
| c1460f4b85 | |||
| bc94f3f790 | |||
| c8c761b017 | |||
| bdb25999c9 | |||
| c58e172a54 | |||
| 160efcd37b | |||
| d7470f4139 | |||
| 01b4e3530d | |||
| 09c62d442f | |||
| d62ad60ce8 | |||
| 8cd34c8aaa | |||
| a52d9e3a2b | |||
| 64a1ea531a | |||
| 19f9f5ea24 | |||
| ffc51e75de | |||
| 2100071991 | |||
| 563d3478ba | |||
| 2c54e9ae2f | |||
| c2eb2e2fcc | |||
| e592f40429 | |||
| 4bd7cfe57c | |||
| c31ece6742 | |||
| aba9e87fc1 | |||
| 2c4df601d8 | |||
| 8bfc90a5b0 | |||
| 8bea05478a | |||
| 42fee90a27 | |||
| 45e57c3943 | |||
| 51b0b8483f | |||
| cbf7a501b7 | |||
| edfcb75ba5 | |||
| ce76843ccc | |||
| 5bbc5ceccc | |||
| 224acd68b1 | |||
| 6c0f1b8b5f | |||
| 481d8198e9 | |||
| b1153ba051 | |||
| aad532bfdd | |||
| a71347c897 | |||
| 034f41ca10 | |||
| 539ad8dea2 | |||
| 48920a87bf | |||
| d1d0d2d59c | |||
| ea811c90c6 | |||
| 5cd5b65e83 | |||
| ea24b5a97a | |||
| 06d620db83 | |||
| 0c25b85a8a | |||
| c536450ab0 | |||
| c83e380c5a | |||
| 894a7551aa | |||
| f875ba88ac | |||
| d9a6cffe78 | |||
| 1704736bc8 | |||
| 6ce9a263ee | |||
| 038eca4267 | |||
| f0722ec0fe | |||
| fd299a61a7 | |||
| 36b7386e30 | |||
| 2670b0dc0b | |||
| c44705e269 | |||
| 7e618d456e | |||
| b28ac99a9e | |||
| 2d7d6b08a9 | |||
| b1419f9c01 | |||
| 32ea42a835 | |||
| 6083de3293 | |||
| 1c9cfa49b3 | |||
| f85d6d4293 | |||
| 017d0b0b39 | |||
| 3146ae48a7 | |||
| 7f26e45ae7 | |||
| 7d97826e4b | |||
| 9c9c79176d | |||
| ed476a4fce | |||
| 44b4f7995b | |||
| da5e3a0bd3 | |||
| ba4678cc23 | |||
| e5eb3014b4 | |||
| ed77b0bc7f | |||
| 434a44e62c | |||
| aaee04c290 | |||
| 48e2bfa82c | |||
| c04f19ffb4 | |||
| 4a624e3902 | |||
| f53cee9616 | |||
| 30385ee5ec | |||
| b30dfb3e23 | |||
| c0d6102857 | |||
| 4537c55e84 | |||
| aff94c0ab8 | |||
| fd70836308 | |||
| 1b0c94b9a5 | |||
| 7cb523eefc | |||
| 941be80cc4 | |||
| 5eecc735aa | |||
| cfe51d01ee | |||
| 20ae09c52d | |||
| b868146129 | |||
| a312080f7c | |||
| 462ca46fee | |||
| 93228c4b2a | |||
| 2ff10f781f | |||
| 9614c62360 | |||
| 5ffaac5e67 | |||
| 71af91608f | |||
| 9603b61776 | |||
| eed84a059a | |||
| 604ef65719 | |||
| 483d536ca4 | |||
| 69a51c5880 | |||
| ca8d25341e | |||
| 8a263235ed | |||
| fb41ff5d0c | |||
| a5e09e8454 | |||
| 730ee21651 | |||
| dea43445c3 | |||
| 4f0ae78d43 | |||
| d9116d619a | |||
| c080e6fdd3 | |||
| d3752dadc6 | |||
| e21ef53a3d | |||
| 71e97998b6 | |||
| 22f9c45232 | |||
| a42897ec5c | |||
| 58a5c1625c | |||
| d53fc09e1e | |||
| 11fecb5dc0 | |||
| f4587663b5 | |||
| b76a2adf61 | |||
| 988fbc8c85 | |||
| f944ad9b9f | |||
| 335d91b9c9 | |||
| 173404eaad | |||
| 674fed5230 | |||
| 12d284cd22 | |||
| 77037c8adb | |||
| 6984393ed1 | |||
| 3abef6ea75 | |||
| 4ae1ab7dd0 | |||
| 69fb22119b | |||
| 3a6c18c6d5 | |||
| 7db1bc460d | |||
| a460e609fa | |||
| 4b4ebdbdaa | |||
| 9358a06839 | |||
| a7baf6cc9f | |||
| 15dc669f35 | |||
| c853957512 | |||
| 8a4e22e423 | |||
| 8a43c58fd4 | |||
| 54fdf5f1fd | |||
| fe50b513b3 | |||
| 17c7fa8f41 | |||
| 2533560d11 | |||
| 5850a16afd | |||
| 7063d62b13 | |||
| 0bd6a217ae | |||
| b7df294d78 | |||
| 5aa78f2554 | |||
| e740571c33 | |||
| e6d9913f4e | |||
| 1869a863a3 | |||
| 524de92f5e | |||
| c1530cc5c4 | |||
| 730fadb834 | |||
| 383e5c61b1 | |||
| 6d42086fa7 | |||
| 5a20fae204 | |||
| 34fd64c5c4 | |||
| 0874fb0764 | |||
| e03ed5b604 | |||
| 41edc89ac7 | |||
| f0a470398d | |||
| 51008d81fb | |||
| 9828904674 | |||
| 2670d835b3 | |||
| 8b7bafc4b6 | |||
| ca759e0246 | |||
| 5141be1c3f | |||
| 669d3950e2 | |||
| b95895cecf | |||
| 80e11bb0ce | |||
| de3a539621 | |||
| b425bfdc89 | |||
| 1d1fbdffdb | |||
| 50cd7f80c6 | |||
| 6eb7d91122 | |||
| 1fb468b3a6 | |||
| 3c7d95cf10 | |||
| ba3f060cc4 | |||
| 11bc7497e8 | |||
| fb5d149413 | |||
| 42b47d73b7 | |||
| 00f36af03a | |||
| b78466ca93 | |||
| 4e6f146b8f | |||
| cbdd4940d0 | |||
| d022cb984a | |||
| 8de6fa267e | |||
| b08b7de8e9 | |||
| a232a39f0e | |||
| fad7966fc4 | |||
| a1487fd60c | |||
| 9606e315d4 | |||
| 978673268b | |||
| 70158a7f15 | |||
| 18d65a2c5b | |||
| 1b0c2afad7 | |||
| cef444d374 | |||
| 75f6efe27a | |||
| 852f9f40ec | |||
| b9dadc0f4f | |||
| d120cf6573 | |||
| d2b3c165c7 | |||
| 742f06ce05 | |||
| e28dabce65 | |||
| 33ef781e21 | |||
| 02e885fbb7 | |||
| 2ca3561093 | |||
| 97f021da87 | |||
| bd2b45e1c1 | |||
| 2b6f7b71b0 | |||
| 0bad380a50 | |||
| ecc20e8521 | |||
| d5c813fac1 | |||
| d9b3757b1c | |||
| fd7f90236d | |||
| df9d21393d | |||
| 1f73f15ad8 | |||
| ba2aa4bee6 | |||
| f709a657fa | |||
| 514780a572 | |||
| 0c51a625f3 | |||
| 6f8b2d1dc4 | |||
| 0087ce2f19 | |||
| b98c2796c8 | |||
| 37d56b3205 | |||
| 5a54c0fb60 | |||
| 99b259836b | |||
| a54daaaa90 | |||
| fa66e6fca6 | |||
| b7864dded2 | |||
| 72d9698f38 | |||
| a7937cd657 | |||
| 998b14c5b0 | |||
| bff9c2a770 | |||
| b5acdffc87 | |||
| 127609160c | |||
| 8f22a5c570 | |||
| c792c18e01 | |||
| cd614e3845 | |||
| 40673f9cb8 | |||
| 911aa4c73d | |||
| 74b4cc2ceb | |||
| c0c463977d | |||
| 273bcafe01 | |||
| 9028059919 | |||
| c86af457e7 | |||
| b8e979be55 | |||
| 8849c19e8e | |||
| f1b4d02323 | |||
| 4aa8847037 | |||
| d2389400a9 | |||
| 721e70d027 | |||
| 3c1a7eac86 | |||
| f8e21dbd4c | |||
| d16a35d26f | |||
| f881caf794 | |||
| eb6fa276de | |||
| e987c8b37a | |||
| 26d76e27ac | |||
| 975549fca0 | |||
| 9a6bed52dd | |||
| efd9ad12a9 | |||
| 93a71a0fd0 | |||
| d373d7ac28 | |||
| e0e991b599 | |||
| 6d5b16ea8b | |||
| bfac67d3f0 | |||
| 0fd316d775 | |||
| c42fcf9ac4 | |||
| 04ade02a06 | |||
| 8ee53d3367 | |||
| 42111945b8 | |||
| 6fde157047 | |||
| 65ec5c9842 | |||
| a6444b5b33 | |||
| c7238cb04c | |||
| 366e5fafad | |||
| fb5710c134 | |||
| c4d9652335 | |||
| df6027cf9f | |||
| e3f7abf7f1 | |||
| c539917d7e | |||
| 5eb870c978 | |||
| b075c46ba3 | |||
| 4b6c7776f5 | |||
| 745a14195d | |||
| fb0f660eaf | |||
| 04a3030b79 | |||
| 5a3114de48 | |||
| b808fc5954 | |||
| 0ec4e37c13 | |||
| 9d301631ca | |||
| ec7145bb01 | |||
| f79d84b5fa | |||
| d00f1e0def | |||
| 637a813732 | |||
| 4f33c9280b | |||
| d54c752637 | |||
| 984d5d198d | |||
| 2568778a87 | |||
| 0d805a64f6 | |||
| 066d1c5b1e | |||
| cdc1950c3a | |||
| 7142874513 | |||
| 409c2f62be | |||
| 82922c5053 | |||
| 9c5bcb13e2 | |||
| 1d87dcdb7f | |||
| c7ba44a91e | |||
| 19b79c5b35 | |||
| 45216763e3 | |||
| 123293dc0e | |||
| 78e61060d2 | |||
| dd5371d2fd | |||
| da041d1f38 | |||
| d01599a2b4 | |||
| e80a2268ac | |||
| 3b86229c8f | |||
| c4d498d183 | |||
| 9bc46914e2 | |||
| 75f982e677 | |||
| f58bfeb1d0 | |||
| ebb077f6e0 | |||
| fdf6908fc6 | |||
| 7936660868 | |||
| 554dd3798f | |||
| 7f40d8e12c | |||
| 31d6f44ff5 | |||
| 86efcde927 | |||
| 4acbc33762 | |||
| 7cd3ebfcba | |||
| 55c97cab7e | |||
| 5bde091704 | |||
| 70489b46a5 | |||
| d7e89cc01f | |||
| a1f6030e75 | |||
| e7b2d8435e | |||
| f890a9633b | |||
| 5bdcd3124b | |||
| 752b3f8e4c | |||
| 001e836ae3 | |||
| 50d7f9a9dd | |||
| 34a5e58872 | |||
| 34fbea6a44 | |||
| 8737535b90 | |||
| 28ef414c3d | |||
| 16279949c1 | |||
| 444e22dd0e | |||
| a231506713 | |||
| 5dcabba73c | |||
| cd71c494da | |||
| f66642b8ec | |||
| 2283e1b0f2 | |||
| e0eb725d8b | |||
| 05e9549410 | |||
| 35fbce3506 | |||
| c1d6825793 | |||
| ae6c6970b4 | |||
| f8fef57cf3 | |||
| 4dc191735d |
@@ -0,0 +1,31 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
**/node_modules
|
||||
|
||||
# Rust build artifacts
|
||||
target
|
||||
**/target
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Build outputs
|
||||
.next
|
||||
**/.next
|
||||
out
|
||||
**/out
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
*.swp
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
@@ -8,6 +8,6 @@ updates:
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*" # Group all Actions updates into a single larger pull request
|
||||
- '*' # Group all Actions updates into a single larger pull request
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: 'CodeQL Advanced'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
pull_request:
|
||||
branches: ['main']
|
||||
schedule:
|
||||
- cron: '38 20 * * 4'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: rust
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||
# or others). This is typically only required for manual builds.
|
||||
# - name: Setup runtime (example)
|
||||
# uses: actions/setup-example@v1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- name: Run manual build steps
|
||||
if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
@@ -1,5 +1,7 @@
|
||||
name: PR checks
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
permissions:
|
||||
@@ -10,21 +12,30 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: '-C target-cpu=skylake'
|
||||
SCCACHE_GHA_ENABLED: 'true'
|
||||
RUSTC_WRAPPER: sccache
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
- name: Install minimal stable with clippy and rustfmt
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: Cache apt packages
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: apt-rust-lint-${{ runner.os }}
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pkg-config libfontconfig-dev libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libsoup-3.0-dev
|
||||
- name: Format
|
||||
- name: Format check
|
||||
working-directory: apps/readest-app/src-tauri
|
||||
run: cargo fmt --check
|
||||
- name: Clippy Check
|
||||
@@ -33,59 +44,162 @@ jobs:
|
||||
|
||||
build_web_app:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- platform: 'web'
|
||||
- platform: 'tauri'
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.14.0
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: cache Next.js build
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: apps/readest-app/.next/cache
|
||||
key: nextjs-${{ matrix.config.platform }}-${{ github.sha }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
key: nextjs-web-${{ github.sha }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
nextjs-${{ matrix.config.platform }}-${{ github.sha }}-
|
||||
nextjs-${{ matrix.config.platform }}-
|
||||
nextjs-web-${{ github.sha }}-
|
||||
nextjs-web-
|
||||
|
||||
- name: install Dependencies
|
||||
working-directory: apps/readest-app
|
||||
run: |
|
||||
pnpm install && pnpm setup-pdfjs
|
||||
pnpm install && pnpm setup-vendors
|
||||
|
||||
- name: run tests
|
||||
working-directory: apps/readest-app
|
||||
- name: install LuaJIT (for koplugin lint)
|
||||
run: sudo apt-get update && sudo apt-get install -y luajit
|
||||
|
||||
- name: run format check
|
||||
run: |
|
||||
pnpm test -- --watch=false
|
||||
pnpm format:check || (pnpm format && git diff && exit 1)
|
||||
|
||||
- name: run lint
|
||||
working-directory: apps/readest-app
|
||||
run: |
|
||||
pnpm lint
|
||||
|
||||
- name: build the web App
|
||||
if: matrix.config.platform == 'web'
|
||||
- name: build the web app
|
||||
working-directory: apps/readest-app
|
||||
run: |
|
||||
pnpm build-web && pnpm check:all
|
||||
|
||||
- name: build the Tauri App
|
||||
if: matrix.config.platform == 'tauri'
|
||||
test_web_app:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: cache Next.js build
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: apps/readest-app/.next/cache
|
||||
key: nextjs-test-${{ github.sha }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
nextjs-test-${{ github.sha }}-
|
||||
nextjs-test-
|
||||
|
||||
- name: install Dependencies
|
||||
working-directory: apps/readest-app
|
||||
run: |
|
||||
pnpm build && pnpm check:all
|
||||
pnpm install && pnpm setup-vendors
|
||||
|
||||
- name: install LuaJIT + busted (for koplugin tests)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
# luajit — pnpm test:lua
|
||||
# luarocks/libsqlite3-dev — required to build lsqlite3complete
|
||||
sudo apt-get install -y luajit luarocks libsqlite3-dev
|
||||
# Install busted + the SQLite binding the LibraryStore specs use,
|
||||
# both pinned to Lua 5.1 (LuaJIT-compatible). System-wide install
|
||||
# so `luarocks --lua-version=5.1 path` (sourced by
|
||||
# scripts/test-koplugin.mjs) picks them up.
|
||||
sudo luarocks --lua-version=5.1 install busted
|
||||
sudo luarocks --lua-version=5.1 install lsqlite3complete
|
||||
|
||||
- name: install playwright browsers
|
||||
working-directory: apps/readest-app
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: run web tests
|
||||
working-directory: apps/readest-app
|
||||
run: pnpm test:pr:web
|
||||
|
||||
- name: run koplugin tests
|
||||
working-directory: apps/readest-app
|
||||
run: pnpm test:lua
|
||||
|
||||
build_tauri_app:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: 'true'
|
||||
RUSTC_WRAPPER: sccache
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: cache Next.js build
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: apps/readest-app/.next/cache
|
||||
key: nextjs-tauri-${{ github.sha }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
nextjs-tauri-${{ github.sha }}-
|
||||
nextjs-tauri-
|
||||
|
||||
- name: install Dependencies
|
||||
working-directory: apps/readest-app
|
||||
run: |
|
||||
pnpm install && pnpm setup-vendors
|
||||
|
||||
- name: setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
|
||||
- name: install Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: tauri-cargo
|
||||
cache-all-crates: 'true'
|
||||
|
||||
- name: Cache apt packages
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: apt-tauri-${{ runner.os }}
|
||||
- name: install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pkg-config libfontconfig-dev libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libsoup-3.0-dev xvfb
|
||||
|
||||
- name: run tauri tests
|
||||
working-directory: apps/readest-app
|
||||
run: xvfb-run pnpm test:pr:tauri
|
||||
|
||||
@@ -17,14 +17,14 @@ jobs:
|
||||
release_version: ${{ steps.get-release-notes.outputs.release_version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
- name: get version
|
||||
run: echo "PACKAGE_VERSION=$(node -p "require('./apps/readest-app/package.json').version")" >> $GITHUB_ENV
|
||||
- name: get release
|
||||
id: get-release
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const { data } = await github.rest.repos.getLatestRelease({
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
core.setOutput('release_tag', data.tag_name);
|
||||
- name: get release notes
|
||||
id: get-release-notes
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
steps:
|
||||
- name: update release
|
||||
id: update-release
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
release_id: ${{ needs.get-release.outputs.release_id }}
|
||||
release_tag: ${{ needs.get-release.outputs.release_tag }}
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
tag_name: process.env.release_tag,
|
||||
})
|
||||
const notes = process.env.release_note.split(/(?:\d\.\s)/).filter(Boolean);
|
||||
const notes = process.env.release_note.split(/\d+\.\s/).filter(Boolean);
|
||||
const formattedNotes = notes.map(note => `* ${note.trim()}`).join("\n");
|
||||
const body = `## Release Highlight\n${formattedNotes}\n\n${data.body}`;
|
||||
github.rest.repos.updateRelease({
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: create KOReader plugin zip
|
||||
env:
|
||||
@@ -95,10 +95,23 @@ jobs:
|
||||
run: |
|
||||
version=${{ needs.get-release.outputs.release_version }}
|
||||
plugin_zip="Readest-${version}-1.koplugin.zip"
|
||||
meta_file="apps/readest.koplugin/_meta.lua"
|
||||
perl -i -pe "s/^}/ version = \"${version}\",\n}/" "${meta_file}"
|
||||
|
||||
cd apps/readest.koplugin
|
||||
zip -r ../../${plugin_zip} .
|
||||
cd ../..
|
||||
# Exclude dev-only artifacts from the published plugin zip:
|
||||
# scripts/ — i18n + build helpers
|
||||
# docs/ — design notes
|
||||
# spec/ — busted test suite
|
||||
# .busted — busted runner config
|
||||
# Mirror these in apps/readest.koplugin/scripts/build-koplugin.mjs
|
||||
# for local builds.
|
||||
cd apps
|
||||
zip -r ../${plugin_zip} readest.koplugin \
|
||||
-x 'readest.koplugin/scripts/*' \
|
||||
'readest.koplugin/docs/*' \
|
||||
'readest.koplugin/spec/*' \
|
||||
'readest.koplugin/.busted'
|
||||
cd ..
|
||||
|
||||
echo "Uploading ${plugin_zip} to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} ${plugin_zip} --clobber
|
||||
@@ -122,11 +135,6 @@ jobs:
|
||||
release: linux
|
||||
arch: aarch64
|
||||
rust_target: aarch64-unknown-linux-gnu
|
||||
- os: ubuntu-22.04-arm
|
||||
release: linux
|
||||
arch: armhf
|
||||
rust_target: arm-unknown-linux-gnueabihf
|
||||
args: '--target arm-unknown-linux-gnueabihf --bundles deb'
|
||||
- os: macos-latest
|
||||
release: macos
|
||||
arch: aarch64
|
||||
@@ -146,20 +154,18 @@ jobs:
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: initialize git submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.14.0
|
||||
uses: pnpm/action-setup@v6
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: setup Java (for Android build only)
|
||||
@@ -171,7 +177,7 @@ jobs:
|
||||
|
||||
- name: setup Android SDK (for Android build only)
|
||||
if: matrix.config.release == 'android'
|
||||
uses: android-actions/setup-android@v3
|
||||
uses: android-actions/setup-android@v4
|
||||
|
||||
- name: install NDK (for Android build only)
|
||||
if: matrix.config.release == 'android'
|
||||
@@ -180,8 +186,8 @@ jobs:
|
||||
- name: install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: copy pdfjs-dist to public directory
|
||||
run: pnpm --filter @readest/readest-app setup-pdfjs
|
||||
- name: copy pdfjs-dist and simplecc-dist to public directory
|
||||
run: pnpm --filter @readest/readest-app setup-vendors
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -196,7 +202,7 @@ jobs:
|
||||
if: contains(matrix.config.os, 'ubuntu') && matrix.config.release != 'android' && matrix.config.arch != 'armhf'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pkg-config libfontconfig-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
|
||||
sudo apt-get install -y pkg-config libfontconfig-dev libgtk-3-dev libwebkit2gtk-4.1 libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1 libjavascriptcoregtk-4.1-dev gir1.2-javascriptcoregtk-4.1 gir1.2-webkit2-4.1 libappindicator3-dev librsvg2-dev patchelf xdg-utils
|
||||
|
||||
- name: install dependencies (ubuntu only - armhf specific)
|
||||
if: contains(matrix.config.os, 'ubuntu') && matrix.config.arch == 'armhf'
|
||||
@@ -208,6 +214,7 @@ jobs:
|
||||
echo 'PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/share/pkgconfig' >> $GITHUB_ENV
|
||||
echo 'PKG_CONFIG_SYSROOT_DIR=/usr/arm-linux-gnueabihf' >> $GITHUB_ENV
|
||||
echo 'CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc' >> $GITHUB_ENV
|
||||
echo 'CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_RUSTFLAGS=--cfg=io_uring_skip_arch_check' >> $GITHUB_ENV
|
||||
|
||||
- name: create .env.local file for Next.js
|
||||
run: |
|
||||
@@ -284,10 +291,15 @@ jobs:
|
||||
echo "Uploading updated latest.json to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} latest.json --clobber
|
||||
|
||||
- name: Override tauri-cli with custom AppImage format (Linux)
|
||||
if: matrix.config.release == 'linux'
|
||||
run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage --force
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
if: matrix.config.release != 'android'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: 'true'
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
@@ -314,6 +326,8 @@ jobs:
|
||||
if: matrix.config.os == 'windows-latest'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
shell: bash
|
||||
@@ -321,7 +335,7 @@ jobs:
|
||||
echo "Building Portable Binaries"
|
||||
pushd apps/readest-app/
|
||||
echo "NEXT_PUBLIC_PORTABLE_APP=true" >> .env.local
|
||||
pnpm tauri build
|
||||
pnpm tauri build ${{ matrix.config.args }}
|
||||
|
||||
popd
|
||||
echo "Uploading Portable Binaries"
|
||||
@@ -346,8 +360,51 @@ jobs:
|
||||
echo "Uploading $bin_file to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} $bin_file --clobber
|
||||
|
||||
echo "Signing portable binary"
|
||||
pushd apps/readest-app/
|
||||
pnpm tauri signer sign "../../$bin_file"
|
||||
popd
|
||||
echo "Uploading signature to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} $bin_file.sig --clobber
|
||||
|
||||
- name: download and update latest.json for Windows portable release
|
||||
if: matrix.config.os == 'windows-latest'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -sL https://github.com/readest/readest/releases/latest/download/latest.json -o latest.json
|
||||
|
||||
version=${{ needs.get-release.outputs.release_version }}
|
||||
arch=${{ matrix.config.arch }}
|
||||
|
||||
if [ "$arch" = "x86_64" ]; then
|
||||
bin_file="Readest_${version}_x64-portable.exe"
|
||||
platform_key="windows-x86_64-portable"
|
||||
elif [ "$arch" = "aarch64" ]; then
|
||||
bin_file="Readest_${version}_arm64-portable.exe"
|
||||
platform_key="windows-aarch64-portable"
|
||||
else
|
||||
echo "Unknown architecture: $arch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
portable_url="https://github.com/readest/readest/releases/download/${{ needs.get-release.outputs.release_tag }}/$bin_file"
|
||||
portable_sig=$(cat $bin_file.sig)
|
||||
|
||||
jq --arg url "$portable_url" \
|
||||
--arg sig "$portable_sig" \
|
||||
--arg key "$platform_key" \
|
||||
'.platforms[$key] = {signature: $sig, url: $url}' latest.json > tmp.$$.json && mv tmp.$$.json latest.json
|
||||
|
||||
echo "Uploading updated latest.json to GitHub release"
|
||||
gh release upload ${{ needs.get-release.outputs.release_tag }} latest.json --clobber
|
||||
|
||||
upload-to-r2:
|
||||
needs: [get-release, build-tauri]
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
uses: ./.github/workflows/upload-to-r2.yml
|
||||
with:
|
||||
tag: ${{ needs.get-release.outputs.release_tag }}
|
||||
|
||||
@@ -8,6 +8,8 @@ on:
|
||||
jobs:
|
||||
rerun:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- name: rerun ${{ inputs.run_id }}
|
||||
env:
|
||||
|
||||
@@ -17,6 +17,8 @@ on:
|
||||
jobs:
|
||||
upload-to-r2:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 3
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -70,6 +72,8 @@ jobs:
|
||||
if: failure() && fromJSON(github.run_attempt) < 3
|
||||
needs: [upload-to-r2]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
|
||||
@@ -11,10 +11,10 @@ jobs:
|
||||
build_and_deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: 'true'
|
||||
- uses: amondnet/vercel-action@v25
|
||||
- uses: amondnet/vercel-action@v42
|
||||
with:
|
||||
vercel-token: ${{ secrets.VERCEL_TOKEN }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+11
@@ -1,4 +1,5 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
docker/.env
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
@@ -40,6 +41,16 @@ next-env.d.ts
|
||||
target
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/metadata/android/en-US/changelogs
|
||||
|
||||
*.koplugin.zip
|
||||
|
||||
# nix
|
||||
result*
|
||||
|
||||
.playwright-mcp/
|
||||
.gstack
|
||||
|
||||
.claude/worktrees
|
||||
.claude/settings.local.json
|
||||
|
||||
|
||||
+15
@@ -7,3 +7,18 @@
|
||||
[submodule "packages/tauri-plugins"]
|
||||
path = packages/tauri-plugins
|
||||
url = https://github.com/readest/tauri-plugins-workspace.git
|
||||
[submodule "packages/simplecc-wasm"]
|
||||
path = packages/simplecc-wasm
|
||||
url = https://github.com/readest/simplecc-wasm.git
|
||||
[submodule "apps/readest-app/src-tauri/plugins/tauri-plugin-turso"]
|
||||
path = apps/readest-app/src-tauri/plugins/tauri-plugin-turso
|
||||
url = https://github.com/readest/tauri-plugin-turso.git
|
||||
[submodule "apps/readest-app/.claude/skills/gstack"]
|
||||
path = apps/readest-app/.claude/skills/gstack
|
||||
url = https://github.com/garrytan/gstack.git
|
||||
[submodule "packages/qcms"]
|
||||
path = packages/qcms
|
||||
url = https://github.com/mozilla/pdf.js.qcms.git
|
||||
[submodule "packages/js-mdict"]
|
||||
path = packages/js-mdict
|
||||
url = https://github.com/readest/js-mdict.git
|
||||
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
pnpm exec lint-staged
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
pnpm -C apps/readest-app lint
|
||||
pnpm -C apps/readest-app test
|
||||
+38
-1
@@ -1 +1,38 @@
|
||||
packages/foliate-js/
|
||||
# Dependencies
|
||||
node_modules
|
||||
pnpm-lock.yaml
|
||||
|
||||
# Build Artifacts (Web & Rust)
|
||||
.next
|
||||
.open-next
|
||||
.build
|
||||
.tauri
|
||||
out
|
||||
build
|
||||
dist
|
||||
target
|
||||
fastlane
|
||||
.wrangler
|
||||
|
||||
# Autogenerated Tauri files
|
||||
gen
|
||||
**/autogenerated
|
||||
**/schemas
|
||||
|
||||
# Submodules (External Repos)
|
||||
packages
|
||||
|
||||
# Claude Code Skills & Config
|
||||
apps/readest-app/.claude
|
||||
|
||||
# Vendored Assets (Generated/External Code)
|
||||
apps/readest-app/public/*.js
|
||||
apps/readest-app/public/vendor
|
||||
apps/readest-app/src-tauri/plugins/tauri-plugin-turso
|
||||
|
||||
# Environment & Editor
|
||||
.env
|
||||
.env.*
|
||||
.vscode
|
||||
.idea
|
||||
*.log
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-vscode.vscode-typescript-next",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"rust-lang.rust-analyzer"
|
||||
]
|
||||
}
|
||||
Vendored
+16
-1
@@ -13,4 +13,19 @@
|
||||
"javascript.validate.enable": false,
|
||||
"javascript.format.enable": false,
|
||||
"typescript.format.enable": false,
|
||||
}
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"prettier.requireConfig": true,
|
||||
"prettier.documentSelectors": ["**/*.{js,jsx,ts,tsx,css,json,md,html,yml}"]
|
||||
}
|
||||
|
||||
+2
-2
@@ -43,8 +43,8 @@ git submodule update --init --recursive
|
||||
```bash
|
||||
# might need to rerun this when code is updated
|
||||
pnpm install
|
||||
# copy pdfjs-dist to Next.js public directory
|
||||
pnpm --filter @readest/readest-app setup-pdfjs
|
||||
# copy vendors dist libs to public directory
|
||||
pnpm --filter @readest/readest-app setup-vendors
|
||||
```
|
||||
|
||||
#### 3. Verify Dependencies Installation
|
||||
|
||||
Generated
+4219
-1309
File diff suppressed because it is too large
Load Diff
+9
-4
@@ -2,8 +2,10 @@
|
||||
members = [
|
||||
"apps/readest-app/src-tauri",
|
||||
"packages/tauri/crates/tauri",
|
||||
"packages/tauri/crates/tauri-utils",
|
||||
"packages/tauri/crates/tauri-build",
|
||||
"packages/tauri-plugins/plugins/fs"
|
||||
]
|
||||
exclude = [
|
||||
"packages/qcms"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -19,8 +21,12 @@ schemars = "0.8"
|
||||
serde_json = "1"
|
||||
thiserror = "2"
|
||||
glob = "0.3"
|
||||
zbus = "5.9"
|
||||
dunce = "1"
|
||||
url = "2"
|
||||
tar = "0.4.45"
|
||||
nix = "0.20.2"
|
||||
glib = "0.20.0"
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Bilingify LLC"]
|
||||
@@ -33,5 +39,4 @@ rust-version = "1.77.2"
|
||||
|
||||
[patch.crates-io]
|
||||
tauri = { path = "packages/tauri/crates/tauri" }
|
||||
tauri-utils = { path = "packages/tauri/crates/tauri-utils" }
|
||||
tauri-build = { path = "packages/tauri/crates/tauri-build" }
|
||||
tauri-plugin-fs = { path = "packages/tauri-plugins/plugins/fs" }
|
||||
|
||||
+33
-31
@@ -1,38 +1,40 @@
|
||||
FROM node:22-slim
|
||||
|
||||
ENV PNPM_HOME="/root/.local/share/pnpm"
|
||||
ENV PATH="${PATH}:${PNPM_HOME}"
|
||||
|
||||
RUN npm install --global pnpm
|
||||
|
||||
# Install necessary packages
|
||||
RUN apt update -y && apt install -y --no-install-recommends \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
build-essential \
|
||||
curl \
|
||||
wget \
|
||||
file \
|
||||
libxdo-dev \
|
||||
libssl-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev \
|
||||
ca-certificates \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Rust and Cargo
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||
|
||||
COPY . /app
|
||||
|
||||
FROM docker.io/node:22-slim AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
RUN corepack prepare pnpm@10.29.3 --activate
|
||||
WORKDIR /app
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY apps/readest-app/package.json ./apps/readest-app/
|
||||
COPY patches/ ./patches/
|
||||
COPY packages/ ./packages/
|
||||
|
||||
RUN pnpm install
|
||||
|
||||
RUN pnpm --filter @readest/readest-app setup-pdfjs
|
||||
FROM base AS dependencies
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
RUN pnpm --filter @readest/readest-app setup-vendors
|
||||
|
||||
FROM dependencies AS development-stage
|
||||
COPY . .
|
||||
WORKDIR /app/apps/readest-app
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT ["pnpm", "dev-web", "-H", "0.0.0.0"]
|
||||
|
||||
FROM base AS build
|
||||
ARG NEXT_PUBLIC_SUPABASE_URL
|
||||
ARG NEXT_PUBLIC_SUPABASE_ANON_KEY
|
||||
ARG NEXT_PUBLIC_APP_PLATFORM
|
||||
ARG NEXT_PUBLIC_API_BASE_URL
|
||||
ARG NEXT_PUBLIC_OBJECT_STORAGE_TYPE
|
||||
ARG NEXT_PUBLIC_STORAGE_FIXED_QUOTA
|
||||
ARG NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA
|
||||
COPY --from=dependencies /app/node_modules /app/node_modules
|
||||
COPY --from=dependencies /app/apps/readest-app/node_modules /app/apps/readest-app/node_modules
|
||||
COPY --from=dependencies /app/apps/readest-app/public/vendor /app/apps/readest-app/public/vendor
|
||||
COPY --from=dependencies /app/packages/foliate-js/node_modules /app/packages/foliate-js/node_modules
|
||||
COPY . .
|
||||
WORKDIR /app/apps/readest-app
|
||||
RUN pnpm build-web
|
||||
|
||||
ENTRYPOINT ["pnpm", "start-web"]
|
||||
FROM build as production-stage
|
||||
ENTRYPOINT ["pnpm", "start-web", "-H", "0.0.0.0"]
|
||||
EXPOSE 3000
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<h1>Readest</h1>
|
||||
<br>
|
||||
|
||||
[Readest][link-website] is an open-source ebook reader designed for immersive and deep reading experiences. Built as a modern rewrite of [Foliate](https://github.com/johnfactotum/foliate), it leverages [Next.js 15](https://github.com/vercel/next.js) and [Tauri v2](https://github.com/tauri-apps/tauri) to deliver a smooth, cross-platform experience across macOS, Windows, Linux, Android, iOS, and the Web.
|
||||
[Readest][link-website] is an open-source ebook reader designed for immersive and deep reading experiences. Built as a modern rewrite of [Foliate](https://github.com/johnfactotum/foliate), it leverages [Next.js 16](https://github.com/vercel/next.js) and [Tauri v2](https://github.com/tauri-apps/tauri) to deliver a smooth, cross-platform experience across macOS, Windows, Linux, Android, iOS, and the Web.
|
||||
|
||||
[![Website][badge-website]][link-website]
|
||||
[![Web App][badge-web-app]][link-web-readest]
|
||||
@@ -14,7 +14,7 @@
|
||||
[![Discord][badge-discord]][link-discord]
|
||||
[![Reddit][badge-reddit]][link-reddit]
|
||||
[![AGPL Licence][badge-license]](LICENSE)
|
||||
[![Language Coverage][badge-language-coverage]]()
|
||||
[![Language Coverage][badge-language-coverage]][link-locales]
|
||||
[![Donate][badge-donate]][link-donate]
|
||||
[![Latest release][badge-release]][link-gh-releases]
|
||||
[![Last commit][badge-last-commit]][link-gh-commits]
|
||||
@@ -45,38 +45,38 @@
|
||||
|
||||
<div align="left">✅ Implemented</div>
|
||||
|
||||
| **Feature** | **Description** | **Status** |
|
||||
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------- |
|
||||
| **Multi-Format Support** | Support EPUB, MOBI, KF8 (AZW3), FB2, CBZ, TXT, PDF (experimental) | ✅ |
|
||||
| **Scroll/Page View Modes** | Switch between scrolling or paginated reading modes. | ✅ |
|
||||
| **Full-Text Search** | Search across the entire book to find relevant sections. | ✅ |
|
||||
| **Annotations and Highlighting** | Add highlights, bookmarks, and notes to enhance your reading experience. | ✅ |
|
||||
| **Excerpt Text for Note-Taking** | Easily excerpt text from books for detailed notes and analysis. | ✅ |
|
||||
| **Dictionary/Wikipedia Lookup** | Instantly look up words and terms when reading. | ✅ |
|
||||
| **[Parallel Read][link-parallel-read]** | Read two books or documents simultaneously in a split-screen view. | ✅ |
|
||||
| **Customize Font and Layout** | Adjust font, layout, theme mode, and theme colors for a personalized experience. | ✅ |
|
||||
| **File Association and Open With** | Quickly open files in Readest in your file browser with one-click. | ✅ |
|
||||
| **Sync across Platforms** | Synchronize book files, reading progress, notes, and bookmarks across all supported platforms. | ✅ |
|
||||
| **Accessibility** | Provides full keyboard navigation and supports for screen readers such as VoiceOver, TalkBack, NVDA, and Orca. | ✅ |
|
||||
| **Translate with DeepL and Yandex** | From a single sentence to the entire book—translate instantly. | ✅ |
|
||||
| **Text-to-Speech (TTS) Support** | Enjoy smooth, multilingual narration—even within a single book. | ✅ |
|
||||
| **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ |
|
||||
| **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ |
|
||||
| **Feature** | **Description** | **Status** |
|
||||
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | ---------- |
|
||||
| **Multi-Format Support** | Support EPUB, MOBI, KF8 (AZW3), FB2, CBZ, TXT, PDF | ✅ |
|
||||
| **Scroll/Page View Modes** | Switch between scrolling or paginated reading modes. | ✅ |
|
||||
| **Full-Text Search** | Search across the entire book to find relevant sections. | ✅ |
|
||||
| **Annotations and Highlighting** | Add highlights, bookmarks, and notes to enhance your reading experience and use instant mode for quicker interactions. | ✅ |
|
||||
| **Dictionary/Wikipedia Lookup** | Instantly look up words and terms when reading. | ✅ |
|
||||
| **[Parallel Read][link-parallel-read]** | Read two books or documents simultaneously in a split-screen view. | ✅ |
|
||||
| **Customize Font and Layout** | Adjust font, layout, theme mode, and theme colors for a personalized experience. | ✅ |
|
||||
| **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ |
|
||||
| **File Association and Open With** | Quickly open files in Readest in your file browser with one-click. | ✅ |
|
||||
| **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ |
|
||||
| **OPDS/Calibre Integration** | Integrate OPDS/Calibre to access online libraries and catalogs. | ✅ |
|
||||
| **Translate with DeepL and Yandex** | From a single sentence to the entire book—translate instantly. | ✅ |
|
||||
| **Text-to-Speech (TTS) Support** | Enjoy smooth, multilingual narration—even within a single book. | ✅ |
|
||||
| **Sync across Platforms** | Synchronize book files, reading progress, notes, and bookmarks across all supported platforms. | ✅ |
|
||||
| [**Sync with Koreader**][link-kosync-wiki] | Synchronize reading progress, notes, and bookmarks with [Koreader][link-koreader] devices. | ✅ |
|
||||
| **Accessibility** | Provides full keyboard navigation and supports for screen readers such as VoiceOver, TalkBack, NVDA, and Orca. | ✅ |
|
||||
| **Visual & Focus Aids** | Reading ruler, paragraph-by-paragraph reading mode, and speed reading features. | ✅ |
|
||||
|
||||
## Planned Features
|
||||
|
||||
<div align="left">🛠 Building</div>
|
||||
<div align="left">🔄 Planned</div>
|
||||
|
||||
| **Feature** | **Description** | **Priority** |
|
||||
| ------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------ |
|
||||
| [**Sync with Koreader**][link-kosync-wiki] | Synchronize reading progress, notes, and bookmarks with [Koreader][link-koreader] devices. | 🛠 |
|
||||
| **AI-Powered Summarization** | Generate summaries of books or chapters using AI for quick insights. | 🛠 |
|
||||
| **Support OPDS/Calibre** | Integrate OPDS/Calibre to access online libraries and catalogs. | 🔄 |
|
||||
| **Audiobook Support** | Extend functionality to play and manage audiobooks. | 🔄 |
|
||||
| **Handwriting Annotations** | Add support for handwriting annotations using a pen on compatible devices. | 🔄 |
|
||||
| **Advanced Reading Stats** | Track reading time, pages read, and more for detailed insights. | 🔄 |
|
||||
| **In-Library Full-Text Search** | Search across your entire ebook library to find topics and quotes. | 🔄 |
|
||||
| **Feature** | **Description** | **Priority** |
|
||||
| ------------------------------- | -------------------------------------------------------------------------- | ------------ |
|
||||
| **AI-Powered Summarization** | Generate summaries of books or chapters using AI for quick insights. | 🛠 |
|
||||
| **Advanced Reading Stats** | Track reading time, pages read, and more for detailed insights. | 🛠 |
|
||||
| **Audiobook Support** | Extend functionality to play and manage audiobooks. | 🔄 |
|
||||
| **Handwriting Annotations** | Add support for handwriting annotations using a pen on compatible devices. | 🔄 |
|
||||
| **In-Library Full-Text Search** | Search across your entire ebook library to find topics and quotes. | 🔄 |
|
||||
|
||||
Stay tuned for continuous improvements and updates! Contributions and suggestions are always welcome—let's build the ultimate reading experience together. 😊
|
||||
|
||||
@@ -111,6 +111,7 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion
|
||||
|
||||
- macOS / iOS / iPadOS : Search and install **Readest** on the [App Store][link-appstore], _also_ available on TestFlight for beta test (send your Apple ID to <readestapp@gmail.com> to request access).
|
||||
- Windows / Linux / Android: Visit and download **Readest** at [https://readest.com][link-website] or the [Releases on GitHub][link-gh-releases].
|
||||
- Linux users can also install [Readest on Flathub][link-flathub].
|
||||
- Web: Visit and use **Readest for Web** at [https://web.readest.com][link-web-readest].
|
||||
|
||||
## Requirements
|
||||
@@ -121,8 +122,8 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion
|
||||
For the best experience to build Readest for yourself, use a recent version of Node.js and Rust. Refer to the [Tauri documentation](https://v2.tauri.app/start/prerequisites/) for details on setting up the development environment prerequisites on different platforms.
|
||||
|
||||
```bash
|
||||
nvm install v22
|
||||
nvm use v22
|
||||
nvm install v24
|
||||
nvm use v24
|
||||
npm install -g pnpm
|
||||
rustup update
|
||||
```
|
||||
@@ -144,8 +145,8 @@ cd readest
|
||||
# might need to rerun this when code is updated
|
||||
git submodule update --init --recursive
|
||||
pnpm install
|
||||
# copy pdfjs-dist to Next.js public directory
|
||||
pnpm --filter @readest/readest-app setup-pdfjs
|
||||
# copy vendors dist libs to public directory
|
||||
pnpm --filter @readest/readest-app setup-vendors
|
||||
```
|
||||
|
||||
### 3. Verify Dependencies Installation
|
||||
@@ -175,7 +176,10 @@ For Android:
|
||||
|
||||
```bash
|
||||
# Initialize the Android environment (run once)
|
||||
rm apps/readest-app/src-tauri/gen/android
|
||||
pnpm tauri android init
|
||||
pnpm tauri icon ../../data/icons/readest-book.png
|
||||
git checkout apps/readest-app/src-tauri/gen/android
|
||||
|
||||
pnpm tauri android dev
|
||||
# or if you want to dev on a real device
|
||||
@@ -187,6 +191,7 @@ For iOS:
|
||||
```bash
|
||||
# Set up the iOS environment (run once)
|
||||
pnpm tauri ios init
|
||||
pnpm tauri icon ../../data/icons/readest-book.png
|
||||
|
||||
pnpm tauri ios dev
|
||||
# or if you want to dev on a real device
|
||||
@@ -201,6 +206,9 @@ pnpm tauri android build
|
||||
pnpm tauri ios build
|
||||
```
|
||||
|
||||
Please refer to our release script if you experience any issues:
|
||||
https://github.com/readest/readest/blob/main/.github/workflows/release.yml
|
||||
|
||||
### 6. Setup dev environment with Nix
|
||||
|
||||
If you have Nix installed, you can leverage flake to enter a development shell
|
||||
@@ -249,6 +257,32 @@ Please check the [wiki][link-gh-wiki] of this project for more information on de
|
||||
|
||||
- See Issue [readest/readest#358](https://github.com/readest/readest/issues/358) for further details, or head over to our [Discord][link-discord] server and open a support discussion with detailed logs of your environment and the steps you’ve taken.
|
||||
|
||||
### 2. AppImage Launches but Only Shows a Taskbar Icon
|
||||
|
||||
On some Arch Linux systems—especially those using Wayland—the Readest AppImage may briefly show an icon in the taskbar and then exit without opening a window.
|
||||
|
||||
You might see logs such as:
|
||||
|
||||
```
|
||||
Could not create default EGL display: EGL_BAD_PARAMETER. Aborting...
|
||||
```
|
||||
|
||||
This behavior is usually caused by compatibility issues between the bundled AppImage libraries and the system’s EGL / Wayland environment.
|
||||
|
||||
**Workaround 1: Launch with LD_PRELOAD (recommended)**
|
||||
|
||||
You can preload the system Wayland client library before launching the AppImage:
|
||||
|
||||
```
|
||||
LD_PRELOAD=/usr/lib/libwayland-client.so /path/to/Readest.AppImage
|
||||
```
|
||||
|
||||
This workaround has been confirmed to resolve the issue on affected systems.
|
||||
|
||||
**Workaround 2: Use the Flatpak Version**
|
||||
|
||||
If you prefer a more reliable out-of-the-box experience on Arch Linux, consider using the [Flatpak build on Flathub][link-flathub] instead. The Flatpak runtime helps avoid system library mismatches and tends to behave more consistently across different Wayland and X11 setups.
|
||||
|
||||
## Contributors
|
||||
|
||||
Readest is open-source, and contributions are welcome! Feel free to open issues, suggest features, or submit pull requests. Please **review our [contributing guidelines](CONTRIBUTING.md) before you start**. We also welcome you to join our [Discord][link-discord] community for either support or contributing guidance.
|
||||
@@ -261,17 +295,15 @@ Readest is open-source, and contributions are welcome! Feel free to open issues,
|
||||
|
||||
## Support
|
||||
|
||||
If Readest has been useful to you, consider supporting its development. Your contribution helps us squash bugs faster, improve performance, and keep building great features.
|
||||
If Readest has been useful to you, consider supporting its development. You can [become a sponsor on GitHub](https://github.com/sponsors/readest), [donate via Stripe](https://donate.stripe.com/4gMcN5aZdcE52kW3TFgjC01), or [donate with crypto](https://donate.readest.com). Your contribution helps us squash bugs faster, improve performance, and keep building great features.
|
||||
|
||||
### How to Donate
|
||||
### Sponsors
|
||||
|
||||
1. **GitHub Sponsors**
|
||||
Back the project directly on GitHub:
|
||||
👉 [https://github.com/sponsors/readest](https://github.com/sponsors/readest)
|
||||
|
||||
2. **Crypto Donations**
|
||||
Prefer crypto? You can donate here:
|
||||
👉 [https://donate.readest.com/](https://donate.readest.com/)
|
||||
<p align="center">
|
||||
<a title="Browser testing via TestMu AI" href="https://www.testmuai.com/?utm_medium=sponsor&utm_source=readest" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/readest/readest/refs/heads/main/data/sponsors/testmu-ai-logo.png" style="vertical-align: middle;" width="250" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## License
|
||||
|
||||
@@ -292,7 +324,9 @@ The following libraries and frameworks are used in this software:
|
||||
|
||||
The following fonts are utilized in this software, either bundled within the application or provided through web fonts:
|
||||
|
||||
[Bitter](https://fonts.google.com/?query=Bitter), [Fira Code](https://fonts.google.com/?query=Fira+Code), [Literata](https://fonts.google.com/?query=Literata), [Merriweather](https://fonts.google.com/?query=Merriweather), [Noto Sans](https://fonts.google.com/?query=Noto+Sans), [Roboto](https://fonts.google.com/?query=Roboto), [LXGW WenKai](https://github.com/lxgw/LxgwWenKai), [MiSans](https://hyperos.mi.com/font/en/), [Source Han](https://github.com/adobe-fonts/source-han-sans/), [WenQuanYi Micro Hei](http://wenq.org/wqy2/)
|
||||
[Bitter](https://fonts.google.com/specimen/Bitter), [Fira Code](https://fonts.google.com/specimen/Fira+Code), [Inter](https://fonts.google.com/specimen/Inter), [Literata](https://fonts.google.com/specimen/Literata), [Merriweather](https://fonts.google.com/specimen/Merriweather), [Noto Sans](https://fonts.google.com/specimen/Noto+Sans), [Roboto](https://fonts.google.com/specimen/Roboto), [LXGW WenKai](https://github.com/lxgw/LxgwWenKai), [MiSans](https://hyperos.mi.com/font/en/), [Source Han](https://github.com/adobe-fonts/source-han-sans/), [WenQuanYi Micro Hei](http://wenq.org/wqy2/)
|
||||
|
||||
We would also like to thank the [Web Chinese Fonts Plan](https://chinese-font.netlify.app) for offering open-source tools that enable the use of Chinese fonts on the web.
|
||||
|
||||
---
|
||||
|
||||
@@ -310,10 +344,11 @@ The following fonts are utilized in this software, either bundled within the app
|
||||
[badge-donate]: https://donate.readest.com/badge.svg
|
||||
[badge-deepwiki]: https://deepwiki.com/badge.svg
|
||||
[badge-reddit]: https://img.shields.io/reddit/subreddit-subscribers/readest?style=flat&logo=reddit&color=F37E41
|
||||
[badge-language-coverage]: https://img.shields.io/badge/coverage-52%25%20population%20🌍-green
|
||||
[badge-language-coverage]: https://img.shields.io/badge/coverage-53%25%20population%20🌍-green
|
||||
[link-donate]: https://donate.readest.com/?tickers=btc%2Ceth%2Csol%2Cusdc
|
||||
[link-appstore]: https://apps.apple.com/app/apple-store/id6738622779?pt=127463130&ct=github&mt=8
|
||||
[link-website]: https://readest.com?utm_source=github&utm_medium=referral&utm_campaign=readme
|
||||
[link-flathub]: https://flathub.org/en/apps/com.bilingify.readest
|
||||
[link-web-readest]: https://web.readest.com
|
||||
[link-gh-releases]: https://github.com/readest/readest/releases
|
||||
[link-gh-commits]: https://github.com/readest/readest/commits/main
|
||||
@@ -324,5 +359,6 @@ The following fonts are utilized in this software, either bundled within the app
|
||||
[link-koreader]: https://github.com/koreader/koreader
|
||||
[link-hellogithub]: https://hellogithub.com/repository/8a5b6ade2aee461a8bd94e59200682a7
|
||||
[link-deepwiki]: https://deepwiki.com/readest/readest
|
||||
[link-locales]: https://github.com/readest/readest/tree/main/apps/readest-app/public/locales
|
||||
[link-kosync-wiki]: https://github.com/readest/readest/wiki/Sync-with-Koreader-devices
|
||||
[link-reddit]: https://reddit.com/r/readest/
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
# Security Policy
|
||||
|
||||
## Threat Model
|
||||
|
||||
### Overview
|
||||
|
||||
Readest is a cross-platform e-reader (macOS, Windows, Linux, Android, iOS, Web) built on Next.js and Tauri. It processes user-supplied ebook files, syncs data to the cloud, integrates with external services (OPDS catalogs, KOReader, DeepL, Yandex), and handles user authentication.
|
||||
|
||||
### Assets
|
||||
|
||||
| Asset | Description |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------ |
|
||||
| Ebook files | User-uploaded EPUB, MOBI, PDF, and other formats stored locally and in cloud storage |
|
||||
| Reading progress & annotations | Highlights, bookmarks, and notes synced across devices |
|
||||
| User credentials | Authentication tokens and session data for cloud sync |
|
||||
| User preferences & settings | Reading preferences, custom fonts, theme configurations |
|
||||
| External API keys | Translation service credentials (DeepL, Yandex) configured by users |
|
||||
|
||||
### Threat Actors
|
||||
|
||||
| Actor | Motivation |
|
||||
| ----------------------- | ---------------------------------------------------------- |
|
||||
| Malicious ebook author | Craft a malformed file to exploit the parser or renderer |
|
||||
| Network attacker (MitM) | Intercept sync traffic to steal credentials or inject data |
|
||||
| Malicious OPDS server | Serve crafted catalog responses to exploit the client |
|
||||
| Compromised dependency | Supply chain attack via npm or Cargo ecosystem |
|
||||
| Unauthorized user | Access another user's synced library or annotations |
|
||||
|
||||
### Attack Surfaces & Mitigations
|
||||
|
||||
#### 1. Ebook File Parsing
|
||||
|
||||
- **Risk:** Malformed EPUB/MOBI/PDF files could trigger parser bugs, path traversal, or script injection via embedded HTML/JS.
|
||||
- **Mitigations:** Ebook content is rendered in a sandboxed iframe. External script execution is blocked. File parsing is isolated from the main process.
|
||||
|
||||
#### 2. Cloud Sync & Authentication
|
||||
|
||||
- **Risk:** Credential theft, session hijacking, or unauthorized access to another user's library data.
|
||||
- **Mitigations:** All sync traffic uses HTTPS/TLS. Authentication tokens are stored securely (OS keychain/secure storage). Server-side authorization ensures users can only access their own data.
|
||||
|
||||
#### 3. OPDS / External Catalog Integration
|
||||
|
||||
- **Risk:** A malicious OPDS server could serve crafted XML to exploit the parser, or redirect downloads to malicious files.
|
||||
- **Mitigations:** OPDS responses are parsed defensively. Users explicitly add catalog sources. Downloaded files are treated as untrusted user content.
|
||||
|
||||
#### 4. Rendered HTML/JS in Ebook Content
|
||||
|
||||
- **Risk:** Embedded JavaScript in EPUB files could attempt XSS or data exfiltration.
|
||||
- **Mitigations:** Book content is rendered in a sandboxed iframe with scripting restrictions. Navigation outside the book context is blocked.
|
||||
|
||||
#### 5. Supply Chain
|
||||
|
||||
- **Risk:** Compromised npm or Cargo packages could introduce malicious code.
|
||||
- **Mitigations:** Dependencies are pinned via `pnpm-lock.yaml` and `Cargo.lock`. Dependabot and GitHub's dependency review are enabled for automated vulnerability detection.
|
||||
|
||||
#### 6. Desktop Native Code (Tauri)
|
||||
|
||||
- **Risk:** Tauri IPC commands could be abused by malicious web content to access the filesystem or OS APIs.
|
||||
- **Mitigations:** Tauri's allowlist restricts which IPC commands are exposed. File system access is scoped to the application data directory.
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Vulnerabilities in user's operating system or browser outside of Readest's control
|
||||
- Physical access attacks to a user's device
|
||||
- Issues in third-party services (DeepL, Yandex, Calibre) themselves
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Readest does not currently maintain separate release channels. Security updates are provided only for the latest release series.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.10.x | :white_check_mark: |
|
||||
| < 0.10 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report suspected vulnerabilities privately. Do not open a public GitHub
|
||||
issue or discussion for security-sensitive reports.
|
||||
|
||||
Use GitHub's private vulnerability reporting for this repository:
|
||||
|
||||
<https://github.com/readest/readest/security/advisories/new>
|
||||
|
||||
When submitting a report, include:
|
||||
|
||||
- A clear description of the issue and the affected component
|
||||
- Steps to reproduce, proof of concept, or a minimal test case
|
||||
- The versions, platforms, or environments you tested
|
||||
- Any suggested remediation or mitigating details, if available
|
||||
|
||||
What to expect after you report:
|
||||
|
||||
- We will aim to acknowledge receipt within 3 business days.
|
||||
- We may contact you for additional details, reproduction steps, or validation.
|
||||
- If the report is accepted, we will work on a fix and coordinate disclosure.
|
||||
- If the report is declined, we will explain why, for example if the behavior is
|
||||
expected, unsupported, or not reproducible.
|
||||
|
||||
Please keep vulnerability details private until a fix is available and the
|
||||
maintainers have approved disclosure.
|
||||
|
||||
## Incident Response Plan
|
||||
|
||||
When a security vulnerability is confirmed, we follow this process:
|
||||
|
||||
### 1. Triage (Day 1–2)
|
||||
|
||||
- Assign a severity level (Critical / High / Medium / Low) based on impact and exploitability.
|
||||
- Identify affected versions, components, and users.
|
||||
- Assign an owner responsible for coordinating the response.
|
||||
|
||||
### 2. Containment (Day 1–3)
|
||||
|
||||
- Assess whether an immediate mitigation or workaround can be published.
|
||||
- Limit further exposure where possible (e.g., disable affected features, update dependencies).
|
||||
|
||||
### 3. Remediation (Day 3–14, depending on severity)
|
||||
|
||||
- Develop and internally review a fix.
|
||||
- Validate the fix does not introduce regressions.
|
||||
- Prepare a patched release and update changelog.
|
||||
|
||||
### 4. Disclosure & Release
|
||||
|
||||
- Coordinate disclosure timing with the reporter.
|
||||
- Publish a GitHub Security Advisory with CVE if applicable.
|
||||
- Release the patched version and notify users via release notes.
|
||||
|
||||
### 5. Post-Incident Review
|
||||
|
||||
- Document the root cause, timeline, and resolution.
|
||||
- Update processes or controls to prevent recurrence.
|
||||
|
||||
### Severity Definitions
|
||||
|
||||
| Severity | Description |
|
||||
| -------- | --------------------------------------------------------------------- |
|
||||
| Critical | Remote code execution, full data compromise, or authentication bypass |
|
||||
| High | Significant data exposure, privilege escalation, or denial of service |
|
||||
| Medium | Limited data exposure or functionality disruption |
|
||||
| Low | Minor issues with minimal security impact |
|
||||
@@ -0,0 +1,47 @@
|
||||
# Readest Project Memory
|
||||
|
||||
## Key Reference Documents
|
||||
- [Bug Fixing Patterns](bug-patterns.md) - Common bug categories, root causes, and fix strategies
|
||||
- [CSS & Style Fixes](css-style-fixes.md) - EPUB CSS override patterns and the style.ts pipeline
|
||||
- [TTS Fixes](tts-fixes.md) - Text-to-Speech architecture and bug patterns
|
||||
- [Layout & UI Fixes](layout-ui-fixes.md) - Safe insets, z-index, platform-specific UI issues
|
||||
- [Platform Compat Fixes](platform-compat-fixes.md) - Android, iOS, Linux, macOS platform-specific bugs
|
||||
- [Annotator & Reader Fixes](annotator-reader-fixes.md) - Highlight, selection, accessibility bugs
|
||||
|
||||
## Critical Files (Most Bug-Prone)
|
||||
- `src/utils/style.ts` - Central EPUB CSS transformation hub (14+ bug fixes)
|
||||
- `packages/foliate-js/paginator.js` - Page layout, image sizing, backgrounds
|
||||
- `src/services/tts/TTSController.ts` - TTS state machine, section tracking
|
||||
- `src/hooks/useSafeAreaInsets.ts` - Safe area inset management
|
||||
- `src/app/reader/components/FoliateViewer.tsx` - Reader view orchestration
|
||||
- `src/app/reader/components/annotator/Annotator.tsx` - Annotation lifecycle
|
||||
|
||||
## Feature Notes
|
||||
- [D-pad Navigation](dpad-navigation.md) — Android TV remote / keyboard arrow navigation design, key files, and pitfalls
|
||||
- [Cloudflare Workers WebSocket](cloudflare-workers-websocket.md) — use fetch() Upgrade pattern (not `ws` npm); CF delivers binary frames as Blob (must serialize async decodes)
|
||||
- [Share-a-Book Feature (in progress)](share-feature.md) — locked decisions for the /s/{token} share-link feature; plan at ~/.claude/plans/ok-we-will-learn-cosmic-acorn.md
|
||||
- [readest.koplugin i18n](koplugin-i18n.md) — gettext loader at `apps/readest.koplugin/i18n.lua`, `.po` catalog at `locales/<i18next-code>/translation.po`, extract/apply scripts in `scripts/`
|
||||
|
||||
## Patterns
|
||||
- [Virtuoso + OverlayScrollbars](virtuoso_overlayscrollbars.md) — useOverlayScrollbars hook integration for overlay scrollbars on mobile webviews
|
||||
- [Design system → DESIGN.md](feedback_design_system_doc.md) — codify recurring UI/UX rules in `apps/readest-app/DESIGN.md`; never `pl/pr/ml/mr/text-left/text-right` (RTL); §5 boxed list anatomy has uniform `min-h-14` rows and chromeless controls
|
||||
|
||||
## Architecture Notes
|
||||
- foliate-js is a git submodule at `packages/foliate-js/`
|
||||
- Multiview paginator: loads adjacent sections in background, multiple View/Overlayer instances per book
|
||||
- Style overrides: `getLayoutStyles()` (always), `getColorStyles()` (when overriding color)
|
||||
- `transformStylesheet()` does regex-based EPUB CSS rewriting at load time
|
||||
- TTS uses independent section tracking (`#ttsSectionIndex`) decoupled from view
|
||||
- Safe area insets flow: Native plugin -> useSafeAreaInsets hook -> component styles
|
||||
- Dropdown menus use `DropdownContext` (not blur-based) for screen reader compat
|
||||
|
||||
## Workflow
|
||||
- [Test file filter](feedback_test_file_filter.md) — use `pnpm test <path>` without `--` to run a single file
|
||||
- [Always rebase before PR](feedback_pr_rebase.md) — rebase onto origin/main before creating PRs
|
||||
- [New branch per PR](feedback_pr_new_branch.md) — always create a fresh branch from main for each new PR/issue
|
||||
- [Upgrade gstack locally](feedback_gstack_upgrade.md) — always upgrade from the project's .claude/skills/gstack, not global
|
||||
- [No lookbehind regex](feedback_no_lookbehind_regex.md) — never use `(?<=)` or `(?<!)` in JS/TS; build check rejects them
|
||||
- [Use worktree](feedback_use_worktree.md) — never `git worktree add` directly; always `pnpm worktree:new` before PR review, issue fix, or feature work
|
||||
- [en/translation.json holds ONLY plural variants + proper nouns](feedback_en_plurals_manual.md) — non-plural strings stay out (defaultValue: key is the en source); plural strings (`_('...', { count })`) need hand-added `_one`/`_other` entries or the singular renders as "1 days"
|
||||
- [Never push on every change](feedback_dont_push_every_change.md) — hold pushes during active bug iteration; commit locally only until user confirms or work hits a clean done-state
|
||||
- [No test seams in production code](feedback_no_test_seams_in_prod.md) — production must never import or call `__reset*ForTests`; cross-module test resets belong in the test file's beforeEach/afterEach
|
||||
@@ -0,0 +1,90 @@
|
||||
# Annotator & Reader Fixes Reference
|
||||
|
||||
## Annotation System Architecture
|
||||
|
||||
### Key Components
|
||||
- `Annotator.tsx` - Annotation lifecycle, popup display, style/color management
|
||||
- `AnnotationRangeEditor.tsx` - Drag handles for adjusting selection range
|
||||
- `MagnifierLoupe.tsx` - Magnifying glass during handle drag (mobile only)
|
||||
- `useTextSelector.ts` - Text selection detection and processing
|
||||
- `useAnnotationEditor.ts` - Editing existing annotations
|
||||
- `useInstantAnnotation.ts` - Creating new annotations on selection
|
||||
|
||||
### Highlight Rendering
|
||||
- Highlights rendered by foliate-js `Overlayer` (SVG overlayer in paginator shadow DOM, not iframe)
|
||||
- Each view in multiview paginator has its own `Overlayer` instance with unique clipPath ID
|
||||
- `Overlayer.add()` stores range + draw function; `redraw()` recalculates positions from stored ranges
|
||||
- Colors stored as color names mapped to custom hex via `globalReadSettings.customHighlightColors`
|
||||
- Sidebar uses `color-mix()` CSS function with custom colors, not Tailwind utility classes (#3273)
|
||||
- Rounded highlight style supported via `vertical` option passed to overlayer (#3208)
|
||||
|
||||
### Multiview Overlayer Pitfalls
|
||||
- **Duplicate SVG IDs**: Each overlayer creates `<clipPath>` for loupe hole — IDs MUST be unique per instance or `url(#id)` resolves to wrong element, clipping everything
|
||||
- **docLoadHandler scope**: `FoliateViewer.tsx` re-adds annotations on `load` event — MUST filter by `detail.index` (loaded section), not re-add ALL annotations (overwrites drag edits)
|
||||
- **MagnifierLoupe lifecycle**: Don't destroy/recreate loupe on every drag tick — `hideLoupe()` should only run on unmount, `showLoupe()` fast path updates position only
|
||||
- **Stale closures in useTextSelector**: `getProgress()` must be called inside callbacks, not captured at hook top-level (useFoliateEvents deps are `[view]` only)
|
||||
|
||||
## Fix History
|
||||
|
||||
| Issue | Problem | Root Cause | Fix |
|
||||
|-------|---------|------------|-----|
|
||||
| #3286 | Selection stuck on first annotation | `initializedRef` guard blocked re-computation | Remove guard, consolidate style/color effects |
|
||||
| #3273 | Custom colors not in sidebar | Hardcoded Tailwind classes | Use inline `style` with `color-mix()` |
|
||||
| #3234 | Letter-by-letter selection on mobile | No word boundary snapping | Add `snapRangeToWords()` using `Intl.Segmenter` |
|
||||
| #3208 | Hard rectangular highlights | No border radius support | Pass `vertical` option, update foliate-js |
|
||||
| #3002 | Can't see text under finger | No magnification UI | New `MagnifierLoupe` component using `view.renderer.showLoupe()` |
|
||||
| #3082 | No page numbers on annotations | `pageNumber` field missing | Add `pageNumber` to BookNote type, compute on create |
|
||||
| #3225 | Android tools unresponsive | Premature `makeSelection()` call | Remove premature re-selection in Android path |
|
||||
|
||||
## Common Annotation Bugs
|
||||
|
||||
### Selection Issues
|
||||
- **Word snapping**: Uses `Intl.Segmenter` with `granularity: 'word'` to snap selection to word boundaries
|
||||
- **Android re-selection**: Don't call `makeSelection(sel, index, true)` immediately on pointer-up; let the popup flow complete
|
||||
- **Range editor handles**: Remove `initializedRef` guards that prevent re-computation when switching annotations
|
||||
|
||||
### Color/Style Issues
|
||||
- **Custom colors in sidebar**: Use inline `style={{ backgroundColor: 'color-mix(...)' }}` not Tailwind classes
|
||||
- **Style synchronization**: Consolidate `selectedStyle` and `selectedColor` into one `useEffect`
|
||||
- **Switching annotations**: Must call `setShowAnnotPopup(false)` and `setEditingAnnotation(null)` before setting up new annotation
|
||||
|
||||
## Reader/Content Fixes
|
||||
|
||||
### Progress Display
|
||||
- Use physical `view.renderer.page` and `view.renderer.pages` for page counts (#3213, #3200)
|
||||
- Last page shows 100% by fixing boundary condition (#3383)
|
||||
- FB2 subsections need special handling for progress (#3136)
|
||||
|
||||
### Translation View (#3078)
|
||||
- Problem: Page jumps back during full-text translation
|
||||
- Root cause: DOM mutations from sequential translation insertions cause paginator relayout
|
||||
- Fix: Batch DOM updates with 50ms timer, use bounded concurrent queue (max 5), show loading overlay
|
||||
|
||||
### TOC Navigation (#3124)
|
||||
- Problem: Expanding TOC chapter scrolls back to current chapter
|
||||
- Fix: Only scroll-into-view on navigation, not on expand/collapse
|
||||
|
||||
## Accessibility (a11y) Fixes
|
||||
|
||||
### Screen Reader (TalkBack) Support
|
||||
- **Page indicator updates** (#2276): Add focus handlers on `<p>` elements that call `view.goTo(cfi)` to update position
|
||||
- **Navigation buttons** (#3036): Always show prev/next buttons when screen reader active; `PageNavigationButtons.tsx`
|
||||
- **Dropdown menus** (#3035): Use `DropdownContext` with overlay dismiss instead of blur-based closing
|
||||
|
||||
### Dropdown Architecture for a11y
|
||||
- `DropdownContext` (`src/context/DropdownContext.tsx`) manages which dropdown is open globally
|
||||
- Uses `useId()` for unique identification
|
||||
- One dropdown open at a time
|
||||
- `<Overlay>` for dismissal (tap/click outside) instead of `onBlur`
|
||||
- `<details>` element with `open={isOpen}` for semantic structure
|
||||
- No auto-focus-first-item (conflicts with TalkBack)
|
||||
|
||||
## E-ink Readability
|
||||
- Use `not-eink:` Tailwind variant for colors and opacity (#3258)
|
||||
- Don't use `text-primary` (blue) or low opacity on e-ink
|
||||
- Highlights use foreground color in dark mode e-ink (#3299)
|
||||
|
||||
## Key Utility Functions
|
||||
- `snapRangeToWords()` in `src/utils/sel.ts` - Word boundary snapping
|
||||
- `handleAccessibilityEvents()` in `src/utils/a11y.ts` - Screen reader focus handling
|
||||
- `color-mix()` CSS function for custom highlight colors with opacity
|
||||
@@ -0,0 +1,126 @@
|
||||
# Bug Fixing Patterns & Strategies
|
||||
|
||||
## Common Root Cause Categories
|
||||
|
||||
### 1. Overly Broad CSS Selectors
|
||||
**Pattern:** A CSS rule targets too many elements, causing unintended visual side effects.
|
||||
**Examples:**
|
||||
- `hr { mix-blend-mode: multiply }` applied to ALL hr elements instead of only decorative ones (#3086)
|
||||
- `p img { mix-blend-mode }` applied to block images, not just inline (#3112)
|
||||
- `svg, img { height: auto; width: auto }` overrode explicit HTML width/height attributes (#3274)
|
||||
- Background-color override applied unconditionally instead of only when user enabled color override (#3316)
|
||||
|
||||
**Fix Strategy:** Narrow selectors with class qualifiers (`.background-img`, `.has-text-siblings`) or attribute pseudo-selectors (`:where(:not([width]))`). Check if the rule should be conditional on a user setting.
|
||||
|
||||
### 2. Conditional vs Unconditional Style Overrides
|
||||
**Pattern:** CSS rules meant for "Override Book Color/Layout" mode are placed in the always-active stylesheet.
|
||||
**Examples:**
|
||||
- Calibre `.calibre { color: unset }` was in `getLayoutStyles()` instead of `getColorStyles()` (#3448)
|
||||
- Image background-color override applied without checking `overrideColor` flag (#3316, #3377)
|
||||
|
||||
**Fix Strategy:** Move rules to the correct conditional block: `getColorStyles()` for color overrides, `getLayoutStyles()` for layout overrides. Check the `overrideColor`/`overrideLayout` flags.
|
||||
|
||||
### 3. Missing EPUB Stylesheet Transformations
|
||||
**Pattern:** EPUB stylesheets contain CSS that conflicts with app functionality.
|
||||
**Examples:**
|
||||
- `user-select: none` prevents text selection (#3370) -> regex replace in `transformStylesheet()`
|
||||
- `font-family: serif/sans-serif` on body bypasses user font (#3334) -> detect and unset
|
||||
- Hardcoded Calibre backgrounds persist in dark mode (#3448) -> unset in color override
|
||||
|
||||
**Fix Strategy:** Add regex-based transformation passes in `transformStylesheet()` in `style.ts`.
|
||||
|
||||
### 4. Stale State / Refs Not Reset
|
||||
**Pattern:** A `useRef` or state variable is set once and never properly reset, blocking re-entry.
|
||||
**Examples:**
|
||||
- TTS `ttsOnRef` prevented restarting TTS from a new location (#3292)
|
||||
- `initializedRef` in AnnotationRangeEditor prevented handle position updates (#3286)
|
||||
- `view.tts` not nulled on shutdown prevented clean TTS restart (#3400)
|
||||
- TTS safety timeout fired after pause, advancing to next sentence (#3244)
|
||||
|
||||
**Fix Strategy:** Check all refs/guards in the affected flow. Ensure cleanup in shutdown/unmount. Remove overly aggressive guards that prevent re-entry.
|
||||
|
||||
### 5. Platform API Differences
|
||||
**Pattern:** A Web API behaves differently or is unavailable on certain platforms.
|
||||
**Examples:**
|
||||
- `navigator.getGamepads()` returns null on older Android WebView (#3245)
|
||||
- `CompressionStream` unavailable on some Android versions (#3255)
|
||||
- `btoa()` throws on non-ASCII characters (#3436)
|
||||
- View Transitions API unsupported in WebKitGTK/Linux (#3417)
|
||||
- `document.startViewTransition()` crashes on Linux
|
||||
|
||||
**Fix Strategy:** Always check API availability before use. Add fallback paths. Use feature detection, not platform detection when possible.
|
||||
|
||||
### 6. Safe Area Inset Issues
|
||||
**Pattern:** UI elements overlap system bars (status bar, navigation bar, notch) on mobile.
|
||||
**Examples:**
|
||||
- Zoom controls behind status bar (#3426)
|
||||
- Android navigation bar overlap (#3466)
|
||||
- iPad sidebar insets incorrect (#3395)
|
||||
- Reader page layout jump after system UI change (#3469)
|
||||
|
||||
**Fix Strategy:** Use `gridInsets` and `statusBarHeight` from `useSafeAreaInsets`. Use `env(safe-area-inset-*)` CSS functions. Call `onUpdateInsets()` after system UI visibility changes. See `docs/safe-area-insets.md`.
|
||||
|
||||
### 7. Z-Index Layering Issues
|
||||
**Pattern:** Interactive elements rendered behind other layers, becoming unclickable.
|
||||
**Examples:**
|
||||
- Navigation buttons invisible on mobile (#3201) -> added `z-10`
|
||||
- Annotation nav bar too prominent (#3386) -> reduced from `z-30` to `z-10`
|
||||
- Page nav buttons behind TTS control (#3184)
|
||||
|
||||
**Fix Strategy:** Check z-index ordering. Use minimum necessary z-index. Reference the z-index hierarchy in the codebase.
|
||||
|
||||
### 8. Event Handling Race Conditions
|
||||
**Pattern:** Timing issues between pointer events, native menus, and React state updates.
|
||||
**Examples:**
|
||||
- macOS context menu steals pointer event loop (#3324) -> 100ms setTimeout delay
|
||||
- Traffic light buttons flicker due to timeout race (#3488, #3129)
|
||||
- Android tool buttons unresponsive due to premature re-selection (#3225)
|
||||
|
||||
**Fix Strategy:** Add small delays before native menu calls. Check event state machine consistency. Remove premature re-triggers on Android.
|
||||
|
||||
### 9. foliate-js Rendering Issues
|
||||
**Pattern:** Bugs in the lower-level EPUB renderer (paginator.js, epub.js).
|
||||
**Examples:**
|
||||
- Image size not constrained in double-page mode (#3432)
|
||||
- Background not shown in scrolled mode (#3344)
|
||||
- Section content cached incorrectly after mode switch (#3242, #3206)
|
||||
- Swipe sensitivity too low for non-animated paging (#3310)
|
||||
|
||||
**Fix Strategy:** Check both `columnize()` and `scrolled()` code paths in paginator.js. Verify CSS variables (`--available-width`, `--available-height`) are computed correctly. Test in both paginated and scrolled modes.
|
||||
|
||||
### 10. Progress/Navigation Calculation Errors
|
||||
**Pattern:** Page counts, progress percentages, or position tracking are wrong.
|
||||
**Examples:**
|
||||
- Progress shows 99.9% at last page (#3383) -> boundary condition
|
||||
- Pages left shows estimated instead of physical count (#3213, #3200)
|
||||
- FB2 subsection progress wrong (#3136) -> nested structure not handled
|
||||
- TOC auto-scrolls on expand (#3124) -> scroll-into-view triggered too broadly
|
||||
|
||||
**Fix Strategy:** Use physical `view.renderer.page`/`view.renderer.pages` instead of estimated section metadata. Check boundary conditions (0-indexed vs 1-indexed, inclusive vs exclusive).
|
||||
|
||||
### 11. Debounced State Stale on User-Initiated Layout Change
|
||||
**Pattern:** A scroll/resize handler is debounced for performance, but during the debounce window any code path that re-runs layout based on saved state (e.g. `#anchor`, `#primaryIndex`) sees stale values.
|
||||
**Example:**
|
||||
- Scrolled-mode toggle reverted to previous chapter (#3987): the paginator's scroll handler is debounced 250 ms, so toggling `flow=scrolled → flow=paginated` within that window made `render() → scrollToAnchor(#anchor)` restore the anchor from before the user scrolled into the next section. Both `#anchor` and `#primaryIndex` were stale together, sending the position back.
|
||||
|
||||
**Fix Strategy:** When an external trigger forces a re-render (here, `setAttribute('flow', ...)`), flush the debounced state synchronously *before* changing the layout. In paginator.js this means overriding `setAttribute` and calling `#detectPrimaryView()` + `#getVisibleRange()` while `this.scrolled` is still true.
|
||||
|
||||
### 12. Multiview Paginator Side Effects
|
||||
**Pattern:** The multiview paginator (e925e9d+) loads adjacent sections in background. Events from these loads can interfere with user interactions on the primary section.
|
||||
**Examples:**
|
||||
- `load` event from adjacent section triggers `docLoadHandler` which re-adds ALL annotations, overwriting drag edits
|
||||
- Multiple overlayers with duplicate SVG `<clipPath>` IDs cause `url(#id)` to resolve to wrong element
|
||||
- `MagnifierLoupe` destroying/recreating body clone on every drag tick triggers ResizeObserver → expand → redraw
|
||||
|
||||
**Fix Strategy:** Scope event handlers to the loaded section's index. Use unique IDs for SVG elements across overlayer instances. Minimize iframe DOM mutations during drag operations.
|
||||
|
||||
## Debugging Workflow
|
||||
|
||||
1. **Identify the category** from the issue description
|
||||
2. **Check `style.ts`** first for any CSS-related visual bugs
|
||||
3. **Check foliate-js** for rendering/layout bugs
|
||||
4. **Check platform-specific code** for mobile/desktop differences
|
||||
5. **Write a failing test** before implementing the fix
|
||||
6. **Test in both paginated and scrolled modes** for layout changes
|
||||
7. **Test on multiple platforms** for any UI change
|
||||
8. **Run `pnpm build-check`** before submitting
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
name: Cloudflare Workers WebSocket
|
||||
description: How to open and read WebSockets from Cloudflare Workers (the Node `ws` package does not work) and the Blob binary-frame gotcha
|
||||
type: project
|
||||
originSessionId: ec3d5424-adc2-4fca-836f-df323797489c
|
||||
---
|
||||
# Cloudflare Workers WebSocket on readest-app
|
||||
|
||||
## Why the Node `ws` package fails
|
||||
|
||||
The Node `ws` npm package (used transitively by `isomorphic-ws`) opens WebSockets by calling `http.request({ createConnection })`. The Cloudflare Workers runtime does not implement `options.createConnection`, so any attempt to `new WebSocket(url, { headers })` in a Worker throws:
|
||||
|
||||
```
|
||||
The options.createConnection option is not implemented
|
||||
```
|
||||
|
||||
This applies even with `compatibility_flags = ["nodejs_compat"]`.
|
||||
|
||||
## Correct pattern: fetch-based upgrade
|
||||
|
||||
On Workers you open a WebSocket by calling `fetch()` with an `Upgrade: websocket` header against the **https://** (not `wss://`) form of the URL. The response has `status === 101` and a non-standard `webSocket` property that must be `accept()`ed before use:
|
||||
|
||||
```ts
|
||||
const upgradeUrl = url.replace(/^wss:\/\//i, 'https://');
|
||||
const response = (await fetch(upgradeUrl, {
|
||||
headers: { ...baseHeaders, Upgrade: 'websocket' },
|
||||
})) as Response & { webSocket?: WebSocket & { accept(): void } };
|
||||
|
||||
if (response.status !== 101 || !response.webSocket) {
|
||||
throw new Error(`WebSocket upgrade failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const ws = response.webSocket;
|
||||
ws.addEventListener('message', onMessage);
|
||||
ws.accept();
|
||||
ws.send(payload);
|
||||
```
|
||||
|
||||
Detect the Workers runtime with `typeof globalThis.WebSocketPair !== 'undefined'` — `WebSocketPair` is a Workers-only global.
|
||||
|
||||
## Binary frames arrive as Blob (critical)
|
||||
|
||||
Cloudflare Workers deliver WebSocket binary frames as **`Blob`** — not `ArrayBuffer` (browsers) and not `Uint8Array` (Node `ws`). Blob decoding is async via `blob.arrayBuffer()`, so:
|
||||
|
||||
1. You must serialize decodes through a promise chain to keep frames in receive order — otherwise parallel awaits can merge bytes out of order.
|
||||
2. Any terminal text message (e.g. Edge TTS's `Path: turn.end`) arrives **synchronously** and will finalize the stream before the in-flight Blob decodes have flushed. Always `await pendingBinary` in the turn.end handler and the close handler before checking whether data was received.
|
||||
|
||||
Example skeleton:
|
||||
|
||||
```ts
|
||||
let pending: Promise<void> = Promise.resolve();
|
||||
const enqueue = (getBuf: () => Promise<ArrayBufferLike> | ArrayBufferLike) => {
|
||||
pending = pending.then(async () => {
|
||||
const buf = await getBuf();
|
||||
appendBinary(buf);
|
||||
});
|
||||
};
|
||||
|
||||
ws.addEventListener('message', (event) => {
|
||||
const data = event.data;
|
||||
if (data instanceof Blob) enqueue(() => data.arrayBuffer());
|
||||
else if (data instanceof ArrayBuffer) enqueue(() => data);
|
||||
else if (data instanceof Uint8Array) enqueue(() => data.buffer.slice(
|
||||
data.byteOffset, data.byteOffset + data.byteLength,
|
||||
));
|
||||
// ... handle text path: turn.end
|
||||
// -> await pending, then resolve
|
||||
});
|
||||
```
|
||||
|
||||
## Where this is used
|
||||
|
||||
`src/libs/edgeTTS.ts` `#fetchEdgeSpeechWs` has three branches: Tauri (plugin-websocket), Cloudflare Workers (fetch upgrade + Blob handling), and browser/Node fallback (`isomorphic-ws`). The route that exercises the CF branch is `src/app/api/tts/edge/route.ts`, hit when the web client falls back from direct `wss://` (which browsers can't set headers on) to the `/api/tts/edge` HTTPS endpoint.
|
||||
@@ -0,0 +1,74 @@
|
||||
# CSS & Style Fixes Reference
|
||||
|
||||
## The `style.ts` Pipeline (`src/utils/style.ts`)
|
||||
|
||||
This is the most bug-prone file in the codebase (14+ fixes). It handles all EPUB CSS transformations.
|
||||
|
||||
### Key Functions
|
||||
|
||||
#### `getLayoutStyles()`
|
||||
- Always-active styles applied to every EPUB section
|
||||
- Controls: line-height, hyphens, image sizing, table display
|
||||
- Rules here should NOT be conditional on user settings
|
||||
- Common mistake: putting color-related rules here instead of `getColorStyles()`
|
||||
|
||||
#### `getColorStyles()`
|
||||
- Conditionally applied when user enables "Override Book Color"
|
||||
- Controls: foreground/background colors, mix-blend modes, image backgrounds
|
||||
- Gate rules on `overrideColor` flag
|
||||
|
||||
#### `transformStylesheet()`
|
||||
- Regex-based rewriting of EPUB CSS at load time
|
||||
- Runs on every stylesheet loaded from the EPUB
|
||||
- Used to neutralize problematic EPUB CSS declarations
|
||||
|
||||
#### `applyTableStyle()`
|
||||
- Post-render function that scales tables to fit available width
|
||||
- Uses `getComputedStyle()` (not inline `style.width`) to read actual width
|
||||
- Has two scaling paths: column-width-based and parent-container-based
|
||||
|
||||
### Fix History by Issue
|
||||
|
||||
| Issue | Problem | Fix in style.ts |
|
||||
|-------|---------|-----------------|
|
||||
| #3494 | Line spacing not on `<li>` | Added `li` CSS rule for `line-height` and `hyphens` |
|
||||
| #3448 | Calibre colors persist | Moved `.calibre` unset to `getColorStyles()`, added `background-color: unset` |
|
||||
| #3441 | Body padding/margin | Added `padding: unset; margin: unset` to body in `getLayoutStyles()` |
|
||||
| #3316 | Image bg unconditional | Made `background-color` rule conditional on `overrideColor` |
|
||||
| #3377 | Image bg override | Same pattern as #3316, only override when `overrideColor` is true |
|
||||
| #3334 | Generic font-family | `transformStylesheet()` replaces `font-family: serif/sans-serif` with `unset` on body |
|
||||
| #3370 | user-select: none | `transformStylesheet()` replaces all `user-select: none` with `unset` |
|
||||
| #3284 | Table scaling | Added fallback when `totalTableWidth` is 0 but parent has width |
|
||||
| #3351 | Table display broken | Added `display: table !important` to table rule |
|
||||
| #3274 | Image dimensions | Changed selectors to `:where(:not([width]))` and `:where(:not([height]))` |
|
||||
| #3205 | Table width reading | Changed from `style.width` to `getComputedStyle().width`, fixed CSS var unit |
|
||||
| #3112 | Mix-blend on all images | Narrowed selector to `.has-text-siblings` class |
|
||||
| #3086 | Mix-blend on hr | Narrowed selector to `hr.background-img` |
|
||||
| #3012 | Vertical alignment | Fixed available dimensions (subtract insets), replaced `100vw/vh` with CSS vars |
|
||||
|
||||
### Common Patterns
|
||||
|
||||
1. **Adding new element rules:** Copy the pattern from `p` rules (e.g., adding `li` for #3494)
|
||||
2. **EPUB CSS neutralization:** Add regex in `transformStylesheet()` to replace problematic declarations
|
||||
3. **Conditional overrides:** Use `overrideColor`/`overrideLayout` flags to gate rules
|
||||
4. **Selector narrowing:** Use class qualifiers or attribute pseudo-selectors to avoid over-matching
|
||||
5. **Table fixes:** Always use `getComputedStyle()`, not inline style. Check both width paths.
|
||||
|
||||
### CSS Variables from foliate-js
|
||||
|
||||
- `--available-width` - Usable content width (set by paginator.js)
|
||||
- `--available-height` - Usable content height
|
||||
- `--full-width` - Full viewport width (numeric, multiply by 1px)
|
||||
- `--full-height` - Full viewport height (numeric, multiply by 1px)
|
||||
- `--overlayer-highlight-opacity` - Highlight transparency (default 0.3)
|
||||
|
||||
### foliate-js Rendering (`packages/foliate-js/paginator.js`)
|
||||
|
||||
Key functions:
|
||||
- `columnize()` - Paginated layout path
|
||||
- `scrolled()` - Scrolled layout path
|
||||
- `setImageSize()` - Constrains image dimensions to available space
|
||||
- `#replaceBackground()` - Transfers EPUB backgrounds to paginator layer
|
||||
- `snap()` - Swipe gesture detection for page turning
|
||||
|
||||
Common issue: A fix applied to `columnize()` but not `scrolled()` (or vice versa). Always check both paths.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: D-pad Navigation Design
|
||||
description: Android TV / Bluetooth remote D-pad navigation architecture, key files, and pitfalls encountered during implementation
|
||||
type: project
|
||||
---
|
||||
|
||||
## D-pad Navigation Architecture
|
||||
|
||||
D-pad support enables Bluetooth remote controller navigation on Android TV (and keyboard arrow navigation on desktop).
|
||||
|
||||
### Key Files
|
||||
|
||||
- `src/app/reader/hooks/useSpatialNavigation.ts` — Reader toolbar D-pad navigation. Left/Right navigates between buttons, Up/Down moves between header↔footer. Auto-focuses first button on show. Uses focus-probe technique for visibility detection.
|
||||
- `src/app/library/hooks/useSpatialNavigation.ts` — Library grid D-pad navigation. Arrow keys move between BookshelfItem elements. ArrowDown from outside bookshelf (e.g. header) enters the grid via window-level listener.
|
||||
- `src/helpers/shortcuts.ts` — `onToggleToolbar` (Enter key) toggles reader toolbar visibility.
|
||||
- `src/app/reader/hooks/useBookShortcuts.ts` — `toggleToolbar` handler shows/hides header+footer bars. Skips when a `<button>` is focused (lets native click fire).
|
||||
- `src/__tests__/hooks/useSpatialNavigation.test.tsx` — Unit tests for reader toolbar navigation.
|
||||
|
||||
### Design Decisions
|
||||
|
||||
- **No third-party library**: Tried `@noriginmedia/norigin-spatial-navigation` but it failed due to init timing issues (React child effects run before parent effects) and conflicts with the existing `useShortcuts` system. Custom solution is simpler and more reliable.
|
||||
- **Two `useSpatialNavigation` hooks**: Same name in different directories — library version handles grid navigation, reader version handles toolbar button navigation. Different navigation patterns but same concept.
|
||||
- **Platform-agnostic hooks**: Both `useSpatialNavigation` hooks work on all platforms, not just Android.
|
||||
- **Focus-probe for visibility**: `offsetParent` is unreliable for detecting visible buttons (returns null inside `position: fixed` containers on mobile). Instead, try `btn.focus()` and check if `document.activeElement === btn` — this correctly handles all hiding methods (display:none, visibility:hidden, fixed positioning).
|
||||
|
||||
### Pitfalls
|
||||
|
||||
1. **WebView spatial navigation conflict**: Android WebView has built-in spatial navigation that intercepts D-pad arrow keys and moves DOM focus between `tabIndex>=0` elements. Added `tabIndex={-1}` to non-interactive overlay elements (HeaderBar trigger, ProgressBar, FooterBar trigger, SectionInfo) to prevent focus theft.
|
||||
|
||||
2. **`eventDispatcher.dispatchSync` short-circuits**: When multiple handlers are registered for `native-key-down`, the first handler returning `true` stops propagation. The FooterBar's Back handler fires before the Reader's. Both must independently call `blur()` — can't rely on the Reader's handler running.
|
||||
|
||||
3. **Must blur on toolbar dismiss**: When Back/Escape dismisses the toolbar, the focused button must be blurred. Otherwise `document.activeElement` remains a hidden button, and `toggleToolbar` skips Enter when `activeElement.tagName === 'BUTTON'`. Blur is called in FooterBar's handleKeyDown (for Back and Escape) and in Reader's handleKeyDown.
|
||||
|
||||
4. **Arrow key trapping must use `stopPropagation`**: Without it, arrow keys bubble to `window` where `useShortcuts` handles them as page turns. The toolbar keydown handler on the container div calls `e.stopPropagation()` + `e.preventDefault()` to prevent this.
|
||||
|
||||
5. **Library grid needs window-level listener**: The bookshelf container keydown handler only fires when focus is inside it. A separate `window` keydown listener handles ArrowDown from the header into the grid (when focus is outside the container).
|
||||
|
||||
6. **Auto-focus race on toolbar show**: Both header and footer bars auto-focus their first button when `isVisible` becomes true simultaneously. The last effect to run wins. This is acceptable — user can navigate between them with Up/Down.
|
||||
|
||||
7. **`offsetParent` null in fixed containers**: On mobile, `.footer-bar` uses `position: fixed`. All child buttons have `offsetParent === null`, making `offsetParent`-based visibility checks useless. The focus-probe approach (try focus, check activeElement) is the reliable alternative.
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: Design rules live in DESIGN.md
|
||||
description: Readest has a design system doc — codify recurring UI/UX rules there, don't just apply them ad-hoc. Memory points at the canonical location and the patterns it covers.
|
||||
type: feedback
|
||||
originSessionId: 85757e57-a029-40f8-b098-88039c43514b
|
||||
---
|
||||
The project's design system is documented at `apps/readest-app/DESIGN.md`.
|
||||
**When the user articulates a UI/UX rule** ("X should always Y", "we follow Z
|
||||
convention"), add it to DESIGN.md so it persists for the team and for future
|
||||
sessions — don't just apply it inline and move on.
|
||||
|
||||
**Why:** Readest's UI is Adwaita-aligned, e-ink-first, cross-platform-aware.
|
||||
A doc'd system avoids drift across panels and gives reviewers a reference
|
||||
point. The user explicitly asked to "remember the UI/UX rules somewhere"
|
||||
when refining the OPDS Integration sub-page.
|
||||
|
||||
**How to apply:** When the user surfaces a new rule:
|
||||
1. Add it to the appropriate `DESIGN.md` section (numbered principles in §2,
|
||||
anatomy details in §5, anti-patterns in §10).
|
||||
2. Cross-reference from related sections so it's discoverable from multiple
|
||||
entry points.
|
||||
3. Save the actual code change reference if it captures a canonical example.
|
||||
|
||||
**Rules already codified there (don't re-invent — reference instead):**
|
||||
- §2.1–2.7: surface continuity, color discipline, two-step depth, localized
|
||||
hover, motion=color, eink-first, focus visibility.
|
||||
- §2.8: RTL — always use logical properties (`ps`/`pe`/`ms`/`me`/`text-start`
|
||||
/`text-end`/`border-s`/`border-e`/`start-*`/`end-*`). Never `pl`/`pr`/`ml`
|
||||
/`mr`/`text-left`/`text-right`/`left-*`/`right-*`. The user is strict on
|
||||
this — Readest ships RTL languages.
|
||||
- §2.9: every panel/sub-page must open with title + one-line description.
|
||||
- §3: surface tier hierarchy (window/view/card → base-200/100-tinted/100).
|
||||
- §4: action vocabulary (Accent CTA, Suggested, Flat, Pill, Destructive,
|
||||
ListExtension).
|
||||
- §5: boxed list anatomy + uniform row height (`min-h-14 items-center`,
|
||||
never `py-3`) + chromeless controls inside the box + end-aligned values
|
||||
with custom `<MdArrowDropDown>` icon (don't trust daisyui's bg-image
|
||||
chevron for trailing-edge alignment).
|
||||
- §8: e-ink overlay rules.
|
||||
- §10: anti-pattern catalog with real before/after examples.
|
||||
|
||||
**Common file paths to remember:**
|
||||
- `apps/readest-app/DESIGN.md` — source of truth.
|
||||
- `apps/readest-app/src/components/settings/SubPageHeader.tsx` — title +
|
||||
description sub-page primitive that embodies §2.9.
|
||||
- `apps/readest-app/src/components/settings/integrations/` — the canonical
|
||||
reference implementation of the boxed-list-with-rows pattern.
|
||||
- `apps/readest-app/src/styles/globals.css` — eink overlay rules at
|
||||
`[data-eink='true']`.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Don't push on every change
|
||||
description: Commit when work is done; don't auto-push every iteration during active debugging
|
||||
type: feedback
|
||||
originSessionId: 49a72b36-8f45-4a57-87e1-e10563bac47a
|
||||
---
|
||||
Don't `git push` after each commit while a bug is being actively iterated on. Commit locally as needed but hold the push.
|
||||
|
||||
**Why:** When a fix doesn't actually solve the user-reported bug, every push is wasted CI cycles + remote churn the user has to look past on the PR. The user is testing live and will tell us when something's actually verified.
|
||||
|
||||
**How to apply:** During debugging or fix iterations on a single user-reported bug, commit locally only. Push when (a) the user confirms the fix works, (b) the user explicitly asks to push, or (c) we hit a clean done-state on a multi-step task. New commits + lint/test green is not enough.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: en/translation.json holds ONLY plural variants and proper-noun overrides
|
||||
description: For non-plural strings, do NOT add to en/translation.json — the source-code key IS the en value via `defaultValue: key`. ONLY plural strings need explicit `_one`/`_other` entries in en, because i18next needs the forms to pick from.
|
||||
type: feedback
|
||||
originSessionId: e4ddc690-b1a9-4557-855f-d4e67055824f
|
||||
---
|
||||
|
||||
**Rule:** `public/locales/en/translation.json` is hand-curated and contains essentially two kinds of entries:
|
||||
|
||||
1. **Plural variants** (`<base>_one`, `<base>_other`, and locale-specific `_few`/`_many`/etc. as needed by CLDR). i18next MUST find these to know which form to render — without them, `count: 1` falls back to the bare key like `{{count}} days` and renders "1 days" instead of "1 day".
|
||||
2. **Proper-noun overrides** (e.g., font names like `LXGW WenKai GB Screen`) where the en value differs from the key.
|
||||
|
||||
Everything else — ordinary translatable strings like `Sign in to share books` — does NOT belong in en/translation.json. The translation hook calls `t(key, { defaultValue: key, ...options })`, so for any string not in en/translation.json, i18next renders the key itself. That's why a 51-key en file works for a codebase with thousands of `_()` callsites.
|
||||
|
||||
**Why en is hand-curated:** the project's `i18next-scanner.config.cjs` lists every locale EXCEPT `en` in its `lngs` array. The scanner generates `__STRING_NOT_TRANSLATED__` placeholders only for the listed locales; `en` is never touched.
|
||||
|
||||
**Workflow when adding `_(...)` calls:**
|
||||
|
||||
- **Non-plural string** (e.g. `_('Sign in to share books')`): add the `_(...)` call, run `pnpm run i18n:extract`, translate the new placeholders in non-en locales. **Do NOT touch `en/translation.json`** — the key itself is the en value.
|
||||
- **Plural string** (e.g. `_('{{count}} days', { count: n })`): same as above, PLUS hand-add `<base>_one` and `<base>_other` to `en/translation.json` (and `_few`/`_many`/etc. only if the source language ever needs them, which English doesn't). Convention from existing entries (e.g., `Are you sure to delete {{count}} selected book(s)?_one` → `Are you sure to delete {{count}} selected book?`): keep `{{count}}` interpolated even in `_one`, and swap any `(s)` placeholder to the proper singular/plural noun.
|
||||
|
||||
**Audit script:** walk `src/`, regex-match `_('...', { ..., count: ... })`, for each base key verify both `<base>_one` and `<base>_other` exist in `en/translation.json`. (See conversation history for an implementation.)
|
||||
|
||||
**Where this bit us:**
|
||||
|
||||
- Initial bug: `_('{{count}} days', { count: n })` rendered "1 days" because en had no `_one` form.
|
||||
- Audit found 16 missing en plural keys from earlier PRs (OPDS / TTS / dictionary import) silently rendering wrong.
|
||||
- Then overcorrected and added non-plural keys like `Sign in to share books` to en/translation.json — wrong, breaks the project's convention. en stays clean for non-plural strings.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: gstack upgrade location
|
||||
description: Always upgrade gstack from the project directory (.claude/skills/gstack), not from a global install
|
||||
type: feedback
|
||||
---
|
||||
|
||||
When upgrading gstack, always run the upgrade from the current project's `.claude/skills/gstack` directory (local-git install), not from a global install path.
|
||||
|
||||
**Why:** The project uses a local-git gstack install at `apps/readest-app/.claude/skills/gstack`. Previous mistakes upgraded a global copy while the project's local copy stayed outdated.
|
||||
|
||||
**How to apply:** When `/gstack-upgrade` is invoked, ensure the `cd` and `git reset --hard origin/main && ./setup` happen inside the project's `.claude/skills/gstack` directory.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: No lookbehind regex
|
||||
description: Never use lookbehind assertions in JS/TS code — the build check rejects them for browser compatibility
|
||||
type: feedback
|
||||
---
|
||||
|
||||
Never use lookbehind regex (`(?<=...)` or `(?<!...)`) in JavaScript/TypeScript source code. Use `(?:^|[^...])` or other alternatives instead.
|
||||
|
||||
**Why:** The project has a `check:lookbehind-regex` build check (`pnpm check:all`) that scans the Next.js output chunks and fails if any lookbehind assertions are found. Older WebViews (especially on some Android devices) don't support lookbehinds.
|
||||
|
||||
**How to apply:** When writing regex that needs to assert what comes before a match, use a non-capturing group with alternation (e.g., `(?:^|[^a-z-])`) instead of a negative lookbehind (`(?<![a-z-])`). This applies to all `.ts`/`.tsx`/`.js` files that end up in the build output.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: No test seams in production code
|
||||
description: Never import or call `__reset*ForTests` (or any test-only helper) from production modules — keep test orchestration on the test side
|
||||
type: feedback
|
||||
originSessionId: 49a72b36-8f45-4a57-87e1-e10563bac47a
|
||||
---
|
||||
Production code must never import or call functions named `__reset*ForTests`
|
||||
(or any other test-only seam). If a `__resetXForTests` function in module A
|
||||
needs to also clear state owned by module B, the test file is responsible
|
||||
for calling both resets — not module A's reset chaining into module B's.
|
||||
|
||||
**Why:** Importing a test-only helper into a production module pulls the
|
||||
test seam into the prod import graph, blurs the test/prod boundary, and
|
||||
risks the helper being shipped or mistakenly called at runtime. Caught
|
||||
once in `src/services/sync/replicaSync.ts` where
|
||||
`__resetReplicaSyncForTests` had been changed to call
|
||||
`__resetSettledEventsForTests` from `@/utils/event` for "convenience."
|
||||
|
||||
**How to apply:**
|
||||
- A `__resetXForTests` function should clear ONLY its own module's state.
|
||||
- If a test needs a coordinated reset across modules, do it in the test
|
||||
file's `beforeEach` / `afterEach` — call each module's seam directly.
|
||||
- Never `import { __reset...ForTests }` inside `src/` outside of
|
||||
`src/__tests__/`. A grep `grep -rn "__reset.*ForTests" src/ --include="*.ts" --include="*.tsx" | grep -v __tests__ | grep -v "^.*export const __reset"` should return zero hits.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Always use a new branch for new PRs
|
||||
description: Each new PR/issue should get its own fresh branch from main, never reuse an existing feature branch
|
||||
type: feedback
|
||||
---
|
||||
|
||||
Always create a new branch from main for each new PR or issue. Never reuse an existing feature branch for unrelated work.
|
||||
|
||||
**Why:** The user corrected this when a storage fix was committed on the `feat/full-sync-annotations` branch instead of a dedicated branch. Mixing unrelated changes on the same branch makes PRs harder to review and manage.
|
||||
|
||||
**How to apply:** Before committing fixes, create a new branch like `fix/<topic>` from `origin/main`. Only reuse a branch if the work is directly related to that branch's existing purpose.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Always rebase before PR
|
||||
description: Rebase to origin/main before creating pull requests
|
||||
type: feedback
|
||||
---
|
||||
|
||||
Always rebase the branch onto origin/main before creating a pull request.
|
||||
|
||||
**Why:** The user wants PRs to be up-to-date with main to avoid merge conflicts and keep a clean history.
|
||||
|
||||
**How to apply:** Before running `gh pr create`, always run `git fetch origin && git rebase origin/main` first. If there are conflicts, resolve them before proceeding.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: test-file-filter
|
||||
description: Use pnpm test/test:browser with path directly (no --) to run a single test file
|
||||
type: feedback
|
||||
---
|
||||
|
||||
Run a specific test file with `pnpm test <path>` or `pnpm test:browser <path>` — no `--` separator.
|
||||
|
||||
**Why:** Adding `--` before the path (e.g. `pnpm test:browser -- <path>`) causes vitest to ignore the file filter and run all test files. Without `--`, pnpm appends the path directly to the vitest command, which correctly filters to that file only.
|
||||
|
||||
**How to apply:** Always use `pnpm test src/__tests__/foo.test.ts` or `pnpm test:browser src/__tests__/foo.browser.test.tsx` when verifying a specific test file.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: Use worktree for PR/issue/feature work
|
||||
description: Always create a git worktree with pnpm worktree:new before reviewing PRs, fixing issues, or implementing features
|
||||
type: feedback
|
||||
originSessionId: 650f8ff2-980d-459f-ad23-ba0af56e28b5
|
||||
---
|
||||
Always use `pnpm worktree:new <branch-name|pr-number>` to create an isolated worktree before starting work on:
|
||||
- Reviewing a GitHub PR (e.g., `pnpm worktree:new 3809`) → worktree at `~/dev/readest-pr-3809`
|
||||
- Fixing a GitHub issue (e.g., `pnpm worktree:new fix/issue-123`) → worktree at `~/dev/readest-fix-issue-123`
|
||||
- Implementing a feature request (e.g., `pnpm worktree:new feat/my-feature`) → worktree at `~/dev/readest-feat-my-feature`
|
||||
|
||||
Worktree directory convention: `readest-<name>` in the parent of the repo root (`~/dev/`), with slashes replaced by dashes.
|
||||
|
||||
Use `pnpm worktree:rm <branch-name|pr-number>` to clean up when done.
|
||||
|
||||
**Why:** Keeps the current bare repo branch untouched. Each task gets its own isolated workspace with submodules, dependencies, env files, and vendor assets already set up.
|
||||
|
||||
**How to apply:** Before touching any code for a PR review, bug fix, or feature, run `pnpm worktree:new` first. Work inside the new worktree directory (e.g., `~/dev/readest-pr-3809/apps/readest-app/`). Clean up with `pnpm worktree:rm` after merging or finishing.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: readest.koplugin i18n system
|
||||
description: Custom gettext loader, .po catalog layout, and extract/apply scripts for the KOReader plugin at apps/readest.koplugin/
|
||||
type: reference
|
||||
originSessionId: 08cfd0cd-b710-4674-9c90-d2ae4827d071
|
||||
---
|
||||
The KOReader plugin (`apps/readest.koplugin/`) has its own gettext-based i18n system, parallel to but separate from the readest-app i18next setup.
|
||||
|
||||
## Loader
|
||||
|
||||
- File: `apps/readest.koplugin/i18n.lua` — isolated module, returns a callable table via `setmetatable({}, {__call = ...})`, so `_("msg")` syntax works as a drop-in replacement for `require("gettext")`. Provides `ngettext`/`pgettext`/`npgettext` too. Falls back to KOReader's native `gettext` for missing strings.
|
||||
- All Lua sources do `local _ = require("i18n")` (not `require("gettext")`).
|
||||
- **Never rename `i18n.lua` to `gettext.lua`** — it would shadow KOReader's module via `require("gettext")` and break the fallback chain (recursive require / never loaded).
|
||||
|
||||
## Catalog layout
|
||||
|
||||
- `apps/readest.koplugin/locales/<lang>/translation.po` — mirrors `apps/readest-app/public/locales/<lang>/translation.json` exactly, using **i18next-style codes** (`zh-CN`, not `zh_CN`).
|
||||
- The loader converts KOReader's locale (e.g. `zh_CN.utf8` → `zh-CN`) before lookup.
|
||||
- Fallback chain: full code → base lang (`pt_BR` → `pt`) → `zh-CN` for any unspecified zh variant.
|
||||
- Language list is the single source of truth at `apps/readest-app/i18next-scanner.config.cjs` (`options.lngs`) — currently 31 languages.
|
||||
|
||||
## Scripts (in `apps/readest.koplugin/scripts/`)
|
||||
|
||||
- **`extract-i18n.js`** — primary tool. Run with `node scripts/extract-i18n.js`. Scans `*.lua` for `_("...")`, `_('...')`, and `_([[...]])` (with proper Lua-escape handling), reads each `.po`, **preserves existing translations**, adds new msgids with empty `msgstr`, drops obsolete msgids. Idempotent.
|
||||
- **`apply-translations.js`** — bulk applier. Reads `/tmp/koplugin-translations/<lang>.json` files (key = msgid, value = translation) and fills empty `msgstr ""` lines only — **never overwrites** existing translations.
|
||||
|
||||
## Workflow for adding/changing strings
|
||||
|
||||
1. Edit Lua source(s). Use `_("Foo")` or `T(_("Foo %1"), arg)` (`T` from `require("ffi/util").template`) — **never** `_("Foo ") .. arg`, because RTL/verb-final languages can't reorder the placeholder.
|
||||
2. `node scripts/extract-i18n.js` — adds new empty msgids, drops obsolete.
|
||||
3. To translate: drop `<lang>.json` files into `/tmp/koplugin-translations/`, then `node scripts/apply-translations.js`.
|
||||
4. Verify with `luac -p apps/readest.koplugin/*.lua` and re-run `extract-i18n.js` (should report no changes — idempotency check).
|
||||
|
||||
## Translation conventions
|
||||
|
||||
- Brand names "Readest" and "KOReader" stay untranslated.
|
||||
- Technical terms ("PDF", "API", "URL", "Supabase", "Hash") generally kept as-is, sometimes transliterated in non-Latin scripts.
|
||||
- Dialog title = title case (`Sync Info`); menu item label = sentence case (`Sync info`).
|
||||
- Lower-confidence translations (bo, si, ta, bn, sl, fa) deserve native-speaker review.
|
||||
|
||||
## Storage conventions for plugin state
|
||||
|
||||
- Global plugin state: `G_reader_settings:saveSetting("readest_sync", settings)` — login tokens, auto-sync flag, etc.
|
||||
- Per-book state: `ui.doc_settings:readSetting("readest_sync")` table — keys like `meta_hash_v1`, `last_synced_at_config`, `last_synced_at_notes` (seconds since epoch).
|
||||
@@ -0,0 +1,70 @@
|
||||
# Layout & UI Fixes Reference
|
||||
|
||||
## Safe Area Insets
|
||||
|
||||
### Architecture
|
||||
- Native plugins push inset values: iOS (`NativeBridgePlugin.swift`), Android (`NativeBridgePlugin.kt`)
|
||||
- `useSafeAreaInsets` hook (`src/hooks/useSafeAreaInsets.ts`) reads and caches values
|
||||
- Components use `gridInsets` for positioning relative to safe areas
|
||||
- CSS: `env(safe-area-inset-top/bottom/left/right)` for CSS-level insets
|
||||
|
||||
### Common Issues
|
||||
- **Stale insets after system UI change** (#3469): Call `onUpdateInsets()` after `setSystemUIVisibility()`
|
||||
- **iPad sidebar insets wrong** (#3395): Different inset handling needed for sidebar vs main view
|
||||
- **Android nav bar overlap** (#3466): Use `calc(env(safe-area-inset-bottom) + 16px)` for Android bottom padding
|
||||
- **Zoom controls behind status bar** (#3426): Pass `gridInsets` through component chain, use `Math.max(gridInsets.top, statusBarHeight)`
|
||||
|
||||
### Rules (see also `docs/safe-area-insets.md`)
|
||||
- Always pass `gridInsets` to overlay/floating components near screen edges
|
||||
- On Android, account for system navigation bar with `env(safe-area-inset-bottom)`
|
||||
- After toggling system UI visibility, force-refresh insets via `onUpdateInsets()`
|
||||
|
||||
## Z-Index Hierarchy
|
||||
- Navigation buttons: `z-10` (when visible)
|
||||
- Annotation nav bar: `z-10` (reduced from z-30 in #3386)
|
||||
- TTS control button: ensure above page nav buttons
|
||||
- Floating overlays: check against `gridInsets` positioning
|
||||
|
||||
## macOS Traffic Lights
|
||||
- Managed by `trafficLightStore.ts` and `HeaderBar.tsx`
|
||||
- **Fullscreen check required** (#3129): `await currentWindow.isFullscreen()` before hiding
|
||||
- **Timeout for visibility toggle** (#3488): Use 100ms delay to prevent flickering
|
||||
- **Sidebar interaction** (#3488): Check `getIsSideBarVisible()` before hiding traffic lights
|
||||
- Never hide traffic lights when sidebar is open
|
||||
|
||||
## Touch/Input Issues
|
||||
- **Slider hit area on iOS** (#3382): Use min-h-12, strip browser appearance with CSS
|
||||
- **Context menu on macOS** (#3324): 100ms delay before `onContextMenu()` to let pointer events complete
|
||||
- **Swipe sensitivity** (#3310): Use average velocity (distance/time) instead of instantaneous velocity for non-animated paging
|
||||
- **Touchpad natural scrolling** (#3127): Respect system natural scrolling setting in `usePagination.ts`
|
||||
|
||||
## Dialog/Menu Layout
|
||||
- **Dialog header** (#3352): Use `px-2 sm:pe-3 sm:ps-2` padding to align with border radius
|
||||
- **Settings alignment** (#3151): Use `Menu` component instead of raw `div` for consistent styling
|
||||
- **Dropdown for screen readers** (#3035): Use `DropdownContext` with overlay dismiss, not blur-based closing
|
||||
|
||||
## Component-Specific Fixes
|
||||
|
||||
### HeaderBar.tsx
|
||||
- Traffic light visibility management
|
||||
- Sidebar toggle persistent position (#3193)
|
||||
- Library button placement
|
||||
|
||||
### PageNavigationButtons.tsx
|
||||
- z-10 when visible (#3201)
|
||||
- Always shown for screen readers (#3036)
|
||||
- Toggle via `showPageNavButtons` setting
|
||||
|
||||
### ProgressInfo.tsx
|
||||
- Use physical `page`/`pages` from renderer, not estimated values (#3213, #3200)
|
||||
- CSS classes: `time-left-label`, `pages-left-label`, `progress-info-label` (#3343)
|
||||
|
||||
### ReadingRuler.tsx
|
||||
- Remove `containerStyle` from overlay so dimmed area covers full screen (#3304)
|
||||
|
||||
### NavigationBar.tsx
|
||||
- Handle `gridInsets` internally, not via pre-computed `navPadding` (#3466)
|
||||
|
||||
### ContentNavBar.tsx (annotation search results)
|
||||
- Floating buttons with drop shadow, not full-width bar (#3386)
|
||||
- z-10 z-index
|
||||
@@ -0,0 +1,85 @@
|
||||
# Platform Compatibility Fixes Reference
|
||||
|
||||
## Android
|
||||
|
||||
### WebView API Issues
|
||||
- **`navigator.getGamepads()`** returns null on older WebView (#3245): Always null-check before `.some()`
|
||||
- **`CompressionStream`** unavailable on some Android WebView versions (#3255): Add fallback zip compression path
|
||||
- **Annotation tools unresponsive** (#3225): Don't call `makeSelection()` immediately on pointer-up; let popup flow complete naturally
|
||||
- **Safe inset updates** (#3469): Call `onUpdateInsets()` after `setSystemUIVisibility()`
|
||||
- **Navigation bar overlap** (#3466): Use `calc(env(safe-area-inset-bottom) + 16px)` for bottom padding
|
||||
|
||||
### General Android Rules
|
||||
- Test with older WebView versions (97+)
|
||||
- Always check API availability before calling Web APIs
|
||||
- Touch event handling differs from iOS - avoid premature re-selection
|
||||
|
||||
## iOS
|
||||
|
||||
### Common Issues
|
||||
- **Slider touch dead zones** (#3382): Strip native appearance, use larger hit areas (min-h-12)
|
||||
- **Safe area insets stale** (#3395): Native Swift plugin must push updated insets
|
||||
- **Section content caching** (#3242, #3206): Don't cache section content in foliate-js when updating subitems; cached content retains stale styles after mode switch
|
||||
- **CompressionStream** (#3255): Also broken on iOS 15.x; zip.js has its own native API disable
|
||||
- **zip.js native API** (#3170): Disable native `CompressionStream`/`DecompressionStream` on iOS 15.x
|
||||
|
||||
### iOS-Specific Code
|
||||
- `src-tauri/plugins/tauri-plugin-native-bridge/ios/Sources/NativeBridgePlugin.swift`
|
||||
- Slider CSS: `-webkit-appearance: none; appearance: none` in globals.css
|
||||
- `useSafeAreaInsets.ts` hook
|
||||
|
||||
## macOS
|
||||
|
||||
### Traffic Lights (Window Controls)
|
||||
- Check `isFullscreen()` before hiding (#3129)
|
||||
- Use timeouts (100ms) for visibility transitions (#3488)
|
||||
- Don't hide when sidebar is open (#3488)
|
||||
|
||||
### Input Issues
|
||||
- **Context menu steals event loop** (#3324): 100ms setTimeout before calling menu.popup()
|
||||
- **Touchpad natural scrolling** (#3127): Respect system setting in `usePagination.ts`
|
||||
- **Two-finger swipe** (#3127): Support trackpad two-finger swipe for pagination
|
||||
|
||||
### macOS-Specific Code
|
||||
- `src-tauri/src/macos/` - Platform-specific Rust code
|
||||
- `src/store/trafficLightStore.ts`
|
||||
- `src/hooks/useTrafficLight.ts`
|
||||
|
||||
## Linux
|
||||
|
||||
### WebKitGTK Issues
|
||||
- **View Transitions API unsupported** (#3417): Feature-detect `document.startViewTransition` before calling
|
||||
- Use `useAppRouter.ts` to avoid transitions on Linux
|
||||
|
||||
## E-ink Devices
|
||||
|
||||
### Legibility Issues
|
||||
- **Low contrast colors** (#3258): Use `not-eink:` Tailwind variant prefix for colors/opacity
|
||||
- **Links invisible** (#3258): Don't apply `text-primary` (blue) on e-ink; use default text color
|
||||
- **Opacity too low** (#3258): Don't apply `opacity-60`/`opacity-75` on e-ink devices
|
||||
- **Highlight visibility** (#3299): Use foreground color for highlights in dark mode e-ink
|
||||
|
||||
### E-ink CSS Pattern
|
||||
```
|
||||
// Instead of:
|
||||
className="text-primary opacity-60"
|
||||
// Use:
|
||||
className="not-eink:text-primary not-eink:opacity-60"
|
||||
```
|
||||
|
||||
## Docker/Self-Hosting
|
||||
- **Missing submodules** (#3233): Run `git submodule update --init --recursive` before build
|
||||
- Simplecc WASM module must be initialized
|
||||
|
||||
## OPDS
|
||||
- **Non-ASCII credentials** (#3436): Use `TextEncoder` + manual Base64 instead of `btoa()`
|
||||
- **Author parsing** (#3120): Handle varied metadata structures in OPDS 2.0 feeds
|
||||
- **Responsive layout** (#3418): Ensure catalog and download button layout works on small screens
|
||||
|
||||
## Cross-Platform Testing Checklist
|
||||
1. Android (old WebView + current)
|
||||
2. iOS (15.x + current)
|
||||
3. macOS (traffic lights, trackpad)
|
||||
4. Linux (WebKitGTK)
|
||||
5. E-ink devices (contrast, colors)
|
||||
6. Web (CloudFlare Workers deployment)
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: Share-a-Book Feature (in progress)
|
||||
description: Active implementation of /s/{token} share links + cherry-picks (CFI auto-include, 1-tap library import, branded OG images). All locked decisions and the plan file path.
|
||||
type: project
|
||||
originSessionId: e4ddc690-b1a9-4557-855f-d4e67055824f
|
||||
---
|
||||
Active feature on branch `dev` as of 2026-05-02. Plan file: `/Users/chrox/.claude/plans/ok-we-will-learn-cosmic-acorn.md` (always read for source of truth before continuing).
|
||||
|
||||
**Why:** complement just-shipped annotation deep-links (PRs #4018, #4019) with whole-book sharing. Cloud-sync infra exists; this layers public time-limited links on top.
|
||||
|
||||
**How to apply:** these decisions are locked across CEO + eng + design reviews. Do NOT re-litigate when implementing.
|
||||
|
||||
## Locked decisions (authoritative)
|
||||
|
||||
- **Universal 7-day cap** on share expiry, no tier differentiation, no "Never". All users pick `[1, 3, 7]` days. DMCA-risk reduction.
|
||||
- **App Router** for all share routes (mirrors `/o` precedent + recent Stripe / AI / IAP / OPDS). Pages API `storage/*` neighbors stay where they are.
|
||||
- **Single non-dynamic landing page** at `src/app/s/page.tsx` + rewrite `'/s/:token' → '/s?token=:token'` in `next.config.mjs`. Mirrors `/o`'s pattern exactly. Avoids `[token]` dynamic-segment trap under `output: 'export'`.
|
||||
- **R2 server-side byte-copy** for `/import` (recipient-side library import). NOT a reference. Preserves invariant that every `files` row's `file_key` starts with that row's `user_id` — keeps stats / purge / delete / download routes working unchanged.
|
||||
- **`token_hash` (sha256) in DB**, never the raw token. Raw token shown to user once at create.
|
||||
- **Live `(user_id, book_hash)` resolution** at every access (no FK to `files`). Re-uploads of the same hash follow the share automatically.
|
||||
- **GET `/download` is a 302 redirect with NO DB writes**. Count via separate `POST /download/confirm` so unfurlers / prefetchers can't inflate counts.
|
||||
- **Atomic SQL `download_count = download_count + 1`**, not read-modify-write.
|
||||
- **Per-user 50-share cap**, enforced at create-time.
|
||||
- **Auth detection on /s landing**: server-render via Supabase auth cookie in `app/s/layout.tsx`. No layout shift. Falls back to anonymous flow + post-hydration upgrade if SSR fails.
|
||||
- **Cover-less `og.png` fallback**: text-only display-type card. NO placeholder rectangle, NO procedural pattern.
|
||||
- **Universal Links / App Links**: handle `https://web.readest.com/s/...` in v1 via `useOpenShareLink`. Tauri's `applinks` config already covers the host.
|
||||
|
||||
## Cherry-picks accepted (CEO review SELECTIVE EXPANSION)
|
||||
|
||||
1. **Position-aware share** — every share initiated from the reader auto-includes `cfi`; recipient lands at sharer's paragraph. Toggle in dialog (default on, only visible when reader-context).
|
||||
3. **1-tap "Add to my library"** — logged-in recipient on `/s` gets primary action that calls `/import` (R2 byte-copy) and navigates to `/reader?ids=...&cfi=...`.
|
||||
4. **Branded OG image** — `/api/share/[token]/og.png` server-renders via `@vercel/og`. Spec includes anti-slop checklist.
|
||||
|
||||
Deferred to TODOS.md: QR code on landing, notify-on-download toggle.
|
||||
|
||||
## Open implementation questions (resolve mid-build)
|
||||
|
||||
1. Upload-confirmation: HEAD R2 in `/create`, or add `uploaded_at` column to `files`? — recommend HEAD.
|
||||
2. Migration directory: project has no `apps/readest-app/supabase/migrations/`. Confirm where SQL actually lands before writing the file.
|
||||
3. `@vercel/og` runtime compat with CloudFlare Workers (OpenNextJS). Verify before relying on it; fallback to Satori + sharp if incompatible.
|
||||
4. App Router route handlers under `src/app/api/share/...` should be silently dropped by `output: 'export'` in Next 16.2.3. Confirm during first Tauri build.
|
||||
|
||||
## Critical files for implementation
|
||||
|
||||
See "Critical Files (modify or create)" table in the plan. Key starting points:
|
||||
- `src/libs/storage-server.ts` — extract `getDownloadSignedUrl` from `pages/api/storage/download.ts`
|
||||
- `src/libs/share-server.ts` (new) — `generateShareToken()`, `hashShareToken(raw)`
|
||||
- `src/libs/share.ts` (new) — typed client used by dialog, manage section, deeplink hook, landing page
|
||||
- `src/utils/share.ts` (new) — `buildShareUrl(token)`, `parseShareDeepLink(url)`
|
||||
- Dialog reference: `src/components/Dialog.tsx`, `BookDetailModal.tsx`
|
||||
- Landing reference: `src/app/o/page.tsx` (lift `Card`, `BrandHeader`, `PageFooter` to `src/components/landing/`)
|
||||
- Deeplink hook reference: `src/hooks/useOpenAnnotationLink.ts`, `useOpenWithBooks.ts`
|
||||
- App-level upload entry: `appService.uploadBook(book)` at `src/services/appService.ts:269` (NOT `cloudService.uploadBook` — lower-level fn)
|
||||
@@ -0,0 +1,50 @@
|
||||
# TTS (Text-to-Speech) Fixes Reference
|
||||
|
||||
## Architecture
|
||||
|
||||
### Key Components
|
||||
- `TTSController` (`src/services/tts/TTSController.ts`) - Core state machine
|
||||
- `EdgeTTSClient` (`src/services/tts/EdgeTTSClient.ts`) - Edge TTS provider
|
||||
- `useTTSControl` hook (`src/app/reader/hooks/useTTSControl.ts`) - React integration
|
||||
- `useTTSMediaSession` hook (`src/app/reader/hooks/useTTSMediaSession.ts`) - Media controls
|
||||
|
||||
### Section-Aware TTS Model
|
||||
TTS tracks its own section independently from the view via `#ttsSectionIndex`:
|
||||
- `#initTTSForSection()` - Creates TTS document for a section without changing the view
|
||||
- `#initTTSForNextSection()` / `#initTTSForPrevSection()` - Navigate TTS across sections
|
||||
- `#getHighlighter()` - Only returns highlighter if view section matches TTS section
|
||||
- `onSectionChange` callback - Notifies UI when TTS crosses section boundary
|
||||
- Highlights use CFI strings (not raw Range objects) for cross-section compatibility
|
||||
|
||||
### State Management Pitfalls
|
||||
1. **`#ttsSectionIndex` must match view section for highlights to work**
|
||||
- If `-1`, all highlight calls are suppressed
|
||||
- `shutdown()` sets it to `-1` but must also null out `this.view.tts`
|
||||
|
||||
2. **Guards/Refs that block re-entry:**
|
||||
- The old `ttsOnRef` guard blocked TTS restart from annotations (removed in #3292)
|
||||
- `view.tts` reference surviving shutdown blocked re-initialization (#3400)
|
||||
|
||||
3. **Timeouts that fire after pause:**
|
||||
- Edge TTS had a safety timeout that advanced sentences even when paused (#3244)
|
||||
- Solution: removed the entire `ontimeupdate` safety timeout mechanism
|
||||
|
||||
## Fix History
|
||||
|
||||
| Issue | Problem | Root Cause | Fix |
|
||||
|-------|---------|------------|-----|
|
||||
| #3100 | TTS scrolls too far | TTS coupled to view section | Added `#ttsSectionIndex`, "Back to TTS Location" button |
|
||||
| #3198 | TTS doesn't follow to next section | No `onSectionChange` callback | Added section change notification, extracted hooks |
|
||||
| #3244 | Paused TTS advances | Safety timeout fires after pause | Removed `ontimeupdate` timeout mechanism |
|
||||
| #3291 | TTS fails without lang attribute | Invalid SSML from missing lang | Set lang/xml:lang on html element from `ttsLang` |
|
||||
| #3292 | Can't restart TTS from annotation | `ttsOnRef` blocks re-entry | Removed the guard ref entirely |
|
||||
| #3400 | TTS highlight stops after restart | `view.tts` not nulled on shutdown | Added `this.view.tts = null` in `shutdown()` |
|
||||
|
||||
## Debugging TTS Issues
|
||||
|
||||
1. **TTS doesn't start:** Check `#initTTSForSection()` - does `view.tts.doc === doc` shortcut early?
|
||||
2. **No highlights:** Check `#ttsSectionIndex` matches view's section index
|
||||
3. **Advances when paused:** Look for setTimeout/timer callbacks that bypass pause state
|
||||
4. **Can't restart:** Check for refs/guards that prevent re-entry into speak handlers
|
||||
5. **Fails on some chapters:** Check if chapter has lang attribute and XHTML namespace
|
||||
6. **SSML errors:** Check `src/utils/ssml.ts` for proper namespace/lang handling
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
name: Virtuoso + OverlayScrollbars pattern
|
||||
description: How to integrate OverlayScrollbars with react-virtuoso for overlay scrollbars on Android/iOS webviews
|
||||
type: reference
|
||||
originSessionId: 9da59a46-3dff-4a77-b7a4-8de4d07297b6
|
||||
---
|
||||
Virtuoso manages its own internal scroller. On Android WebView (and similar) native scrollbars auto-hide, so users see no scrollbar. The fix: wrap Virtuoso with OverlayScrollbars using the `useOverlayScrollbars` hook — **not** the `OverlayScrollbarsComponent`.
|
||||
|
||||
## Migration from `customScrollParent`
|
||||
|
||||
The previous approach used `customScrollParent` to let an outer `OverlayScrollbarsComponent` own the scroll. This was replaced: Virtuoso now owns its own scroller, and OverlayScrollbars wraps it. This means:
|
||||
- Remove `customScrollParent` prop from Virtuoso/VirtuosoGrid
|
||||
- Remove the outer `OverlayScrollbarsComponent` wrapper
|
||||
- Use `scrollerRef` instead to capture Virtuoso's scroller element
|
||||
- If the parent needs the scroller ref (e.g. for pull-to-refresh, scroll save/restore), expose it via a callback prop like `onScrollerRef`
|
||||
|
||||
## Boilerplate
|
||||
|
||||
```tsx
|
||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
|
||||
// Inside the component:
|
||||
const osRootRef = useRef<HTMLDivElement>(null);
|
||||
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
||||
const [initialize, osInstance] = useOverlayScrollbars({
|
||||
defer: true,
|
||||
options: { scrollbars: { autoHide: 'scroll' } },
|
||||
events: {
|
||||
initialized(instance) {
|
||||
const { viewport } = instance.elements();
|
||||
viewport.style.overflowX = 'var(--os-viewport-overflow-x)';
|
||||
viewport.style.overflowY = 'var(--os-viewport-overflow-y)';
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const root = osRootRef.current;
|
||||
if (scroller && root) {
|
||||
initialize({ target: root, elements: { viewport: scroller } });
|
||||
}
|
||||
return () => osInstance()?.destroy();
|
||||
}, [scroller, initialize, osInstance]);
|
||||
|
||||
const handleScrollerRef = useCallback((el: HTMLElement | Window | null) => {
|
||||
const div = el instanceof HTMLElement ? el : null;
|
||||
setScroller(div);
|
||||
// If parent needs the scroller (e.g. for pull-to-refresh):
|
||||
onScrollerRef?.(div as HTMLDivElement | null);
|
||||
}, [onScrollerRef]);
|
||||
```
|
||||
|
||||
## JSX structure
|
||||
|
||||
```tsx
|
||||
<div ref={osRootRef} data-overlayscrollbars-initialize='' className='h-full'>
|
||||
<Virtuoso
|
||||
scrollerRef={handleScrollerRef}
|
||||
style={{ height: containerHeight }}
|
||||
totalCount={items.length}
|
||||
itemContent={renderItem}
|
||||
overscan={200}
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
For `VirtuosoGrid`, same pattern — pass `scrollerRef={handleScrollerRef}`.
|
||||
|
||||
## Footer spacer
|
||||
|
||||
When Virtuoso owns its own scroller (no `customScrollParent`), the last items may be hidden behind bottom UI (tab bars, safe area). Add a Virtuoso `Footer` component to the components config:
|
||||
|
||||
```tsx
|
||||
const VIRTUOSO_COMPONENTS = {
|
||||
List: MyListComponent,
|
||||
Footer: () => <div style={{ height: 34 }} />,
|
||||
};
|
||||
```
|
||||
|
||||
## Key points
|
||||
|
||||
- **`useOverlayScrollbars`** hook, not `OverlayScrollbarsComponent` — the component can't share a viewport with Virtuoso
|
||||
- Wrapper div needs `ref={osRootRef}` and `data-overlayscrollbars-initialize=""`
|
||||
- `initialize({ target: root, elements: { viewport: scroller } })` tells OverlayScrollbars to use Virtuoso's existing scroller as its viewport (no new DOM element)
|
||||
- The `initialized` event **must** restore overflow CSS vars (`--os-viewport-overflow-x/y`) so OverlayScrollbars doesn't fight Virtuoso's scroll management
|
||||
- No custom Scroller component needed — `scrollerRef` replaces the old `Scroller` component pattern (e.g. `TOCScroller` was removed)
|
||||
|
||||
## Used in
|
||||
|
||||
- `src/app/library/components/Bookshelf.tsx` — library grid/list with parent scroller exposure for pull-to-refresh and scroll save/restore
|
||||
- `src/app/reader/components/sidebar/TOCView.tsx` — sidebar TOC (self-contained, no parent scroller needed)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
## Test-First Development
|
||||
|
||||
- Always write a failing unit test **before** implementing a fix.
|
||||
- Run the test to confirm it reproduces the bug or fails as expected, then apply the fix and verify the test passes.
|
||||
- Run the full test suite (`pnpm test`) after changes to ensure no regressions.
|
||||
@@ -0,0 +1,5 @@
|
||||
## TypeScript
|
||||
|
||||
- Never use the `any` type. Use `unknown`, proper types, or generics instead.
|
||||
- Strict mode is enabled. Target is ES2022.
|
||||
- Unused vars prefixed with `_` are allowed (ESLint configured).
|
||||
@@ -0,0 +1,9 @@
|
||||
## Verification (done-conditions)
|
||||
|
||||
Before marking work complete, all applicable checks must pass:
|
||||
|
||||
1. `pnpm test` — unit tests (vitest)
|
||||
2. `pnpm lint` — Biome + tsgo (also runs `pnpm lint:lua` if luajit is installed)
|
||||
3. `pnpm test:lua` — busted unit tests for `apps/readest.koplugin/spec/` (only when koplugin Lua files changed; soft-skips when busted/luajit not installed)
|
||||
4. `pnpm fmt:check` — Rust format check (only when `src-tauri/` files changed)
|
||||
5. `pnpm clippy:check` — Rust lint (only when `src-tauri/` files changed)
|
||||
Submodule
+1
Submodule apps/readest-app/.claude/skills/gstack added at 19e699ab9b
@@ -0,0 +1,111 @@
|
||||
---
|
||||
name: i18n-koplugin
|
||||
description: >
|
||||
Extract i18n strings from readest.koplugin Lua sources and translate empty
|
||||
msgstrs in apps/readest.koplugin/locales. Use when the user invokes
|
||||
/i18n-koplugin or asks to extract/translate koplugin i18n strings.
|
||||
Runs scripts/extract-i18n.js to sync .po catalogs from `_("...")` calls,
|
||||
then fills any empty `msgstr ""` entries across all locale files.
|
||||
user_invocable: true
|
||||
---
|
||||
|
||||
Extract/translate i18n strings for `readest.koplugin`. The catalogs are gettext `.po` files (not JSON like the main app). Run from the repo root or any worktree — the script resolves paths relative to the plugin dir.
|
||||
|
||||
## Step 1: Determine the working directory
|
||||
|
||||
If currently in a PR worktree (e.g., `/Users/chrox/dev/readest-pr-*`), use that. Otherwise use the main repo. The plugin dir is `<repo-root>/apps/readest.koplugin`.
|
||||
|
||||
## Step 2: Extract msgids from Lua sources
|
||||
|
||||
```bash
|
||||
cd <repo-root>/apps/readest.koplugin
|
||||
node scripts/extract-i18n.js
|
||||
```
|
||||
|
||||
This scans every `*.lua` file under `apps/readest.koplugin/` (except `spec/` and dotdirs) for `_("...")` and `_([[...]])` calls, then for each language listed in `apps/readest-app/i18next-scanner.config.cjs`:
|
||||
|
||||
- appends new msgids with empty `msgstr ""`
|
||||
- preserves existing translations
|
||||
- drops obsolete msgids
|
||||
- rewrites the `.po` header (Plural-Forms etc.)
|
||||
|
||||
The output prints `<lang> <kept>/<total> (-<dropped> obsolete)` per locale.
|
||||
|
||||
## Step 3: Find untranslated entries
|
||||
|
||||
An untranslated entry is a non-empty `msgid` followed by an empty `msgstr ""` (the file's header pair `msgid ""` / `msgstr ""` is NOT a translation — skip it).
|
||||
|
||||
```bash
|
||||
cd <repo-root>/apps/readest.koplugin/locales
|
||||
# List locales that still have untranslated strings, with counts
|
||||
for f in */translation.po; do
|
||||
# Count empty msgstrs that follow a non-empty msgid
|
||||
n=$(awk '
|
||||
/^msgid "/ { msgid=$0; next }
|
||||
/^msgstr ""$/ { if (msgid != "msgid \"\"") c++; next }
|
||||
' "$f")
|
||||
[ "$n" -gt 0 ] && echo "$f: $n untranslated"
|
||||
done
|
||||
```
|
||||
|
||||
If no results, report that all strings are translated and stop.
|
||||
|
||||
To list the actual untranslated msgids in one locale:
|
||||
|
||||
```bash
|
||||
awk '
|
||||
/^msgid "/ { msgid=$0; next }
|
||||
/^msgstr ""$/ { if (msgid != "msgid \"\"") print msgid; next }
|
||||
' <repo-root>/apps/readest.koplugin/locales/<lang>/translation.po
|
||||
```
|
||||
|
||||
## Step 4: Translate empty msgstrs
|
||||
|
||||
For each empty `msgstr ""` found:
|
||||
|
||||
1. Read the preceding `msgid "..."` — that's the English source string.
|
||||
2. Identify the target locale from the file path (e.g., `locales/ja/translation.po` → Japanese; see table below).
|
||||
3. Provide an accurate translation. Use the locale reference table for the language; match the tone/terminology already used in the same file (check existing translated entries for context).
|
||||
4. Preserve `printf`-style placeholders verbatim: `%s`, `%d`, `%1$s`, `%(name)s`, etc.
|
||||
5. Preserve newlines as `\n`, tabs as `\t`, and escape `"` as `\"` and backslashes as `\\` inside the msgstr.
|
||||
|
||||
Edit the `.po` files directly with the Edit tool — do NOT use sed for this, because msgids may contain characters that confuse shell quoting. Each replacement should target the unique `msgid "<English>"\nmsgstr ""` block:
|
||||
|
||||
Old:
|
||||
```
|
||||
msgid "<English string>"
|
||||
msgstr ""
|
||||
```
|
||||
|
||||
New:
|
||||
```
|
||||
msgid "<English string>"
|
||||
msgstr "<translation>"
|
||||
```
|
||||
|
||||
Batch all locales for the same key together when possible — keeps the translation set consistent.
|
||||
|
||||
### Locale reference
|
||||
|
||||
The supported language set is **not hardcoded in this skill**. The ground truth is `apps/readest-app/i18n-langs.json` — both `i18next-scanner.config.cjs` (via `require`) and `src/i18n/i18n.ts` (via JSON import) source from it, and `extract-i18n.js` reads `lngs` from the scanner config at runtime. To list the current set:
|
||||
|
||||
```bash
|
||||
cat <repo-root>/apps/readest-app/i18n-langs.json
|
||||
```
|
||||
|
||||
Map each code to a language name when translating. If a code in `i18n-langs.json` is missing from the `LANG_META` table inside `extract-i18n.js`, the script prints `<code> skipped (no metadata in extract-i18n.js)` — in that case, add the metadata entry there first, then re-run extraction.
|
||||
|
||||
## Step 5: Verify
|
||||
|
||||
Re-run the count loop from Step 3 and confirm zero untranslated strings remain. Report:
|
||||
|
||||
- number of msgids extracted
|
||||
- per-locale count of strings translated
|
||||
- any locales that were already complete
|
||||
|
||||
Optionally, run the koplugin Lua tests if `busted`/`luajit` are installed:
|
||||
|
||||
```bash
|
||||
cd <repo-root>/apps/readest-app
|
||||
pnpm test:lua
|
||||
```
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: i18n
|
||||
description: >
|
||||
Extract i18n strings, translate missing translations, or add a new language to readest-app.
|
||||
Use when the user invokes /i18n or asks to extract/translate i18n strings or add a new locale.
|
||||
Runs i18next-scanner to extract keys, then translates any __STRING_NOT_TRANSLATED__
|
||||
placeholders across all locale files.
|
||||
user_invocable: true
|
||||
---
|
||||
|
||||
Extract/translate i18n strings or add a new language for readest-app. Run from the app directory (either the main repo or a worktree).
|
||||
|
||||
## Step 0: Determine the mode
|
||||
|
||||
- If the user asks to **add a new language/locale**, go to **Adding a New Language** below.
|
||||
- Otherwise, go to **Extracting & Translating Strings** below.
|
||||
|
||||
## Step 1: Determine the working directory
|
||||
|
||||
If currently in a PR worktree (e.g., `/Users/chrox/dev/readest-pr-*`), use that. Otherwise use the main repo. The app directory is `<repo-root>/apps/readest-app`.
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Language
|
||||
|
||||
When the user asks to add a new language (e.g., "add Hungarian", "add hu locale"):
|
||||
|
||||
### Step A1: Register the locale in two places
|
||||
|
||||
1. **`i18n-langs.json`** — Append the locale code to the array. Both `i18next-scanner.config.cjs` and `src/i18n/i18n.ts` import from this file, so they pick up the new entry automatically.
|
||||
2. **`src/services/constants.ts`** — Add an entry to `TRANSLATED_LANGS` with the locale code and native language name (e.g., `hu: 'Magyar'`). If the locale already exists in `TRANSLATOR_LANGS`, remove the duplicate there since it will be inherited via the spread.
|
||||
|
||||
### Step A2: Generate the translation file
|
||||
|
||||
```bash
|
||||
cd <app-dir>
|
||||
pnpm run i18n:extract
|
||||
```
|
||||
|
||||
This creates `public/locales/<code>/translation.json` with all keys set to `__STRING_NOT_TRANSLATED__`.
|
||||
|
||||
### Step A3: Translate all strings
|
||||
|
||||
Follow **Step 4** below to translate every `__STRING_NOT_TRANSLATED__` entry in the new locale file.
|
||||
|
||||
### Step A4: Verify
|
||||
|
||||
Follow **Step 5** below to confirm zero remaining untranslated strings for the new locale.
|
||||
|
||||
---
|
||||
|
||||
## Extracting & Translating Strings
|
||||
|
||||
### Step 2: Extract i18n strings
|
||||
|
||||
```bash
|
||||
cd <app-dir>
|
||||
pnpm run i18n:extract
|
||||
```
|
||||
|
||||
This runs `i18next-scanner` which scans source files for translation keys and adds any new keys to all locale files with `__STRING_NOT_TRANSLATED__` as the placeholder value.
|
||||
|
||||
### Step 3: Find untranslated strings
|
||||
|
||||
```bash
|
||||
grep -r "__STRING_NOT_TRANSLATED__" <app-dir>/public/locales/
|
||||
```
|
||||
|
||||
If no results, report that all strings are already translated and stop.
|
||||
|
||||
### Step 4: Translate missing strings
|
||||
|
||||
For each `__STRING_NOT_TRANSLATED__` found:
|
||||
|
||||
1. Identify the English key (e.g., `"Hide Scrollbar"`)
|
||||
2. Identify the target locale from the file path (e.g., `locales/ja/translation.json` -> Japanese)
|
||||
3. Provide an accurate translation for each locale
|
||||
|
||||
Use `sed -i ''` on macOS to replace in-place. Handle all locales in one batch:
|
||||
|
||||
```bash
|
||||
cd <app-dir>/public/locales
|
||||
sed -i '' 's/"<Key>": "__STRING_NOT_TRANSLATED__"/"<Key>": "<translation>"/' <locale>/translation.json
|
||||
```
|
||||
|
||||
### Locale reference
|
||||
|
||||
The canonical, complete list of supported locales lives in `i18n-langs.json` (codes only) and `TRANSLATED_LANGS` in `src/services/constants.ts` (codes → native display names). Read those for the source of truth — translate every locale that appears in `i18n-langs.json`. Don't carry forward older "out-of-scope" exclusions like `pt-BR` or `uz`; if it's in `i18n-langs.json` it ships, and it needs translation.
|
||||
|
||||
### Step 5: Verify
|
||||
|
||||
```bash
|
||||
grep -r "__STRING_NOT_TRANSLATED__" <app-dir>/public/locales/
|
||||
```
|
||||
|
||||
Confirm zero remaining untranslated strings. Report the number of keys translated and locales updated.
|
||||
@@ -3,7 +3,7 @@ PDFJS_FONTS_PATH=../../packages/foliate-js/node_modules/pdfjs-dist
|
||||
PDFJS_STYLE_PATH=../../packages/foliate-js/vendor/pdfjs
|
||||
|
||||
NEXT_PUBLIC_DEFAULT_POSTHOG_URL_BASE64="aHR0cHM6Ly91cy5pLnBvc3Rob2cuY29t"
|
||||
NEXT_PUBLIC_DEFAULT_POSTHOG_KEY_BASE64="cGhjX2ViNXowbVRxWm8yZm5YYnZGNmE3bFh5TThpTmRSNTNsR1A3VFM3VGh4S08="
|
||||
NEXT_PUBLIC_DEFAULT_POSTHOG_KEY_BASE64="cGhjX0x3ekhZRWtsZUVub3ZSc05ZQlRpTVRTV2MyS1NUOFdZMzBIWWFhN0ZPa1IK"
|
||||
|
||||
NEXT_PUBLIC_DEFAULT_SUPABASE_URL_BASE64="aHR0cHM6Ly9yZWFkZXN0LnN1cGFiYXNlLmNv"
|
||||
NEXT_PUBLIC_DEFAULT_SUPABASE_KEY_BASE64="ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBjM01pT2lKemRYQmhZbUZ6WlNJc0luSmxaaUk2SW5aaWMzbDRablZ6YW1weFpIaHJhbkZzZVhOaklpd2ljbTlzWlNJNkltRnViMjRpTENKcFlYUWlPakUzTXpReE1qTTJOekVzSW1WNGNDSTZNakEwT1RZNU9UWTNNWDAuM1U1VXFhb3VfMVNnclZlMWVvOXJBcGMwdUtqcWhwUWRVWGh2d1VIbVVmZw=="
|
||||
|
||||
@@ -28,3 +28,5 @@ S3_ACCESS_KEY_ID=PLACE_HOLDER
|
||||
S3_SECRET_ACCESS_KEY=PLACE_HOLDER
|
||||
S3_BUCKET_NAME=PLACE_HOLDER
|
||||
S3_REGION=PLACE_HOLDER
|
||||
|
||||
TEMP_STORAGE_PUBLIC_BUCKET_NAME=PLACE_HOLDER
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
NEXT_PUBLIC_APP_PLATFORM=tauri
|
||||
NEXT_PUBLIC_APP_PLATFORM=tauri
|
||||
|
||||
DBUS_ID=com.bilingify.readest
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
NEXT_PUBLIC_APP_PLATFORM=tauri
|
||||
AI_GATEWAY_API_KEY=your_key_here
|
||||
@@ -0,0 +1,3 @@
|
||||
NEXT_PUBLIC_APP_PLATFORM=web
|
||||
AI_GATEWAY_API_KEY=your_key_here
|
||||
NEXT_PUBLIC_AI_GATEWAY_API_KEY=your_key_here
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
.test-sandbox-node/
|
||||
.vitest-attachments/
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
@@ -48,6 +50,9 @@ tauri.*.conf.json
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# eslint
|
||||
.eslintcache
|
||||
|
||||
#generated
|
||||
src-tauri/gen
|
||||
|
||||
@@ -60,3 +65,9 @@ src-tauri/gen
|
||||
/public/fallback-*.js
|
||||
/public/swe-worker-*.js
|
||||
|
||||
/dist/
|
||||
|
||||
.context/
|
||||
.claude/settings.local.json
|
||||
.claude/skills
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
## Project Overview
|
||||
|
||||
Readest is a cross-platform ebook reader built as a **Next.js 16 + Tauri v2** hybrid app. It's part of a pnpm monorepo at `/apps/readest-app/`. The app runs on web (CloudFlare Workers), desktop (macOS/Windows/Linux via Tauri), and mobile (iOS/Android via Tauri).
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
pnpm dev-web # Web-only dev server (no Rust compilation needed)
|
||||
pnpm tauri dev # Desktop dev with Tauri (compiles Rust backend)
|
||||
|
||||
# Building
|
||||
pnpm build # Build Next.js for Tauri
|
||||
pnpm build-web # Build Next.js for web deployment
|
||||
|
||||
# Testing (see [docs/testing.md](docs/testing.md) for full details)
|
||||
pnpm test # Unit tests (vitest + jsdom)
|
||||
pnpm test -- src/__tests__/utils/misc.test.ts # Run a single test file
|
||||
pnpm test -- --watch # Watch mode
|
||||
pnpm test:browser # Browser tests (Chromium via Playwright)
|
||||
pnpm tauri:dev:test # Start Tauri app with webdriver
|
||||
pnpm test:tauri # Run Tauri integration tests
|
||||
|
||||
# Linting & Formatting
|
||||
pnpm lint # Biome (linter) + tsgo (type check)
|
||||
pnpm format # Prettier (runs from monorepo root)
|
||||
pnpm format:check # Check formatting without writing
|
||||
|
||||
# Rust
|
||||
pnpm fmt:check # Check formatting Rust code (src-tauri)
|
||||
pnpm clippy:check # Lint Rust code (src-tauri)
|
||||
```
|
||||
|
||||
### Source Layout
|
||||
|
||||
| Directory | Purpose |
|
||||
| ----------------- | ------------------------------------------------------------- |
|
||||
| `src/app/` | Next.js App Router pages and API routes |
|
||||
| `src/components/` | React components (reader, settings, library, assistant, etc.) |
|
||||
| `src/services/` | Business logic: TTS, translators, OPDS, sync, AI, metadata |
|
||||
| `src/store/` | Zustand state stores |
|
||||
| `src/hooks/` | Custom React hooks |
|
||||
| `src/libs/` | Document loaders, payment, storage, sync |
|
||||
| `src/utils/` | Pure utility functions |
|
||||
| `src/types/` | TypeScript type definitions |
|
||||
| `src/context/` | React Context providers (Auth, Env, Sync, etc.) |
|
||||
| `src/workers/` | Web Workers for background tasks |
|
||||
| `src-tauri/` | Rust backend: Tauri plugins, platform-specific code |
|
||||
|
||||
### Path Aliases (tsconfig)
|
||||
|
||||
- `@/*` → `./src/*`
|
||||
- `@/components/ui/*` → `./src/components/primitives/*`
|
||||
|
||||
### Rust Backend (`src-tauri/`)
|
||||
|
||||
Platform-specific code lives in `src-tauri/src/{macos,windows,android,ios}/`. Custom Tauri plugins are in `src-tauri/plugins/`.
|
||||
|
||||
## Git Worktrees
|
||||
|
||||
Always use `pnpm worktree:new <branch-name|pr-number>` to create worktrees. Never use `git worktree add` directly — the script handles submodule initialization (simplecc WASM, foliate-js), dependency installation, `.env` copying, vendor assets, and Tauri gen symlinks that are required for lint and tests to pass.
|
||||
|
||||
```bash
|
||||
pnpm worktree:new feat/my-feature # New branch from origin/main
|
||||
pnpm worktree:new 3837 # Checkout PR #3837 with push access to fork
|
||||
```
|
||||
|
||||
## Project Rules
|
||||
|
||||
Rules are in `.claude/rules/`: test-first, typescript, verification.
|
||||
|
||||
### i18n
|
||||
|
||||
See [docs/i18n.md](docs/i18n.md) for the key-as-content translation approach, `stubTranslation` usage in non-React modules, and extraction workflow.
|
||||
|
||||
### Safe Area Insets
|
||||
|
||||
See [docs/safe-area-insets.md](docs/safe-area-insets.md) for rules on handling top/bottom insets for UI elements near screen edges.
|
||||
|
||||
### Design System
|
||||
|
||||
UI/UX rules — surface tiers, action vocabulary, settings primitives (`BoxedList`, `SettingsRow`, `SettingsSwitchRow`, `SettingsSelect`, `NavigationRow`, `Tips`, etc.), boxed-list anatomy, RTL conventions, e-ink overlay, and anti-patterns — live in [DESIGN.md](DESIGN.md). Codify recurring decisions there so they persist for the team and future contributors. Reach for the primitives in `src/components/settings/primitives/` instead of inlining chassis classes.
|
||||
|
||||
### E-ink mode
|
||||
|
||||
Every new UI widget must look right under `[data-eink='true']`. E-ink screens have no shadows, no gradients, slow refresh, and need crisp 1px borders for delineation. The conventions live in `src/styles/globals.css` — reuse the existing classes instead of inventing new ones:
|
||||
|
||||
- **Surfaces / inputs** — add `eink-bordered`. In eink mode it swaps to `bg-base-100` + 1px `base-content` border. Use it on inputs, custom button backgrounds, ghost-styled cancel buttons, and any container that needs a visible boundary.
|
||||
- **Primary action buttons** — add `btn-primary` (alongside whatever Tailwind classes you use for color themes). The `[data-eink] .btn-primary` rule inverts to `base-content` bg + `base-100` text so the primary CTA stays distinct from secondary actions.
|
||||
- **`.modal-box`** picks up no-shadow + 1px border automatically; dialogs that use it don't need additions.
|
||||
- **Don't rely on color/shadow alone for hierarchy.** Two same-tone buttons differ only by hover on color themes, and hover doesn't exist on e-ink touchscreens. Pair a borderless ghost (cancel) with a solid CTA (submit) so eink can invert one without flattening the difference.
|
||||
|
||||
When in doubt, toggle E-ink in Settings → Misc and check. The rules in `globals.css` cover most cases automatically, but composite components (custom buttons, layered cards) often need `eink-bordered` on the right element to stay legible.
|
||||
|
||||
Available gstack skills:
|
||||
|
||||
- `/plan-ceo-review` — CEO/founder-mode plan review
|
||||
- `/plan-eng-review` — Eng manager-mode plan review
|
||||
- `/plan-design-review` — Designer's eye review of a live site
|
||||
- `/design-consultation` — Design system consultation
|
||||
- `/review` — Pre-landing PR review
|
||||
- `/ship` — Ship workflow (merge, test, review, bump, PR)
|
||||
- `/browse` — Fast headless browser for QA and site interaction
|
||||
- `/qa` — QA test and fix bugs
|
||||
- `/qa-only` — QA report only (no fixes)
|
||||
- `/qa-design-review` — Designer's eye QA with fixes
|
||||
- `/setup-browser-cookies` — Import cookies for authenticated testing
|
||||
- `/retro` — Weekly engineering retrospective
|
||||
- `/document-release` — Post-ship documentation update
|
||||
|
||||
If gstack skills aren't working, run `cd .claude/skills/gstack && ./setup` to build the binary and register skills.
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
AGENTS.md
|
||||
@@ -0,0 +1,972 @@
|
||||
## Readest Design Language
|
||||
|
||||
Readest's UI is **Adwaita-aligned**, **e-ink-first**, **cross-platform-aware**. This doc is the
|
||||
reference for that language: principles, vocabulary, anti-patterns. New work should read it
|
||||
before reaching for daisyui defaults; existing work is gradually migrating toward it.
|
||||
|
||||
### Status
|
||||
|
||||
This doc is the **first articulation** of the system, not a retrospective. Many existing
|
||||
components don't fully match it yet (especially older buttons and ad-hoc panels). The goal
|
||||
is that **new code uses these conventions** and **migrations land opportunistically** as
|
||||
features get touched.
|
||||
|
||||
---
|
||||
|
||||
### 1. Identity & lineage
|
||||
|
||||
Readest's visual language descends from **Adwaita / libadwaita** — GNOME's design system —
|
||||
adapted for a cross-platform Tauri + Next.js app that also runs on iOS, Android, web, and
|
||||
e-ink readers.
|
||||
|
||||
What we take from Adwaita:
|
||||
|
||||
- **Content first, chrome recedes.** The reading surface is the product. Settings, toolbars,
|
||||
popups never compete with the page.
|
||||
- **Boldly minimal.** Restraint over density. Whitespace is structural.
|
||||
- **Surface hierarchy** — window → view → card — three explicit elevation tiers, no shadow
|
||||
gymnastics.
|
||||
- **Color discipline.** Brand color is rare and earned. Neutral palette carries the weight.
|
||||
- **Boxed lists are the chassis.** AdwActionRow's prefix · title · suffix anatomy is the
|
||||
canonical settings/list row everywhere.
|
||||
- **Pills, ghosts, flats.** Three-tier button palette: pill/circular ghost in headers, flat
|
||||
secondary over view-bg, accent CTA only when truly primary.
|
||||
- **Banner vs Toast.** AdwBanner = inline, top-of-window, persistent. AdwToast = transient,
|
||||
bottom slide-in.
|
||||
- **Switches over checkboxes** for boolean settings.
|
||||
- **Subtle motion.** Short, ease-out, never bouncy.
|
||||
|
||||
What's Readest-specific:
|
||||
|
||||
- **E-ink as a first-class mode.** Every surface flips to flat 1px contrast borders under
|
||||
`[data-eink='true']`. Adwaita is desktop-GNOME-only; we ship to e-ink readers and the
|
||||
visual language has to survive there.
|
||||
- **Cross-platform reality.** Readest runs on macOS, Windows, Linux, iOS, Android, web. The
|
||||
identity stays Adwaita; platform grace notes (radii, target sizes) follow host
|
||||
conventions where they matter.
|
||||
|
||||
---
|
||||
|
||||
### 2. Principles
|
||||
|
||||
The seven rules. When in doubt, work backward from these.
|
||||
|
||||
#### 2.1 Surfaces continue surfaces
|
||||
|
||||
A control that extends a list/card should match its parent's border + fill. The
|
||||
"+ Import Dictionary" button at `src/components/settings/CustomDictionaries.tsx` reads as
|
||||
detached card siblings of the dictionary list above it because they share
|
||||
`border-base-200 bg-base-100 rounded-lg`.
|
||||
|
||||
> **Bad**: a list of dictionaries in a `bg-base-100` card, followed by a `btn-outline btn-primary`
|
||||
> add button. The button shouts; the list whispers; the eye bounces.
|
||||
>
|
||||
> **Good**: list and add-button share the same surface vocabulary. The eye flows.
|
||||
|
||||
#### 2.2 Color is earned
|
||||
|
||||
Brand `primary` is reserved for **the** primary action of a surface. Most actions don't have
|
||||
a primary action — they have a list of equally-weighted choices, or a single accent.
|
||||
|
||||
- Settings dialog has no primary. Every panel is a list of toggles. **Zero brand color.**
|
||||
- "Import a Book" in onboarding is a primary CTA. **One brand color.**
|
||||
- "Add Web Search" extends a list — it's not the surface's primary action. **Neutral.**
|
||||
|
||||
#### 2.3 Two-step depth
|
||||
|
||||
State changes cycle through **`base-100 → base-200 → base-300`** instead of recoloring.
|
||||
Hover lifts, active deepens, disabled fades opacity. This is theme-safe (works across all
|
||||
11 color themes), e-ink-friendly (depth is preserved as borders, not shades), and
|
||||
calmer than recoloring.
|
||||
|
||||
#### 2.4 Localize the hover signal
|
||||
|
||||
When a button hovers, **one focal element changes**, not the whole button. The icon chip
|
||||
inverts; the label stays steady. The badge intensifies; the row stays neutral. This reads
|
||||
as deliberate, not decorative.
|
||||
|
||||
#### 2.5 Motion is color, not transform
|
||||
|
||||
Default to `transition-colors duration-150`. No `scale`, no `translate`, no `rotate` unless
|
||||
the motion **is** the message (a chevron rotating to indicate expansion is fine; a button
|
||||
that scales on hover is not). Transforms break under `[data-eink='true']` and feel
|
||||
gimmicky under Adwaita's calm rhythm.
|
||||
|
||||
#### 2.6 Eink-first by default
|
||||
|
||||
Every custom-styled bordered surface gets the `eink-bordered` class. Every primary action
|
||||
gets `btn-primary` (which has dedicated eink rules). Don't rely on color or shadow alone
|
||||
for hierarchy — eink screens have neither.
|
||||
|
||||
If you can't toggle Settings → Misc → Eink and still tell which button is the CTA, the
|
||||
hierarchy is broken.
|
||||
|
||||
#### 2.7 Focus is visible but quiet
|
||||
|
||||
Keyboard focus needs a visible ring. `focus-visible:ring-2 focus-visible:ring-base-content/15`
|
||||
is the canonical treatment for custom buttons. Loud `ring-primary` reserved for inputs
|
||||
where the focus state IS the affordance.
|
||||
|
||||
#### 2.8 RTL: always use logical properties (REQUIRED)
|
||||
|
||||
Readest ships with RTL languages enabled. **Never use direction-bound Tailwind
|
||||
utilities** when a logical equivalent exists — the visual edges flip in RTL,
|
||||
the logical ones don't.
|
||||
|
||||
| Don't use | Use instead |
|
||||
| ---------------------------------- | ---------------------------------- |
|
||||
| `pl-*` / `pr-*` | `ps-*` (start) / `pe-*` (end) |
|
||||
| `ml-*` / `mr-*` | `ms-*` / `me-*` |
|
||||
| `text-left` / `text-right` | `text-start` / `text-end` |
|
||||
| `border-l` / `border-r` | `border-s` / `border-e` |
|
||||
| `rounded-l-*` / `rounded-r-*` | `rounded-s-*` / `rounded-e-*` |
|
||||
| `left-*` / `right-*` (positioning) | `start-*` / `end-*` |
|
||||
| `justify-start` / `justify-end` | (these ARE direction-aware) — keep |
|
||||
|
||||
The `flex-row` direction is automatically reversed in RTL by the browser, so
|
||||
you usually don't need to do anything for `flex` / `gap`. Only **explicit
|
||||
edges** (padding, margin, borders, radius, absolute positioning) need
|
||||
logical properties.
|
||||
|
||||
**Quick scan when reviewing a diff:** grep for `\b(pl|pr|ml|mr|left-|right-|text-left|text-right|border-l|border-r|rounded-l|rounded-r)-` in changed files. Any hit that isn't a deliberate LTR-only
|
||||
case (rare — usually only icon glyphs that have a fixed orientation) should
|
||||
be flipped to the logical equivalent.
|
||||
|
||||
#### 2.9 Every panel and sub-page starts with title + description (REQUIRED)
|
||||
|
||||
Every settings panel and every sub-page must open with:
|
||||
|
||||
1. **A title** — the panel name. Style: `text-lg font-semibold tracking-tight`. In a
|
||||
top-level panel this is an `<h2>`; in a sub-page this is the `parentLabel /
|
||||
currentLabel` breadcrumb in `SubPageHeader` (which uses the same typography so the
|
||||
word stays anchored visually as the user navigates in/out).
|
||||
2. **A one-line description** — a short sentence under the title explaining what this
|
||||
surface does or how it fits in the user's workflow. Style: `text-sm
|
||||
text-base-content/70 leading-relaxed`. Skip it only when the surface is so trivial
|
||||
the breadcrumb already says everything (rare — when in doubt, write one).
|
||||
|
||||
Why: orientation, visual rhythm, and Adwaita parity (`AdwPreferencesPage` always has
|
||||
both). The same vertical opening across every surface makes the system feel cohesive
|
||||
and gives users a predictable place to learn what a screen does.
|
||||
|
||||
**Canonical components.** The `<SubPageHeader>` primitive in
|
||||
`src/components/settings/SubPageHeader.tsx` accepts a `description?: React.ReactNode`
|
||||
prop that renders the description in the canonical style — sub-pages should pass it
|
||||
there rather than rolling their own `<p>` below the header. Top-level panels currently
|
||||
inline the title + description; if a third or fourth panel needs the same pattern,
|
||||
extract a `<PanelHeader>` primitive following the same shape.
|
||||
|
||||
**Examples.**
|
||||
|
||||
```tsx
|
||||
// Sub-page (Integrations → OPDS Catalogs)
|
||||
<SubPageHeader
|
||||
parentLabel={_('Integrations')}
|
||||
currentLabel={_('OPDS Catalogs')}
|
||||
description={_('Browse and download books from online catalogs')}
|
||||
onBack={() => setSubPage(null)}
|
||||
/>
|
||||
|
||||
// Top-level panel (Integrations panel root)
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-1.5 text-lg font-semibold tracking-tight'>{_('Integrations')}</h2>
|
||||
<p className='text-base-content/70 text-sm leading-relaxed'>
|
||||
{_('Connect Readest to external services for sync, highlights, and catalogs.')}
|
||||
</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Surface hierarchy
|
||||
|
||||
Three named tiers, mapped onto daisyui tokens. Use these terms in conversation and code
|
||||
comments even though the classes are still daisyui-native.
|
||||
|
||||
| Tier | Token | Role | Example |
|
||||
| ---------- | ------------------------------------ | ----------------------------------------------------------------------------- | ----------------------------------------------- |
|
||||
| **Window** | `bg-base-200` | The outermost backdrop. Modal scrims, dialog content area, scroll containers. | `<Dialog>` body |
|
||||
| **View** | `bg-base-100/60` or `bg-base-200/40` | Mid-tier surface inside a window. Tip boxes, secondary panels. | The "提示 / Tips" callout in CustomDictionaries |
|
||||
| **Card** | `bg-base-100` | Top-tier content surface. Boxed lists, popovers, modal-box. | The dictionaries list card |
|
||||
|
||||
Border treatment:
|
||||
|
||||
- **Window** has no border (it IS the boundary).
|
||||
- **View** uses no border or `border-base-200/60` for very soft delineation.
|
||||
- **Card** uses `border border-base-200`. In e-ink, `eink-bordered` flips it to 1px
|
||||
`border-base-content`.
|
||||
|
||||
Corner radius:
|
||||
|
||||
- **Card / View**: `rounded-lg` (8px) — Readest's house radius. Adwaita uses 9px; 8px is
|
||||
close enough and matches Tailwind's scale.
|
||||
- **Modal / Sheet**: `modal-box` default (~1rem / 16px) — bigger surfaces get bigger radii.
|
||||
- **Pills / Chips**: `rounded-full`.
|
||||
- **Inputs / small buttons**: `rounded-md` (6px) or `rounded-lg` (8px).
|
||||
|
||||
#### Surface continuity rule
|
||||
|
||||
When a control extends a card (an "add row" affordance, a footer button bar attached to a
|
||||
list), it inherits the card's surface treatment: same `bg-base-100`, same
|
||||
`border-base-200`, same `rounded-lg`. It is the card grown by one row.
|
||||
|
||||
---
|
||||
|
||||
### 4. Action vocabulary
|
||||
|
||||
Six archetypes. Pick by **role**, not by **appearance**.
|
||||
|
||||
#### 4.1 Accent CTA
|
||||
|
||||
The primary, accent-colored button. **One per surface, max.** Submit on a form, "Open
|
||||
Book", "Sign In".
|
||||
|
||||
```tsx
|
||||
className = 'btn btn-primary';
|
||||
```
|
||||
|
||||
Eink: `btn-primary` has dedicated rules (inverts to base-content bg + base-100 text) so it
|
||||
stays distinct from secondary actions on monochrome screens.
|
||||
|
||||
#### 4.2 Suggested
|
||||
|
||||
A non-accent-but-emphasized action. Used when there are multiple equally-weighted actions
|
||||
and one is the recommended path. Adwaita's "suggested-action" CSS class.
|
||||
|
||||
```tsx
|
||||
className = 'btn btn-neutral';
|
||||
```
|
||||
|
||||
Rare. Most surfaces don't need this tier.
|
||||
|
||||
#### 4.3 Flat
|
||||
|
||||
The default secondary button. Sits on a view or card surface, no border, hover lifts to
|
||||
`base-200`. The bulk of buttons should be flat.
|
||||
|
||||
```tsx
|
||||
className="btn btn-ghost"
|
||||
// or for a custom surface treatment:
|
||||
className={clsx(
|
||||
'rounded-lg px-4 py-2 text-sm font-medium',
|
||||
'hover:bg-base-200 transition-colors duration-150',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-base-content/15',
|
||||
)}
|
||||
```
|
||||
|
||||
#### 4.4 Pill / Circular ghost
|
||||
|
||||
Compact icon-only buttons in header bars and toolbars. Always `rounded-full`,
|
||||
`btn-circle` or hand-rolled circular ghost.
|
||||
|
||||
```tsx
|
||||
className = 'btn btn-ghost btn-circle h-8 min-h-8 w-8 p-0';
|
||||
```
|
||||
|
||||
The window controls in `SettingsDialog.tsx` (search, menu, close) use this archetype.
|
||||
|
||||
#### 4.5 Destructive
|
||||
|
||||
Delete, remove, irreversible. Adwaita uses `destructive-action`. Readest uses red
|
||||
sparingly — usually only the icon, not the whole button.
|
||||
|
||||
```tsx
|
||||
// Icon-only delete X in delete mode:
|
||||
className = 'btn btn-ghost btn-sm shrink-0 px-1';
|
||||
// with <IoMdCloseCircleOutline className="text-error h-4 w-4" />
|
||||
```
|
||||
|
||||
For destructive **dialogs** (confirmation modals), the confirm button can be `btn-error`,
|
||||
but only in the modal — never on the main surface.
|
||||
|
||||
#### 4.6 ListExtension
|
||||
|
||||
A Readest-named archetype for "add another row to the list above" affordances. The two
|
||||
buttons at the bottom of `CustomDictionaries.tsx` are the canonical example.
|
||||
|
||||
Anatomy:
|
||||
|
||||
- Surface matches the parent card (`border border-base-200 bg-base-100 rounded-lg`)
|
||||
- Height ~h-11
|
||||
- Centered: small icon chip + label
|
||||
- Icon chip: `bg-base-200 text-base-content/60 rounded-full h-5 w-5`
|
||||
- Hover: border deepens to `base-300`, bg lightens to `bg-base-200/60`, icon chip inverts
|
||||
to `bg-base-content text-base-100`
|
||||
- `eink-bordered` on the button itself
|
||||
|
||||
```tsx
|
||||
<button
|
||||
type='button'
|
||||
onClick={handleAdd}
|
||||
className={clsx(
|
||||
'eink-bordered group flex h-11 items-center justify-center gap-2.5',
|
||||
'border-base-200 bg-base-100 rounded-lg border px-4',
|
||||
'text-base-content text-sm font-medium',
|
||||
'transition-colors duration-150',
|
||||
'hover:border-base-300 hover:bg-base-200/60',
|
||||
'active:bg-base-200/80',
|
||||
'focus-visible:ring-base-content/15 focus-visible:outline-none focus-visible:ring-2',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={clsx(
|
||||
'flex h-5 w-5 items-center justify-center rounded-full',
|
||||
'bg-base-200 text-base-content/60',
|
||||
'transition-colors duration-150',
|
||||
'group-hover:bg-base-content group-hover:text-base-100',
|
||||
)}
|
||||
>
|
||||
<MdAdd className='h-3.5 w-3.5' />
|
||||
</span>
|
||||
<span className='line-clamp-1'>{label}</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
Use this for: "Import Dictionary", "Add Web Search", "Add Custom Theme", any "+ add new
|
||||
to this list" pattern. **Do not** use `btn-outline btn-primary` for these.
|
||||
|
||||
---
|
||||
|
||||
### 5. Boxed list anatomy
|
||||
|
||||
The settings UI is built on boxed lists. One pattern, used everywhere.
|
||||
|
||||
#### Container
|
||||
|
||||
Use the `<BoxedList>` primitive at `src/components/settings/primitives/BoxedList.tsx`
|
||||
rather than inlining the chassis classes:
|
||||
|
||||
```tsx
|
||||
<BoxedList title={_('Reading Sync')} data-setting-id='settings.section.id'>
|
||||
{/* rows */}
|
||||
</BoxedList>
|
||||
```
|
||||
|
||||
The primitive renders:
|
||||
|
||||
```tsx
|
||||
<div className='card eink-bordered border-base-200 bg-base-100 border'>
|
||||
<div className='divide-base-200 divide-y'>{children}</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
- `card` for the radius
|
||||
- `border border-base-200` for the boundary (eink upgrades this automatically)
|
||||
- `eink-bordered` for the e-ink-mode contrast border
|
||||
- `divide-base-200 divide-y` for inter-row separators
|
||||
|
||||
> **No `overflow-hidden` on the card.** Children may host popovers (color
|
||||
> pickers, dropdowns, tooltips) that need to escape the card bounds. The
|
||||
> `divide-y` rules sit between rows and don't touch the card's rounded
|
||||
> corners, so omitting overflow-clip is visually safe AND keeps embedded
|
||||
> popovers from getting clipped.
|
||||
|
||||
#### Row anatomy
|
||||
|
||||
Three slots, in order, always:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ [prefix] Title text [suffix slots] │
|
||||
│ [ ] Subtitle text (optional) [ ][ ]│
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Slot | Contents |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------- |
|
||||
| **Prefix** | Drag handle, leading icon, avatar, status dot, or empty. |
|
||||
| **Title** | Primary label. `font-medium`. Truncates with `truncate`. |
|
||||
| **Subtitle** | Optional secondary line. `text-sm text-base-content/70`. Used for warnings, descriptions, status. |
|
||||
| **Suffix** | Badge, switch, button, chevron, value, or any combination. End-aligned. |
|
||||
|
||||
Canonical example: `SortableRow` in `src/components/settings/CustomDictionaries.tsx`. The
|
||||
drag handle is the prefix, the dict name is the title, the warning reason is the
|
||||
subtitle, and the badge + toggle + edit/delete buttons stack as suffixes.
|
||||
|
||||
#### Row variants
|
||||
|
||||
- **ActionRow** — title + suffix is a single button or chevron. Tap anywhere navigates.
|
||||
- **SwitchRow** — title + suffix is a toggle. Tap anywhere toggles.
|
||||
- **ComboRow** — title + suffix is a dropdown/select.
|
||||
- **ExpanderRow** — chevron suffix; tap expands to reveal nested rows.
|
||||
|
||||
These names come from libadwaita and apply 1:1 to Readest's lists. Use the names in code
|
||||
comments and PR descriptions.
|
||||
|
||||
#### Spacing
|
||||
|
||||
- Row vertical padding: `py-2` (8px) for compact lists, `py-3` (12px) for breathing room.
|
||||
- Row horizontal padding: `px-3` (12px) or `px-4` (16px). Stay consistent within a list.
|
||||
- Slot gap: `gap-2` (8px) between prefix/title/suffix elements.
|
||||
|
||||
#### Disabled rows
|
||||
|
||||
Disabled rows fade the title to `text-base-content/60` and disable the suffix control. The
|
||||
row itself stays at full opacity — only the **content** dims, not the row.
|
||||
|
||||
#### Toggle size
|
||||
|
||||
| Daisyui class | Use case |
|
||||
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| `toggle` (default, h-5 / ~20px) | **Settings panel boxed-list rows** — `<SettingsSwitchRow>` uses this. Visible weight matches the 56px `min-h-14` row. |
|
||||
| `toggle-sm` (h-4 / ~16px) | Inline secondary switches in tighter contexts — e.g. dictionary list rows in `CustomDictionaries`. |
|
||||
| `toggle-xs` (h-3 / ~12px) | Compact metadata toggles inside cards — e.g. OPDS catalog "Auto-download". |
|
||||
|
||||
The `<SettingsSwitchRow>` primitive bakes in the default `toggle`. **Don't override
|
||||
to `toggle-sm` inside boxed-list rows** — it looks orphaned in the row's vertical
|
||||
breathing room. Use the smaller sizes only when the row itself is shorter than 56px.
|
||||
|
||||
#### Typography inherits from `.settings-content`
|
||||
|
||||
The Settings dialog (and any settings-style sheet/popup) wraps its content
|
||||
in `.settings-content`, which is defined in `src/styles/globals.css` as:
|
||||
|
||||
```css
|
||||
.dropdown-content,
|
||||
.settings-content {
|
||||
font-size: 14px; /* desktop */
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.dropdown-content,
|
||||
.settings-content {
|
||||
font-size: 16px; /* mobile bump — high-DPI phones need bigger body text */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Don't hardcode `text-sm` on row labels, NavigationRow titles, or panel
|
||||
descriptions** — that locks the text to 14px on every viewport and kills
|
||||
the mobile bump. Instead:
|
||||
|
||||
- **Primary labels** (SettingsRow label, NavigationRow title, SubPageHeader
|
||||
description, ad-hoc row labels in panels and integration forms): no
|
||||
font-size class — inherits 14/16 from the wrapper. Use `<SettingLabel>`
|
||||
rather than inlining a `<span>`; it adds `font-medium` for cased scripts
|
||||
and drops the weight for caseless scripts (CJK / Arabic / Hebrew / Indic
|
||||
/ Thai / Tibetan), since those bold poorly at body size and `font-medium`
|
||||
on Han / Hangul / Devanagari renders as uneven stroke-thickening across
|
||||
system fonts.
|
||||
- **Secondary text** (SettingsRow description, NavigationRow status, Tips
|
||||
body, BoxedList description): use `text-[0.85em]` so it stays
|
||||
proportional (≈12px desktop, ≈13.6px mobile).
|
||||
- **Form controls** (`<input>`, `<select>`): browsers don't inherit
|
||||
font-size onto form elements, so add the `settings-content` class
|
||||
_directly on the element_ to re-apply the 14/16 cascade. The legacy
|
||||
NumberInput already does this — match its pattern.
|
||||
- **Section headers** (`BoxedList` uppercase title): use `text-[0.85em]
|
||||
font-semibold uppercase tracking-wider`. The em-relative size keeps it
|
||||
proportional with the `.settings-content` cascade. **Caseless-script
|
||||
exception:** when `isCaselessUILang()` is true, bump to `text-[1em]`.
|
||||
The `uppercase` rule is a no-op in scripts without case (CJK, Arabic,
|
||||
Hebrew, Devanagari/Bengali/Tamil/Sinhala, Thai, Tibetan), so the size
|
||||
has to carry the emphasis those scripts can't pick up from casing. The
|
||||
helper lives in `src/utils/misc.ts`; the underlying `isCaselessLang`
|
||||
predicate lists every covered language code in `src/utils/lang.ts`.
|
||||
|
||||
Why this matters: Tailwind's `text-xs` / `text-sm` are rem-based — they
|
||||
ignore the parent's `font-size` because rem is rooted at the document.
|
||||
The `.settings-content` cascade is in `px`, so any child that picks a
|
||||
Tailwind size literally tunes itself to the desktop default and never
|
||||
grows on mobile. iOS and Android have small physical screens but high
|
||||
DPI, so the mobile bump is what makes the text legible at typical reading
|
||||
distance.
|
||||
|
||||
#### Uniform row height
|
||||
|
||||
Settings rows in a boxed list MUST all be the same visual height. Use
|
||||
`min-h-14 items-center` (56px) on each row container — toggle, select, and
|
||||
input rows then center their controls vertically inside identical boxes.
|
||||
**Don't use `py-3`** — content-driven padding produces uneven heights
|
||||
because toggles, selects (`h-9`), and inputs (`h-9`) have different
|
||||
intrinsic sizes.
|
||||
|
||||
```tsx
|
||||
// ✓ Right — no text-sm; label inherits .settings-content (14/16)
|
||||
<label className='flex min-h-14 items-center justify-between px-4'>
|
||||
<span className='font-medium'>{_('Sync Enabled')}</span>
|
||||
<input type='checkbox' className='toggle' ... />
|
||||
</label>
|
||||
|
||||
// ✗ Wrong — toggle row will be 48px, select rows 60px
|
||||
<label className='flex items-center justify-between px-4 py-3'>...</label>
|
||||
|
||||
// ✗ Wrong — text-sm hardcodes 14px even on mobile (kills the bump)
|
||||
<span className='text-sm font-medium'>{_('Sync Enabled')}</span>
|
||||
```
|
||||
|
||||
#### Controls inside a boxed list have no chrome
|
||||
|
||||
When a control sits inside a bordered card, it shouldn't carry its own
|
||||
border or fill. The card supplies the visual boundary; the control just
|
||||
sits on the row.
|
||||
|
||||
- **Selects:** drop `select-bordered` and `eink-bordered`. Add
|
||||
`!bg-transparent !bg-none !appearance-none` to suppress daisyui's
|
||||
background chevron and native arrow. Render a real `<MdArrowDropDown>`
|
||||
icon at the cell's trailing edge for the affordance — see "End-aligned
|
||||
values" below.
|
||||
- **Inputs:** drop `input-bordered` and `eink-bordered`. Add `!bg-transparent`
|
||||
with `hover:!bg-base-200/60 focus:!bg-base-200/60` so the field still
|
||||
signals interactability. Use `text-end` and `!pe-0` so the value sits
|
||||
flush against the row's trailing edge.
|
||||
- **Toggles:** untouched — they're already chromeless.
|
||||
|
||||
This is the iOS Settings / Adwaita PreferencesGroup convention: list
|
||||
chrome belongs to the container, not its children.
|
||||
|
||||
#### End-aligned values + chevron alignment
|
||||
|
||||
The selected value of a select/input MUST end-align (`text-end`). The
|
||||
**visible right edge** of every row's value (toggle, chevron icon, input
|
||||
text) MUST land at the same X — the row's trailing padding.
|
||||
|
||||
The trap: daisyui's select renders its chevron via background-image at
|
||||
`calc(100% - 1rem) center`, which floats the glyph 16px _inside_ the
|
||||
select's right edge. So if the toggle in row 1 ends at the row's `pe-4`
|
||||
edge, the chevron in row 2 ends 16px before that — visibly misaligned.
|
||||
|
||||
**Fix:** suppress daisyui's bg-image chevron and render an explicit icon at
|
||||
the cell's trailing edge. The select's own daisyui focus chrome (outline +
|
||||
box-shadow + ring) is suppressed; **no focus ring** on controls inside the
|
||||
boxed list — focus state is signaled by a subtle wrapper bg-shift instead
|
||||
(hover and focus-within both lift to `bg-base-200/60`). Rings would compete
|
||||
with the card's own border and double-stack with adjacent rows.
|
||||
|
||||
```tsx
|
||||
<div className='hover:bg-base-200/60 focus-within:bg-base-200/60 flex max-w-[60%] items-center rounded-md'>
|
||||
<select className='select h-9 min-w-0 cursor-pointer !appearance-none truncate !border-0 !bg-transparent !bg-none !pe-1 !ps-2 text-end text-sm focus:!border-0 focus:!shadow-none focus:!outline-none focus:!ring-0'>
|
||||
{/* options */}
|
||||
</select>
|
||||
<MdArrowDropDown
|
||||
aria-hidden='true'
|
||||
className='text-base-content/55 pointer-events-none h-5 w-5 flex-shrink-0'
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
> **Why so many `!` overrides?** daisyui's `.select` and `.input` apply
|
||||
> `border-width: 1px` + `border-color` (transparent at rest, `var(--bc)` on
|
||||
> focus), plus `outline`, `box-shadow`, and `ring` chrome on focus. To make
|
||||
> the control truly chromeless inside a boxed list, you need to kill all
|
||||
> four properties. Missing any of them — especially `border-0` — leaves a
|
||||
> visible focus border leaking through.
|
||||
|
||||
The `<MdArrowDropDown>` icon's trailing edge now lives at the same X as the
|
||||
toggle's trailing edge in adjacent rows, because both are flush with the
|
||||
row's `pe-4` padding.
|
||||
|
||||
For inputs, no wrapper is needed — the input is one element, so put the
|
||||
hover/focus bg directly on it. Suppress daisyui's own focus chrome the
|
||||
same way:
|
||||
|
||||
```tsx
|
||||
<input className='input hover:!bg-base-200/60 focus:!bg-base-200/60 h-9 max-w-[60%] rounded-md !border-0 !bg-transparent !pe-0 !ps-2 text-end text-sm focus:!border-0 focus:!shadow-none focus:!outline-none focus:!ring-0' />
|
||||
```
|
||||
|
||||
> **Why no ring here when §2.7 says "focus needs a visible ring"?** §2.7 is
|
||||
> for standalone custom buttons (Submit, Cancel, ListExtension, etc.). In a
|
||||
> boxed list, the row already provides strong visual containment via the
|
||||
> card border + dividers, and stacking a per-control ring inside that
|
||||
> creates double chrome. The bg-shift IS the focus indicator — keyboard
|
||||
> users still get clear feedback; the surface stays calm.
|
||||
|
||||
---
|
||||
|
||||
### 6. Header bars, dialogs, popups, sheets
|
||||
|
||||
#### Header bar
|
||||
|
||||
The dialog/page header. Adwaita's AdwHeaderBar.
|
||||
|
||||
- **48–56px tall** (`h-12` to `h-14`).
|
||||
- **Center-aligned title** in `font-semibold text-base`.
|
||||
- **Leading slot**: back chevron (mobile) or empty (desktop).
|
||||
- **Trailing slot**: window controls — search (pill ghost), menu (pill ghost),
|
||||
close (pill ghost circle with `bg-base-300/65`).
|
||||
- No bottom border; rely on tab/divider that follows.
|
||||
|
||||
`SettingsDialog.tsx`'s mobile header is the canonical example. The desktop header is
|
||||
slightly different — tabs sit in the same row as window controls, no center title — but
|
||||
it's the same archetype adapted for screen real estate.
|
||||
|
||||
#### Dialog (modal)
|
||||
|
||||
```tsx
|
||||
<Dialog
|
||||
isOpen={...}
|
||||
onClose={...}
|
||||
boxClassName="sm:min-w-[520px] overflow-hidden"
|
||||
header={<HeaderBar />}
|
||||
>
|
||||
{/* content */}
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
- `modal-box` provides the radius, max-width, and shadow (auto-removed in eink).
|
||||
- Width ~520px on desktop, full-width on mobile.
|
||||
- Bottom sheets on mobile via `snapHeight` prop.
|
||||
- Backdrop: `sm:!bg-black/50` (or `/20` when nested over a darker surface).
|
||||
|
||||
#### Popup (popover)
|
||||
|
||||
For dictionary lookups, annotation editors, and other anchored overlays. Uses the
|
||||
`Popup` component with a triangle pointer.
|
||||
|
||||
- **Width**: clamp to fit content; ~320–420px typical.
|
||||
- **Surface**: `bg-base-100`, `rounded-lg`, soft shadow (eink removes shadow).
|
||||
- **Triangle**: pointer toward the anchor; eink has special triangle classes.
|
||||
- **Padding**: `p-3` to `p-4` for content.
|
||||
|
||||
#### Sheet (mobile bottom)
|
||||
|
||||
Reserved for mobile contextual menus and full-screen secondary panels. Uses the dialog's
|
||||
`snapHeight` prop. Adwaita doesn't have a native sheet but Readest's mobile pattern is
|
||||
the closest analog.
|
||||
|
||||
- Always full-width.
|
||||
- Top corners rounded; bottom corners flat (it's anchored to the bottom).
|
||||
- Drag handle at top (the small horizontal pill) is mandatory if the sheet supports
|
||||
swipe-to-dismiss.
|
||||
|
||||
---
|
||||
|
||||
### 7. Motion + a11y
|
||||
|
||||
#### Motion
|
||||
|
||||
- Default duration: **150ms** for color transitions.
|
||||
- Default easing: browser default (`ease`) or `ease-out`. Never `ease-in`.
|
||||
- Longer transitions (300ms+) only for layout changes (sheet snap, panel slide).
|
||||
- **Never** use `transform` for hover unless the transform IS the message
|
||||
(chevron rotation, drag-handle drag visualization). E-ink doesn't render mid-transitions
|
||||
cleanly and Adwaita's identity is calm.
|
||||
|
||||
```tsx
|
||||
// Good — hover:bg-base-200 with transition-colors
|
||||
className = 'transition-colors duration-150 hover:bg-base-200';
|
||||
|
||||
// Bad — scale on hover
|
||||
className = 'transition-transform hover:scale-105';
|
||||
```
|
||||
|
||||
Existing exceptions: `.window-button` in globals.css uses `hover:scale-105`. That's
|
||||
legacy; new code shouldn't follow it.
|
||||
|
||||
#### Reduced motion
|
||||
|
||||
Reduced-motion preference is honored via the `no-transitions` class
|
||||
(`globals.css:624`). Layout-changing transitions should respect
|
||||
`prefers-reduced-motion: reduce` either via this class or `motion-safe:` Tailwind
|
||||
prefixes.
|
||||
|
||||
#### Focus
|
||||
|
||||
- Every focusable element must have a visible focus indicator.
|
||||
- Custom buttons:
|
||||
`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-base-content/15`.
|
||||
- Inputs: rely on daisyui's input focus ring; inputs with custom styling use
|
||||
`focus:ring-2 focus:ring-primary/40`.
|
||||
- Don't use `outline-none` without `focus-visible:` replacement.
|
||||
|
||||
#### Hit targets
|
||||
|
||||
- **Minimum**: 32px (the size of `btn-sm`).
|
||||
- **Recommended**: 40px (`btn`) on touch surfaces.
|
||||
- **Mobile**: 44px+ for taps that aren't fail-safe (delete, navigate-away).
|
||||
- The `touch-target` class in globals.css extends a small visual control's hit area to
|
||||
44px without changing its rendered size — use it on icon-sized buttons in mobile UIs.
|
||||
|
||||
#### Color contrast
|
||||
|
||||
- Body text on background: WCAG AA (4.5:1) minimum.
|
||||
- Large text: WCAG AA Large (3:1) minimum.
|
||||
- Interactive text on hover state: still passes contrast on the new background.
|
||||
- Theme palette is generated from `(bg, fg, primary)`; the tinycolor pipeline keeps
|
||||
contrast within range, but custom themes can break this — Settings → Color flags
|
||||
low-contrast custom themes.
|
||||
|
||||
#### Keyboard
|
||||
|
||||
- Tab order matches visual order. If you use `flex-row-reverse` for visual layout,
|
||||
consider `tabIndex` to fix order.
|
||||
- Modal focus trap: `<Dialog>` handles this.
|
||||
- Esc to dismiss: `<Dialog>` and `<Popup>` handle this.
|
||||
- Arrow keys for grouped controls (radio-like tab strips, sortable lists). dnd-kit's
|
||||
`KeyboardSensor` is wired for sortable lists.
|
||||
|
||||
---
|
||||
|
||||
### 8. E-ink overlay (cross-cutting)
|
||||
|
||||
E-ink mode is toggled by `[data-eink='true']` on the document. It applies a global
|
||||
override layer in `src/styles/globals.css:484-622` that:
|
||||
|
||||
- Removes all `box-shadow`.
|
||||
- Forces `text-base-content`, `text-blue-*`, `text-red-*`, `text-neutral-content` to a
|
||||
single foreground color.
|
||||
- Inverts `btn-primary` and `btn-outline` to base-content bg + base-100 text.
|
||||
- Adds 1px contrast borders to `.eink-bordered`, `.modal-box`, `.menu-container`,
|
||||
`.popup-container`, `.alert`, `.opds-navigation .card`, `.booknote-item`,
|
||||
`.bookitem-main`.
|
||||
|
||||
What this means for new components:
|
||||
|
||||
| Surface type | Required class | Why |
|
||||
| ------------------------------------ | ----------------------- | ------------------------------------------------------------- |
|
||||
| Custom bordered button or input | `eink-bordered` | Gets the 1px contrast border in eink |
|
||||
| Primary CTA | `btn-primary` | Picks up the inverted treatment |
|
||||
| Cancel / secondary action | `btn-ghost` (no border) | Reads as "outlined" only after pairing with the CTA |
|
||||
| Card / panel using `border-base-200` | `eink-bordered` | Otherwise the soft border vanishes in eink |
|
||||
| Modal / Popup | (auto) | `modal-box` and `.popup-container` are handled in globals.css |
|
||||
|
||||
Verification checklist before shipping a new UI:
|
||||
|
||||
- [ ] Toggle Settings → Misc → Eink mode and re-test every screen.
|
||||
- [ ] Every container that has a soft border (`border-base-200`) still has visible
|
||||
delineation.
|
||||
- [ ] Every CTA is distinguishable from its neighbors (cancel, secondary).
|
||||
- [ ] No hover transforms make the UI feel jumpy.
|
||||
- [ ] Text is fully opaque (no `text-base-content/60` content; eink can't render the
|
||||
reduced opacity well).
|
||||
|
||||
#### What's NOT compatible with e-ink
|
||||
|
||||
- Drop shadows for hierarchy (use borders).
|
||||
- Color-only state changes (use border weight or fill swap).
|
||||
- Hover scale / translate (they look broken on slow refresh).
|
||||
- Animations longer than ~200ms (visible refresh artifacts).
|
||||
|
||||
---
|
||||
|
||||
### 9. Cross-platform grace notes
|
||||
|
||||
Readest ships on **macOS, Windows, Linux, iOS, Android, web**. Adwaita is desktop-GNOME-
|
||||
native; we adapt where the host OS has strong conventions, but never at the cost of
|
||||
identity.
|
||||
|
||||
#### iOS
|
||||
|
||||
- Slightly larger corner radii feel native (`rounded-xl` on dialogs, `rounded-lg` on
|
||||
cards).
|
||||
- Safe area insets are mandatory for top + bottom anchored elements (see
|
||||
`docs/safe-area-insets.md`).
|
||||
- Avoid Material Design ripple effects.
|
||||
- Sheet-style modals (bottom-anchored) match iOS conventions and are preferred over
|
||||
centered dialogs on phone-sized screens.
|
||||
|
||||
#### Android
|
||||
|
||||
- Material 3 conventions that conflict with Adwaita (FABs, elevation shadows, ripple
|
||||
inks): **don't** copy them. Readest's identity is Adwaita; the user is reading on
|
||||
Android, not in Android.
|
||||
- Touch targets bumped to 48px for primary actions (Material's recommended target).
|
||||
- Back-gesture-aware UIs: ensure swipe-from-edge doesn't conflict with horizontal swipe
|
||||
controls.
|
||||
|
||||
#### Linux
|
||||
|
||||
- Native Adwaita territory. Readest can match host theme for window chrome (Tauri
|
||||
decorations) but should keep its own internal palette for the reading surface — book
|
||||
themes (sepia, gruvbox, etc.) are user choices, not OS choices.
|
||||
|
||||
#### macOS / Windows
|
||||
|
||||
- Window controls (close/minimize/maximize) are platform-native via Tauri.
|
||||
- Title bar height matches platform convention; internal layout follows Readest's
|
||||
Adwaita palette.
|
||||
|
||||
#### Web
|
||||
|
||||
- No safe-area insets needed.
|
||||
- Keyboard shortcuts are doubled with command-palette discoverability (Cmd/Ctrl+K).
|
||||
- Browser-native focus rings: respected, augmented with `focus-visible:ring-*`.
|
||||
|
||||
#### E-ink readers (Android-based, custom firmware)
|
||||
|
||||
- Detected via the eink mode toggle (Settings → Misc).
|
||||
- All rules in §8 apply.
|
||||
- This is a **first-class** target, not a fallback.
|
||||
|
||||
---
|
||||
|
||||
### 10. Anti-patterns
|
||||
|
||||
Things that LOOK fine in isolation but break the system. Each one has a real source diff
|
||||
or commit reference.
|
||||
|
||||
#### 10.1 Loud outlined CTAs for non-primary actions
|
||||
|
||||
```tsx
|
||||
// Anti-pattern (was in CustomDictionaries.tsx, fixed Nov 2026):
|
||||
<button className='btn btn-outline btn-primary gap-2 normal-case [--animation-btn:0s]'>
|
||||
<MdAdd className='h-5 w-5' />
|
||||
Import Dictionary
|
||||
</button>
|
||||
|
||||
// Correct: ListExtension archetype (see §4.6)
|
||||
```
|
||||
|
||||
Why it broke: the buttons read as primary CTAs but are list extensions. They competed
|
||||
with the active settings tab indicator and pulled the eye from the list itself.
|
||||
|
||||
#### 10.2 Recoloring the whole button on hover
|
||||
|
||||
```tsx
|
||||
// Anti-pattern:
|
||||
<button className="text-base-content/70 hover:text-base-content hover:bg-primary/10">
|
||||
|
||||
// Correct: keep the label color steady, hover via bg shift on the surface
|
||||
<button className="text-base-content hover:bg-base-200 transition-colors">
|
||||
```
|
||||
|
||||
Why: principle 2.4 (localize the hover signal). Whole-button color shifts feel decorative.
|
||||
|
||||
#### 10.3 Transform-based hover
|
||||
|
||||
```tsx
|
||||
// Anti-pattern:
|
||||
<button className="hover:scale-105 transition-transform">
|
||||
|
||||
// Correct: color/border-based hover
|
||||
<button className="hover:bg-base-200 hover:border-base-300 transition-colors">
|
||||
```
|
||||
|
||||
Why: breaks under e-ink (§2.5), feels jumpy under Adwaita's calm rhythm.
|
||||
|
||||
#### 10.4 Soft borders without `eink-bordered`
|
||||
|
||||
```tsx
|
||||
// Anti-pattern:
|
||||
<div className="border border-base-200 bg-base-100 rounded-lg p-4">
|
||||
...
|
||||
</div>
|
||||
|
||||
// Correct:
|
||||
<div className="eink-bordered border border-base-200 bg-base-100 rounded-lg p-4">
|
||||
...
|
||||
</div>
|
||||
```
|
||||
|
||||
Why: in e-ink mode, `base-200` borders disappear into the background. `eink-bordered`
|
||||
flips the border to `base-content` so the boundary stays visible.
|
||||
|
||||
Exception: containers that **don't** need a visible boundary in eink (e.g., a
|
||||
`bg-base-100` surface that's already against `bg-base-200`) can skip `eink-bordered`.
|
||||
The class is opt-in for "this surface needs a border to read correctly".
|
||||
|
||||
#### 10.5 Reduced-opacity text in e-ink
|
||||
|
||||
```tsx
|
||||
// Anti-pattern (in eink):
|
||||
<span className="text-base-content/50">Optional metadata</span>
|
||||
|
||||
// Correct (still readable in eink):
|
||||
<span className="text-base-content text-xs">Optional metadata</span>
|
||||
// Or use semantic muting that the eink overlay handles:
|
||||
<span className="text-neutral-content">Optional metadata</span>
|
||||
```
|
||||
|
||||
Why: e-ink's reduced color depth turns `/50` opacity into illegible mush. Use size or
|
||||
weight for hierarchy on muted secondary text.
|
||||
|
||||
#### 10.6 Daisyui `btn` defaults without intent
|
||||
|
||||
```tsx
|
||||
// Anti-pattern: just reaching for `btn` with no role:
|
||||
<button className="btn">Click me</button>
|
||||
|
||||
// Correct: pick an archetype from §4.
|
||||
<button className="btn btn-ghost">Cancel</button> // Flat
|
||||
<button className="btn btn-primary">Save</button> // Accent CTA
|
||||
```
|
||||
|
||||
Why: daisyui's `btn` default isn't tuned for any specific role. Pick from the action
|
||||
vocabulary so the button signals its weight in the surface hierarchy.
|
||||
|
||||
#### 10.7 Ad-hoc surface tokens
|
||||
|
||||
```tsx
|
||||
// Anti-pattern:
|
||||
<div className="bg-white border-gray-200">
|
||||
|
||||
// Correct:
|
||||
<div className="bg-base-100 border-base-200">
|
||||
```
|
||||
|
||||
Why: hard-coded colors don't theme. Readest has 11 themes plus user-defined custom themes.
|
||||
Always use the daisyui semantic tokens.
|
||||
|
||||
#### 10.8 Mixing `btn` sizes within a surface
|
||||
|
||||
```tsx
|
||||
// Anti-pattern:
|
||||
<header>
|
||||
<button className="btn btn-sm">Search</button>
|
||||
<button className="btn btn-md">Settings</button>
|
||||
<button className="btn btn-xs">Close</button>
|
||||
</header>
|
||||
|
||||
// Correct: one size per surface
|
||||
<header>
|
||||
<button className="btn btn-ghost btn-circle h-8 min-h-8 w-8">Search</button>
|
||||
<button className="btn btn-ghost btn-circle h-8 min-h-8 w-8">Settings</button>
|
||||
<button className="btn btn-ghost btn-circle h-8 min-h-8 w-8">Close</button>
|
||||
</header>
|
||||
```
|
||||
|
||||
Why: visual rhythm. Mixed sizes feel like the surface is unfinished.
|
||||
|
||||
---
|
||||
|
||||
### 11. Quick reference
|
||||
|
||||
When designing a new surface, walk this checklist:
|
||||
|
||||
1. **What's the surface tier?** Window / View / Card. (§3)
|
||||
2. **What's the corner radius?** Match the tier. (§3)
|
||||
3. **Is there a primary action?** If yes, ONE accent CTA. If no, all flats. (§4.1, §4.3)
|
||||
4. **Are there list extensions?** Use the ListExtension archetype, not `btn-outline btn-primary`. (§4.6)
|
||||
5. **Is it a list?** Use the BoxedList chassis with ActionRow / SwitchRow / ComboRow / ExpanderRow rows. (§5)
|
||||
6. **Does it need `eink-bordered`?** If it has a soft border that must stay visible in
|
||||
eink mode, yes. (§8)
|
||||
7. **Is the hover signal localized?** One focal element changes, not the whole control. (§2.4)
|
||||
8. **Is motion color-only?** No transforms unless the transform IS the message. (§2.5)
|
||||
9. **Is focus visible?** `focus-visible:ring-2 focus-visible:ring-base-content/15` on
|
||||
custom buttons. (§7)
|
||||
10. **Will it work on the smallest theme + e-ink?** Toggle Sepia + Eink, retest.
|
||||
|
||||
---
|
||||
|
||||
### 12. Glossary
|
||||
|
||||
- **Adwaita / libadwaita**: GNOME's design system and widget toolkit. Source of Readest's
|
||||
visual lineage.
|
||||
- **AdwActionRow / AdwSwitchRow / AdwComboRow / AdwExpanderRow**: libadwaita's row
|
||||
primitives. Readest mirrors these conceptually with custom React components.
|
||||
- **AdwBoxedList**: libadwaita's named container for grouped action rows.
|
||||
- **AdwBanner**: top-of-window inline alert (persistent).
|
||||
- **AdwToast**: bottom slide-in transient alert.
|
||||
- **Window / View / Card**: surface tiers (§3).
|
||||
- **ListExtension**: Readest-named archetype for "+ add new row" buttons (§4.6).
|
||||
- **eink-bordered**: utility class in `globals.css` that gives a surface its e-ink-mode
|
||||
contrast border. Opt-in.
|
||||
- **Pill ghost**: circular icon button, `btn-ghost btn-circle`.
|
||||
|
||||
---
|
||||
|
||||
### 13. Maintenance
|
||||
|
||||
This doc is the **source of truth** for new design decisions. When the system grows:
|
||||
|
||||
- New archetypes get a numbered subsection in §4 or §5.
|
||||
- New anti-patterns get added to §10 with a real source reference.
|
||||
- Updates to existing principles require a brief why-changed note in the relevant section.
|
||||
|
||||
Cross-references that must stay in sync:
|
||||
|
||||
- `CLAUDE.md` E-ink mode section → §8 of this doc.
|
||||
- `docs/safe-area-insets.md` → §9 (cross-platform).
|
||||
- `src/styles/globals.css` `[data-eink]` rules → §8.
|
||||
- `src/styles/themes.ts` Palette type → §3 token table.
|
||||
|
||||
If you change a rule here, search for the cross-reference and update both.
|
||||
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.9/schema.json",
|
||||
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
|
||||
"files": { "ignoreUnknown": true },
|
||||
"formatter": { "enabled": false },
|
||||
"assist": { "enabled": false },
|
||||
"css": { "linter": { "enabled": false } },
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"includes": [
|
||||
"**",
|
||||
"!.next/**",
|
||||
"!.open-next/**",
|
||||
"!.wrangler/**",
|
||||
"!.claude/**",
|
||||
"!dist/**",
|
||||
"!out/**",
|
||||
"!build/**",
|
||||
"!public/**",
|
||||
"!src-tauri/**",
|
||||
"!next-env.d.ts",
|
||||
"!i18next-scanner.config.cjs"
|
||||
],
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"a11y": {
|
||||
"recommended": true,
|
||||
"useKeyWithClickEvents": "off",
|
||||
"useKeyWithMouseEvents": "off",
|
||||
"noSvgWithoutTitle": "off",
|
||||
"noLabelWithoutControl": "off",
|
||||
"useSemanticElements": "off",
|
||||
"noAriaHiddenOnFocusable": "off",
|
||||
"noInteractiveElementToNoninteractiveRole": "off",
|
||||
"noNoninteractiveElementToInteractiveRole": "off",
|
||||
"noNoninteractiveElementInteractions": "off",
|
||||
"noStaticElementInteractions": "off",
|
||||
"noNoninteractiveTabindex": "off",
|
||||
"useButtonType": "off",
|
||||
"useAriaPropsSupportedByRole": "off",
|
||||
"useFocusableInteractive": "off",
|
||||
"noAutofocus": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"noForEach": "off",
|
||||
"noStaticOnlyClass": "off",
|
||||
"noUselessSwitchCase": "off",
|
||||
"noUselessFragments": "off",
|
||||
"noUselessCatch": "off",
|
||||
"useLiteralKeys": "off",
|
||||
"useOptionalChain": "off",
|
||||
"noThisInStatic": "off",
|
||||
"useArrowFunction": "off",
|
||||
"noUselessEscapeInRegex": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedVariables": "warn",
|
||||
"noUnusedImports": "error",
|
||||
"useExhaustiveDependencies": "off",
|
||||
"useHookAtTopLevel": "error",
|
||||
"useJsxKeyInIterable": "error",
|
||||
"noChildrenProp": "error",
|
||||
"noNextAsyncClientComponent": "warn",
|
||||
"noSwitchDeclarations": "off",
|
||||
"noUndeclaredVariables": "off",
|
||||
"noEmptyCharacterClassInRegex": "off",
|
||||
"useParseIntRadix": "off",
|
||||
"noEmptyPattern": "off"
|
||||
},
|
||||
"nursery": {
|
||||
"noBeforeInteractiveScriptOutsideDocument": "warn",
|
||||
"noDuplicateEnumValues": "error",
|
||||
"noSyncScripts": "warn",
|
||||
"useInlineScriptId": "error"
|
||||
},
|
||||
"performance": {
|
||||
"noImgElement": "off",
|
||||
"noUnwantedPolyfillio": "warn",
|
||||
"useGoogleFontPreconnect": "warn"
|
||||
},
|
||||
"security": {
|
||||
"noBlankTarget": "off",
|
||||
"noDangerouslySetInnerHtmlWithChildren": "off",
|
||||
"noDangerouslySetInnerHtml": "off"
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "off",
|
||||
"useImportType": "off",
|
||||
"noParameterAssign": "off",
|
||||
"useDefaultParameterLast": "off",
|
||||
"noUselessElse": "off",
|
||||
"noHeadElement": "warn",
|
||||
"noCommonJs": "off",
|
||||
"useFilenamingConvention": "off",
|
||||
"useNamingConvention": "off",
|
||||
"noUnusedTemplateLiteral": "off",
|
||||
"useTemplate": "off",
|
||||
"useExponentiationOperator": "off",
|
||||
"useNodejsImportProtocol": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "error",
|
||||
"noArrayIndexKey": "off",
|
||||
"noAssignInExpressions": "off",
|
||||
"noDoubleEquals": "off",
|
||||
"noDocumentImportInPage": "error",
|
||||
"noHeadImportInDocument": "error",
|
||||
"useGoogleFontDisplay": "warn",
|
||||
"noCommentText": "error",
|
||||
"noDuplicateJsxProps": "error",
|
||||
"noAsyncPromiseExecutor": "off",
|
||||
"noImplicitAnyLet": "off",
|
||||
"noControlCharactersInRegex": "off",
|
||||
"noEmptyBlockStatements": "off",
|
||||
"useIterableCallbackReturn": "off",
|
||||
"noGlobalIsNan": "off",
|
||||
"noConfusingVoidType": "off",
|
||||
"noConstEnum": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"globals": ["React"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/styles/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/libs",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"registries": {}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
## i18n Guide
|
||||
|
||||
Readest uses a **key-as-content** approach — English strings are the translation keys. The English locale (`en/translation.json`) is empty because keys serve as content. Other locales contain actual translations.
|
||||
|
||||
### In React Components
|
||||
|
||||
```tsx
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
const _ = useTranslation();
|
||||
_('Progress synced');
|
||||
```
|
||||
|
||||
### In Non-React Modules
|
||||
|
||||
Two-step process:
|
||||
|
||||
**1. Declaration** — Use `stubTranslation` to mark strings for scanner extraction (returns key as-is, does NOT translate):
|
||||
|
||||
```ts
|
||||
import { stubTranslation as _ } from '@/utils/misc';
|
||||
|
||||
// These calls only register keys for extraction
|
||||
_('Reveal in Finder');
|
||||
_('Reveal in Explorer');
|
||||
```
|
||||
|
||||
**2. Usage** — In the React component that consumes the value, apply the real `_()` from `useTranslation`:
|
||||
|
||||
```tsx
|
||||
const _ = useTranslation();
|
||||
const label = _(getRevealLabel()); // translates at runtime
|
||||
```
|
||||
|
||||
### Extraction & Translation
|
||||
|
||||
```bash
|
||||
pnpm i18n:extract # Scans codebase, adds new keys with __STRING_NOT_TRANSLATED__
|
||||
```
|
||||
|
||||
- Translation files: `public/locales/<locale>/translation.json`
|
||||
- Only `_('KEY')` and `_('KEY', options)` patterns are recognized by i18next-scanner
|
||||
|
||||
### Adding a New Translation Language
|
||||
|
||||
The supported language set has a single ground truth: [`i18n-langs.json`](../i18n-langs.json). Both the i18next runtime (`src/i18n/i18n.ts`) and the extractor (`i18next-scanner.config.cjs`) read from it, so adding a locale is a two-file change plus a translation pass.
|
||||
|
||||
1. **Add the locale code** to [`i18n-langs.json`](../i18n-langs.json). Use the exact code i18next will emit (e.g. `hu`, `zh-CN`). Do not add `en` — it's the source language and lives outside this list.
|
||||
|
||||
2. **Add a display label** to `TRANSLATED_LANGS` in [`src/services/constants.ts`](../src/services/constants.ts). The key is the locale code, the value is the language's native name (e.g. `hu: 'Magyar'`). This is what users see in the language picker.
|
||||
|
||||
3. **Generate the translation file**:
|
||||
|
||||
```bash
|
||||
pnpm i18n:extract
|
||||
```
|
||||
|
||||
This creates `public/locales/<code>/translation.json` with every key set to `__STRING_NOT_TRANSLATED__`.
|
||||
|
||||
4. **Translate** every `__STRING_NOT_TRANSLATED__` placeholder in the new file. The `/i18n` skill automates this; the singular `en/translation.json` only holds plural variants and proper nouns, so use the JSON keys themselves as the English source.
|
||||
|
||||
5. **Verify** with `grep -r "__STRING_NOT_TRANSLATED__" public/locales/<code>/` — the result should be empty.
|
||||
|
||||
6. **Translate the KOReader companion plugin** (`apps/readest.koplugin`). It pulls the locale set from the same `i18n-langs.json` via the scanner config, but the catalog format is gettext `.po`, not JSON. Steps:
|
||||
- Add a `LANG_META` entry (label + Plural-Forms) for the new code in [`apps/readest.koplugin/scripts/extract-i18n.js`](../../readest.koplugin/scripts/extract-i18n.js). Without it the extractor prints `<code> skipped (no metadata in extract-i18n.js)` and the catalog is never created.
|
||||
- Trigger the `/i18n-koplugin` skill to run extraction and fill every empty `msgstr ""` in `apps/readest.koplugin/locales/<code>/translation.po`.
|
||||
|
||||
### Rules
|
||||
|
||||
- `stubTranslation` is for extraction only — always apply `_()` from `useTranslation` in the component for runtime translation.
|
||||
- Fallback: when no translation exists, the English key itself is displayed.
|
||||
- Error messages: register keys with `stubTranslation` in utility modules (e.g. `src/services/errors.ts`), return the English key from helpers, wrap with `_()` in the component.
|
||||
@@ -0,0 +1,52 @@
|
||||
## Safe Area Insets
|
||||
|
||||
The app runs on devices with notches, status bars, and rounded corners (iOS, Android). UI elements near screen edges must account for safe area insets to avoid being obscured.
|
||||
|
||||
### Key Concepts
|
||||
|
||||
- **`gridInsets: Insets`** — Per-view insets derived from view settings (header/footer visibility, margins). Calculated by `getViewInsets()` in `src/utils/insets.ts`. Passed as a prop from `BooksGrid` → child components.
|
||||
- **`statusBarHeight: number`** — OS status bar height (default 24px). Stored in `themeStore`.
|
||||
- **`systemUIVisible: boolean`** — Whether the system UI (status bar, navigation bar) is currently shown. Stored in `themeStore`.
|
||||
- **`appService?.hasSafeAreaInset`** — Whether the platform requires safe area handling (mobile devices).
|
||||
|
||||
### Top Inset Rules
|
||||
|
||||
For UI elements anchored to the **top** of the screen (headers, close buttons, overlays):
|
||||
|
||||
```tsx
|
||||
// When system UI is visible, use the larger of gridInsets.top and statusBarHeight
|
||||
// When system UI is hidden, use gridInsets.top alone
|
||||
style={{
|
||||
marginTop: systemUIVisible
|
||||
? `${Math.max(gridInsets.top, statusBarHeight)}px`
|
||||
: `${gridInsets.top}px`,
|
||||
}}
|
||||
```
|
||||
|
||||
For containers that need safe area padding at the top:
|
||||
|
||||
```tsx
|
||||
style={{
|
||||
paddingTop: appService?.hasSafeAreaInset ? `${gridInsets.top}px` : '0px',
|
||||
}}
|
||||
```
|
||||
|
||||
### Bottom Inset Rules
|
||||
|
||||
For UI elements anchored to the **bottom** of the screen (footer bars, controls, progress indicators), use `gridInsets.bottom * 0.33` as padding — a fraction of the full inset since bottom bars don't need as much clearance as the home indicator area:
|
||||
|
||||
```tsx
|
||||
style={{
|
||||
paddingBottom: appService?.hasSafeAreaInset ? `${gridInsets.bottom * 0.33}px` : 0,
|
||||
}}
|
||||
```
|
||||
|
||||
### Passing `gridInsets`
|
||||
|
||||
When creating overlay components (image viewers, table viewers, zoom controls, etc.), always pass `gridInsets` as a prop so they can position their controls correctly:
|
||||
|
||||
```tsx
|
||||
<ImageViewer gridInsets={gridInsets} ... />
|
||||
<TableViewer gridInsets={gridInsets} ... />
|
||||
<ZoomControls gridInsets={gridInsets} ... />
|
||||
```
|
||||
@@ -0,0 +1,110 @@
|
||||
# Testing
|
||||
|
||||
Readest uses three test tiers, all powered by [Vitest](https://vitest.dev/).
|
||||
|
||||
## Unit Tests (`pnpm test`)
|
||||
|
||||
Runs tests in a **jsdom** environment. No browser or Tauri runtime required.
|
||||
|
||||
```bash
|
||||
pnpm test # Run all unit tests
|
||||
pnpm test -- src/__tests__/utils/misc.test.ts # Run a single file
|
||||
pnpm test -- --watch # Watch mode
|
||||
```
|
||||
|
||||
- **Config:** `vitest.config.mts`
|
||||
- **Pattern:** `src/**/*.test.ts` (excludes `*.browser.test.ts` and `*.tauri.test.ts`)
|
||||
- **Environment:** jsdom
|
||||
- **Use for:** Pure logic, utilities, services that don't need real browser APIs or Tauri IPC.
|
||||
|
||||
## Browser Tests (`pnpm test:browser`)
|
||||
|
||||
Runs tests in a **real Chromium** browser via Playwright. Required for code that depends on Web Workers, SharedArrayBuffer, OPFS, or other browser-only APIs.
|
||||
|
||||
```bash
|
||||
pnpm test:browser
|
||||
```
|
||||
|
||||
- **Config:** `vitest.browser.config.mts`
|
||||
- **Pattern:** `src/**/*.browser.test.ts`
|
||||
- **Browser:** Chromium (headless, via `@vitest/browser-playwright`)
|
||||
- **Use for:** WASM modules (e.g. `@tursodatabase/database-wasm`), Web Worker integration, browser-specific storage APIs.
|
||||
|
||||
## Tauri Integration Tests (`pnpm test:tauri`)
|
||||
|
||||
Runs Vitest tests **inside the Tauri WebView**, with access to Tauri IPC and native plugin commands. Tests execute in the actual app environment.
|
||||
|
||||
### Step 1: Start the Tauri App
|
||||
|
||||
In one terminal, start the app with the `webdriver` feature enabled:
|
||||
|
||||
```bash
|
||||
pnpm tauri:dev:test # Dev mode (uses tauri dev server, faster iteration)
|
||||
pnpm tauri:build:test # Debug release build (closer to production)
|
||||
```
|
||||
|
||||
These commands compile the Rust backend with `--features webdriver`, which:
|
||||
|
||||
- Includes `tauri-plugin-webdriver` (embeds a W3C WebDriver server on port 4445)
|
||||
- Adds a runtime capability granting plugin permissions to remote URLs (`http://127.0.0.1:*`), so Vitest's browser-mode iframe can call Tauri IPC
|
||||
|
||||
Keep this running while you run tests.
|
||||
|
||||
### Step 2: Run Tests
|
||||
|
||||
In another terminal:
|
||||
|
||||
```bash
|
||||
pnpm test:tauri
|
||||
```
|
||||
|
||||
Vitest connects directly to the embedded WebDriver server (port 4445) in the running Tauri app and executes tests inside its WebView.
|
||||
|
||||
- **Config:** `vitest.tauri.config.mts`
|
||||
- **Pattern:** `src/**/*.tauri.test.ts`
|
||||
- **Browser provider:** `@vitest/browser-webdriverio` (connects to port 4445)
|
||||
- **Use for:** Tauri plugin commands (turso, native-tts, etc.), native filesystem, Tauri IPC.
|
||||
|
||||
### Writing Tauri Tests
|
||||
|
||||
Tests access Tauri IPC via a shared helper:
|
||||
|
||||
```typescript
|
||||
import { invoke } from '../tauri/tauri-invoke';
|
||||
|
||||
it('calls a plugin command', async () => {
|
||||
const result = await invoke('plugin:turso|load', { options: { path: 'sqlite::memory:' } });
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
```
|
||||
|
||||
The `invoke()` helper accesses `window.top.__TAURI_INTERNALS__` (Vitest runs in an iframe, Tauri injects IPC into the main frame).
|
||||
|
||||
**Limitations:** Only custom invoke commands and plugin commands listed in the webdriver capability work. Standard Tauri JS APIs (e.g. `@tauri-apps/api`) that rely on `URL: local` may not work from the Vitest iframe.
|
||||
|
||||
## E2E Tests (WDIO)
|
||||
|
||||
Full end-to-end tests using WebDriverIO, for UI-level testing against the running Tauri app. Same two-step workflow as Tauri integration tests.
|
||||
|
||||
```bash
|
||||
# Terminal 1: start the app (same as for Tauri integration tests)
|
||||
pnpm tauri:dev:test
|
||||
|
||||
# Terminal 2: run E2E tests
|
||||
pnpm test:e2e
|
||||
```
|
||||
|
||||
- **Config:** `wdio.conf.ts`
|
||||
- **Pattern:** `e2e/**/*.e2e.ts`
|
||||
- **Framework:** Mocha (via `@wdio/mocha-framework`)
|
||||
- **Connects to:** port 4445 (embedded WebDriver server)
|
||||
- **Use for:** UI interaction tests, window management, navigation flows.
|
||||
|
||||
## Test File Naming
|
||||
|
||||
| Suffix | Runner | Environment |
|
||||
| ------------------- | ------------------- | --------------------- |
|
||||
| `*.test.ts` | `pnpm test` | jsdom |
|
||||
| `*.browser.test.ts` | `pnpm test:browser` | Chromium (Playwright) |
|
||||
| `*.tauri.test.ts` | `pnpm test:tauri` | Tauri WebView |
|
||||
| `*.e2e.ts` | `pnpm test:e2e` | Tauri app (WDIO) |
|
||||
@@ -0,0 +1,209 @@
|
||||
## Adding a Config to `ViewSettings`
|
||||
|
||||
`ViewSettings` is the per-book view state (layout, fonts, colors, TTS, etc.) composed from several sub-interfaces defined in `src/types/book.ts`. A matching `globalViewSettings` lives on `SystemSettings` and acts as the default for every book. The per-book value is derived by merging the global defaults with any overrides stored on the book's `BookConfig`.
|
||||
|
||||
This doc covers how to plumb a new config through the three layers:
|
||||
|
||||
1. **Types** — `src/types/book.ts`
|
||||
2. **Defaults** — `src/services/constants.ts` and `src/services/settingsService.ts`
|
||||
3. **Read/write** — components via `saveViewSettings` from `src/helpers/settings.ts`
|
||||
|
||||
### Pick a Pattern
|
||||
|
||||
**Pattern A — add a field to an existing sub-interface.** Use when the new option belongs to an existing bundle (`BookLayout`, `BookStyle`, `BookFont`, `ViewConfig`, `TTSConfig`, etc.).
|
||||
|
||||
**Pattern B — introduce a new sub-interface.** Use when several related fields cluster together, or when a single field is semantically its own concept (e.g. `ParagraphModeConfig`, `ViewSettingsConfig`). Then extend `ViewSettings` with it.
|
||||
|
||||
Both patterns follow the same three-layer flow. The only difference is whether you reuse an existing `DEFAULT_*` constant or add a new one.
|
||||
|
||||
### Step 1 — Declare the Type
|
||||
|
||||
**Pattern A** — add a required field to the sub-interface that owns this concern:
|
||||
|
||||
```ts
|
||||
// src/types/book.ts
|
||||
export interface ViewConfig {
|
||||
// ...existing fields
|
||||
myNewToggle: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern B** — define a new interface and extend `ViewSettings`:
|
||||
|
||||
```ts
|
||||
// src/types/book.ts
|
||||
export interface ViewSettingsConfig {
|
||||
isGlobal: boolean;
|
||||
}
|
||||
|
||||
export interface ViewSettings
|
||||
extends
|
||||
BookLayout,
|
||||
BookStyle,
|
||||
// ...other bundles
|
||||
ViewSettingsConfig {}
|
||||
```
|
||||
|
||||
Fields should be **required**, not optional. Optional fields make downstream code defensive. Provide a sensible default in Step 2 instead.
|
||||
|
||||
### Step 2 — Provide a Default
|
||||
|
||||
Every field in `ViewSettings` must have a default, otherwise `getDefaultViewSettings()` produces an incomplete object.
|
||||
|
||||
**Pattern A** — add the value to the existing `DEFAULT_*` constant:
|
||||
|
||||
```ts
|
||||
// src/services/constants.ts
|
||||
export const DEFAULT_VIEW_CONFIG: ViewConfig = {
|
||||
// ...existing defaults
|
||||
myNewToggle: false,
|
||||
};
|
||||
```
|
||||
|
||||
**Pattern B** — add a `DEFAULT_*_CONFIG` constant for your new bundle, then register it in `getDefaultViewSettings`:
|
||||
|
||||
```ts
|
||||
// src/services/constants.ts
|
||||
export const DEFAULT_VIEW_SETTINGS_CONFIG: ViewSettingsConfig = {
|
||||
isGlobal: true,
|
||||
};
|
||||
```
|
||||
|
||||
```ts
|
||||
// src/services/settingsService.ts
|
||||
export function getDefaultViewSettings(ctx: Context): ViewSettings {
|
||||
return {
|
||||
...DEFAULT_BOOK_LAYOUT,
|
||||
...DEFAULT_BOOK_STYLE,
|
||||
// ...other bundles
|
||||
...DEFAULT_VIEW_SETTINGS_CONFIG,
|
||||
// platform overrides go last so they win
|
||||
...(ctx.isMobile ? DEFAULT_MOBILE_VIEW_SETTINGS : {}),
|
||||
...(ctx.isEink ? DEFAULT_EINK_VIEW_SETTINGS : {}),
|
||||
...(isCJKEnv() ? DEFAULT_CJK_VIEW_SETTINGS : {}),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### Platform Overrides
|
||||
|
||||
To tweak the default on mobile, e-ink, or CJK locales, add the field to the matching `Partial<ViewSettings>` constant (`DEFAULT_MOBILE_VIEW_SETTINGS`, `DEFAULT_EINK_VIEW_SETTINGS`, `DEFAULT_CJK_VIEW_SETTINGS`). These are spread after the base defaults in `getDefaultViewSettings`, so they override them.
|
||||
|
||||
#### Migration
|
||||
|
||||
Old `settings.json` files on disk won't have your new field. `loadSettings` merges the stored blob over fresh defaults:
|
||||
|
||||
```ts
|
||||
settings.globalViewSettings = {
|
||||
...getDefaultViewSettings(ctx),
|
||||
...settings.globalViewSettings,
|
||||
};
|
||||
```
|
||||
|
||||
So existing users pick up your default automatically — no explicit migration is needed for adding a field. Only bump `SYSTEM_SETTINGS_VERSION` if you are reshaping existing data.
|
||||
|
||||
### Step 3 — Read and Write from Components
|
||||
|
||||
Read the current value by preferring the per-book settings, falling back to the global:
|
||||
|
||||
```tsx
|
||||
const { settings } = useSettingsStore();
|
||||
const { getViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey) || settings.globalViewSettings;
|
||||
```
|
||||
|
||||
Write via `saveViewSettings` — never mutate the store directly. The helper handles the global-vs-per-book routing, persists to disk, and re-applies styles when needed.
|
||||
|
||||
```tsx
|
||||
import { saveViewSettings } from '@/helpers/settings';
|
||||
|
||||
const [myNewToggle, setMyNewToggle] = useState(viewSettings.myNewToggle);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'myNewToggle', myNewToggle);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [myNewToggle]);
|
||||
```
|
||||
|
||||
The `useEffect`-on-local-state pattern is the established convention in `LayoutPanel`, `ControlPanel`, `ColorPanel`, etc. It keeps the UI responsive and batches store updates until the user stops interacting.
|
||||
|
||||
#### Signature
|
||||
|
||||
```ts
|
||||
saveViewSettings<K extends keyof ViewSettings>(
|
||||
envConfig,
|
||||
bookKey,
|
||||
key: K,
|
||||
value: ViewSettings[K],
|
||||
skipGlobal = false, // true → only update this book's settings
|
||||
applyStyles = true, // false → don't re-run style recomputation
|
||||
)
|
||||
```
|
||||
|
||||
**Global vs. per-book routing.** `saveViewSettings` inspects `viewSettings.isGlobal` on the target book. When `true` (the default), it writes to `globalViewSettings`, loops through every open book, and saves to disk. When `false`, it writes only to the one book's config.
|
||||
|
||||
**Skip global.** Pass `skipGlobal=true` when the setting is meta — i.e. it describes the settings system itself, not book content. The canonical case is toggling `isGlobal` from `DialogMenu`: you want the scope flag to live on the specific book without propagating it to every other book.
|
||||
|
||||
```tsx
|
||||
saveViewSettings(envConfig, bookKey, 'isGlobal', !isSettingsGlobal, true, false);
|
||||
```
|
||||
|
||||
**Skip styles.** Pass `applyStyles=false` for options that don't affect CSS rendering (toggles, flags, metadata). This avoids an unnecessary `renderer.setStyles` call.
|
||||
|
||||
### Step 4 — Support Reset
|
||||
|
||||
If your field should be resettable from the panel menu, register a setter in the panel's `handleReset` via `useResetViewSettings`:
|
||||
|
||||
```tsx
|
||||
const resetToDefaults = useResetViewSettings();
|
||||
|
||||
const handleReset = () => {
|
||||
resetToDefaults({
|
||||
myNewToggle: setMyNewToggle,
|
||||
// ...other setters
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
The hook resolves the default by reading from `getDefaultViewSettings(ctx)` and calls each provided setter with that value, which then fires your `useEffect` and persists the change.
|
||||
|
||||
### Step 5 — Register in the Command Palette
|
||||
|
||||
If your setting has a visible row in a panel, register it in the matching `*PanelItems` array in `src/services/commandRegistry.ts`. This wires it into the command-palette fuzzy search so users can jump straight to it.
|
||||
|
||||
```ts
|
||||
// src/services/commandRegistry.ts
|
||||
const layoutPanelItems = [
|
||||
// ...existing entries
|
||||
{
|
||||
id: 'settings.layout.myNewToggle',
|
||||
labelKey: _('My New Toggle'),
|
||||
keywords: ['search', 'terms', 'for', 'discoverability'],
|
||||
section: 'Paragraph',
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
- `id` must match the `data-setting-id` attribute on the panel row. The palette uses it to scroll/highlight the target control.
|
||||
- `labelKey` uses `stubTranslation` (imported as `_`) so the extractor picks it up — the same string that appears in the panel.
|
||||
- `keywords` broadens fuzzy-search hits beyond the label; include synonyms, related jargon, and the panel section name.
|
||||
- `section` groups the entry in the palette results (matches the panel's sub-header: `Layout`, `Paragraph`, `Page`, `Header & Footer`, etc.).
|
||||
|
||||
Skip this step only for settings that don't surface as a user-visible row (hidden toggles, flags used by other settings).
|
||||
|
||||
### Don'ts
|
||||
|
||||
- **Don't make the field optional** just to skip providing a default. Add a default in Step 2 instead.
|
||||
- **Don't mutate `settings.globalViewSettings` directly** in a component — `saveViewSettings` already handles global propagation when `isGlobal` is true.
|
||||
- **Don't bump `SYSTEM_SETTINGS_VERSION`** for a plain additive field. The load-time merge handles it.
|
||||
|
||||
### Minimal Checklist
|
||||
|
||||
- [ ] Field or new interface added in `src/types/book.ts`
|
||||
- [ ] Default value in `src/services/constants.ts`
|
||||
- [ ] New `DEFAULT_*_CONFIG` spread into `getDefaultViewSettings` (Pattern B only)
|
||||
- [ ] Optional mobile/eink/CJK override in the matching `Partial<ViewSettings>` constant
|
||||
- [ ] Read via `getViewSettings(bookKey) || settings.globalViewSettings`
|
||||
- [ ] Write via `saveViewSettings(envConfig, bookKey, 'key', value)`
|
||||
- [ ] Reset setter wired into `useResetViewSettings` if the panel has a reset menu
|
||||
- [ ] Command-palette entry added to the matching `*PanelItems` array in `src/services/commandRegistry.ts`, with an `id` that matches the panel row's `data-setting-id`
|
||||
@@ -0,0 +1,123 @@
|
||||
describe('Readest App Launch', () => {
|
||||
it('should have a visible body element', async () => {
|
||||
const body = await $('body');
|
||||
await body.waitForDisplayed({ timeout: 10000 });
|
||||
expect(await body.isDisplayed()).toBe(true);
|
||||
});
|
||||
|
||||
it('should have the correct window handle', async () => {
|
||||
const handle = await browser.getWindowHandle();
|
||||
expect(handle).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return the page source', async () => {
|
||||
const source = await browser.getPageSource();
|
||||
expect(source).toContain('html');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Library Page', () => {
|
||||
it('should navigate to the library page', async () => {
|
||||
const url = await browser.getUrl();
|
||||
expect(url).toMatch(/library|localhost/);
|
||||
});
|
||||
|
||||
it('should display the library container', async () => {
|
||||
const library = await $('[aria-label="Your Library"]');
|
||||
await library.waitForExist({ timeout: 15000 });
|
||||
expect(await library.isExisting()).toBe(true);
|
||||
});
|
||||
|
||||
it('should display the library header', async () => {
|
||||
const header = await $('[aria-label="Library Header"]');
|
||||
await header.waitForExist({ timeout: 10000 });
|
||||
expect(await header.isExisting()).toBe(true);
|
||||
});
|
||||
|
||||
it('should display the bookshelf area', async () => {
|
||||
const bookshelf = await $('[aria-label="Bookshelf"]');
|
||||
await bookshelf.waitForExist({ timeout: 10000 });
|
||||
expect(await bookshelf.isExisting()).toBe(true);
|
||||
});
|
||||
|
||||
it('should have a search input', async () => {
|
||||
const searchInput = await $('.search-input');
|
||||
await searchInput.waitForExist({ timeout: 10000 });
|
||||
expect(await searchInput.isExisting()).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow typing in the search input', async () => {
|
||||
const searchInput = await $('.search-input');
|
||||
await searchInput.waitForDisplayed({ timeout: 10000 });
|
||||
await searchInput.setValue('test search');
|
||||
const value = await searchInput.getValue();
|
||||
expect(value).toBe('test search');
|
||||
});
|
||||
|
||||
it('should show the clear search button after typing', async () => {
|
||||
const clearBtn = await $('[aria-label="Clear Search"]');
|
||||
await clearBtn.waitForExist({ timeout: 5000 });
|
||||
expect(await clearBtn.isExisting()).toBe(true);
|
||||
});
|
||||
|
||||
it('should clear the search input when clear button is clicked', async () => {
|
||||
const clearBtn = await $('[aria-label="Clear Search"]');
|
||||
await clearBtn.click();
|
||||
const searchInput = await $('.search-input');
|
||||
const value = await searchInput.getValue();
|
||||
expect(value).toBe('');
|
||||
});
|
||||
|
||||
it('should have a select books button', async () => {
|
||||
const selectBtn = await $('[aria-label="Select Books"]');
|
||||
await selectBtn.waitForExist({ timeout: 10000 });
|
||||
expect(await selectBtn.isExisting()).toBe(true);
|
||||
});
|
||||
|
||||
it('should have an import books button', async () => {
|
||||
const importBtn = await $('[aria-label="Import Books"]');
|
||||
await importBtn.waitForExist({ timeout: 10000 });
|
||||
expect(await importBtn.isExisting()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Window Management', () => {
|
||||
it('should return the window size', async () => {
|
||||
const size = await browser.getWindowSize();
|
||||
expect(size.width).toBeGreaterThan(0);
|
||||
expect(size.height).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JavaScript Execution', () => {
|
||||
it('should execute JavaScript in the app context', async () => {
|
||||
const result = await browser.execute(() => {
|
||||
return document.readyState;
|
||||
});
|
||||
expect(result).toBe('complete');
|
||||
});
|
||||
|
||||
it('should access the document title via JS', async () => {
|
||||
const title = await browser.execute(() => {
|
||||
return document.title;
|
||||
});
|
||||
expect(title).toContain('Readest');
|
||||
});
|
||||
|
||||
it('should detect the app platform globals', async () => {
|
||||
const hasCLIAccess = await browser.execute(() => {
|
||||
return (window as unknown as Record<string, unknown>).__READEST_CLI_ACCESS === true;
|
||||
});
|
||||
expect(hasCLIAccess).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigation', () => {
|
||||
it('should navigate back to library after visiting another route', async () => {
|
||||
const currentUrl = await browser.getUrl();
|
||||
await browser.url(currentUrl.replace(/\/[^/]*$/, '/library'));
|
||||
const library = await $('[aria-label="Your Library"]');
|
||||
await library.waitForExist({ timeout: 15000 });
|
||||
expect(await library.isExisting()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["mocha", "@wdio/globals/types"]
|
||||
},
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import path from 'node:path';
|
||||
import js from '@eslint/js';
|
||||
import jsxA11y from 'eslint-plugin-jsx-a11y';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { FlatCompat } from '@eslint/eslintrc';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
|
||||
export default [
|
||||
...compat.extends('next/core-web-vitals', 'next/typescript'),
|
||||
{
|
||||
plugins: {
|
||||
'jsx-a11y': jsxA11y,
|
||||
},
|
||||
rules: {
|
||||
...jsxA11y.configs.recommended.rules,
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,588 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-rs-sys",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f954a9e9159ec994f73a30a12b96a702dde78f5547bcb561174597924f7d4162"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"color_quant",
|
||||
"gif",
|
||||
"image-webp",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
|
||||
dependencies = [
|
||||
"byteorder-lite",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3502d6155304a4173a5f2c34b52b7ed0dd085890326cb50fd625fdf39e86b3b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_thumbnail"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"directories-next",
|
||||
"image",
|
||||
"md5",
|
||||
"once_cell",
|
||||
"windows",
|
||||
"windows-core",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773"
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "windows_thumbnail"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[workspace]
|
||||
|
||||
[lib]
|
||||
name = "windows_thumbnail"
|
||||
path = "src/mod.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
base64 = "0.22"
|
||||
directories-next = "2.0"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "gif", "webp"] }
|
||||
md5 = "0.8"
|
||||
once_cell = "1.19"
|
||||
zip = { version = "6.0", default-features = false, features = ["deflate"] }
|
||||
windows = { version = "0.62", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Security",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Registry",
|
||||
"Win32_UI_Shell",
|
||||
] }
|
||||
windows-core = "0.62"
|
||||
@@ -0,0 +1,134 @@
|
||||
# Windows Thumbnail Provider for Readest
|
||||
|
||||
This crate provides Windows Explorer thumbnail support for eBook files when Readest is set as the default application.
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic Cover Extraction**: Extracts cover images from EPUB, MOBI, AZW, AZW3, FB2, CBZ, CBR files
|
||||
- **Readest Branding**: Adds a small Readest icon overlay at the bottom-right corner
|
||||
- **Smart Caching**: Caches generated thumbnails for faster subsequent loads
|
||||
- **File Association Aware**: Only shows thumbnails when Readest is the default app for the file type
|
||||
- **COM Integration**: Full Windows Shell extension implementation via `IThumbnailProvider`
|
||||
|
||||
## Supported Formats
|
||||
|
||||
| Format | Extension | Cover Source |
|
||||
| ---------- | ----------------------- | ---------------------------- |
|
||||
| EPUB | `.epub` | OPF manifest cover reference |
|
||||
| MOBI/AZW | `.mobi`, `.azw`, `.prc` | EXTH cover offset |
|
||||
| AZW3/KF8 | `.azw3`, `.kf8` | KF8 format cover |
|
||||
| FB2 | `.fb2` | `<binary>` coverpage element |
|
||||
| Comic Book | `.cbz`, `.cbr` | First image in archive |
|
||||
| Plain Text | `.txt` | Generated placeholder |
|
||||
|
||||
## Building
|
||||
|
||||
### Library Only
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
### COM DLL (for Windows Explorer integration)
|
||||
|
||||
```bash
|
||||
cargo build --release --features com
|
||||
```
|
||||
|
||||
### CLI Tool
|
||||
|
||||
```bash
|
||||
cargo build --release --features cli
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
The thumbnail provider DLL is automatically registered when Readest is installed via the NSIS installer.
|
||||
|
||||
### Manual Registration (for development)
|
||||
|
||||
```powershell
|
||||
# Register the DLL
|
||||
regsvr32 /s target\release\windows_thumbnail.dll
|
||||
|
||||
# Unregister the DLL
|
||||
regsvr32 /s /u target\release\windows_thumbnail.dll
|
||||
|
||||
# Refresh Explorer to see changes
|
||||
ie4uinit.exe -show
|
||||
```
|
||||
|
||||
After registration, you may need to restart Windows Explorer or log out/in for changes to take effect.
|
||||
|
||||
## Usage (Development / Manual testing)
|
||||
|
||||
For local development and testing, build the Windows DLL (or the library) from the Readest Tauri app folder and register it manually. The legacy CLI test harness used to live in the separate `packages/tauri` workspace, but the thumbnail handler implementation now lives inside Readest's Tauri app.
|
||||
|
||||
Build the DLL (for Windows explorer integration):
|
||||
|
||||
```powershell
|
||||
cd apps/readest-app/src-tauri
|
||||
cargo build --release --manifest-path Cargo.toml --features com
|
||||
```
|
||||
|
||||
The standalone CLI test harness is no longer distributed with the app. To test the thumbnail provider locally, build and register the DLL as shown above and use a small test harness that imports `readestlib`'s thumbnail code or use Explorer after registering the handler.
|
||||
|
||||
Manual registration for development (register the generated DLL):
|
||||
|
||||
```powershell
|
||||
# Register the DLL
|
||||
regsvr32 /s target\release\windows_thumbnail.dll
|
||||
|
||||
# Unregister the DLL
|
||||
regsvr32 /s /u target\release\windows_thumbnail.dll
|
||||
|
||||
# Refresh Explorer to see changes
|
||||
ie4uinit.exe -show
|
||||
```
|
||||
|
||||
This generates a thumbnail with the Readest overlay at the specified size.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Windows Explorer │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │ │
|
||||
│ IThumbnailProvider ───┼──► ThumbnailProvider │
|
||||
│ │ │ │
|
||||
│ │ ▼ │
|
||||
│ │ Check File Association │
|
||||
│ │ (is Readest the default?) │
|
||||
│ │ │ │
|
||||
│ │ ▼ (if yes) │
|
||||
│ │ Extract Cover Image │
|
||||
│ │ │ │
|
||||
│ │ ▼ │
|
||||
│ │ Add Readest Overlay │
|
||||
│ │ │ │
|
||||
│ │ ▼ │
|
||||
│ │ Return HBITMAP │
|
||||
│ │ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## COM Details
|
||||
|
||||
- **CLSID**: `{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}`
|
||||
- **Shell Thumbnail Handler GUID**: `{e357fccd-a995-4576-b01f-234630154e96}`
|
||||
- **Threading Model**: Apartment
|
||||
|
||||
## How It Works
|
||||
|
||||
1. When Windows Explorer needs a thumbnail, it queries the registered shell extension
|
||||
2. The COM DLL implements `IInitializeWithItem` to receive the file path
|
||||
3. It checks if Readest.exe is the default application for that file type using `AssocQueryStringW`
|
||||
4. If Readest is the default, it extracts the cover and generates the thumbnail
|
||||
5. If Readest is NOT the default, it returns `S_FALSE` to let Windows use other handlers
|
||||
|
||||
This ensures thumbnails only appear for files the user has associated with Readest.
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file for details.
|
||||
@@ -0,0 +1,506 @@
|
||||
/// Windows COM Thumbnail Provider for Readest
|
||||
///
|
||||
/// Implements IThumbnailProvider and IInitializeWithItem for Windows Shell integration.
|
||||
/// This allows Windows Explorer to show book covers as thumbnails for eBook files.
|
||||
///
|
||||
/// **Important**: Thumbnails are only shown when Readest.exe is the default application
|
||||
/// for the file type.
|
||||
///
|
||||
/// ## CLSID: {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
|
||||
use std::cell::UnsafeCell;
|
||||
use std::ffi::c_void;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicIsize, AtomicU32, Ordering};
|
||||
|
||||
use windows::core::{IUnknown, Interface, GUID, HRESULT, PCWSTR, PWSTR};
|
||||
use windows::Win32::Foundation::{
|
||||
CLASS_E_NOAGGREGATION, E_FAIL, E_INVALIDARG, E_NOINTERFACE, HMODULE, S_FALSE, S_OK,
|
||||
};
|
||||
use windows::Win32::Graphics::Gdi::{
|
||||
CreateDIBSection, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, HBITMAP,
|
||||
};
|
||||
use windows::Win32::System::Com::{CoTaskMemFree, IClassFactory, IClassFactory_Impl};
|
||||
use windows::Win32::System::LibraryLoader::GetModuleFileNameW;
|
||||
use windows::Win32::System::Registry::{
|
||||
RegCloseKey, RegCreateKeyExW, RegDeleteTreeW, RegSetValueExW, HKEY, HKEY_CLASSES_ROOT,
|
||||
KEY_WRITE, REG_OPTION_NON_VOLATILE, REG_SZ,
|
||||
};
|
||||
use windows::Win32::UI::Shell::{
|
||||
AssocQueryStringW, IInitializeWithItem, IInitializeWithItem_Impl, IShellItem,
|
||||
IThumbnailProvider, IThumbnailProvider_Impl, ASSOCF_NONE, ASSOCSTR_EXECUTABLE,
|
||||
SIGDN_FILESYSPATH, WTSAT_ARGB, WTS_ALPHATYPE,
|
||||
};
|
||||
use windows_core::BOOL;
|
||||
use windows_core::{implement, Ref};
|
||||
|
||||
use super::cached_thumbnail_for_path;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// CLSID for Readest Thumbnail Provider
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// CLSID: {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
|
||||
pub const CLSID_READEST_THUMBNAIL: GUID = GUID::from_u128(0xA1B2C3D4_E5F6_7890_ABCD_EF1234567890);
|
||||
|
||||
/// Supported file extensions
|
||||
pub const SUPPORTED_EXTENSIONS: &[&str] = &[
|
||||
".epub", ".mobi", ".azw", ".azw3", ".kf8", ".prc", ".fb2", ".cbz", ".cbr", ".txt",
|
||||
];
|
||||
|
||||
// DLL reference counting
|
||||
static DLL_REF_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
static DLL_MODULE_PTR: AtomicIsize = AtomicIsize::new(0);
|
||||
|
||||
fn dll_add_ref() {
|
||||
DLL_REF_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
fn dll_release() {
|
||||
DLL_REF_COUNT.fetch_sub(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn set_dll_module(h: HMODULE) {
|
||||
DLL_MODULE_PTR.store(h.0 as isize, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn get_dll_module() -> Option<HMODULE> {
|
||||
let ptr = DLL_MODULE_PTR.load(Ordering::SeqCst);
|
||||
if ptr == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(HMODULE(ptr as *mut c_void))
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// File Association Check
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Check if Readest.exe is the default application for a given file extension.
|
||||
fn is_readest_default_for_extension(ext: &str) -> bool {
|
||||
let ext_wide: Vec<u16> = ext.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
let mut buffer = [0u16; 260];
|
||||
let mut buffer_size = buffer.len() as u32;
|
||||
|
||||
unsafe {
|
||||
let result = AssocQueryStringW(
|
||||
ASSOCF_NONE,
|
||||
ASSOCSTR_EXECUTABLE,
|
||||
PCWSTR(ext_wide.as_ptr()),
|
||||
None,
|
||||
Some(PWSTR(buffer.as_mut_ptr())),
|
||||
&mut buffer_size,
|
||||
);
|
||||
|
||||
if result.is_ok() {
|
||||
let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
|
||||
let path = String::from_utf16_lossy(&buffer[..len]).to_lowercase();
|
||||
return path.contains("readest");
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if Readest is the default app for a specific file path.
|
||||
fn is_readest_default_for_file(path: &PathBuf) -> bool {
|
||||
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
|
||||
let ext_with_dot = format!(".{}", ext.to_lowercase());
|
||||
return is_readest_default_for_extension(&ext_with_dot);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ThumbnailProvider
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Interior mutability wrapper for COM single-threaded apartment
|
||||
struct ComCell<T>(UnsafeCell<T>);
|
||||
|
||||
impl<T> ComCell<T> {
|
||||
fn new(value: T) -> Self {
|
||||
Self(UnsafeCell::new(value))
|
||||
}
|
||||
fn get(&self) -> &T {
|
||||
unsafe { &*self.0.get() }
|
||||
}
|
||||
fn set(&self, value: T) {
|
||||
unsafe {
|
||||
*self.0.get() = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: COM thumbnail providers run in single-threaded apartment (STA)
|
||||
unsafe impl<T> Sync for ComCell<T> {}
|
||||
unsafe impl<T> Send for ComCell<T> {}
|
||||
|
||||
#[implement(IThumbnailProvider, IInitializeWithItem)]
|
||||
pub struct ThumbnailProvider {
|
||||
file_path: ComCell<Option<PathBuf>>,
|
||||
file_ext: ComCell<Option<String>>,
|
||||
should_provide: ComCell<bool>,
|
||||
}
|
||||
|
||||
impl ThumbnailProvider {
|
||||
pub fn new() -> Self {
|
||||
dll_add_ref();
|
||||
Self {
|
||||
file_path: ComCell::new(None),
|
||||
file_ext: ComCell::new(None),
|
||||
should_provide: ComCell::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ThumbnailProvider {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ThumbnailProvider {
|
||||
fn drop(&mut self) {
|
||||
dll_release();
|
||||
}
|
||||
}
|
||||
|
||||
impl IInitializeWithItem_Impl for ThumbnailProvider_Impl {
|
||||
fn Initialize(&self, psi: Ref<'_, IShellItem>, _grfmode: u32) -> windows::core::Result<()> {
|
||||
let item = psi.ok()?;
|
||||
|
||||
unsafe {
|
||||
let path_pwstr = item.GetDisplayName(SIGDN_FILESYSPATH)?;
|
||||
|
||||
let mut len = 0usize;
|
||||
let mut ptr = path_pwstr.0;
|
||||
while *ptr != 0 {
|
||||
len += 1;
|
||||
ptr = ptr.add(1);
|
||||
}
|
||||
|
||||
let slice = std::slice::from_raw_parts(path_pwstr.0, len);
|
||||
let path_str = String::from_utf16_lossy(slice);
|
||||
let path = PathBuf::from(&path_str);
|
||||
|
||||
CoTaskMemFree(Some(path_pwstr.0 as *const c_void));
|
||||
|
||||
let is_default = is_readest_default_for_file(&path);
|
||||
self.should_provide.set(is_default);
|
||||
|
||||
if !is_default {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let ext = path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|s| s.to_lowercase())
|
||||
.unwrap_or_default();
|
||||
|
||||
self.file_path.set(Some(path));
|
||||
self.file_ext.set(Some(ext));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IThumbnailProvider_Impl for ThumbnailProvider_Impl {
|
||||
fn GetThumbnail(
|
||||
&self,
|
||||
cx: u32,
|
||||
phbmp: *mut HBITMAP,
|
||||
pdwalpha: *mut WTS_ALPHATYPE,
|
||||
) -> windows::core::Result<()> {
|
||||
if !*self.should_provide.get() {
|
||||
return Err(E_FAIL.into());
|
||||
}
|
||||
|
||||
let path = self.file_path.get().as_ref().ok_or(E_FAIL)?;
|
||||
let ext = self.file_ext.get().as_ref().ok_or(E_FAIL)?;
|
||||
|
||||
let png_bytes = cached_thumbnail_for_path(path, ext, cx).map_err(|_| E_FAIL)?;
|
||||
let img = image::load_from_memory(&png_bytes).map_err(|_| E_FAIL)?;
|
||||
let rgba = img.to_rgba8();
|
||||
let (width, height) = (rgba.width(), rgba.height());
|
||||
|
||||
let bmi = BITMAPINFO {
|
||||
bmiHeader: BITMAPINFOHEADER {
|
||||
biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
|
||||
biWidth: width as i32,
|
||||
biHeight: -(height as i32),
|
||||
biPlanes: 1,
|
||||
biBitCount: 32,
|
||||
biCompression: BI_RGB.0,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut bits: *mut c_void = std::ptr::null_mut();
|
||||
|
||||
unsafe {
|
||||
let hbmp = CreateDIBSection(None, &bmi, DIB_RGB_COLORS, &mut bits, None, 0)
|
||||
.map_err(|_| E_FAIL)?;
|
||||
if bits.is_null() {
|
||||
return Err(E_FAIL.into());
|
||||
}
|
||||
|
||||
// RGBA -> BGRA
|
||||
let dst =
|
||||
std::slice::from_raw_parts_mut(bits as *mut u8, (width * height * 4) as usize);
|
||||
let src = rgba.as_raw();
|
||||
for i in 0..(width * height) as usize {
|
||||
let si = i * 4;
|
||||
dst[si] = src[si + 2]; // B
|
||||
dst[si + 1] = src[si + 1]; // G
|
||||
dst[si + 2] = src[si]; // R
|
||||
dst[si + 3] = src[si + 3]; // A
|
||||
}
|
||||
|
||||
*phbmp = hbmp;
|
||||
*pdwalpha = WTSAT_ARGB;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ClassFactory
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[implement(IClassFactory)]
|
||||
pub struct ThumbnailProviderFactory;
|
||||
|
||||
impl ThumbnailProviderFactory {
|
||||
pub fn new() -> Self {
|
||||
dll_add_ref();
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ThumbnailProviderFactory {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ThumbnailProviderFactory {
|
||||
fn drop(&mut self) {
|
||||
dll_release();
|
||||
}
|
||||
}
|
||||
|
||||
impl IClassFactory_Impl for ThumbnailProviderFactory_Impl {
|
||||
fn CreateInstance(
|
||||
&self,
|
||||
punkouter: Ref<'_, IUnknown>,
|
||||
riid: *const GUID,
|
||||
ppvobject: *mut *mut c_void,
|
||||
) -> windows::core::Result<()> {
|
||||
unsafe {
|
||||
if ppvobject.is_null() {
|
||||
return Err(E_INVALIDARG.into());
|
||||
}
|
||||
*ppvobject = std::ptr::null_mut();
|
||||
if !punkouter.is_null() {
|
||||
return Err(CLASS_E_NOAGGREGATION.into());
|
||||
}
|
||||
|
||||
let provider: IThumbnailProvider = ThumbnailProvider::new().into();
|
||||
provider.query(&*riid, ppvobject).ok()
|
||||
}
|
||||
}
|
||||
|
||||
fn LockServer(&self, flock: BOOL) -> windows::core::Result<()> {
|
||||
if flock.as_bool() {
|
||||
dll_add_ref();
|
||||
} else {
|
||||
dll_release();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// DLL Exports
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn DllMain(hinstance: HMODULE, reason: u32, _reserved: *mut c_void) -> BOOL {
|
||||
const DLL_PROCESS_ATTACH: u32 = 1;
|
||||
if reason == DLL_PROCESS_ATTACH {
|
||||
set_dll_module(hinstance);
|
||||
}
|
||||
BOOL::from(true)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn DllCanUnloadNow() -> HRESULT {
|
||||
if DLL_REF_COUNT.load(Ordering::SeqCst) == 0 {
|
||||
S_OK
|
||||
} else {
|
||||
S_FALSE
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn DllGetClassObject(
|
||||
rclsid: *const GUID,
|
||||
riid: *const GUID,
|
||||
ppv: *mut *mut c_void,
|
||||
) -> HRESULT {
|
||||
if ppv.is_null() || rclsid.is_null() || riid.is_null() {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
*ppv = std::ptr::null_mut();
|
||||
|
||||
if *rclsid != CLSID_READEST_THUMBNAIL {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
if *riid != IClassFactory::IID && *riid != IUnknown::IID {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
let factory: IClassFactory = ThumbnailProviderFactory::new().into();
|
||||
factory.query(&*riid, ppv)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn DllRegisterServer() -> HRESULT {
|
||||
match register_server_impl() {
|
||||
Ok(()) => S_OK,
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn DllUnregisterServer() -> HRESULT {
|
||||
let _ = unregister_server_impl();
|
||||
S_OK
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Registry helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn get_dll_path() -> Option<String> {
|
||||
let module = get_dll_module()?;
|
||||
let mut buffer = [0u16; 260];
|
||||
unsafe {
|
||||
let len = GetModuleFileNameW(Some(module), &mut buffer);
|
||||
if len == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(String::from_utf16_lossy(&buffer[..len as usize]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clsid_string() -> String {
|
||||
format!(
|
||||
"{{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
|
||||
CLSID_READEST_THUMBNAIL.data1,
|
||||
CLSID_READEST_THUMBNAIL.data2,
|
||||
CLSID_READEST_THUMBNAIL.data3,
|
||||
CLSID_READEST_THUMBNAIL.data4[0],
|
||||
CLSID_READEST_THUMBNAIL.data4[1],
|
||||
CLSID_READEST_THUMBNAIL.data4[2],
|
||||
CLSID_READEST_THUMBNAIL.data4[3],
|
||||
CLSID_READEST_THUMBNAIL.data4[4],
|
||||
CLSID_READEST_THUMBNAIL.data4[5],
|
||||
CLSID_READEST_THUMBNAIL.data4[6],
|
||||
CLSID_READEST_THUMBNAIL.data4[7]
|
||||
)
|
||||
}
|
||||
|
||||
fn to_wide(s: &str) -> Vec<u16> {
|
||||
s.encode_utf16().chain(std::iter::once(0)).collect()
|
||||
}
|
||||
|
||||
unsafe fn set_reg_value(key: HKEY, name: &str, value: &str) -> Result<(), HRESULT> {
|
||||
let name_w = to_wide(name);
|
||||
let value_w = to_wide(value);
|
||||
let bytes: &[u8] = std::slice::from_raw_parts(value_w.as_ptr() as *const u8, value_w.len() * 2);
|
||||
if RegSetValueExW(key, PCWSTR(name_w.as_ptr()), Some(0), REG_SZ, Some(bytes)).is_err() {
|
||||
Err(E_FAIL)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn create_reg_key(parent: HKEY, subkey: &str) -> Result<HKEY, HRESULT> {
|
||||
let subkey_w = to_wide(subkey);
|
||||
let mut hkey = HKEY::default();
|
||||
let result = RegCreateKeyExW(
|
||||
parent,
|
||||
PCWSTR(subkey_w.as_ptr()),
|
||||
Some(0),
|
||||
None,
|
||||
REG_OPTION_NON_VOLATILE,
|
||||
KEY_WRITE,
|
||||
None,
|
||||
&mut hkey,
|
||||
None,
|
||||
);
|
||||
if result.is_err() {
|
||||
Err(E_FAIL)
|
||||
} else {
|
||||
Ok(hkey)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn register_server_impl() -> Result<(), HRESULT> {
|
||||
let dll_path = get_dll_path().ok_or(E_FAIL)?;
|
||||
let clsid = clsid_string();
|
||||
|
||||
// CLSID key
|
||||
let clsid_key = create_reg_key(HKEY_CLASSES_ROOT, &format!("CLSID\\{}", clsid))?;
|
||||
set_reg_value(clsid_key, "", "Readest Thumbnail Provider")?;
|
||||
|
||||
// CRITICAL: DisableProcessIsolation = 1
|
||||
let disable_isolation_name = to_wide("DisableProcessIsolation");
|
||||
let value: u32 = 1;
|
||||
let _ = windows::Win32::System::Registry::RegSetValueExW(
|
||||
clsid_key,
|
||||
PCWSTR(disable_isolation_name.as_ptr()),
|
||||
Some(0),
|
||||
windows::Win32::System::Registry::REG_DWORD,
|
||||
Some(std::slice::from_raw_parts(
|
||||
&value as *const u32 as *const u8,
|
||||
4,
|
||||
)),
|
||||
);
|
||||
|
||||
let inproc_key = create_reg_key(clsid_key, "InprocServer32")?;
|
||||
set_reg_value(inproc_key, "", &dll_path)?;
|
||||
set_reg_value(inproc_key, "ThreadingModel", "Apartment")?;
|
||||
let _ = RegCloseKey(inproc_key);
|
||||
let _ = RegCloseKey(clsid_key);
|
||||
|
||||
// Register ShellEx thumbnail handler for each extension
|
||||
for ext in SUPPORTED_EXTENSIONS {
|
||||
let ext_shellex_path =
|
||||
format!("{}\\ShellEx\\{{e357fccd-a995-4576-b01f-234630154e96}}", ext);
|
||||
if let Ok(ext_shellex_key) = create_reg_key(HKEY_CLASSES_ROOT, &ext_shellex_path) {
|
||||
let _ = set_reg_value(ext_shellex_key, "", &clsid);
|
||||
let _ = RegCloseKey(ext_shellex_key);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn unregister_server_impl() -> Result<(), HRESULT> {
|
||||
let clsid = clsid_string();
|
||||
let clsid_path = to_wide(&format!("CLSID\\{}", clsid));
|
||||
let _ = RegDeleteTreeW(HKEY_CLASSES_ROOT, PCWSTR(clsid_path.as_ptr()));
|
||||
|
||||
for ext in SUPPORTED_EXTENSIONS {
|
||||
let ext_path = to_wide(&format!(
|
||||
"{}\\ShellEx\\{{e357fccd-a995-4576-b01f-234630154e96}}",
|
||||
ext
|
||||
));
|
||||
let _ = RegDeleteTreeW(HKEY_CLASSES_ROOT, PCWSTR(ext_path.as_ptr()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,648 @@
|
||||
/// Cover image extraction for various eBook formats
|
||||
///
|
||||
/// Supports: EPUB, MOBI/AZW3/KF8, FB2, CBZ/CBR, TXT
|
||||
use anyhow::{anyhow, Result};
|
||||
use base64::engine::general_purpose;
|
||||
use base64::Engine as _;
|
||||
use directories_next::ProjectDirs;
|
||||
use image::{imageops, DynamicImage, Rgba};
|
||||
use md5::Context;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use std::path::Path;
|
||||
use zip::ZipArchive;
|
||||
|
||||
/// Thumbnail cache directory (per-user)
|
||||
static CACHE_DIR: Lazy<Option<std::path::PathBuf>> = Lazy::new(|| {
|
||||
ProjectDirs::from("app", "Readest", "").map(|pd| {
|
||||
let dir = pd.cache_dir().join("thumbnails");
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
dir
|
||||
})
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// EPUB extraction
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Extract cover image bytes from an EPUB file.
|
||||
pub fn extract_epub_cover_bytes<R: Read + Seek>(reader: R) -> Result<Vec<u8>> {
|
||||
let mut archive = ZipArchive::new(reader)?;
|
||||
|
||||
// Pass 1: Look for files with "cover" in the name
|
||||
let mut candidates: Vec<(usize, String, u64)> = Vec::new();
|
||||
for i in 0..archive.len() {
|
||||
let file = archive.by_index(i)?;
|
||||
let name = file.name().to_lowercase();
|
||||
let size = file.size();
|
||||
drop(file);
|
||||
|
||||
if is_image_extension(&name) && (name.contains("cover") || name.contains("front")) {
|
||||
candidates.push((i, name, size));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by priority: exact "cover" match first, then by size
|
||||
if !candidates.is_empty() {
|
||||
candidates.sort_by(|a, b| {
|
||||
let a_exact = a.1.contains("cover.") || a.1.ends_with("cover");
|
||||
let b_exact = b.1.contains("cover.") || b.1.ends_with("cover");
|
||||
match (a_exact, b_exact) {
|
||||
(true, false) => std::cmp::Ordering::Less,
|
||||
(false, true) => std::cmp::Ordering::Greater,
|
||||
_ => b.2.cmp(&a.2),
|
||||
}
|
||||
});
|
||||
|
||||
let idx = candidates[0].0;
|
||||
let mut file = archive.by_index(idx)?;
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf)?;
|
||||
return Ok(buf);
|
||||
}
|
||||
|
||||
// Pass 2: Parse container.xml to find OPF, then parse OPF for cover
|
||||
let container_xml = read_zip_file_to_string(&mut archive, "META-INF/container.xml");
|
||||
if let Ok(xml) = container_xml {
|
||||
if let Some(rootfile) = extract_attribute(&xml, "rootfile", "full-path") {
|
||||
let opf_content = read_zip_file_to_string(&mut archive, &rootfile);
|
||||
if let Ok(opf) = opf_content {
|
||||
if let Some(cover_id) = find_cover_id_in_opf(&opf) {
|
||||
if let Some(href) = find_href_by_id_in_opf(&opf, &cover_id) {
|
||||
let base = Path::new(&rootfile).parent().unwrap_or(Path::new(""));
|
||||
let cover_path = base.join(&href).to_string_lossy().replace('\\', "/");
|
||||
if let Ok(bytes) = read_zip_file_to_bytes(&mut archive, &cover_path) {
|
||||
return Ok(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(href) = find_first_image_in_manifest(&opf) {
|
||||
let base = Path::new(&rootfile).parent().unwrap_or(Path::new(""));
|
||||
let cover_path = base.join(&href).to_string_lossy().replace('\\', "/");
|
||||
if let Ok(bytes) = read_zip_file_to_bytes(&mut archive, &cover_path) {
|
||||
return Ok(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3: Just grab the largest image file
|
||||
let mut largest: Option<(usize, u64)> = None;
|
||||
for i in 0..archive.len() {
|
||||
let file = archive.by_index(i)?;
|
||||
let name = file.name().to_lowercase();
|
||||
let size = file.size();
|
||||
drop(file);
|
||||
|
||||
if is_image_extension(&name) && (largest.is_none() || size > largest.unwrap().1) {
|
||||
largest = Some((i, size));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((idx, _)) = largest {
|
||||
let mut file = archive.by_index(idx)?;
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf)?;
|
||||
return Ok(buf);
|
||||
}
|
||||
|
||||
Err(anyhow!("No cover image found in EPUB"))
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// MOBI/AZW3/KF8 extraction
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Extract cover image from MOBI/AZW3/KF8 files.
|
||||
pub fn extract_mobi_cover_bytes<R: Read + Seek>(mut reader: R) -> Result<Vec<u8>> {
|
||||
let mut header = [0u8; 78];
|
||||
reader.read_exact(&mut header)?;
|
||||
|
||||
if &header[60..68] != b"BOOKMOBI" {
|
||||
return Err(anyhow!("Not a valid MOBI file"));
|
||||
}
|
||||
|
||||
let num_records = u16::from_be_bytes([header[76], header[77]]) as usize;
|
||||
|
||||
let mut record_offsets: Vec<u32> = Vec::with_capacity(num_records);
|
||||
for _ in 0..num_records {
|
||||
let mut rec = [0u8; 8];
|
||||
reader.read_exact(&mut rec)?;
|
||||
record_offsets.push(u32::from_be_bytes([rec[0], rec[1], rec[2], rec[3]]));
|
||||
}
|
||||
|
||||
if record_offsets.is_empty() {
|
||||
return Err(anyhow!("No records in MOBI file"));
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(record_offsets[0] as u64))?;
|
||||
let mut mobi_header = [0u8; 256];
|
||||
reader.read_exact(&mut mobi_header)?;
|
||||
|
||||
if &mobi_header[16..20] != b"MOBI" {
|
||||
return Err(anyhow!("Invalid MOBI header"));
|
||||
}
|
||||
|
||||
let header_length = u32::from_be_bytes([
|
||||
mobi_header[20],
|
||||
mobi_header[21],
|
||||
mobi_header[22],
|
||||
mobi_header[23],
|
||||
]) as usize;
|
||||
|
||||
let exth_flags = u32::from_be_bytes([
|
||||
mobi_header[128],
|
||||
mobi_header[129],
|
||||
mobi_header[130],
|
||||
mobi_header[131],
|
||||
]);
|
||||
if exth_flags & 0x40 == 0 {
|
||||
return Err(anyhow!("No EXTH header in MOBI file"));
|
||||
}
|
||||
|
||||
let exth_offset = record_offsets[0] as u64 + 16 + header_length as u64;
|
||||
reader.seek(SeekFrom::Start(exth_offset))?;
|
||||
|
||||
let mut exth_magic = [0u8; 4];
|
||||
reader.read_exact(&mut exth_magic)?;
|
||||
if &exth_magic != b"EXTH" {
|
||||
return Err(anyhow!("EXTH header not found"));
|
||||
}
|
||||
|
||||
let mut exth_len_bytes = [0u8; 4];
|
||||
reader.read_exact(&mut exth_len_bytes)?;
|
||||
|
||||
let mut exth_count_bytes = [0u8; 4];
|
||||
reader.read_exact(&mut exth_count_bytes)?;
|
||||
let exth_count = u32::from_be_bytes(exth_count_bytes) as usize;
|
||||
|
||||
let mut cover_offset: Option<u32> = None;
|
||||
let first_img_idx = u32::from_be_bytes([
|
||||
mobi_header[108],
|
||||
mobi_header[109],
|
||||
mobi_header[110],
|
||||
mobi_header[111],
|
||||
]);
|
||||
|
||||
for _ in 0..exth_count {
|
||||
let mut rec_header = [0u8; 8];
|
||||
if reader.read_exact(&mut rec_header).is_err() {
|
||||
break;
|
||||
}
|
||||
let rec_type =
|
||||
u32::from_be_bytes([rec_header[0], rec_header[1], rec_header[2], rec_header[3]]);
|
||||
let rec_len =
|
||||
u32::from_be_bytes([rec_header[4], rec_header[5], rec_header[6], rec_header[7]])
|
||||
as usize;
|
||||
|
||||
let data_len = rec_len.saturating_sub(8);
|
||||
let mut data = vec![0u8; data_len];
|
||||
if reader.read_exact(&mut data).is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
if rec_type == 201 && data_len >= 4 {
|
||||
cover_offset = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]));
|
||||
}
|
||||
}
|
||||
|
||||
let cover_record_idx = if let Some(offset) = cover_offset {
|
||||
first_img_idx + offset
|
||||
} else {
|
||||
first_img_idx
|
||||
};
|
||||
|
||||
if cover_record_idx as usize >= record_offsets.len() {
|
||||
return Err(anyhow!("Cover record index out of bounds"));
|
||||
}
|
||||
|
||||
let start = record_offsets[cover_record_idx as usize] as u64;
|
||||
let end = if (cover_record_idx as usize + 1) < record_offsets.len() {
|
||||
record_offsets[cover_record_idx as usize + 1] as u64
|
||||
} else {
|
||||
reader.seek(SeekFrom::End(0))?;
|
||||
reader.stream_position()?
|
||||
};
|
||||
|
||||
let len = (end - start) as usize;
|
||||
reader.seek(SeekFrom::Start(start))?;
|
||||
let mut cover_data = vec![0u8; len];
|
||||
reader.read_exact(&mut cover_data)?;
|
||||
|
||||
if cover_data.starts_with(&[0xFF, 0xD8, 0xFF])
|
||||
|| cover_data.starts_with(&[0x89, 0x50, 0x4E, 0x47])
|
||||
|| cover_data.starts_with(b"GIF")
|
||||
{
|
||||
return Ok(cover_data);
|
||||
}
|
||||
|
||||
Err(anyhow!("No valid cover image found in MOBI"))
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// CBZ extraction
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Extract cover image from CBZ (comic book ZIP) file.
|
||||
pub fn extract_cbz_cover_bytes<R: Read + Seek>(reader: R) -> Result<Vec<u8>> {
|
||||
let mut archive = ZipArchive::new(reader)?;
|
||||
|
||||
let mut images: Vec<(usize, String)> = Vec::new();
|
||||
for i in 0..archive.len() {
|
||||
let file = archive.by_index(i)?;
|
||||
let name = file.name().to_string();
|
||||
drop(file);
|
||||
|
||||
if is_image_extension(&name.to_lowercase()) {
|
||||
images.push((i, name));
|
||||
}
|
||||
}
|
||||
|
||||
images.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
|
||||
if let Some((idx, _)) = images.first() {
|
||||
let mut file = archive.by_index(*idx)?;
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf)?;
|
||||
return Ok(buf);
|
||||
}
|
||||
|
||||
Err(anyhow!("No images found in CBZ"))
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// FB2 extraction
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Extract cover image from FB2 (FictionBook) file.
|
||||
pub fn extract_fb2_cover_bytes<R: Read>(mut reader: R) -> Result<Vec<u8>> {
|
||||
let mut content = String::new();
|
||||
reader.read_to_string(&mut content)?;
|
||||
|
||||
let cover_id = if let Some(start) = content.find("<coverpage>") {
|
||||
let end = content[start..].find("</coverpage>").unwrap_or(500);
|
||||
let coverpage = &content[start..start + end];
|
||||
if let Some(href_pos) = coverpage.find("href=\"#") {
|
||||
let id_start = href_pos + 7;
|
||||
let id_end = coverpage[id_start..].find('"').unwrap_or(50);
|
||||
Some(coverpage[id_start..id_start + id_end].to_string())
|
||||
} else if let Some(href_pos) = coverpage.find("l:href=\"#") {
|
||||
let id_start = href_pos + 9;
|
||||
let id_end = coverpage[id_start..].find('"').unwrap_or(50);
|
||||
Some(coverpage[id_start..id_start + id_end].to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let search_pattern = if let Some(ref id) = cover_id {
|
||||
format!("<binary id=\"{}\"", id)
|
||||
} else {
|
||||
"<binary".to_string()
|
||||
};
|
||||
|
||||
if let Some(pos) = content.find(&search_pattern) {
|
||||
if let Some(tag_end) = content[pos..].find('>') {
|
||||
let data_start = pos + tag_end + 1;
|
||||
if let Some(data_end) = content[data_start..].find("</binary>") {
|
||||
let b64_data = content[data_start..data_start + data_end].trim();
|
||||
let b64_clean: String = b64_data.chars().filter(|c| !c.is_whitespace()).collect();
|
||||
let bytes = general_purpose::STANDARD.decode(&b64_clean)?;
|
||||
return Ok(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cover_id.is_some() {
|
||||
if let Some(pos) = content.find("<binary") {
|
||||
if let Some(tag_end) = content[pos..].find('>') {
|
||||
let data_start = pos + tag_end + 1;
|
||||
if let Some(data_end) = content[data_start..].find("</binary>") {
|
||||
let b64_data = content[data_start..data_start + data_end].trim();
|
||||
let b64_clean: String =
|
||||
b64_data.chars().filter(|c| !c.is_whitespace()).collect();
|
||||
let bytes = general_purpose::STANDARD.decode(&b64_clean)?;
|
||||
return Ok(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!("No cover image found in FB2"))
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// TXT "cover" (placeholder)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Generate a placeholder thumbnail for TXT files.
|
||||
pub fn extract_txt_cover_bytes<R: Read>(mut reader: R, size: u32) -> Result<Vec<u8>> {
|
||||
let mut buf = vec![0u8; 4096];
|
||||
let _n = reader.read(&mut buf)?;
|
||||
|
||||
let mut img = image::RgbaImage::from_pixel(size, size, Rgba([245, 245, 245, 255]));
|
||||
|
||||
for x in 0..size {
|
||||
img.put_pixel(x, 0, Rgba([200, 200, 200, 255]));
|
||||
img.put_pixel(x, size - 1, Rgba([200, 200, 200, 255]));
|
||||
}
|
||||
for y in 0..size {
|
||||
img.put_pixel(0, y, Rgba([200, 200, 200, 255]));
|
||||
img.put_pixel(size - 1, y, Rgba([200, 200, 200, 255]));
|
||||
}
|
||||
|
||||
let mut out = Vec::new();
|
||||
DynamicImage::ImageRgba8(img).write_to(&mut Cursor::new(&mut out), image::ImageFormat::Png)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Unified extraction by extension
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Extract cover image bytes based on file extension.
|
||||
pub fn extract_cover_bytes_by_ext(path: &Path, ext: &str) -> Result<Vec<u8>> {
|
||||
let file = std::fs::File::open(path)?;
|
||||
match ext.to_lowercase().as_str() {
|
||||
"epub" => extract_epub_cover_bytes(file),
|
||||
"mobi" | "azw" | "azw3" | "kf8" | "prc" => extract_mobi_cover_bytes(file),
|
||||
"cbz" | "cbr" => extract_cbz_cover_bytes(file),
|
||||
"fb2" => extract_fb2_cover_bytes(file),
|
||||
"txt" => extract_txt_cover_bytes(file, 256),
|
||||
_ => Err(anyhow!("Unsupported format: {}", ext)),
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Thumbnail creation with overlay
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Create a thumbnail from cover image bytes with Readest icon overlay.
|
||||
pub fn create_thumbnail_with_overlay(cover_bytes: &[u8], requested_size: u32) -> Result<Vec<u8>> {
|
||||
let img = image::load_from_memory(cover_bytes)?;
|
||||
let thumbnail = img.thumbnail(requested_size, requested_size);
|
||||
|
||||
let overlay_img = load_overlay_icon();
|
||||
|
||||
let mut base = thumbnail.to_rgba8();
|
||||
let (base_w, base_h) = (base.width(), base.height());
|
||||
|
||||
if let Some(ov) = overlay_img {
|
||||
let overlay_size = (requested_size / 5).clamp(24, 48);
|
||||
let ov_resized = ov.resize(overlay_size, overlay_size, imageops::FilterType::Lanczos3);
|
||||
let ovb = ov_resized.to_rgba8();
|
||||
let (ov_w, ov_h) = (ovb.width(), ovb.height());
|
||||
|
||||
let x = base_w.saturating_sub(ov_w + 4);
|
||||
let y = base_h.saturating_sub(ov_h + 4);
|
||||
|
||||
for oy in 0..ov_h {
|
||||
for ox in 0..ov_w {
|
||||
let dst_x = x + ox;
|
||||
let dst_y = y + oy;
|
||||
|
||||
if dst_x < base_w && dst_y < base_h {
|
||||
let src_pixel = ovb.get_pixel(ox, oy);
|
||||
let alpha = src_pixel.0[3] as f32 / 255.0;
|
||||
|
||||
if alpha > 0.0 {
|
||||
let dst_pixel = base.get_pixel(dst_x, dst_y);
|
||||
let mut result = dst_pixel.0;
|
||||
|
||||
for c in 0..3 {
|
||||
let fg = src_pixel.0[c] as f32;
|
||||
let bg = result[c] as f32;
|
||||
result[c] = (fg * alpha + bg * (1.0 - alpha)) as u8;
|
||||
}
|
||||
result[3] = 255;
|
||||
|
||||
base.put_pixel(dst_x, dst_y, Rgba(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = Vec::new();
|
||||
DynamicImage::ImageRgba8(base).write_to(&mut Cursor::new(&mut out), image::ImageFormat::Png)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Load the Readest overlay icon.
|
||||
fn load_overlay_icon() -> Option<DynamicImage> {
|
||||
// Try embedded icon
|
||||
let icon_bytes = include_bytes!("../../../public/icon.png");
|
||||
if let Ok(img) = image::load_from_memory(icon_bytes) {
|
||||
return Some(img);
|
||||
}
|
||||
|
||||
// Fallback: try loading from filesystem
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
let candidates = [
|
||||
exe.parent().map(|p| p.join("icon.png")),
|
||||
exe.parent().map(|p| p.join("resources").join("icon.png")),
|
||||
exe.parent()
|
||||
.and_then(|p| p.parent())
|
||||
.map(|p| p.join("resources").join("icon.png")),
|
||||
];
|
||||
|
||||
for candidate in candidates.into_iter().flatten() {
|
||||
if candidate.exists() {
|
||||
if let Ok(bytes) = std::fs::read(&candidate) {
|
||||
if let Ok(img) = image::load_from_memory(&bytes) {
|
||||
return Some(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Caching
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Generate a thumbnail with disk caching.
|
||||
pub fn cached_thumbnail_for_path(path: &Path, ext: &str, size: u32) -> Result<Vec<u8>> {
|
||||
// Compute cache key by hashing file parts for stability without loading entire file
|
||||
let mut hasher = Context::new();
|
||||
hasher.consume(ext.as_bytes());
|
||||
hasher.consume(&size.to_le_bytes());
|
||||
|
||||
let file = std::fs::File::open(path)?;
|
||||
let metadata = file.metadata()?;
|
||||
let file_len = metadata.len();
|
||||
|
||||
// Read partial chunks like the TypeScript partialMD5 implementation
|
||||
const STEP: u64 = 1024;
|
||||
const SIZE: u64 = 1024;
|
||||
let mut file = file;
|
||||
|
||||
for i in -1i32..=10 {
|
||||
let pos = if i == -1 {
|
||||
256u64
|
||||
} else {
|
||||
STEP << (2 * i as u32)
|
||||
};
|
||||
let start = pos.min(file_len);
|
||||
let end = (start + SIZE).min(file_len);
|
||||
|
||||
if start >= file_len {
|
||||
break;
|
||||
}
|
||||
|
||||
file.seek(SeekFrom::Start(start))?;
|
||||
let mut buf = vec![0u8; (end - start) as usize];
|
||||
file.read_exact(&mut buf)?;
|
||||
hasher.consume(&buf);
|
||||
}
|
||||
|
||||
let digest = hasher.finalize();
|
||||
let key = format!("{:x}.png", digest);
|
||||
|
||||
if let Some(ref dir) = *CACHE_DIR {
|
||||
let cache_path = dir.join(&key);
|
||||
if cache_path.exists() {
|
||||
if let Ok(cached) = std::fs::read(&cache_path) {
|
||||
return Ok(cached);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let cover = extract_cover_bytes_by_ext(path, ext)?;
|
||||
let thumbnail = create_thumbnail_with_overlay(&cover, size)?;
|
||||
|
||||
if let Some(ref dir) = *CACHE_DIR {
|
||||
let cache_path = dir.join(&key);
|
||||
let _ = std::fs::write(&cache_path, &thumbnail);
|
||||
}
|
||||
|
||||
Ok(thumbnail)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Helper functions
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn is_image_extension(name: &str) -> bool {
|
||||
name.ends_with(".jpg")
|
||||
|| name.ends_with(".jpeg")
|
||||
|| name.ends_with(".png")
|
||||
|| name.ends_with(".gif")
|
||||
|| name.ends_with(".webp")
|
||||
|| name.ends_with(".bmp")
|
||||
}
|
||||
|
||||
fn read_zip_file_to_string<R: Read + Seek>(
|
||||
archive: &mut ZipArchive<R>,
|
||||
name: &str,
|
||||
) -> Result<String> {
|
||||
let mut file = archive.by_name(name)?;
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content)?;
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
fn read_zip_file_to_bytes<R: Read + Seek>(
|
||||
archive: &mut ZipArchive<R>,
|
||||
name: &str,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut file = archive.by_name(name)?;
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn extract_attribute(xml: &str, tag: &str, attr: &str) -> Option<String> {
|
||||
let pattern = format!("<{}", tag);
|
||||
if let Some(tag_pos) = xml.find(&pattern) {
|
||||
let tag_end = xml[tag_pos..].find('>').unwrap_or(500) + tag_pos;
|
||||
let tag_content = &xml[tag_pos..tag_end];
|
||||
|
||||
let attr_pattern = format!("{}=\"", attr);
|
||||
if let Some(attr_pos) = tag_content.find(&attr_pattern) {
|
||||
let value_start = attr_pos + attr_pattern.len();
|
||||
if let Some(value_end) = tag_content[value_start..].find('"') {
|
||||
return Some(tag_content[value_start..value_start + value_end].to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_cover_id_in_opf(opf: &str) -> Option<String> {
|
||||
if let Some(pos) = opf.find("name=\"cover\"") {
|
||||
let window_start = pos.saturating_sub(50);
|
||||
let window_end = (pos + 100).min(opf.len());
|
||||
let window = &opf[window_start..window_end];
|
||||
|
||||
if let Some(content_pos) = window.find("content=\"") {
|
||||
let start = content_pos + 9;
|
||||
if let Some(end) = window[start..].find('"') {
|
||||
return Some(window[start..start + end].to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pos) = opf.find("properties=\"cover-image\"") {
|
||||
let window_start = pos.saturating_sub(200);
|
||||
let window_end = pos;
|
||||
let window = &opf[window_start..window_end];
|
||||
|
||||
if let Some(id_pos) = window.rfind("id=\"") {
|
||||
let start = id_pos + 4;
|
||||
if let Some(end) = window[start..].find('"') {
|
||||
return Some(window[start..start + end].to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn find_href_by_id_in_opf(opf: &str, id: &str) -> Option<String> {
|
||||
let pattern = format!("id=\"{}\"", id);
|
||||
if let Some(pos) = opf.find(&pattern) {
|
||||
let window_start = pos.saturating_sub(10);
|
||||
let window_end = (pos + 200).min(opf.len());
|
||||
let window = &opf[window_start..window_end];
|
||||
|
||||
if let Some(href_pos) = window.find("href=\"") {
|
||||
let start = href_pos + 6;
|
||||
if let Some(end) = window[start..].find('"') {
|
||||
return Some(window[start..start + end].to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_first_image_in_manifest(opf: &str) -> Option<String> {
|
||||
let manifest_start = opf.find("<manifest")?;
|
||||
let manifest_end = opf[manifest_start..]
|
||||
.find("</manifest>")
|
||||
.map(|e| manifest_start + e)?;
|
||||
let manifest = &opf[manifest_start..manifest_end];
|
||||
|
||||
for media_type in ["image/jpeg", "image/png", "image/gif", "image/webp"] {
|
||||
let pattern = format!("media-type=\"{}\"", media_type);
|
||||
if let Some(pos) = manifest.find(&pattern) {
|
||||
let window_start = pos.saturating_sub(200);
|
||||
let window = &manifest[window_start..pos];
|
||||
|
||||
if let Some(href_pos) = window.rfind("href=\"") {
|
||||
let start = href_pos + 6;
|
||||
if let Some(end) = window[start..].find('"') {
|
||||
return Some(window[start..start + end].to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//! Windows Thumbnail Provider for Readest
|
||||
//!
|
||||
//! This module provides Windows Explorer thumbnail support for eBook files.
|
||||
//! Thumbnails are only shown when Readest is set as the default application.
|
||||
//!
|
||||
//! Supported formats: EPUB, MOBI, AZW, AZW3, KF8, FB2, CBZ, CBR
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
mod com_provider;
|
||||
mod extraction;
|
||||
|
||||
pub use extraction::*;
|
||||
@@ -0,0 +1,35 @@
|
||||
[
|
||||
"de",
|
||||
"ja",
|
||||
"es",
|
||||
"fa",
|
||||
"fr",
|
||||
"it",
|
||||
"el",
|
||||
"ko",
|
||||
"uk",
|
||||
"nl",
|
||||
"sl",
|
||||
"sv",
|
||||
"pl",
|
||||
"pt",
|
||||
"ru",
|
||||
"tr",
|
||||
"hi",
|
||||
"id",
|
||||
"vi",
|
||||
"ms",
|
||||
"he",
|
||||
"ar",
|
||||
"th",
|
||||
"bo",
|
||||
"bn",
|
||||
"ta",
|
||||
"si",
|
||||
"ro",
|
||||
"hu",
|
||||
"uz",
|
||||
"pt-BR",
|
||||
"zh-CN",
|
||||
"zh-TW"
|
||||
]
|
||||
@@ -0,0 +1,35 @@
|
||||
const lngs = require('./i18n-langs.json');
|
||||
|
||||
const options = {
|
||||
debug: false,
|
||||
sort: false,
|
||||
func: {
|
||||
list: ['_'],
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
lngs,
|
||||
ns: ['translation'],
|
||||
defaultNs: 'translation',
|
||||
defaultValue: '__STRING_NOT_TRANSLATED__',
|
||||
resource: {
|
||||
loadPath: './public/locales/{{lng}}/{{ns}}.json',
|
||||
savePath: './public/locales/{{lng}}/{{ns}}.json',
|
||||
jsonIndent: 2,
|
||||
lineEnding: '\n',
|
||||
},
|
||||
keySeparator: false,
|
||||
nsSeparator: false,
|
||||
interpolation: {
|
||||
prefix: '{{',
|
||||
suffix: '}}',
|
||||
},
|
||||
metadata: {},
|
||||
allowDynamicKeys: true,
|
||||
removeUnusedKeys: true,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
input: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.test.{js,jsx,ts,tsx}'],
|
||||
output: '.',
|
||||
options,
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
module.exports = {
|
||||
input: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.test.{js,jsx,ts,tsx}'],
|
||||
output: '.',
|
||||
options: {
|
||||
debug: false,
|
||||
sort: false,
|
||||
func: {
|
||||
list: ['_'],
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
lngs: [
|
||||
'de',
|
||||
'ja',
|
||||
'es',
|
||||
'fr',
|
||||
'it',
|
||||
'el',
|
||||
'ko',
|
||||
'uk',
|
||||
'nl',
|
||||
'sv',
|
||||
'pl',
|
||||
'pt',
|
||||
'ru',
|
||||
'tr',
|
||||
'hi',
|
||||
'id',
|
||||
'vi',
|
||||
'ar',
|
||||
'th',
|
||||
'bo',
|
||||
'bn',
|
||||
'ta',
|
||||
'si',
|
||||
'zh-CN',
|
||||
'zh-TW',
|
||||
],
|
||||
ns: ['translation'],
|
||||
defaultNs: 'translation',
|
||||
defaultValue: '__STRING_NOT_TRANSLATED__',
|
||||
resource: {
|
||||
loadPath: './public/locales/{{lng}}/{{ns}}.json',
|
||||
savePath: './public/locales/{{lng}}/{{ns}}.json',
|
||||
jsonIndent: 2,
|
||||
lineEnding: '\n',
|
||||
},
|
||||
keySeparator: false,
|
||||
nsSeparator: false,
|
||||
interpolation: {
|
||||
prefix: '{{',
|
||||
suffix: '}}',
|
||||
},
|
||||
metadata: {},
|
||||
allowDynamicKeys: true,
|
||||
removeUnusedKeys: true,
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,9 @@
|
||||
import withPWAInit from '@ducanh2912/next-pwa';
|
||||
import withSerwistInit from '@serwist/next';
|
||||
import withBundleAnalyzer from '@next/bundle-analyzer';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const isDev = process.env['NODE_ENV'] === 'development';
|
||||
const appPlatform = process.env['NEXT_PUBLIC_APP_PLATFORM'];
|
||||
@@ -27,18 +31,65 @@ const nextConfig = {
|
||||
assetPrefix: '',
|
||||
reactStrictMode: true,
|
||||
serverExternalPackages: ['isows'],
|
||||
transpilePackages: !isDev
|
||||
? [
|
||||
'i18next-browser-languagedetector',
|
||||
'react-i18next',
|
||||
'i18next',
|
||||
'@ducanh2912/next-pwa',
|
||||
'@tauri-apps',
|
||||
'highlight.js',
|
||||
'foliate-js',
|
||||
'marked',
|
||||
]
|
||||
: [],
|
||||
allowedDevOrigins: ['192.168.2.120'],
|
||||
webpack: (config) => {
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
nunjucks: 'nunjucks/browser/nunjucks.js',
|
||||
// `js-mdict` is consumed as TS source via tsconfig paths from
|
||||
// `packages/js-mdict/src/`; its sources `import 'fflate'` directly.
|
||||
// Without an alias, webpack walks up from that source location and
|
||||
// can't find fflate (only installed in this app's node_modules).
|
||||
fflate: path.resolve(__dirname, 'node_modules/fflate'),
|
||||
...(appPlatform !== 'web' ? { '@tursodatabase/database-wasm': false } : {}),
|
||||
};
|
||||
return config;
|
||||
},
|
||||
turbopack: {
|
||||
resolveAlias: {
|
||||
nunjucks: 'nunjucks/browser/nunjucks.js',
|
||||
// Turbopack rejects absolute paths in resolveAlias ("server relative
|
||||
// imports not implemented") — use a project-relative path.
|
||||
fflate: './node_modules/fflate',
|
||||
...(appPlatform !== 'web' ? { '@tursodatabase/database-wasm': './src/utils/stub.ts' } : {}),
|
||||
},
|
||||
},
|
||||
transpilePackages: [
|
||||
'ai',
|
||||
'ai-sdk-ollama',
|
||||
'@ai-sdk/react',
|
||||
'@assistant-ui/react',
|
||||
'@assistant-ui/react-ai-sdk',
|
||||
'@assistant-ui/react-markdown',
|
||||
'streamdown',
|
||||
...(isDev
|
||||
? []
|
||||
: [
|
||||
'i18next-browser-languagedetector',
|
||||
'react-i18next',
|
||||
'i18next',
|
||||
'@tauri-apps',
|
||||
'highlight.js',
|
||||
'foliate-js',
|
||||
'marked',
|
||||
]),
|
||||
],
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/reader/:ids',
|
||||
destination: '/reader?ids=:ids',
|
||||
},
|
||||
{
|
||||
source: '/o/book/:hash/annotation/:id',
|
||||
destination: '/o?book=:hash¬e=:id',
|
||||
},
|
||||
{
|
||||
source: '/s/:token',
|
||||
destination: '/s?token=:token',
|
||||
},
|
||||
];
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
@@ -50,24 +101,34 @@ const nextConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
source: '/_next/static/:path*',
|
||||
headers: [
|
||||
{
|
||||
key: 'Cache-Control',
|
||||
value: isDev
|
||||
? 'public, max-age=0, must-revalidate'
|
||||
: 'public, max-age=31536000, immutable',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
const withPWA = withPWAInit({
|
||||
dest: 'public',
|
||||
disable: isDev || appPlatform !== 'web',
|
||||
cacheOnFrontEndNav: true,
|
||||
aggressiveFrontEndNavCaching: true,
|
||||
reloadOnOnline: true,
|
||||
swcMinify: true,
|
||||
fallbacks: {
|
||||
document: '/offline',
|
||||
},
|
||||
workboxOptions: {
|
||||
disableDevLogs: true,
|
||||
},
|
||||
});
|
||||
const pwaDisabled = isDev || appPlatform !== 'web';
|
||||
|
||||
const withPWA = pwaDisabled
|
||||
? (config) => config
|
||||
: withSerwistInit({
|
||||
swSrc: 'src/sw.ts',
|
||||
swDest: 'public/sw.js',
|
||||
cacheOnNavigation: true,
|
||||
reloadOnOnline: true,
|
||||
disable: false,
|
||||
register: true,
|
||||
scope: '/',
|
||||
});
|
||||
|
||||
const withAnalyzer = withBundleAnalyzer({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
|
||||
+164
-55
@@ -1,26 +1,54 @@
|
||||
{
|
||||
"name": "@readest/readest-app",
|
||||
"version": "0.9.87",
|
||||
"version": "0.11.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "dotenv -e .env.tauri -- next dev --turbopack",
|
||||
"dev": "dotenv -e .env.tauri -- next dev",
|
||||
"build": "dotenv -e .env.tauri -- next build",
|
||||
"start": "dotenv -e .env.tauri -- next start",
|
||||
"dev-web": "dotenv -e .env.web -- next dev --turbopack",
|
||||
"dev-web": "dotenv -e .env.web -- next dev",
|
||||
"build-web": "dotenv -e .env.web -- next build",
|
||||
"start-web": "dotenv -e .env.web -- next start",
|
||||
"i18n:extract": "i18next-scanner",
|
||||
"lint": "next lint",
|
||||
"test": "dotenv -e .env -e .env.test.local vitest",
|
||||
"dev-web:vinext": "dotenv -e .env.web -- vinext dev",
|
||||
"build-web:vinext": "dotenv -e .env.web -- vinext build",
|
||||
"start-web:vinext": "dotenv -e .env.web -- vinext start",
|
||||
"build-tauri": "dotenv -e .env.tauri -- next build",
|
||||
"dev-android": "tauri android build -t aarch64 -- --features devtools && adb install -r src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk",
|
||||
"dev-ios": "tauri ios build -- --features devtools && ideviceinstaller -i src-tauri/gen/apple/build/arm64/Readest.ipa",
|
||||
"i18n:extract": "i18next-scanner --config i18next-scanner.config.cjs",
|
||||
"lint": "tsgo --noEmit && biome check . && pnpm lint:lua",
|
||||
"lint:lua": "node ../readest.koplugin/scripts/lint-koplugin.mjs",
|
||||
"test:lua": "node ../readest.koplugin/scripts/test-koplugin.mjs",
|
||||
"test": "dotenv -e .env -e .env.test.local -- vitest",
|
||||
"test:coverage": "dotenv -e .env -e .env.test.local -- vitest run --coverage",
|
||||
"test:browser": "dotenv -e .env -e .env.test.local -- vitest run --config vitest.browser.config.mts",
|
||||
"test:tauri": "bash scripts/test-tauri.sh",
|
||||
"test:pr:web": "pnpm test -- --watch=false && pnpm test:browser",
|
||||
"test:pr:tauri": "bash scripts/test-tauri.sh",
|
||||
"test:all": "pnpm test -- --watch=false && pnpm test:browser && bash scripts/test-tauri.sh",
|
||||
"test:e2e": "wdio run wdio.conf.ts",
|
||||
"tauri:dev:test": "dotenv -e .env.tauri -- tauri dev --features webdriver",
|
||||
"tauri:build:test": "dotenv -e .env.tauri -- tauri build --debug --features webdriver",
|
||||
"tauri": "tauri",
|
||||
"prepare-public-vendor": "mkdirp ./public/vendor/pdfjs",
|
||||
"copy-pdfjs-js": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/legacy/build/{pdf.worker.min.mjs,pdf.mjs,pdf.d.mts}\" ./public/vendor/pdfjs",
|
||||
"fmt:check": "cargo fmt -p Readest --check",
|
||||
"clippy:check": "cargo clippy -p Readest --no-deps -- -D warnings",
|
||||
"format": "pnpm -w format",
|
||||
"format:check": "pnpm -w format:check",
|
||||
"prepare-public-vendor": "mkdirp ./public/vendor/pdfjs ./public/vendor/simplecc ./public/vendor/jieba",
|
||||
"copy-pdfjs-js": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/legacy/build/{pdf.worker.min.mjs,pdf.min.mjs,pdf.d.mts}\" ./public/vendor/pdfjs",
|
||||
"copy-pdfjs-wasm": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/wasm/{openjpeg.wasm,qcms_bg.wasm}\" ./public/vendor/pdfjs",
|
||||
"copy-pdfjs-fonts": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/{cmaps,standard_fonts}/*\" ./public/vendor/pdfjs",
|
||||
"copy-flatten-pdfjs-annotation-layer-css": "npx postcss \"../../packages/foliate-js/vendor/pdfjs/annotation_layer_builder.css\" --no-map -u postcss-nested > ./public/vendor/pdfjs/annotation_layer_builder.css",
|
||||
"copy-flatten-pdfjs-text-layer-css": "npx postcss \"../../packages/foliate-js/vendor/pdfjs/text_layer_builder.css\" --no-map -u postcss-nested > ./public/vendor/pdfjs/text_layer_builder.css",
|
||||
"copy-flatten-pdfjs-css": "pnpm copy-flatten-pdfjs-annotation-layer-css && pnpm copy-flatten-pdfjs-text-layer-css",
|
||||
"copy-pdfjs": "pnpm copy-pdfjs-js && pnpm copy-pdfjs-fonts && pnpm copy-flatten-pdfjs-css",
|
||||
"copy-pdfjs": "pnpm copy-pdfjs-js && pnpm copy-pdfjs-wasm && pnpm copy-pdfjs-fonts && pnpm copy-flatten-pdfjs-css",
|
||||
"copy-simplecc": "cpx \"../../packages/simplecc-wasm/dist/web/*\" ./public/vendor/simplecc",
|
||||
"copy-jieba": "cpx \"./node_modules/jieba-wasm/pkg/web/*\" ./public/vendor/jieba",
|
||||
"setup-pdfjs": "pnpm prepare-public-vendor && pnpm copy-pdfjs",
|
||||
"setup-simplecc": "pnpm prepare-public-vendor && pnpm copy-simplecc",
|
||||
"setup-jieba": "pnpm prepare-public-vendor && pnpm copy-jieba",
|
||||
"setup-vendors": "pnpm setup-pdfjs && pnpm setup-simplecc && pnpm setup-jieba",
|
||||
"build-win-x64": "dotenv -e .env.tauri.local -- tauri build --target i686-pc-windows-msvc --bundles nsis",
|
||||
"build-win-arm64": "dotenv -e .env.tauri.local -- tauri build --target aarch64-pc-windows-msvc --bundles nsis",
|
||||
"build-linux-x64": "dotenv -e .env.tauri.local -- tauri build --target x86_64-unknown-linux-gnu --bundles appimage",
|
||||
@@ -33,112 +61,193 @@
|
||||
"release-ios-appstore": "dotenv -e .env.ios-appstore.local -- bash scripts/release-ios-appstore.sh",
|
||||
"release-google-play": "dotenv -e .env.google-play.local -- bash scripts/release-google-play.sh",
|
||||
"config-wrangler": "sed -i \"s/\\${TRANSLATIONS_KV_ID}/$TRANSLATIONS_KV_ID/g\" wrangler.toml",
|
||||
"preview": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && opennextjs-cloudflare preview --ip 0.0.0.0",
|
||||
"deploy": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && opennextjs-cloudflare deploy",
|
||||
"upload": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && opennextjs-cloudflare upload",
|
||||
"preview": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare preview --ip 0.0.0.0 --port 3001",
|
||||
"deploy": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare deploy",
|
||||
"upload": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare upload",
|
||||
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts",
|
||||
"patch-build-webpack": "if [ \"$(uname)\" = \"Darwin\" ]; then sed -i '' 's/next build\"/next build --webpack\"/' package.json; else sed -i 's/next build\"/next build --webpack\"/' package.json; fi",
|
||||
"restore-build-original": "if [ \"$(uname)\" = \"Darwin\" ]; then sed -i '' 's/next build --webpack\"/next build\"/' package.json; else sed -i 's/next build --webpack\"/next build\"/' package.json; fi",
|
||||
"update-metadata": "bash ./scripts/sync-release-notes.sh release-notes.json ../../data/metainfo/appdata.xml",
|
||||
"check:optional-chaining": "count=$(grep -rno '\\?\\.[a-zA-Z_$]' .next/static/chunks/* out/_next/static/chunks/* | wc -l); if [ \"$count\" -gt 0 ]; then echo '❌ Optional chaining found in output!'; exit 1; else echo '✅ No optional chaining found.'; fi",
|
||||
"check:translations": "count=$(grep -rno '__STRING_NOT_TRANSLATED__' public/locales/* | wc -l); if [ \"$count\" -gt 0 ]; then echo '❌ Untranslated strings found!'; exit 1; else echo '✅ All strings translated.'; fi",
|
||||
"check:all": "pnpm check:optional-chaining && pnpm check:translations",
|
||||
"build-check": "pnpm build && pnpm build-web && pnpm check:all"
|
||||
"check:lookbehind-regex": "count=$(grep -rnoE '\\(\\?<[!=]' .next/static/chunks/* out/_next/static/chunks/* | wc -l); if [ \"$count\" -gt 0 ]; then echo '❌ Lookbehind regex found in output!'; exit 1; else echo '✅ No lookbehind regex found.'; fi",
|
||||
"check:all": "pnpm check:translations && pnpm check:lookbehind-regex",
|
||||
"build-check": "pnpm build && pnpm build-web && pnpm check:all",
|
||||
"worktree:new": "pnpm exec tsx scripts/worktree-new.ts",
|
||||
"worktree:rm": "pnpm exec tsx scripts/worktree-rm.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.735.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.735.0",
|
||||
"@ducanh2912/next-pwa": "^10.2.9",
|
||||
"@ai-sdk/react": "^3.0.49",
|
||||
"@assistant-ui/react": "0.11.56",
|
||||
"@assistant-ui/react-ai-sdk": "1.1.21",
|
||||
"@assistant-ui/react-markdown": "0.11.9",
|
||||
"@aws-sdk/client-s3": "^3.1000.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1000.0",
|
||||
"@choochmeque/tauri-plugin-sharekit-api": "^0.3.0",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1",
|
||||
"@fabianlars/tauri-plugin-oauth": "2",
|
||||
"@opennextjs/cloudflare": "^1.6.5",
|
||||
"@stripe/react-stripe-js": "^3.7.0",
|
||||
"@stripe/stripe-js": "^7.4.0",
|
||||
"@napi-rs/wasm-runtime": "^1.1.1",
|
||||
"@opennextjs/cloudflare": "^1.19.4",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-hover-card": "^1.1.15",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@serwist/next": "^9.5.6",
|
||||
"@serwist/webpack-plugin": "^9.5.6",
|
||||
"@stripe/react-stripe-js": "^6.2.0",
|
||||
"@stripe/stripe-js": "^9.3.1",
|
||||
"@supabase/auth-ui-react": "^0.4.7",
|
||||
"@supabase/auth-ui-shared": "^0.1.8",
|
||||
"@supabase/supabase-js": "^2.55.0",
|
||||
"@tauri-apps/api": "2.8.0",
|
||||
"@tauri-apps/plugin-cli": "^2.4.0",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.3",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.2",
|
||||
"@tauri-apps/plugin-haptics": "^2.3.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.2",
|
||||
"@tauri-apps/plugin-log": "^2.7.0",
|
||||
"@tauri-apps/plugin-opener": "^2.5.0",
|
||||
"@tauri-apps/plugin-os": "^2.3.1",
|
||||
"@tauri-apps/plugin-process": "^2.3.0",
|
||||
"@tauri-apps/plugin-shell": "~2.3.1",
|
||||
"@tauri-apps/plugin-updater": "^2.9.0",
|
||||
"@zip.js/zip.js": "^2.7.53",
|
||||
"@supabase/supabase-js": "^2.76.1",
|
||||
"@tauri-apps/api": "2.10.1",
|
||||
"@tauri-apps/plugin-cli": "^2.4.1",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.7",
|
||||
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||
"@tauri-apps/plugin-haptics": "^2.3.2",
|
||||
"@tauri-apps/plugin-http": "^2.5.7",
|
||||
"@tauri-apps/plugin-log": "^2.8.0",
|
||||
"@tauri-apps/plugin-opener": "^2.5.3",
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-shell": "~2.3.5",
|
||||
"@tauri-apps/plugin-updater": "^2.10.0",
|
||||
"@tauri-apps/plugin-websocket": "~2.4.2",
|
||||
"@readest/turso-database-common": "0.6.0-pre.28-readest.0",
|
||||
"@readest/turso-database-wasm-common": "0.6.0-pre.28-readest.0",
|
||||
"@readest/turso-database-wasm": "0.6.0-pre.28-readest.0",
|
||||
"@tybys/wasm-util": "^0.10.1",
|
||||
"@zip.js/zip.js": "^2.8.16",
|
||||
"abortcontroller-polyfill": "^1.7.8",
|
||||
"ai": "^6.0.47",
|
||||
"ai-sdk-ollama": "^3.2.0",
|
||||
"app-store-server-api": "^0.17.1",
|
||||
"aws4fetch": "^1.0.20",
|
||||
"buffer": "^6.0.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"cssbeautify": "^0.3.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"dompurify": "^3.4.0",
|
||||
"fflate": "^0.8.2",
|
||||
"foliate-js": "workspace:*",
|
||||
"franc-min": "^6.2.0",
|
||||
"fzf": "^0.5.2",
|
||||
"google-auth-library": "^10.5.0",
|
||||
"googleapis": "^164.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"i18next": "^24.2.0",
|
||||
"i18next-browser-languagedetector": "^8.0.2",
|
||||
"i18next-http-backend": "^3.0.1",
|
||||
"i18next-http-backend": "^3.0.5",
|
||||
"iso-639-2": "^3.0.2",
|
||||
"iso-639-3": "^3.0.1",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"jieba-wasm": "^2.4.0",
|
||||
"js-md5": "^0.8.3",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lucide-react": "^0.562.0",
|
||||
"lunr": "^2.3.9",
|
||||
"marked": "^15.0.12",
|
||||
"next": "15.5.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"next": "16.2.3",
|
||||
"next-view-transitions": "^0.3.5",
|
||||
"nunjucks": "^3.2.4",
|
||||
"overlayscrollbars": "^2.11.4",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"posthog-js": "^1.246.0",
|
||||
"react": "19.0.0",
|
||||
"react": "19.2.5",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "19.0.0",
|
||||
"react-dom": "19.2.5",
|
||||
"react-i18next": "^15.2.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-responsive": "^10.0.0",
|
||||
"react-virtuoso": "^4.17.0",
|
||||
"react-window": "^1.8.11",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"semver": "^7.7.1",
|
||||
"stripe": "^18.2.1",
|
||||
"streamdown": "^1.6.10",
|
||||
"stripe": "^22.0.2",
|
||||
"styled-jsx": "^5.1.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tauri-plugin-device-info-api": "^1.0.1",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"uuid": "^11.1.0",
|
||||
"uuid": "^14.0.0",
|
||||
"ws": "^8.18.3",
|
||||
"zod": "^4.0.8",
|
||||
"zustand": "5.0.6"
|
||||
"zustand": "5.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^15.4.2",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tauri-apps/cli": "2.8.4",
|
||||
"@tauri-apps/cli": "2.10.1",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@tursodatabase/database": "0.6.0-pre.28",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/cssbeautify": "^0.3.5",
|
||||
"@types/lunr": "^2.3.7",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "^22.15.31",
|
||||
"@types/react": "18.3.12",
|
||||
"@types/nunjucks": "^3.2.6",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-color": "^3.0.13",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260312.1",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"@vitejs/plugin-rsc": "^0.5.23",
|
||||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"@vitest/browser-webdriverio": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@wdio/cli": "^9.25.0",
|
||||
"@wdio/globals": "^9.23.0",
|
||||
"@wdio/local-runner": "^9.24.0",
|
||||
"@wdio/mocha-framework": "^9.24.0",
|
||||
"@wdio/spec-reporter": "^9.24.0",
|
||||
"@wdio/types": "^9.24.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"caniuse-lite": "^1.0.30001746",
|
||||
"cpx2": "^8.0.0",
|
||||
"daisyui": "^4.12.24",
|
||||
"dotenv-cli": "^7.4.4",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-config-next": "15.0.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"i18next-scanner": "^4.6.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"jsdom": "^28.1.0",
|
||||
"mkdirp": "^3.0.1",
|
||||
"node-env-run": "^4.0.2",
|
||||
"playwright": "^1.58.2",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-cli": "^11.0.0",
|
||||
"postcss-nested": "^7.0.2",
|
||||
"raw-loader": "^4.0.2",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"react-server-dom-webpack": "^19.2.5",
|
||||
"serwist": "^9.3.0",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"typescript": "^5.7.2",
|
||||
"vinext": "^0.0.21",
|
||||
"vite": "^7.3.3",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.4",
|
||||
"wrangler": "^4.31.0"
|
||||
}
|
||||
"vitest": "^4.0.18",
|
||||
"wrangler": "^4.85.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"chrome 92",
|
||||
"edge 92",
|
||||
"firefox 92",
|
||||
"safari 15.2"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,15 @@
|
||||
"components": [
|
||||
{
|
||||
"/": "/auth/*",
|
||||
"comment": "Matches any URL whose path starts with /auth/"
|
||||
"comment": "Matches OAuth callback paths"
|
||||
},
|
||||
{
|
||||
"/": "/o/*",
|
||||
"comment": "Matches annotation deeplinks"
|
||||
},
|
||||
{
|
||||
"/": "/s/*",
|
||||
"comment": "Matches book share links"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ed533042-5626-4704-b5f2-fa3bbd1136ed
|
||||
@@ -1,2 +0,0 @@
|
||||
/_next/static/*
|
||||
Cache-Control: public,max-age=31536000,immutable
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
File diff suppressed because it is too large
Load Diff
@@ -24,9 +24,6 @@
|
||||
"Token": "টোকেন",
|
||||
"Your OTP token": "আপনার ওটিপি টোকেন",
|
||||
"Verify token": "টোকেন যাচাই করুন",
|
||||
"Sign in with Google": "গুগল দিয়ে সাইন ইন",
|
||||
"Sign in with Apple": "অ্যাপল দিয়ে সাইন ইন",
|
||||
"Sign in with GitHub": "গিটহাব দিয়ে সাইন ইন",
|
||||
"New Password": "নতুন পাসওয়ার্ড",
|
||||
"Your new password": "আপনার নতুন পাসওয়ার্ড",
|
||||
"Update password": "পাসওয়ার্ড আপডেট করুন",
|
||||
@@ -105,9 +102,9 @@
|
||||
"Book Covers": "বইয়ের মলাট",
|
||||
"Sort by...": "সাজানোর ভিত্তি...",
|
||||
"No supported files found. Supported formats: {{formats}}": "কোন সমর্থিত ফাইল পাওয়া যায়নি। সমর্থিত ফরম্যাট: {{formats}}",
|
||||
"No chapters detected.": "কোন অধ্যায় শনাক্ত হয়নি।",
|
||||
"Failed to parse the EPUB file.": "EPUB ফাইল পার্স করতে ব্যর্থ।",
|
||||
"This book format is not supported.": "এই বই ফরম্যাট সমর্থিত নয়।",
|
||||
"No chapters detected": "কোন অধ্যায় শনাক্ত হয়নি।",
|
||||
"Failed to parse the EPUB file": "EPUB ফাইল পার্স করতে ব্যর্থ।",
|
||||
"This book format is not supported": "এই বই ফরম্যাট সমর্থিত নয়।",
|
||||
"Failed to import book(s): {{filenames}}": "বই আমদানি ব্যর্থ: {{filenames}}",
|
||||
"Book uploaded: {{title}}": "বই আপলোড হয়েছে: {{title}}",
|
||||
"Insufficient storage quota": "অপর্যাপ্ত স্টোরেজ কোটা",
|
||||
@@ -120,7 +117,6 @@
|
||||
"Failed to delete book: {{title}}": "বই মুছতে ব্যর্থ: {{title}}",
|
||||
"Failed to delete cloud backup of the book: {{title}}": "বইয়ের ক্লাউড ব্যাকআপ মুছতে ব্যর্থ: {{title}}",
|
||||
"Failed to delete local copy of the book: {{title}}": "বইয়ের স্থানীয় কপি মুছতে ব্যর্থ: {{title}}",
|
||||
"Welcome to your library. You can import your books here and read them anytime.": "আপনার লাইব্রেরিতে স্বাগতম। আপনি এখানে বই আমদানি করে যেকোনো সময় পড়তে পারেন।",
|
||||
"Copied to notebook": "নোটবুকে কপি করা হয়েছে",
|
||||
"No annotations to export": "রপ্তানি করার জন্য কোন টীকা নেই",
|
||||
"Exported from Readest": "রিডেস্ট থেকে রপ্তানি করা হয়েছে",
|
||||
@@ -158,13 +154,9 @@
|
||||
"Small": "ছোট",
|
||||
"Large": "বড়",
|
||||
"Sync Conflict": "সিঙ্ক দ্বন্দ্ব",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "\"{{deviceName}}\" থেকে পড়ার অগ্রগতি সিঙ্ক করবেন?",
|
||||
"another device": "অন্য ডিভাইস",
|
||||
"Local Progress": "স্থানীয় অগ্রগতি",
|
||||
"Remote Progress": "দূরবর্তী অগ্রগতি",
|
||||
"Failed to connect": "সংযোগ ব্যর্থ",
|
||||
"Disconnected": "সংযোগ বিচ্ছিন্ন",
|
||||
"KOReader Sync Settings": "KOReader সিঙ্ক সেটিংস",
|
||||
"Sync as {{userDisplayName}}": "{{userDisplayName}} হিসেবে সিঙ্ক",
|
||||
"Sync Server Connected": "সিঙ্ক সার্ভার সংযুক্ত",
|
||||
"Sync Strategy": "সিঙ্ক কৌশল",
|
||||
@@ -173,7 +165,6 @@
|
||||
"Send changes only": "শুধু পরিবর্তন পাঠান",
|
||||
"Receive changes only": "শুধু পরিবর্তন গ্রহণ করুন",
|
||||
"Checksum Method": "চেকসাম পদ্ধতি",
|
||||
"File Content (recommended)": "ফাইল কন্টেন্ট (সুপারিশকৃত)",
|
||||
"File Name": "ফাইলের নাম",
|
||||
"Device Name": "ডিভাইসের নাম",
|
||||
"Connect to your KOReader Sync server.": "আপনার KOReader সিঙ্ক সার্ভারে সংযোগ করুন।",
|
||||
@@ -189,8 +180,8 @@
|
||||
"Add your notes here...": "এখানে আপনার নোট যোগ করুন...",
|
||||
"Search notes and excerpts...": "নোট ও উদ্ধৃতি খুঁজুন...",
|
||||
"{{time}} min left in chapter": "অধ্যায়ে {{time}} মিনিট বাকি",
|
||||
"{{count}} pages left in chapter_one": "অধ্যায়ে ১ পৃষ্ঠা বাকি",
|
||||
"{{count}} pages left in chapter_other": "অধ্যায়ে {{count}} পৃষ্ঠা বাকি",
|
||||
"{{count}} pages left in chapter_one": "<1>অধ্যায়ে ১ পৃষ্ঠা বাকি</1>",
|
||||
"{{count}} pages left in chapter_other": "<1>অধ্যায়ে </1><0>{{count}}</0><1> পৃষ্ঠা বাকি</1>",
|
||||
"Theme Mode": "থিম মোড",
|
||||
"Invert Image In Dark Mode": "ডার্ক মোডে ছবি উল্টান",
|
||||
"Override Book Color": "বইয়ের রঙ পরিবর্তন",
|
||||
@@ -202,11 +193,7 @@
|
||||
"Auto": "অটো",
|
||||
"Scroll": "স্ক্রল",
|
||||
"Scrolled Mode": "স্ক্রল মোড",
|
||||
"Continuous Scroll": "ক্রমাগত স্ক্রল",
|
||||
"Overlap Pixels": "ওভারল্যাপ পিক্সেল",
|
||||
"Click": "ক্লিক",
|
||||
"Clicks for Page Flip": "পৃষ্ঠা উল্টানোর জন্য ক্লিক",
|
||||
"Swap Clicks Area": "ক্লিক এরিয়া অদলবদল",
|
||||
"Disable Double Click": "ডাবল ক্লিক নিষ্ক্রিয়",
|
||||
"Volume Keys for Page Flip": "পৃষ্ঠা উল্টানোর জন্য ভলিউম কী",
|
||||
"Animation": "অ্যানিমেশন",
|
||||
@@ -296,7 +283,7 @@
|
||||
"Color": "রঙ",
|
||||
"Behavior": "আচরণ",
|
||||
"Reset {{settings}}": "{{settings}} রিসেট",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "সারা বিশ্বটাই একটা মঞ্চ,\nআর সব নর-নারী কেবল অভিনেতা;\nতাদের আছে প্রস্থান ও প্রবেশ,\nআর একজন মানুষ তার জীবনে অনেক চরিত্র পালন করে,\nতার অভিনয় সাত যুগের।\n\n— উইলিয়াম শেক্সপিয়র",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "সারা বিশ্বটাই একটা মঞ্চ,\nআর সব নর-নারী কেবল অভিনেতা;\nতাদের আছে প্রস্থান ও প্রবেশ,\nআর একজন মানুষ তার জীবনে অনেক চরিত্র পালন করে,\nতার অভিনয় সাত যুগের।\n\n— উইলিয়াম শেক্সপিয়র",
|
||||
"(from 'As You Like It', Act II)": "('অ্যাজ ইউ লাইক ইট', দ্বিতীয় অঙ্ক থেকে)",
|
||||
"Custom Theme": "কাস্টম থিম",
|
||||
"Theme Name": "থিমের নাম",
|
||||
@@ -305,7 +292,6 @@
|
||||
"Link Color": "লিঙ্ক রঙ",
|
||||
"Preview": "প্রিভিউ",
|
||||
"Font & Layout": "ফন্ট ও লেআউট",
|
||||
"Book Cover": "বইয়ের মলাট",
|
||||
"More Info": "আরো তথ্য",
|
||||
"Parallel Read": "সমান্তরাল পড়া",
|
||||
"Disable": "নিষ্ক্রিয়",
|
||||
@@ -326,7 +312,6 @@
|
||||
"Table of Contents": "সূচিপত্র",
|
||||
"Sidebar": "সাইডবার",
|
||||
"Disable Translation": "অনুবাদ নিষ্ক্রিয়",
|
||||
"TTS not supported for PDF": "PDF এর জন্য TTS সমর্থিত নয়",
|
||||
"TTS not supported for this document": "এই ডকুমেন্টের জন্য TTS সমর্থিত নয়",
|
||||
"No Timeout": "কোন টাইমআউট নেই",
|
||||
"{{value}} minute": "{{value}} মিনিট",
|
||||
@@ -339,7 +324,6 @@
|
||||
"{{engine}}: {{count}} voices_one": "{{engine}}: ১টি কণ্ঠস্বর",
|
||||
"{{engine}}: {{count}} voices_other": "{{engine}}: {{count}}টি কণ্ঠস্বর",
|
||||
"Sign in to Sync": "সিঙ্কের জন্য সাইন ইন",
|
||||
"Synced at {{time}}": "{{time}} এ সিঙ্ক হয়েছে",
|
||||
"Never synced": "কখনো সিঙ্ক হয়নি",
|
||||
"Reading Progress Synced": "পড়ার অগ্রগতি সিঙ্ক হয়েছে",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "{{total}} এর {{page}} পৃষ্ঠা ({{percentage}}%)",
|
||||
@@ -379,7 +363,6 @@
|
||||
"Email:": "ইমেইল:",
|
||||
"Plan:": "প্ল্যান:",
|
||||
"Amount:": "পরিমাণ:",
|
||||
"Subscription ID:": "সাবস্ক্রিপশন ID:",
|
||||
"Go to Library": "লাইব্রেরিতে যান",
|
||||
"Need help? Contact our support team at support@readest.com": "সাহায্য দরকার? support@readest.com এ আমাদের সাপোর্ট টিমের সাথে যোগাযোগ করুন",
|
||||
"Free Plan": "ফ্রি প্ল্যান",
|
||||
@@ -493,8 +476,8 @@
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai GB Screen",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"GuanKiapTsingKhai-T": "GuanKiapTsingKhai-T",
|
||||
"Source Han Serif CN VF": "Source Han Serif CN VF",
|
||||
"Huiwen-mincho": "Huiwen-mincho",
|
||||
"Source Han Serif CN": "Source Han Serif CN",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa_OldSong",
|
||||
"Google Books": "গুগল বুকস",
|
||||
"Open Library": "ওপেন লাইব্রেরি",
|
||||
@@ -606,5 +589,828 @@
|
||||
"Opacity": "স্বচ্ছতা",
|
||||
"Size": "আকার",
|
||||
"Cover": "ঢাকা",
|
||||
"Contain": "ধারণ"
|
||||
"Contain": "ধারণ",
|
||||
"{{number}} pages left in chapter": "<1>অধ্যায়ে </1><0>{{number}}</0><1> পৃষ্ঠা বাকি</1>",
|
||||
"Device": "যন্ত্র",
|
||||
"E-Ink Mode": "ই-ইঙ্ক মোড",
|
||||
"Highlight Colors": "হাইলাইট রঙ",
|
||||
"Pagination": "পৃষ্ঠা বিন্যাস",
|
||||
"Disable Double Tap": "ডাবল ট্যাপ অক্ষম করুন",
|
||||
"Tap to Paginate": "পৃষ্ঠায় যেতে ট্যাপ করুন",
|
||||
"Click to Paginate": "পৃষ্ঠায় যেতে ক্লিক করুন",
|
||||
"Tap Both Sides": "দুই পাশে ট্যাপ করুন",
|
||||
"Click Both Sides": "দুই পাশে ক্লিক করুন",
|
||||
"Swap Tap Sides": "ট্যাপের পাশে পরিবর্তন করুন",
|
||||
"Swap Click Sides": "ক্লিকের পাশে পরিবর্তন করুন",
|
||||
"Source and Translated": "উৎস এবং অনূদিত",
|
||||
"Translated Only": "শুধুমাত্র অনূদিত",
|
||||
"Source Only": "শুধুমাত্র উৎস",
|
||||
"TTS Text": "TTS টেক্সট",
|
||||
"The book file is corrupted": "বইয়ের ফাইলটি ক্ষতিগ্রস্ত।",
|
||||
"The book file is empty": "বইয়ের ফাইলটি খালি।",
|
||||
"Failed to open the book file": "বইয়ের ফাইল খুলতে ব্যর্থ।",
|
||||
"On-Demand Purchase": "অনুরোধে ক্রয়",
|
||||
"Full Customization": "সম্পূর্ণ কাস্টমাইজেশন",
|
||||
"Lifetime Plan": "জীবনকাল পরিকল্পনা",
|
||||
"One-Time Payment": "এককালীন পেমেন্ট",
|
||||
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "নির্দিষ্ট বৈশিষ্ট্যগুলিতে সমস্ত ডিভাইসে জীবনকাল অ্যাক্সেস উপভোগ করতে এককালীন পেমেন্ট করুন। যখন আপনার প্রয়োজন তখনই নির্দিষ্ট বৈশিষ্ট্য বা পরিষেবাগুলি কিনুন।",
|
||||
"Expand Cloud Sync Storage": "ক্লাউড সিঙ্ক স্টোরেজ বাড়ান",
|
||||
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "এককালীন ক্রয়ের মাধ্যমে আপনার ক্লাউড স্টোরেজ চিরকাল বাড়ান। প্রতিটি অতিরিক্ত ক্রয় আরও স্থান যোগ করে।",
|
||||
"Unlock All Customization Options": "সমস্ত কাস্টমাইজেশন বিকল্প আনলক করুন",
|
||||
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "অতিরিক্ত থিম, ফন্ট, লেআউট বিকল্প এবং উচ্চস্বরে পড়া, অনুবাদক, ক্লাউড স্টোরেজ পরিষেবাগুলি আনলক করুন।",
|
||||
"Purchase Successful!": "ক্রয় সফল!",
|
||||
"lifetime": "জীবনকাল",
|
||||
"Thank you for your purchase! Your payment has been processed successfully.": "আপনার ক্রয়ের জন্য ধন্যবাদ! আপনার পেমেন্ট সফলভাবে প্রক্রিয়া করা হয়েছে।",
|
||||
"Order ID:": "অর্ডার আইডি:",
|
||||
"TTS Highlighting": "টিটিএস হাইলাইটিং",
|
||||
"Style": "শৈলী",
|
||||
"Underline": "আন্ডারলাইন",
|
||||
"Strikethrough": "কাটা লেখা",
|
||||
"Squiggly": "ঢেউখেলানো দাগ",
|
||||
"Outline": "বাহিরের রেখা",
|
||||
"Quick Colors": "দ্রুত রংসমূহ",
|
||||
"Highlighter": "হাইলাইটার",
|
||||
"Save Book Cover": "বইয়ের মলাট সংরক্ষণ করুন",
|
||||
"Auto-save last book cover": "শেষ বইয়ের মলাট স্বয়ংক্রিয়ভাবে সংরক্ষণ করুন",
|
||||
"Back": "পেছনে",
|
||||
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "নিশ্চিতকরণ ইমেল পাঠানো হয়েছে! পরিবর্তন নিশ্চিত করতে আপনার পুরানো এবং নতুন ইমেল ঠিকানা পরীক্ষা করুন।",
|
||||
"Failed to update email": "ইমেল আপডেট করতে ব্যর্থ",
|
||||
"New Email": "নতুন ইমেল",
|
||||
"Your new email": "আপনার নতুন ইমেল",
|
||||
"Updating email ...": "ইমেল আপডেট হচ্ছে ...",
|
||||
"Update email": "ইমেল আপডেট করুন",
|
||||
"Current email": "বর্তমান ইমেল",
|
||||
"Update Email": "ইমেল আপডেট করুন",
|
||||
"All": "সব",
|
||||
"Unable to open book": "বই খুলতে অক্ষম",
|
||||
"Punctuation": "বিরামচিহ্ন",
|
||||
"Replace Quotation Marks": "উদ্ধৃতি চিহ্ন প্রতিস্থাপন করুন",
|
||||
"Enabled only in vertical layout.": "শুধুমাত্র উল্লম্ব বিন্যাসে সক্রিয়।",
|
||||
"No Conversion": "রূপান্তর নয়",
|
||||
"Simplified to Traditional": "সরল → প্রথাগত",
|
||||
"Traditional to Simplified": "প্রথাগত → সরল",
|
||||
"Simplified to Traditional (Taiwan)": "সরল → প্রথাগত (তাইওয়ান)",
|
||||
"Simplified to Traditional (Hong Kong)": "সরল → প্রথাগত (হংকং)",
|
||||
"Simplified to Traditional (Taiwan), with phrases": "সরল → প্রথাগত (তাইওয়ান • বাক্য)",
|
||||
"Traditional (Taiwan) to Simplified": "প্রথাগত (তাইওয়ান) → সরল",
|
||||
"Traditional (Hong Kong) to Simplified": "প্রথাগত (হংকং) → সরল",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "প্রথাগত (তাইওয়ান • বাক্য) → সরল",
|
||||
"Convert Simplified and Traditional Chinese": "সরল/প্রথাগত রূপান্তর",
|
||||
"Convert Mode": "রূপান্তর মোড",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "লক স্ক্রিনের জন্য বইয়ের মলাট স্বয়ংক্রিয়ভাবে সংরক্ষণ করতে ব্যর্থ: {{error}}",
|
||||
"Download from Cloud": "ক্লাউড থেকে ডাউনলোড করুন",
|
||||
"Upload to Cloud": "ক্লাউডে আপলোড করুন",
|
||||
"Clear Custom Fonts": "কাস্টম ফন্টস মুছুন",
|
||||
"Columns": "কলামস",
|
||||
"OPDS Catalogs": "OPDS ক্যাটালগস",
|
||||
"Adding LAN addresses is not supported in the web app version.": "ওয়েব অ্যাপ সংস্করণে LAN ঠিকানা যোগ করা সমর্থিত নয়।",
|
||||
"Invalid OPDS catalog. Please check the URL.": "অবৈধ OPDS ক্যাটালগ। দয়া করে URL চেক করুন।",
|
||||
"Browse and download books from online catalogs": "অনলাইন ক্যাটালগ থেকে বই ব্রাউজ এবং ডাউনলোড করুন",
|
||||
"My Catalogs": "আমার ক্যাটালগস",
|
||||
"Add Catalog": "ক্যাটালগ যোগ করুন",
|
||||
"No catalogs yet": "এখনও কোন ক্যাটালগ নেই",
|
||||
"Add your first OPDS catalog to start browsing books": "আপনার প্রথম OPDS ক্যাটালগ যোগ করুন বই ব্রাউজ শুরু করতে",
|
||||
"Add Your First Catalog": "আপনার প্রথম ক্যাটালগ যোগ করুন",
|
||||
"Browse": "ব্রাউজ করুন",
|
||||
"Popular Catalogs": "জনপ্রিয় ক্যাটালগস",
|
||||
"Add": "যোগ করুন",
|
||||
"Add OPDS Catalog": "OPDS ক্যাটালগ যোগ করুন",
|
||||
"Catalog Name": "ক্যাটালগের নাম",
|
||||
"My Calibre Library": "আমার ক্যালিব্র লাইব্রেরি",
|
||||
"OPDS URL": "OPDS URL",
|
||||
"Username (optional)": "ব্যবহারকারীর নাম (ঐচ্ছিক)",
|
||||
"Password (optional)": "পাসওয়ার্ড (ঐচ্ছিক)",
|
||||
"Description (optional)": "বর্ণনা (ঐচ্ছিক)",
|
||||
"A brief description of this catalog": "এই ক্যাটালগের সংক্ষিপ্ত বিবরণ",
|
||||
"Validating...": "যাচাই করা হচ্ছে...",
|
||||
"View All": "সব দেখুন",
|
||||
"Forward": "ফরোয়ার্ড",
|
||||
"Home": "হোম",
|
||||
"{{count}} items_one": "{{count}} আইটেম",
|
||||
"{{count}} items_other": "{{count}} আইটেম",
|
||||
"Download completed": "ডাউনলোড সম্পন্ন হয়েছে",
|
||||
"Download failed": "ডাউনলোড ব্যর্থ হয়েছে",
|
||||
"Open Access": "ওপেন অ্যাক্সেস",
|
||||
"Borrow": "ঋণ নিন",
|
||||
"Buy": "কিনুন",
|
||||
"Subscribe": "সাবস্ক্রাইব করুন",
|
||||
"Sample": "নমুনা",
|
||||
"Download": "ডাউনলোড",
|
||||
"Open & Read": "খুলুন ও পড়ুন",
|
||||
"Tags": "ট্যাগসমূহ",
|
||||
"Tag": "ট্যাগ",
|
||||
"First": "প্রথম",
|
||||
"Previous": "পূর্ববর্তী",
|
||||
"Next": "পরবর্তী",
|
||||
"Last": "শেষ",
|
||||
"Cannot Load Page": "পৃষ্ঠা লোড করা যায়নি",
|
||||
"An error occurred": "একটি ত্রুটি ঘটেছে",
|
||||
"Online Library": "অনলাইন লাইব্রেরি",
|
||||
"URL must start with http:// or https://": "URL অবশ্যই http:// বা https:// দিয়ে শুরু হতে হবে",
|
||||
"Title, Author, Tag, etc...": "শিরোনাম, লেখক, ট্যাগ, ইত্যাদি...",
|
||||
"Query": "কোয়েরি",
|
||||
"Subject": "বিষয়",
|
||||
"Enter {{terms}}": "{{terms}} লিখুন",
|
||||
"No search results found": "কোনও অনুসন্ধান ফলাফল পাওয়া যায়নি",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS ফিড লোড করতে ব্যর্থ: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}} এ অনুসন্ধান করুন",
|
||||
"Manage Storage": "স্টোরেজ ম্যানেজমেন্ট",
|
||||
"Failed to load files": "ফাইল লোড করতে ব্যর্থ",
|
||||
"Deleted {{count}} file(s)_one": "{{count}}টি ফাইল মুছে ফেলা হয়েছে",
|
||||
"Deleted {{count}} file(s)_other": "{{count}}টি ফাইল মুছে ফেলা হয়েছে",
|
||||
"Failed to delete {{count}} file(s)_one": "{{count}}টি ফাইল মুছতে ব্যর্থ",
|
||||
"Failed to delete {{count}} file(s)_other": "{{count}}টি ফাইল মুছতে ব্যর্থ",
|
||||
"Failed to delete files": "ফাইল মুছতে ব্যর্থ",
|
||||
"Total Files": "মোট ফাইল",
|
||||
"Total Size": "মোট আকার",
|
||||
"Quota": "কোটা",
|
||||
"Used": "ব্যবহৃত",
|
||||
"Files": "ফাইল",
|
||||
"Search files...": "ফাইল খুঁজুন...",
|
||||
"Newest First": "নতুন আগে",
|
||||
"Oldest First": "পুরনো আগে",
|
||||
"Largest First": "বড় আগে",
|
||||
"Smallest First": "ছোট আগে",
|
||||
"Name A-Z": "নাম A-Z",
|
||||
"Name Z-A": "নাম Z-A",
|
||||
"{{count}} selected_one": "{{count}}টি নির্বাচিত",
|
||||
"{{count}} selected_other": "{{count}}টি নির্বাচিত",
|
||||
"Delete Selected": "নির্বাচিত মুছুন",
|
||||
"Created": "তৈরি হয়েছে",
|
||||
"No files found": "কোনো ফাইল পাওয়া যায়নি",
|
||||
"No files uploaded yet": "এখনও কোনো ফাইল আপলোড করা হয়নি",
|
||||
"files": "ফাইল",
|
||||
"Page {{current}} of {{total}}": "{{total}}টির মধ্যে {{current}} পৃষ্ঠা",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "আপনি কি নিশ্চিত যে {{count}}টি নির্বাচিত ফাইল মুছতে চান?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "আপনি কি নিশ্চিত যে {{count}}টি নির্বাচিত ফাইল মুছতে চান?",
|
||||
"Cloud Storage Usage": "ক্লাউড স্টোরেজ ব্যবহৃত",
|
||||
"Rename Group": "গ্রুপের নাম পরিবর্তন করুন",
|
||||
"From Directory": "ডিরেক্টরি থেকে",
|
||||
"Successfully imported {{count}} book(s)_one": "সফলভাবে ১টি বই আমদানি করা হয়েছে",
|
||||
"Successfully imported {{count}} book(s)_other": "সফলভাবে {{count}}টি বই আমদানি করা হয়েছে",
|
||||
"Count": "গণনা",
|
||||
"Start Page": "শুরু পৃষ্ঠা",
|
||||
"Search in OPDS Catalog...": "OPDS ক্যাটালগে অনুসন্ধান করুন...",
|
||||
"Please log in to use advanced TTS features": "উন্নত TTS বৈশিষ্ট্যগুলি ব্যবহার করতে লগইন করুন।",
|
||||
"Word limit of 30 words exceeded.": "৩০ শব্দের শব্দসীমা অতিক্রম হয়েছে।",
|
||||
"Proofread": "প্রুফরিড",
|
||||
"Current selection": "বর্তমান নির্বাচন",
|
||||
"All occurrences in this book": "এই বইয়ে সমস্ত ঘটনা",
|
||||
"All occurrences in your library": "আপনার লাইব্রেরির সমস্ত ঘটনা",
|
||||
"Selected text:": "নির্বাচিত টেক্সট:",
|
||||
"Replace with:": "এর সাথে প্রতিস্থাপন করুন:",
|
||||
"Enter text...": "টেক্সট লিখুন...",
|
||||
"Case sensitive:": "কেস সংবেদনশীল:",
|
||||
"Scope:": "পরিসর:",
|
||||
"Selection": "নির্বাচন",
|
||||
"Library": "লাইব্রেরি",
|
||||
"Yes": "হ্যাঁ",
|
||||
"No": "না",
|
||||
"Proofread Replacement Rules": "প্রুফরিড বিকল্প নিয়ম",
|
||||
"Selected Text Rules": "নির্বাচিত টেক্সট নিয়ম",
|
||||
"No selected text replacement rules": "কোনও নির্বাচিত টেক্সট বিকল্প নিয়ম নেই",
|
||||
"Book Specific Rules": "বই নির্দিষ্ট নিয়ম",
|
||||
"No book-level replacement rules": "কোনও বই-স্তরের বিকল্প নিয়ম নেই",
|
||||
"Disable Quick Action": "দ্রুত ক্রিয়া নিষ্ক্রিয় করুন",
|
||||
"Enable Quick Action on Selection": "নির্বাচনের উপর দ্রুত ক্রিয়া সক্ষম করুন",
|
||||
"None": "কোনোটিই নয়",
|
||||
"Annotation Tools": "অ্যনোটেশন সরঞ্জামসমূহ",
|
||||
"Enable Quick Actions": "দ্রুত ক্রিয়া সক্ষম করুন",
|
||||
"Quick Action": "দ্রুত ক্রিয়া",
|
||||
"Copy to Notebook": "নোটবুকে কপি করুন",
|
||||
"Copy text after selection": "নির্বাচনের পরে টেক্সট কপি করুন",
|
||||
"Highlight text after selection": "নির্বাচনের পরে টেক্সট হাইলাইট করুন",
|
||||
"Annotate text after selection": "নির্বাচনের পরে টেক্সট মন্তব্য করুন",
|
||||
"Search text after selection": "নির্বাচনের পরে টেক্সট অনুসন্ধান করুন",
|
||||
"Look up text in dictionary after selection": "নির্বাচনের পরে টেক্সট অভিধানে দেখুন",
|
||||
"Translate text after selection": "নির্বাচনের পরে টেক্সট অনুবাদ করুন",
|
||||
"Read text aloud after selection": "নির্বাচনের পরে টেক্সট উচ্চারণ করুন",
|
||||
"Proofread text after selection": "নির্বাচনের পরে টেক্সট প্রুফরিড করুন",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} সক্রিয়, {{pendingCount}} অপেক্ষমাণ",
|
||||
"{{failedCount}} failed": "{{failedCount}} ব্যর্থ",
|
||||
"Waiting...": "অপেক্ষা করছে...",
|
||||
"Failed": "ব্যর্থ",
|
||||
"Completed": "সম্পন্ন",
|
||||
"Cancelled": "বাতিল",
|
||||
"Retry": "পুনরায় চেষ্টা করুন",
|
||||
"Active": "সক্রিয়",
|
||||
"Transfer Queue": "স্থানান্তর সারি",
|
||||
"Upload All": "সব আপলোড করুন",
|
||||
"Download All": "সব ডাউনলোড করুন",
|
||||
"Resume Transfers": "স্থানান্তর পুনরায় শুরু করুন",
|
||||
"Pause Transfers": "স্থানান্তর বিরতি দিন",
|
||||
"Pending": "অপেক্ষমাণ",
|
||||
"No transfers": "কোনো স্থানান্তর নেই",
|
||||
"Retry All": "সব পুনরায় চেষ্টা করুন",
|
||||
"Clear Completed": "সম্পন্ন মুছুন",
|
||||
"Clear Failed": "ব্যর্থ মুছুন",
|
||||
"Upload queued: {{title}}": "আপলোড সারিতে: {{title}}",
|
||||
"Download queued: {{title}}": "ডাউনলোড সারিতে: {{title}}",
|
||||
"Book not found in library": "লাইব্রেরিতে বই পাওয়া যায়নি",
|
||||
"Unknown error": "অজানা ত্রুটি",
|
||||
"Please log in to continue": "চালিয়ে যেতে লগইন করুন",
|
||||
"Cloud File Transfers": "ক্লাউড ফাইল স্থানান্তরসমূহ",
|
||||
"Show Search Results": "অনুসন্ধান ফলাফল দেখান",
|
||||
"Search results for '{{term}}'": "'{{term}}' এর ফলাফল",
|
||||
"Close Search": "অনুসন্ধান বন্ধ করুন",
|
||||
"Previous Result": "আগের ফলাফল",
|
||||
"Next Result": "পরবর্তী ফলাফল",
|
||||
"Bookmarks": "বুকমার্ক",
|
||||
"Annotations": "টীকা",
|
||||
"Show Results": "ফলাফল দেখুন",
|
||||
"Clear search": "অনুসন্ধান সাফ করুন",
|
||||
"Clear search history": "অনুসন্ধান ইতিহাস সাফ করুন",
|
||||
"Tap to Toggle Footer": "ফুটার টগল করতে আলতো চাপুন",
|
||||
"Show Current Time": "বর্তমান সময় দেখান",
|
||||
"Use 24 Hour Clock": "২৪ ঘণ্টার ঘড়ি ব্যবহার করুন",
|
||||
"Show Current Battery Status": "বর্তমান ব্যাটারি অবস্থা দেখান",
|
||||
"Exported successfully": "সফলভাবে রপ্তানি হয়েছে",
|
||||
"Book exported successfully.": "বইটি সফলভাবে রপ্তানি হয়েছে।",
|
||||
"Failed to export the book.": "বই রপ্তানি করতে ব্যর্থ।",
|
||||
"Export Book": "বই রপ্তানি করুন",
|
||||
"Whole word:": "সম্পূর্ণ শব্দ:",
|
||||
"Date Published": "প্রকাশনার তারিখ",
|
||||
"Only for TTS:": "শুধুমাত্র TTS-এর জন্য:",
|
||||
"Uploaded": "আপলোড হয়েছে",
|
||||
"Downloaded": "ডাউনলোড হয়েছে",
|
||||
"Deleted": "মুছে ফেলা হয়েছে",
|
||||
"Note:": "নোট:",
|
||||
"Time:": "সময়:",
|
||||
"Format Options": "বিন্যাস বিকল্প",
|
||||
"Export Date": "রপ্তানি তারিখ",
|
||||
"Chapter Titles": "অধ্যায়ের শিরোনাম",
|
||||
"Chapter Separator": "অধ্যায় বিভাজক",
|
||||
"Highlights": "হাইলাইট",
|
||||
"Note Date": "নোট তারিখ",
|
||||
"Advanced": "উন্নত",
|
||||
"Hide": "লুকান",
|
||||
"Show": "দেখান",
|
||||
"Use Custom Template": "কাস্টম টেমপ্লেট ব্যবহার করুন",
|
||||
"Export Template": "রপ্তানি টেমপ্লেট",
|
||||
"Template Syntax:": "টেমপ্লেট সিনট্যাক্স:",
|
||||
"Insert value": "মান সন্নিবেশ করুন",
|
||||
"Format date (locale)": "তারিখ বিন্যাস (স্থানীয়)",
|
||||
"Format date (custom)": "তারিখ বিন্যাস (কাস্টম)",
|
||||
"Conditional": "শর্তসাপেক্ষ",
|
||||
"Loop": "লুপ",
|
||||
"Available Variables:": "উপলব্ধ ভেরিয়েবল:",
|
||||
"Book title": "বইয়ের শিরোনাম",
|
||||
"Book author": "বইয়ের লেখক",
|
||||
"Export date": "রপ্তানি তারিখ",
|
||||
"Array of chapters": "অধ্যায়ের তালিকা",
|
||||
"Chapter title": "অধ্যায়ের শিরোনাম",
|
||||
"Array of annotations": "টীকাগুলির তালিকা",
|
||||
"Highlighted text": "হাইলাইট করা পাঠ্য",
|
||||
"Annotation note": "টীকা নোট",
|
||||
"Date Format Tokens:": "তারিখ বিন্যাস টোকেন:",
|
||||
"Year (4 digits)": "বছর (4 সংখ্যা)",
|
||||
"Month (01-12)": "মাস (01-12)",
|
||||
"Day (01-31)": "দিন (01-31)",
|
||||
"Hour (00-23)": "ঘণ্টা (00-23)",
|
||||
"Minute (00-59)": "মিনিট (00-59)",
|
||||
"Second (00-59)": "সেকেন্ড (00-59)",
|
||||
"Show Source": "উৎস দেখান",
|
||||
"No content to preview": "প্রিভিউ করার জন্য কোনো কন্টেন্ট নেই",
|
||||
"Export": "রপ্তানি",
|
||||
"Set Timeout": "টাইমআউট সেট করুন",
|
||||
"Select Voice": "ভয়েস নির্বাচন করুন",
|
||||
"Toggle Sticky Bottom TTS Bar": "স্থির TTS বার টগল করুন",
|
||||
"Display what I'm reading on Discord": "Discord-এ পড়ছি যা দেখান",
|
||||
"Show on Discord": "Discord-এ দেখান",
|
||||
"Instant {{action}}": "তাৎক্ষণিক {{action}}",
|
||||
"Instant {{action}} Disabled": "তাৎক্ষণিক {{action}} নিষ্ক্রিয়",
|
||||
"Annotation": "টীকা",
|
||||
"Reset Template": "টেমপ্লেট রিসেট করুন",
|
||||
"Annotation style": "টীকা শৈলী",
|
||||
"Annotation color": "টীকা রঙ",
|
||||
"Annotation time": "টীকা সময়",
|
||||
"AI": "কৃত্রিম বুদ্ধিমত্তা (AI)",
|
||||
"Are you sure you want to re-index this book?": "আপনি কি নিশ্চিত যে আপনি এই বইটি পুনরায় ইনডেক্স করতে চান?",
|
||||
"Enable AI in Settings": "সেটিংস থেকে AI সক্রিয় করুন",
|
||||
"Index This Book": "এই বইটি ইনডেক্স করুন",
|
||||
"Enable AI search and chat for this book": "এই বইটির জন্য AI অনুসন্ধান এবং চ্যাট সক্রিয় করুন",
|
||||
"Start Indexing": "ইনডেক্সিং শুরু করুন",
|
||||
"Indexing book...": "বই ইনডেক্স করা হচ্ছে...",
|
||||
"Preparing...": "প্রস্তুত করা হচ্ছে...",
|
||||
"Delete this conversation?": "এই কথোপকথনটি মুছবেন?",
|
||||
"No conversations yet": "এখনও কোনও কথোপকথন নেই",
|
||||
"Start a new chat to ask questions about this book": "এই বইটি সম্পর্কে প্রশ্ন জিজ্ঞাসা করতে একটি নতুন চ্যাট শুরু করুন",
|
||||
"Rename": "নাম পরিবর্তন",
|
||||
"New Chat": "নতুন চ্যাট",
|
||||
"Chat": "চ্যাট",
|
||||
"Please enter a model ID": "দয়া করে একটি মডেল আইডি লিখুন",
|
||||
"Model not available or invalid": "মডেল উপলব্ধ নয় বা অবৈধ",
|
||||
"Failed to validate model": "মডেল যাচাই করতে ব্যর্থ হয়েছে",
|
||||
"Couldn't connect to Ollama. Is it running?": "Ollama এর সাথে সংযোগ করা যায়নি। এটি কি চলছে?",
|
||||
"Invalid API key or connection failed": "অবৈধ API কী বা সংযোগ ব্যর্থ হয়েছে",
|
||||
"Connection failed": "সংযোগ ব্যর্থ হয়েছে",
|
||||
"AI Assistant": "AI সহকারী",
|
||||
"Enable AI Assistant": "AI সহকারী সক্রিয় করুন",
|
||||
"Provider": "প্রদানকারী",
|
||||
"Ollama (Local)": "Ollama (স্থানীয়)",
|
||||
"AI Gateway (Cloud)": "AI গেটওয়ে (ক্লাউড)",
|
||||
"Ollama Configuration": "Ollama কনফিগারেশন",
|
||||
"Refresh Models": "মডেল রিফ্রেশ করুন",
|
||||
"AI Model": "AI মডেল",
|
||||
"No models detected": "কোনও মডেল শনাক্ত করা যায়নি",
|
||||
"AI Gateway Configuration": "AI গেটওয়ে কনফিগারেশন",
|
||||
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "উচ্চ-মানের, সাশ্রয়ী AI মডেলগুলি থেকে নির্বাচন করুন। আপনি নিচে \"কাস্টম মডেল\" নির্বাচন করে আপনার নিজস্ব মডেলও আনতে পারেন।",
|
||||
"API Key": "API কী",
|
||||
"Get Key": "কী পান",
|
||||
"Model": "মডেল",
|
||||
"Custom Model...": "কাস্টম মডেল...",
|
||||
"Custom Model ID": "কাস্টম মডেল আইডি",
|
||||
"Validate": "যাচাই করুন",
|
||||
"Model available": "মডেল উপলব্ধ",
|
||||
"Connection": "সংযোগ",
|
||||
"Test Connection": "সংযোগ পরীক্ষা করুন",
|
||||
"Connected": "সংযুক্ত",
|
||||
"Custom Colors": "কাস্টম রঙ",
|
||||
"Color E-Ink Mode": "কালার ই-ইঙ্ক মোড",
|
||||
"Reading Ruler": "রিডিং রুলার",
|
||||
"Enable Reading Ruler": "রিডিং রুলার সক্ষম করুন",
|
||||
"Lines to Highlight": "হাইলাইট করার জন্য লাইন",
|
||||
"Ruler Color": "রুলারের রঙ",
|
||||
"Command Palette": "কমান্ড প্যালেট",
|
||||
"Search settings and actions...": "সেটিংস এবং অ্যাকশন খুঁজুন...",
|
||||
"No results found for": "এর জন্য কোনো ফলাফল পাওয়া যায়নি",
|
||||
"Type to search settings and actions": "সেটিংস এবং অ্যাকশন খুঁজতে টাইপ করুন",
|
||||
"Recent": "সাম্প্রতিক",
|
||||
"navigate": "নেভিগেট",
|
||||
"select": "নির্বাচন করুন",
|
||||
"close": "বন্ধ করুন",
|
||||
"Search Settings": "সেটিংস খুঁজুন",
|
||||
"Page Margins": "পৃষ্ঠার মার্জিন",
|
||||
"AI Provider": "AI প্রদানকারী",
|
||||
"Ollama URL": "Ollama URL",
|
||||
"Ollama Model": "Ollama মডেল",
|
||||
"AI Gateway Model": "AI গেটওয়ে মডেল",
|
||||
"Actions": "অ্যাকশন",
|
||||
"Navigation": "নেভিগেশন",
|
||||
"Set status for {{count}} book(s)_one": "{{count}} টি বইয়ের স্ট্যাটাস সেট করুন",
|
||||
"Set status for {{count}} book(s)_other": "{{count}} টি বইয়ের স্ট্যাটাস সেট করুন",
|
||||
"Mark as Unread": "পড়া হয়নি হিসেবে চিহ্নিত করুন",
|
||||
"Mark as Finished": "পড়া শেষ হিসেবে চিহ্নিত করুন",
|
||||
"Finished": "শেষ হয়েছে",
|
||||
"Unread": "অপঠিত",
|
||||
"Clear Status": "স্থিতি মুছুন",
|
||||
"Status": "অবস্থা",
|
||||
"Loading": "লোড হচ্ছে...",
|
||||
"Exit Paragraph Mode": "অনুচ্ছেদ মোড থেকে প্রস্থান করুন",
|
||||
"Paragraph Mode": "অনুচ্ছেদ মোড",
|
||||
"Embedding Model": "এম্বেডিং মডেল",
|
||||
"{{count}} book(s) synced_one": "{{count}}টি বই সিঙ্ক করা হয়েছে",
|
||||
"{{count}} book(s) synced_other": "{{count}}টি বই সিঙ্ক করা হয়েছে",
|
||||
"Unable to start RSVP": "RSVP शुरू করতে অসমর্থ",
|
||||
"RSVP not supported for PDF": "PDF-এর জন্য RSVP সমর্থিত নয়",
|
||||
"Select Chapter": "অধ্যায় নির্বাচন করুন",
|
||||
"Context": "প্রসঙ্গ",
|
||||
"Ready": "প্রস্তুত",
|
||||
"Chapter Progress": "অধ্যায় অগ্রগতি",
|
||||
"words": "শব্দ",
|
||||
"{{time}} left": "{{time}} বাকি",
|
||||
"Reading progress": "পড়ার অগ্রগতি",
|
||||
"Skip back 15 words": "১৫ শব্দ পিছিয়ে যান",
|
||||
"Back 15 words (Shift+Left)": "১৫ শব্দ পিছিয়ে যান (Shift+Left)",
|
||||
"Pause (Space)": "বিরতি (Space)",
|
||||
"Play (Space)": "চালান (Space)",
|
||||
"Skip forward 15 words": "১৫ শব্দ এগিয়ে যান",
|
||||
"Forward 15 words (Shift+Right)": "১৫ শব্দ এগিয়ে যান (Shift+Right)",
|
||||
"Decrease speed": "গতি কমান",
|
||||
"Slower (Left/Down)": "ধীর (Left/Down)",
|
||||
"Increase speed": "গতি বাড়ান",
|
||||
"Faster (Right/Up)": "দ্রুত (Right/Up)",
|
||||
"Start RSVP Reading": "RSVP পড়া শুরু করুন",
|
||||
"Choose where to start reading": "কোথা থেকে পড়া শুরু করবেন তা চয়ন করুন",
|
||||
"From Chapter Start": "অধ্যায় শুরু থেকে",
|
||||
"Start reading from the beginning of the chapter": "অধ্যায়ের শুরু থেকে পড়া শুরু করুন",
|
||||
"Resume": "পুনরায় শুরু করুন",
|
||||
"Continue from where you left off": "যেখানে আপনি ছেড়েছিলেন সেখান থেকে চালিয়ে যান",
|
||||
"From Current Page": "বর্তমান পৃষ্ঠা থেকে",
|
||||
"Start from where you are currently reading": "আপনি বর্তমানে যেখানে পড়ছেন সেখান থেকে শুরু করুন",
|
||||
"From Selection": "নির্বাচন থেকে",
|
||||
"Speed Reading Mode": "দ্রুত পাঠ্য মোড",
|
||||
"Scroll left": "বামে স্ক্রোল করুন",
|
||||
"Scroll right": "ডানে স্ক্রোল করুন",
|
||||
"Library Sync Progress": "লাইব্রেরি সিঙ্ক অগ্রগতি",
|
||||
"Back to library": "লাইব্রেরিতে ফিরে যান",
|
||||
"Group by...": "গ্রুপ অনুযায়ী...",
|
||||
"Export as Plain Text": "সাধারণ টেক্সট হিসেবে এক্সপোর্ট করুন",
|
||||
"Export as Markdown": "Markdown হিসেবে এক্সপোর্ট করুন",
|
||||
"Show Page Navigation Buttons": "নেভিগেশন বোতাম",
|
||||
"Page {{number}}": "পৃষ্ঠা {{number}}",
|
||||
"highlight": "হাইলাইট",
|
||||
"underline": "আন্ডারলাইন",
|
||||
"squiggly": "এঁকেবেঁকে চলা",
|
||||
"red": "লাল",
|
||||
"violet": "বেগুনী",
|
||||
"blue": "নীল",
|
||||
"green": "সবুজ",
|
||||
"yellow": "হলুদ",
|
||||
"Select {{style}} style": "{{style}} স্টাইল নির্বাচন করুন",
|
||||
"Select {{color}} color": "{{color}} রঙ নির্বাচন করুন",
|
||||
"Close Book": "বই বন্ধ করুন",
|
||||
"Speed Reading": "দ্রুত পঠন",
|
||||
"Close Speed Reading": "দ্রুত পঠন বন্ধ করুন",
|
||||
"Authors": "লেখকগণ",
|
||||
"Books": "বই",
|
||||
"Groups": "দল",
|
||||
"Back to TTS Location": "টিটিএস অবস্থানে ফিরে যান",
|
||||
"Metadata": "মেটাডেটা",
|
||||
"Image viewer": "চিত্র প্রদর্শক",
|
||||
"Previous Image": "পূর্ববর্তী চিত্র",
|
||||
"Next Image": "পরবর্তী চিত্র",
|
||||
"Zoomed": "জুম করা হয়েছে",
|
||||
"Zoom level": "জুম স্তর",
|
||||
"Table viewer": "টেবিল প্রদর্শক",
|
||||
"Unable to connect to Readwise. Please check your network connection.": "Readwise এর সাথে সংযোগ করতে অক্ষম। আপনার নেটওয়ার্ক সংযোগটি পরীক্ষা করুন।",
|
||||
"Invalid Readwise access token": "অকার্যকর Readwise অ্যাক্সেস টোকেন",
|
||||
"Disconnected from Readwise": "Readwise থেকে সংযোগ বিচ্ছিন্ন হয়েছে",
|
||||
"Never": "কখনো না",
|
||||
"Sync Enabled": "সিঙ্কিং সক্ষম",
|
||||
"Disconnect": "সংযোগ বিচ্ছিন্ন করুন",
|
||||
"Connect your Readwise account to sync highlights.": "হাইলাইটগুলি সিঙ্ক করার জন্য আপনার Readwise অ্যাকাউন্টটি সংযোগ করুন।",
|
||||
"Get your access token at": "আপনার অ্যাক্সেস টোকেনটি এখানে পাবেন",
|
||||
"Access Token": "অ্যাক্সেস টোকেন",
|
||||
"Paste your Readwise access token": "আপনার Readwise অ্যাক্সেস টোকেনটি পেস্ট করুন",
|
||||
"Readwise Sync": "Readwise সিঙ্ক",
|
||||
"Push Highlights": "হাইলাইট পাঠান",
|
||||
"Highlights synced to Readwise": "হাইলাইটগুলি Readwise এ সিঙ্ক করা হয়েছে",
|
||||
"Readwise sync failed: no internet connection": "Readwise সিঙ্ক ব্যর্থ হয়েছে: ইন্টারনেট সংযোগ নেই",
|
||||
"Readwise sync failed: {{error}}": "Readwise সিঙ্ক ব্যর্থ হয়েছে: {{error}}",
|
||||
"System Screen Brightness": "সিস্টেম স্ক্রিন উজ্জ্বলতা",
|
||||
"Page:": "পৃষ্ঠা:",
|
||||
"Page: {{number}}": "পৃষ্ঠা: {{number}}",
|
||||
"Annotation page number": "টীকা পৃষ্ঠা নম্বর",
|
||||
"Translating...": "অনুবাদ করা হচ্ছে...",
|
||||
"Show Battery Percentage": "ব্যাটারি শতাংশ দেখান",
|
||||
"Hide Scrollbar": "স্ক্রলবার লুকান",
|
||||
"Skip to last reading position": "শেষ পড়ার অবস্থানে যান",
|
||||
"Show context": "প্রসঙ্গ দেখান",
|
||||
"Hide context": "প্রসঙ্গ লুকান",
|
||||
"Decrease font size": "ফন্টের আকার কমান",
|
||||
"Increase font size": "ফন্টের আকার বাড়ান",
|
||||
"Focus": "ফোকাস",
|
||||
"Theme color": "থিম রঙ",
|
||||
"Import failed": "আমদানি ব্যর্থ",
|
||||
"Available Formatters:": "উপলব্ধ ফরম্যাটার:",
|
||||
"Format date": "তারিখ ফরম্যাট",
|
||||
"Markdown block quote (> per line)": "Markdown ব্লক কোট (প্রতি লাইনে >)",
|
||||
"Newlines to <br>": "নতুন লাইন থেকে <br>",
|
||||
"Change case": "অক্ষরের কেস পরিবর্তন",
|
||||
"Trim whitespace": "শূন্যস্থান ছাঁটা",
|
||||
"Truncate to n characters": "n অক্ষরে সংক্ষিপ্ত করুন",
|
||||
"Replace text": "টেক্সট প্রতিস্থাপন",
|
||||
"Fallback value": "ফলব্যাক মান",
|
||||
"Get length": "দৈর্ঘ্য নিন",
|
||||
"First/last element": "প্রথম/শেষ উপাদান",
|
||||
"Join array": "অ্যারে যুক্ত করুন",
|
||||
"Backup failed: {{error}}": "ব্যাকআপ ব্যর্থ: {{error}}",
|
||||
"Select Backup": "ব্যাকআপ নির্বাচন করুন",
|
||||
"Restore failed: {{error}}": "পুনরুদ্ধার ব্যর্থ: {{error}}",
|
||||
"Backup & Restore": "ব্যাকআপ ও পুনরুদ্ধার",
|
||||
"Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "আপনার লাইব্রেরির ব্যাকআপ তৈরি করুন বা পূর্ববর্তী ব্যাকআপ থেকে পুনরুদ্ধার করুন। পুনরুদ্ধার আপনার বর্তমান লাইব্রেরির সাথে মার্জ হবে।",
|
||||
"Backup Library": "লাইব্রেরি ব্যাকআপ",
|
||||
"Restore Library": "লাইব্রেরি পুনরুদ্ধার",
|
||||
"Creating backup...": "ব্যাকআপ তৈরি হচ্ছে...",
|
||||
"Restoring library...": "লাইব্রেরি পুনরুদ্ধার হচ্ছে...",
|
||||
"{{current}} of {{total}} items": "{{current}} / {{total}} আইটেম",
|
||||
"Backup completed successfully!": "ব্যাকআপ সফলভাবে সম্পন্ন!",
|
||||
"Restore completed successfully!": "পুনরুদ্ধার সফলভাবে সম্পন্ন!",
|
||||
"Your library has been saved to the selected location.": "আপনার লাইব্রেরি নির্বাচিত স্থানে সংরক্ষিত হয়েছে।",
|
||||
"{{added}} books added, {{updated}} books updated.": "{{added}}টি বই যোগ হয়েছে, {{updated}}টি বই আপডেট হয়েছে।",
|
||||
"Operation failed": "অপারেশন ব্যর্থ",
|
||||
"Loading library...": "লাইব্রেরি লোড হচ্ছে...",
|
||||
"{{count}} books refreshed_one": "{{count}}টি বইয়ের মেটাডেটা রিফ্রেশ হয়েছে",
|
||||
"{{count}} books refreshed_other": "{{count}}টি বইয়ের মেটাডেটা রিফ্রেশ হয়েছে",
|
||||
"Failed to refresh metadata": "মেটাডেটা রিফ্রেশ ব্যর্থ",
|
||||
"Refresh Metadata": "মেটাডেটা রিফ্রেশ",
|
||||
"Keyboard Shortcuts": "কীবোর্ড শর্টকাট",
|
||||
"View all keyboard shortcuts": "সমস্ত কীবোর্ড শর্টকাট দেখুন",
|
||||
"Switch Sidebar Tab": "সাইডবার ট্যাব পরিবর্তন করুন",
|
||||
"Toggle Notebook": "নোটবুক দেখান/লুকান",
|
||||
"Search in Book": "বইয়ে অনুসন্ধান করুন",
|
||||
"Toggle Scroll Mode": "স্ক্রল মোড টগল করুন",
|
||||
"Toggle Select Mode": "নির্বাচন মোড টগল করুন",
|
||||
"Toggle Bookmark": "বুকমার্ক টগল করুন",
|
||||
"Toggle Text to Speech": "টেক্সট টু স্পিচ টগল করুন",
|
||||
"Play / Pause TTS": "TTS চালান / বিরতি দিন",
|
||||
"Toggle Paragraph Mode": "অনুচ্ছেদ মোড টগল করুন",
|
||||
"Highlight Selection": "নির্বাচিত অংশ হাইলাইট করুন",
|
||||
"Underline Selection": "নির্বাচিত অংশে আন্ডারলাইন করুন",
|
||||
"Annotate Selection": "নির্বাচিত অংশে টীকা যোগ করুন",
|
||||
"Search Selection": "নির্বাচিত অংশ অনুসন্ধান করুন",
|
||||
"Copy Selection": "নির্বাচিত অংশ কপি করুন",
|
||||
"Translate Selection": "নির্বাচিত অংশ অনুবাদ করুন",
|
||||
"Dictionary Lookup": "অভিধানে খুঁজুন",
|
||||
"Read Aloud Selection": "নির্বাচিত অংশ জোরে পড়ুন",
|
||||
"Proofread Selection": "নির্বাচিত অংশ প্রুফরিড করুন",
|
||||
"Open Settings": "সেটিংস খুলুন",
|
||||
"Open Command Palette": "কমান্ড প্যালেট খুলুন",
|
||||
"Show Keyboard Shortcuts": "কীবোর্ড শর্টকাট দেখান",
|
||||
"Open Books": "বই খুলুন",
|
||||
"Toggle Fullscreen": "পূর্ণ পর্দা টগল করুন",
|
||||
"Close Window": "উইন্ডো বন্ধ করুন",
|
||||
"Quit App": "অ্যাপ থেকে প্রস্থান করুন",
|
||||
"Go Left / Previous Page": "বামে যান / আগের পৃষ্ঠা",
|
||||
"Go Right / Next Page": "ডানে যান / পরের পৃষ্ঠা",
|
||||
"Go Up": "উপরে যান",
|
||||
"Go Down": "নিচে যান",
|
||||
"Previous Chapter": "আগের অধ্যায়",
|
||||
"Next Chapter": "পরের অধ্যায়",
|
||||
"Scroll Half Page Down": "অর্ধেক পৃষ্ঠা নিচে স্ক্রল করুন",
|
||||
"Scroll Half Page Up": "অর্ধেক পৃষ্ঠা উপরে স্ক্রল করুন",
|
||||
"Save Note": "নোট সংরক্ষণ করুন",
|
||||
"Single Section Scroll": "একক অধ্যায় স্ক্রল",
|
||||
"General": "সাধারণ",
|
||||
"Text to Speech": "পাঠ থেকে বাক্য",
|
||||
"Zoom": "জুম",
|
||||
"Window": "উইন্ডো",
|
||||
"Your Bookshelf": "আপনার বইয়ের তাক",
|
||||
"TTS": "টেক্সট টু স্পিচ",
|
||||
"Media Info": "মিডিয়া তথ্য",
|
||||
"Update Frequency": "আপডেটের ফ্রিকোয়েন্সি",
|
||||
"Every Sentence": "প্রতিটি বাক্য",
|
||||
"Every Paragraph": "প্রতিটি অনুচ্ছেদ",
|
||||
"Every Chapter": "প্রতিটি অধ্যায়",
|
||||
"TTS Media Info Update Frequency": "TTS মিডিয়া তথ্য আপডেট ফ্রিকোয়েন্সি",
|
||||
"Select reading speed": "পড়ার গতি নির্বাচন করুন",
|
||||
"Drag to seek": "খুঁজতে টানুন",
|
||||
"Punctuation Delay": "বিরাম চিহ্ন বিলম্ব",
|
||||
"Please confirm that this OPDS connection will be proxied through Readest servers on the web app before continuing.": "অনুগ্রহ করে নিশ্চিত করুন যে এই OPDS সংযোগটি ওয়েব অ্যাপে Readest সার্ভারের মাধ্যমে প্রক্সি করা হবে।",
|
||||
"Custom Headers (optional)": "কাস্টম হেডার (ঐচ্ছিক)",
|
||||
"Add one header per line using \"Header-Name: value\".": "প্রতি লাইনে একটি হেডার যোগ করুন \"Header-Name: value\" ব্যবহার করে।",
|
||||
"I understand this OPDS connection will be proxied through Readest servers on the web app. If I do not trust Readest with these credentials or headers, I should use the native app instead.": "আমি বুঝতে পারছি যে এই OPDS সংযোগটি ওয়েব অ্যাপে Readest সার্ভারের মাধ্যমে প্রক্সি করা হবে। আমি যদি এই শংসাপত্র বা হেডারগুলির সাথে Readest-কে বিশ্বাস না করি, তবে আমার পরিবর্তে নেটিভ অ্যাপটি ব্যবহার করা উচিত।",
|
||||
"Unable to connect to Hardcover. Please check your network connection.": "Hardcover-এ সংযোগ করা যাচ্ছে না। অনুগ্রহ করে আপনার নেটওয়ার্ক সংযোগ পরীক্ষা করুন।",
|
||||
"Invalid Hardcover API token": "অবৈধ Hardcover API টোকেন",
|
||||
"Disconnected from Hardcover": "Hardcover থেকে সংযোগ বিচ্ছিন্ন",
|
||||
"Connect your Hardcover account to sync reading progress and notes.": "পড়ার অগ্রগতি এবং নোট সিঙ্ক করতে আপনার Hardcover অ্যাকাউন্ট সংযুক্ত করুন।",
|
||||
"Get your API token from hardcover.app → Settings → API.": "hardcover.app → সেটিংস → API থেকে আপনার API টোকেন পান।",
|
||||
"API Token": "API টোকেন",
|
||||
"Paste your Hardcover API token": "আপনার Hardcover API টোকেন পেস্ট করুন",
|
||||
"Hardcover Sync": "Hardcover সিঙ্ক",
|
||||
"Push Notes": "নোট পুশ করুন",
|
||||
"No annotations or excerpts to sync for this book.": "এই বইয়ের জন্য সিঙ্ক করার মতো কোনো টীকা বা উদ্ধৃতি নেই।",
|
||||
"Configure Hardcover in Settings first.": "প্রথমে সেটিংসে Hardcover কনফিগার করুন।",
|
||||
"No new Hardcover note changes to sync.": "সিঙ্ক করার জন্য নতুন কোনো Hardcover নোট পরিবর্তন নেই।",
|
||||
"Hardcover synced: {{inserted}} new, {{updated}} updated, {{skipped}} unchanged": "Hardcover সিঙ্ক সম্পন্ন: {{inserted}} নতুন, {{updated}} আপডেট, {{skipped}} অপরিবর্তিত",
|
||||
"Hardcover notes sync failed: {{error}}": "Hardcover নোট সিঙ্ক ব্যর্থ: {{error}}",
|
||||
"Reading progress synced to Hardcover": "পড়ার অগ্রগতি Hardcover-এ সিঙ্ক হয়েছে",
|
||||
"Hardcover progress sync failed: {{error}}": "Hardcover অগ্রগতি সিঙ্ক ব্যর্থ: {{error}}",
|
||||
"ISBN": "ISBN",
|
||||
"Keep existing source identifier": "বিদ্যমান উৎস শনাক্তকারী রাখুন",
|
||||
"Toggle Toolbar": "টুলবার টগল করুন",
|
||||
"Split Hyphens": "হাইফেন বিভক্ত করুন",
|
||||
"Apply Theme Colors to PDF": "PDF-এ থিম রং প্রয়োগ করুন",
|
||||
"Name": "নাম",
|
||||
"Remove": "সরান",
|
||||
"Edit OPDS Catalog": "OPDS ক্যাটালগ সম্পাদনা",
|
||||
"Save Changes": "পরিবর্তন সংরক্ষণ",
|
||||
"Use Book Layout": "বইয়ের লেআউট ব্যবহার করুন",
|
||||
"End of this section. Continue to the next.": "এই বিভাগের শেষ। পরবর্তী বিভাগে চালিয়ে যান।",
|
||||
"Auto-download": "স্বয়ংক্রিয় ডাউনলোড",
|
||||
"Auto-download new items": "নতুন আইটেম স্বয়ংক্রিয়ভাবে ডাউনলোড করুন",
|
||||
"Automatically download new publications when the app syncs": "অ্যাপ সিঙ্ক হওয়ার সময় নতুন প্রকাশনা স্বয়ংক্রিয়ভাবে ডাউনলোড করুন",
|
||||
"{{count}} new item(s) downloaded from OPDS_one": "OPDS থেকে {{count}}টি নতুন আইটেম ডাউনলোড হয়েছে",
|
||||
"{{count}} new item(s) downloaded from OPDS_other": "OPDS থেকে {{count}}টি নতুন আইটেম ডাউনলোড হয়েছে",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_one": "{{count}}টি OPDS ক্যাটালগ সিঙ্ক করতে ব্যর্থ",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_other": "{{count}}টি OPDS ক্যাটালগ সিঙ্ক করতে ব্যর্থ",
|
||||
"Last synced {{when}}": "শেষ সিঙ্ক হয়েছে {{when}}",
|
||||
"Failed downloads": "ব্যর্থ ডাউনলোড",
|
||||
"No failed downloads": "কোনো ব্যর্থ ডাউনলোড নেই",
|
||||
"Attempts: {{count}}_one": "প্রচেষ্টা: {{count}}",
|
||||
"Attempts: {{count}}_other": "প্রচেষ্টা: {{count}}",
|
||||
"Skip": "এড়িয়ে যান",
|
||||
"Skip all": "সব এড়িয়ে যান",
|
||||
"Retry all": "সব পুনরায় চেষ্টা করুন",
|
||||
"{{count}} failed_one": "{{count}}টি ব্যর্থ",
|
||||
"{{count}} failed_other": "{{count}}টি ব্যর্থ",
|
||||
"(none)": "(কোনোটি না)",
|
||||
"Identifiers": "শনাক্তকারী",
|
||||
"Read (Stream)": "পড়ুন (স্ট্রিম)",
|
||||
"Failed to start stream": "স্ট্রিম শুরু করতে ব্যর্থ",
|
||||
"No dictionaries enabled": "কোনো অভিধান সক্রিয় নেই",
|
||||
"Enable a dictionary in Settings → Language → Dictionaries.": "সেটিংস → ভাষা → অভিধান থেকে একটি অভিধান সক্রিয় করুন।",
|
||||
"Wiktionary": "উইক্শনারি",
|
||||
"Drag to reorder": "পুনঃক্রম করতে টানুন",
|
||||
"Built-in": "বিল্ট-ইন",
|
||||
"Bundle is missing on this device. Re-import to use it.": "এই ডিভাইসে বান্ডলটি নেই। ব্যবহার করতে পুনরায় আমদানি করুন।",
|
||||
"This dictionary format is not supported.": "এই অভিধান বিন্যাস সমর্থিত নয়।",
|
||||
"MDict": "MDict",
|
||||
"StarDict": "StarDict",
|
||||
"Imported {{count}} dictionary_one": "{{count}}টি অভিধান আমদানি করা হয়েছে",
|
||||
"Imported {{count}} dictionary_other": "{{count}}টি অভিধান আমদানি করা হয়েছে",
|
||||
"Skipped incomplete bundles: {{names}}": "অসম্পূর্ণ বান্ডল এড়ানো হয়েছে: {{names}}",
|
||||
"Failed to import dictionary: {{message}}": "অভিধান আমদানি ব্যর্থ: {{message}}",
|
||||
"Dictionaries": "অভিধান",
|
||||
"Delete Dictionary": "অভিধান মুছুন",
|
||||
"Importing…": "আমদানি হচ্ছে…",
|
||||
"Import Dictionary": "অভিধান আমদানি করুন",
|
||||
"No dictionaries available.": "কোনো অভিধান উপলব্ধ নেই।",
|
||||
"StarDict bundles need .ifo, .idx, and .dict.dz files (.syn optional).": "StarDict বান্ডলে .ifo, .idx এবং .dict.dz ফাইল প্রয়োজন (.syn ঐচ্ছিক)।",
|
||||
"Select all the bundle files together when importing.": "আমদানির সময় বান্ডলের সব ফাইল একসঙ্গে নির্বাচন করুন।",
|
||||
"Manage Dictionaries": "অভিধান পরিচালনা",
|
||||
"Select Dictionary Files": "অভিধান ফাইল নির্বাচন করুন",
|
||||
"Read on Wikipedia →": "উইকিপিডিয়ায় পড়ুন →",
|
||||
"This link can't be opened": "এই লিঙ্কটি খোলা যাচ্ছে না",
|
||||
"The annotation link is missing required information. The original link may have been truncated.": "অ্যানোটেশন লিঙ্কে প্রয়োজনীয় তথ্য নেই। মূল লিঙ্কটি কাটা পড়ে থাকতে পারে।",
|
||||
"Go to Readest": "Readest-এ যান",
|
||||
"Open-source ebook reader for everyone, on every device.": "সবার জন্য, প্রতিটি ডিভাইসের জন্য — ওপেন-সোর্স ইবুক রিডার।",
|
||||
"Open in Readest": "Readest-এ খুলুন",
|
||||
"If Readest didn't open automatically, choose an option below:": "Readest স্বয়ংক্রিয়ভাবে না খুললে, নিচের একটি বিকল্প বেছে নিন:",
|
||||
"Continue reading where you left off.": "যেখানে রেখেছিলেন, সেখান থেকেই পড়া চালিয়ে যান।",
|
||||
"Readest logo": "Readest লোগো",
|
||||
"Opening Readest...": "Readest খোলা হচ্ছে…",
|
||||
"Open in Readest app": "Readest অ্যাপে খুলুন",
|
||||
"Continue in browser": "ব্রাউজারে চালিয়ে যান",
|
||||
"Don't have Readest?": "Readest নেই?",
|
||||
"Book not in your library": "এই বইটি আপনার লাইব্রেরিতে নেই",
|
||||
"Share Book": "বই শেয়ার করুন",
|
||||
"Could not create share link": "শেয়ার লিঙ্ক তৈরি করা যায়নি",
|
||||
"Link copied": "লিঙ্ক কপি হয়েছে",
|
||||
"Could not copy link": "লিঙ্ক কপি করা যায়নি",
|
||||
"Share revoked": "শেয়ার বাতিল হয়েছে",
|
||||
"Could not revoke share": "শেয়ার বাতিল করা যায়নি",
|
||||
"Uploading book…": "বই আপলোড হচ্ছে…",
|
||||
"Generating…": "তৈরি হচ্ছে…",
|
||||
"Generate share link": "শেয়ার লিঙ্ক তৈরি করুন",
|
||||
"Share URL": "শেয়ার URL",
|
||||
"Copy link": "লিঙ্ক কপি করুন",
|
||||
"Copied": "কপি হয়েছে",
|
||||
"Share via…": "মাধ্যমে শেয়ার করুন…",
|
||||
"Expires {{date}}": "{{date}} এ মেয়াদ শেষ",
|
||||
"Revoking…": "বাতিল হচ্ছে…",
|
||||
"Revoke share": "শেয়ার বাতিল করুন",
|
||||
"Sign in to share books": "বই শেয়ার করতে সাইন ইন করুন",
|
||||
"Expiring soon": "শীঘ্রই মেয়াদ শেষ হবে",
|
||||
"Missing share token": "শেয়ার টোকেন অনুপস্থিত",
|
||||
"Could not add to your library": "আপনার লাইব্রেরিতে যোগ করা যায়নি",
|
||||
"This share link is no longer available": "এই শেয়ার লিঙ্কটি আর উপলব্ধ নেই",
|
||||
"The original link may have expired or been revoked.": "মূল লিঙ্কটির মেয়াদ শেষ হয়ে থাকতে পারে বা বাতিল হয়েছে।",
|
||||
"Get Readest": "Readest পান",
|
||||
"Loading shared book…": "শেয়ার করা বই লোড হচ্ছে…",
|
||||
"Adding…": "যোগ করা হচ্ছে…",
|
||||
"Add to my library": "আমার লাইব্রেরিতে যোগ করুন",
|
||||
"Could not load your shares": "আপনার শেয়ারগুলো লোড করা যায়নি",
|
||||
"Revoked": "বাতিল",
|
||||
"Expired": "মেয়াদোত্তীর্ণ",
|
||||
"Shared books": "শেয়ার করা বই",
|
||||
"You haven't shared any books yet": "আপনি এখনও কোনো বই শেয়ার করেননি",
|
||||
"Open a book and tap Share to send it to a friend.": "একটি বই খুলুন এবং বন্ধুকে পাঠাতে শেয়ার ট্যাপ করুন।",
|
||||
"{{count}} active_one": "{{count}}টি সক্রিয়",
|
||||
"{{count}} active_other": "{{count}}টি সক্রিয়",
|
||||
"{{count}} downloads_one": "{{count}}টি ডাউনলোড",
|
||||
"{{count}} downloads_other": "{{count}}টি ডাউনলোড",
|
||||
"starts at saved page": "সংরক্ষিত পৃষ্ঠা থেকে শুরু হয়",
|
||||
"More actions": "আরও ক্রিয়াকলাপ",
|
||||
"Loading…": "লোড হচ্ছে…",
|
||||
"Load more": "আরও লোড করুন",
|
||||
"Could not load shared book": "শেয়ার করা বই লোড করা যায়নি",
|
||||
"The share link is missing required information.": "শেয়ার লিঙ্কে প্রয়োজনীয় তথ্য নেই।",
|
||||
"Please check your connection and try again.": "অনুগ্রহ করে আপনার সংযোগ পরীক্ষা করুন এবং আবার চেষ্টা করুন।",
|
||||
"Sign in to import shared books": "শেয়ার করা বই আমদানি করতে সাইন ইন করুন",
|
||||
"Already in your library": "ইতিমধ্যে আপনার লাইব্রেরিতে রয়েছে",
|
||||
"Added to your library": "আপনার লাইব্রেরিতে যোগ করা হয়েছে",
|
||||
"Could not import shared book": "শেয়ার করা বই আমদানি করা যায়নি",
|
||||
"Manage Shared Links": "শেয়ার লিঙ্ক পরিচালনা করুন",
|
||||
"Expires in": "মেয়াদ শেষ হবে",
|
||||
"{{count}} days_one": "১ দিন",
|
||||
"{{count}} days_other": "{{count}} দিন",
|
||||
"Expires in {{count}} days_one": "১ দিনে মেয়াদ শেষ",
|
||||
"Expires in {{count}} days_other": "{{count}} দিনে মেয়াদ শেষ",
|
||||
"Expires in {{count}} hours_one": "১ ঘণ্টায় মেয়াদ শেষ",
|
||||
"Expires in {{count}} hours_other": "{{count}} ঘণ্টায় মেয়াদ শেষ",
|
||||
"Open in app": "অ্যাপে খুলুন",
|
||||
"Shared with you": "আপনার সাথে শেয়ার করা",
|
||||
"Downloading… {{percent}}%": "ডাউনলোড হচ্ছে… {{percent}}%",
|
||||
"Import progress": "আমদানি অগ্রগতি",
|
||||
"Share reading progress": "পড়ার অগ্রগতি শেয়ার করুন",
|
||||
"Includes your reading progress": "আপনার পড়ার অগ্রগতি অন্তর্ভুক্ত",
|
||||
"Synced {{time}}": "{{time}} সিঙ্ক হয়েছে",
|
||||
"Sync Info": "সিঙ্ক তথ্য",
|
||||
"Last Synced": "শেষ সিঙ্ক",
|
||||
"DICT": "DICT",
|
||||
"Slob": "Slob",
|
||||
"DICT bundles need a .index file and a .dict.dz file.": "DICT বান্ডেলের জন্য একটি .index ফাইল এবং একটি .dict.dz ফাইল প্রয়োজন।",
|
||||
"Slob bundles need a .slob file.": "Slob বান্ডেলের জন্য একটি .slob ফাইল প্রয়োজন।",
|
||||
"URL template must start with http(s):// and contain %WORD%.": "URL টেমপ্লেটটি অবশ্যই http(s):// দিয়ে শুরু হতে হবে এবং %WORD% থাকতে হবে।",
|
||||
"Web": "ওয়েব",
|
||||
"Add Web Search": "ওয়েব অনুসন্ধান যোগ করুন",
|
||||
"Edit Web Search": "ওয়েব অনুসন্ধান সম্পাদনা করুন",
|
||||
"e.g. Google": "যেমন Google",
|
||||
"URL Template": "URL টেমপ্লেট",
|
||||
"Use %WORD% where the looked-up word should appear.": "যেখানে অনুসন্ধান করা শব্দ উপস্থিত হওয়া উচিত সেখানে %WORD% ব্যবহার করুন।",
|
||||
"Open the search result in your browser:": "আপনার ব্রাউজারে অনুসন্ধান ফলাফল খুলুন:",
|
||||
"Open in {{name}}": "{{name}}-এ খুলুন",
|
||||
"Book Fingerprint": "বইয়ের ফিঙ্গারপ্রিন্ট",
|
||||
"Search the web": "ওয়েবে অনুসন্ধান করুন",
|
||||
"App deeplink (readest://)": "অ্যাপ ডিপলিংক (readest://)",
|
||||
"Universal web link (https://)": "সর্বজনীন ওয়েব লিঙ্ক (https://)",
|
||||
"Name cannot be empty.": "নাম খালি রাখা যাবে না।",
|
||||
"Replaced {{count}} existing dictionary_one": "{{count}}টি বিদ্যমান অভিধান প্রতিস্থাপিত হয়েছে",
|
||||
"Replaced {{count}} existing dictionary_other": "{{count}}টি বিদ্যমান অভিধান প্রতিস্থাপিত হয়েছে",
|
||||
"Cancel Edit": "সম্পাদনা বাতিল করুন",
|
||||
"Edit Dictionary": "অভিধান সম্পাদনা করুন",
|
||||
"MDict bundles use .mdx files; companion .mdd and .css files are optional.": "MDict বান্ডেল .mdx ফাইল ব্যবহার করে; সহগামী .mdd ও .css ফাইল ঐচ্ছিক।",
|
||||
"Dictionary name": "অভিধানের নাম",
|
||||
"This audio can't play here — the dictionary uses an outdated format. Try one with Opus, MP3, or WAV audio.": "এই অডিও এখানে চালানো যাচ্ছে না — অভিধানটি একটি পুরনো ফর্ম্যাট ব্যবহার করছে। Opus, MP3 বা WAV অডিও সহ একটি ব্যবহার করে দেখুন।",
|
||||
"File uploaded: {{title}}": "ফাইল আপলোড হয়েছে: {{title}}",
|
||||
"File downloaded: {{title}}": "ফাইল ডাউনলোড হয়েছে: {{title}}",
|
||||
"Deleted cloud copy of the file: {{title}}": "ফাইলের ক্লাউড অনুলিপি মুছে ফেলা হয়েছে: {{title}}",
|
||||
"Failed to upload file: {{title}}": "ফাইল আপলোড ব্যর্থ: {{title}}",
|
||||
"Failed to download file: {{title}}": "ফাইল ডাউনলোড ব্যর্থ: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ফাইলের ক্লাউড অনুলিপি মুছতে ব্যর্থ: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% ব্যবহৃত",
|
||||
"Set PIN…": "PIN সেট করুন…",
|
||||
"Require a 4-digit PIN to open Readest": "Readest খুলতে একটি ৪-সংখ্যার PIN প্রয়োজন করুন",
|
||||
"Change PIN…": "PIN পরিবর্তন করুন…",
|
||||
"Disable PIN…": "PIN নিষ্ক্রিয় করুন…",
|
||||
"Sync passphrase ready": "সিঙ্ক পাসফ্রেজ প্রস্তুত",
|
||||
"This permanently deletes the encrypted credentials we sync (e.g., OPDS catalog passwords) on every device. Local copies are preserved. You will need to re-enter the sync passphrase or set a new one. Continue?": "এটি প্রতিটি ডিভাইসে আমরা সিঙ্ক করি এমন এনক্রিপ্ট করা শংসাপত্র (যেমন, OPDS ক্যাটালগ পাসওয়ার্ড) স্থায়ীভাবে মুছে ফেলে। স্থানীয় কপিগুলি সংরক্ষিত থাকে। আপনাকে সিঙ্ক পাসফ্রেজ পুনরায় প্রবেশ করতে হবে বা একটি নতুন সেট করতে হবে। চালিয়ে যাবেন?",
|
||||
"Sync passphrase forgotten — all encrypted fields cleared": "সিঙ্ক পাসফ্রেজ ভুলে গিয়েছেন — সমস্ত এনক্রিপ্ট করা ক্ষেত্র সাফ করা হয়েছে",
|
||||
"Sync passphrase": "সিঙ্ক পাসফ্রেজ",
|
||||
"Set passphrase": "পাসফ্রেজ সেট করুন",
|
||||
"Forgot passphrase": "পাসফ্রেজ ভুলে গেছেন",
|
||||
"Incorrect PIN": "ভুল PIN",
|
||||
"App locked": "অ্যাপ লক করা হয়েছে",
|
||||
"Enter your PIN": "আপনার PIN লিখুন",
|
||||
"Readest is locked. Enter your 4-digit PIN to continue.": "Readest লক করা আছে। চালিয়ে যেতে আপনার ৪-সংখ্যার PIN লিখুন।",
|
||||
"PIN code": "PIN কোড",
|
||||
"Forgetting your PIN locks you out of this device. You'll need to clear the app's data to reset it.": "PIN ভুলে গেলে আপনি এই ডিভাইসে প্রবেশ করতে পারবেন না। এটি রিসেট করতে অ্যাপের ডেটা মুছে ফেলতে হবে।",
|
||||
"Passphrase must be at least 8 characters": "পাসফ্রেজ কমপক্ষে 8 অক্ষরের হতে হবে",
|
||||
"Passphrases do not match": "পাসফ্রেজগুলি মিলছে না",
|
||||
"Set sync passphrase": "সিঙ্ক পাসফ্রেজ সেট করুন",
|
||||
"Enter sync passphrase": "সিঙ্ক পাসফ্রেজ লিখুন",
|
||||
"A sync passphrase encrypts your sensitive fields (like OPDS catalog credentials) before they sync. We never see this passphrase. Pick something memorable — there is no recovery without it.": "একটি সিঙ্ক পাসফ্রেজ আপনার সংবেদনশীল ক্ষেত্রগুলি (যেমন OPDS ক্যাটালগ শংসাপত্র) সিঙ্ক করার আগে এনক্রিপ্ট করে। আমরা কখনই এই পাসফ্রেজ দেখি না। এমন কিছু বেছে নিন যা মনে রাখা যায় — এটি ছাড়া পুনরুদ্ধার সম্ভব নয়।",
|
||||
"Enter the sync passphrase you set on another device to decrypt your synced credentials.": "আপনার সিঙ্ক করা শংসাপত্রগুলি ডিক্রিপ্ট করতে অন্য একটি ডিভাইসে আপনি যে সিঙ্ক পাসফ্রেজ সেট করেছেন তা লিখুন।",
|
||||
"Confirm passphrase": "পাসফ্রেজ নিশ্চিত করুন",
|
||||
"Unlock": "আনলক করুন",
|
||||
"Set PIN": "PIN সেট করুন",
|
||||
"Change PIN": "PIN পরিবর্তন করুন",
|
||||
"Disable PIN": "PIN নিষ্ক্রিয় করুন",
|
||||
"Enter your current PIN, then choose a new 4-digit PIN.": "আপনার বর্তমান PIN লিখুন, তারপর একটি নতুন ৪-সংখ্যার PIN বেছে নিন।",
|
||||
"Enter your current PIN to disable the app lock.": "অ্যাপ লক নিষ্ক্রিয় করতে আপনার বর্তমান PIN লিখুন।",
|
||||
"PIN must be {{length}} digits": "PIN অবশ্যই {{length}} সংখ্যার হতে হবে",
|
||||
"PINs do not match": "PIN মিলছে না",
|
||||
"Current PIN": "বর্তমান PIN",
|
||||
"New PIN": "নতুন PIN",
|
||||
"Confirm new PIN": "নতুন PIN নিশ্চিত করুন",
|
||||
"Manage Sync": "সিঙ্ক ব্যবস্থাপনা",
|
||||
"Imported book files and library metadata": "আমদানি করা বইয়ের ফাইল ও লাইব্রেরির মেটাডেটা",
|
||||
"Last-read position, bookmarks, and per-book preferences": "শেষ পঠিত অবস্থান, বুকমার্ক এবং প্রতি-বই পছন্দসমূহ",
|
||||
"Highlights and notes": "হাইলাইট এবং নোট",
|
||||
"Fonts": "ফন্ট",
|
||||
"Custom font files": "কাস্টম ফন্ট ফাইল",
|
||||
"Backgrounds": "পটভূমিসমূহ",
|
||||
"Custom background textures": "কাস্টম পটভূমির টেক্সচার",
|
||||
"OPDS catalogs": "OPDS ক্যাটালগ",
|
||||
"Saved catalog URLs and (encrypted) credentials": "সংরক্ষিত ক্যাটালগ URL এবং (এনক্রিপ্ট করা) ক্রেডেনশিয়াল",
|
||||
"Wrong sync passphrase — synced credentials could not be decrypted": "ভুল সিঙ্ক পাসফ্রেজ — সিঙ্ক করা ক্রেডেনশিয়াল ডিক্রিপ্ট করা যায়নি",
|
||||
"Sync passphrase data on the server was reset. Re-encrypting your credentials under the new passphrase…": "সার্ভারের সিঙ্ক পাসফ্রেজ ডেটা রিসেট করা হয়েছে। নতুন পাসফ্রেজ দিয়ে আপনার ক্রেডেনশিয়াল পুনরায় এনক্রিপ্ট করা হচ্ছে…",
|
||||
"Failed to decrypt synced credentials": "সিঙ্ক করা ক্রেডেনশিয়াল ডিক্রিপ্ট করতে ব্যর্থ",
|
||||
"Imported dictionary bundles and settings": "আমদানি করা অভিধান বান্ডিল এবং সেটিংস",
|
||||
"Choose what syncs across your devices. Disabling a category stops this device from sending or receiving rows of that kind. Anything already on the server is left alone, re-enabling resumes from where you stopped.": "আপনার ডিভাইসগুলোর মধ্যে কী সিঙ্ক হবে তা বেছে নিন। কোনো বিভাগ নিষ্ক্রিয় করলে এই ডিভাইসটি সেই ধরনের সারি পাঠানো বা গ্রহণ করা বন্ধ করে দেয়। সার্ভারে ইতিমধ্যে থাকা কিছুই পরিবর্তন হয় না, পুনরায় সক্রিয় করলে যেখানে থেমেছিলেন সেখান থেকে আবার শুরু হয়।",
|
||||
"Data Sync": "ডেটা সিঙ্ক",
|
||||
"Manage Fonts": "ফন্ট পরিচালনা",
|
||||
"App settings": "অ্যাপ সেটিংস",
|
||||
"Theme, highlight colours, integrations (KOSync, Readwise, Hardcover), and dictionary order": "থিম, হাইলাইট রং, ইন্টিগ্রেশন (KOSync, Readwise, Hardcover) এবং অভিধানের ক্রম",
|
||||
"Required while Dictionaries sync is enabled": "অভিধান সিঙ্ক সক্রিয় থাকাকালীন প্রয়োজনীয়",
|
||||
"Resets in {{hours}} hr {{minutes}} min": "{{hours}} ঘ {{minutes}} মি পরে রিসেট হবে",
|
||||
"Pick a 4-digit PIN. You will need to enter it every time you open Readest. There is no way to recover a forgotten PIN. Clearing the app data is the only way to reset it.": "একটি ৪-অঙ্কের পিন বেছে নিন। প্রতিবার Readest খোলার সময় আপনাকে এটি প্রবেশ করাতে হবে। ভুলে যাওয়া পিন পুনরুদ্ধারের কোনো উপায় নেই। অ্যাপের ডেটা মুছে ফেলাই এটি রিসেট করার একমাত্র উপায়।",
|
||||
"Credentials": "ক্রেডেনশিয়াল",
|
||||
"Tokens, usernames, and passwords for OPDS, KOReader, Hardcover, and Readwise. When disabled, credentials remain on this device only and are never uploaded.": "OPDS, KOReader, Hardcover এবং Readwise-এর জন্য টোকেন, ব্যবহারকারীর নাম এবং পাসওয়ার্ড। নিষ্ক্রিয় থাকলে ক্রেডেনশিয়ালগুলি কেবল এই ডিভাইসেই থাকে এবং কখনই আপলোড হয় না।",
|
||||
"Sensitive synced fields are encrypted before upload. Set a passphrase now or later when encryption is needed.": "সংবেদনশীল সিঙ্ক করা ক্ষেত্রগুলি আপলোডের আগে এনক্রিপ্ট করা হয়। এনক্রিপশনের প্রয়োজন হলে এখনই অথবা পরে একটি পাসফ্রেজ সেট করুন।",
|
||||
"Saved to this account. You will be prompted for it when decrypting credentials.": "এই অ্যাকাউন্টে সংরক্ষিত। ক্রেডেনশিয়াল ডিক্রিপ্ট করার সময় আপনাকে এটি দিতে বলা হবে।",
|
||||
"Reading progress on this device differs from \"{{deviceName}}\".": "এই ডিভাইসে পড়ার অগ্রগতি \"{{deviceName}}\" থেকে আলাদা।",
|
||||
"This device": "এই ডিভাইস",
|
||||
"Red": "লাল",
|
||||
"Yellow": "হলুদ",
|
||||
"Green": "সবুজ",
|
||||
"Blue": "নীল",
|
||||
"Violet": "বেগুনি",
|
||||
"Edit color": "রঙ সম্পাদনা করুন",
|
||||
"Add custom color": "কাস্টম রঙ যোগ করুন",
|
||||
"Add label": "লেবেল যোগ করুন",
|
||||
"Choose color": "রঙ নির্বাচন করুন",
|
||||
"Connected to Hardcover. Last synced {{time}}.": "Hardcover-এর সাথে সংযুক্ত। সর্বশেষ সিঙ্ক {{time}}।",
|
||||
"Integrations": "ইন্টিগ্রেশন",
|
||||
"Hardcover": "Hardcover",
|
||||
"Connected to Readwise. Last synced {{time}}.": "Readwise-এর সাথে সংযুক্ত। সর্বশেষ সিঙ্ক {{time}}।",
|
||||
"Readwise": "Readwise",
|
||||
"Browse and download books from online catalogs.": "অনলাইন ক্যাটালগ থেকে বই ব্রাউজ এবং ডাউনলোড করুন।",
|
||||
"Connected as {{user}}": "{{user}} হিসাবে সংযুক্ত",
|
||||
"Not connected": "সংযুক্ত নয়",
|
||||
"{{count}} catalog_one": "{{count}}টি ক্যাটালগ",
|
||||
"{{count}} catalog_other": "{{count}}টি ক্যাটালগ",
|
||||
"No catalogs": "কোনো ক্যাটালগ নেই",
|
||||
"Connect Readest to external services for sync, highlights, and catalogs.": "সিঙ্ক, হাইলাইট এবং ক্যাটালগের জন্য Readest-কে বাহ্যিক পরিষেবার সাথে সংযুক্ত করুন।",
|
||||
"Reading Sync": "পড়ার সিঙ্ক",
|
||||
"Content Sources": "কন্টেন্ট উৎস",
|
||||
"Scroll tabs": "ট্যাব স্ক্রোল করুন",
|
||||
"Catalog actions": "ক্যাটালগ অ্যাকশন",
|
||||
"File Content": "ফাইল সামগ্রী",
|
||||
"Start your library": "আপনার লাইব্রেরি শুরু করুন",
|
||||
"Pick a book from your device to add it to your library.": "আপনার লাইব্রেরিতে যোগ করতে ডিভাইস থেকে একটি বই বেছে নিন।",
|
||||
"Drop a book anywhere on this window, or pick one from your computer.": "এই উইন্ডোর যেকোনো জায়গায় একটি বই ছাড়ুন, অথবা আপনার কম্পিউটার থেকে একটি বেছে নিন।",
|
||||
"Sign in to sync your library": "আপনার লাইব্রেরি সিঙ্ক করতে সাইন ইন করুন"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"Auto Mode": "རང་འགུལ་བརྗོད་བྱ།",
|
||||
"Behavior": "བྱ་སྤྱོད།",
|
||||
"Book": "དཔེ་དེབ།",
|
||||
"Book Cover": "དཔེ་དེབ་ཀྱི་སྟོང་ངོས།",
|
||||
"Bookmark": "དཔེ་རྟགས།",
|
||||
"Cancel": "འདོར་བ།",
|
||||
"Chapter": "ལེའུ།",
|
||||
@@ -103,11 +102,9 @@
|
||||
"Updated": "བསྐྱར་བརྗེའི་ཚེས་གྲངས།",
|
||||
"Version {{version}}": "ཐོན་རིམ {{version}}",
|
||||
"Vertical Direction": "ཀྲིང་རིམ།",
|
||||
"Welcome to your library. You can import your books here and read them anytime.": "དཔེ་མཛོད་སྟོང་པ་ཡིན། ཁྱེད་ཀྱིས་ཁྱེད་རང་གི་དཔེ་དེབ་ནང་འདྲེན་བྱས་ནས་དུས་དང་རྣམ་པ་ཀུན་ཏུ་ཀློག་ཐུབ།",
|
||||
"Wikipedia": "ཝེ་ཁི་པི་ཌི་ཡ།",
|
||||
"Writing Mode": "པར་སྒྲིག་གི་རྣམ་པ།",
|
||||
"Your Library": "དཔེ་མཛོད།",
|
||||
"TTS not supported for PDF": "PDF ཡིག་ཆར་ TTS རྒྱབ་སྐྱོར་མི་བྱེད།",
|
||||
"Override Book Font": "དཔེ་དེབ་ཀྱི་ཡིག་གཟུགས་བརྗེ་བ།",
|
||||
"Apply to All Books": "དཔེ་དེབ་ཡོད་རྒུར་སྤྱོད་པ།",
|
||||
"Apply to This Book": "དཔེ་དེབ་འདིར་སྤྱོད་པ།",
|
||||
@@ -174,9 +171,6 @@
|
||||
"Token": "བཀོལ་ཐོགས།",
|
||||
"Your OTP token": "ཁྱེད་ཀྱི་ OTP བཀོལ་ཐོགས།",
|
||||
"Verify token": "བཀོལ་ཐོགས་བདེན་སྦྱོང་།",
|
||||
"Sign in with Google": "Google བེད་སྤྱད་ནས་ནང་བསྐྱོད།",
|
||||
"Sign in with Apple": "Apple བེད་སྤྱད་ནས་ནང་བསྐྱོད།",
|
||||
"Sign in with GitHub": "GitHub བེད་སྤྱད་ནས་ནང་བསྐྱོད།",
|
||||
"Account": "རྩིས་ཐོ།",
|
||||
"Failed to delete user. Please try again later.": "སྤྱོད་མཁན་བསུབ་མ་ཐུབ། ཅུང་ཙམ་སྒུག་ནས་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད་རོགས།",
|
||||
"Community Support": "སྡེ་ཁུལ་གྱི་རྒྱབ་སྐྱོར།",
|
||||
@@ -189,7 +183,6 @@
|
||||
"RTL Direction": "གཡས་ནས་གཡོན་དུ།",
|
||||
"Maximum Column Height": "གྲལ་ཐིག་མཐོ་ཤོས།",
|
||||
"Maximum Column Width": "གྲལ་ཐིག་རྒྱ་ཆེ་ཤོས།",
|
||||
"Continuous Scroll": "རྒྱུན་མཐུད་འཁོར་རྒྱུག",
|
||||
"Fullscreen": "བརྡ་ཁྱབ་ངོས་ཆ་ཚང་།",
|
||||
"No supported files found. Supported formats: {{formats}}": "རྒྱབ་སྐྱོར་བྱེད་པའི་ཡིག་ཆ་མ་རྙེད། རྒྱབ་སྐྱོར་བྱེད་པའི་རྣམ་པ། {{formats}}",
|
||||
"Drop to Import Books": "འདྲུད་འཇོག་བྱས་ནས་དཔེ་དེབ་ནང་འདྲེན།",
|
||||
@@ -220,9 +213,9 @@
|
||||
"Auto Import on File Open": "ཡིག་ཆ་ཁ་ཕྱེ་སྐབས་རང་འགུལ་གྱིས་ནང་འདྲེན།",
|
||||
"LXGW WenKai GB Screen": "ཤ་ཝུ་ཝུན་ཁའེ།",
|
||||
"LXGW WenKai TC": "ཤ་ཝུ་ཝུན་ཁའེ།",
|
||||
"No chapters detected.": "ལེའུ་བརྟག་དཔྱད་བྱུང་མེད།",
|
||||
"Failed to parse the EPUB file.": "EPUB ཡིག་ཆ་འགྲེལ་བཤད་བྱེད་ཐུབ་མེད།",
|
||||
"This book format is not supported.": "དཔེ་དེབ་ཀྱི་རྣམ་པ་འདི་རྒྱབ་སྐྱོར་མི་བྱེད།",
|
||||
"No chapters detected": "ལེའུ་བརྟག་དཔྱད་བྱུང་མེད།",
|
||||
"Failed to parse the EPUB file": "EPUB ཡིག་ཆ་འགྲེལ་བཤད་བྱེད་ཐུབ་མེད།",
|
||||
"This book format is not supported": "དཔེ་དེབ་ཀྱི་རྣམ་པ་འདི་རྒྱབ་སྐྱོར་མི་བྱེད།",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "སྒྱུར་བཅོས་ལེན་ཐུབ་མེད། ནང་བསྐྱོད་བྱས་རྗེས་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད་རོགས།",
|
||||
"Group": "སྡེ་ཚན།",
|
||||
"Always on Top": "སྒེའུ་ཁུང་གནས་སུ་བཀོད་པ།",
|
||||
@@ -259,8 +252,6 @@
|
||||
"(from 'As You Like It', Act II)": "《ཐམས་ཅད་དགའ་བ》ནས་དྲངས་པ། ལེའུ་གཉིས་པ།",
|
||||
"Link Color": "སྦྲེལ་ཐག་གི་ཚོན་མདོག",
|
||||
"Volume Keys for Page Flip": "སྒྲ་ཚད་ལྡེ་མིག་གིས་ཤོག་ལྷེ་བསྒྱུར་བ།",
|
||||
"Clicks for Page Flip": "མནན་ནས་ཤོག་ལྷེ་བསྒྱུར་བ།",
|
||||
"Swap Clicks Area": "མནན་སའི་ཁོངས་སུ་བརྗེ་བ།",
|
||||
"Screen": "བརྡ་ཁྱབ་ངོས།",
|
||||
"Orientation": "བརྡ་ཁྱབ་ངོས་ཀྱི་ཁ་ཕྱོགས།",
|
||||
"Portrait": "ཀྲིང་ངོས།",
|
||||
@@ -295,7 +286,6 @@
|
||||
"Disable Translation": "སྒྱུར་བཅོས་སྒོ་བརྒྱབ།",
|
||||
"Scroll": "འཁོར་རྒྱུག",
|
||||
"Overlap Pixels": "ལྷན་པའི་པིག་སེལ།",
|
||||
"Click": "མནན་པ།",
|
||||
"System Language": "མ་ལག་གི་སྐད་ཡིག",
|
||||
"Security": "བདེ་འཇགས།",
|
||||
"Allow JavaScript": "JavaScript ཆོག་མཆན།",
|
||||
@@ -305,7 +295,6 @@
|
||||
"No notes match your search": "མཐུན་པའི་ཟིན་བྲིས་མ་རྙེད།",
|
||||
"Search notes and excerpts...": "ཟིན་བྲིས་འཚོལ་བཤེར།...",
|
||||
"Sign in to Sync": "ནང་བསྐྱོད་བྱས་རྗེས་སྐད་མཉམ་བྱེད་པ།",
|
||||
"Synced at {{time}}": "སྐད་མཉམ་བྱས་པའི་དུས་ཚོད། {{time}}",
|
||||
"Never synced": "ནམ་ཡང་སྐད་མཉམ་བྱས་མ་མྱོང་།",
|
||||
"Show Remaining Time": "ལྷག་མའི་དུས་ཚོད་མངོན་པ།",
|
||||
"{{time}} min left in chapter": "ལེའུ་འདིར་ད་དུང་ {{time}} སྐར་མ་ལྷག་ཡོད།",
|
||||
@@ -338,10 +327,10 @@
|
||||
"Fit": "མཐུན་སྒྲིག",
|
||||
"Reset {{settings}}": "{{settings}} བསྐྱར་སྒྲིག",
|
||||
"Reset Settings": "སྒྲིག་བཀོད་བསྐྱར་སྒྲིག",
|
||||
"{{count}} pages left in chapter_other": "ལེའུ་འདིར་ད་དུང་ {{count}} ཤོག་ལྷེ་ལྷག་ཡོད།",
|
||||
"{{count}} pages left in chapter_other": "<1>ལེའུ་འདིར་ད་དུང་ </1><0>{{count}}</0><1> ཤོག་ལྷེ་ལྷག་ཡོད།</1>",
|
||||
"Show Remaining Pages": "ལྷག་མའི་ཤོག་ལྷེའི་གྲངས་ཀ་མངོན་པ།",
|
||||
"Source Han Serif CN VF": "སི་ཡོན་སུང་ཐི།",
|
||||
"Huiwen-mincho": "ཧུའེ་ཝུན་མིང་ཁྲའོ་ཐི།",
|
||||
"Source Han Serif CN": "སི་ཡོན་སུང་ཐི།",
|
||||
"Huiwen-MinchoGBK": "ཧུའེ་ཝུན་མིང་ཁྲའོ་ཐི།",
|
||||
"KingHwa_OldSong": "ཅིང་ཧྭ་ལའོ་སུང་ཐི།",
|
||||
"Manage Subscription": "མཚམས་སྦྱོར་དོ་དམ།",
|
||||
"Coming Soon": "ཉེ་བར་སྤྱོད་འགོ་འཛུགས་རྒྱུ།",
|
||||
@@ -361,7 +350,6 @@
|
||||
"Email:": "གློག་རྡུལ་ཡིག་ཟམ།",
|
||||
"Plan:": "ཐུམ་སྒྲིལ།",
|
||||
"Amount:": "དངུལ་གྲངས།",
|
||||
"Subscription ID:": "མཚམས་སྦྱོར་ ID །",
|
||||
"Go to Library": "དཔེ་མཛོད་ལ་འགྲོ།",
|
||||
"Need help? Contact our support team at support@readest.com": "རོགས་རམ་དགོས་སམ། རྒྱབ་སྐྱོར་ཚོགས་པར་འབྲེལ་བ་གནང་རོགས། support@readest.com",
|
||||
"Free Plan": "རིན་མེད་ཐུམ་སྒྲིལ།",
|
||||
@@ -467,7 +455,6 @@
|
||||
"Remove from Device Only": "སྒྲིག་ཆས་ཐོག་ནས་ཕྱིར་འཐེན།",
|
||||
"Failed to connect": "འབྲེལ་བ་བྱས་མ་ཐུབ།",
|
||||
"Disconnected": "འབྲེལ་བ་བྱས་མ་ཐུབ།",
|
||||
"KOReader Sync Settings": "KOReader སྤྲི་དོན་གཞི་འདེམས་པ།",
|
||||
"Sync as {{userDisplayName}}": "ཁྱེད་ཀྱིས {{userDisplayName}} སྤྲི་དོན་གཞི་འདེམས་པ།",
|
||||
"Sync Server Connected": "སྤྲི་དོན་སང་བཞིན་འདེམས་པ།",
|
||||
"Sync Strategy": "སྤྲི་དོན་འབྱུང་ཁུངས།",
|
||||
@@ -476,7 +463,6 @@
|
||||
"Send changes only": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Receive changes only": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Checksum Method": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"File Content (recommended)": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"File Name": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"Device Name": "ཡིག་སྣེ་འབྱུང་ཁུངས།",
|
||||
"Connect to your KOReader Sync server.": "ཁྱེད་ཀྱིས KOReader སྤྲི་དོན་སང་བཞིན་འདེམས་པ།",
|
||||
@@ -487,11 +473,8 @@
|
||||
"Connect": "འབྲེལ་བ་བྱས་པ།",
|
||||
"KOReader Sync": "KOReader སྤྲི་དོན་འདེམས་པ།",
|
||||
"Sync Conflict": "སྤྲི་དོན་འབྱུང་ཁུངས།",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "ཁྱེད་ཀྱིས \"{{deviceName}}\" སྤྲི་དོན་འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"another device": "གཞན་ཡིག་སྣེ།",
|
||||
"Local Progress": "ཀུན་འབྱུང་ཁུངས།",
|
||||
"Remote Progress": "བརྒྱབ་འབྱུང་ཁུངས།",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "འབྱུང་ཁུངས་འདེམས་པ་བྱས་ནས་བརྗེ་བ།",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "ཤོག་ {{page}} དང་ {{total}} ({{percentage}}%)",
|
||||
"Current position": "ད་ལྟའི་གནས་ས།",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "ཕྲན་བུ་ཤོག་ངོས་ {{page}} / {{total}} ({{percentage}}%)",
|
||||
@@ -602,5 +585,807 @@
|
||||
"Opacity": "སྤོ་སྐོར་གྱི་འོད་ཟེར།",
|
||||
"Size": "ཨང་",
|
||||
"Cover": "དེབ་གྱི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Contain": "འབྱོར་བ།"
|
||||
"Contain": "འབྱོར་བ།",
|
||||
"{{number}} pages left in chapter": "<1>དོན་ཚན་ནང་ཤོག་ཨང་ </1><0>{{number}}</0><1> ལོག་གི་འདུག།</1>",
|
||||
"Device": "རྐྱབ་སྐོར",
|
||||
"E-Ink Mode": "ཡིག་སྒྱུར་སྤོ་བ",
|
||||
"Highlight Colors": "མདོག་ཚད་ཀྱི་བསྡུར་བ།",
|
||||
"Pagination": "ཤོག་གདོང་།",
|
||||
"Disable Double Tap": "བརྒྱབ་གདོང་གཉིས་མ་འགྱོད།",
|
||||
"Tap to Paginate": "ཤོག་གདོང་ལ་ཐོག་འགྲོ།",
|
||||
"Click to Paginate": "ཤོག་གདོང་ལ་ཁྱེད་ལུས་འགྲོ།",
|
||||
"Tap Both Sides": "གཡས་གཉིས་ལ་ཐོག་འགྲོ།",
|
||||
"Click Both Sides": "གཡས་གཉིས་ལ་ཁྱེད་ལུས་འགྲོ།",
|
||||
"Swap Tap Sides": "ཐོག་གཡས་གཉིས་བརྗེ་བ།",
|
||||
"Swap Click Sides": "ཁྱེད་ལུས་གཡས་གཉིས་བརྗེ་བ།",
|
||||
"Source and Translated": "ཡིག་སྒྱུར་སྤོ་བ།",
|
||||
"Translated Only": "མདོག་ཚད་ཀྱི་བསྡུར་བ།",
|
||||
"Source Only": "ཡིག་སྒྱུར་སྤོ་བ།",
|
||||
"TTS Text": "TTS ཡིག",
|
||||
"The book file is corrupted": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"The book file is empty": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Failed to open the book file": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"On-Demand Purchase": "অনুরোধে ক্রয়",
|
||||
"Full Customization": "সম্পূর্ণ কাস্টমাইজেশন",
|
||||
"Lifetime Plan": "জীবনকাল পরিকল্পনা",
|
||||
"One-Time Payment": "এককালীন পেমেন্ট",
|
||||
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "নির্দিষ্ট বৈশিষ্ট্যগুলিতে সমস্ত ডিভাইসে জীবনকাল অ্যাক্সেস উপভোগ করতে এককালীন পেমেন্ট করুন। যখন আপনার প্রয়োজন তখনই নির্দিষ্ট বৈশিষ্ট্য বা পরিষেবাগুলি কিনুন।",
|
||||
"Expand Cloud Sync Storage": "ক্লাউড সিঙ্ক স্টোরেজ বাড়ান",
|
||||
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "এককালীন ক্রয়ের মাধ্যমে আপনার ক্লাউড স্টোরেজ চিরকাল বাড়ান। প্রতিটি অতিরিক্ত ক্রয় আরও স্থান যোগ করে।",
|
||||
"Unlock All Customization Options": "সমস্ত কাস্টমাইজেশন বিকল্প আনলক করুন",
|
||||
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "অতিরিক্ত থিম, ফন্ট, লেআউট বিকল্প এবং উচ্চস্বরে পড়া, অনুবাদক, ক্লাউড স্টোরেজ পরিষেবাগুলি আনলক করুন।",
|
||||
"Purchase Successful!": "ক্রয় সফল!",
|
||||
"lifetime": "জীবনকাল",
|
||||
"Thank you for your purchase! Your payment has been processed successfully.": "আপনার ক্রয়ের জন্য ধন্যবাদ! আপনার পেমেন্ট সফলভাবে প্রক্রিয়াকৃত হয়েছে।",
|
||||
"Order ID:": "অর্ডার আইডি:",
|
||||
"TTS Highlighting": "ཡིག་འབྲུ་སྦྱོར་བའི་གསལ་བཤད།",
|
||||
"Style": "བཟོ་ལྟ།",
|
||||
"Underline": "འོག་ཐིག",
|
||||
"Strikethrough": "འཐེན་ཐིག",
|
||||
"Squiggly": "འཁྱོག་ཐིག",
|
||||
"Outline": "མཐའ་ཐིག",
|
||||
"Quick Colors": "མྱུར་ཚོན།",
|
||||
"Highlighter": "མདོག་ཚད་ཀྱི་བསྡུར་བ།",
|
||||
"Save Book Cover": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Auto-save last book cover": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Back": "ལོག་བྱེད།",
|
||||
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "གཏན་འཁེལ་ཡིག་འཕྲིན་བཏང་ཡོད། ཡིག་འཕྲིན་རྣམས་གཉིས་ལ་བལྟས་ནས་བརྗེ་བ་དེ་གཏན་འཁེལ་བྱོས།",
|
||||
"Failed to update email": "ཡིག་འཕྲིན་གསར་བསྒྱུར་ཕམ་པ།",
|
||||
"New Email": "ཡིག་འཕྲིན་གསར་པ།",
|
||||
"Your new email": "ཁྱེད་ཀྱི་ཡིག་འཕྲིན་གསར་པ།",
|
||||
"Updating email ...": "ཡིག་འཕྲིན་གསར་བསྒྱུར་བཞིན་པ། ...",
|
||||
"Update email": "ཡིག་འཕྲིན་གསར་བསྒྱུར།",
|
||||
"Current email": "མིག་སྔའི་ཡིག་འཕྲིན།",
|
||||
"Update Email": "ཡིག་འཕྲིན་གསར་བསྒྱུར།",
|
||||
"All": "ཆེན་མོ།",
|
||||
"Unable to open book": "དེབ་ཀྱི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Punctuation": "ཚིག་གྲུབ།",
|
||||
"Replace Quotation Marks": "འདྲེན་འབྲེལ་མཐུད་ཐིག་བརྗེ་བ།",
|
||||
"Enabled only in vertical layout.": "གཡས་སྒྲིག་ནང་མིང་བརྗེ་བ།",
|
||||
"No Conversion": "བསྒྱུར་བཤེར་མེད་",
|
||||
"Simplified to Traditional": "སྟོང་བཟོས → སྲོལ་རྒྱུན",
|
||||
"Traditional to Simplified": "སྲོལ་རྒྱུན → སྟོང་བཟོས",
|
||||
"Simplified to Traditional (Taiwan)": "སྟོང་བཟོས → སྲོལ་རྒྱུན (ཏའི་ཝན)",
|
||||
"Simplified to Traditional (Hong Kong)": "སྟོང་བཟོས → སྲོལ་རྒྱུན (ཧོང་ཀོང)",
|
||||
"Simplified to Traditional (Taiwan), with phrases": "སྟོང་བཟོས → སྲོལ་རྒྱུན (ཏའི་ཝན • ཚིག་ཕྲེང)",
|
||||
"Traditional (Taiwan) to Simplified": "སྲོལ་རྒྱུན (ཏའི་ཝན) → སྟོང་བཟོས",
|
||||
"Traditional (Hong Kong) to Simplified": "སྲོལ་རྒྱུན (ཧོང་ཀོང) → སྟོང་བཟོས",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "སྲོལ་རྒྱུན (ཏའི་ཝན • ཚིག་ཕྲེང) → སྟོང་བཟོས",
|
||||
"Convert Simplified and Traditional Chinese": "རྒྱ་ཡིག སྟོང་བཟོས/སྲོལ་རྒྱུན བསྒྱུར་བ",
|
||||
"Convert Mode": "བསྒྱུར་བའི་རྣམ་པ",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "སྒྲོམ་སྒོར་དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།: {{error}}",
|
||||
"Download from Cloud": "སྤྲིན་ནས་ཕབ་སྟེ་འབེབས་",
|
||||
"Upload to Cloud": "སྤྲིན་ལ་ཡར་སྤེལ་",
|
||||
"Clear Custom Fonts": "རང་བཞིན་ཡིག་གཟུགས་སུབ་པ།",
|
||||
"Columns": "མཚན་ཉིད།",
|
||||
"OPDS Catalogs": "OPDS གནས་ཚུལ།",
|
||||
"Adding LAN addresses is not supported in the web app version.": "གནས་ཚུལ་དང་ལུས་སྐོར་གྱི་གྲོང་ཁྱེར་གྱི་གློག་འཕྲིན་ལས་མཐུན་པ་མེད།",
|
||||
"Invalid OPDS catalog. Please check the URL.": "མི་འབྱུང་བའི OPDS གནས་ཚུལ། ཨེ་ཨོ་ཨེར་ལུས་སྐོར་བཟོ་བྱས།",
|
||||
"Browse and download books from online catalogs": "དེབ་གནས་བཅས་པའི་གྲོང་ཁྱེར་ལས་དེབ་འདེམས་དང་ཕབ་སྟེ་འབེབས།",
|
||||
"My Catalogs": "ངའི་དཀར་ཆག",
|
||||
"Add Catalog": "དཀར་ཆག་ཁ་སྣོན་",
|
||||
"No catalogs yet": "དཀར་ཆག་མེད་པས།",
|
||||
"Add your first OPDS catalog to start browsing books": "དེབ་ལྟ་བཤེར་འགོ་བཙུགས་ནས་ དང་པོའི་ OPDS དཀར་ཆག་ཁ་སྣོན་གནང་།",
|
||||
"Add Your First Catalog": "དཀར་ཆག་དང་པོ་ཁ་སྣོན་",
|
||||
"Browse": "ལྟ་བཤེར་",
|
||||
"Popular Catalogs": "མི་མང་མཐོང་བའི་དཀར་ཆག",
|
||||
"Add": "ཁ་སྣོན་",
|
||||
"Add OPDS Catalog": "OPDS དཀར་ཆག་ཁ་སྣོན་",
|
||||
"Catalog Name": "དཀར་ཆག་མིང་",
|
||||
"My Calibre Library": "ངའི Calibre དེབ་མཛོད་",
|
||||
"OPDS URL": "OPDS URL",
|
||||
"Username (optional)": "མིང་སྒྲོམ་ (འདེམས་རུང་)",
|
||||
"Password (optional)": "གསང་ཚིག (འདེམས་རུང་)",
|
||||
"Description (optional)": "འགྲེལ་བཤད་ (འདེམས་རུང་)",
|
||||
"A brief description of this catalog": "དཀར་ཆག་འདིའི་གསལ་བཤད་ཐུང་ཐུང་",
|
||||
"Validating...": "བདེན་སྦྱོར་བཞིན་...",
|
||||
"View All": "ཡོངས་ལྟ་བ་",
|
||||
"Forward": "མདུན་དུ་",
|
||||
"Home": "གཙོ་ངོས་",
|
||||
"{{count}} items_other": "{{count}} རྣམ་གྲངས་",
|
||||
"Download completed": "ཕབ་ལེན་རྫོགས་སོང་།",
|
||||
"Download failed": "ཕབ་ལེན་ཕམ་པ་",
|
||||
"Open Access": "ཁ་ཕྱབ་ལྟ་བཤེར་",
|
||||
"Borrow": "ལེན་པ་",
|
||||
"Buy": "ཉོ་",
|
||||
"Subscribe": "མཁོ་མངགས་",
|
||||
"Sample": "དཔེ་དབང་",
|
||||
"Download": "ཕབ་ལེན་",
|
||||
"Open & Read": "ཁ་ཕྱེས་ནས་ཀློག་",
|
||||
"Tags": "མཚོན་འགྲེལ་",
|
||||
"Tag": "མཚོན་འགྲེལ་",
|
||||
"First": "དང་པོ།",
|
||||
"Previous": "སྔོན་མ།",
|
||||
"Next": "རྗེས་མ།",
|
||||
"Last": "མཐའ་མ།",
|
||||
"Cannot Load Page": "ཤོག་ངོས་འགུལ་སྐྱོང་བྱེད་ཐུབ་མེད།",
|
||||
"An error occurred": "ནོར་འཁྲུལ་ཞིག་བྱུང་སོང་།",
|
||||
"Online Library": "དྲ་རྒྱུན་དེབ་མཛོད།",
|
||||
"URL must start with http:// or https://": "URL ནི་ http:// ཡང་ https:// ནས་འགོ་བཙུགས་དགོ།",
|
||||
"Title, Author, Tag, etc...": "མིང་།, རྩོམ་པ།, མཚོན་འགྲེལ།, དེ་ལས་སྐུགས་...",
|
||||
"Query": "འཚོལ་ཞིབ་",
|
||||
"Subject": "དོན་ཚན་",
|
||||
"Enter {{terms}}": "{{terms}} ལ་འགྲོ།",
|
||||
"No search results found": "འཚོལ་ཞིབ་རྫོགས་མ་ཐུབ།",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS འཕྲིན་འདེམས་བྱས་མ་ཐུབ།: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}} ནང་འཚོལ།",
|
||||
"Manage Storage": "སྣོད་གསོག་དོ་དམ་བྱེད་པ",
|
||||
"Failed to load files": "ཡིག་ཆ་སྣོན་པ་ཕམ་པ",
|
||||
"Deleted {{count}} file(s)_other": "ཡིག་ཆ་ {{count}} བསུབས་ཟིན་པ",
|
||||
"Failed to delete {{count}} file(s)_other": "ཡིག་ཆ་ {{count}} བསུབས་པ་ཕམ་པ",
|
||||
"Failed to delete files": "ཡིག་ཆ་བསུབས་པ་ཕམ་པ",
|
||||
"Total Files": "ཡིག་ཆ་ཡོངས་བསྡོམས",
|
||||
"Total Size": "ཆེས་ཆེར་ཆེ་ཆུང",
|
||||
"Quota": "ཁུལ་ཚད",
|
||||
"Used": "ལག་ལེན་བྱས་ཟིན་པ",
|
||||
"Files": "ཡིག་ཆ",
|
||||
"Search files...": "ཡིག་ཆ་འཚོལ...",
|
||||
"Newest First": "གསར་ཤོས་སྔོན་དུ",
|
||||
"Oldest First": "རྙིང་ཤོས་སྔོན་དུ",
|
||||
"Largest First": "ཆེ་ཤོས་སྔོན་དུ",
|
||||
"Smallest First": "ཆུང་ཤོས་སྔོན་དུ",
|
||||
"Name A-Z": "མིང་ A-Z",
|
||||
"Name Z-A": "མིང་ Z-A",
|
||||
"{{count}} selected_other": "{{count}} ཡིག་ཆ་འདེམས་ཟིན་པ",
|
||||
"Delete Selected": "འདེམས་པ་བསུབས་པ",
|
||||
"Created": "སྤེལ་བྱས་ཟིན་པ",
|
||||
"No files found": "ཡིག་ཆ་མ་རྙེད་པ",
|
||||
"No files uploaded yet": "ད་ཚུན་ཡིག་ཆ་སྣོན་མི་འདུག",
|
||||
"files": "ཡིག་ཆ",
|
||||
"Page {{current}} of {{total}}": "ཤོག་ངོས་ {{total}} ནས་ {{current}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "ཁྱེད་ཀྱིས་འདེམས་པའི་ཡིག་ཆ་ {{count}} བསུབས་དགོས་པ་ངེས་ཡིན་ན?",
|
||||
"Cloud Storage Usage": "སྤྲིན་གནས་སྣོད་གསོག་ལུས་སྐོར།",
|
||||
"Rename Group": "ཚོགས་མིང་བསྒྱུར་བ།",
|
||||
"From Directory": "སྐོར་འདེམས་པ་ནས།",
|
||||
"Successfully imported {{count}} book(s)_other": "སྤྲིན་ནས་ཕབ་སྟེ་འབེབས་ {{count}} དེབ་འདེམས་སོང་",
|
||||
"Count": "ཨང་",
|
||||
"Start Page": "ཤོག་ངོས་དང་པོ",
|
||||
"Search in OPDS Catalog...": "OPDS དཀར་ཆག་ནང་འཚོལ།...",
|
||||
"Please log in to use advanced TTS features": "དབང་བསྐྱོད་ཀྱི TTS རྣམ་པ་ཚུགས་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Word limit of 30 words exceeded.": "ཚིག་གཅིག 30 ལྟར་འདོད་མེད།",
|
||||
"Proofread": "མིག་སྔའི་བཤད་རྒྱུན།",
|
||||
"Current selection": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"All occurrences in this book": "དེབ་ནང་གི་འདི་ཚུགས་ཀྱི་འཚོལ་ཞིབ།",
|
||||
"All occurrences in your library": "ཁྱོད་ཀྱི་དེབ་མཛོད་ནང་གི་འདི་ཚུགས་ཀྱི་འཚོལ་ཞིབ།",
|
||||
"Selected text:": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Replace with:": "བརྗེ་བ།:",
|
||||
"Enter text...": "ཡིག་ཆ་ལ་འགྲོ།...",
|
||||
"Case sensitive:": "གནས་སྟངས་འདི་ལ་འབད་དགོས།",
|
||||
"Scope:": "གནས་ཚུལ།:",
|
||||
"Selection": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Library": "དེབ་མཛོད།",
|
||||
"Yes": "ཨིན་",
|
||||
"No": "མེད་",
|
||||
"Proofread Replacement Rules": "མིག་སྔའི་བཤད་རྒྱུན་བརྗེ་བའི་རྣམ་པ།",
|
||||
"Selected Text Rules": "བདམས་པའི་ཡིག་ཆ་བརྗེ་སྒྱུར་སྲིད་སྒྲིག",
|
||||
"No selected text replacement rules": "བདམས་པའི་ཡིག་ཆ་བརྗེ་སྒྱུར་སྲིད་སྒྲིག་མེད་པ",
|
||||
"Book Specific Rules": "དེབ་སྒོས་ཀྱི་བརྗེ་སྒྱུར་སྲིད་སྒྲིག",
|
||||
"No book-level replacement rules": "དེབ་རིམ་གྱི་བརྗེ་སྒྱུར་སྲིད་སྒྲིག་མེད་པ",
|
||||
"Disable Quick Action": "མགྱོགས་མྱུར་བྱ་བ་སྤང་",
|
||||
"Enable Quick Action on Selection": "འདེམས་པའི་སྐབས་མགྱོགས་མྱུར་བྱ་བ་སྤྱོད་",
|
||||
"None": "མེད་",
|
||||
"Annotation Tools": "མཆན་འགྲེལ་ལག་ཆ་",
|
||||
"Enable Quick Actions": "མགྱོགས་མྱུར་བྱ་བ་སྤྱོད་",
|
||||
"Quick Action": "མགྱོགས་མྱུར་བྱ་བ་",
|
||||
"Copy to Notebook": "ཟིན་དེབ་ནང་འདྲ་བཤུས་",
|
||||
"Copy text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་འདྲ་བཤུས་",
|
||||
"Highlight text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་མངོན་གསལ་བྱེད་",
|
||||
"Annotate text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་མཆན་འགྲེལ་བྱེད་",
|
||||
"Search text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་འཚོལ་ཞིབ་བྱེད་",
|
||||
"Look up text in dictionary after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་ཚིག་མཛོད་ནང་འཚོལ་",
|
||||
"Translate text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་སྐད་བསྒྱུར་བྱེད་",
|
||||
"Read text aloud after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་སྐད་ཀྱིས་ཀློག་",
|
||||
"Proofread text after selection": "ཡིག་ཆ་འདེམས་པའི་ཐབས་ཀྱིས་ཞིབ་བཤེར་བྱེད་",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} བྱེད་བཞིན་པ་, {{pendingCount}} སྒུག་བཞིན་པ་",
|
||||
"{{failedCount}} failed": "{{failedCount}} ཕམ་པ་",
|
||||
"Waiting...": "སྒུག་བཞིན་པ་...",
|
||||
"Failed": "ཕམ་པ་",
|
||||
"Completed": "གྲུབ་སོང་",
|
||||
"Cancelled": "དོར་སོང་",
|
||||
"Retry": "བསྐྱར་ཚོད་",
|
||||
"Active": "བྱེད་བཞིན་པ་",
|
||||
"Transfer Queue": "སྐྱེལ་འདྲེན་སྒྲིག་གཅོད་",
|
||||
"Upload All": "ཚང་མ་འཇོག་",
|
||||
"Download All": "ཚང་མ་ལེན་",
|
||||
"Resume Transfers": "སྐྱེལ་འདྲེན་མུ་མཐུད་",
|
||||
"Pause Transfers": "སྐྱེལ་འདྲེན་མཚམས་འཇོག་",
|
||||
"Pending": "སྒུག་བཞིན་པ་",
|
||||
"No transfers": "སྐྱེལ་འདྲེན་མེད་",
|
||||
"Retry All": "ཚང་མ་བསྐྱར་ཚོད་",
|
||||
"Clear Completed": "གྲུབ་པ་གསལ་བ་",
|
||||
"Clear Failed": "ཕམ་པ་གསལ་བ་",
|
||||
"Upload queued: {{title}}": "འཇོག་པའི་སྒྲིག་གཅོད་ནང་བཅུག་: {{title}}",
|
||||
"Download queued: {{title}}": "ལེན་པའི་སྒྲིག་གཅོད་ནང་བཅུག་: {{title}}",
|
||||
"Book not found in library": "དཔེ་མཛོད་ནང་དཔེ་ཆ་རྙེད་མ་སོང་",
|
||||
"Unknown error": "མ་ཤེས་པའི་ནོར་འཁྲུལ་",
|
||||
"Please log in to continue": "མུ་མཐུད་པར་ནང་འཛུལ་བྱོས་",
|
||||
"Cloud File Transfers": "སྤྲིན་གནས་ཡིག་ཆ་སྐྱེལ་འདྲེན།",
|
||||
"Show Search Results": "འཚོལ་བཤེར་འབྲས་བུ་སྟོན།",
|
||||
"Search results for '{{term}}'": "'{{term}}'ཡི་འབྲས་བུ།",
|
||||
"Close Search": "འཚོལ་བཤེར་སྒོ་རྒྱག",
|
||||
"Previous Result": "སྔོན་མའི་འབྲས་བུ།",
|
||||
"Next Result": "རྗེས་མའི་འབྲས་བུ།",
|
||||
"Bookmarks": "དཔེ་རྟགས།",
|
||||
"Annotations": "མཆན།",
|
||||
"Show Results": "འབྲས་བུ་སྟོན།",
|
||||
"Clear search": "འཚོལ་བཤེར་གཙང་སེལ།",
|
||||
"Clear search history": "འཚོལ་བཤེར་ལོ་རྒྱུས་གཙང་སེལ།",
|
||||
"Tap to Toggle Footer": "ཞབས་མཇུག་སྒོ་འབྱེད་བྱེད་པར་གནོན།",
|
||||
"Show Current Time": "ད་ལྟའི་དུས་ཚོད་སྟོན་པ།",
|
||||
"Use 24 Hour Clock": "ཆུ་ཚོད་ ༢༤ ཅན་གྱི་ཟློས་འཁོར་བཀོལ་བ།",
|
||||
"Show Current Battery Status": "ད་ལྟའི་གློག་རྫས་གནས་བབ་སྟོན་པ།",
|
||||
"Exported successfully": "ཕྱིར་འདོན་ལེགས་གྲུབ་བྱུང་།",
|
||||
"Book exported successfully.": "དཔེ་དེབ་ཕྱིར་འདོན་ལེགས་གྲུབ་བྱུང་།",
|
||||
"Failed to export the book.": "དཔེ་དེབ་ཕྱིར་འདོན་མི་ཐུབ།",
|
||||
"Export Book": "དཔེ་དེབ་ཕྱིར་འདོན།",
|
||||
"Whole word:": "ཚིག་གྲུབ་ཆ་ཚང་:",
|
||||
"Date Published": "པར་སྐྲུན་ཚེས་གྲངས།",
|
||||
"Only for TTS:": "TTS ལ་ཁོ་ན།:",
|
||||
"Uploaded": "ཡར་སྐྱེལ་བྱས་ཟིན།",
|
||||
"Downloaded": "མར་ལེན་བྱས་ཟིན།",
|
||||
"Deleted": "བསུབས་ཟིན།",
|
||||
"Note:": "གསལ་བཤད།:",
|
||||
"Time:": "དུས་ཚོད།:",
|
||||
"Format Options": "རྣམ་གཞག་གདམ་ག",
|
||||
"Export Date": "ཕྱིར་འདོན་ཚེས་གྲངས།",
|
||||
"Chapter Titles": "ལེའུའི་མཚན།",
|
||||
"Chapter Separator": "ལེའུའི་དབར་མཚམས།",
|
||||
"Highlights": "གཙོ་གནད།",
|
||||
"Note Date": "གསལ་བཤད་ཚེས་གྲངས།",
|
||||
"Advanced": "མཐོ་རིམ།",
|
||||
"Hide": "སྦས་པ།",
|
||||
"Show": "མངོན་པ།",
|
||||
"Use Custom Template": "རང་བཟོའི་དཔེ་གཞི་བེད་སྤྱོད།",
|
||||
"Export Template": "ཕྱིར་འདོན་དཔེ་གཞི།",
|
||||
"Template Syntax:": "དཔེ་གཞིའི་སྒྲིག་གཞི།:",
|
||||
"Insert value": "གྲངས་ཐང་བཙུགས་པ།",
|
||||
"Format date (locale)": "ཚེས་གྲངས་རྣམ་གཞག (ས་གནས།)",
|
||||
"Format date (custom)": "ཚེས་གྲངས་རྣམ་གཞག (རང་བཟོ།)",
|
||||
"Conditional": "དམིགས་བསལ།",
|
||||
"Loop": "འཁོར་ལོ།",
|
||||
"Available Variables:": "བེད་སྤྱད་རུང་བའི་འགྱུར་ཅན།:",
|
||||
"Book title": "དེབ་མཚན།",
|
||||
"Book author": "དེབ་རྩོམ་པ་པོ།",
|
||||
"Export date": "ཕྱིར་འདོན་ཚེས་གྲངས།",
|
||||
"Array of chapters": "ལེའུའི་ལེབ་ངོས།",
|
||||
"Chapter title": "ལེའུའི་མཚན།",
|
||||
"Array of annotations": "གསལ་བཤད་ལེབ་ངོས།",
|
||||
"Highlighted text": "གཙོ་གནད་ཡིག་རྐྱང་།",
|
||||
"Annotation note": "གསལ་བཤད་ཟིན་བྲིས།",
|
||||
"Date Format Tokens:": "ཚེས་གྲངས་རྣམ་གཞག་རྟགས།:",
|
||||
"Year (4 digits)": "ལོ། (གྲངས་ཐང་4)",
|
||||
"Month (01-12)": "ཟླ། (01-12)",
|
||||
"Day (01-31)": "ཚེས། (01-31)",
|
||||
"Hour (00-23)": "ཆུ་ཚོད། (00-23)",
|
||||
"Minute (00-59)": "སྐར་མ། (00-59)",
|
||||
"Second (00-59)": "སྐར་ཆ། (00-59)",
|
||||
"Show Source": "འབྱུང་ཁུངས་མངོན་པ།",
|
||||
"No content to preview": "སྔོན་ལྟ་བྱེད་པའི་དོན་རྐྱེན་མེད།",
|
||||
"Export": "ཕྱིར་འདོན།",
|
||||
"Set Timeout": "དུས་ཚོད་སྒྲིག་པ།",
|
||||
"Select Voice": "སྐད་གདངས་འདེམས།",
|
||||
"Toggle Sticky Bottom TTS Bar": "TTS སྡོམ་ཐིག་བརྗེ་བ།",
|
||||
"Display what I'm reading on Discord": "Discord ཐོག་ཀློག་བཞིན་པའི་དཔེ་ཆ་སྟོན།",
|
||||
"Show on Discord": "Discord ཐོག་སྟོན།",
|
||||
"Instant {{action}}": "འཕྲལ་མར་{{action}}",
|
||||
"Instant {{action}} Disabled": "འཕྲལ་མར་{{action}}་ལྕོགས་མིན་བཟོས།",
|
||||
"Annotation": "མཆན་འགྲེལ།",
|
||||
"Reset Template": "དཔེ་གཞི་བསྐྱར་སྒྲིག",
|
||||
"Annotation style": "མཆན་འགྲེལ་གྱི་བཟོ་ལྟ།",
|
||||
"Annotation color": "མཆན་འགྲེལ་གྱི་ཚོན་མདོག",
|
||||
"Annotation time": "མཆན་འགྲེལ་གྱི་དུས་ཚོད།",
|
||||
"AI": "རིག་ནུས་མིས་བཟོས། (AI)",
|
||||
"Are you sure you want to re-index this book?": "ཁྱེད་ཀྱིས་དཔེ་དེབ་འདི་བསྐྱར་དུ་དཀར་ཆག་བཟོ་རྒྱུ་གཏན་འཁེལ་ཡིན་ནམ།",
|
||||
"Enable AI in Settings": "སྒྲིག་བཀོད་ནང་ AI སྤྱོད་པར་བྱོས།",
|
||||
"Index This Book": "དཔེ་དེབ་འདི་དཀར་ཆག་བཟོ་བ།",
|
||||
"Enable AI search and chat for this book": "དཔེ་དེབ་འདིར་ AI འཚོལ་བཤེར་དང་ཁ་བརྡ་སྤྱོད་པར་བྱོས།",
|
||||
"Start Indexing": "དཀར་ཆག་བཟོ་འགོ་བཙུགས་པ།",
|
||||
"Indexing book...": "དཔེ་དེབ་དཀར་ཆག་བཟོ་བཞིན་པ།...",
|
||||
"Preparing...": "གྲ་སྒྲིག་བྱེད་བཞིན་པ།...",
|
||||
"Delete this conversation?": "ཁ་བརྡ་འདི་བསུབ་དགོས་སམ།",
|
||||
"No conversations yet": "ད་ལྟའི་བར་ཁ་བརྡ་མེད།",
|
||||
"Start a new chat to ask questions about this book": "དཔེ་དེབ་འདིའི་སྐོར་ལ་དྲི་བ་དྲི་བར་ཁ་བརྡ་གསར་པ་ཞིག་འགོ་བཙུགས་པ།",
|
||||
"Rename": "མིང་བརྗེ་བ།",
|
||||
"New Chat": "ཁ་བརྡ་གསར་པ།",
|
||||
"Chat": "ཁ་བརྡ།",
|
||||
"Please enter a model ID": "དཔེ་གཞིའི་ཨང་རྟགས་ནང་འཇུག་བྱོས།",
|
||||
"Model not available or invalid": "དཔེ་གཞི་མེད་པ་འམ་ནུས་མེད།",
|
||||
"Failed to validate model": "དཔེ་གཞི་བདེན་སྦྱོར་བྱེད་ཐུབ་མེད།",
|
||||
"Couldn't connect to Ollama. Is it running?": "Ollama ལ་འབྲེལ་མཐུད་བྱེད་ཐུབ་མེད། དེ་འཁོར་བཞིན་ཡོད་དམ།",
|
||||
"Invalid API key or connection failed": "API ལྡེ་མིག་ནུས་མེད་དམ་འབྲེལ་མཐུད་ཕམ་པ།",
|
||||
"Connection failed": "འབྲེལ་མཐུད་ཕམ་པ།",
|
||||
"AI Assistant": "AI རོགས་པ།",
|
||||
"Enable AI Assistant": "AI རོགས་པ་སྤྱོད་པར་བྱོས།",
|
||||
"Provider": "མཁོ་འདོན་པ།",
|
||||
"Ollama (Local)": "Ollama (ས་གནས།)",
|
||||
"AI Gateway (Cloud)": "AI འཛུལ་སྒོ། (སྤྲིན་གནས།)",
|
||||
"Ollama Configuration": "Ollama སྒྲིག་བཀོད།",
|
||||
"Refresh Models": "དཔེ་གཞི་བསྐྱར་བརྗེ།",
|
||||
"AI Model": "AI དཔེ་གཞི།",
|
||||
"No models detected": "དཔེ་གཞི་མ་རྙེད།",
|
||||
"AI Gateway Configuration": "AI འཛུལ་སྒོའི་སྒྲིག་བཀོད།",
|
||||
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "སྤུས་ལེགས་དང་ཁེ་ཕན་ཆེ་བའི་ AI དཔེ་གཞི་འདེམས་དགོས། ཁྱེད་ཀྱིས་འོག་གི་ \"རང་བཟོའི་དཔེ་གཞི།\" བདམས་ནས་རང་གི་དཔེ་གཞི་བེད་སྤྱོད་ཀྱང་ཆོག",
|
||||
"API Key": "API ལྡེ་མིག་",
|
||||
"Get Key": "ལྡེ་མིག་ལེན་པ།",
|
||||
"Model": "དཔེ་གཞི།",
|
||||
"Custom Model...": "རང་བཟོའི་དཔེ་གཞི།...",
|
||||
"Custom Model ID": "རང་བཟོའི་དཔེ་གཞིའི་ཨང་རྟགས།",
|
||||
"Validate": "བདེན་སྦྱོར།",
|
||||
"Model available": "དཔེ་གཞི་ཡོད།",
|
||||
"Connection": "འབྲེལ་མཐུད།",
|
||||
"Test Connection": "འབྲེལ་མཐུད་ཚོད་ལྟ།",
|
||||
"Connected": "འབྲེལ་མཐུད་ཟིན།",
|
||||
"Custom Colors": "རང་བཟོས་ཚོན་མདོག",
|
||||
"Color E-Ink Mode": "ཚོན་ལྡན་གློག་རྡུལ་སྣག་ཚའི་རྣམ་པ།",
|
||||
"Reading Ruler": "ལྟ་ཀློག་ཐིག་ཤིང་།",
|
||||
"Enable Reading Ruler": "ལྟ་ཀློག་ཐིག་ཤིང་སྤྱོད་པ།",
|
||||
"Lines to Highlight": "མངོན་གསལ་དུ་གཏོང་དགོས་པའི་ཐིག་ཕྲེང་།",
|
||||
"Ruler Color": "ཐིག་ཤིང་ගི་ཚོན་མདོག",
|
||||
"Command Palette": "བཀོད་འདོམས་པང་ལེབ།",
|
||||
"Search settings and actions...": "སྒྲིག་བཀོད་དང་བྱ་འགུལ་འཚོལ་བ།...",
|
||||
"No results found for": "འབྲས་བུ་མ་རྙེད་པ།",
|
||||
"Type to search settings and actions": "ཡིག་གཟུགས་ནང་འཇུག་བྱས་ཏེ་སྒྲིག་བཀོད་དང་བྱ་འགུལ་འཚོལ་བ།",
|
||||
"Recent": "ཉེ་ཆར།",
|
||||
"navigate": "འགྲུལ་བཞུད།",
|
||||
"select": "འདེམས་པ།",
|
||||
"close": "ཁ་རྒྱག་པ།",
|
||||
"Search Settings": "སྒྲིག་བཀོད་འཚོལ་བ།",
|
||||
"Page Margins": "ཤོག་ངོས་མཐའ་འགྲམ།",
|
||||
"AI Provider": "ནུས་པའི་རིག་རྩལ་མཁོ་སྤྲོད་པ།",
|
||||
"Ollama URL": "Ollama URL",
|
||||
"Ollama Model": "Ollama དཔེ་གཞི།",
|
||||
"AI Gateway Model": "AI Gateway དཔེ་གཞི།",
|
||||
"Actions": "བྱ་འགུལ།",
|
||||
"Navigation": "འགྲུལ་བཞུད།",
|
||||
"Set status for {{count}} book(s)_other": "དེབ་ {{count}} གི་གནས་སྟངས་གཏན་འཁེལ་བྱེད།",
|
||||
"Mark as Unread": "མ་ཀློག་པར་རྟགས་རྒྱག",
|
||||
"Mark as Finished": "ཚར་བར་རྟགས་རྒྱག",
|
||||
"Finished": "ཚར་སོང་།",
|
||||
"Unread": "མ་ཀློག་པ།",
|
||||
"Clear Status": "གནས་སྟངས་གཙང་མ།",
|
||||
"Status": "གནས་སྟངས།",
|
||||
"Loading": "ལེན་བཞིན་པ།...",
|
||||
"Exit Paragraph Mode": "དུམ་མཚམས་རྣམ་པ་ནས་ཕྱིར་ཐོན།",
|
||||
"Paragraph Mode": "དུམ་མཚམས་རྣམ་པ།",
|
||||
"Embedding Model": "གནས་སྒྲིག་དཔེ་གཞི།",
|
||||
"{{count}} book(s) synced_other": "དེབ་ {{count}} མཉམ་འགྲིག་བྱས་ཟིན།",
|
||||
"Unable to start RSVP": "RSVP འགོ་འཛུགས་མ་ཐུབ།",
|
||||
"RSVP not supported for PDF": "PDF ལ་ RSVP རྒྱབ་སྐྱོར་མེད།",
|
||||
"Select Chapter": "ལེའུ་འདེམས་པ།",
|
||||
"Context": "བརྗོད་དོན།",
|
||||
"Ready": "གྲ་སྒྲིག་ཡོད།",
|
||||
"Chapter Progress": "ལེའུའི་འཕེལ་རིམ།",
|
||||
"words": "ཚིག",
|
||||
"{{time}} left": "དུས་ཚོད་ {{time}} ལྷག་ཡོད།",
|
||||
"Reading progress": "ཀློག་པའི་འཕེལ་རིམ།",
|
||||
"Skip back 15 words": "ཚིག་ ༡༥ རྒྱབ་ལ་བཤུད་པ།",
|
||||
"Back 15 words (Shift+Left)": "ཚིག་ ༡༥ རྒྱབ་ལ་བཤུད་པ། (Shift+Left)",
|
||||
"Pause (Space)": "མཚམས་འཇོག་པ། (Space)",
|
||||
"Play (Space)": "གཏོང་བ། (Space)",
|
||||
"Skip forward 15 words": "ཚིག་ ༡༥ མདུན་ལ་བཤུད་པ།",
|
||||
"Forward 15 words (Shift+Right)": "ཚིག་ ༡༥ མདུན་ལ་བཤུད་པ། (Shift+Right)",
|
||||
"Decrease speed": "མགྱོགས་ཚད་འགོར་དུ་གཏོང་བ།",
|
||||
"Slower (Left/Down)": "འགོར་བ། (Left/Down)",
|
||||
"Increase speed": "མགྱོགས་ཚད་མྱུར་དུ་གཏོང་བ།",
|
||||
"Faster (Right/Up)": "མྱུར་བ། (Right/Up)",
|
||||
"Start RSVP Reading": "RSVP ཀློག་འགོ་འཛུགས་པ།",
|
||||
"Choose where to start reading": "གང་ནས་ཀློག་འགོ་འཛུགས་མིན་འདེམས་པ།",
|
||||
"From Chapter Start": "ལེའུའི་འགོ་ནས།",
|
||||
"Start reading from the beginning of the chapter": "ལེའུ་འདིའི་འགོ་ནས་ཀློག་པ།",
|
||||
"Resume": "མུ་མཐུད་པ།",
|
||||
"Continue from where you left off": "མཚམས་བཞག་སའི་གནས་ནས་མུ་མཐུད་པ།",
|
||||
"From Current Page": "ད་ལྟའི་ཤོག་ལྷེ་ནས།",
|
||||
"Start from where you are currently reading": "ད་ལྟ་ཀློག་བཞིན་པའི་གནས་ནས་འགོ་འཛུགས་པ།",
|
||||
"From Selection": "བདམས་པའི་ནང་དོན་ནས།",
|
||||
"Speed Reading Mode": "མྱུར་ཀློག་རྣམ་པ།",
|
||||
"Scroll left": "གཡོན་ལ་བཤུད་དགོས།",
|
||||
"Scroll right": "གཡས་ལ་བཤུད་དགོས།",
|
||||
"Library Sync Progress": "དཔེ་མཛོད་མཉམ་འབྱུང་གི་རིམ་པ།",
|
||||
"Back to library": "དཔེ་མཛོད་ལ་ལོག་པ།",
|
||||
"Group by...": "དབྱེ་བ་འབྱེད་སྟངས།...",
|
||||
"Export as Plain Text": "ཡིག་རྐྱང་དཀྱུས་མར་ཕྱིར་འདྲེན།",
|
||||
"Export as Markdown": "Markdown དུ་ཕྱིར་འདྲེན།",
|
||||
"Show Page Navigation Buttons": "ཤོག་ངོས་མཐེབ་གནོན།",
|
||||
"Page {{number}}": "ཤོག་ལྷེ། {{number}}",
|
||||
"highlight": "འོད་རྟགས།",
|
||||
"underline": "ཞབས་ཐིག",
|
||||
"squiggly": "ཀྱག་ཀྱག་ཐིག",
|
||||
"red": "དམར་པོ།",
|
||||
"violet": "སྨུག་པོ།",
|
||||
"blue": "སྔོན་པོ།",
|
||||
"green": "ལྗང་ཁུ།",
|
||||
"yellow": "སེར་པོ།",
|
||||
"Select {{style}} style": "{{style}} བཟོ་ལྟ་བདམ་པ།",
|
||||
"Select {{color}} color": "{{color}} ཚོན་མདོག་བདམ་པ།",
|
||||
"Close Book": "དེབ་ཁ་རྒྱག་པ།",
|
||||
"Speed Reading": "མགྱོགས་ཀློག",
|
||||
"Close Speed Reading": "མགྱོགས་ཀློག་ཁ་རྒྱག་པ།",
|
||||
"Authors": "རྩོམ་པ་པོ།",
|
||||
"Books": "དཔེ་ཆ།",
|
||||
"Groups": "ཚོགས་པ།",
|
||||
"Back to TTS Location": "TTS གནས་སར་ཕྱིར་ལོག་པ།",
|
||||
"Metadata": "གནད་སྨིན་གོ་དོན།",
|
||||
"Image viewer": "པར་རིས་ལྟ་བྱེད།",
|
||||
"Previous Image": "སྔོན་མའི་པར་རིས།",
|
||||
"Next Image": "རྗེས་མའི་པར་རིས།",
|
||||
"Zoomed": "ཆེར་བསྐྱེད་ཟིན།",
|
||||
"Zoom level": "ཆེར་བསྐྱེད་རིམ་པ།",
|
||||
"Table viewer": "རེའུ་མིག་ལྟ་བྱེད།",
|
||||
"Unable to connect to Readwise. Please check your network connection.": "Readwise ལ་མཐུད་ཐུབ་མ་སོང་། ཁྱེད་ཀྱི་དྲ་རྒྱའི་མཐུད་ལམ་ལ་བརྟག་དཔྱད་གནང་རོགས།",
|
||||
"Invalid Readwise access token": "Readwise འཛུལ་སྤྱོད་ལག་ཁྱེར་ནོར་འདུག",
|
||||
"Disconnected from Readwise": "Readwise ནས་མཐུད་ལམ་བཅད་ཟིན།",
|
||||
"Never": "ནམ་ཡང་མིན།",
|
||||
"Sync Enabled": "མཉམ་བྱུང་ནུས་པ་སྤར་ཟིན།",
|
||||
"Disconnect": "མཐུད་ལམ་གཅོད་པ།",
|
||||
"Connect your Readwise account to sync highlights.": "ཁྱེད་ཀྱི་ Readwise རྩིས་ཐོ་མཐུད་ནས་བཀོད་མཆན་མཉམ་བྱུང་གནང་རོགས།",
|
||||
"Get your access token at": "འཛུལ་སྤྱོད་ལག་ཁྱེར་འདི་ནས་ལེན་རོགས།",
|
||||
"Access Token": "འཛུལ་སྤྱོད་ལག་ཁྱེེར།",
|
||||
"Paste your Readwise access token": "Readwise འཛུལ་སྤྱོད་ལག་ཁྱེར་འདིར་སྦྱོར་རོགས།",
|
||||
"Readwise Sync": "Readwise མཉམ་བྱུང་།",
|
||||
"Push Highlights": "བཀོད་མཆན་སྐྱེལ་བ།",
|
||||
"Highlights synced to Readwise": "བཀོད་མཆན་ Readwise ལ་མཉམ་བྱུང་བྱས་ཟིན།",
|
||||
"Readwise sync failed: no internet connection": "Readwise མཉམ་བྱུང་མ་ཐུབ། དྲ་རྒྱའི་མཐུད་ལམ་མི་འདུག",
|
||||
"Readwise sync failed: {{error}}": "Readwise མཉམ་བྱུང་མ་ཐུབ། {{error}}",
|
||||
"System Screen Brightness": "མ་ལག་ཤེལ་སྒོའི་གསལ་ཚད།",
|
||||
"Page:": "ཤོག་ལྷེ།:",
|
||||
"Page: {{number}}": "ཤོག་ལྷེ།: {{number}}",
|
||||
"Annotation page number": "མཆན་འགྲེལ་ཤོག་ཨང་།",
|
||||
"Translating...": "ཡིག་བསྒྱུར་བྱེད་བཞིན་པ།...",
|
||||
"Show Battery Percentage": "གློག་རྫས་བརྒྱ་ཆ་སྟོན།",
|
||||
"Hide Scrollbar": "འགྲིལ་ཤིང་སྦེད་པ།",
|
||||
"Skip to last reading position": "མཐའ་མའི་ཀློག་གནས་སུ་མཆོང་",
|
||||
"Show context": "སྐབས་དོན་མངོན་པ",
|
||||
"Hide context": "སྐབས་དོན་སྦས་པ",
|
||||
"Decrease font size": "ཡིག་གཟུགས་ཆུང་དུ་གཏོང་བ",
|
||||
"Increase font size": "ཡིག་གཟུགས་ཆེ་རུ་གཏོང་བ",
|
||||
"Focus": "དམིགས་གཏད",
|
||||
"Theme color": "བརྗོད་དོན་ཚོན་མདོག",
|
||||
"Import failed": "ནང་འདྲེན་མི་ཐུབ།",
|
||||
"Available Formatters:": "སྒྲིག་བཟོ་ཆས་ཡོད་པ།:",
|
||||
"Format date": "ཚེས་གྲངས་སྒྲིག་བཟོ",
|
||||
"Markdown block quote (> per line)": "Markdown དྲིལ་བསྡུ་འདྲེན་ (མི་ལྟར་ > )",
|
||||
"Newlines to <br>": "གསར་བརྗེ་ <br> ལ་བསྒྱུར",
|
||||
"Change case": "ཡིག་གཟུགས་བསྒྱུར",
|
||||
"Trim whitespace": "སྟོང་ཆ་གཙང་བཟོ",
|
||||
"Truncate to n characters": "n ཡི་གེར་ཐུང་དུ་བཅད",
|
||||
"Replace text": "ཡི་གེ་བརྗེ་བ",
|
||||
"Fallback value": "ཚབ་ཀྱི་གྲངས་ཀ",
|
||||
"Get length": "རིང་ཚད་ལེན",
|
||||
"First/last element": "དང་པོ/མཇུག་གི་རྒྱུ་ཆ",
|
||||
"Join array": "ཚོ་སྒྲིག་སྦྲེལ",
|
||||
"Backup failed: {{error}}": "གྲབས་ཉར་བྱེད་མ་ཐུབ: {{error}}",
|
||||
"Select Backup": "གྲབས་ཉར་འདེམས་པ",
|
||||
"Restore failed: {{error}}": "སླར་གསོ་བྱེད་མ་ཐུབ: {{error}}",
|
||||
"Backup & Restore": "གྲབས་ཉར་དང་སླར་གསོ",
|
||||
"Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "ཁྱོད་ཀྱི་དཔེ་མཛོད་ཀྱི་གྲབས་ཉར་བཟོ་བའམ་སྔོན་གྱི་གྲབས་ཉར་ནས་སླར་གསོ་བྱོས། སླར་གསོ་བྱས་ན་ཁྱོད་ཀྱི་ད་ལྟའི་དཔེ་མཛོད་དང་སྤྲོད་རེས་བྱེད།",
|
||||
"Backup Library": "དཔེ་མཛོད་གྲབས་ཉར",
|
||||
"Restore Library": "དཔེ་མཛོད་སླར་གསོ",
|
||||
"Creating backup...": "གྲབས་ཉར་བཟོ་བཞིན་པ...",
|
||||
"Restoring library...": "དཔེ་མཛོད་སླར་གསོ་བྱེད་བཞིན་པ...",
|
||||
"{{current}} of {{total}} items": "{{current}} / {{total}} རྣམ་གྲངས",
|
||||
"Backup completed successfully!": "གྲབས་ཉར་ལེགས་གྲུབ་བྱུང་!",
|
||||
"Restore completed successfully!": "སླར་གསོ་ལེགས་གྲུབ་བྱུང་!",
|
||||
"Your library has been saved to the selected location.": "ཁྱོད་ཀྱི་དཔེ་མཛོད་འདེམས་ས་དེར་ཉར་ཟིན།",
|
||||
"{{added}} books added, {{updated}} books updated.": "དཔེ་དེབ {{added}} བསྣན་ཟིན, {{updated}} གསར་བསྒྱུར་བྱས་ཟིན།",
|
||||
"Operation failed": "བཀོལ་སྤྱོད་བྱེད་མ་ཐུབ",
|
||||
"Loading library...": "དཔེ་མཛོད་འཇུག་བཞིན་པ...",
|
||||
"{{count}} books refreshed_other": "تم تحديث {{count}} كتب",
|
||||
"Failed to refresh metadata": "མེ་ཊ་ཌེ་ཊ་གསར་བསྒྱུར་བྱེད་མ་ཐུབ",
|
||||
"Refresh Metadata": "མེ་ཊ་ཌེ་ཊ་གསར་བསྒྱུར",
|
||||
"Keyboard Shortcuts": "མཐེབ་གཞོང་མྱུར་ལམ།",
|
||||
"View all keyboard shortcuts": "མཐེབ་གཞོང་མྱུར་ལམ་ཚང་མ་ལྟ་བ།",
|
||||
"Switch Sidebar Tab": "ཟུར་འབར་གྱི་ཤོག་བྱང་བརྗེ་བ།",
|
||||
"Toggle Notebook": "ཟིན་དེབ་སྟོན་པ།/སྦས་པ།",
|
||||
"Search in Book": "དཔེ་དེབ་ནང་འཚོལ་བ།",
|
||||
"Toggle Scroll Mode": "འཁྱོག་སྒུལ་རྣམ་པ་བརྗེ་བ།",
|
||||
"Toggle Select Mode": "འདེམས་རྣམ་པ་བརྗེ་བ།",
|
||||
"Toggle Bookmark": "ཤོག་རྟགས་བརྗེ་བ།",
|
||||
"Toggle Text to Speech": "ཡི་གེ་སྒྲ་ལ་བརྗེ་བ།",
|
||||
"Play / Pause TTS": "སྒྲ་ཀློག་འགོ་འཛུགས / མཚམས་འཇོག",
|
||||
"Toggle Paragraph Mode": "དུམ་བུ་རྣམ་པ་བརྗེ་བ།",
|
||||
"Highlight Selection": "བདམས་པ་གསལ་བཀོད།",
|
||||
"Underline Selection": "བདམས་པའི་འོག་ཐིག",
|
||||
"Annotate Selection": "བདམས་པར་མཆན་འགོད།",
|
||||
"Search Selection": "བདམས་པ་འཚོལ་བ།",
|
||||
"Copy Selection": "བདམས་པ་བཤུ་བ།",
|
||||
"Translate Selection": "བདམས་པ་ཡིག་སྒྱུར།",
|
||||
"Dictionary Lookup": "ཚིག་མཛོད་ནང་འཚོལ་བ།",
|
||||
"Read Aloud Selection": "བདམས་པ་སྒྲ་ཆེན་པོས་ཀློག",
|
||||
"Proofread Selection": "བདམས་པ་ཞུ་དག",
|
||||
"Open Settings": "སྒྲིག་འགོད་ཁ་ཕྱེ་བ།",
|
||||
"Open Command Palette": "བཀའ་བརྡའི་པང་ཁ་ཕྱེ་བ།",
|
||||
"Show Keyboard Shortcuts": "མཐེབ་གཞོང་མྱུར་ལམ་སྟོན་པ།",
|
||||
"Open Books": "དཔེ་དེབ་ཁ་ཕྱེ་བ།",
|
||||
"Toggle Fullscreen": "ཡོངས་ཁྱབ་བརྗེ་བ།",
|
||||
"Close Window": "སྒེའུ་ཁུང་ཁ་རྒྱག",
|
||||
"Quit App": "ཉེར་སྤྱོད་ཁ་རྒྱག",
|
||||
"Go Left / Previous Page": "གཡོན་དུ / སྔོན་མའི་ཤོག་ངོས།",
|
||||
"Go Right / Next Page": "གཡས་སུ / རྗེས་མའི་ཤོག་ངོས།",
|
||||
"Go Up": "གོང་དུ།",
|
||||
"Go Down": "འོག་ཏུ།",
|
||||
"Previous Chapter": "སྔོན་མའི་ལེའུ།",
|
||||
"Next Chapter": "རྗེས་མའི་ལེའུ།",
|
||||
"Scroll Half Page Down": "ཤོག་ངོས་ཕྱེད་ཀ་འོག་ཏུ།",
|
||||
"Scroll Half Page Up": "ཤོག་ངོས་ཕྱེད་ཀ་གོང་དུ།",
|
||||
"Save Note": "ཟིན་བྲིས་ཉར་བ།",
|
||||
"Single Section Scroll": "དོན་ཚན་གཅིག་འགུལ་བ།",
|
||||
"General": "སྤྱིར་བཏང་།",
|
||||
"Text to Speech": "ཡི་གེ་ནས་སྒྲ།",
|
||||
"Zoom": "ཆེ་རུ་གཏོང་།",
|
||||
"Window": "སྒེའུ་ཁུང་།",
|
||||
"Your Bookshelf": "ཁྱོད་ཀྱི་དཔེ་མཛོད།",
|
||||
"TTS": "ཡི་གེ་ནས་སྒྲ",
|
||||
"Media Info": "སྨྱན་བྱད་ཆ་འཕྲིན",
|
||||
"Update Frequency": "གསར་སྒྱུར་ཐེངས་གྲངས",
|
||||
"Every Sentence": "ཚིག་གྲུབ་རེ་རེ",
|
||||
"Every Paragraph": "དུམ་མཚམས་རེ་རེ",
|
||||
"Every Chapter": "ལེའུ་རེ་རེ",
|
||||
"TTS Media Info Update Frequency": "TTS སྨྱན་བྱད་ཆ་འཕྲིན་གསར་སྒྱུར་ཐེངས་གྲངས",
|
||||
"Select reading speed": "ཀློག་པའི་མྱུར་ཚད་འདེམས།",
|
||||
"Drag to seek": "འཚོལ་བར་འདྲུད།",
|
||||
"Punctuation Delay": "ཚེག་ཤད་ཕྱིར་འགྱངས།",
|
||||
"Please confirm that this OPDS connection will be proxied through Readest servers on the web app before continuing.": "མུ་མཐུད་མ་བྱས་གོང་ OPDS འབྲེལ་མཐུད་འདི་དྲ་རྒྱའི་ཉེར་སྤྱོད་ཐོག་ Readest ཞབས་ཞུ་བ་བརྒྱུད་ནས་བསྐུར་རྒྱུ་ཡིན་པ་གཏན་འཁེལ་བྱོས།",
|
||||
"Custom Headers (optional)": "སྲོལ་སྒྲིག་མགོ་བརྗེ (གདམ་གའི)",
|
||||
"Add one header per line using \"Header-Name: value\".": "\"Header-Name: value\" བེད་སྤྱོད་བྱས་ནས་ཕྲེང་རེ་རེའི་ནང་མགོ་བརྗེ་གཅིག་སྣོན།",
|
||||
"I understand this OPDS connection will be proxied through Readest servers on the web app. If I do not trust Readest with these credentials or headers, I should use the native app instead.": "OPDS འབྲེལ་མཐུད་འདི་དྲ་རྒྱའི་ཉེར་སྤྱོད་ཐོག་ Readest ཞབས་ཞུ་བ་བརྒྱུད་ནས་བསྐུར་རྒྱུ་ཡིན་པ་ངས་ཧ་གོ། ང་ Readest ལ་ངེས་འཛིན་འམ་མགོ་བརྗེ་འདི་དག་ཡིད་ཆེས་མེད་ན། ང་ས་གནས་ཉེར་སྤྱོད་བེད་སྤྱོད་བྱ་དགོས།",
|
||||
"Unable to connect to Hardcover. Please check your network connection.": "Hardcover ལ་འབྲེལ་མཐུད་བྱེད་ཐུབ་མ་སོང་། ཁྱེད་ཀྱི་དྲ་རྒྱའི་འབྲེལ་མཐུད་ལ་ཞིབ་བཤེར་གནང་།",
|
||||
"Invalid Hardcover API token": "Hardcover API ཐོབ་ཐང་མ་ཆིག་སྒྲིལ་མིན།",
|
||||
"Disconnected from Hardcover": "Hardcover ནས་འབྲེལ་མཐུད་ཆད་སོང་།",
|
||||
"Connect your Hardcover account to sync reading progress and notes.": "ཀློག་འགྲོས་དང་མཆན་ཟླ་སྒྲིག་བྱེད་པར་ཁྱེད་ཀྱི Hardcover ཐོ་ཁུངས་འབྲེལ་མཐུད་བྱོས།",
|
||||
"Get your API token from hardcover.app → Settings → API.": "hardcover.app → སྒྲིག་འགོད → API ནས་ཁྱེད་ཀྱི API ཐོབ་ཐང་ལེན།",
|
||||
"API Token": "API ཐོབ་ཐང་།",
|
||||
"Paste your Hardcover API token": "ཁྱེད་ཀྱི Hardcover API ཐོབ་ཐང་སྦྱར།",
|
||||
"Hardcover Sync": "Hardcover ཟླ་སྒྲིག",
|
||||
"Push Notes": "མཆན་གཏོང་བ",
|
||||
"No annotations or excerpts to sync for this book.": "དེབ་འདིའི་ཆེད་ཟླ་སྒྲིག་བྱེད་པའི་མཆན་ཡང་ན་དྲངས་ཡིག་མེད།",
|
||||
"Configure Hardcover in Settings first.": "སྔོན་ལ་སྒྲིག་འགོད་ནང་ Hardcover རིགས་སྒྲིག་བྱོས།",
|
||||
"No new Hardcover note changes to sync.": "ཟླ་སྒྲིག་བྱེད་པའི་ Hardcover མཆན་གསར་པ་མེད།",
|
||||
"Hardcover synced: {{inserted}} new, {{updated}} updated, {{skipped}} unchanged": "Hardcover ཟླ་སྒྲིག་བྱས་ཟིན: {{inserted}} གསར་པ, {{updated}} གསར་བཅོས, {{skipped}} མི་འགྱུར",
|
||||
"Hardcover notes sync failed: {{error}}": "Hardcover མཆན་ཟླ་སྒྲིག་མི་ཚོགས: {{error}}",
|
||||
"Reading progress synced to Hardcover": "ཀློག་འགྲོས་ Hardcover ལ་ཟླ་སྒྲིག་བྱས་ཟིན།",
|
||||
"Hardcover progress sync failed: {{error}}": "Hardcover ཀློག་འགྲོས་ཟླ་སྒྲིག་མི་ཚོགས: {{error}}",
|
||||
"ISBN": "ISBN",
|
||||
"Keep existing source identifier": "ཡོད་བཞིན་པའི་འབྱུང་ཁུངས་ཐོབ་ཐང་གཞག",
|
||||
"Toggle Toolbar": "ལག་ཆའི་ཚན་བར་བརྗེ་བ།",
|
||||
"Split Hyphens": "སྦྲེལ་རྟགས་གཅད་པ",
|
||||
"Apply Theme Colors to PDF": "PDF ལ་བརྗོད་དོན་ཚོས་གཞི་སྤྱོད་པ",
|
||||
"Name": "མིང",
|
||||
"Remove": "བསུབ།",
|
||||
"Edit OPDS Catalog": "OPDS དཀར་ཆག་རྩོམ་སྒྲིག",
|
||||
"Save Changes": "བསྒྱུར་བཅོས་ཉར་ཚགས།",
|
||||
"Use Book Layout": "དཔེ་ཆའི་བཀོད་སྒྲིག་སྤྱོད་པ",
|
||||
"End of this section. Continue to the next.": "སྡེ་ཚན་འདིའི་མཇུག་རེད། རྗེས་མའི་སྡེ་ཚན་དུ་མུ་མཐུད་རོགས།",
|
||||
"Auto-download": "རང་འགུལ་ཕབ་ལེན།",
|
||||
"Auto-download new items": "གསར་འདོན་གསར་པ་རང་འགུལ་ཕབ་ལེན།",
|
||||
"Automatically download new publications when the app syncs": "མཉེན་ཆས་མཉམ་སྦྱོར་སྐབས་གསར་འདོན་གསར་པ་རང་འགུལ་ཕབ་ལེན་བྱེད།",
|
||||
"{{count}} new item(s) downloaded from OPDS_other": "OPDS ནས་གསར་འདོན་གསར་པ་ {{count}} ཕབ་ལེན་བྱས།",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_other": "OPDS དཀར་ཆག་ {{count}} མཉམ་སྦྱོར་མ་ཐུབ།",
|
||||
"Last synced {{when}}": "མཐའ་མའི་མཐུན་སྒྲིག་ {{when}}",
|
||||
"Failed downloads": "ཕབ་ལེན་ཕམ་ཁ།",
|
||||
"No failed downloads": "ཕབ་ལེན་ཕམ་ཁ་མེད།",
|
||||
"Attempts: {{count}}_other": "ཐེངས་ {{count}}",
|
||||
"Skip": "གཡོལ།",
|
||||
"Skip all": "ཡོད་ཚད་གཡོལ།",
|
||||
"Retry all": "ཡོད་ཚད་ཡང་སྐྱར།",
|
||||
"{{count}} failed_other": "{{count}} ཕམ་ཁ།",
|
||||
"(none)": "(མེད)",
|
||||
"Identifiers": "ངོས་འཛིན་བྱེད་མཁན",
|
||||
"Read (Stream)": "ཀློག་པ། (རྒྱུན་སྤེལ།)",
|
||||
"Failed to start stream": "རྒྱུན་སྤེལ་འགོ་འཛུགས་མ་ཐུབ།",
|
||||
"No dictionaries enabled": "ཚིག་མཛོད་གང་ཡང་སྒོ་མི་འབྱེད།",
|
||||
"Enable a dictionary in Settings → Language → Dictionaries.": "སྒྲིག་འགོད ← སྐད་ཡིག ← ཚིག་མཛོད་ནས་ཚིག་མཛོད་ཤིག་ལ་སྒོ་འབྱེད།",
|
||||
"Wiktionary": "ཝི་ཀེ་ཤོག་ནར།",
|
||||
"Drag to reorder": "གོ་རིམ་སྒྱུར་བར་འཐེན།",
|
||||
"Built-in": "ནང་ཁུལ།",
|
||||
"Bundle is missing on this device. Re-import to use it.": "ཡོ་བྱད་འདི་དུས་ཆས་ཁྲོད་མི་འདུག འདི་སྤྱོད་པར་ཡང་བསྐྱར་ནང་འདྲེན་གནང་རོགས།",
|
||||
"This dictionary format is not supported.": "ཚིག་མཛོད་འདིའི་རྣམ་པར་རྒྱབ་སྐྱོར་མི་བྱེད།",
|
||||
"MDict": "MDict",
|
||||
"StarDict": "StarDict",
|
||||
"Imported {{count}} dictionary_other": "ཚིག་མཛོད་ {{count}} ནང་འདྲེན་གྲུབ།",
|
||||
"Skipped incomplete bundles: {{names}}": "ཚང་མི་འདུག་པའི་ཡོ་བྱད་སྤོས་ནས་འགྲོ་སོང༌། {{names}}",
|
||||
"Failed to import dictionary: {{message}}": "ཚིག་མཛོད་ནང་འདྲེན་གྲུབ་མ་སོང༌། {{message}}",
|
||||
"Dictionaries": "ཚིག་མཛོད་ཁག",
|
||||
"Delete Dictionary": "ཚིག་མཛོད་སུབ།",
|
||||
"Importing…": "ནང་འདྲེན་བྱེད་བཞིན་པ…",
|
||||
"Import Dictionary": "ཚིག་མཛོད་ནང་འདྲེན།",
|
||||
"No dictionaries available.": "ཚིག་མཛོད་གང་ཡང་མི་འདུག།",
|
||||
"StarDict bundles need .ifo, .idx, and .dict.dz files (.syn optional).": "StarDict ཡོ་བྱད་ལ་ .ifo དང་ .idx, .dict.dz ཡིག་ཆ་ཁག་དགོས་པ་ཡིན། (.syn ནི་འདེམས་འཁྱོངས་ཡིན།)",
|
||||
"Select all the bundle files together when importing.": "ནང་འདྲེན་སྐབས་ཡོ་བྱད་ཡིག་ཆ་ཡོངས་མཉམ་དུ་འདེམས་རོགས།",
|
||||
"Manage Dictionaries": "ཚིག་མཛོད་སྟངས་འཛིན།",
|
||||
"Select Dictionary Files": "ཚིག་མཛོད་ཡིག་ཆ་འདེམས།",
|
||||
"Read on Wikipedia →": "ཝི་ཁི་ཕིཌིཡ་ནས་ཀློག →",
|
||||
"This link can't be opened": "འདྲེན་བྱང་འདི་ཁ་ཕྱེ་ཐུབ་མེད།",
|
||||
"The annotation link is missing required information. The original link may have been truncated.": "དྲན་བྱང་འདྲེན་བྱང་ལ་དགོས་ངེས་གནས་ཚུལ་མི་འདུག འདྲེན་བྱང་གདོད་མ་གཅོད་ཡོད་སྲིད།",
|
||||
"Go to Readest": "Readest ལ་འགྲོ",
|
||||
"Open-source ebook reader for everyone, on every device.": "མི་ཀུན་ལ། སྒྲིག་ཆས་སྣ་ཚོགས་ཐོག་ཏུ་སྤྱོད་རུང་བའི་འབྱེད་པའི་ཀོ་ཌ་ཨི་དེབ་ཀློག་ཆས།",
|
||||
"Open in Readest": "Readest ནང་ཁ་ཕྱེ",
|
||||
"If Readest didn't open automatically, choose an option below:": "Readest རང་འགུལ་གྱིས་ཁ་ཕྱེ་མེད་ན། འོག་གི་གདམ་ག་ཞིག་འདེམས་རོགས།",
|
||||
"Continue reading where you left off.": "ཁྱོད་ཀྱིས་མཚམས་བཞག་སའི་ས་ནས་མུ་མཐུད་ཀློག་རོགས།",
|
||||
"Readest logo": "Readest ཀྱི་ལས་རྟགས།",
|
||||
"Opening Readest...": "Readest ཁ་ཕྱེ་བཞིན་པ…",
|
||||
"Open in Readest app": "Readest ཉེར་སྤྱོད་ནང་ཁ་ཕྱེ",
|
||||
"Continue in browser": "བརྡར་ཞིབ་ཆས་ནང་མུ་མཐུད",
|
||||
"Don't have Readest?": "Readest མེད་དམ།",
|
||||
"Book not in your library": "དེབ་འདི་ཁྱེད་ཀྱི་དཔེ་མཛོད་ནང་མི་འདུག",
|
||||
"Share Book": "དཔེ་ཆ་མཉམ་སྤྱོད།",
|
||||
"Could not create share link": "མཉམ་སྤྱོད་འབྲེལ་མཐུད་བཟོ་མ་ཐུབ།",
|
||||
"Link copied": "འབྲེལ་མཐུད་པར་སློག་གྲུབ།",
|
||||
"Could not copy link": "འབྲེལ་མཐུད་པར་སློག་མ་ཐུབ།",
|
||||
"Share revoked": "མཉམ་སྤྱོད་ཕྱིར་འཐེན།",
|
||||
"Could not revoke share": "མཉམ་སྤྱོད་ཕྱིར་འཐེན་མ་ཐུབ།",
|
||||
"Uploading book…": "དཔེ་ཆ་ཡར་སྐྱེལ་བཞིན་པ…",
|
||||
"Generating…": "བཟོ་བཞིན་པ…",
|
||||
"Generate share link": "མཉམ་སྤྱོད་འབྲེལ་མཐུད་བཟོ།",
|
||||
"Share URL": "མཉམ་སྤྱོད་གནས་སྡོད།",
|
||||
"Copy link": "འབྲེལ་མཐུད་པར་སློག",
|
||||
"Copied": "པར་སློག་གྲུབ།",
|
||||
"Share via…": "བརྒྱུད་ནས་མཉམ་སྤྱོད…",
|
||||
"Expires {{date}}": "{{date}} ལ་དུས་ཟད།",
|
||||
"Revoking…": "ཕྱིར་འཐེན་བཞིན་པ…",
|
||||
"Revoke share": "མཉམ་སྤྱོད་ཕྱིར་འཐེན།",
|
||||
"Sign in to share books": "དཔེ་ཆ་མཉམ་སྤྱོད་པར་ཐོ་འགོད་གནང་རོགས།",
|
||||
"Expiring soon": "མྱུར་དུ་ཟད་ངེས།",
|
||||
"Missing share token": "མཉམ་སྤྱོད་ཐམ་ག་མེད།",
|
||||
"Could not add to your library": "ཁྱེད་ཀྱི་དཔེ་མཛོད་ལ་སྣོན་མ་ཐུབ།",
|
||||
"This share link is no longer available": "མཉམ་སྤྱོད་འབྲེལ་མཐུད་འདི་མི་འཐོབ།",
|
||||
"The original link may have expired or been revoked.": "གདོད་མའི་འབྲེལ་མཐུད་དུས་ཟད་པའམ་ཕྱིར་འཐེན་སྲིད།",
|
||||
"Get Readest": "Readest བཞེས་པ།",
|
||||
"Loading shared book…": "མཉམ་སྤྱོད་དཔེ་ཆ་མར་སྐྱེལ་བཞིན་པ…",
|
||||
"Adding…": "སྣོན་བཞིན་པ…",
|
||||
"Add to my library": "ངའི་དཔེ་མཛོད་ལ་སྣོན།",
|
||||
"Could not load your shares": "ཁྱེད་ཀྱི་མཉམ་སྤྱོད་མར་སྐྱེལ་མ་ཐུབ།",
|
||||
"Revoked": "ཕྱིར་འཐེན།",
|
||||
"Expired": "དུས་ཟད།",
|
||||
"Shared books": "མཉམ་སྤྱོད་དཔེ་ཆ།",
|
||||
"You haven't shared any books yet": "ཁྱོས་ད་དུང་དཔེ་ཆ་གང་ཡང་མཉམ་སྤྱོད་མ་བྱས།",
|
||||
"Open a book and tap Share to send it to a friend.": "དཔེ་ཆ་ཞིག་ཁ་ཕྱེ་ནས་གྲོགས་པོ་ལ་སྐུར་ཆེད་མཉམ་སྤྱོད་གཏུགས།",
|
||||
"{{count}} active_other": "{{count}} ལས་འཛིན་བཞིན་པ།",
|
||||
"{{count}} downloads_other": "{{count}} ཕབ་ལེན།",
|
||||
"starts at saved page": "ཉར་ཚགས་ཤོག་ངོས་ནས་འགོ་འཛུགས།",
|
||||
"More actions": "བྱ་འགུལ་མང་བ།",
|
||||
"Loading…": "མར་སྐྱེལ་བཞིན་པ…",
|
||||
"Load more": "མང་བ་མར་སྐྱེལ།",
|
||||
"Could not load shared book": "མཉམ་སྤྱོད་དཔེ་ཆ་མར་སྐྱེལ་མ་ཐུབ།",
|
||||
"The share link is missing required information.": "མཉམ་སྤྱོད་འབྲེལ་མཐུད་ལ་ཆ་རྐྱེན་མེད།",
|
||||
"Please check your connection and try again.": "ཁྱེད་ཀྱི་འབྲེལ་མཐུད་ཞིབ་འཇུག་གནང་ནས་ཡང་བསྐྱར་ཐེངས་གཅིག་འབད་རོགས།",
|
||||
"Sign in to import shared books": "མཉམ་སྤྱོད་དཔེ་ཆ་འདྲེན་པར་ཐོ་འགོད་གནང་རོགས།",
|
||||
"Already in your library": "ཁྱེད་ཀྱི་དཔེ་མཛོད་ནང་ཡོད་ཟིན་པ།",
|
||||
"Added to your library": "ཁྱེད་ཀྱི་དཔེ་མཛོད་ལ་སྣོན་ཟིན།",
|
||||
"Could not import shared book": "མཉམ་སྤྱོད་དཔེ་ཆ་འདྲེན་མ་ཐུབ།",
|
||||
"Manage Shared Links": "མཉམ་སྤྱོད་འབྲེལ་མཐུད་དོ་དམ།",
|
||||
"Expires in": "དུས་ཟད།",
|
||||
"{{count}} days_other": "ཉིན་{{count}}",
|
||||
"Expires in {{count}} days_other": "ཉིན་{{count}}ནང་ཟད།",
|
||||
"Expires in {{count}} hours_other": "ཆུ་ཚོད་{{count}}ནང་ཟད།",
|
||||
"Open in app": "ཉེར་སྤྱོད་ནང་ཁ་ཕྱེ།",
|
||||
"Shared with you": "ཁྱེད་དང་མཉམ་སྤྱོད་བྱས།",
|
||||
"Downloading… {{percent}}%": "ཕབ་ལེན་བྱེད་བཞིན་པ… {{percent}}%",
|
||||
"Import progress": "ནང་འདྲེན་གྱི་ཡར་ཐོན།",
|
||||
"Share reading progress": "ཀློག་འདོན་གྱི་ཡར་ཐོན་སྤེལ་མཁོ།",
|
||||
"Includes your reading progress": "ཁྱེད་ཀྱི་ཀློག་འདོན་གྱི་ཡར་ཐོན་འདུས།",
|
||||
"Synced {{time}}": "{{time}}མཉམ་སྒྲིག་བྱས་པ",
|
||||
"Sync Info": "མཉམ་སྒྲིག་གྱི་ཆ་འཕྲིན",
|
||||
"Last Synced": "མཐའ་མའི་མཉམ་སྒྲིག",
|
||||
"DICT": "DICT",
|
||||
"Slob": "Slob",
|
||||
"DICT bundles need a .index file and a .dict.dz file.": "DICT ཕུང་པོར་ .index ཡིག་ཆ་དང་ .dict.dz ཡིག་ཆ་གཉིས་ཀ་དགོས།",
|
||||
"Slob bundles need a .slob file.": "Slob ཕུང་པོར་ .slob ཡིག་ཆ་ཞིག་དགོས།",
|
||||
"URL template must start with http(s):// and contain %WORD%.": "URL ཕྱི་མོ་ http(s):// གིས་འགོ་འཛུགས་དགོས་ལ་ %WORD% ཡོད་དགོས།",
|
||||
"Web": "དྲ་རྒྱ",
|
||||
"Add Web Search": "དྲ་རྒྱའི་འཚོལ་བཤེར་སྣོན།",
|
||||
"Edit Web Search": "དྲ་རྒྱའི་འཚོལ་བཤེར་རྩོམ་སྒྲིག",
|
||||
"e.g. Google": "དཔེར་ན། Google",
|
||||
"URL Template": "URL ཕྱི་མོ",
|
||||
"Use %WORD% where the looked-up word should appear.": "འཚོལ་བཤེར་བྱས་པའི་ཚིག་འཆར་ས་གནས་སུ་ %WORD% བཀོལ་སྤྱོད་གནང་རོགས།",
|
||||
"Open the search result in your browser:": "ཁྱེད་ཀྱི་བརྡ་འཚོལ་མཁོ་ཆས་ལ་འཚོལ་བཤེར་གྲུབ་འབྲས་ཁ་ཕྱེ:",
|
||||
"Open in {{name}}": "{{name}} ནང་ཁ་ཕྱེ།",
|
||||
"Book Fingerprint": "དཔེ་ཆའི་མཛུབ་རྗེས།",
|
||||
"Search the web": "དྲ་ངོས་སུ་འཚོལ་ཞིབ།",
|
||||
"App deeplink (readest://)": "ཉེར་སྤྱོད་ཟབ་སྦྲེལ (readest://)",
|
||||
"Universal web link (https://)": "སྤྱི་ཁྱབ་དྲ་སྦྲེལ (https://)",
|
||||
"Name cannot be empty.": "མིང་སྟོང་པར་འཇོག་མི་རུང་།",
|
||||
"Replaced {{count}} existing dictionary_other": "གཞི་ནས་ཡོད་པའི་ཚིག་མཛོད་ {{count}} བརྗེ་སྒྱུར་བྱས།",
|
||||
"Cancel Edit": "བཟོ་བཅོས་ཕྱིར་འཐེན།",
|
||||
"Edit Dictionary": "ཚིག་མཛོད་བཟོ་བཅོས།",
|
||||
"MDict bundles use .mdx files; companion .mdd and .css files are optional.": "MDict གི་ཡིག་ཆས་ཚོགས་ཀྱིས་ .mdx ཡིག་ཆ་སྤྱོད། སྦྲེལ་མཐུན་ .mdd དང་ .css ཡིག་ཆ་ཡིན་ན་འདེམས་ཆོག",
|
||||
"Dictionary name": "ཚིག་མཛོད་མིང་།",
|
||||
"This audio can't play here — the dictionary uses an outdated format. Try one with Opus, MP3, or WAV audio.": "སྒྲ་འདི་འདིར་སྒྲོག་མི་ཐུབ — ཚིག་མཛོད་འདིས་རྩིས་མེད་པའི་རྣམ་པ་སྤྱོད། Opus, MP3, ཡང་ན་ WAV གི་སྒྲ་ལྡན་པ་ཞིག་ལ་འདེམས་སྡུར་གྱིས།",
|
||||
"File uploaded: {{title}}": "ཡིག་ཆ་སྤྲད་ཟིན། {{title}}",
|
||||
"File downloaded: {{title}}": "ཡིག་ཆ་ཕབ་ལེན་བྱས་ཟིན། {{title}}",
|
||||
"Deleted cloud copy of the file: {{title}}": "ཡིག་ཆའི་སྤྲི་དོན་གྱི་འདྲ་བཤུས་བསུབ་ཟིན། {{title}}",
|
||||
"Failed to upload file: {{title}}": "ཡིག་ཆ་སྤྲད་མ་ཐུབ། {{title}}",
|
||||
"Failed to download file: {{title}}": "ཡིག་ཆ་ཕབ་ལེན་བྱས་མ་ཐུབ། {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ཡིག་ཆའི་སྤྲི་དོན་གྱི་འདྲ་བཤུས་བསུབ་ཐུབ་མེད། {{title}}",
|
||||
"{{percentage}}% used": "སྤྱོད་ཟིན་ {{percentage}}%",
|
||||
"Set PIN…": "PIN སྒྲིག་པ་…",
|
||||
"Require a 4-digit PIN to open Readest": "Readest ཁ་ཕྱེ་བར་ཨང་གྲངས་ ༤ ཡོད་པའི་ PIN ཞིག་དགོས",
|
||||
"Change PIN…": "PIN བརྗེ་བ་…",
|
||||
"Disable PIN…": "PIN སྤོ་བ་…",
|
||||
"Sync passphrase ready": "མཉམ་སྦྱོར་གསང་ཚིག་གྲ་སྒྲིག་ཟིན་པ།",
|
||||
"This permanently deletes the encrypted credentials we sync (e.g., OPDS catalog passwords) on every device. Local copies are preserved. You will need to re-enter the sync passphrase or set a new one. Continue?": "འདིས་ཡིག་ཆ་ཁག་གསང་སྦས་སུ་མཉམ་སྦྱོར་བྱས་པ་རྣམས་ (དཔེར་ན། OPDS དཀར་ཆག་གི་གསང་ཨང་) སྒྲིག་ཆས་རེ་རེའི་ཐོག་གཏན་འཇགས་སུ་སུབ་སྲིད། ས་གནས་ཀྱི་པར་གཞི་ཉར་ཡོད། ཁྱོད་ཀྱིས་མཉམ་སྦྱོར་གསང་ཚིག་སྔར་བཞིན་འཇུག་པའམ་གསར་པ་སྒྲིག་དགོས། མུ་མཐུད་དམ།",
|
||||
"Sync passphrase forgotten — all encrypted fields cleared": "མཉམ་སྦྱོར་གསང་ཚིག་བརྗེད་སོང་ — གསང་སྦས་ཀྱི་ས་སྟོང་ཚང་མ་སུབ་ཟིན།",
|
||||
"Sync passphrase": "མཉམ་སྦྱོར་གསང་ཚིག",
|
||||
"Set passphrase": "གསང་ཚིག་སྒྲིག་པ།",
|
||||
"Forgot passphrase": "གསང་ཚིག་བརྗེད་སོང་།",
|
||||
"Incorrect PIN": "PIN ནོར་འདུག",
|
||||
"App locked": "མཉེན་ཆས་སྒོ་བརྒྱབ་ཡོད",
|
||||
"Enter your PIN": "ཁྱེད་ཀྱི་ PIN འབྲི་རོགས",
|
||||
"Readest is locked. Enter your 4-digit PIN to continue.": "Readest སྒོ་བརྒྱབ་ཡོད། མུ་མཐུད་པར་ཨང་གྲངས་ ༤ ཡོད་པའི་ PIN འབྲི་རོགས།",
|
||||
"PIN code": "PIN ཨང་གྲངས",
|
||||
"Forgetting your PIN locks you out of this device. You'll need to clear the app's data to reset it.": "PIN བརྗེད་ན་ཁྱེད་ཀྱིས་སྒྲིག་ཆས་འདིར་འཛུལ་མི་ཐུབ། སླར་སྒྲིག་པར་མཉེན་ཆས་ཀྱི་གཞི་གྲངས་གཙང་སེལ་བྱ་དགོས།",
|
||||
"Passphrase must be at least 8 characters": "གསང་ཚིག་ལ་ཡི་གེ་གྲངས་ ༨ ལས་མི་ཉུང་བ་དགོས།",
|
||||
"Passphrases do not match": "གསང་ཚིག་གཉིས་མཐུན་མི་འདུག",
|
||||
"Set sync passphrase": "མཉམ་སྦྱོར་གསང་ཚིག་སྒྲིག་པ།",
|
||||
"Enter sync passphrase": "མཉམ་སྦྱོར་གསང་ཚིག་འཇུག་རོགས།",
|
||||
"A sync passphrase encrypts your sensitive fields (like OPDS catalog credentials) before they sync. We never see this passphrase. Pick something memorable — there is no recovery without it.": "མཉམ་སྦྱོར་གསང་ཚིག་གིས་གལ་ཆེའི་ས་སྟོང་རྣམས་ (དཔེར་ན་ OPDS དཀར་ཆག་གི་ངོས་འཛིན་ཆ་འཕྲིན) མཉམ་སྦྱོར་མ་བྱས་གོང་གསང་སྦས་སུ་སྒྱུར། ང་ཚོས་གསང་ཚིག་འདི་ནམ་ཡང་མཐོང་མི་ཐུབ། དྲན་སླ་བ་ཞིག་འདེམས་རོགས། — དེ་མེད་ན་སླར་འཚོལ་མི་ཐུབ།",
|
||||
"Enter the sync passphrase you set on another device to decrypt your synced credentials.": "ཁྱེད་ཀྱི་མཉམ་སྦྱོར་ངོས་འཛིན་ཆ་འཕྲིན་གསང་སྦས་འགྲོལ་ཆེད་སྒྲིག་ཆས་གཞན་པོར་སྒྲིག་པའི་མཉམ་སྦྱོར་གསང་ཚིག་འཇུག་རོགས།",
|
||||
"Confirm passphrase": "གསང་ཚིག་གཏན་འཁེལ།",
|
||||
"Unlock": "ཁ་ཕྱེ།",
|
||||
"Set PIN": "PIN སྒྲིག་པ",
|
||||
"Change PIN": "PIN བརྗེ་བ",
|
||||
"Disable PIN": "PIN སྤོ་བ",
|
||||
"Enter your current PIN, then choose a new 4-digit PIN.": "ཁྱེད་ཀྱི་ད་ལྟའི་ PIN འབྲི་རྗེས། ཨང་གྲངས་ ༤ ཡོད་པའི་ PIN གསར་པ་ཞིག་འདེམས།",
|
||||
"Enter your current PIN to disable the app lock.": "མཉེན་ཆས་ཀྱི་སྒོ་བརྒྱབ་པ་སྤོ་བར་ཁྱེད་ཀྱི་ད་ལྟའི་ PIN འབྲི་རོགས།",
|
||||
"PIN must be {{length}} digits": "PIN ནི་ཨང་གྲངས་ {{length}} ཡིན་དགོས",
|
||||
"PINs do not match": "PIN མི་འདྲ",
|
||||
"Current PIN": "ད་ལྟའི་ PIN",
|
||||
"New PIN": "PIN གསར་པ",
|
||||
"Confirm new PIN": "PIN གསར་པ་གཏན་འཁེལ",
|
||||
"Manage Sync": "མཉམ་འགྲོ་སྟངས་འཛིན།",
|
||||
"Imported book files and library metadata": "ནང་འཇུག་བྱས་པའི་དཔེ་ཆའི་ཡིག་ཆ་དང་དཔེ་མཛོད་མཛོད་གནད་སྡུད།",
|
||||
"Last-read position, bookmarks, and per-book preferences": "མཐའ་མཇུག་ཀློག་སའི་གནས་ས། དཔེ་རྟགས། དཔེ་ཆ་རེ་རེའི་འདེམས་སྒྲིག།",
|
||||
"Highlights and notes": "གསལ་རྟགས་དང་ཟིན་བྲིས།",
|
||||
"Fonts": "ཡིག་གཟུགས།",
|
||||
"Custom font files": "རང་འདེམས་ཡིག་གཟུགས་ཀྱི་ཡིག་ཆ།",
|
||||
"Backgrounds": "རྒྱབ་ལྗོངས།",
|
||||
"Custom background textures": "རང་འདེམས་རྒྱབ་ལྗོངས་ཀྱི་རི་མོ།",
|
||||
"OPDS catalogs": "OPDS དཀར་ཆག།",
|
||||
"Saved catalog URLs and (encrypted) credentials": "ཉར་ཚགས་བྱས་པའི་དཀར་ཆག་གི་URL་དང་(གསང་འཇུག་བྱས་པའི་)ཐོབ་ཐང་ལག་འཁྱེར།",
|
||||
"Wrong sync passphrase — synced credentials could not be decrypted": "མཉམ་འགྲོ་གསང་གྲངས་ནོར་འདུག — མཉམ་འགྲོ་བྱས་པའི་ཐོབ་ཐང་ལག་འཁྱེར་ཁ་ཕྱེ་མི་ཐུབ།",
|
||||
"Sync passphrase data on the server was reset. Re-encrypting your credentials under the new passphrase…": "ཞབས་ཞུ་བའི་ཐོག་གི་མཉམ་འགྲོ་གསང་གྲངས་ཀྱི་གནད་སྡུད་སླར་སྒྲིག་བྱས་སོང་། གསང་གྲངས་གསར་པས་ཁྱེད་ཀྱི་ཐོབ་ཐང་ལག་འཁྱེར་སླར་གསང་འཇུག་བྱེད་བཞིན་པ་ཡིན…",
|
||||
"Failed to decrypt synced credentials": "མཉམ་འགྲོ་བྱས་པའི་ཐོབ་ཐང་ལག་འཁྱེར་ཁ་ཕྱེ་བར་ཕམ་ཉེས་བྱུང་།",
|
||||
"Imported dictionary bundles and settings": "ནང་འཇུག་བྱས་པའི་ཚིག་མཛོད་ཐུམ་སྒྲིལ་དང་སྒྲིག་འགོད།",
|
||||
"Choose what syncs across your devices. Disabling a category stops this device from sending or receiving rows of that kind. Anything already on the server is left alone, re-enabling resumes from where you stopped.": "ཁྱེད་ཀྱི་སྒྲིག་ཆས་ཁག་གི་བར་ལ་གང་མཉམ་འགྲོ་དགོས་པ་འདེམས་རོགས། སྡེ་ཚན་ཞིག་ནུས་མེད་བཟོས་ན། སྒྲིག་ཆས་འདིས་རིགས་དེའི་ཡིག་ཆ་སྐུར་སྤྲོད་དམ་ལེན་པ་མཚམས་འཇོག་བྱེད། ཞབས་ཞུ་བ་སྟེང་གནས་ཟིན་པའི་གང་ཡང་འགྱུར་ལྡོག་མི་ཐེབས། ཡང་བསྐྱར་ནུས་ཅན་བཟོས་ཚེ། མཚམས་འཇོག་སའི་གནས་ནས་མུ་མཐུད་འགྲོ་བ་ཡིན།",
|
||||
"Data Sync": "གཞི་གྲངས་མཉམ་འགྲོ།",
|
||||
"Manage Fonts": "ཡིག་གཟུགས་སྟངས་འཛིན།",
|
||||
"App settings": "མཉེན་ཆས་སྒྲིག་འགོད།",
|
||||
"Theme, highlight colours, integrations (KOSync, Readwise, Hardcover), and dictionary order": "བརྗོད་གཞི། གསལ་རྟགས་ཁ་དོག ཟུང་འབྲེལ་ (KOSync, Readwise, Hardcover) དང་ཚིག་མཛོད་ཀྱི་གོ་རིམ།",
|
||||
"Required while Dictionaries sync is enabled": "ཚིག་མཛོད་ཁག་མཉམ་འགྲོ་སྤྱོད་སྐབས་དགོས་མཁོ་ཡོད།",
|
||||
"Resets in {{hours}} hr {{minutes}} min": "{{hours}} ཆུ་ཚོད་ {{minutes}} སྐར་མ་ནང་སླར་གསོ་བྱེད།",
|
||||
"Pick a 4-digit PIN. You will need to enter it every time you open Readest. There is no way to recover a forgotten PIN. Clearing the app data is the only way to reset it.": "ཨང་གྲངས་བཞི་ཅན་གྱི་ PIN ཞིག་འདེམས་རོགས། Readest ཕྱེ་རུས་ཐེངས་རེར་ཁྱེད་ཀྱིས་དེ་ནང་འཇུག་དགོས། བརྗེད་སོང་བའི་ PIN ཞིག་སླར་གསོ་བྱེད་ཐབས་མེད། ཉེར་སྤྱོད་ཀྱི་གཞི་གྲངས་གཙང་སེལ་བྱེད་པ་ནི་སླར་སྒྲིག་བྱེད་ཐབས་གཅིག་པོ་ཡིན།",
|
||||
"Credentials": "ངོས་སྦྱོར་ཆ་འཕྲིན།",
|
||||
"Tokens, usernames, and passwords for OPDS, KOReader, Hardcover, and Readwise. When disabled, credentials remain on this device only and are never uploaded.": "OPDS, KOReader, Hardcover, དང་ Readwise བཅས་ཀྱི་ཐོ་ཀེན་དང་སྤྱོད་མི་མིང་དང་གསང་གྲངས། སྤོ་འཛུགས་མེད་པའི་སྐབས། ངོས་སྦྱོར་ཆ་འཕྲིན་ཡིག་ཆས་འདི་ཁོ་ནར་གནས་པ་དང་ནམ་ཡང་སྤོ་གཏོང་མི་བྱེད།",
|
||||
"Sensitive synced fields are encrypted before upload. Set a passphrase now or later when encryption is needed.": "གཙོ་གནད་ཆགས་པའི་མཉམ་སྡེབ་ཐོ་ཁོངས་ཡིག་ཆ་སྤོ་གཏོང་མ་བྱས་སྔོན་ལ་གསང་སྦས་སུ་འགྱུར་བར་བཟོ། གསང་སྦས་དགོས་སྐབས་ད་ལྟ་འམ་ཕྱིས་སུ་གསང་ཚིག་ཕྲེང་གཅིག་སྒྲིག་འཛུགས་གནང་རོགས།",
|
||||
"Saved to this account. You will be prompted for it when decrypting credentials.": "རྩིས་ཐོ་འདིར་ཉར་ཚགས་བྱས་ཡོད། ངོས་སྦྱོར་ཆ་འཕྲིན་ལ་གསང་སྦས་འགྲོལ་སྐབས་ཁྱེད་ལ་འདྲི་སློང་གནང་འགྱུར།",
|
||||
"Reading progress on this device differs from \"{{deviceName}}\".": "སྒྲིག་ཆས་འདིའི་ཀློག་འགྲོས་ནི་\"{{deviceName}}\"དང་མི་འདྲ།",
|
||||
"This device": "སྒྲིག་ཆས་འདི།",
|
||||
"Red": "དམར་པོ།",
|
||||
"Yellow": "སེར་པོ།",
|
||||
"Green": "ལྗང་ཁུ།",
|
||||
"Blue": "སྔོན་པོ།",
|
||||
"Violet": "མུ་མེན།",
|
||||
"Edit color": "ཁ་དོག་རྩོམ་སྒྲིག",
|
||||
"Add custom color": "རང་འདེམས་ཁ་དོག་སྣོན་པ།",
|
||||
"Add label": "བྱང་བུ་སྣོན་པ།",
|
||||
"Choose color": "ཁ་དོག་འདེམས་པ།",
|
||||
"Connected to Hardcover. Last synced {{time}}.": "Hardcover ལ་མཐུད་ཟིན། མཐའ་མའི་མཉམ་སྒྲིག་ {{time}}།",
|
||||
"Integrations": "མཉམ་སྦྲེལ།",
|
||||
"Hardcover": "Hardcover",
|
||||
"Connected to Readwise. Last synced {{time}}.": "Readwise ལ་མཐུད་ཟིན། མཐའ་མའི་མཉམ་སྒྲིག་ {{time}}།",
|
||||
"Readwise": "Readwise",
|
||||
"Browse and download books from online catalogs.": "དྲ་ཐོག་དཀར་ཆག་ནས་དེབ་བལྟ་ཞིབ་དང་ཕབ་ལེན་བྱེད་པ།",
|
||||
"Connected as {{user}}": "{{user}} ཡི་མིང་འོག་མཐུད་ཟིན།",
|
||||
"Not connected": "མ་མཐུད།",
|
||||
"{{count}} catalog_other": "དཀར་ཆག་ {{count}}",
|
||||
"No catalogs": "དཀར་ཆག་མེད།",
|
||||
"Connect Readest to external services for sync, highlights, and catalogs.": "Readest ཕྱི་ཕྱོགས་ཞབས་ཞུ་ལ་སྦྲེལ་ནས་མཉམ་སྒྲིག་དང་གསལ་སྟོན། དཀར་ཆག་སོགས་སྤྱོད།",
|
||||
"Reading Sync": "ཀློག་པའི་མཉམ་སྒྲིག",
|
||||
"Content Sources": "ནང་དོན་འབྱུང་ཁུངས།",
|
||||
"Scroll tabs": "ཤོག་བྱང་འགུལ་སྐྱོད།",
|
||||
"Catalog actions": "དཔེ་ཐོའི་བྱ་སྤྱོད།",
|
||||
"File Content": "ཡིག་ཆའི་ནང་དོན།",
|
||||
"Start your library": "ཁྱེད་ཀྱི་དཔེ་མཛོད་འགོ་འཛུགས།",
|
||||
"Pick a book from your device to add it to your library.": "ཁྱེད་ཀྱི་སྒྲིག་ཆས་ནས་དཔེ་དེབ་ཅིག་འདེམས་ནས་ཁྱེད་ཀྱི་དཔེ་མཛོད་ལ་ཁ་སྣོན་གྱིས།",
|
||||
"Drop a book anywhere on this window, or pick one from your computer.": "སྒེའུ་ཁུང་འདིའི་གང་སར་དཔེ་དེབ་ཅིག་འཇོག་པའམ་ཡང་ན་ཁྱེད་ཀྱི་གློག་ཀླད་ནས་འདེམས་རོགས།",
|
||||
"Sign in to sync your library": "ཁྱེད་ཀྱི་དཔེ་མཛོད་མཉམ་སྦྱོར་གྱི་ཆེད་ནང་བསྐྱོད་གནང་རོགས།"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"Auto Mode": "Automatischer Modus",
|
||||
"Behavior": "Verhalten",
|
||||
"Book": "Buch",
|
||||
"Book Cover": "Buchcover",
|
||||
"Bookmark": "Lesezeichen",
|
||||
"Cancel": "Abbrechen",
|
||||
"Chapter": "Kapitel",
|
||||
@@ -102,11 +101,9 @@
|
||||
"Updated": "Aktualisiert",
|
||||
"Version {{version}}": "Version {{version}}",
|
||||
"Vertical Direction": "Vertikale Richtung",
|
||||
"Welcome to your library. You can import your books here and read them anytime.": "Willkommen in Ihrer Bibliothek. Sie können hier Ihre Bücher importieren und jederzeit lesen.",
|
||||
"Wikipedia": "Wikipedia",
|
||||
"Writing Mode": "Schreibmodus",
|
||||
"Your Library": "Ihre Bibliothek",
|
||||
"TTS not supported for PDF": "TTS wird für PDF nicht unterstützt",
|
||||
"Override Book Font": "Buch-Schriftart überschreiben",
|
||||
"Apply to All Books": "Auf alle Bücher anwenden",
|
||||
"Apply to This Book": "Auf dieses Buch anwenden",
|
||||
@@ -173,9 +170,6 @@
|
||||
"Token": "Token",
|
||||
"Your OTP token": "Ihr OTP-Token",
|
||||
"Verify token": "Token überprüfen",
|
||||
"Sign in with Google": "Mit Google anmelden",
|
||||
"Sign in with Apple": "Mit Apple anmelden",
|
||||
"Sign in with GitHub": "Mit GitHub anmelden",
|
||||
"Account": "Konto",
|
||||
"Failed to delete user. Please try again later.": "Benutzer konnte nicht gelöscht werden. Bitte versuchen Sie es später erneut.",
|
||||
"Community Support": "Community-Support",
|
||||
@@ -188,12 +182,11 @@
|
||||
"RTL Direction": "RTL-Richtung",
|
||||
"Maximum Column Height": "Maximale Spaltenhöhe",
|
||||
"Maximum Column Width": "Maximale Spaltenbreite",
|
||||
"Continuous Scroll": "Kontinuierliches Scrollen",
|
||||
"Fullscreen": "Vollbild",
|
||||
"No supported files found. Supported formats: {{formats}}": "Keine unterstützten Dateien gefunden. Unterstützte Formate: {{formats}}",
|
||||
"Drop to Import Books": "Zum Importieren von Büchern ablegen",
|
||||
"Custom": "Eigen",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Die ganze Welt ist eine Bühne,\nUnd alle Männer und Frauen sind bloß Spieler;\nSie haben ihre Abgänge und ihre Auftritte,\nUnd ein Mann spielt in seinem Leben viele Rollen,\nSeine Taten sind sieben Zeitalter.\n\n— William Shakespeare",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Die ganze Welt ist eine Bühne,\nUnd alle Männer und Frauen sind bloß Spieler;\nSie haben ihre Abgänge und ihre Auftritte,\nUnd ein Mann spielt in seinem Leben viele Rollen,\nSeine Taten sind sieben Zeitalter.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Benutzerdefiniertes Thema",
|
||||
"Theme Name": "Themenname",
|
||||
"Text Color": "Textfarbe",
|
||||
@@ -220,9 +213,9 @@
|
||||
"Auto Import on File Open": "Automatischer Import beim Öffnen einer Datei",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Keine Kapitel erkannt.",
|
||||
"Failed to parse the EPUB file.": "Fehler beim Parsen der EPUB-Datei.",
|
||||
"This book format is not supported.": "Dieses Buchformat wird nicht unterstützt.",
|
||||
"No chapters detected": "Keine Kapitel erkannt",
|
||||
"Failed to parse the EPUB file": "Fehler beim Parsen der EPUB-Datei",
|
||||
"This book format is not supported": "Dieses Buchformat wird nicht unterstützt",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Übersetzung konnte nicht abgerufen werden. Bitte zuerst anmelden und erneut versuchen.",
|
||||
"Group": "Gruppieren",
|
||||
"Always on Top": "Immer im Vordergrund",
|
||||
@@ -259,8 +252,6 @@
|
||||
"(from 'As You Like It', Act II)": "(aus 'Wie es euch gefällt', Akt II)",
|
||||
"Link Color": "Linkfarbe",
|
||||
"Volume Keys for Page Flip": "Volumen-Tasten für Seitenwechsel",
|
||||
"Clicks for Page Flip": "Tippen zum Blättern",
|
||||
"Swap Clicks Area": "Tippbereiche tauschen",
|
||||
"Screen": "Bildschirm",
|
||||
"Orientation": "Orientierung",
|
||||
"Portrait": "Hochformat",
|
||||
@@ -296,7 +287,6 @@
|
||||
"Disable Translation": "Übersetzung deaktivieren",
|
||||
"Scroll": "Scrollen",
|
||||
"Overlap Pixels": "Überlappungspixel",
|
||||
"Click": "Tippen",
|
||||
"System Language": "Systemsprache",
|
||||
"Security": "Sicherheit",
|
||||
"Allow JavaScript": "JavaScript zulassen",
|
||||
@@ -307,7 +297,6 @@
|
||||
"No notes match your search": "Noch keine Notizen gefunden",
|
||||
"Search notes and excerpts...": "Notizen und Auszüge suchen...",
|
||||
"Sign in to Sync": "Anmelden zum Synchronisieren",
|
||||
"Synced at {{time}}": "Synchronisiert um {{time}}",
|
||||
"Never synced": "Nie synchronisiert",
|
||||
"Show Remaining Time": "Verbleibende Zeit anzeigen",
|
||||
"{{time}} min left in chapter": "{{time}} min verbleibend im Kapitel",
|
||||
@@ -341,11 +330,11 @@
|
||||
"Fit": "Anpassen",
|
||||
"Reset {{settings}}": "{{settings}} zurücksetzen",
|
||||
"Reset Settings": "Einstellungen zurücksetzen",
|
||||
"{{count}} pages left in chapter_one": "{{count}} Seite verbleibend im Kapitel",
|
||||
"{{count}} pages left in chapter_other": "{{count}} Seiten verbleibend im Kapitel",
|
||||
"{{count}} pages left in chapter_one": "<0>{{count}}</0><1> Seite verbleibend im Kapitel</1>",
|
||||
"{{count}} pages left in chapter_other": "<0>{{count}}</0><1> Seiten verbleibend im Kapitel</1>",
|
||||
"Show Remaining Pages": "Verbleibende Seiten anzeigen",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Abo verwalten",
|
||||
"Coming Soon": "Demnächst verfügbar",
|
||||
@@ -365,7 +354,6 @@
|
||||
"Email:": "E-Mail:",
|
||||
"Plan:": "Tarif:",
|
||||
"Amount:": "Betrag:",
|
||||
"Subscription ID:": "Abo-ID:",
|
||||
"Go to Library": "Zur Bibliothek",
|
||||
"Need help? Contact our support team at support@readest.com": "Brauchen Sie Hilfe? Kontaktieren Sie unser Support-Team unter support@readest.com",
|
||||
"Free Plan": "Gratis-Tarif",
|
||||
@@ -471,14 +459,12 @@
|
||||
"Remove from Cloud Only": "Nur aus der Cloud entfernen",
|
||||
"Remove from Device Only": "Nur vom Gerät entfernen",
|
||||
"Disconnected": "Getrennt",
|
||||
"KOReader Sync Settings": "KOReader Sync-Einstellungen",
|
||||
"Sync Strategy": "Sync-Strategie",
|
||||
"Ask on conflict": "Bei Konflikten fragen",
|
||||
"Always use latest": "Immer die neueste Version verwenden",
|
||||
"Send changes only": "Nur Änderungen senden",
|
||||
"Receive changes only": "Nur Änderungen empfangen",
|
||||
"Checksum Method": "Prüfziffernverfahren",
|
||||
"File Content (recommended)": "Dateiinhalte (empfohlen)",
|
||||
"File Name": "Dateiname",
|
||||
"Device Name": "Gerätename",
|
||||
"Connect to your KOReader Sync server.": "Mit Ihrem KOReader Sync-Server verbinden.",
|
||||
@@ -489,10 +475,7 @@
|
||||
"Connect": "Verbinden",
|
||||
"KOReader Sync": "KOReader Sync",
|
||||
"Sync Conflict": "Sync-Konflikt",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "Lesefortschritt von \"{{deviceName}}\" synchronisieren?",
|
||||
"another device": "ein anderes Gerät",
|
||||
"Local Progress": "Lokaler Fortschritt",
|
||||
"Remote Progress": "Entfernter Fortschritt",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "Seite {{page}} von {{total}} ({{percentage}}%)",
|
||||
"Current position": "Aktuelle Position",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "Ungefähr Seite {{page}} von {{total}} ({{percentage}}%)",
|
||||
@@ -606,5 +589,828 @@
|
||||
"Opacity": "Opazität",
|
||||
"Size": "Größe",
|
||||
"Cover": "Cover",
|
||||
"Contain": "Inhalt"
|
||||
"Contain": "Inhalt",
|
||||
"{{number}} pages left in chapter": "<0>{{number}}</0><1> Seiten verbleibend im Kapitel</1>",
|
||||
"Device": "Gerät",
|
||||
"E-Ink Mode": "E-Ink-Modus",
|
||||
"Highlight Colors": "Hervorhebungsfarben",
|
||||
"Pagination": "Seitenumbruch",
|
||||
"Disable Double Tap": "Doppeltippen deaktivieren",
|
||||
"Tap to Paginate": "Tippen zum Blättern",
|
||||
"Click to Paginate": "Klicken zum Blättern",
|
||||
"Tap Both Sides": "Tippen Sie auf beide Seiten",
|
||||
"Click Both Sides": "Klicken Sie auf beide Seiten",
|
||||
"Swap Tap Sides": "Seiten für das Tippen tauschen",
|
||||
"Swap Click Sides": "Seiten für das Klicken tauschen",
|
||||
"Source and Translated": "Quell- und Übersetzungstext",
|
||||
"Translated Only": "Nur Übersetzungstext",
|
||||
"Source Only": "Nur Quelltext",
|
||||
"TTS Text": "TTS-Text",
|
||||
"The book file is corrupted": "Die Buchdatei ist beschädigt",
|
||||
"The book file is empty": "Die Buchdatei ist leer",
|
||||
"Failed to open the book file": "Fehler beim Öffnen der Buchdatei",
|
||||
"On-Demand Purchase": "Kauf auf Anfrage",
|
||||
"Full Customization": "Vollständige Anpassung",
|
||||
"Lifetime Plan": "Lebenslange Planung",
|
||||
"One-Time Payment": "Einmalige Zahlung",
|
||||
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Zahlen Sie einmalig, um lebenslangen Zugriff auf bestimmte Funktionen auf allen Geräten zu genießen. Kaufen Sie bestimmte Funktionen oder Dienstleistungen nur, wenn Sie sie benötigen.",
|
||||
"Expand Cloud Sync Storage": "Cloud-Synchronisierungs-Speicher erweitern",
|
||||
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Erweitern Sie Ihren Cloud-Speicher für immer mit einem einmaligen Kauf. Jeder zusätzliche Kauf fügt mehr Speicherplatz hinzu.",
|
||||
"Unlock All Customization Options": "Alle Anpassungsoptionen freischalten",
|
||||
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Zusätzliche Themen, Schriftarten, Layoutoptionen und Vorlesefunktionen, Übersetzer, Cloud-Speicherdienste freischalten.",
|
||||
"Purchase Successful!": "Kauf erfolgreich!",
|
||||
"lifetime": "lebenslang",
|
||||
"Thank you for your purchase! Your payment has been processed successfully.": "Danke für Ihren Kauf! Ihre Zahlung wurde erfolgreich verarbeitet.",
|
||||
"Order ID:": "Bestell-ID:",
|
||||
"TTS Highlighting": "TTS-Hervorhebung",
|
||||
"Style": "Stil",
|
||||
"Underline": "Unterstreichen",
|
||||
"Strikethrough": "Durchgestrichen",
|
||||
"Squiggly": "Wellig",
|
||||
"Outline": "Umriss",
|
||||
"Quick Colors": "Schnellfarben",
|
||||
"Highlighter": "Textmarker",
|
||||
"Save Book Cover": "Buchcover speichern",
|
||||
"Auto-save last book cover": "Letztes Buchcover automatisch speichern",
|
||||
"Back": "Zurück",
|
||||
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Bestätigungs-E-Mail gesendet! Bitte überprüfe deine alte und neue E-Mail-Adresse, um die Änderung zu bestätigen.",
|
||||
"Failed to update email": "E-Mail konnte nicht aktualisiert werden",
|
||||
"New Email": "Neue E-Mail",
|
||||
"Your new email": "Deine neue E-Mail-Adresse",
|
||||
"Updating email ...": "E-Mail wird aktualisiert ...",
|
||||
"Update email": "E-Mail aktualisieren",
|
||||
"Current email": "Aktuelle E-Mail",
|
||||
"Update Email": "E-Mail aktualisieren",
|
||||
"All": "Alle",
|
||||
"Unable to open book": "Buch kann nicht geöffnet werden",
|
||||
"Punctuation": "Interpunktion",
|
||||
"Replace Quotation Marks": "Anführungszeichen ersetzen",
|
||||
"Enabled only in vertical layout.": "Nur im vertikalen Layout aktiviert.",
|
||||
"No Conversion": "Keine Konvertierung",
|
||||
"Simplified to Traditional": "Vereinfacht → Traditionell",
|
||||
"Traditional to Simplified": "Traditionell → Vereinfacht",
|
||||
"Simplified to Traditional (Taiwan)": "Vereinfacht → Traditionell (Taiwan)",
|
||||
"Simplified to Traditional (Hong Kong)": "Vereinfacht → Traditionell (Hongkong)",
|
||||
"Simplified to Traditional (Taiwan), with phrases": "Vereinfacht → Traditionell (Taiwan • Phrasen)",
|
||||
"Traditional (Taiwan) to Simplified": "Traditionell (Taiwan) → Vereinfacht",
|
||||
"Traditional (Hong Kong) to Simplified": "Traditionell (Hongkong) → Vereinfacht",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Traditionell (Taiwan • Phrasen) → Vereinfacht",
|
||||
"Convert Simplified and Traditional Chinese": "Vereinfach./Traditionell konvertieren",
|
||||
"Convert Mode": "Konvertierungsmodus",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Automatisches Speichern des Buchcovers für den Sperrbildschirm fehlgeschlagen: {{error}}",
|
||||
"Download from Cloud": "Aus der Cloud herunterladen",
|
||||
"Upload to Cloud": "In die Cloud hochladen",
|
||||
"Clear Custom Fonts": "Benutzerdefinierte Schriftarten löschen",
|
||||
"Columns": "Spalten",
|
||||
"OPDS Catalogs": "OPDS-Kataloge",
|
||||
"Adding LAN addresses is not supported in the web app version.": "Das Hinzufügen von LAN-Adressen wird in der Web-App nicht unterstützt.",
|
||||
"Invalid OPDS catalog. Please check the URL.": "Ungültiger OPDS-Katalog. Bitte überprüfe die URL.",
|
||||
"Browse and download books from online catalogs": "Bücher aus Online-Katalogen durchsuchen und herunterladen",
|
||||
"My Catalogs": "Meine Kataloge",
|
||||
"Add Catalog": "Katalog hinzufügen",
|
||||
"No catalogs yet": "Noch keine Kataloge",
|
||||
"Add your first OPDS catalog to start browsing books": "Füge deinen ersten OPDS-Katalog hinzu, um Bücher zu durchsuchen.",
|
||||
"Add Your First Catalog": "Ersten Katalog hinzufügen",
|
||||
"Browse": "Durchsuchen",
|
||||
"Popular Catalogs": "Beliebte Kataloge",
|
||||
"Add": "Hinzufügen",
|
||||
"Add OPDS Catalog": "OPDS-Katalog hinzufügen",
|
||||
"Catalog Name": "Katalogname",
|
||||
"My Calibre Library": "Meine Calibre-Bibliothek",
|
||||
"OPDS URL": "OPDS-URL",
|
||||
"Username (optional)": "Benutzername (optional)",
|
||||
"Password (optional)": "Passwort (optional)",
|
||||
"Description (optional)": "Beschreibung (optional)",
|
||||
"A brief description of this catalog": "Kurzbeschreibung dieses Katalogs",
|
||||
"Validating...": "Wird überprüft...",
|
||||
"View All": "Alle anzeigen",
|
||||
"Forward": "Weiter",
|
||||
"Home": "Start",
|
||||
"{{count}} items_one": "{{count}} Element",
|
||||
"{{count}} items_other": "{{count}} Elemente",
|
||||
"Download completed": "Download abgeschlossen",
|
||||
"Download failed": "Download fehlgeschlagen",
|
||||
"Open Access": "Offener Zugriff",
|
||||
"Borrow": "Ausleihen",
|
||||
"Buy": "Kaufen",
|
||||
"Subscribe": "Abonnieren",
|
||||
"Sample": "Leseprobe",
|
||||
"Download": "Herunterladen",
|
||||
"Open & Read": "Öffnen & Lesen",
|
||||
"Tags": "Tags",
|
||||
"Tag": "Tag",
|
||||
"First": "Erste",
|
||||
"Previous": "Vorherige",
|
||||
"Next": "Nächste",
|
||||
"Last": "Letzte",
|
||||
"Cannot Load Page": "Seite kann nicht geladen werden",
|
||||
"An error occurred": "Ein Fehler ist aufgetreten",
|
||||
"Online Library": "Online-Bibliothek",
|
||||
"URL must start with http:// or https://": "Die URL muss mit http:// oder https:// beginnen",
|
||||
"Title, Author, Tag, etc...": "Titel, Autor, Tag, etc...",
|
||||
"Query": "Suchbegriff",
|
||||
"Subject": "Thema",
|
||||
"Enter {{terms}}": "Gib {{terms}} ein",
|
||||
"No search results found": "Keine Suchergebnisse gefunden",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS-Feed konnte nicht geladen werden: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Suche in {{title}}",
|
||||
"Manage Storage": "Speicher verwalten",
|
||||
"Failed to load files": "Dateien konnten nicht geladen werden",
|
||||
"Deleted {{count}} file(s)_one": "{{count}} Datei gelöscht",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} Dateien gelöscht",
|
||||
"Failed to delete {{count}} file(s)_one": "Löschen von {{count}} Datei fehlgeschlagen",
|
||||
"Failed to delete {{count}} file(s)_other": "Löschen von {{count}} Dateien fehlgeschlagen",
|
||||
"Failed to delete files": "Dateien konnten nicht gelöscht werden",
|
||||
"Total Files": "Gesamtdateien",
|
||||
"Total Size": "Gesamtgröße",
|
||||
"Quota": "Kontingent",
|
||||
"Used": "Verwendet",
|
||||
"Files": "Dateien",
|
||||
"Search files...": "Dateien suchen...",
|
||||
"Newest First": "Neueste zuerst",
|
||||
"Oldest First": "Älteste zuerst",
|
||||
"Largest First": "Größte zuerst",
|
||||
"Smallest First": "Kleinste zuerst",
|
||||
"Name A-Z": "Name A–Z",
|
||||
"Name Z-A": "Name Z–A",
|
||||
"{{count}} selected_one": "{{count}} ausgewählt",
|
||||
"{{count}} selected_other": "{{count}} ausgewählt",
|
||||
"Delete Selected": "Ausgewählte löschen",
|
||||
"Created": "Erstellt",
|
||||
"No files found": "Keine Dateien gefunden",
|
||||
"No files uploaded yet": "Noch keine Dateien hochgeladen",
|
||||
"files": "Dateien",
|
||||
"Page {{current}} of {{total}}": "Seite {{current}} von {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Möchten Sie {{count}} ausgewählte Datei wirklich löschen?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Möchten Sie {{count}} ausgewählte Dateien wirklich löschen?",
|
||||
"Cloud Storage Usage": "Cloud-Speichernutzung",
|
||||
"Rename Group": "Gruppe umbenennen",
|
||||
"From Directory": "Aus Verzeichnis",
|
||||
"Successfully imported {{count}} book(s)_one": "Erfolgreich 1 Buch importiert",
|
||||
"Successfully imported {{count}} book(s)_other": "Erfolgreich {{count}} Bücher importiert",
|
||||
"Count": "Anzahl",
|
||||
"Start Page": "Startseite",
|
||||
"Search in OPDS Catalog...": "Im OPDS-Katalog suchen...",
|
||||
"Please log in to use advanced TTS features": "Bitte melde dich an, um erweiterte TTS-Funktionen zu nutzen",
|
||||
"Word limit of 30 words exceeded.": "Das Wortlimit von 30 Wörtern wurde überschritten.",
|
||||
"Proofread": "Korrekturlesen",
|
||||
"Current selection": "Aktuelle Auswahl",
|
||||
"All occurrences in this book": "Alle Vorkommen in diesem Buch",
|
||||
"All occurrences in your library": "Alle Vorkommen in Ihrer Bibliothek",
|
||||
"Selected text:": "Ausgewählter Text:",
|
||||
"Replace with:": "Ersetzen durch:",
|
||||
"Enter text...": "Text eingeben …",
|
||||
"Case sensitive:": "Groß-/Kleinschreibung:",
|
||||
"Scope:": "Geltungsbereich:",
|
||||
"Selection": "Auswahl",
|
||||
"Library": "Bibliothek",
|
||||
"Yes": "Ja",
|
||||
"No": "Nein",
|
||||
"Proofread Replacement Rules": "Korrektur-Ersetzungsregeln",
|
||||
"Selected Text Rules": "Regeln für ausgewählten Text",
|
||||
"No selected text replacement rules": "Keine Ersetzungsregeln für ausgewählten Text",
|
||||
"Book Specific Rules": "Buchspezifische Regeln",
|
||||
"No book-level replacement rules": "Keine Ersetzungsregeln auf Buchebene",
|
||||
"Disable Quick Action": "Schnellaktion deaktivieren",
|
||||
"Enable Quick Action on Selection": "Schnellaktion bei Auswahl aktivieren",
|
||||
"None": "Keine",
|
||||
"Annotation Tools": "Anmerkungswerkzeuge",
|
||||
"Enable Quick Actions": "Schnellaktionen aktivieren",
|
||||
"Quick Action": "Schnellaktion",
|
||||
"Copy to Notebook": "In Notizbuch kopieren",
|
||||
"Copy text after selection": "Text nach Auswahl kopieren",
|
||||
"Highlight text after selection": "Text nach Auswahl hervorheben",
|
||||
"Annotate text after selection": "Text nach Auswahl annotieren",
|
||||
"Search text after selection": "Text nach Auswahl suchen",
|
||||
"Look up text in dictionary after selection": "Text nach Auswahl im Wörterbuch nachschlagen",
|
||||
"Translate text after selection": "Text nach Auswahl übersetzen",
|
||||
"Read text aloud after selection": "Text nach Auswahl vorlesen",
|
||||
"Proofread text after selection": "Text nach Auswahl Korrektur lesen",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} aktiv, {{pendingCount}} ausstehend",
|
||||
"{{failedCount}} failed": "{{failedCount}} fehlgeschlagen",
|
||||
"Waiting...": "Warten...",
|
||||
"Failed": "Fehlgeschlagen",
|
||||
"Completed": "Abgeschlossen",
|
||||
"Cancelled": "Abgebrochen",
|
||||
"Retry": "Erneut versuchen",
|
||||
"Active": "Aktiv",
|
||||
"Transfer Queue": "Übertragungswarteschlange",
|
||||
"Upload All": "Alle hochladen",
|
||||
"Download All": "Alle herunterladen",
|
||||
"Resume Transfers": "Übertragungen fortsetzen",
|
||||
"Pause Transfers": "Übertragungen pausieren",
|
||||
"Pending": "Ausstehend",
|
||||
"No transfers": "Keine Übertragungen",
|
||||
"Retry All": "Alle erneut versuchen",
|
||||
"Clear Completed": "Abgeschlossene löschen",
|
||||
"Clear Failed": "Fehlgeschlagene löschen",
|
||||
"Upload queued: {{title}}": "Upload in Warteschlange: {{title}}",
|
||||
"Download queued: {{title}}": "Download in Warteschlange: {{title}}",
|
||||
"Book not found in library": "Buch nicht in der Bibliothek gefunden",
|
||||
"Unknown error": "Unbekannter Fehler",
|
||||
"Please log in to continue": "Bitte melden Sie sich an, um fortzufahren",
|
||||
"Cloud File Transfers": "Cloud-Dateiübertragungen",
|
||||
"Show Search Results": "Suchergebnisse anzeigen",
|
||||
"Search results for '{{term}}'": "Ergebnisse für '{{term}}'",
|
||||
"Close Search": "Suche schließen",
|
||||
"Previous Result": "Vorheriges Ergebnis",
|
||||
"Next Result": "Nächstes Ergebnis",
|
||||
"Bookmarks": "Lesezeichen",
|
||||
"Annotations": "Anmerkungen",
|
||||
"Show Results": "Ergebnisse anzeigen",
|
||||
"Clear search": "Suche löschen",
|
||||
"Clear search history": "Suchverlauf löschen",
|
||||
"Tap to Toggle Footer": "Tippen, um Fußzeile umzuschalten",
|
||||
"Show Current Time": "Aktuelle Uhrzeit anzeigen",
|
||||
"Use 24 Hour Clock": "24-Stunden-Format verwenden",
|
||||
"Show Current Battery Status": "Aktuellen Akkustand anzeigen",
|
||||
"Exported successfully": "Erfolgreich exportiert",
|
||||
"Book exported successfully.": "Buch erfolgreich exportiert.",
|
||||
"Failed to export the book.": "Buch konnte nicht exportiert werden.",
|
||||
"Export Book": "Buch exportieren",
|
||||
"Whole word:": "Ganzes Wort:",
|
||||
"Date Published": "Veröffentlichungsdatum",
|
||||
"Only for TTS:": "Nur für TTS:",
|
||||
"Uploaded": "Hochgeladen",
|
||||
"Downloaded": "Heruntergeladen",
|
||||
"Deleted": "Gelöscht",
|
||||
"Note:": "Notiz:",
|
||||
"Time:": "Zeit:",
|
||||
"Format Options": "Formatoptionen",
|
||||
"Export Date": "Exportdatum",
|
||||
"Chapter Titles": "Kapiteltitel",
|
||||
"Chapter Separator": "Kapiteltrennzeichen",
|
||||
"Highlights": "Markierungen",
|
||||
"Note Date": "Notizdatum",
|
||||
"Advanced": "Erweitert",
|
||||
"Hide": "Ausblenden",
|
||||
"Show": "Anzeigen",
|
||||
"Use Custom Template": "Benutzerdefinierte Vorlage verwenden",
|
||||
"Export Template": "Exportvorlage",
|
||||
"Template Syntax:": "Vorlagensyntax:",
|
||||
"Insert value": "Wert einfügen",
|
||||
"Format date (locale)": "Datum formatieren (Gebietsschema)",
|
||||
"Format date (custom)": "Datum formatieren (benutzerdefiniert)",
|
||||
"Conditional": "Bedingung",
|
||||
"Loop": "Schleife",
|
||||
"Available Variables:": "Verfügbare Variablen:",
|
||||
"Book title": "Buchtitel",
|
||||
"Book author": "Buchautor",
|
||||
"Export date": "Exportdatum",
|
||||
"Array of chapters": "Array von Kapiteln",
|
||||
"Chapter title": "Kapiteltitel",
|
||||
"Array of annotations": "Array von Anmerkungen",
|
||||
"Highlighted text": "Markierter Text",
|
||||
"Annotation note": "Anmerkungsnotiz",
|
||||
"Date Format Tokens:": "Datumsformat-Token:",
|
||||
"Year (4 digits)": "Jahr (4 Ziffern)",
|
||||
"Month (01-12)": "Monat (01-12)",
|
||||
"Day (01-31)": "Tag (01-31)",
|
||||
"Hour (00-23)": "Stunde (00-23)",
|
||||
"Minute (00-59)": "Minute (00-59)",
|
||||
"Second (00-59)": "Sekunde (00-59)",
|
||||
"Show Source": "Quelle anzeigen",
|
||||
"No content to preview": "Kein Inhalt zur Vorschau",
|
||||
"Export": "Exportieren",
|
||||
"Set Timeout": "Zeitlimit festlegen",
|
||||
"Select Voice": "Stimme auswählen",
|
||||
"Toggle Sticky Bottom TTS Bar": "Fixierte TTS-Leiste umschalten",
|
||||
"Display what I'm reading on Discord": "Zeige was ich auf Discord lese",
|
||||
"Show on Discord": "Auf Discord zeigen",
|
||||
"Instant {{action}}": "Sofort {{action}}",
|
||||
"Instant {{action}} Disabled": "Sofort {{action}} deaktiviert",
|
||||
"Annotation": "Anmerkung",
|
||||
"Reset Template": "Vorlage zurücksetzen",
|
||||
"Annotation style": "Anmerkungsstil",
|
||||
"Annotation color": "Anmerkungsfarbe",
|
||||
"Annotation time": "Anmerkungszeit",
|
||||
"AI": "KI",
|
||||
"Are you sure you want to re-index this book?": "Möchten Sie dieses Buch wirklich neu indizieren?",
|
||||
"Enable AI in Settings": "KI in den Einstellungen aktivieren",
|
||||
"Index This Book": "Dieses Buch indizieren",
|
||||
"Enable AI search and chat for this book": "KI-Suche und Chat für dieses Buch aktivieren",
|
||||
"Start Indexing": "Indizierung starten",
|
||||
"Indexing book...": "Buch wird indiziert...",
|
||||
"Preparing...": "Vorbereitung...",
|
||||
"Delete this conversation?": "Diese Unterhaltung löschen?",
|
||||
"No conversations yet": "Noch keine Unterhaltungen",
|
||||
"Start a new chat to ask questions about this book": "Starten Sie einen neuen Chat, um Fragen zu diesem Buch zu stellen",
|
||||
"Rename": "Umbenennen",
|
||||
"New Chat": "Neuer Chat",
|
||||
"Chat": "Chat",
|
||||
"Please enter a model ID": "Bitte geben Sie eine Modell-ID ein",
|
||||
"Model not available or invalid": "Modell nicht verfügbar oder ungültig",
|
||||
"Failed to validate model": "Modellvalidierung fehlgeschlagen",
|
||||
"Couldn't connect to Ollama. Is it running?": "Verbindung zu Ollama fehlgeschlagen. Läuft es?",
|
||||
"Invalid API key or connection failed": "Ungültiger API-Schlüssel oder Verbindung fehlgeschlagen",
|
||||
"Connection failed": "Verbindung fehlgeschlagen",
|
||||
"AI Assistant": "KI-Assistent",
|
||||
"Enable AI Assistant": "KI-Assistent aktivieren",
|
||||
"Provider": "Anbieter",
|
||||
"Ollama (Local)": "Ollama (Lokal)",
|
||||
"AI Gateway (Cloud)": "KI-Gateway (Cloud)",
|
||||
"Ollama Configuration": "Ollama-Konfiguration",
|
||||
"Refresh Models": "Modelle aktualisieren",
|
||||
"AI Model": "KI-Modell",
|
||||
"No models detected": "Keine Modelle erkannt",
|
||||
"AI Gateway Configuration": "KI-Gateway-Konfiguration",
|
||||
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Wählen Sie aus einer Auswahl hochwertiger, wirtschaftlicher KI-Modelle. Sie können auch Ihr eigenes Modell verwenden, indem Sie unten \"Benutzerdefiniertes Modell\" auswählen.",
|
||||
"API Key": "API-Schlüssel",
|
||||
"Get Key": "Schlüssel erhalten",
|
||||
"Model": "Modell",
|
||||
"Custom Model...": "Benutzerdefiniertes Modell...",
|
||||
"Custom Model ID": "Benutzerdefinierte Modell-ID",
|
||||
"Validate": "Validieren",
|
||||
"Model available": "Modell verfügbar",
|
||||
"Connection": "Verbindung",
|
||||
"Test Connection": "Verbindung testen",
|
||||
"Connected": "Verbunden",
|
||||
"Custom Colors": "Benutzerdefinierte Farben",
|
||||
"Color E-Ink Mode": "Farb-E-Ink-Modus",
|
||||
"Reading Ruler": "Lese-Lineal",
|
||||
"Enable Reading Ruler": "Lese-Lineal aktivieren",
|
||||
"Lines to Highlight": "Hervorzuhebende Zeilen",
|
||||
"Ruler Color": "Linealfarbe",
|
||||
"Command Palette": "Befehlspalette",
|
||||
"Search settings and actions...": "Einstellungen und Aktionen suchen...",
|
||||
"No results found for": "Keine Ergebnisse gefunden für",
|
||||
"Type to search settings and actions": "Tippen, um Einstellungen und Aktionen zu suchen",
|
||||
"Recent": "Zuletzt verwendet",
|
||||
"navigate": "navigieren",
|
||||
"select": "auswählen",
|
||||
"close": "schließen",
|
||||
"Search Settings": "Einstellungen suchen",
|
||||
"Page Margins": "Seitenränder",
|
||||
"AI Provider": "KI-Anbieter",
|
||||
"Ollama URL": "Ollama-URL",
|
||||
"Ollama Model": "Ollama-Modell",
|
||||
"AI Gateway Model": "AI Gateway-Modell",
|
||||
"Actions": "Aktionen",
|
||||
"Navigation": "Navigation",
|
||||
"Set status for {{count}} book(s)_one": "Status für {{count}} Buch festlegen",
|
||||
"Set status for {{count}} book(s)_other": "Status für {{count}} Bücher festlegen",
|
||||
"Mark as Unread": "Als ungelesen markieren",
|
||||
"Mark as Finished": "Als beendet markieren",
|
||||
"Finished": "Beendet",
|
||||
"Unread": "Ungelesen",
|
||||
"Clear Status": "Status löschen",
|
||||
"Status": "Status",
|
||||
"Loading": "Laden...",
|
||||
"Exit Paragraph Mode": "Absatzmodus verlassen",
|
||||
"Paragraph Mode": "Absatzmodus",
|
||||
"Embedding Model": "Einbettungsmodell",
|
||||
"{{count}} book(s) synced_one": "{{count}} Buch synchronisiert",
|
||||
"{{count}} book(s) synced_other": "{{count}} Bücher synchronisiert",
|
||||
"Unable to start RSVP": "RSVP kann nicht gestartet werden",
|
||||
"RSVP not supported for PDF": "RSVP wird für PDF nicht unterstützt",
|
||||
"Select Chapter": "Kapitel auswählen",
|
||||
"Context": "Kontext",
|
||||
"Ready": "Bereit",
|
||||
"Chapter Progress": "Kapitelfortschritt",
|
||||
"words": "Wörter",
|
||||
"{{time}} left": "{{time}} übrig",
|
||||
"Reading progress": "Lesefortschritt",
|
||||
"Skip back 15 words": "15 Wörter zurück",
|
||||
"Back 15 words (Shift+Left)": "15 Wörter zurück (Shift+Links)",
|
||||
"Pause (Space)": "Pause (Leertaste)",
|
||||
"Play (Space)": "Abspielen (Leertaste)",
|
||||
"Skip forward 15 words": "15 Wörter vor",
|
||||
"Forward 15 words (Shift+Right)": "15 Wörter vor (Shift+Rechts)",
|
||||
"Decrease speed": "Geschwindigkeit verringern",
|
||||
"Slower (Left/Down)": "Langsamer (Links/Unten)",
|
||||
"Increase speed": "Geschwindigkeit erhöhen",
|
||||
"Faster (Right/Up)": "Schneller (Rechts/Oben)",
|
||||
"Start RSVP Reading": "RSVP-Lesen starten",
|
||||
"Choose where to start reading": "Wählen Sie, wo das Lesen beginnen soll",
|
||||
"From Chapter Start": "Vom Kapitelanfang",
|
||||
"Start reading from the beginning of the chapter": "Ab dem Anfang des Kapitels lesen",
|
||||
"Resume": "Fortsetzen",
|
||||
"Continue from where you left off": "Dort fortfahren, wo Sie aufgehört haben",
|
||||
"From Current Page": "Von der aktuellen Seite",
|
||||
"Start from where you are currently reading": "Dort beginnen, wo Sie gerade lesen",
|
||||
"From Selection": "Von der Auswahl",
|
||||
"Speed Reading Mode": "Schnelllesemodus",
|
||||
"Scroll left": "Nach links scrollen",
|
||||
"Scroll right": "Nach rechts scrollen",
|
||||
"Library Sync Progress": "Bibliotheks-Synchronisierungsfortschritt",
|
||||
"Back to library": "Zurück zur Bibliothek",
|
||||
"Group by...": "Gruppieren nach...",
|
||||
"Export as Plain Text": "Als reinen Text exportieren",
|
||||
"Export as Markdown": "Als Markdown exportieren",
|
||||
"Show Page Navigation Buttons": "Navigationsschaltflächen",
|
||||
"Page {{number}}": "Seite {{number}}",
|
||||
"highlight": "Hervorhebung",
|
||||
"underline": "Unterstreichung",
|
||||
"squiggly": "geschlängelt",
|
||||
"red": "rot",
|
||||
"violet": "violett",
|
||||
"blue": "blau",
|
||||
"green": "grün",
|
||||
"yellow": "gelb",
|
||||
"Select {{style}} style": "Stil {{style}} auswählen",
|
||||
"Select {{color}} color": "Farbe {{color}} auswählen",
|
||||
"Close Book": "Buch schließen",
|
||||
"Speed Reading": "Schnelllesen",
|
||||
"Close Speed Reading": "Schnelllesen schließen",
|
||||
"Authors": "Autoren",
|
||||
"Books": "Bücher",
|
||||
"Groups": "Gruppen",
|
||||
"Back to TTS Location": "Zurück zur TTS-Position",
|
||||
"Metadata": "Metadaten",
|
||||
"Image viewer": "Bildbetrachter",
|
||||
"Previous Image": "Vorheriges Bild",
|
||||
"Next Image": "Nächstes Bild",
|
||||
"Zoomed": "Gezoomt",
|
||||
"Zoom level": "Zoomstufe",
|
||||
"Table viewer": "Tabellenbetrachter",
|
||||
"Unable to connect to Readwise. Please check your network connection.": "Verbindung zu Readwise nicht möglich. Bitte überprüfen Sie Ihre Netzwerkverbindung.",
|
||||
"Invalid Readwise access token": "Ungültiges Readwise-Zugriffstoken",
|
||||
"Disconnected from Readwise": "Von Readwise getrennt",
|
||||
"Never": "Nie",
|
||||
"Sync Enabled": "Synchronisierung aktiviert",
|
||||
"Disconnect": "Trennen",
|
||||
"Connect your Readwise account to sync highlights.": "Verbinden Sie Ihr Readwise-Konto, um Highlights zu synchronisieren.",
|
||||
"Get your access token at": "Holen Sie sich Ihr Zugriffstoken unter",
|
||||
"Access Token": "Zugriffstoken",
|
||||
"Paste your Readwise access token": "Fügen Sie Ihr Readwise-Zugriffstoken ein",
|
||||
"Readwise Sync": "Readwise-Synchronisierung",
|
||||
"Push Highlights": "Highlights übertragen",
|
||||
"Highlights synced to Readwise": "Highlights mit Readwise synchronisiert",
|
||||
"Readwise sync failed: no internet connection": "Readwise-Synchronisierung fehlgeschlagen: Keine Internetverbindung",
|
||||
"Readwise sync failed: {{error}}": "Readwise-Synchronisierung fehlgeschlagen: {{error}}",
|
||||
"System Screen Brightness": "System-Bildschirmhelligkeit",
|
||||
"Page:": "Seite:",
|
||||
"Page: {{number}}": "Seite: {{number}}",
|
||||
"Annotation page number": "Seitenzahl der Anmerkung",
|
||||
"Translating...": "Übersetzen...",
|
||||
"Show Battery Percentage": "Akkustand in Prozent anzeigen",
|
||||
"Hide Scrollbar": "Scrollleiste ausblenden",
|
||||
"Skip to last reading position": "Zur letzten Leseposition springen",
|
||||
"Show context": "Kontext anzeigen",
|
||||
"Hide context": "Kontext ausblenden",
|
||||
"Decrease font size": "Schriftgröße verkleinern",
|
||||
"Increase font size": "Schriftgröße vergrößern",
|
||||
"Focus": "Fokus",
|
||||
"Theme color": "Themenfarbe",
|
||||
"Import failed": "Import fehlgeschlagen",
|
||||
"Available Formatters:": "Verfügbare Formatierer:",
|
||||
"Format date": "Datum formatieren",
|
||||
"Markdown block quote (> per line)": "Markdown-Blockzitat (> pro Zeile)",
|
||||
"Newlines to <br>": "Zeilenumbrüche zu <br>",
|
||||
"Change case": "Groß-/Kleinschreibung ändern",
|
||||
"Trim whitespace": "Leerzeichen entfernen",
|
||||
"Truncate to n characters": "Auf n Zeichen kürzen",
|
||||
"Replace text": "Text ersetzen",
|
||||
"Fallback value": "Ersatzwert",
|
||||
"Get length": "Länge ermitteln",
|
||||
"First/last element": "Erstes/letztes Element",
|
||||
"Join array": "Array verbinden",
|
||||
"Backup failed: {{error}}": "Sicherung fehlgeschlagen: {{error}}",
|
||||
"Select Backup": "Sicherung auswählen",
|
||||
"Restore failed: {{error}}": "Wiederherstellung fehlgeschlagen: {{error}}",
|
||||
"Backup & Restore": "Sichern & Wiederherstellen",
|
||||
"Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Erstellen Sie eine Sicherung Ihrer Bibliothek oder stellen Sie eine frühere Sicherung wieder her. Die Wiederherstellung wird mit Ihrer aktuellen Bibliothek zusammengeführt.",
|
||||
"Backup Library": "Bibliothek sichern",
|
||||
"Restore Library": "Bibliothek wiederherstellen",
|
||||
"Creating backup...": "Sicherung wird erstellt...",
|
||||
"Restoring library...": "Bibliothek wird wiederhergestellt...",
|
||||
"{{current}} of {{total}} items": "{{current}} von {{total}} Elementen",
|
||||
"Backup completed successfully!": "Sicherung erfolgreich abgeschlossen!",
|
||||
"Restore completed successfully!": "Wiederherstellung erfolgreich abgeschlossen!",
|
||||
"Your library has been saved to the selected location.": "Ihre Bibliothek wurde am ausgewählten Ort gespeichert.",
|
||||
"{{added}} books added, {{updated}} books updated.": "{{added}} Bücher hinzugefügt, {{updated}} Bücher aktualisiert.",
|
||||
"Operation failed": "Vorgang fehlgeschlagen",
|
||||
"Loading library...": "Bibliothek wird geladen...",
|
||||
"{{count}} books refreshed_one": "{{count}} Buch aktualisiert",
|
||||
"{{count}} books refreshed_other": "{{count}} Bücher aktualisiert",
|
||||
"Failed to refresh metadata": "Metadaten-Aktualisierung fehlgeschlagen",
|
||||
"Refresh Metadata": "Metadaten aktualisieren",
|
||||
"Keyboard Shortcuts": "Tastaturkürzel",
|
||||
"View all keyboard shortcuts": "Alle Tastaturkürzel anzeigen",
|
||||
"Switch Sidebar Tab": "Seitenleisten-Tab wechseln",
|
||||
"Toggle Notebook": "Notizbuch ein-/ausblenden",
|
||||
"Search in Book": "Im Buch suchen",
|
||||
"Toggle Scroll Mode": "Scrollmodus umschalten",
|
||||
"Toggle Select Mode": "Auswahlmodus umschalten",
|
||||
"Toggle Bookmark": "Lesezeichen umschalten",
|
||||
"Toggle Text to Speech": "Sprachausgabe umschalten",
|
||||
"Play / Pause TTS": "Sprachausgabe abspielen / pausieren",
|
||||
"Toggle Paragraph Mode": "Absatzmodus umschalten",
|
||||
"Highlight Selection": "Auswahl hervorheben",
|
||||
"Underline Selection": "Auswahl unterstreichen",
|
||||
"Annotate Selection": "Auswahl kommentieren",
|
||||
"Search Selection": "Auswahl suchen",
|
||||
"Copy Selection": "Auswahl kopieren",
|
||||
"Translate Selection": "Auswahl übersetzen",
|
||||
"Dictionary Lookup": "Im Wörterbuch nachschlagen",
|
||||
"Read Aloud Selection": "Auswahl vorlesen",
|
||||
"Proofread Selection": "Auswahl korrekturlesen",
|
||||
"Open Settings": "Einstellungen öffnen",
|
||||
"Open Command Palette": "Befehlspalette öffnen",
|
||||
"Show Keyboard Shortcuts": "Tastaturkürzel anzeigen",
|
||||
"Open Books": "Bücher öffnen",
|
||||
"Toggle Fullscreen": "Vollbild umschalten",
|
||||
"Close Window": "Fenster schließen",
|
||||
"Quit App": "App beenden",
|
||||
"Go Left / Previous Page": "Nach links / Vorherige Seite",
|
||||
"Go Right / Next Page": "Nach rechts / Nächste Seite",
|
||||
"Go Up": "Nach oben",
|
||||
"Go Down": "Nach unten",
|
||||
"Previous Chapter": "Vorheriges Kapitel",
|
||||
"Next Chapter": "Nächstes Kapitel",
|
||||
"Scroll Half Page Down": "Halbe Seite nach unten scrollen",
|
||||
"Scroll Half Page Up": "Halbe Seite nach oben scrollen",
|
||||
"Save Note": "Notiz speichern",
|
||||
"Single Section Scroll": "Einzelabschnitt-Scrollen",
|
||||
"General": "Allgemein",
|
||||
"Text to Speech": "Sprachausgabe",
|
||||
"Zoom": "Zoom",
|
||||
"Window": "Fenster",
|
||||
"Your Bookshelf": "Dein Bücherregal",
|
||||
"TTS": "Sprachausgabe",
|
||||
"Media Info": "Medieninfo",
|
||||
"Update Frequency": "Aktualisierungshäufigkeit",
|
||||
"Every Sentence": "Jeden Satz",
|
||||
"Every Paragraph": "Jeden Absatz",
|
||||
"Every Chapter": "Jedes Kapitel",
|
||||
"TTS Media Info Update Frequency": "TTS-Medieninfo-Aktualisierungshäufigkeit",
|
||||
"Select reading speed": "Lesegeschwindigkeit wählen",
|
||||
"Drag to seek": "Ziehen zum Suchen",
|
||||
"Punctuation Delay": "Satzzeichen-Verzögerung",
|
||||
"Please confirm that this OPDS connection will be proxied through Readest servers on the web app before continuing.": "Bitte bestätigen Sie, dass diese OPDS-Verbindung über Readest-Server in der Web-App weitergeleitet wird, bevor Sie fortfahren.",
|
||||
"Custom Headers (optional)": "Benutzerdefinierte Header (optional)",
|
||||
"Add one header per line using \"Header-Name: value\".": "Fügen Sie pro Zeile einen Header im Format \"Header-Name: value\" hinzu.",
|
||||
"I understand this OPDS connection will be proxied through Readest servers on the web app. If I do not trust Readest with these credentials or headers, I should use the native app instead.": "Ich verstehe, dass diese OPDS-Verbindung über Readest-Server in der Web-App weitergeleitet wird. Wenn ich Readest meine Anmeldedaten oder Header nicht anvertraue, sollte ich stattdessen die native App verwenden.",
|
||||
"Unable to connect to Hardcover. Please check your network connection.": "Verbindung zu Hardcover nicht möglich. Bitte überprüfen Sie Ihre Netzwerkverbindung.",
|
||||
"Invalid Hardcover API token": "Ungültiges Hardcover-API-Token",
|
||||
"Disconnected from Hardcover": "Von Hardcover getrennt",
|
||||
"Connect your Hardcover account to sync reading progress and notes.": "Verbinden Sie Ihr Hardcover-Konto, um Lesefortschritt und Notizen zu synchronisieren.",
|
||||
"Get your API token from hardcover.app → Settings → API.": "Holen Sie Ihr API-Token von hardcover.app → Einstellungen → API.",
|
||||
"API Token": "API-Token",
|
||||
"Paste your Hardcover API token": "Fügen Sie Ihr Hardcover-API-Token ein",
|
||||
"Hardcover Sync": "Hardcover-Synchronisierung",
|
||||
"Push Notes": "Notizen übertragen",
|
||||
"No annotations or excerpts to sync for this book.": "Keine Anmerkungen oder Auszüge zum Synchronisieren für dieses Buch.",
|
||||
"Configure Hardcover in Settings first.": "Konfigurieren Sie zuerst Hardcover in den Einstellungen.",
|
||||
"No new Hardcover note changes to sync.": "Keine neuen Hardcover-Notizänderungen zum Synchronisieren.",
|
||||
"Hardcover synced: {{inserted}} new, {{updated}} updated, {{skipped}} unchanged": "Hardcover synchronisiert: {{inserted}} neu, {{updated}} aktualisiert, {{skipped}} unverändert",
|
||||
"Hardcover notes sync failed: {{error}}": "Hardcover-Notizsynchronisierung fehlgeschlagen: {{error}}",
|
||||
"Reading progress synced to Hardcover": "Lesefortschritt mit Hardcover synchronisiert",
|
||||
"Hardcover progress sync failed: {{error}}": "Hardcover-Fortschrittssynchronisierung fehlgeschlagen: {{error}}",
|
||||
"ISBN": "ISBN",
|
||||
"Keep existing source identifier": "Vorhandene Quellkennung beibehalten",
|
||||
"Toggle Toolbar": "Symbolleiste umschalten",
|
||||
"Split Hyphens": "Bindestriche trennen",
|
||||
"Apply Theme Colors to PDF": "Designfarben auf PDF anwenden",
|
||||
"Name": "Name",
|
||||
"Remove": "Entfernen",
|
||||
"Edit OPDS Catalog": "OPDS-Katalog bearbeiten",
|
||||
"Save Changes": "Änderungen speichern",
|
||||
"Use Book Layout": "Buchlayout verwenden",
|
||||
"End of this section. Continue to the next.": "Ende dieses Abschnitts. Weiter zum nächsten.",
|
||||
"Auto-download": "Automatisch herunterladen",
|
||||
"Auto-download new items": "Neue Einträge automatisch herunterladen",
|
||||
"Automatically download new publications when the app syncs": "Neue Veröffentlichungen automatisch herunterladen, wenn die App synchronisiert",
|
||||
"{{count}} new item(s) downloaded from OPDS_one": "{{count}} neuer Eintrag von OPDS heruntergeladen",
|
||||
"{{count}} new item(s) downloaded from OPDS_other": "{{count}} neue Einträge von OPDS heruntergeladen",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_one": "Synchronisation von {{count}} OPDS-Katalog fehlgeschlagen",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_other": "Synchronisation von {{count}} OPDS-Katalogen fehlgeschlagen",
|
||||
"Last synced {{when}}": "Zuletzt synchronisiert {{when}}",
|
||||
"Failed downloads": "Fehlgeschlagene Downloads",
|
||||
"No failed downloads": "Keine fehlgeschlagenen Downloads",
|
||||
"Attempts: {{count}}_one": "Versuche: {{count}}",
|
||||
"Attempts: {{count}}_other": "Versuche: {{count}}",
|
||||
"Skip": "Überspringen",
|
||||
"Skip all": "Alle überspringen",
|
||||
"Retry all": "Alle erneut versuchen",
|
||||
"{{count}} failed_one": "{{count}} fehlgeschlagen",
|
||||
"{{count}} failed_other": "{{count}} fehlgeschlagen",
|
||||
"(none)": "(keine)",
|
||||
"Identifiers": "Bezeichner",
|
||||
"Read (Stream)": "Lesen (Stream)",
|
||||
"Failed to start stream": "Stream konnte nicht gestartet werden",
|
||||
"No dictionaries enabled": "Keine Wörterbücher aktiviert",
|
||||
"Enable a dictionary in Settings → Language → Dictionaries.": "Aktiviere ein Wörterbuch unter Einstellungen → Sprache → Wörterbücher.",
|
||||
"Wiktionary": "Wiktionary",
|
||||
"Drag to reorder": "Zum Neuanordnen ziehen",
|
||||
"Built-in": "Integriert",
|
||||
"Bundle is missing on this device. Re-import to use it.": "Bundle fehlt auf diesem Gerät. Erneut importieren, um es zu verwenden.",
|
||||
"This dictionary format is not supported.": "Dieses Wörterbuchformat wird nicht unterstützt.",
|
||||
"MDict": "MDict",
|
||||
"StarDict": "StarDict",
|
||||
"Imported {{count}} dictionary_one": "{{count}} Wörterbuch importiert",
|
||||
"Imported {{count}} dictionary_other": "{{count}} Wörterbücher importiert",
|
||||
"Skipped incomplete bundles: {{names}}": "Unvollständige Bundles übersprungen: {{names}}",
|
||||
"Failed to import dictionary: {{message}}": "Wörterbuch-Import fehlgeschlagen: {{message}}",
|
||||
"Dictionaries": "Wörterbücher",
|
||||
"Delete Dictionary": "Wörterbuch löschen",
|
||||
"Importing…": "Wird importiert …",
|
||||
"Import Dictionary": "Wörterbuch importieren",
|
||||
"No dictionaries available.": "Keine Wörterbücher verfügbar.",
|
||||
"StarDict bundles need .ifo, .idx, and .dict.dz files (.syn optional).": "StarDict-Bundles benötigen .ifo-, .idx- und .dict.dz-Dateien (.syn optional).",
|
||||
"Select all the bundle files together when importing.": "Wähle beim Import alle Bundle-Dateien zusammen aus.",
|
||||
"Manage Dictionaries": "Wörterbücher verwalten",
|
||||
"Select Dictionary Files": "Wörterbuchdateien auswählen",
|
||||
"Read on Wikipedia →": "Auf Wikipedia lesen →",
|
||||
"This link can't be opened": "Dieser Link kann nicht geöffnet werden",
|
||||
"The annotation link is missing required information. The original link may have been truncated.": "Dem Annotationslink fehlen erforderliche Informationen. Der ursprüngliche Link wurde möglicherweise abgeschnitten.",
|
||||
"Go to Readest": "Zu Readest",
|
||||
"Open-source ebook reader for everyone, on every device.": "Open-Source-E-Book-Reader für alle, auf jedem Gerät.",
|
||||
"Open in Readest": "In Readest öffnen",
|
||||
"If Readest didn't open automatically, choose an option below:": "Falls Readest nicht automatisch geöffnet wurde, wähle eine Option:",
|
||||
"Continue reading where you left off.": "Lies dort weiter, wo du aufgehört hast.",
|
||||
"Readest logo": "Readest-Logo",
|
||||
"Opening Readest...": "Readest wird geöffnet …",
|
||||
"Open in Readest app": "In der Readest-App öffnen",
|
||||
"Continue in browser": "Im Browser fortfahren",
|
||||
"Don't have Readest?": "Du hast Readest noch nicht?",
|
||||
"Book not in your library": "Buch nicht in deiner Bibliothek",
|
||||
"Share Book": "Buch teilen",
|
||||
"Could not create share link": "Freigabelink konnte nicht erstellt werden",
|
||||
"Link copied": "Link kopiert",
|
||||
"Could not copy link": "Link konnte nicht kopiert werden",
|
||||
"Share revoked": "Freigabe widerrufen",
|
||||
"Could not revoke share": "Freigabe konnte nicht widerrufen werden",
|
||||
"Uploading book…": "Buch wird hochgeladen…",
|
||||
"Generating…": "Wird erstellt…",
|
||||
"Generate share link": "Freigabelink erstellen",
|
||||
"Share URL": "Freigabelink",
|
||||
"Copy link": "Link kopieren",
|
||||
"Copied": "Kopiert",
|
||||
"Share via…": "Teilen über…",
|
||||
"Expires {{date}}": "Läuft ab am {{date}}",
|
||||
"Revoking…": "Wird widerrufen…",
|
||||
"Revoke share": "Freigabe widerrufen",
|
||||
"Sign in to share books": "Melde dich an, um Bücher zu teilen",
|
||||
"Expiring soon": "Läuft bald ab",
|
||||
"Missing share token": "Freigabe-Token fehlt",
|
||||
"Could not add to your library": "Konnte nicht zu deiner Bibliothek hinzugefügt werden",
|
||||
"This share link is no longer available": "Dieser Freigabelink ist nicht mehr verfügbar",
|
||||
"The original link may have expired or been revoked.": "Der ursprüngliche Link ist möglicherweise abgelaufen oder wurde widerrufen.",
|
||||
"Get Readest": "Readest holen",
|
||||
"Loading shared book…": "Geteiltes Buch wird geladen…",
|
||||
"Adding…": "Wird hinzugefügt…",
|
||||
"Add to my library": "Zu meiner Bibliothek hinzufügen",
|
||||
"Could not load your shares": "Deine Freigaben konnten nicht geladen werden",
|
||||
"Revoked": "Widerrufen",
|
||||
"Expired": "Abgelaufen",
|
||||
"Shared books": "Geteilte Bücher",
|
||||
"You haven't shared any books yet": "Du hast noch keine Bücher geteilt",
|
||||
"Open a book and tap Share to send it to a friend.": "Öffne ein Buch und tippe auf „Teilen“, um es an einen Freund zu senden.",
|
||||
"{{count}} active_one": "{{count}} aktiv",
|
||||
"{{count}} active_other": "{{count}} aktiv",
|
||||
"{{count}} downloads_one": "{{count}} Download",
|
||||
"{{count}} downloads_other": "{{count}} Downloads",
|
||||
"starts at saved page": "beginnt auf gespeicherter Seite",
|
||||
"More actions": "Weitere Aktionen",
|
||||
"Loading…": "Wird geladen…",
|
||||
"Load more": "Mehr laden",
|
||||
"Could not load shared book": "Geteiltes Buch konnte nicht geladen werden",
|
||||
"The share link is missing required information.": "Dem Freigabelink fehlen erforderliche Informationen.",
|
||||
"Please check your connection and try again.": "Bitte überprüfen Sie Ihre Verbindung und versuchen Sie es erneut.",
|
||||
"Sign in to import shared books": "Melde dich an, um geteilte Bücher zu importieren",
|
||||
"Already in your library": "Bereits in deiner Bibliothek",
|
||||
"Added to your library": "Zu deiner Bibliothek hinzugefügt",
|
||||
"Could not import shared book": "Geteiltes Buch konnte nicht importiert werden",
|
||||
"Manage Shared Links": "Freigabelinks verwalten",
|
||||
"Expires in": "Läuft ab in",
|
||||
"{{count}} days_one": "1 Tag",
|
||||
"{{count}} days_other": "{{count}} Tage",
|
||||
"Expires in {{count}} days_one": "Läuft in 1 Tag ab",
|
||||
"Expires in {{count}} days_other": "Läuft in {{count}} Tagen ab",
|
||||
"Expires in {{count}} hours_one": "Läuft in 1 Stunde ab",
|
||||
"Expires in {{count}} hours_other": "Läuft in {{count}} Stunden ab",
|
||||
"Open in app": "In der App öffnen",
|
||||
"Shared with you": "Mit dir geteilt",
|
||||
"Downloading… {{percent}}%": "Wird heruntergeladen… {{percent}}%",
|
||||
"Import progress": "Importfortschritt",
|
||||
"Share reading progress": "Lesefortschritt teilen",
|
||||
"Includes your reading progress": "Enthält deinen Lesefortschritt",
|
||||
"Synced {{time}}": "Synchronisiert {{time}}",
|
||||
"Sync Info": "Synchronisierungs-Info",
|
||||
"Last Synced": "Zuletzt synchronisiert",
|
||||
"DICT": "DICT",
|
||||
"Slob": "Slob",
|
||||
"DICT bundles need a .index file and a .dict.dz file.": "DICT-Bundles benötigen eine .index-Datei und eine .dict.dz-Datei.",
|
||||
"Slob bundles need a .slob file.": "Slob-Bundles benötigen eine .slob-Datei.",
|
||||
"URL template must start with http(s):// and contain %WORD%.": "URL-Vorlage muss mit http(s):// beginnen und %WORD% enthalten.",
|
||||
"Web": "Web",
|
||||
"Add Web Search": "Websuche hinzufügen",
|
||||
"Edit Web Search": "Websuche bearbeiten",
|
||||
"e.g. Google": "z. B. Google",
|
||||
"URL Template": "URL-Vorlage",
|
||||
"Use %WORD% where the looked-up word should appear.": "Verwenden Sie %WORD%, wo das gesuchte Wort erscheinen soll.",
|
||||
"Open the search result in your browser:": "Öffne das Suchergebnis in deinem Browser:",
|
||||
"Open in {{name}}": "In {{name}} öffnen",
|
||||
"Book Fingerprint": "Buch-Fingerabdruck",
|
||||
"Search the web": "Im Web suchen",
|
||||
"App deeplink (readest://)": "App-Deeplink (readest://)",
|
||||
"Universal web link (https://)": "Universeller Weblink (https://)",
|
||||
"Name cannot be empty.": "Name darf nicht leer sein.",
|
||||
"Replaced {{count}} existing dictionary_one": "{{count}} bestehendes Wörterbuch ersetzt",
|
||||
"Replaced {{count}} existing dictionary_other": "{{count}} bestehende Wörterbücher ersetzt",
|
||||
"Cancel Edit": "Bearbeiten abbrechen",
|
||||
"Edit Dictionary": "Wörterbuch bearbeiten",
|
||||
"MDict bundles use .mdx files; companion .mdd and .css files are optional.": "MDict-Bundles verwenden .mdx-Dateien; begleitende .mdd- und .css-Dateien sind optional.",
|
||||
"Dictionary name": "Wörterbuchname",
|
||||
"This audio can't play here — the dictionary uses an outdated format. Try one with Opus, MP3, or WAV audio.": "Diese Audio kann hier nicht abgespielt werden — das Wörterbuch verwendet ein veraltetes Format. Probieren Sie ein Wörterbuch mit Opus-, MP3- oder WAV-Audio.",
|
||||
"File uploaded: {{title}}": "Datei hochgeladen: {{title}}",
|
||||
"File downloaded: {{title}}": "Datei heruntergeladen: {{title}}",
|
||||
"Deleted cloud copy of the file: {{title}}": "Cloud-Kopie der Datei gelöscht: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Fehler beim Hochladen der Datei: {{title}}",
|
||||
"Failed to download file: {{title}}": "Fehler beim Herunterladen der Datei: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Fehler beim Löschen der Cloud-Kopie der Datei: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% verbraucht",
|
||||
"Set PIN…": "PIN festlegen…",
|
||||
"Require a 4-digit PIN to open Readest": "Eine 4-stellige PIN zum Öffnen von Readest verlangen",
|
||||
"Change PIN…": "PIN ändern…",
|
||||
"Disable PIN…": "PIN deaktivieren…",
|
||||
"Sync passphrase ready": "Sync-Passphrase bereit",
|
||||
"This permanently deletes the encrypted credentials we sync (e.g., OPDS catalog passwords) on every device. Local copies are preserved. You will need to re-enter the sync passphrase or set a new one. Continue?": "Dies löscht dauerhaft die verschlüsselten Anmeldedaten, die wir auf jedem Gerät synchronisieren (z. B. OPDS-Katalog-Passwörter). Lokale Kopien bleiben erhalten. Sie müssen die Sync-Passphrase erneut eingeben oder eine neue festlegen. Fortfahren?",
|
||||
"Sync passphrase forgotten — all encrypted fields cleared": "Sync-Passphrase vergessen — alle verschlüsselten Felder wurden gelöscht",
|
||||
"Sync passphrase": "Sync-Passphrase",
|
||||
"Set passphrase": "Passphrase festlegen",
|
||||
"Forgot passphrase": "Passphrase vergessen",
|
||||
"Incorrect PIN": "Falsche PIN",
|
||||
"App locked": "App gesperrt",
|
||||
"Enter your PIN": "PIN eingeben",
|
||||
"Readest is locked. Enter your 4-digit PIN to continue.": "Readest ist gesperrt. Geben Sie Ihre 4-stellige PIN ein, um fortzufahren.",
|
||||
"PIN code": "PIN-Code",
|
||||
"Forgetting your PIN locks you out of this device. You'll need to clear the app's data to reset it.": "Wenn Sie Ihre PIN vergessen, sperren Sie sich von diesem Gerät aus. Sie müssen die App-Daten löschen, um sie zurückzusetzen.",
|
||||
"Passphrase must be at least 8 characters": "Die Passphrase muss mindestens 8 Zeichen lang sein",
|
||||
"Passphrases do not match": "Die Passphrasen stimmen nicht überein",
|
||||
"Set sync passphrase": "Sync-Passphrase festlegen",
|
||||
"Enter sync passphrase": "Sync-Passphrase eingeben",
|
||||
"A sync passphrase encrypts your sensitive fields (like OPDS catalog credentials) before they sync. We never see this passphrase. Pick something memorable — there is no recovery without it.": "Eine Sync-Passphrase verschlüsselt Ihre sensiblen Felder (wie OPDS-Katalog-Anmeldedaten), bevor sie synchronisiert werden. Wir sehen diese Passphrase nie. Wählen Sie etwas Einprägsames — ohne sie ist keine Wiederherstellung möglich.",
|
||||
"Enter the sync passphrase you set on another device to decrypt your synced credentials.": "Geben Sie die Sync-Passphrase ein, die Sie auf einem anderen Gerät festgelegt haben, um Ihre synchronisierten Anmeldedaten zu entschlüsseln.",
|
||||
"Confirm passphrase": "Passphrase bestätigen",
|
||||
"Unlock": "Entsperren",
|
||||
"Set PIN": "PIN festlegen",
|
||||
"Change PIN": "PIN ändern",
|
||||
"Disable PIN": "PIN deaktivieren",
|
||||
"Enter your current PIN, then choose a new 4-digit PIN.": "Geben Sie Ihre aktuelle PIN ein und wählen Sie dann eine neue 4-stellige PIN.",
|
||||
"Enter your current PIN to disable the app lock.": "Geben Sie Ihre aktuelle PIN ein, um die App-Sperre zu deaktivieren.",
|
||||
"PIN must be {{length}} digits": "Die PIN muss aus {{length}} Ziffern bestehen",
|
||||
"PINs do not match": "PINs stimmen nicht überein",
|
||||
"Current PIN": "Aktuelle PIN",
|
||||
"New PIN": "Neue PIN",
|
||||
"Confirm new PIN": "Neue PIN bestätigen",
|
||||
"Manage Sync": "Synchronisierung verwalten",
|
||||
"Imported book files and library metadata": "Importierte Buchdateien und Bibliotheksmetadaten",
|
||||
"Last-read position, bookmarks, and per-book preferences": "Zuletzt gelesene Position, Lesezeichen und buchspezifische Einstellungen",
|
||||
"Highlights and notes": "Hervorhebungen und Notizen",
|
||||
"Fonts": "Schriftarten",
|
||||
"Custom font files": "Benutzerdefinierte Schriftdateien",
|
||||
"Backgrounds": "Hintergründe",
|
||||
"Custom background textures": "Benutzerdefinierte Hintergrundtexturen",
|
||||
"OPDS catalogs": "OPDS-Kataloge",
|
||||
"Saved catalog URLs and (encrypted) credentials": "Gespeicherte Katalog-URLs und (verschlüsselte) Anmeldedaten",
|
||||
"Wrong sync passphrase — synced credentials could not be decrypted": "Falsche Sync-Passphrase — synchronisierte Anmeldedaten konnten nicht entschlüsselt werden",
|
||||
"Sync passphrase data on the server was reset. Re-encrypting your credentials under the new passphrase…": "Sync-Passphrase auf dem Server wurde zurückgesetzt. Anmeldedaten werden mit der neuen Passphrase neu verschlüsselt…",
|
||||
"Failed to decrypt synced credentials": "Synchronisierte Anmeldedaten konnten nicht entschlüsselt werden",
|
||||
"Imported dictionary bundles and settings": "Importierte Wörterbuch-Pakete und Einstellungen",
|
||||
"Choose what syncs across your devices. Disabling a category stops this device from sending or receiving rows of that kind. Anything already on the server is left alone, re-enabling resumes from where you stopped.": "Lege fest, was zwischen deinen Geräten synchronisiert wird. Beim Deaktivieren einer Kategorie sendet und empfängt dieses Gerät keine Einträge dieser Art mehr. Bereits auf dem Server vorhandene Daten bleiben unverändert, beim erneuten Aktivieren wird dort fortgesetzt, wo du aufgehört hast.",
|
||||
"Data Sync": "Datensynchronisierung",
|
||||
"Manage Fonts": "Schriftarten verwalten",
|
||||
"App settings": "App-Einstellungen",
|
||||
"Theme, highlight colours, integrations (KOSync, Readwise, Hardcover), and dictionary order": "Design, Hervorhebungsfarben, Integrationen (KOSync, Readwise, Hardcover) und Wörterbuchreihenfolge",
|
||||
"Required while Dictionaries sync is enabled": "Erforderlich, solange die Synchronisierung von Wörterbüchern aktiviert ist",
|
||||
"Resets in {{hours}} hr {{minutes}} min": "Zurückgesetzt in {{hours}} Std. {{minutes}} Min.",
|
||||
"Pick a 4-digit PIN. You will need to enter it every time you open Readest. There is no way to recover a forgotten PIN. Clearing the app data is the only way to reset it.": "Wählen Sie eine 4-stellige PIN. Sie müssen sie jedes Mal eingeben, wenn Sie Readest öffnen. Eine vergessene PIN kann nicht wiederhergestellt werden. Das Löschen der App-Daten ist die einzige Möglichkeit, sie zurückzusetzen.",
|
||||
"Credentials": "Anmeldedaten",
|
||||
"Tokens, usernames, and passwords for OPDS, KOReader, Hardcover, and Readwise. When disabled, credentials remain on this device only and are never uploaded.": "Tokens, Benutzernamen und Passwörter für OPDS, KOReader, Hardcover und Readwise. Wenn deaktiviert, bleiben die Anmeldedaten nur auf diesem Gerät und werden nie hochgeladen.",
|
||||
"Sensitive synced fields are encrypted before upload. Set a passphrase now or later when encryption is needed.": "Sensible synchronisierte Felder werden vor dem Hochladen verschlüsselt. Lege jetzt oder später eine Passphrase fest, wenn die Verschlüsselung benötigt wird.",
|
||||
"Saved to this account. You will be prompted for it when decrypting credentials.": "In diesem Konto gespeichert. Du wirst beim Entschlüsseln von Anmeldedaten danach gefragt.",
|
||||
"Reading progress on this device differs from \"{{deviceName}}\".": "Der Lesefortschritt auf diesem Gerät weicht von „{{deviceName}}“ ab.",
|
||||
"This device": "Dieses Gerät",
|
||||
"Red": "Rot",
|
||||
"Yellow": "Gelb",
|
||||
"Green": "Grün",
|
||||
"Blue": "Blau",
|
||||
"Violet": "Violett",
|
||||
"Edit color": "Farbe bearbeiten",
|
||||
"Add custom color": "Benutzerdefinierte Farbe hinzufügen",
|
||||
"Add label": "Beschriftung hinzufügen",
|
||||
"Choose color": "Farbe auswählen",
|
||||
"Connected to Hardcover. Last synced {{time}}.": "Mit Hardcover verbunden. Zuletzt synchronisiert {{time}}.",
|
||||
"Integrations": "Integrationen",
|
||||
"Hardcover": "Hardcover",
|
||||
"Connected to Readwise. Last synced {{time}}.": "Mit Readwise verbunden. Zuletzt synchronisiert {{time}}.",
|
||||
"Readwise": "Readwise",
|
||||
"Browse and download books from online catalogs.": "Bücher aus Online-Katalogen durchsuchen und herunterladen.",
|
||||
"Connected as {{user}}": "Verbunden als {{user}}",
|
||||
"Not connected": "Nicht verbunden",
|
||||
"{{count}} catalog_one": "{{count}} Katalog",
|
||||
"{{count}} catalog_other": "{{count}} Kataloge",
|
||||
"No catalogs": "Keine Kataloge",
|
||||
"Connect Readest to external services for sync, highlights, and catalogs.": "Verbinden Sie Readest mit externen Diensten für Synchronisierung, Markierungen und Kataloge.",
|
||||
"Reading Sync": "Lese-Synchronisierung",
|
||||
"Content Sources": "Inhaltsquellen",
|
||||
"Scroll tabs": "Tabs scrollen",
|
||||
"Catalog actions": "Katalogaktionen",
|
||||
"File Content": "Dateiinhalt",
|
||||
"Start your library": "Starten Sie Ihre Bibliothek",
|
||||
"Pick a book from your device to add it to your library.": "Wählen Sie ein Buch von Ihrem Gerät, um es Ihrer Bibliothek hinzuzufügen.",
|
||||
"Drop a book anywhere on this window, or pick one from your computer.": "Ziehen Sie ein Buch an eine beliebige Stelle dieses Fensters oder wählen Sie eines von Ihrem Computer aus.",
|
||||
"Sign in to sync your library": "Melden Sie sich an, um Ihre Bibliothek zu synchronisieren"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"Auto Mode": "Αυτόματη λειτουργία",
|
||||
"Behavior": "Συμπεριφορά",
|
||||
"Book": "Βιβλίο",
|
||||
"Book Cover": "Εξώφυλλο βιβλίου",
|
||||
"Bookmark": "Σελιδοδείκτης",
|
||||
"Cancel": "Ακύρωση",
|
||||
"Chapter": "Κεφάλαιο",
|
||||
@@ -103,11 +102,9 @@
|
||||
"Updated": "Ενημερώθηκε",
|
||||
"Version {{version}}": "Έκδοση {{version}}",
|
||||
"Vertical Direction": "Κάθετη κατεύθυνση",
|
||||
"Welcome to your library. You can import your books here and read them anytime.": "Καλώς ήρθατε στη βιβλιοθήκη σας. Μπορείτε να εισάγετε τα βιβλία σας εδώ και να τα διαβάσετε οποιαδήποτε στιγμή.",
|
||||
"Wikipedia": "Βικιπαίδεια",
|
||||
"Writing Mode": "Λειτουργία γραφής",
|
||||
"Your Library": "Η βιβλιοθήκη σας",
|
||||
"TTS not supported for PDF": "Η μετατροπή κειμένου σε ομιλία δεν υποστηρίζεται για αρχεία PDF",
|
||||
"Override Book Font": "Παράκαμψη γραμματοσειράς βιβλίου",
|
||||
"Apply to All Books": "Εφαρμογή σε όλα τα βιβλία",
|
||||
"Apply to This Book": "Εφαρμογή σε αυτό το βιβλίο",
|
||||
@@ -174,9 +171,6 @@
|
||||
"Token": "Κωδικός επαλήθευσης",
|
||||
"Your OTP token": "Ο κωδικός OTP σας",
|
||||
"Verify token": "Επαλήθευση κωδικού",
|
||||
"Sign in with Google": "Σύνδεση με Google",
|
||||
"Sign in with Apple": "Σύνδεση με Apple",
|
||||
"Sign in with GitHub": "Σύνδεση με GitHub",
|
||||
"Account": "Λογαριασμός",
|
||||
"Failed to delete user. Please try again later.": "Αποτυχία διαγραφής χρήστη. Παρακαλώ δοκιμάστε ξανά αργότερα.",
|
||||
"Community Support": "Υποστήριξη κοινότητας",
|
||||
@@ -189,12 +183,11 @@
|
||||
"RTL Direction": "Κατεύθυνση RTL",
|
||||
"Maximum Column Height": "Μέγιστο ύψος στήλης",
|
||||
"Maximum Column Width": "Μέγιστο πλάτος στήλης",
|
||||
"Continuous Scroll": "Συνεχής κύλιση",
|
||||
"Fullscreen": "Πλήρης οθόνη",
|
||||
"No supported files found. Supported formats: {{formats}}": "Δεν βρέθηκαν υποστηριζόμενα αρχεία. Υποστηριζόμενες μορφές: {{formats}}",
|
||||
"Drop to Import Books": "Ρίξτε για εισαγωγή βιβλίων",
|
||||
"Custom": "Προσαρμοσμένο",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Όλος ο κόσμος είναι σκηνή,\nΚαι όλοι οι άνδρες και οι γυναίκες απλώς ηθοποιοί·\nΈχουν τις εξόδους και τις εισόδους τους,\nΚαι ένας άνθρωπος στον χρόνο του παίζει πολλούς ρόλους,\nΟι πράξεις του υπάρχουν σε επτά ηλικίες.\n\n— William Shakespeare",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Όλος ο κόσμος είναι σκηνή,\nΚαι όλοι οι άνδρες και οι γυναίκες απλώς ηθοποιοί·\nΈχουν τις εξόδους και τις εισόδους τους,\nΚαι ένας άνθρωπος στον χρόνο του παίζει πολλούς ρόλους,\nΟι πράξεις του υπάρχουν σε επτά ηλικίες.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Προσαρμοσμένο θέμα",
|
||||
"Theme Name": "Όνομα θέματος",
|
||||
"Text Color": "Χρώμα κειμένου",
|
||||
@@ -221,9 +214,9 @@
|
||||
"Auto Import on File Open": "Αυτόματη εισαγωγή κατά το άνοιγμα αρχείου",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Δεν εντοπίστηκαν κεφάλαια.",
|
||||
"Failed to parse the EPUB file.": "Αποτυχία ανάλυσης του αρχείου EPUB.",
|
||||
"This book format is not supported.": "Αυτή η μορφή βιβλίου δεν υποστηρίζεται.",
|
||||
"No chapters detected": "Δεν εντοπίστηκαν κεφάλαια",
|
||||
"Failed to parse the EPUB file": "Αποτυχία ανάλυσης του αρχείου EPUB",
|
||||
"This book format is not supported": "Αυτή η μορφή βιβλίου δεν υποστηρίζεται",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Αδυναμία λήψης της μετάφρασης. Παρακαλώ συνδεθείτε πρώτα και δοκιμάστε ξανά.",
|
||||
"Group": "Ομάδα",
|
||||
"Always on Top": "Πάντα στην κορυφή",
|
||||
@@ -260,8 +253,6 @@
|
||||
"(from 'As You Like It', Act II)": "('Όπως σας αρέσει', Πράξη II)",
|
||||
"Link Color": "Χρώμα συνδέσμου",
|
||||
"Volume Keys for Page Flip": "Ανατροπή με πλήκτρα έντασης",
|
||||
"Clicks for Page Flip": "Κλικ για ανατροπή σελίδας",
|
||||
"Swap Clicks Area": "Ανταλλαγή περιοχής κλικ",
|
||||
"Screen": "Οθόνη",
|
||||
"Orientation": "Προσανατολισμός",
|
||||
"Portrait": "Πορτραίτο",
|
||||
@@ -297,7 +288,6 @@
|
||||
"Disable Translation": "Απενεργοποίηση μετάφρασης",
|
||||
"Scroll": "Κύλιση",
|
||||
"Overlap Pixels": "Επικάλυψη εικονοστοιχείων",
|
||||
"Click": "Κλικ",
|
||||
"System Language": "Γλώσσα συστήματος",
|
||||
"Security": "Ασφάλεια",
|
||||
"Allow JavaScript": "Επιτρέψτε JavaScript",
|
||||
@@ -308,7 +298,6 @@
|
||||
"No notes match your search": "Καμία σημείωση δεν βρέθηκε",
|
||||
"Search notes and excerpts...": "Αναζήτηση σημειώσεων...",
|
||||
"Sign in to Sync": "Σύνδεση για συγχρονισμό",
|
||||
"Synced at {{time}}": "Συγχρονίστηκε στις {{time}}",
|
||||
"Never synced": "Ποτέ δεν συγχρονίστηκε",
|
||||
"Show Remaining Time": "Εμφάνιση υπολειπόμενου χρόνου",
|
||||
"{{time}} min left in chapter": "{{time}} λεπτά απομένουν στο κεφάλαιο",
|
||||
@@ -342,11 +331,11 @@
|
||||
"Fit": "Προσαρμογή",
|
||||
"Reset {{settings}}": "Επαναφορά {{settings}}",
|
||||
"Reset Settings": "Επαναφορά ρυθμίσεων",
|
||||
"{{count}} pages left in chapter_one": "Μένει {{count}} σελίδα",
|
||||
"{{count}} pages left in chapter_other": "Μένουν {{count}} σελίδες",
|
||||
"{{count}} pages left in chapter_one": "<1>Μένει </1><0>{{count}}</0><1> σελίδα</1>",
|
||||
"{{count}} pages left in chapter_other": "<1>Μένουν </1><0>{{count}}</0><1> σελίδες</1>",
|
||||
"Show Remaining Pages": "Εμφάνιση υπολοίπων",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Διαχείριση συνδρομής",
|
||||
"Coming Soon": "Έρχεται σύντομα",
|
||||
@@ -366,7 +355,6 @@
|
||||
"Email:": "Email:",
|
||||
"Plan:": "Πρόγραμμα:",
|
||||
"Amount:": "Ποσό:",
|
||||
"Subscription ID:": "ID συνδρομής:",
|
||||
"Go to Library": "Μετάβαση στη βιβλιοθήκη",
|
||||
"Need help? Contact our support team at support@readest.com": "Χρειάζεστε βοήθεια; Επικοινωνήστε με την υποστήριξη στο support@readest.com",
|
||||
"Free Plan": "Δωρεάν πρόγραμμα",
|
||||
@@ -471,14 +459,12 @@
|
||||
"Remove from Cloud Only": "Αφαίρεση μόνο από το Cloud",
|
||||
"Remove from Device Only": "Αφαίρεση μόνο από το Device",
|
||||
"Disconnected": "Αποσυνδεδεμένο",
|
||||
"KOReader Sync Settings": "Ρυθμίσεις συγχρονισμού KOReader",
|
||||
"Sync Strategy": "Στρατηγική συγχρονισμού",
|
||||
"Ask on conflict": "Ρώτησε σε περίπτωση σύγκρουσης",
|
||||
"Always use latest": "Χρησιμοποίησε πάντα την τελευταία έκδοση",
|
||||
"Send changes only": "Αποστολή μόνο αλλαγών",
|
||||
"Receive changes only": "Λήψη μόνο αλλαγών",
|
||||
"Checksum Method": "Μέθοδος ελέγχου",
|
||||
"File Content (recommended)": "Περιεχόμενο αρχείου (συνιστάται)",
|
||||
"File Name": "Όνομα αρχείου",
|
||||
"Device Name": "Όνομα συσκευής",
|
||||
"Connect to your KOReader Sync server.": "Συνδεθείτε στον διακομιστή συγχρονισμού KOReader.",
|
||||
@@ -489,10 +475,7 @@
|
||||
"Connect": "Σύνδεση",
|
||||
"KOReader Sync": "Συγχρονισμός KOReader",
|
||||
"Sync Conflict": "Σύγκρουση συγχρονισμού",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "Συγχρονίστε την πρόοδο ανάγνωσης από το \"{{deviceName}}\";",
|
||||
"another device": "άλλη συσκευή",
|
||||
"Local Progress": "Τοπική πρόοδος",
|
||||
"Remote Progress": "Απομακρυσμένη πρόοδος",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "Σελίδα {{page}} από {{total}} ({{percentage}}%)",
|
||||
"Current position": "Τρέχουσα θέση",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "Περίπου σελίδα {{page}} από {{total}} ({{percentage}}%)",
|
||||
@@ -606,5 +589,828 @@
|
||||
"Opacity": "Αδιαφάνεια",
|
||||
"Size": "Μέγεθος",
|
||||
"Cover": "Εξώφυλλο",
|
||||
"Contain": "Περιέχει"
|
||||
"Contain": "Περιέχει",
|
||||
"{{number}} pages left in chapter": "<1>Μένουν </1><0>{{number}}</0><1> σελίδες στο κεφάλαιο</1>",
|
||||
"Device": "Συσκευή",
|
||||
"E-Ink Mode": "Λειτουργία E-Ink",
|
||||
"Highlight Colors": "Χρώματα επισήμανσης",
|
||||
"Pagination": "Σελιδοποίηση",
|
||||
"Disable Double Tap": "Απενεργοποίηση διπλού ταπ",
|
||||
"Tap to Paginate": "Ταπ για σελιδοποίηση",
|
||||
"Click to Paginate": "Κλικ για σελιδοποίηση",
|
||||
"Tap Both Sides": "Ταπ και στις δύο πλευρές",
|
||||
"Click Both Sides": "Κλικ και στις δύο πλευρές",
|
||||
"Swap Tap Sides": "Ανταλλαγή πλευρών ταπ",
|
||||
"Swap Click Sides": "Ανταλλαγή πλευρών κλικ",
|
||||
"Source and Translated": "Πηγή και Μεταφρασμένο",
|
||||
"Translated Only": "Μόνο Μεταφρασμένο",
|
||||
"Source Only": "Μόνο πηγή",
|
||||
"TTS Text": "Κείμενο TTS",
|
||||
"The book file is corrupted": "Το αρχείο του βιβλίου είναι κατεστραμμένο",
|
||||
"The book file is empty": "Το αρχείο του βιβλίου είναι κενό",
|
||||
"Failed to open the book file": "Αποτυχία ανοίγματος του αρχείου βιβλίου",
|
||||
"On-Demand Purchase": "Αγορά κατόπιν αιτήματος",
|
||||
"Full Customization": "Πλήρης προσαρμογή",
|
||||
"Lifetime Plan": "Σχέδιο ζωής",
|
||||
"One-Time Payment": "Εφάπαξ πληρωμή",
|
||||
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Κάντε μια εφάπαξ πληρωμή για να απολαύσετε διαρκή πρόσβαση σε συγκεκριμένες δυνατότητες σε όλες τις συσκευές. Αγοράστε συγκεκριμένες δυνατότητες ή υπηρεσίες μόνο όταν τις χρειάζεστε.",
|
||||
"Expand Cloud Sync Storage": "Επέκταση αποθήκευσης συγχρονισμού Cloud",
|
||||
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Επεκτείνετε την αποθήκευση cloud σας για πάντα με μια εφάπαξ αγορά. Κάθε επιπλέον αγορά προσθέτει περισσότερο χώρο.",
|
||||
"Unlock All Customization Options": "Ξεκλειδώστε όλες τις επιλογές προσαρμογής",
|
||||
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Ξεκλειδώστε επιπλέον θέματα, γραμματοσειρές, επιλογές διάταξης και δυνατότητες ανάγνωσης, μεταφραστές, υπηρεσίες αποθήκευσης cloud.",
|
||||
"Purchase Successful!": "Η αγορά ήταν επιτυχής!",
|
||||
"lifetime": "διαρκής",
|
||||
"Thank you for your purchase! Your payment has been processed successfully.": "Σας ευχαριστούμε για την αγορά σας! Η πληρωμή σας έχει επεξεργαστεί με επιτυχία.",
|
||||
"Order ID:": "Αριθμός παραγγελίας:",
|
||||
"TTS Highlighting": "Επισήμανση TTS",
|
||||
"Style": "Στυλ",
|
||||
"Underline": "Υπογράμμιση",
|
||||
"Strikethrough": "Διαγραμμένο",
|
||||
"Squiggly": "Κυματιστό",
|
||||
"Outline": "Περίγραμμα",
|
||||
"Quick Colors": "Γρήγορα χρώματα",
|
||||
"Highlighter": "Υπογραμμιστής",
|
||||
"Save Book Cover": "Αποθήκευση εξωφύλλου βιβλίου",
|
||||
"Auto-save last book cover": "Αυτόματη αποθήκευση τελευταίου εξωφύλλου βιβλίου",
|
||||
"Back": "Πίσω",
|
||||
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "Το email επιβεβαίωσης στάλθηκε! Παρακαλώ έλεγξε την παλιά και τη νέα σου διεύθυνση email για να επιβεβαιώσεις την αλλαγή.",
|
||||
"Failed to update email": "Αποτυχία ενημέρωσης email",
|
||||
"New Email": "Νέο email",
|
||||
"Your new email": "Το νέο σου email",
|
||||
"Updating email ...": "Ενημέρωση email ...",
|
||||
"Update email": "Ενημέρωση email",
|
||||
"Current email": "Τρέχον email",
|
||||
"Update Email": "Ενημέρωση email",
|
||||
"All": "Όλα",
|
||||
"Unable to open book": "Αδύνατη η άνοιγμα του βιβλίου",
|
||||
"Punctuation": "Στίξη",
|
||||
"Replace Quotation Marks": "Αντικατάσταση εισαγωγικών",
|
||||
"Enabled only in vertical layout.": "Ενεργοποιημένο μόνο σε κάθετη διάταξη.",
|
||||
"No Conversion": "Χωρίς μετατροπή",
|
||||
"Simplified to Traditional": "Απλοπ. → Παραδ.",
|
||||
"Traditional to Simplified": "Παραδ. → Απλοπ.",
|
||||
"Simplified to Traditional (Taiwan)": "Απλοπ. → Παραδ. (Ταϊβάν)",
|
||||
"Simplified to Traditional (Hong Kong)": "Απλοπ. → Παραδ. (Χονγκ Κονγκ)",
|
||||
"Simplified to Traditional (Taiwan), with phrases": "Απλοπ. → Παραδ. (Ταϊβάν • φράσεις)",
|
||||
"Traditional (Taiwan) to Simplified": "Παραδ. (Ταϊβάν) → Απλοπ.",
|
||||
"Traditional (Hong Kong) to Simplified": "Παραδ. (Χονγκ Κονγκ) → Απλοπ.",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Παραδ. (Ταϊβάν • φράσεις) → Απλοπ.",
|
||||
"Convert Simplified and Traditional Chinese": "Μετατροπή Απλοπ./Παραδ. Κινέζικων",
|
||||
"Convert Mode": "Λειτουργία μετατροπής",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Αποτυχία αυτόματης αποθήκευσης εξωφύλλου βιβλίου για την οθόνη κλειδώματος: {{error}}",
|
||||
"Download from Cloud": "Λήψη από το Cloud",
|
||||
"Upload to Cloud": "Ανέβασμα στο Cloud",
|
||||
"Clear Custom Fonts": "Καθαρισμός προσαρμοσμένων γραμματοσειρών",
|
||||
"Columns": "Στήλες",
|
||||
"OPDS Catalogs": "Κατάλογοι OPDS",
|
||||
"Adding LAN addresses is not supported in the web app version.": "Η προσθήκη διευθύνσεων LAN δεν υποστηρίζεται στην έκδοση web της εφαρμογής.",
|
||||
"Invalid OPDS catalog. Please check the URL.": "Μη έγκυρος κατάλογος OPDS. Ελέγξτε το URL.",
|
||||
"Browse and download books from online catalogs": "Περιηγηθείτε και κατεβάστε βιβλία από online καταλόγους",
|
||||
"My Catalogs": "Οι Κατάλογοί μου",
|
||||
"Add Catalog": "Προσθήκη Καταλόγου",
|
||||
"No catalogs yet": "Δεν υπάρχουν ακόμα κατάλογοι",
|
||||
"Add your first OPDS catalog to start browsing books": "Προσθέστε τον πρώτο σας κατάλογο OPDS για να αρχίσετε την περιήγηση.",
|
||||
"Add Your First Catalog": "Προσθήκη Πρώτου Καταλόγου",
|
||||
"Browse": "Περιήγηση",
|
||||
"Popular Catalogs": "Δημοφιλείς Κατάλογοι",
|
||||
"Add": "Προσθήκη",
|
||||
"Add OPDS Catalog": "Προσθήκη Καταλόγου OPDS",
|
||||
"Catalog Name": "Όνομα Καταλόγου",
|
||||
"My Calibre Library": "Η Βιβλιοθήκη μου στο Calibre",
|
||||
"OPDS URL": "OPDS URL",
|
||||
"Username (optional)": "Όνομα χρήστη (προαιρετικό)",
|
||||
"Password (optional)": "Κωδικός πρόσβασης (προαιρετικό)",
|
||||
"Description (optional)": "Περιγραφή (προαιρετική)",
|
||||
"A brief description of this catalog": "Μια σύντομη περιγραφή του καταλόγου",
|
||||
"Validating...": "Γίνεται έλεγχος...",
|
||||
"View All": "Προβολή όλων",
|
||||
"Forward": "Μπροστά",
|
||||
"Home": "Αρχική",
|
||||
"{{count}} items_one": "{{count}} στοιχείο",
|
||||
"{{count}} items_other": "{{count}} στοιχεία",
|
||||
"Download completed": "Η λήψη ολοκληρώθηκε",
|
||||
"Download failed": "Η λήψη απέτυχε",
|
||||
"Open Access": "Ανοιχτή πρόσβαση",
|
||||
"Borrow": "Δανεισμός",
|
||||
"Buy": "Αγορά",
|
||||
"Subscribe": "Συνδρομή",
|
||||
"Sample": "Δείγμα",
|
||||
"Download": "Λήψη",
|
||||
"Open & Read": "Άνοιγμα & Ανάγνωση",
|
||||
"Tags": "Ετικέτες",
|
||||
"Tag": "Ετικέτα",
|
||||
"First": "Πρώτο",
|
||||
"Previous": "Προηγούμενο",
|
||||
"Next": "Επόμενο",
|
||||
"Last": "Τελευταίο",
|
||||
"Cannot Load Page": "Δεν είναι δυνατή η φόρτωση της σελίδας",
|
||||
"An error occurred": "Προέκυψε σφάλμα",
|
||||
"Online Library": "Online Βιβλιοθήκη",
|
||||
"URL must start with http:// or https://": "Το URL πρέπει να ξεκινά με http:// ή https://",
|
||||
"Title, Author, Tag, etc...": "Τίτλος, Συγγραφέας, Ετικέτα, κ.λπ...",
|
||||
"Query": "Ερώτημα",
|
||||
"Subject": "Θέμα",
|
||||
"Enter {{terms}}": "Εισαγάγετε {{terms}}",
|
||||
"No search results found": "Δεν βρέθηκαν αποτελέσματα αναζήτησης",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Αποτυχία φόρτωσης ροής OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Αναζήτηση στο {{title}}",
|
||||
"Manage Storage": "Διαχείριση αποθήκευσης",
|
||||
"Failed to load files": "Αποτυχία φόρτωσης αρχείων",
|
||||
"Deleted {{count}} file(s)_one": "Διαγράφηκε {{count}} αρχείο",
|
||||
"Deleted {{count}} file(s)_other": "Διαγράφηκαν {{count}} αρχεία",
|
||||
"Failed to delete {{count}} file(s)_one": "Αποτυχία διαγραφής {{count}} αρχείου",
|
||||
"Failed to delete {{count}} file(s)_other": "Αποτυχία διαγραφής {{count}} αρχείων",
|
||||
"Failed to delete files": "Αποτυχία διαγραφής αρχείων",
|
||||
"Total Files": "Σύνολο αρχείων",
|
||||
"Total Size": "Συνολικό μέγεθος",
|
||||
"Quota": "Όριο",
|
||||
"Used": "Χρησιμοποιήθηκε",
|
||||
"Files": "Αρχεία",
|
||||
"Search files...": "Αναζήτηση αρχείων...",
|
||||
"Newest First": "Νεότερα πρώτα",
|
||||
"Oldest First": "Παλαιότερα πρώτα",
|
||||
"Largest First": "Μεγαλύτερα πρώτα",
|
||||
"Smallest First": "Μικρότερα πρώτα",
|
||||
"Name A-Z": "Όνομα A–Z",
|
||||
"Name Z-A": "Όνομα Z–A",
|
||||
"{{count}} selected_one": "{{count}} επιλεγμένο",
|
||||
"{{count}} selected_other": "{{count}} επιλεγμένα",
|
||||
"Delete Selected": "Διαγραφή επιλεγμένων",
|
||||
"Created": "Δημιουργήθηκε",
|
||||
"No files found": "Δεν βρέθηκαν αρχεία",
|
||||
"No files uploaded yet": "Δεν έχουν ανέβει αρχεία ακόμη",
|
||||
"files": "αρχεία",
|
||||
"Page {{current}} of {{total}}": "Σελίδα {{current}} από {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Σίγουρα θέλετε να διαγράψετε {{count}} επιλεγμένο αρχείο;",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Σίγουρα θέλετε να διαγράψετε {{count}} επιλεγμένα αρχεία;",
|
||||
"Cloud Storage Usage": "Χρήση αποθήκευσης cloud",
|
||||
"Rename Group": "Μετονομασία ομάδας",
|
||||
"From Directory": "Από κατάλογο",
|
||||
"Successfully imported {{count}} book(s)_one": "Επιτυχής εισαγωγή 1 βιβλίου",
|
||||
"Successfully imported {{count}} book(s)_other": "Επιτυχής εισαγωγή {{count}} βιβλίων",
|
||||
"Count": "Πλήθος",
|
||||
"Start Page": "Αρχική Σελίδα",
|
||||
"Search in OPDS Catalog...": "Αναζήτηση στον κατάλογο OPDS...",
|
||||
"Please log in to use advanced TTS features": "Παρακαλώ συνδεθείτε για να χρησιμοποιήσετε προηγμένες λειτουργίες TTS",
|
||||
"Word limit of 30 words exceeded.": "Υπέρβαση ορίου 30 λέξεων.",
|
||||
"Proofread": "Διόρθωση",
|
||||
"Current selection": "Τρέχουσα επιλογή",
|
||||
"All occurrences in this book": "Όλες οι εμφανίσεις σε αυτό το βιβλίο",
|
||||
"All occurrences in your library": "Όλες οι εμφανίσεις στη βιβλιοθήκη σας",
|
||||
"Selected text:": "Επιλεγμένο κείμενο:",
|
||||
"Replace with:": "Αντικατάσταση με:",
|
||||
"Enter text...": "Εισαγωγή κειμένου…",
|
||||
"Case sensitive:": "Διάκριση πεζών-κεφαλαίων:",
|
||||
"Scope:": "Εύρος εφαρμογής:",
|
||||
"Selection": "Επιλογή",
|
||||
"Library": "Βιβλιοθήκη",
|
||||
"Yes": "Ναι",
|
||||
"No": "Όχι",
|
||||
"Proofread Replacement Rules": "Κανόνες αντικατάστασης διόρθωσης",
|
||||
"Selected Text Rules": "Κανόνες επιλεγμένου κειμένου",
|
||||
"No selected text replacement rules": "Δεν υπάρχουν κανόνες αντικατάστασης για το επιλεγμένο κείμενο",
|
||||
"Book Specific Rules": "Κανόνες για συγκεκριμένο βιβλίο",
|
||||
"No book-level replacement rules": "Δεν υπάρχουν κανόνες αντικατάστασης σε επίπεδο βιβλίου",
|
||||
"Disable Quick Action": "Απενεργοποίηση γρήγορης ενέργειας",
|
||||
"Enable Quick Action on Selection": "Ενεργοποίηση γρήγορης ενέργειας κατά την επιλογή",
|
||||
"None": "Καμία",
|
||||
"Annotation Tools": "Εργαλεία σχολιασμού",
|
||||
"Enable Quick Actions": "Ενεργοποίηση γρήγορων ενεργειών",
|
||||
"Quick Action": "Γρήγορη ενέργεια",
|
||||
"Copy to Notebook": "Αντιγραφή στο σημειωματάριο",
|
||||
"Copy text after selection": "Αντιγραφή κειμένου μετά την επιλογή",
|
||||
"Highlight text after selection": "Επισήμανση κειμένου μετά την επιλογή",
|
||||
"Annotate text after selection": "Σχολιασμός κειμένου μετά την επιλογή",
|
||||
"Search text after selection": "Αναζήτηση κειμένου μετά την επιλογή",
|
||||
"Look up text in dictionary after selection": "Αναζήτηση κειμένου στο λεξικό μετά την επιλογή",
|
||||
"Translate text after selection": "Μετάφραση κειμένου μετά την επιλογή",
|
||||
"Read text aloud after selection": "Ανάγνωση κειμένου μετά την επιλογή",
|
||||
"Proofread text after selection": "Διόρθωση κειμένου μετά την επιλογή",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} ενεργές, {{pendingCount}} σε αναμονή",
|
||||
"{{failedCount}} failed": "{{failedCount}} απέτυχαν",
|
||||
"Waiting...": "Αναμονή...",
|
||||
"Failed": "Απέτυχε",
|
||||
"Completed": "Ολοκληρώθηκε",
|
||||
"Cancelled": "Ακυρώθηκε",
|
||||
"Retry": "Επανάληψη",
|
||||
"Active": "Ενεργές",
|
||||
"Transfer Queue": "Ουρά μεταφορών",
|
||||
"Upload All": "Ανέβασμα όλων",
|
||||
"Download All": "Λήψη όλων",
|
||||
"Resume Transfers": "Συνέχιση μεταφορών",
|
||||
"Pause Transfers": "Παύση μεταφορών",
|
||||
"Pending": "Σε αναμονή",
|
||||
"No transfers": "Χωρίς μεταφορές",
|
||||
"Retry All": "Επανάληψη όλων",
|
||||
"Clear Completed": "Εκκαθάριση ολοκληρωμένων",
|
||||
"Clear Failed": "Εκκαθάριση αποτυχημένων",
|
||||
"Upload queued: {{title}}": "Ανέβασμα στην ουρά: {{title}}",
|
||||
"Download queued: {{title}}": "Λήψη στην ουρά: {{title}}",
|
||||
"Book not found in library": "Το βιβλίο δεν βρέθηκε στη βιβλιοθήκη",
|
||||
"Unknown error": "Άγνωστο σφάλμα",
|
||||
"Please log in to continue": "Παρακαλώ συνδεθείτε για να συνεχίσετε",
|
||||
"Cloud File Transfers": "Μεταφορές αρχείων στο cloud",
|
||||
"Show Search Results": "Εμφάνιση αποτελεσμάτων",
|
||||
"Search results for '{{term}}'": "Αποτελέσματα για '{{term}}'",
|
||||
"Close Search": "Κλείσιμο αναζήτησης",
|
||||
"Previous Result": "Προηγούμενο αποτέλεσμα",
|
||||
"Next Result": "Επόμενο αποτέλεσμα",
|
||||
"Bookmarks": "Σελιδοδείκτες",
|
||||
"Annotations": "Σχολιασμοί",
|
||||
"Show Results": "Εμφάνιση αποτελεσμάτων",
|
||||
"Clear search": "Εκκαθάριση αναζήτησης",
|
||||
"Clear search history": "Εκκαθάριση ιστορικού αναζήτησης",
|
||||
"Tap to Toggle Footer": "Πατήστε για εναλλαγή υποσέλιδου",
|
||||
"Show Current Time": "Εμφάνιση τρέχουσας ώρας",
|
||||
"Use 24 Hour Clock": "Χρήση 24ωρης μορφής ώρας",
|
||||
"Show Current Battery Status": "Εμφάνιση τρέχουσας κατάστασης μπαταρίας",
|
||||
"Exported successfully": "Εξαγωγή επιτυχής",
|
||||
"Book exported successfully.": "Το βιβλίο εξήχθη επιτυχώς.",
|
||||
"Failed to export the book.": "Αποτυχία εξαγωγής του βιβλίου.",
|
||||
"Export Book": "Εξαγωγή βιβλίου",
|
||||
"Whole word:": "Ολόκληρη λέξη:",
|
||||
"Date Published": "Ημερομηνία δημοσίευσης",
|
||||
"Only for TTS:": "Μόνο για TTS:",
|
||||
"Uploaded": "Μεταφορτώθηκε",
|
||||
"Downloaded": "Λήφθηκε",
|
||||
"Deleted": "Διαγράφηκε",
|
||||
"Note:": "Σημείωση:",
|
||||
"Time:": "Ώρα:",
|
||||
"Format Options": "Επιλογές μορφοποίησης",
|
||||
"Export Date": "Ημερομηνία εξαγωγής",
|
||||
"Chapter Titles": "Τίτλοι κεφαλαίων",
|
||||
"Chapter Separator": "Διαχωριστικό κεφαλαίων",
|
||||
"Highlights": "Επισημάνσεις",
|
||||
"Note Date": "Ημερομηνία σημείωσης",
|
||||
"Advanced": "Για προχωρημένους",
|
||||
"Hide": "Απόκρυψη",
|
||||
"Show": "Εμφάνιση",
|
||||
"Use Custom Template": "Χρήση προσαρμοσμένου προτύπου",
|
||||
"Export Template": "Πρότυπο εξαγωγής",
|
||||
"Template Syntax:": "Σύνταξη προτύπου:",
|
||||
"Insert value": "Εισαγωγή τιμής",
|
||||
"Format date (locale)": "Μορφοποίηση ημερομηνίας (τοπική)",
|
||||
"Format date (custom)": "Μορφοποίηση ημερομηνίας (προσαρμοσμένη)",
|
||||
"Conditional": "Υπό όρους",
|
||||
"Loop": "Βρόχος",
|
||||
"Available Variables:": "Διαθέσιμες μεταβλητές:",
|
||||
"Book title": "Τίτλος βιβλίου",
|
||||
"Book author": "Συγγραφέας βιβλίου",
|
||||
"Export date": "Ημερομηνία εξαγωγής",
|
||||
"Array of chapters": "Πίνακας κεφαλαίων",
|
||||
"Chapter title": "Τίτλος κεφαλαίου",
|
||||
"Array of annotations": "Πίνακας σχολιασμών",
|
||||
"Highlighted text": "Επισημασμένο κείμενο",
|
||||
"Annotation note": "Σημείωση σχολιασμού",
|
||||
"Date Format Tokens:": "Σύμβολα μορφοποίησης ημερομηνίας:",
|
||||
"Year (4 digits)": "Έτος (4 ψηφία)",
|
||||
"Month (01-12)": "Μήνας (01-12)",
|
||||
"Day (01-31)": "Ημέρα (01-31)",
|
||||
"Hour (00-23)": "Ώρα (00-23)",
|
||||
"Minute (00-59)": "Λεπτό (00-59)",
|
||||
"Second (00-59)": "Δευτερόλεπτο (00-59)",
|
||||
"Show Source": "Εμφάνιση πηγής",
|
||||
"No content to preview": "Δεν υπάρχει περιεχόμενο για προεπισκόπηση",
|
||||
"Export": "Εξαγωγή",
|
||||
"Set Timeout": "Ορισμός χρονικού ορίου",
|
||||
"Select Voice": "Επιλογή φωνής",
|
||||
"Toggle Sticky Bottom TTS Bar": "Εναλλαγή καρφιτσωμένης μπάρας TTS",
|
||||
"Display what I'm reading on Discord": "Εμφάνιση του βιβλίου που διαβάζω στο Discord",
|
||||
"Show on Discord": "Εμφάνιση στο Discord",
|
||||
"Instant {{action}}": "Άμεση {{action}}",
|
||||
"Instant {{action}} Disabled": "Άμεση {{action}} απενεργοποιημένη",
|
||||
"Annotation": "Σημείωση",
|
||||
"Reset Template": "Επαναφορά προτύπου",
|
||||
"Annotation style": "Στυλ σχολίου",
|
||||
"Annotation color": "Χρώμα σχολίου",
|
||||
"Annotation time": "Ώρα σχολίου",
|
||||
"AI": "AI",
|
||||
"Are you sure you want to re-index this book?": "Είστε σίγουροι ότι θέλετε να επαναδημιουργήσετε το ευρετήριο αυτού του βιβλίου;",
|
||||
"Enable AI in Settings": "Ενεργοποίηση AI στις ρυθμίσεις",
|
||||
"Index This Book": "Δημιουργία ευρετηρίου για αυτό το βιβλίο",
|
||||
"Enable AI search and chat for this book": "Ενεργοποίηση αναζήτησης AI και συνομιλίας για αυτό το βιβλίο",
|
||||
"Start Indexing": "Έναρξη δημιουργίας ευρετηρίου",
|
||||
"Indexing book...": "Δημιουργία ευρετηρίου βιβλίου...",
|
||||
"Preparing...": "Προετοιμασία...",
|
||||
"Delete this conversation?": "Διαγραφή αυτής της συνομιλίας;",
|
||||
"No conversations yet": "Δεν υπάρχουν συνομιλίες ακόμα",
|
||||
"Start a new chat to ask questions about this book": "Ξεκινήστε μια νέα συνομιλία για να κάνετε ερωτήσεις σχετικά με αυτό το βιβλίο",
|
||||
"Rename": "Μετονομασία",
|
||||
"New Chat": "Νέα συνομιλία",
|
||||
"Chat": "Συνομιλία",
|
||||
"Please enter a model ID": "Εισάγετε ένα αναγνωριστικό μοντέλου",
|
||||
"Model not available or invalid": "Το μοντέλο δεν είναι διαθέσιμο ή δεν είναι έγκυρο",
|
||||
"Failed to validate model": "Αποτυχία επικύρωσης μοντέλου",
|
||||
"Couldn't connect to Ollama. Is it running?": "Δεν ήταν δυνατή η σύνδεση με το Ollama. Εκτελείται;",
|
||||
"Invalid API key or connection failed": "Μη έγκυρο κλειδί API ή αποτυχία σύνδεσης",
|
||||
"Connection failed": "Αποτυχία σύνδεσης",
|
||||
"AI Assistant": "Βοηθός AI",
|
||||
"Enable AI Assistant": "Ενεργοποίηση βοηθού AI",
|
||||
"Provider": "Πάροχος",
|
||||
"Ollama (Local)": "Ollama (Τοπικό)",
|
||||
"AI Gateway (Cloud)": "Πύλη AI (Cloud)",
|
||||
"Ollama Configuration": "Διαμόρφωση Ollama",
|
||||
"Refresh Models": "Ανανέωση μοντέλων",
|
||||
"AI Model": "Μοντέλο AI",
|
||||
"No models detected": "Δεν εντοπίστηκαν μοντέλα",
|
||||
"AI Gateway Configuration": "Διαμόρφωση πύλης AI",
|
||||
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Επιλέξτε από μια συλλογή υψηλής ποιότητας, οικονομικών μοντέλων AI. Μπορείτε επίσης να χρησιμοποιήσετε το δικό σας μοντέλο επιλέγοντας \"Προσαρμοσμένο μοντέλο\" παρακάτω.",
|
||||
"API Key": "Κλειδί API",
|
||||
"Get Key": "Λήψη κλειδιού",
|
||||
"Model": "Μοντέλο",
|
||||
"Custom Model...": "Προσαρμοσμένο μοντέλο...",
|
||||
"Custom Model ID": "Αναγνωριστικό προσαρμοσμένου μοντέλου",
|
||||
"Validate": "Επικύρωση",
|
||||
"Model available": "Το μοντέλο είναι διαθέσιμο",
|
||||
"Connection": "Σύνδεση",
|
||||
"Test Connection": "Δοκιμή σύνδεσης",
|
||||
"Connected": "Συνδεδεμένο",
|
||||
"Custom Colors": "Προσαρμοσμένα χρώματα",
|
||||
"Color E-Ink Mode": "Λειτουργία έγχρωμου E-Ink",
|
||||
"Reading Ruler": "Χάρακας ανάγνωσης",
|
||||
"Enable Reading Ruler": "Ενεργοποίηση χάρακα ανάγνωσης",
|
||||
"Lines to Highlight": "Γραμμές για επισήμανση",
|
||||
"Ruler Color": "Χρώμα χάρακα",
|
||||
"Command Palette": "Παλέτα εντολών",
|
||||
"Search settings and actions...": "Αναζήτηση ρυθμίσεων και ενεργειών...",
|
||||
"No results found for": "Δεν βρέθηκαν αποτελέσματα για",
|
||||
"Type to search settings and actions": "Πληκτρολογήστε για αναζήτηση ρυθμίσεων και ενεργειών",
|
||||
"Recent": "Πρόσφατα",
|
||||
"navigate": "πλοήγηση",
|
||||
"select": "επιλογή",
|
||||
"close": "κλείσιμο",
|
||||
"Search Settings": "Αναζήτηση ρυθμίσεων",
|
||||
"Page Margins": "Περιθώρια σελίδας",
|
||||
"AI Provider": "Πάροχος AI",
|
||||
"Ollama URL": "URL Ollama",
|
||||
"Ollama Model": "Μοντέλο Ollama",
|
||||
"AI Gateway Model": "Μοντέλο AI Gateway",
|
||||
"Actions": "Ενέργειες",
|
||||
"Navigation": "Πλοήγηση",
|
||||
"Set status for {{count}} book(s)_one": "Ορισμός κατάστασης για {{count}} βιβλίο",
|
||||
"Set status for {{count}} book(s)_other": "Ορισμός κατάστασης για {{count}} βιβλία",
|
||||
"Mark as Unread": "Σήμανση ως μη αναγνωσμένο",
|
||||
"Mark as Finished": "Σήμανση ως ολοκληρωμένο",
|
||||
"Finished": "Ολοκληρώθηκε",
|
||||
"Unread": "Μη αναγνωσμένο",
|
||||
"Clear Status": "Εκκαθάριση κατάστασης",
|
||||
"Status": "Κατάσταση",
|
||||
"Loading": "Φόρτωση...",
|
||||
"Exit Paragraph Mode": "Έξοδος από τη λειτουργία παραγράφου",
|
||||
"Paragraph Mode": "Λειτουργία παραγράφου",
|
||||
"Embedding Model": "Μοντέλο ενσωμάτωσης",
|
||||
"{{count}} book(s) synced_one": "{{count}} βιβλίο συγχρονίστηκε",
|
||||
"{{count}} book(s) synced_other": "{{count}} βιβλία συγχρονίστηκαν",
|
||||
"Unable to start RSVP": "Αδυναμία έναρξης RSVP",
|
||||
"RSVP not supported for PDF": "Το RSVP δεν υποστηρίζεται για PDF",
|
||||
"Select Chapter": "Επιλογή Κεφαλαίου",
|
||||
"Context": "Πλαίσιο",
|
||||
"Ready": "Έτοιμο",
|
||||
"Chapter Progress": "Πρόοδος Κεφαλαίου",
|
||||
"words": "λέξεις",
|
||||
"{{time}} left": "{{time}} απομένουν",
|
||||
"Reading progress": "Πρόοδος ανάγνωσης",
|
||||
"Skip back 15 words": "Επιστροφή 15 λέξεων",
|
||||
"Back 15 words (Shift+Left)": "Πίσω 15 λέξεις (Shift+Αριστερά)",
|
||||
"Pause (Space)": "Παύση (Διάστημα)",
|
||||
"Play (Space)": "Αναπαραγωγή (Διάστημα)",
|
||||
"Skip forward 15 words": "Προώθηση 15 λέξεων",
|
||||
"Forward 15 words (Shift+Right)": "Εμπρός 15 λέξεις (Shift+Δεξιά)",
|
||||
"Decrease speed": "Μείωση ταχύτητας",
|
||||
"Slower (Left/Down)": "Πιο αργά (Αριστερά/Κάτω)",
|
||||
"Increase speed": "Αύξηση ταχύτητας",
|
||||
"Faster (Right/Up)": "Πιο γρήγορα (Δεξιά/Πάνω)",
|
||||
"Start RSVP Reading": "Έναρξη ανάγνωσης RSVP",
|
||||
"Choose where to start reading": "Επιλέξτε από πού θα ξεκινήσετε την ανάγνωση",
|
||||
"From Chapter Start": "Από την αρχή του κεφαλαίου",
|
||||
"Start reading from the beginning of the chapter": "Ξεκινήστε την ανάγνωση από την αρχή του κεφαλαίου",
|
||||
"Resume": "Συνέχεια",
|
||||
"Continue from where you left off": "Συνεχίστε από εκεί που σταματήσατε",
|
||||
"From Current Page": "Από την τρέχουσα σελίδα",
|
||||
"Start from where you are currently reading": "Ξεκινήστε από εκεί που διαβάζετε αυτή τη στιγμή",
|
||||
"From Selection": "Από την επιλογή",
|
||||
"Speed Reading Mode": "Λειτουργία ταχείας ανάγνωσης",
|
||||
"Scroll left": "Κύλιση αριστερά",
|
||||
"Scroll right": "Κύλιση δεξιά",
|
||||
"Library Sync Progress": "Πρόοδος συγχρονισμού βιβλιοθήκης",
|
||||
"Back to library": "Επιστροφή στη βιβλιοθήκη",
|
||||
"Group by...": "Ομαδοποίηση κατά...",
|
||||
"Export as Plain Text": "Αποστολή ως απλό κείμενο",
|
||||
"Export as Markdown": "Αποστολή ως Markdown",
|
||||
"Show Page Navigation Buttons": "Κουμπιά πλοήγησης",
|
||||
"Page {{number}}": "Σελίδα {{number}}",
|
||||
"highlight": "επισήμανση",
|
||||
"underline": "υπογράμμιση",
|
||||
"squiggly": "κυματιστή γραμμή",
|
||||
"red": "κόκκινο",
|
||||
"violet": "βιολετί",
|
||||
"blue": "μπλε",
|
||||
"green": "πράσινο",
|
||||
"yellow": "κίτρινο",
|
||||
"Select {{style}} style": "Επιλέξτε στυλ {{style}}",
|
||||
"Select {{color}} color": "Επιλέξτε χρώμα {{color}}",
|
||||
"Close Book": "Κλείσιμο βιβλίου",
|
||||
"Speed Reading": "Γρήγορη Ανάγνωση",
|
||||
"Close Speed Reading": "Κλείσιμο Γρήγορης Ανάγνωσης",
|
||||
"Authors": "Συγγραφείς",
|
||||
"Books": "Βιβλία",
|
||||
"Groups": "Ομάδες",
|
||||
"Back to TTS Location": "Επιστροφή στην τοποθεσία TTS",
|
||||
"Metadata": "Μεταδεδομένα",
|
||||
"Image viewer": "Πρόγραμμα προβολής εικόνων",
|
||||
"Previous Image": "Προηγούμενη εικόνα",
|
||||
"Next Image": "Επόμενη εικόνα",
|
||||
"Zoomed": "Ζουμαρισμένο",
|
||||
"Zoom level": "Επίπεδο ζουμ",
|
||||
"Table viewer": "Πρόγραμμα προβολής πινάκων",
|
||||
"Unable to connect to Readwise. Please check your network connection.": "Αδυναμία σύνδεσης στο Readwise. Ελέγξτε τη σύνδεση του δικτύου σας.",
|
||||
"Invalid Readwise access token": "Μη έγκυρο διακριτικό πρόσβασης Readwise",
|
||||
"Disconnected from Readwise": "Αποσυνδέθηκε από το Readwise",
|
||||
"Never": "Ποτέ",
|
||||
"Sync Enabled": "Ο συγχρονισμός ενεργοποιήθηκε",
|
||||
"Disconnect": "Αποσύνδεση",
|
||||
"Connect your Readwise account to sync highlights.": "Συνδέστε τον λογαριασμό σας Readwise για να συγχρονίσετε τις επισημάνσεις.",
|
||||
"Get your access token at": "Αποκτήστε το διακριτικό πρόσβασής σας στο",
|
||||
"Access Token": "Διακριτικό πρόσβασης",
|
||||
"Paste your Readwise access token": "Επικολλήστε το διακριτικό πρόσβασης Readwise",
|
||||
"Readwise Sync": "Συγχρονισμός Readwise",
|
||||
"Push Highlights": "Προώθηση επισημάνσεων",
|
||||
"Highlights synced to Readwise": "Οι επισημάνσεις συγχρονίστηκαν στο Readwise",
|
||||
"Readwise sync failed: no internet connection": "Ο συγχρονισμός Readwise απέτυχε: δεν υπάρχει σύνδεση στο διαδίκτυο",
|
||||
"Readwise sync failed: {{error}}": "Ο συγχρονισμός Readwise απέτυχε: {{error}}",
|
||||
"System Screen Brightness": "Φωτεινότητα οθόνης συστήματος",
|
||||
"Page:": "Σελίδα:",
|
||||
"Page: {{number}}": "Σελίδα: {{number}}",
|
||||
"Annotation page number": "Αριθμός σελίδας σχολίου",
|
||||
"Translating...": "Μετάφραση...",
|
||||
"Show Battery Percentage": "Εμφάνιση ποσοστού μπαταρίας",
|
||||
"Hide Scrollbar": "Απόκρυψη γραμμής κύλισης",
|
||||
"Skip to last reading position": "Μετάβαση στην τελευταία θέση ανάγνωσης",
|
||||
"Show context": "Εμφάνιση πλαισίου",
|
||||
"Hide context": "Απόκρυψη πλαισίου",
|
||||
"Decrease font size": "Μείωση μεγέθους γραμματοσειράς",
|
||||
"Increase font size": "Αύξηση μεγέθους γραμματοσειράς",
|
||||
"Focus": "Εστίαση",
|
||||
"Theme color": "Χρώμα θέματος",
|
||||
"Import failed": "Η εισαγωγή απέτυχε",
|
||||
"Available Formatters:": "Διαθέσιμοι μορφοποιητές:",
|
||||
"Format date": "Μορφοποίηση ημερομηνίας",
|
||||
"Markdown block quote (> per line)": "Παράθεση Markdown (> ανά γραμμή)",
|
||||
"Newlines to <br>": "Αλλαγές γραμμής σε <br>",
|
||||
"Change case": "Αλλαγή πεζών/κεφαλαίων",
|
||||
"Trim whitespace": "Αφαίρεση κενών",
|
||||
"Truncate to n characters": "Περικοπή σε n χαρακτήρες",
|
||||
"Replace text": "Αντικατάσταση κειμένου",
|
||||
"Fallback value": "Εναλλακτική τιμή",
|
||||
"Get length": "Λήψη μήκους",
|
||||
"First/last element": "Πρώτο/τελευταίο στοιχείο",
|
||||
"Join array": "Ένωση πίνακα",
|
||||
"Backup failed: {{error}}": "Η δημιουργία αντιγράφου ασφαλείας απέτυχε: {{error}}",
|
||||
"Select Backup": "Επιλογή αντιγράφου ασφαλείας",
|
||||
"Restore failed: {{error}}": "Η επαναφορά απέτυχε: {{error}}",
|
||||
"Backup & Restore": "Αντίγραφα ασφαλείας & Επαναφορά",
|
||||
"Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Δημιουργήστε αντίγραφο ασφαλείας της βιβλιοθήκης σας ή επαναφέρετε από προηγούμενο αντίγραφο. Η επαναφορά θα συγχωνευθεί με την τρέχουσα βιβλιοθήκη σας.",
|
||||
"Backup Library": "Αντίγραφο βιβλιοθήκης",
|
||||
"Restore Library": "Επαναφορά βιβλιοθήκης",
|
||||
"Creating backup...": "Δημιουργία αντιγράφου ασφαλείας...",
|
||||
"Restoring library...": "Επαναφορά βιβλιοθήκης...",
|
||||
"{{current}} of {{total}} items": "{{current}} από {{total}} στοιχεία",
|
||||
"Backup completed successfully!": "Το αντίγραφο ασφαλείας ολοκληρώθηκε!",
|
||||
"Restore completed successfully!": "Η επαναφορά ολοκληρώθηκε!",
|
||||
"Your library has been saved to the selected location.": "Η βιβλιοθήκη σας αποθηκεύτηκε στην επιλεγμένη τοποθεσία.",
|
||||
"{{added}} books added, {{updated}} books updated.": "{{added}} βιβλία προστέθηκαν, {{updated}} βιβλία ενημερώθηκαν.",
|
||||
"Operation failed": "Η λειτουργία απέτυχε",
|
||||
"Loading library...": "Φόρτωση βιβλιοθήκης...",
|
||||
"{{count}} books refreshed_one": "{{count}} βιβλίο ανανεώθηκε",
|
||||
"{{count}} books refreshed_other": "{{count}} βιβλία ανανεώθηκαν",
|
||||
"Failed to refresh metadata": "Αποτυχία ανανέωσης μεταδεδομένων",
|
||||
"Refresh Metadata": "Ανανέωση μεταδεδομένων",
|
||||
"Keyboard Shortcuts": "Συντομεύσεις πληκτρολογίου",
|
||||
"View all keyboard shortcuts": "Προβολή όλων των συντομεύσεων",
|
||||
"Switch Sidebar Tab": "Εναλλαγή καρτέλας πλαϊνής γραμμής",
|
||||
"Toggle Notebook": "Εμφάνιση/Απόκρυψη σημειωματάριου",
|
||||
"Search in Book": "Αναζήτηση στο βιβλίο",
|
||||
"Toggle Scroll Mode": "Εναλλαγή λειτουργίας κύλισης",
|
||||
"Toggle Select Mode": "Εναλλαγή λειτουργίας επιλογής",
|
||||
"Toggle Bookmark": "Εναλλαγή σελιδοδείκτη",
|
||||
"Toggle Text to Speech": "Εναλλαγή κειμένου σε ομιλία",
|
||||
"Play / Pause TTS": "Αναπαραγωγή / Παύση TTS",
|
||||
"Toggle Paragraph Mode": "Εναλλαγή λειτουργίας παραγράφου",
|
||||
"Highlight Selection": "Επισήμανση επιλογής",
|
||||
"Underline Selection": "Υπογράμμιση επιλογής",
|
||||
"Annotate Selection": "Σχολιασμός επιλογής",
|
||||
"Search Selection": "Αναζήτηση επιλογής",
|
||||
"Copy Selection": "Αντιγραφή επιλογής",
|
||||
"Translate Selection": "Μετάφραση επιλογής",
|
||||
"Dictionary Lookup": "Αναζήτηση στο λεξικό",
|
||||
"Read Aloud Selection": "Ανάγνωση επιλογής",
|
||||
"Proofread Selection": "Διόρθωση επιλογής",
|
||||
"Open Settings": "Άνοιγμα ρυθμίσεων",
|
||||
"Open Command Palette": "Άνοιγμα παλέτας εντολών",
|
||||
"Show Keyboard Shortcuts": "Εμφάνιση συντομεύσεων",
|
||||
"Open Books": "Άνοιγμα βιβλίων",
|
||||
"Toggle Fullscreen": "Εναλλαγή πλήρους οθόνης",
|
||||
"Close Window": "Κλείσιμο παραθύρου",
|
||||
"Quit App": "Έξοδος εφαρμογής",
|
||||
"Go Left / Previous Page": "Αριστερά / Προηγούμενη σελίδα",
|
||||
"Go Right / Next Page": "Δεξιά / Επόμενη σελίδα",
|
||||
"Go Up": "Πάνω",
|
||||
"Go Down": "Κάτω",
|
||||
"Previous Chapter": "Προηγούμενο κεφάλαιο",
|
||||
"Next Chapter": "Επόμενο κεφάλαιο",
|
||||
"Scroll Half Page Down": "Κύλιση μισής σελίδας κάτω",
|
||||
"Scroll Half Page Up": "Κύλιση μισής σελίδας πάνω",
|
||||
"Save Note": "Αποθήκευση σημείωσης",
|
||||
"Single Section Scroll": "Κύλιση μεμονωμένης ενότητας",
|
||||
"General": "Γενικά",
|
||||
"Text to Speech": "Κείμενο σε ομιλία",
|
||||
"Zoom": "Ζουμ",
|
||||
"Window": "Παράθυρο",
|
||||
"Your Bookshelf": "Η βιβλιοθήκη σου",
|
||||
"TTS": "Εκφώνηση",
|
||||
"Media Info": "Πληροφορίες μέσων",
|
||||
"Update Frequency": "Συχνότητα ενημέρωσης",
|
||||
"Every Sentence": "Κάθε πρόταση",
|
||||
"Every Paragraph": "Κάθε παράγραφο",
|
||||
"Every Chapter": "Κάθε κεφάλαιο",
|
||||
"TTS Media Info Update Frequency": "Συχνότητα ενημέρωσης πληροφοριών μέσων TTS",
|
||||
"Select reading speed": "Επιλέξτε ταχύτητα ανάγνωσης",
|
||||
"Drag to seek": "Σύρετε για αναζήτηση",
|
||||
"Punctuation Delay": "Καθυστέρηση στίξης",
|
||||
"Please confirm that this OPDS connection will be proxied through Readest servers on the web app before continuing.": "Επιβεβαιώστε ότι αυτή η σύνδεση OPDS θα δρομολογηθεί μέσω των διακομιστών Readest στην εφαρμογή ιστού πριν συνεχίσετε.",
|
||||
"Custom Headers (optional)": "Προσαρμοσμένες κεφαλίδες (προαιρετικό)",
|
||||
"Add one header per line using \"Header-Name: value\".": "Προσθέστε μία κεφαλίδα ανά γραμμή χρησιμοποιώντας \"Header-Name: value\".",
|
||||
"I understand this OPDS connection will be proxied through Readest servers on the web app. If I do not trust Readest with these credentials or headers, I should use the native app instead.": "Κατανοώ ότι αυτή η σύνδεση OPDS θα δρομολογηθεί μέσω των διακομιστών Readest στην εφαρμογή ιστού. Αν δεν εμπιστεύομαι τo Readest με αυτά τα διαπιστευτήρια ή τις κεφαλίδες, θα πρέπει να χρησιμοποιήσω την εγγενή εφαρμογή.",
|
||||
"Unable to connect to Hardcover. Please check your network connection.": "Αδυναμία σύνδεσης στο Hardcover. Ελέγξτε τη σύνδεση δικτύου σας.",
|
||||
"Invalid Hardcover API token": "Μη έγκυρο Hardcover API token",
|
||||
"Disconnected from Hardcover": "Αποσυνδέθηκε από το Hardcover",
|
||||
"Connect your Hardcover account to sync reading progress and notes.": "Συνδέστε τον λογαριασμό σας Hardcover για συγχρονισμό προόδου ανάγνωσης και σημειώσεων.",
|
||||
"Get your API token from hardcover.app → Settings → API.": "Λάβετε το API token σας από hardcover.app → Ρυθμίσεις → API.",
|
||||
"API Token": "API Token",
|
||||
"Paste your Hardcover API token": "Επικολλήστε το Hardcover API token σας",
|
||||
"Hardcover Sync": "Συγχρονισμός Hardcover",
|
||||
"Push Notes": "Αποστολή σημειώσεων",
|
||||
"No annotations or excerpts to sync for this book.": "Δεν υπάρχουν σχολιασμοί ή αποσπάσματα για συγχρονισμό σε αυτό το βιβλίο.",
|
||||
"Configure Hardcover in Settings first.": "Ρυθμίστε πρώτα το Hardcover στις Ρυθμίσεις.",
|
||||
"No new Hardcover note changes to sync.": "Δεν υπάρχουν νέες αλλαγές σημειώσεων Hardcover για συγχρονισμό.",
|
||||
"Hardcover synced: {{inserted}} new, {{updated}} updated, {{skipped}} unchanged": "Hardcover συγχρονίστηκε: {{inserted}} νέα, {{updated}} ενημερωμένα, {{skipped}} αμετάβλητα",
|
||||
"Hardcover notes sync failed: {{error}}": "Αποτυχία συγχρονισμού σημειώσεων Hardcover: {{error}}",
|
||||
"Reading progress synced to Hardcover": "Η πρόοδος ανάγνωσης συγχρονίστηκε στο Hardcover",
|
||||
"Hardcover progress sync failed: {{error}}": "Αποτυχία συγχρονισμού προόδου Hardcover: {{error}}",
|
||||
"ISBN": "ISBN",
|
||||
"Keep existing source identifier": "Διατήρηση υπάρχοντος αναγνωριστικού πηγής",
|
||||
"Toggle Toolbar": "Εναλλαγή γραμμής εργαλείων",
|
||||
"Split Hyphens": "Διαχωρισμός παύλων",
|
||||
"Apply Theme Colors to PDF": "Εφαρμογή χρωμάτων θέματος σε PDF",
|
||||
"Name": "Όνομα",
|
||||
"Remove": "Αφαίρεση",
|
||||
"Edit OPDS Catalog": "Επεξεργασία καταλόγου OPDS",
|
||||
"Save Changes": "Αποθήκευση αλλαγών",
|
||||
"Use Book Layout": "Χρήση διάταξης βιβλίου",
|
||||
"End of this section. Continue to the next.": "Τέλος αυτής της ενότητας. Συνεχίστε στην επόμενη.",
|
||||
"Auto-download": "Αυτόματη λήψη",
|
||||
"Auto-download new items": "Αυτόματη λήψη νέων στοιχείων",
|
||||
"Automatically download new publications when the app syncs": "Αυτόματη λήψη νέων δημοσιεύσεων κατά τον συγχρονισμό της εφαρμογής",
|
||||
"{{count}} new item(s) downloaded from OPDS_one": "Λήφθηκε {{count}} νέο στοιχείο από OPDS",
|
||||
"{{count}} new item(s) downloaded from OPDS_other": "Λήφθηκαν {{count}} νέα στοιχεία από OPDS",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_one": "Αποτυχία συγχρονισμού {{count}} καταλόγου OPDS",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_other": "Αποτυχία συγχρονισμού {{count}} καταλόγων OPDS",
|
||||
"Last synced {{when}}": "Τελευταίος συγχρονισμός {{when}}",
|
||||
"Failed downloads": "Αποτυχημένες λήψεις",
|
||||
"No failed downloads": "Δεν υπάρχουν αποτυχημένες λήψεις",
|
||||
"Attempts: {{count}}_one": "Προσπάθειες: {{count}}",
|
||||
"Attempts: {{count}}_other": "Προσπάθειες: {{count}}",
|
||||
"Skip": "Παράλειψη",
|
||||
"Skip all": "Παράλειψη όλων",
|
||||
"Retry all": "Επανάληψη όλων",
|
||||
"{{count}} failed_one": "{{count}} απέτυχε",
|
||||
"{{count}} failed_other": "{{count}} απέτυχαν",
|
||||
"(none)": "(κανένα)",
|
||||
"Identifiers": "Αναγνωριστικά",
|
||||
"Read (Stream)": "Ανάγνωση (Ροή)",
|
||||
"Failed to start stream": "Αποτυχία έναρξης ροής",
|
||||
"No dictionaries enabled": "Δεν είναι ενεργοποιημένο κανένα λεξικό",
|
||||
"Enable a dictionary in Settings → Language → Dictionaries.": "Ενεργοποιήστε ένα λεξικό από Ρυθμίσεις → Γλώσσα → Λεξικά.",
|
||||
"Wiktionary": "Wiktionary",
|
||||
"Drag to reorder": "Σύρετε για επαναταξινόμηση",
|
||||
"Built-in": "Ενσωματωμένο",
|
||||
"Bundle is missing on this device. Re-import to use it.": "Το πακέτο λείπει από αυτή τη συσκευή. Επανεισαγάγετέ το για χρήση.",
|
||||
"This dictionary format is not supported.": "Αυτή η μορφή λεξικού δεν υποστηρίζεται.",
|
||||
"MDict": "MDict",
|
||||
"StarDict": "StarDict",
|
||||
"Imported {{count}} dictionary_one": "Εισήχθη {{count}} λεξικό",
|
||||
"Imported {{count}} dictionary_other": "Εισήχθησαν {{count}} λεξικά",
|
||||
"Skipped incomplete bundles: {{names}}": "Παραλείφθηκαν ημιτελή πακέτα: {{names}}",
|
||||
"Failed to import dictionary: {{message}}": "Αποτυχία εισαγωγής λεξικού: {{message}}",
|
||||
"Dictionaries": "Λεξικά",
|
||||
"Delete Dictionary": "Διαγραφή λεξικού",
|
||||
"Importing…": "Εισαγωγή…",
|
||||
"Import Dictionary": "Εισαγωγή λεξικού",
|
||||
"No dictionaries available.": "Δεν υπάρχουν διαθέσιμα λεξικά.",
|
||||
"StarDict bundles need .ifo, .idx, and .dict.dz files (.syn optional).": "Τα πακέτα StarDict χρειάζονται αρχεία .ifo, .idx και .dict.dz (το .syn είναι προαιρετικό).",
|
||||
"Select all the bundle files together when importing.": "Επιλέξτε όλα τα αρχεία του πακέτου μαζί κατά την εισαγωγή.",
|
||||
"Manage Dictionaries": "Διαχείριση λεξικών",
|
||||
"Select Dictionary Files": "Επιλογή αρχείων λεξικού",
|
||||
"Read on Wikipedia →": "Διάβασέ το στη Wikipedia →",
|
||||
"This link can't be opened": "Δεν είναι δυνατό το άνοιγμα αυτού του συνδέσμου",
|
||||
"The annotation link is missing required information. The original link may have been truncated.": "Από τον σύνδεσμο σχολιασμού λείπουν οι απαιτούμενες πληροφορίες. Ο αρχικός σύνδεσμος ενδέχεται να έχει αποκοπεί.",
|
||||
"Go to Readest": "Μετάβαση στο Readest",
|
||||
"Open-source ebook reader for everyone, on every device.": "Αναγνώστης ηλεκτρονικών βιβλίων ανοικτού κώδικα για όλους, σε κάθε συσκευή.",
|
||||
"Open in Readest": "Άνοιγμα στο Readest",
|
||||
"If Readest didn't open automatically, choose an option below:": "Αν το Readest δεν άνοιξε αυτόματα, επιλέξτε μία από τις παρακάτω επιλογές:",
|
||||
"Continue reading where you left off.": "Συνεχίστε να διαβάζετε από εκεί που σταματήσατε.",
|
||||
"Readest logo": "Λογότυπο Readest",
|
||||
"Opening Readest...": "Γίνεται άνοιγμα του Readest…",
|
||||
"Open in Readest app": "Άνοιγμα στην εφαρμογή Readest",
|
||||
"Continue in browser": "Συνέχεια στο πρόγραμμα περιήγησης",
|
||||
"Don't have Readest?": "Δεν έχετε το Readest;",
|
||||
"Book not in your library": "Το βιβλίο δεν βρίσκεται στη βιβλιοθήκη σας",
|
||||
"Share Book": "Κοινή χρήση βιβλίου",
|
||||
"Could not create share link": "Δεν ήταν δυνατή η δημιουργία συνδέσμου κοινής χρήσης",
|
||||
"Link copied": "Ο σύνδεσμος αντιγράφηκε",
|
||||
"Could not copy link": "Δεν ήταν δυνατή η αντιγραφή του συνδέσμου",
|
||||
"Share revoked": "Η κοινή χρήση ανακλήθηκε",
|
||||
"Could not revoke share": "Δεν ήταν δυνατή η ανάκληση της κοινής χρήσης",
|
||||
"Uploading book…": "Μεταφόρτωση βιβλίου…",
|
||||
"Generating…": "Δημιουργία…",
|
||||
"Generate share link": "Δημιουργία συνδέσμου κοινής χρήσης",
|
||||
"Share URL": "URL κοινής χρήσης",
|
||||
"Copy link": "Αντιγραφή συνδέσμου",
|
||||
"Copied": "Αντιγράφηκε",
|
||||
"Share via…": "Κοινή χρήση μέσω…",
|
||||
"Expires {{date}}": "Λήγει στις {{date}}",
|
||||
"Revoking…": "Ανάκληση…",
|
||||
"Revoke share": "Ανάκληση κοινής χρήσης",
|
||||
"Sign in to share books": "Συνδεθείτε για να μοιραστείτε βιβλία",
|
||||
"Expiring soon": "Λήγει σύντομα",
|
||||
"Missing share token": "Λείπει το διακριτικό κοινής χρήσης",
|
||||
"Could not add to your library": "Δεν ήταν δυνατή η προσθήκη στη βιβλιοθήκη σας",
|
||||
"This share link is no longer available": "Αυτός ο σύνδεσμος κοινής χρήσης δεν είναι πλέον διαθέσιμος",
|
||||
"The original link may have expired or been revoked.": "Ο αρχικός σύνδεσμος ενδέχεται να έχει λήξει ή να έχει ανακληθεί.",
|
||||
"Get Readest": "Αποκτήστε το Readest",
|
||||
"Loading shared book…": "Φόρτωση κοινόχρηστου βιβλίου…",
|
||||
"Adding…": "Προσθήκη…",
|
||||
"Add to my library": "Προσθήκη στη βιβλιοθήκη μου",
|
||||
"Could not load your shares": "Δεν ήταν δυνατή η φόρτωση των κοινοποιήσεών σας",
|
||||
"Revoked": "Ανακλήθηκε",
|
||||
"Expired": "Έληξε",
|
||||
"Shared books": "Κοινόχρηστα βιβλία",
|
||||
"You haven't shared any books yet": "Δεν έχετε μοιραστεί κανένα βιβλίο ακόμη",
|
||||
"Open a book and tap Share to send it to a friend.": "Ανοίξτε ένα βιβλίο και πατήστε «Κοινή χρήση» για να το στείλετε σε έναν φίλο.",
|
||||
"{{count}} active_one": "{{count}} ενεργό",
|
||||
"{{count}} active_other": "{{count}} ενεργά",
|
||||
"{{count}} downloads_one": "{{count}} λήψη",
|
||||
"{{count}} downloads_other": "{{count}} λήψεις",
|
||||
"starts at saved page": "ξεκινά από την αποθηκευμένη σελίδα",
|
||||
"More actions": "Περισσότερες ενέργειες",
|
||||
"Loading…": "Φόρτωση…",
|
||||
"Load more": "Φόρτωση περισσότερων",
|
||||
"Could not load shared book": "Δεν ήταν δυνατή η φόρτωση του κοινόχρηστου βιβλίου",
|
||||
"The share link is missing required information.": "Από τον σύνδεσμο κοινής χρήσης λείπουν απαιτούμενες πληροφορίες.",
|
||||
"Please check your connection and try again.": "Ελέγξτε τη σύνδεσή σας και δοκιμάστε ξανά.",
|
||||
"Sign in to import shared books": "Συνδεθείτε για να εισαγάγετε κοινόχρηστα βιβλία",
|
||||
"Already in your library": "Υπάρχει ήδη στη βιβλιοθήκη σας",
|
||||
"Added to your library": "Προστέθηκε στη βιβλιοθήκη σας",
|
||||
"Could not import shared book": "Δεν ήταν δυνατή η εισαγωγή του κοινόχρηστου βιβλίου",
|
||||
"Manage Shared Links": "Διαχείριση συνδέσμων κοινής χρήσης",
|
||||
"Expires in": "Λήγει σε",
|
||||
"{{count}} days_one": "1 ημέρα",
|
||||
"{{count}} days_other": "{{count}} ημέρες",
|
||||
"Expires in {{count}} days_one": "Λήγει σε 1 ημέρα",
|
||||
"Expires in {{count}} days_other": "Λήγει σε {{count}} ημέρες",
|
||||
"Expires in {{count}} hours_one": "Λήγει σε 1 ώρα",
|
||||
"Expires in {{count}} hours_other": "Λήγει σε {{count}} ώρες",
|
||||
"Open in app": "Άνοιγμα στην εφαρμογή",
|
||||
"Shared with you": "Κοινοποιήθηκε σε εσάς",
|
||||
"Downloading… {{percent}}%": "Λήψη… {{percent}}%",
|
||||
"Import progress": "Πρόοδος εισαγωγής",
|
||||
"Share reading progress": "Κοινή χρήση προόδου ανάγνωσης",
|
||||
"Includes your reading progress": "Περιλαμβάνει την πρόοδο ανάγνωσής σας",
|
||||
"Synced {{time}}": "Συγχρονίστηκε {{time}}",
|
||||
"Sync Info": "Πληροφορίες συγχρονισμού",
|
||||
"Last Synced": "Τελευταίος συγχρονισμός",
|
||||
"DICT": "DICT",
|
||||
"Slob": "Slob",
|
||||
"DICT bundles need a .index file and a .dict.dz file.": "Τα πακέτα DICT χρειάζονται ένα αρχείο .index και ένα αρχείο .dict.dz.",
|
||||
"Slob bundles need a .slob file.": "Τα πακέτα Slob χρειάζονται ένα αρχείο .slob.",
|
||||
"URL template must start with http(s):// and contain %WORD%.": "Το πρότυπο URL πρέπει να ξεκινά με http(s):// και να περιέχει %WORD%.",
|
||||
"Web": "Διαδίκτυο",
|
||||
"Add Web Search": "Προσθήκη αναζήτησης ιστού",
|
||||
"Edit Web Search": "Επεξεργασία αναζήτησης ιστού",
|
||||
"e.g. Google": "π.χ. Google",
|
||||
"URL Template": "Πρότυπο URL",
|
||||
"Use %WORD% where the looked-up word should appear.": "Χρησιμοποιήστε %WORD% όπου πρέπει να εμφανίζεται η λέξη αναζήτησης.",
|
||||
"Open the search result in your browser:": "Ανοίξτε το αποτέλεσμα αναζήτησης στο πρόγραμμα περιήγησής σας:",
|
||||
"Open in {{name}}": "Άνοιγμα σε {{name}}",
|
||||
"Book Fingerprint": "Δακτυλικό αποτύπωμα βιβλίου",
|
||||
"Search the web": "Αναζήτηση στον ιστό",
|
||||
"App deeplink (readest://)": "Σύνδεσμος εφαρμογής (readest://)",
|
||||
"Universal web link (https://)": "Γενικός σύνδεσμος ιστού (https://)",
|
||||
"Name cannot be empty.": "Το όνομα δεν μπορεί να είναι κενό.",
|
||||
"Replaced {{count}} existing dictionary_one": "Αντικαταστάθηκε {{count}} υπάρχον λεξικό",
|
||||
"Replaced {{count}} existing dictionary_other": "Αντικαταστάθηκαν {{count}} υπάρχοντα λεξικά",
|
||||
"Cancel Edit": "Ακύρωση επεξεργασίας",
|
||||
"Edit Dictionary": "Επεξεργασία λεξικού",
|
||||
"MDict bundles use .mdx files; companion .mdd and .css files are optional.": "Τα πακέτα MDict χρησιμοποιούν αρχεία .mdx· τα συνοδευτικά αρχεία .mdd και .css είναι προαιρετικά.",
|
||||
"Dictionary name": "Όνομα λεξικού",
|
||||
"This audio can't play here — the dictionary uses an outdated format. Try one with Opus, MP3, or WAV audio.": "Αυτός ο ήχος δεν μπορεί να αναπαραχθεί εδώ — το λεξικό χρησιμοποιεί ξεπερασμένη μορφή. Δοκιμάστε ένα λεξικό με ήχο Opus, MP3 ή WAV.",
|
||||
"File uploaded: {{title}}": "Το αρχείο με τίτλο {{title}} ανέβηκε",
|
||||
"File downloaded: {{title}}": "Το αρχείο με τίτλο {{title}} κατέβηκε",
|
||||
"Deleted cloud copy of the file: {{title}}": "Διαγράφηκε το αντίγραφο του αρχείου: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Αποτυχία ανέβασμα αρχείου: {{title}}",
|
||||
"Failed to download file: {{title}}": "Αποτυχία λήψης αρχείου: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Αποτυχία διαγραφής του αντιγράφου του αρχείου: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% σε χρήση",
|
||||
"Set PIN…": "Ορισμός PIN…",
|
||||
"Require a 4-digit PIN to open Readest": "Απαιτείται 4ψήφιο PIN για το άνοιγμα του Readest",
|
||||
"Change PIN…": "Αλλαγή PIN…",
|
||||
"Disable PIN…": "Απενεργοποίηση PIN…",
|
||||
"Sync passphrase ready": "Η φράση πρόσβασης συγχρονισμού είναι έτοιμη",
|
||||
"This permanently deletes the encrypted credentials we sync (e.g., OPDS catalog passwords) on every device. Local copies are preserved. You will need to re-enter the sync passphrase or set a new one. Continue?": "Αυτό διαγράφει οριστικά τα κρυπτογραφημένα διαπιστευτήρια που συγχρονίζουμε (π.χ. κωδικούς καταλόγου OPDS) σε κάθε συσκευή. Τα τοπικά αντίγραφα διατηρούνται. Θα χρειαστεί να εισαγάγετε ξανά τη φράση πρόσβασης συγχρονισμού ή να ορίσετε μια νέα. Συνέχεια;",
|
||||
"Sync passphrase forgotten — all encrypted fields cleared": "Η φράση πρόσβασης συγχρονισμού ξεχάστηκε — όλα τα κρυπτογραφημένα πεδία διαγράφηκαν",
|
||||
"Sync passphrase": "Φράση πρόσβασης συγχρονισμού",
|
||||
"Set passphrase": "Ορισμός φράσης πρόσβασης",
|
||||
"Forgot passphrase": "Ξεχάσατε τη φράση πρόσβασης",
|
||||
"Incorrect PIN": "Λανθασμένο PIN",
|
||||
"App locked": "Η εφαρμογή είναι κλειδωμένη",
|
||||
"Enter your PIN": "Εισαγάγετε το PIN σας",
|
||||
"Readest is locked. Enter your 4-digit PIN to continue.": "Το Readest είναι κλειδωμένο. Εισαγάγετε το 4ψήφιο PIN σας για να συνεχίσετε.",
|
||||
"PIN code": "Κωδικός PIN",
|
||||
"Forgetting your PIN locks you out of this device. You'll need to clear the app's data to reset it.": "Αν ξεχάσετε το PIN σας, θα κλειδωθείτε από αυτή τη συσκευή. Θα χρειαστεί να διαγράψετε τα δεδομένα της εφαρμογής για να το επαναφέρετε.",
|
||||
"Passphrase must be at least 8 characters": "Η φράση πρόσβασης πρέπει να έχει τουλάχιστον 8 χαρακτήρες",
|
||||
"Passphrases do not match": "Οι φράσεις πρόσβασης δεν ταιριάζουν",
|
||||
"Set sync passphrase": "Ορισμός φράσης πρόσβασης συγχρονισμού",
|
||||
"Enter sync passphrase": "Εισαγωγή φράσης πρόσβασης συγχρονισμού",
|
||||
"A sync passphrase encrypts your sensitive fields (like OPDS catalog credentials) before they sync. We never see this passphrase. Pick something memorable — there is no recovery without it.": "Μια φράση πρόσβασης συγχρονισμού κρυπτογραφεί τα ευαίσθητα πεδία σας (όπως διαπιστευτήρια καταλόγου OPDS) πριν συγχρονιστούν. Δεν βλέπουμε ποτέ αυτή τη φράση πρόσβασης. Επιλέξτε κάτι αξιομνημόνευτο — δεν υπάρχει ανάκτηση χωρίς αυτή.",
|
||||
"Enter the sync passphrase you set on another device to decrypt your synced credentials.": "Εισαγάγετε τη φράση πρόσβασης συγχρονισμού που ορίσατε σε άλλη συσκευή για να αποκρυπτογραφήσετε τα συγχρονισμένα διαπιστευτήριά σας.",
|
||||
"Confirm passphrase": "Επιβεβαίωση φράσης πρόσβασης",
|
||||
"Unlock": "Ξεκλείδωμα",
|
||||
"Set PIN": "Ορισμός PIN",
|
||||
"Change PIN": "Αλλαγή PIN",
|
||||
"Disable PIN": "Απενεργοποίηση PIN",
|
||||
"Enter your current PIN, then choose a new 4-digit PIN.": "Εισαγάγετε το τρέχον PIN σας και, στη συνέχεια, επιλέξτε ένα νέο 4ψήφιο PIN.",
|
||||
"Enter your current PIN to disable the app lock.": "Εισαγάγετε το τρέχον PIN σας για να απενεργοποιήσετε το κλείδωμα της εφαρμογής.",
|
||||
"PIN must be {{length}} digits": "Το PIN πρέπει να έχει {{length}} ψηφία",
|
||||
"PINs do not match": "Τα PIN δεν ταιριάζουν",
|
||||
"Current PIN": "Τρέχον PIN",
|
||||
"New PIN": "Νέο PIN",
|
||||
"Confirm new PIN": "Επιβεβαίωση νέου PIN",
|
||||
"Manage Sync": "Διαχείριση συγχρονισμού",
|
||||
"Imported book files and library metadata": "Εισηγμένα αρχεία βιβλίων και μεταδεδομένα βιβλιοθήκης",
|
||||
"Last-read position, bookmarks, and per-book preferences": "Τελευταία θέση ανάγνωσης, σελιδοδείκτες και προτιμήσεις ανά βιβλίο",
|
||||
"Highlights and notes": "Επισημάνσεις και σημειώσεις",
|
||||
"Fonts": "Γραμματοσειρές",
|
||||
"Custom font files": "Προσαρμοσμένα αρχεία γραμματοσειρών",
|
||||
"Backgrounds": "Φόντα",
|
||||
"Custom background textures": "Προσαρμοσμένες υφές φόντου",
|
||||
"OPDS catalogs": "Κατάλογοι OPDS",
|
||||
"Saved catalog URLs and (encrypted) credentials": "Αποθηκευμένες διευθύνσεις καταλόγων και (κρυπτογραφημένα) διαπιστευτήρια",
|
||||
"Wrong sync passphrase — synced credentials could not be decrypted": "Λανθασμένη φράση πρόσβασης συγχρονισμού — δεν ήταν δυνατή η αποκρυπτογράφηση των συγχρονισμένων διαπιστευτηρίων",
|
||||
"Sync passphrase data on the server was reset. Re-encrypting your credentials under the new passphrase…": "Τα δεδομένα της φράσης πρόσβασης συγχρονισμού στον διακομιστή επαναφέρθηκαν. Επανακρυπτογράφηση των διαπιστευτηρίων σας με τη νέα φράση πρόσβασης…",
|
||||
"Failed to decrypt synced credentials": "Αποτυχία αποκρυπτογράφησης των συγχρονισμένων διαπιστευτηρίων",
|
||||
"Imported dictionary bundles and settings": "Εισηγμένα πακέτα λεξικών και ρυθμίσεις",
|
||||
"Choose what syncs across your devices. Disabling a category stops this device from sending or receiving rows of that kind. Anything already on the server is left alone, re-enabling resumes from where you stopped.": "Επιλέξτε τι συγχρονίζεται μεταξύ των συσκευών σας. Η απενεργοποίηση μιας κατηγορίας σταματά αυτή τη συσκευή από το να στέλνει ή να λαμβάνει εγγραφές αυτού του τύπου. Ό,τι υπάρχει ήδη στον διακομιστή παραμένει ως έχει, η εκ νέου ενεργοποίηση συνεχίζει από εκεί που σταματήσατε.",
|
||||
"Data Sync": "Συγχρονισμός δεδομένων",
|
||||
"Manage Fonts": "Διαχείριση γραμματοσειρών",
|
||||
"App settings": "Ρυθμίσεις εφαρμογής",
|
||||
"Theme, highlight colours, integrations (KOSync, Readwise, Hardcover), and dictionary order": "Θέμα, χρώματα επισήμανσης, ενσωματώσεις (KOSync, Readwise, Hardcover) και σειρά λεξικών",
|
||||
"Required while Dictionaries sync is enabled": "Απαιτείται όσο είναι ενεργός ο συγχρονισμός Λεξικών",
|
||||
"Resets in {{hours}} hr {{minutes}} min": "Επαναφορά σε {{hours}} ώρ. {{minutes}} λεπ.",
|
||||
"Pick a 4-digit PIN. You will need to enter it every time you open Readest. There is no way to recover a forgotten PIN. Clearing the app data is the only way to reset it.": "Επιλέξτε ένα 4ψήφιο PIN. Θα πρέπει να το εισάγετε κάθε φορά που ανοίγετε το Readest. Δεν υπάρχει τρόπος ανάκτησης ενός ξεχασμένου PIN. Η εκκαθάριση των δεδομένων της εφαρμογής είναι ο μοναδικός τρόπος επαναφοράς του.",
|
||||
"Credentials": "Διαπιστευτήρια",
|
||||
"Tokens, usernames, and passwords for OPDS, KOReader, Hardcover, and Readwise. When disabled, credentials remain on this device only and are never uploaded.": "Διακριτικά, ονόματα χρήστη και κωδικοί πρόσβασης για OPDS, KOReader, Hardcover και Readwise. Όταν είναι απενεργοποιημένο, τα διαπιστευτήρια παραμένουν μόνο σε αυτή τη συσκευή και δεν αποστέλλονται ποτέ.",
|
||||
"Sensitive synced fields are encrypted before upload. Set a passphrase now or later when encryption is needed.": "Τα ευαίσθητα συγχρονισμένα πεδία κρυπτογραφούνται πριν τη μεταφόρτωση. Ορίστε μια φράση πρόσβασης τώρα ή αργότερα, όταν χρειαστεί η κρυπτογράφηση.",
|
||||
"Saved to this account. You will be prompted for it when decrypting credentials.": "Αποθηκεύτηκε σε αυτόν τον λογαριασμό. Θα σας ζητηθεί όταν χρειαστεί να αποκρυπτογραφηθούν διαπιστευτήρια.",
|
||||
"Reading progress on this device differs from \"{{deviceName}}\".": "Η πρόοδος ανάγνωσης σε αυτή τη συσκευή διαφέρει από το «{{deviceName}}».",
|
||||
"This device": "Αυτή η συσκευή",
|
||||
"Red": "Κόκκινο",
|
||||
"Yellow": "Κίτρινο",
|
||||
"Green": "Πράσινο",
|
||||
"Blue": "Μπλε",
|
||||
"Violet": "Βιολετί",
|
||||
"Edit color": "Επεξεργασία χρώματος",
|
||||
"Add custom color": "Προσθήκη προσαρμοσμένου χρώματος",
|
||||
"Add label": "Προσθήκη ετικέτας",
|
||||
"Choose color": "Επιλογή χρώματος",
|
||||
"Connected to Hardcover. Last synced {{time}}.": "Συνδέθηκε στο Hardcover. Τελευταίος συγχρονισμός {{time}}.",
|
||||
"Integrations": "Ενσωματώσεις",
|
||||
"Hardcover": "Hardcover",
|
||||
"Connected to Readwise. Last synced {{time}}.": "Συνδέθηκε στο Readwise. Τελευταίος συγχρονισμός {{time}}.",
|
||||
"Readwise": "Readwise",
|
||||
"Browse and download books from online catalogs.": "Περιηγηθείτε και κατεβάστε βιβλία από διαδικτυακούς καταλόγους.",
|
||||
"Connected as {{user}}": "Συνδέθηκε ως {{user}}",
|
||||
"Not connected": "Μη συνδεδεμένο",
|
||||
"{{count}} catalog_one": "{{count}} κατάλογος",
|
||||
"{{count}} catalog_other": "{{count}} κατάλογοι",
|
||||
"No catalogs": "Δεν υπάρχουν κατάλογοι",
|
||||
"Connect Readest to external services for sync, highlights, and catalogs.": "Συνδέστε το Readest με εξωτερικές υπηρεσίες για συγχρονισμό, επισημάνσεις και καταλόγους.",
|
||||
"Reading Sync": "Συγχρονισμός ανάγνωσης",
|
||||
"Content Sources": "Πηγές περιεχομένου",
|
||||
"Scroll tabs": "Κύλιση καρτελών",
|
||||
"Catalog actions": "Ενέργειες καταλόγου",
|
||||
"File Content": "Περιεχόμενο αρχείου",
|
||||
"Start your library": "Ξεκινήστε τη βιβλιοθήκη σας",
|
||||
"Pick a book from your device to add it to your library.": "Επιλέξτε ένα βιβλίο από τη συσκευή σας για να το προσθέσετε στη βιβλιοθήκη σας.",
|
||||
"Drop a book anywhere on this window, or pick one from your computer.": "Αφήστε ένα βιβλίο οπουδήποτε σε αυτό το παράθυρο ή επιλέξτε ένα από τον υπολογιστή σας.",
|
||||
"Sign in to sync your library": "Συνδεθείτε για να συγχρονίσετε τη βιβλιοθήκη σας"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,57 @@
|
||||
{
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Are you sure to delete {{count}} selected book(s)?_one": "Are you sure to delete {{count}} selected book?",
|
||||
"Are you sure to delete {{count}} selected book(s)?_other": "Are you sure to delete {{count}} selected books?",
|
||||
"Search in {{count}} Book(s)..._one": "Search in {{count}} book...",
|
||||
"Search in {{count}} Book(s)..._other": "Search in {{count}} books...",
|
||||
"{{count}} pages left in chapter_one": "{{count}} page left in chapter",
|
||||
"{{count}} pages left in chapter_other": "{{count}} pages left in chapter"
|
||||
"{{count}} pages left in chapter_one": "<0>{{count}}</0><1> page left in chapter</1>",
|
||||
"{{count}} pages left in chapter_other": "<0>{{count}}</0><1> pages left in chapter</1>",
|
||||
"Deleted {{count}} file(s)_one": "Deleted {{count}} file",
|
||||
"Deleted {{count}} file(s)_other": "Deleted {{count}} files",
|
||||
"Failed to delete {{count}} file(s)_one": "Failed to delete {{count}} file",
|
||||
"Failed to delete {{count}} file(s)_other": "Failed to delete {{count}} files",
|
||||
"{{count}} selected_one": "{{count}} selected",
|
||||
"{{count}} selected_other": "{{count}} selected",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Are you sure to delete {{count}} selected file?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Are you sure to delete {{count}} selected files?",
|
||||
"Successfully imported {{count}} book(s)_one": "Successfully imported {{count}} book",
|
||||
"Successfully imported {{count}} book(s)_other": "Successfully imported {{count}} books",
|
||||
"Set status for {{count}} book(s)_one": "Set status for {{count}} book",
|
||||
"Set status for {{count}} book(s)_other": "Set status for {{count}} books",
|
||||
"{{count}} book(s) synced_one": "{{count}} book synced",
|
||||
"{{count}} book(s) synced_other": "{{count}} books synced",
|
||||
"{{count}} active_one": "{{count}} active",
|
||||
"{{count}} active_other": "{{count}} active",
|
||||
"{{count}} downloads_one": "{{count}} download",
|
||||
"{{count}} downloads_other": "{{count}} downloads",
|
||||
"{{count}} days_one": "{{count}} day",
|
||||
"{{count}} days_other": "{{count}} days",
|
||||
"Expires in {{count}} days_one": "Expires in {{count}} day",
|
||||
"Expires in {{count}} days_other": "Expires in {{count}} days",
|
||||
"Expires in {{count}} hours_one": "Expires in {{count}} hour",
|
||||
"Expires in {{count}} hours_other": "Expires in {{count}} hours",
|
||||
"Attempts: {{count}}_one": "Attempts: {{count}}",
|
||||
"Attempts: {{count}}_other": "Attempts: {{count}}",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_one": "Failed to sync {{count}} OPDS catalog",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_other": "Failed to sync {{count}} OPDS catalogs",
|
||||
"Imported {{count}} dictionary_one": "Imported {{count}} dictionary",
|
||||
"Imported {{count}} dictionary_other": "Imported {{count}} dictionaries",
|
||||
"{{count}} catalog_one": "{{count}} catalog",
|
||||
"{{count}} catalog_other": "{{count}} catalogs",
|
||||
"Replaced {{count}} existing dictionary_one": "Replaced {{count}} existing dictionary",
|
||||
"Replaced {{count}} existing dictionary_other": "Replaced {{count}} existing dictionaries",
|
||||
"{{count}} books refreshed_one": "{{count}} book refreshed",
|
||||
"{{count}} books refreshed_other": "{{count}} books refreshed",
|
||||
"{{count}} failed_one": "{{count}} failed",
|
||||
"{{count}} failed_other": "{{count}} failed",
|
||||
"{{count}} items_one": "{{count}} item",
|
||||
"{{count}} items_other": "{{count}} items",
|
||||
"{{count}} new item(s) downloaded from OPDS_one": "{{count}} new item downloaded from OPDS",
|
||||
"{{count}} new item(s) downloaded from OPDS_other": "{{count}} new items downloaded from OPDS",
|
||||
"{{engine}}: {{count}} voices_one": "{{engine}}: {{count}} voice",
|
||||
"{{engine}}: {{count}} voices_other": "{{engine}}: {{count}} voices"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"Auto Mode": "Modo automático",
|
||||
"Behavior": "Comportamiento",
|
||||
"Book": "Libro",
|
||||
"Book Cover": "Portada del libro",
|
||||
"Bookmark": "Marcador",
|
||||
"Cancel": "Cancelar",
|
||||
"Chapter": "Capítulo",
|
||||
@@ -70,7 +69,6 @@
|
||||
"Published": "Publicado",
|
||||
"Publisher": "Editorial",
|
||||
"KOReader Sync": "Sincronización con KOReader",
|
||||
"KOReader Sync Settings": "Ajustes de Sincronización con KOReader",
|
||||
"Your Username": "Tu nombre de usuario",
|
||||
"Connect to your KOReader Sync server.": "Conéctate a tu servidor de KOReader Sync.",
|
||||
"Server URL": "URL del Servidor",
|
||||
@@ -84,14 +82,10 @@
|
||||
"Send changes only": "Solo enviar cambios",
|
||||
"Receive changes only": "Solo recibir cambios",
|
||||
"Checksum Method": "Método de identificación",
|
||||
"File Content (recommended)": "Contenido del archivo (recomendado)",
|
||||
"File Name": "Nombre del archivo",
|
||||
"Device Name": "Nombre del dispositivo",
|
||||
"Reading Progress Synced": "Progreso de lectura sincronizado",
|
||||
"Sync Conflict": "Conflicto de sincronización",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "¿Quieres sincronizar el progreso de lectura desde el dispositivo \"{{deviceName}}\"?",
|
||||
"Local Progress": "Progreso local",
|
||||
"Remote Progress": "Progreso remoto",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "Página {{page}} de {{total}} ({{percentage}}%)",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "Aproximadamente página {{page}} de {{total}} ({{percentage}}%)",
|
||||
"Current position": "Posición actual",
|
||||
@@ -130,11 +124,9 @@
|
||||
"Updated": "Actualizado",
|
||||
"Version {{version}}": "Versión {{version}}",
|
||||
"Vertical Direction": "Dirección vertical",
|
||||
"Welcome to your library. You can import your books here and read them anytime.": "Bienvenido a tu biblioteca. Puedes importar tus libros aquí y leerlos en cualquier momento.",
|
||||
"Wikipedia": "Wikipedia",
|
||||
"Writing Mode": "Modo de escritura",
|
||||
"Your Library": "Tu biblioteca",
|
||||
"TTS not supported for PDF": "TTS no es compatible con PDF",
|
||||
"Override Book Font": "Sobrescribir fuente del libro",
|
||||
"Apply to All Books": "Aplicar a todos los libros",
|
||||
"Apply to This Book": "Aplicar a este libro",
|
||||
@@ -201,9 +193,6 @@
|
||||
"Token": "Código de verificación",
|
||||
"Your OTP token": "Tu código OTP",
|
||||
"Verify token": "Verificar código",
|
||||
"Sign in with Google": "Iniciar sesión con Google",
|
||||
"Sign in with Apple": "Iniciar sesión con Apple",
|
||||
"Sign in with GitHub": "Iniciar sesión con GitHub",
|
||||
"Account": "Cuenta",
|
||||
"Failed to delete user. Please try again later.": "Error al eliminar usuario. Por favor, inténtelo de nuevo más tarde.",
|
||||
"Community Support": "Soporte comunitario",
|
||||
@@ -216,12 +205,11 @@
|
||||
"RTL Direction": "Dirección RTL",
|
||||
"Maximum Column Height": "Altura máxima de columna",
|
||||
"Maximum Column Width": "Ancho máximo de columna",
|
||||
"Continuous Scroll": "Desplazamiento continuo",
|
||||
"Fullscreen": "Pantalla completa",
|
||||
"No supported files found. Supported formats: {{formats}}": "No se encontraron archivos compatibles. Formatos compatibles: {{formats}}",
|
||||
"Drop to Import Books": "Soltar para importar libros",
|
||||
"Custom": "Personalizado",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Todo el mundo es un escenario,\nY todos los hombres y mujeres son meros actores;\nTienen sus salidas y sus entradas,\nY un hombre en su tiempo interpreta muchos papeles,\nSus actos son siete edades.\n\n— William Shakespeare",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Todo el mundo es un escenario,\nY todos los hombres y mujeres son meros actores;\nTienen sus salidas y sus entradas,\nY un hombre en su tiempo interpreta muchos papeles,\nSus actos son siete edades.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Tema personalizado",
|
||||
"Theme Name": "Nombre del tema",
|
||||
"Text Color": "Color del texto",
|
||||
@@ -248,9 +236,9 @@
|
||||
"Auto Import on File Open": "Importación automática al abrir archivo",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "No se detectaron capítulos.",
|
||||
"Failed to parse the EPUB file.": "Error al analizar el archivo EPUB.",
|
||||
"This book format is not supported.": "Este formato de libro no es compatible.",
|
||||
"No chapters detected": "No se detectaron capítulos",
|
||||
"Failed to parse the EPUB file": "Error al analizar el archivo EPUB",
|
||||
"This book format is not supported": "Este formato de libro no es compatible",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "No se puede obtener la traducción. Por favor, inicia sesión primero e inténtalo de nuevo.",
|
||||
"Group": "Grupo",
|
||||
"Always on Top": "Siempre en la parte superior",
|
||||
@@ -287,8 +275,6 @@
|
||||
"(from 'As You Like It', Act II)": "(de 'Como gustéis', Acto II)",
|
||||
"Link Color": "Color del enlace",
|
||||
"Volume Keys for Page Flip": "Botones de volumen para pasar página",
|
||||
"Clicks for Page Flip": "Toques para pasar página",
|
||||
"Swap Clicks Area": "Invertir zonas de toque",
|
||||
"Screen": "Pantalla",
|
||||
"Orientation": "Orientación",
|
||||
"Portrait": "Vertical",
|
||||
@@ -325,7 +311,6 @@
|
||||
"Disable Translation": "Deshabilitar traducción",
|
||||
"Scroll": "Desplazar",
|
||||
"Overlap Pixels": "Superponer píxeles",
|
||||
"Click": "Click",
|
||||
"System Language": "Idioma del sistema",
|
||||
"Security": "Seguridad",
|
||||
"Allow JavaScript": "Permitir JavaScript",
|
||||
@@ -337,7 +322,6 @@
|
||||
"No notes match your search": "No se encontraron notas",
|
||||
"Search notes and excerpts...": "Buscar notas...",
|
||||
"Sign in to Sync": "Inicia sesión para sincronizar",
|
||||
"Synced at {{time}}": "Sincronizado a las {{time}}",
|
||||
"Never synced": "Nunca sincronizado",
|
||||
"Show Remaining Time": "Mostrar tiempo restante",
|
||||
"{{time}} min left in chapter": "{{time}} min restantes en el capítulo",
|
||||
@@ -372,12 +356,12 @@
|
||||
"Fit": "Encajar",
|
||||
"Reset {{settings}}": "Restablecer {{settings}}",
|
||||
"Reset Settings": "Restablecer configuración",
|
||||
"{{count}} pages left in chapter_one": "{{count}} página restante en el capítulo",
|
||||
"{{count}} pages left in chapter_many": "{{count}} páginas restantes en el capítulo",
|
||||
"{{count}} pages left in chapter_other": "{{count}} páginas restantes en el capítulo",
|
||||
"{{count}} pages left in chapter_one": "<0>{{count}}</0><1> página restante en el capítulo</1>",
|
||||
"{{count}} pages left in chapter_many": "<0>{{count}}</0><1> páginas restantes en el capítulo</1>",
|
||||
"{{count}} pages left in chapter_other": "<0>{{count}}</0><1> páginas restantes en el capítulo</1>",
|
||||
"Show Remaining Pages": "Mostrar resto",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Gestionar suscripción",
|
||||
"Coming Soon": "Próximamente",
|
||||
@@ -396,7 +380,6 @@
|
||||
"Email:": "Correo electrónico:",
|
||||
"Plan:": "Plan:",
|
||||
"Amount:": "Importe:",
|
||||
"Subscription ID:": "ID de suscripción:",
|
||||
"Go to Library": "Ir a la biblioteca",
|
||||
"Need help? Contact our support team at support@readest.com": "¿Necesitas ayuda? Contacta con nuestro equipo de soporte en support@readest.com",
|
||||
"Free Plan": "Plan gratuito",
|
||||
@@ -610,5 +593,849 @@
|
||||
"Opacity": "Opacidad",
|
||||
"Size": "Tamaño",
|
||||
"Cover": "Cubierta",
|
||||
"Contain": "Contener"
|
||||
"Contain": "Contener",
|
||||
"{{number}} pages left in chapter": "<0>{{number}}</0><1> páginas restantes en el capítulo</1>",
|
||||
"Device": "Dispositivo",
|
||||
"E-Ink Mode": "Modo E-Ink",
|
||||
"Highlight Colors": "Colores de resaltado",
|
||||
"Pagination": "Paginación",
|
||||
"Disable Double Tap": "Deshabilitar Doble Toque",
|
||||
"Tap to Paginate": "Tocar para Paginación",
|
||||
"Click to Paginate": "Hacer Clic para Paginación",
|
||||
"Tap Both Sides": "Tocar Ambos Lados",
|
||||
"Click Both Sides": "Hacer Clic en Ambos Lados",
|
||||
"Swap Tap Sides": "Intercambiar Lados de Toque",
|
||||
"Swap Click Sides": "Intercambiar Lados de Clic",
|
||||
"Source and Translated": "Fuente y Traducido",
|
||||
"Translated Only": "Solo Traducido",
|
||||
"Source Only": "Solo Fuente",
|
||||
"TTS Text": "TTS Texto",
|
||||
"The book file is corrupted": "El archivo del libro está dañado",
|
||||
"The book file is empty": "El archivo del libro está vacío",
|
||||
"Failed to open the book file": "Error al abrir el archivo del libro",
|
||||
"On-Demand Purchase": "Compra bajo demanda",
|
||||
"Full Customization": "Personalización Completa",
|
||||
"Lifetime Plan": "Plan de por vida",
|
||||
"One-Time Payment": "Pago Único",
|
||||
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Realiza un solo pago para disfrutar de acceso de por vida a funciones específicas en todos los dispositivos. Compra funciones o servicios específicos solo cuando los necesites.",
|
||||
"Expand Cloud Sync Storage": "Expandir Almacenamiento de Sincronización en la Nube",
|
||||
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Expande tu almacenamiento en la nube para siempre con una compra única. Cada compra adicional agrega más espacio.",
|
||||
"Unlock All Customization Options": "Desbloquear Todas las Opciones de Personalización",
|
||||
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Desbloquear temas adicionales, fuentes, opciones de diseño y funciones de lectura en voz alta, traductores, servicios de almacenamiento en la nube.",
|
||||
"Purchase Successful!": "¡Compra Exitosa!",
|
||||
"lifetime": "de por vida",
|
||||
"Thank you for your purchase! Your payment has been processed successfully.": "¡Gracias por tu compra! Tu pago se ha procesado correctamente.",
|
||||
"Order ID:": "ID de pedido:",
|
||||
"TTS Highlighting": "TTS Resaltado",
|
||||
"Style": "Estilo",
|
||||
"Underline": "Subrayado",
|
||||
"Strikethrough": "Tachado",
|
||||
"Squiggly": "Ondulado",
|
||||
"Outline": "Contorno",
|
||||
"Quick Colors": "Colores Rápidos",
|
||||
"Highlighter": "Resaltador",
|
||||
"Save Book Cover": "Guardar Portada del Libro",
|
||||
"Auto-save last book cover": "Guardar automáticamente la última portada del libro",
|
||||
"Back": "Atrás",
|
||||
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "¡Correo de confirmación enviado! Por favor, revisa tus direcciones de correo electrónico antigua y nueva para confirmar el cambio.",
|
||||
"Failed to update email": "Error al actualizar el correo electrónico",
|
||||
"New Email": "Nuevo correo electrónico",
|
||||
"Your new email": "Tu nuevo correo electrónico",
|
||||
"Updating email ...": "Actualizando correo electrónico ...",
|
||||
"Update email": "Actualizar correo electrónico",
|
||||
"Current email": "Correo electrónico actual",
|
||||
"Update Email": "Actualizar correo electrónico",
|
||||
"All": "Todos",
|
||||
"Unable to open book": "No se puede abrir el libro",
|
||||
"Punctuation": "Signos de puntuación",
|
||||
"Replace Quotation Marks": "Reemplazar comillas",
|
||||
"Enabled only in vertical layout.": "Habilitado solo en diseño vertical.",
|
||||
"No Conversion": "Sin conversión",
|
||||
"Simplified to Traditional": "Simpl. → Trad.",
|
||||
"Traditional to Simplified": "Trad. → Simpl.",
|
||||
"Simplified to Traditional (Taiwan)": "Simpl. → Trad. (Taiwan)",
|
||||
"Simplified to Traditional (Hong Kong)": "Simpl. → Trad. (Hong Kong)",
|
||||
"Simplified to Traditional (Taiwan), with phrases": "Simpl. → Trad. (Taiwan • frases)",
|
||||
"Traditional (Taiwan) to Simplified": "Trad. (Taiwan) → Simpl.",
|
||||
"Traditional (Hong Kong) to Simplified": "Trad. (Hong Kong) → Simpl.",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Trad. (Taiwan • frases) → Simpl.",
|
||||
"Convert Simplified and Traditional Chinese": "Convertir chino Simpl./Trad.",
|
||||
"Convert Mode": "Modo de conversión",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Error al guardar automáticamente la portada del libro para la pantalla de bloqueo: {{error}}",
|
||||
"Download from Cloud": "Descargar desde la nube",
|
||||
"Upload to Cloud": "Subir a la nube",
|
||||
"Clear Custom Fonts": "Limpiar fuentes personalizadas",
|
||||
"Columns": "Columnas",
|
||||
"OPDS Catalogs": "Catálogos OPDS",
|
||||
"Adding LAN addresses is not supported in the web app version.": "La versión web no permite añadir direcciones LAN.",
|
||||
"Invalid OPDS catalog. Please check the URL.": "Catálogo OPDS no válido. Por favor, comprueba la URL.",
|
||||
"Browse and download books from online catalogs": "Explora y descarga libros de catálogos en línea",
|
||||
"My Catalogs": "Mis catálogos",
|
||||
"Add Catalog": "Añadir catálogo",
|
||||
"No catalogs yet": "Aún no hay catálogos",
|
||||
"Add your first OPDS catalog to start browsing books": "Añade tu primer catálogo OPDS para empezar a explorar libros.",
|
||||
"Add Your First Catalog": "Añadir primer catálogo",
|
||||
"Browse": "Explorar",
|
||||
"Popular Catalogs": "Catálogos populares",
|
||||
"Add": "Añadir",
|
||||
"Add OPDS Catalog": "Añadir catálogo OPDS",
|
||||
"Catalog Name": "Nombre del catálogo",
|
||||
"My Calibre Library": "Mi biblioteca Calibre",
|
||||
"OPDS URL": "URL OPDS",
|
||||
"Username (optional)": "Usuario (opcional)",
|
||||
"Password (optional)": "Contraseña (opcional)",
|
||||
"Description (optional)": "Descripción (opcional)",
|
||||
"A brief description of this catalog": "Una breve descripción de este catálogo",
|
||||
"Validating...": "Validando...",
|
||||
"View All": "Ver todo",
|
||||
"Forward": "Adelante",
|
||||
"Home": "Inicio",
|
||||
"{{count}} items_one": "{{count}} elemento",
|
||||
"{{count}} items_many": "{{count}} elementos",
|
||||
"{{count}} items_other": "{{count}} elementos",
|
||||
"Download completed": "Descarga completa",
|
||||
"Download failed": "Error en la descarga",
|
||||
"Open Access": "Acceso abierto",
|
||||
"Borrow": "Prestar",
|
||||
"Buy": "Comprar",
|
||||
"Subscribe": "Suscribirse",
|
||||
"Sample": "Muestra",
|
||||
"Download": "Descargar",
|
||||
"Open & Read": "Abrir y leer",
|
||||
"Tags": "Etiquetas",
|
||||
"Tag": "Etiqueta",
|
||||
"First": "Primero",
|
||||
"Previous": "Anterior",
|
||||
"Next": "Siguiente",
|
||||
"Last": "Último",
|
||||
"Cannot Load Page": "No se puede cargar la página",
|
||||
"An error occurred": "Ocurrió un error",
|
||||
"Online Library": "Biblioteca en línea",
|
||||
"URL must start with http:// or https://": "URL debe comenzar con http:// o https://",
|
||||
"Title, Author, Tag, etc...": "Titulo, Autor, Etiqueta, etc...",
|
||||
"Query": "Consulta",
|
||||
"Subject": "Asunto",
|
||||
"Enter {{terms}}": "Ingrese {{terms}}",
|
||||
"No search results found": "No se encontraron resultados de búsqueda",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Error al cargar el feed OPDS: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Buscar en {{title}}",
|
||||
"Manage Storage": "Administrar almacenamiento",
|
||||
"Failed to load files": "Error al cargar los archivos",
|
||||
"Deleted {{count}} file(s)_one": "Se eliminó {{count}} archivo",
|
||||
"Deleted {{count}} file(s)_many": "Se eliminaron {{count}} archivos",
|
||||
"Deleted {{count}} file(s)_other": "Se eliminaron {{count}} archivos",
|
||||
"Failed to delete {{count}} file(s)_one": "Error al eliminar {{count}} archivo",
|
||||
"Failed to delete {{count}} file(s)_many": "Error al eliminar {{count}} archivos",
|
||||
"Failed to delete {{count}} file(s)_other": "Error al eliminar {{count}} archivos",
|
||||
"Failed to delete files": "Error al eliminar los archivos",
|
||||
"Total Files": "Total de archivos",
|
||||
"Total Size": "Tamaño total",
|
||||
"Quota": "Cuota",
|
||||
"Used": "Usado",
|
||||
"Files": "Archivos",
|
||||
"Search files...": "Buscar archivos...",
|
||||
"Newest First": "Más nuevos primero",
|
||||
"Oldest First": "Más antiguos primero",
|
||||
"Largest First": "Más grandes primero",
|
||||
"Smallest First": "Más pequeños primero",
|
||||
"Name A-Z": "Nombre A–Z",
|
||||
"Name Z-A": "Nombre Z–A",
|
||||
"{{count}} selected_one": "{{count}} seleccionado",
|
||||
"{{count}} selected_many": "{{count}} seleccionados",
|
||||
"{{count}} selected_other": "{{count}} seleccionados",
|
||||
"Delete Selected": "Eliminar seleccionados",
|
||||
"Created": "Creado",
|
||||
"No files found": "No se encontraron archivos",
|
||||
"No files uploaded yet": "Aún no se han subido archivos",
|
||||
"files": "archivos",
|
||||
"Page {{current}} of {{total}}": "Página {{current}} de {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "¿Seguro que deseas eliminar {{count}} archivo seleccionado?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "¿Seguro que deseas eliminar {{count}} archivos seleccionados?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "¿Seguro que deseas eliminar {{count}} archivos seleccionados?",
|
||||
"Cloud Storage Usage": "Uso de almacenamiento en la nube",
|
||||
"Rename Group": "Renombrar grupo",
|
||||
"From Directory": "Desde el directorio",
|
||||
"Successfully imported {{count}} book(s)_one": "Se importó correctamente 1 libro",
|
||||
"Successfully imported {{count}} book(s)_many": "Se importaron correctamente {{count}} libros",
|
||||
"Successfully imported {{count}} book(s)_other": "Se importaron correctamente {{count}} libros",
|
||||
"Count": "Cuenta",
|
||||
"Start Page": "Página de inicio",
|
||||
"Search in OPDS Catalog...": "Buscar en el catálogo OPDS...",
|
||||
"Please log in to use advanced TTS features": "Por favor, inicie sesión para usar funciones avanzadas de TTS",
|
||||
"Word limit of 30 words exceeded.": "Se superó el límite de 30 palabras.",
|
||||
"Proofread": "Corrección",
|
||||
"Current selection": "Selección actual",
|
||||
"All occurrences in this book": "Todas las apariciones en este libro",
|
||||
"All occurrences in your library": "Todas las apariciones en tu biblioteca",
|
||||
"Selected text:": "Texto seleccionado:",
|
||||
"Replace with:": "Reemplazar por:",
|
||||
"Enter text...": "Introduce texto…",
|
||||
"Case sensitive:": "Distinguir mayúsculas y minúsculas:",
|
||||
"Scope:": "Ámbito:",
|
||||
"Selection": "Selección",
|
||||
"Library": "Biblioteca",
|
||||
"Yes": "Sí",
|
||||
"No": "No",
|
||||
"Proofread Replacement Rules": "Reglas de reemplazo de corrección",
|
||||
"Selected Text Rules": "Reglas de texto seleccionado",
|
||||
"No selected text replacement rules": "No hay reglas de reemplazo para el texto seleccionado",
|
||||
"Book Specific Rules": "Reglas específicas del libro",
|
||||
"No book-level replacement rules": "No hay reglas de reemplazo a nivel de libro",
|
||||
"Disable Quick Action": "Desactivar acción rápida",
|
||||
"Enable Quick Action on Selection": "Activar acción rápida al seleccionar",
|
||||
"None": "Ninguna",
|
||||
"Annotation Tools": "Herramientas de anotación",
|
||||
"Enable Quick Actions": "Activar acciones rápidas",
|
||||
"Quick Action": "Acción rápida",
|
||||
"Copy to Notebook": "Copiar al cuaderno",
|
||||
"Copy text after selection": "Copiar texto después de la selección",
|
||||
"Highlight text after selection": "Resaltar texto después de la selección",
|
||||
"Annotate text after selection": "Anotar texto después de la selección",
|
||||
"Search text after selection": "Buscar texto después de la selección",
|
||||
"Look up text in dictionary after selection": "Buscar texto en el diccionario después de la selección",
|
||||
"Translate text after selection": "Traducir texto después de la selección",
|
||||
"Read text aloud after selection": "Leer texto en voz alta después de la selección",
|
||||
"Proofread text after selection": "Corregir texto después de la selección",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} activas, {{pendingCount}} pendientes",
|
||||
"{{failedCount}} failed": "{{failedCount}} fallidas",
|
||||
"Waiting...": "Esperando...",
|
||||
"Failed": "Fallido",
|
||||
"Completed": "Completado",
|
||||
"Cancelled": "Cancelado",
|
||||
"Retry": "Reintentar",
|
||||
"Active": "Activas",
|
||||
"Transfer Queue": "Cola de transferencias",
|
||||
"Upload All": "Subir todos",
|
||||
"Download All": "Descargar todos",
|
||||
"Resume Transfers": "Reanudar transferencias",
|
||||
"Pause Transfers": "Pausar transferencias",
|
||||
"Pending": "Pendientes",
|
||||
"No transfers": "Sin transferencias",
|
||||
"Retry All": "Reintentar todos",
|
||||
"Clear Completed": "Limpiar completados",
|
||||
"Clear Failed": "Limpiar fallidos",
|
||||
"Upload queued: {{title}}": "Subida en cola: {{title}}",
|
||||
"Download queued: {{title}}": "Descarga en cola: {{title}}",
|
||||
"Book not found in library": "Libro no encontrado en la biblioteca",
|
||||
"Unknown error": "Error desconocido",
|
||||
"Please log in to continue": "Inicia sesión para continuar",
|
||||
"Cloud File Transfers": "Transferencias en la nube",
|
||||
"Show Search Results": "Mostrar resultados",
|
||||
"Search results for '{{term}}'": "Resultados para '{{term}}'",
|
||||
"Close Search": "Cerrar búsqueda",
|
||||
"Previous Result": "Resultado anterior",
|
||||
"Next Result": "Resultado siguiente",
|
||||
"Bookmarks": "Marcadores",
|
||||
"Annotations": "Anotaciones",
|
||||
"Show Results": "Mostrar resultados",
|
||||
"Clear search": "Borrar búsqueda",
|
||||
"Clear search history": "Borrar historial de búsqueda",
|
||||
"Tap to Toggle Footer": "Toca para alternar pie de página",
|
||||
"Show Current Time": "Mostrar hora actual",
|
||||
"Use 24 Hour Clock": "Usar formato de 24 horas",
|
||||
"Show Current Battery Status": "Mostrar el estado actual de la batería",
|
||||
"Exported successfully": "Exportado con éxito",
|
||||
"Book exported successfully.": "Libro exportado con éxito.",
|
||||
"Failed to export the book.": "Error al exportar el libro.",
|
||||
"Export Book": "Exportar libro",
|
||||
"Whole word:": "Palabra completa:",
|
||||
"Date Published": "Fecha de publicación",
|
||||
"Only for TTS:": "Solo para TTS:",
|
||||
"Uploaded": "Subido",
|
||||
"Downloaded": "Descargado",
|
||||
"Deleted": "Eliminado",
|
||||
"Note:": "Nota:",
|
||||
"Time:": "Hora:",
|
||||
"Format Options": "Opciones de formato",
|
||||
"Export Date": "Fecha de exportación",
|
||||
"Chapter Titles": "Títulos de capítulos",
|
||||
"Chapter Separator": "Separador de capítulos",
|
||||
"Highlights": "Resaltados",
|
||||
"Note Date": "Fecha de nota",
|
||||
"Advanced": "Avanzado",
|
||||
"Hide": "Ocultar",
|
||||
"Show": "Mostrar",
|
||||
"Use Custom Template": "Usar plantilla personalizada",
|
||||
"Export Template": "Plantilla de exportación",
|
||||
"Template Syntax:": "Sintaxis de plantilla:",
|
||||
"Insert value": "Insertar valor",
|
||||
"Format date (locale)": "Formatear fecha (regional)",
|
||||
"Format date (custom)": "Formatear fecha (personalizado)",
|
||||
"Conditional": "Condicional",
|
||||
"Loop": "Bucle",
|
||||
"Available Variables:": "Variables disponibles:",
|
||||
"Book title": "Título del libro",
|
||||
"Book author": "Autor del libro",
|
||||
"Export date": "Fecha de exportación",
|
||||
"Array of chapters": "Lista de capítulos",
|
||||
"Chapter title": "Título del capítulo",
|
||||
"Array of annotations": "Lista de anotaciones",
|
||||
"Highlighted text": "Texto resaltado",
|
||||
"Annotation note": "Nota de anotación",
|
||||
"Date Format Tokens:": "Tokens de formato de fecha:",
|
||||
"Year (4 digits)": "Año (4 dígitos)",
|
||||
"Month (01-12)": "Mes (01-12)",
|
||||
"Day (01-31)": "Día (01-31)",
|
||||
"Hour (00-23)": "Hora (00-23)",
|
||||
"Minute (00-59)": "Minuto (00-59)",
|
||||
"Second (00-59)": "Segundo (00-59)",
|
||||
"Show Source": "Mostrar fuente",
|
||||
"No content to preview": "Sin contenido para previsualizar",
|
||||
"Export": "Exportar",
|
||||
"Set Timeout": "Establecer tiempo límite",
|
||||
"Select Voice": "Seleccionar voz",
|
||||
"Toggle Sticky Bottom TTS Bar": "Alternar barra TTS fija",
|
||||
"Display what I'm reading on Discord": "Mostrar lo que estoy leyendo en Discord",
|
||||
"Show on Discord": "Mostrar en Discord",
|
||||
"Instant {{action}}": "{{action}} instantáneo",
|
||||
"Instant {{action}} Disabled": "{{action}} instantáneo desactivado",
|
||||
"Annotation": "Anotación",
|
||||
"Reset Template": "Restablecer plantilla",
|
||||
"Annotation style": "Estilo de anotación",
|
||||
"Annotation color": "Color de anotación",
|
||||
"Annotation time": "Hora de anotación",
|
||||
"AI": "IA",
|
||||
"Are you sure you want to re-index this book?": "¿Está seguro de que desea reindexar este libro?",
|
||||
"Enable AI in Settings": "Habilitar IA en Configuración",
|
||||
"Index This Book": "Indexar este libro",
|
||||
"Enable AI search and chat for this book": "Habilitar búsqueda y chat con IA para este libro",
|
||||
"Start Indexing": "Iniciar indexación",
|
||||
"Indexing book...": "Indexando libro...",
|
||||
"Preparing...": "Preparando...",
|
||||
"Delete this conversation?": "¿Eliminar esta conversación?",
|
||||
"No conversations yet": "Aún no hay conversaciones",
|
||||
"Start a new chat to ask questions about this book": "Inicie un nuevo chat para hacer preguntas sobre este libro",
|
||||
"Rename": "Renombrar",
|
||||
"New Chat": "Nuevo chat",
|
||||
"Chat": "Chat",
|
||||
"Please enter a model ID": "Por favor, ingrese un ID de modelo",
|
||||
"Model not available or invalid": "Modelo no disponible o inválido",
|
||||
"Failed to validate model": "Error al validar el modelo",
|
||||
"Couldn't connect to Ollama. Is it running?": "No se pudo conectar a Ollama. ¿Está ejecutándose?",
|
||||
"Invalid API key or connection failed": "Clave API inválida o conexión fallida",
|
||||
"Connection failed": "Conexión fallida",
|
||||
"AI Assistant": "Asistente de IA",
|
||||
"Enable AI Assistant": "Habilitar asistente de IA",
|
||||
"Provider": "Proveedor",
|
||||
"Ollama (Local)": "Ollama (Local)",
|
||||
"AI Gateway (Cloud)": "Pasarela de IA (Nube)",
|
||||
"Ollama Configuration": "Configuración de Ollama",
|
||||
"Refresh Models": "Actualizar modelos",
|
||||
"AI Model": "Modelo de IA",
|
||||
"No models detected": "No se detectaron modelos",
|
||||
"AI Gateway Configuration": "Configuración de pasarela de IA",
|
||||
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Elija entre una selección de modelos de IA de alta calidad y económicos. También puede usar su propio modelo seleccionando \"Modelo personalizado\" a continuación.",
|
||||
"API Key": "Clave API",
|
||||
"Get Key": "Obtener clave",
|
||||
"Model": "Modelo",
|
||||
"Custom Model...": "Modelo personalizado...",
|
||||
"Custom Model ID": "ID de modelo personalizado",
|
||||
"Validate": "Validar",
|
||||
"Model available": "Modelo disponible",
|
||||
"Connection": "Conexión",
|
||||
"Test Connection": "Probar conexión",
|
||||
"Connected": "Conectado",
|
||||
"Custom Colors": "Colores personalizados",
|
||||
"Color E-Ink Mode": "Modo E-Ink a color",
|
||||
"Reading Ruler": "Regla de lectura",
|
||||
"Enable Reading Ruler": "Habilitar regla de lectura",
|
||||
"Lines to Highlight": "Líneas a resaltar",
|
||||
"Ruler Color": "Color de la regla",
|
||||
"Command Palette": "Paleta de comandos",
|
||||
"Search settings and actions...": "Buscar ajustes y acciones...",
|
||||
"No results found for": "No se encontraron resultados para",
|
||||
"Type to search settings and actions": "Escribe para buscar ajustes y acciones",
|
||||
"Recent": "Reciente",
|
||||
"navigate": "navegar",
|
||||
"select": "seleccionar",
|
||||
"close": "cerrar",
|
||||
"Search Settings": "Buscar ajustes",
|
||||
"Page Margins": "Márgenes de página",
|
||||
"AI Provider": "Proveedor de IA",
|
||||
"Ollama URL": "URL de Ollama",
|
||||
"Ollama Model": "Modelo de Ollama",
|
||||
"AI Gateway Model": "Modelo de AI Gateway",
|
||||
"Actions": "Acciones",
|
||||
"Navigation": "Navegación",
|
||||
"Set status for {{count}} book(s)_one": "Establecer estado para {{count}} libro",
|
||||
"Set status for {{count}} book(s)_other": "Establecer estado para {{count}} libros",
|
||||
"Mark as Unread": "Marcar como no leído",
|
||||
"Mark as Finished": "Marcar como terminado",
|
||||
"Finished": "Terminado",
|
||||
"Unread": "Sin leer",
|
||||
"Clear Status": "Borrar estado",
|
||||
"Status": "Estado",
|
||||
"Set status for {{count}} book(s)_many": "Establecer estado para {{count}} libros",
|
||||
"Loading": "Cargando...",
|
||||
"Exit Paragraph Mode": "Salir del modo de párrafo",
|
||||
"Paragraph Mode": "Modo de párrafo",
|
||||
"Embedding Model": "Modelo de incrustación",
|
||||
"{{count}} book(s) synced_one": "{{count}} libro sincronizado",
|
||||
"{{count}} book(s) synced_many": "{{count}} libros sincronizados",
|
||||
"{{count}} book(s) synced_other": "{{count}} libros sincronizados",
|
||||
"Unable to start RSVP": "No se puede iniciar RSVP",
|
||||
"RSVP not supported for PDF": "RSVP no es compatible con PDF",
|
||||
"Select Chapter": "Seleccionar capítulo",
|
||||
"Context": "Contexto",
|
||||
"Ready": "Listo",
|
||||
"Chapter Progress": "Progreso del capítulo",
|
||||
"words": "palabras",
|
||||
"{{time}} left": "faltan {{time}}",
|
||||
"Reading progress": "Progreso de lectura",
|
||||
"Skip back 15 words": "Retroceder 15 palabras",
|
||||
"Back 15 words (Shift+Left)": "Retroceder 15 palabras (Shift+Izquierda)",
|
||||
"Pause (Space)": "Pausa (Espacio)",
|
||||
"Play (Space)": "Reproducir (Espacio)",
|
||||
"Skip forward 15 words": "Adelantar 15 palabras",
|
||||
"Forward 15 words (Shift+Right)": "Adelantar 15 palabras (Shift+Derecha)",
|
||||
"Decrease speed": "Disminuir velocidad",
|
||||
"Slower (Left/Down)": "Más lento (Izquierda/Abajo)",
|
||||
"Increase speed": "Aumentar velocidad",
|
||||
"Faster (Right/Up)": "Más rápido (Derecha/Arriba)",
|
||||
"Start RSVP Reading": "Iniciar lectura RSVP",
|
||||
"Choose where to start reading": "Elegir dónde empezar a leer",
|
||||
"From Chapter Start": "Desde el inicio del capítulo",
|
||||
"Start reading from the beginning of the chapter": "Empezar a leer desde el principio del capítulo",
|
||||
"Resume": "Reanudar",
|
||||
"Continue from where you left off": "Continuar desde donde lo dejaste",
|
||||
"From Current Page": "Desde la página actual",
|
||||
"Start from where you are currently reading": "Empezar desde donde estás leyendo actualmente",
|
||||
"From Selection": "Desde la selección",
|
||||
"Speed Reading Mode": "Modo de lectura rápida",
|
||||
"Scroll left": "Desplazar a la izquierda",
|
||||
"Scroll right": "Desplazar a la derecha",
|
||||
"Library Sync Progress": "Progreso de sincronización de la biblioteca",
|
||||
"Back to library": "Volver a la biblioteca",
|
||||
"Group by...": "Agrupar por...",
|
||||
"Export as Plain Text": "Exportar como texto sin formato",
|
||||
"Export as Markdown": "Exportar como Markdown",
|
||||
"Show Page Navigation Buttons": "Botones de navegación",
|
||||
"Page {{number}}": "Página {{number}}",
|
||||
"highlight": "resaltado",
|
||||
"underline": "subrayado",
|
||||
"squiggly": "ondulado",
|
||||
"red": "rojo",
|
||||
"violet": "violeta",
|
||||
"blue": "azul",
|
||||
"green": "verde",
|
||||
"yellow": "amarillo",
|
||||
"Select {{style}} style": "Seleccionar estilo {{style}}",
|
||||
"Select {{color}} color": "Seleccionar color {{color}}",
|
||||
"Close Book": "Cerrar libro",
|
||||
"Speed Reading": "Lectura rápida",
|
||||
"Close Speed Reading": "Cerrar lectura rápida",
|
||||
"Authors": "Autores",
|
||||
"Books": "Libros",
|
||||
"Groups": "Grupos",
|
||||
"Back to TTS Location": "Volver a la ubicación de TTS",
|
||||
"Metadata": "Metadatos",
|
||||
"Image viewer": "Visor de imágenes",
|
||||
"Previous Image": "Imagen anterior",
|
||||
"Next Image": "Imagen siguiente",
|
||||
"Zoomed": "Con zoom",
|
||||
"Zoom level": "Nivel de zoom",
|
||||
"Table viewer": "Visor de tablas",
|
||||
"Unable to connect to Readwise. Please check your network connection.": "No se pudo conectar a Readwise. Por favor, comprueba tu conexión a la red.",
|
||||
"Invalid Readwise access token": "Token de acceso a Readwise no válido",
|
||||
"Disconnected from Readwise": "Desconectado de Readwise",
|
||||
"Never": "Nunca",
|
||||
"Sync Enabled": "Sincronización habilitada",
|
||||
"Disconnect": "Desconectar",
|
||||
"Connect your Readwise account to sync highlights.": "Conecta tu cuenta de Readwise para sincronizar los resaltados.",
|
||||
"Get your access token at": "Obtén tu token de acceso en",
|
||||
"Access Token": "Token de acceso",
|
||||
"Paste your Readwise access token": "Pega tu token de acceso de Readwise",
|
||||
"Readwise Sync": "Sincronización con Readwise",
|
||||
"Push Highlights": "Enviar resaltados",
|
||||
"Highlights synced to Readwise": "Resaltados sincronizados con Readwise",
|
||||
"Readwise sync failed: no internet connection": "Error en la sincronización con Readwise: sin conexión a internet",
|
||||
"Readwise sync failed: {{error}}": "Error en la sincronización con Readwise: {{error}}",
|
||||
"System Screen Brightness": "Brillo de pantalla del sistema",
|
||||
"Page:": "Página:",
|
||||
"Page: {{number}}": "Página: {{number}}",
|
||||
"Annotation page number": "Número de página de la anotación",
|
||||
"Translating...": "Traduciendo...",
|
||||
"Show Battery Percentage": "Mostrar porcentaje de batería",
|
||||
"Hide Scrollbar": "Ocultar barra de desplazamiento",
|
||||
"Skip to last reading position": "Ir a la última posición de lectura",
|
||||
"Show context": "Mostrar contexto",
|
||||
"Hide context": "Ocultar contexto",
|
||||
"Decrease font size": "Reducir tamaño de fuente",
|
||||
"Increase font size": "Aumentar tamaño de fuente",
|
||||
"Focus": "Enfoque",
|
||||
"Theme color": "Color del tema",
|
||||
"Import failed": "Error de importación",
|
||||
"Available Formatters:": "Formateadores disponibles:",
|
||||
"Format date": "Formatear fecha",
|
||||
"Markdown block quote (> per line)": "Cita en bloque Markdown (> por línea)",
|
||||
"Newlines to <br>": "Saltos de línea a <br>",
|
||||
"Change case": "Cambiar mayúsculas/minúsculas",
|
||||
"Trim whitespace": "Recortar espacios",
|
||||
"Truncate to n characters": "Truncar a n caracteres",
|
||||
"Replace text": "Reemplazar texto",
|
||||
"Fallback value": "Valor alternativo",
|
||||
"Get length": "Obtener longitud",
|
||||
"First/last element": "Primer/último elemento",
|
||||
"Join array": "Unir array",
|
||||
"Backup failed: {{error}}": "Error en la copia de seguridad: {{error}}",
|
||||
"Select Backup": "Seleccionar copia de seguridad",
|
||||
"Restore failed: {{error}}": "Error en la restauración: {{error}}",
|
||||
"Backup & Restore": "Copia de seguridad y restauración",
|
||||
"Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Crea una copia de seguridad de tu biblioteca o restaura desde una copia anterior. La restauración se fusionará con tu biblioteca actual.",
|
||||
"Backup Library": "Hacer copia de seguridad",
|
||||
"Restore Library": "Restaurar biblioteca",
|
||||
"Creating backup...": "Creando copia de seguridad...",
|
||||
"Restoring library...": "Restaurando biblioteca...",
|
||||
"{{current}} of {{total}} items": "{{current}} de {{total}} elementos",
|
||||
"Backup completed successfully!": "¡Copia de seguridad completada!",
|
||||
"Restore completed successfully!": "¡Restauración completada!",
|
||||
"Your library has been saved to the selected location.": "Tu biblioteca se ha guardado en la ubicación seleccionada.",
|
||||
"{{added}} books added, {{updated}} books updated.": "{{added}} libros añadidos, {{updated}} libros actualizados.",
|
||||
"Operation failed": "La operación falló",
|
||||
"Loading library...": "Cargando biblioteca...",
|
||||
"{{count}} books refreshed_one": "{{count}} libro actualizado",
|
||||
"{{count}} books refreshed_many": "{{count}} libros actualizados",
|
||||
"{{count}} books refreshed_other": "{{count}} libros actualizados",
|
||||
"Failed to refresh metadata": "Error al actualizar metadatos",
|
||||
"Refresh Metadata": "Actualizar metadatos",
|
||||
"Keyboard Shortcuts": "Atajos de teclado",
|
||||
"View all keyboard shortcuts": "Ver todos los atajos de teclado",
|
||||
"Switch Sidebar Tab": "Cambiar pestaña de la barra lateral",
|
||||
"Toggle Notebook": "Mostrar/ocultar cuaderno",
|
||||
"Search in Book": "Buscar en el libro",
|
||||
"Toggle Scroll Mode": "Alternar modo desplazamiento",
|
||||
"Toggle Select Mode": "Alternar modo selección",
|
||||
"Toggle Bookmark": "Alternar marcador",
|
||||
"Toggle Text to Speech": "Alternar texto a voz",
|
||||
"Play / Pause TTS": "Reproducir / Pausar TTS",
|
||||
"Toggle Paragraph Mode": "Alternar modo párrafo",
|
||||
"Highlight Selection": "Resaltar selección",
|
||||
"Underline Selection": "Subrayar selección",
|
||||
"Annotate Selection": "Anotar selección",
|
||||
"Search Selection": "Buscar selección",
|
||||
"Copy Selection": "Copiar selección",
|
||||
"Translate Selection": "Traducir selección",
|
||||
"Dictionary Lookup": "Buscar en diccionario",
|
||||
"Read Aloud Selection": "Leer selección en voz alta",
|
||||
"Proofread Selection": "Corregir selección",
|
||||
"Open Settings": "Abrir ajustes",
|
||||
"Open Command Palette": "Abrir paleta de comandos",
|
||||
"Show Keyboard Shortcuts": "Mostrar atajos de teclado",
|
||||
"Open Books": "Abrir libros",
|
||||
"Toggle Fullscreen": "Alternar pantalla completa",
|
||||
"Close Window": "Cerrar ventana",
|
||||
"Quit App": "Salir de la aplicación",
|
||||
"Go Left / Previous Page": "Ir a la izquierda / Página anterior",
|
||||
"Go Right / Next Page": "Ir a la derecha / Página siguiente",
|
||||
"Go Up": "Ir arriba",
|
||||
"Go Down": "Ir abajo",
|
||||
"Previous Chapter": "Capítulo anterior",
|
||||
"Next Chapter": "Capítulo siguiente",
|
||||
"Scroll Half Page Down": "Desplazar media página abajo",
|
||||
"Scroll Half Page Up": "Desplazar media página arriba",
|
||||
"Save Note": "Guardar nota",
|
||||
"Single Section Scroll": "Desplazamiento por sección",
|
||||
"General": "General",
|
||||
"Text to Speech": "Texto a voz",
|
||||
"Zoom": "Zoom",
|
||||
"Window": "Ventana",
|
||||
"Your Bookshelf": "Tu estantería",
|
||||
"TTS": "Lectura en voz alta",
|
||||
"Media Info": "Info multimedia",
|
||||
"Update Frequency": "Frecuencia de actualización",
|
||||
"Every Sentence": "Cada oración",
|
||||
"Every Paragraph": "Cada párrafo",
|
||||
"Every Chapter": "Cada capítulo",
|
||||
"TTS Media Info Update Frequency": "Frecuencia de actualización de info multimedia TTS",
|
||||
"Select reading speed": "Seleccionar velocidad de lectura",
|
||||
"Drag to seek": "Arrastra para buscar",
|
||||
"Punctuation Delay": "Retraso de puntuación",
|
||||
"Please confirm that this OPDS connection will be proxied through Readest servers on the web app before continuing.": "Confirme que esta conexión OPDS se enrutará a través de los servidores de Readest en la aplicación web antes de continuar.",
|
||||
"Custom Headers (optional)": "Encabezados personalizados (opcional)",
|
||||
"Add one header per line using \"Header-Name: value\".": "Añada un encabezado por línea usando \"Header-Name: value\".",
|
||||
"I understand this OPDS connection will be proxied through Readest servers on the web app. If I do not trust Readest with these credentials or headers, I should use the native app instead.": "Entiendo que esta conexión OPDS se enrutará a través de los servidores de Readest en la aplicación web. Si no confío en Readest con estas credenciales o encabezados, debería usar la aplicación nativa.",
|
||||
"Unable to connect to Hardcover. Please check your network connection.": "No se puede conectar a Hardcover. Compruebe su conexión de red.",
|
||||
"Invalid Hardcover API token": "Token de API de Hardcover no válido",
|
||||
"Disconnected from Hardcover": "Desconectado de Hardcover",
|
||||
"Connect your Hardcover account to sync reading progress and notes.": "Conecte su cuenta de Hardcover para sincronizar el progreso de lectura y las notas.",
|
||||
"Get your API token from hardcover.app → Settings → API.": "Obtenga su token de API en hardcover.app → Ajustes → API.",
|
||||
"API Token": "Token de API",
|
||||
"Paste your Hardcover API token": "Pegue su token de API de Hardcover",
|
||||
"Hardcover Sync": "Sincronización de Hardcover",
|
||||
"Push Notes": "Enviar notas",
|
||||
"No annotations or excerpts to sync for this book.": "No hay anotaciones ni extractos para sincronizar en este libro.",
|
||||
"Configure Hardcover in Settings first.": "Configure Hardcover en los ajustes primero.",
|
||||
"No new Hardcover note changes to sync.": "No hay nuevos cambios en notas de Hardcover para sincronizar.",
|
||||
"Hardcover synced: {{inserted}} new, {{updated}} updated, {{skipped}} unchanged": "Hardcover sincronizado: {{inserted}} nuevos, {{updated}} actualizados, {{skipped}} sin cambios",
|
||||
"Hardcover notes sync failed: {{error}}": "Error al sincronizar notas de Hardcover: {{error}}",
|
||||
"Reading progress synced to Hardcover": "Progreso de lectura sincronizado con Hardcover",
|
||||
"Hardcover progress sync failed: {{error}}": "Error al sincronizar progreso de Hardcover: {{error}}",
|
||||
"ISBN": "ISBN",
|
||||
"Keep existing source identifier": "Mantener el identificador de origen existente",
|
||||
"Toggle Toolbar": "Alternar barra de herramientas",
|
||||
"Split Hyphens": "Dividir guiones",
|
||||
"Apply Theme Colors to PDF": "Aplicar colores del tema al PDF",
|
||||
"Name": "Nombre",
|
||||
"Remove": "Eliminar",
|
||||
"Edit OPDS Catalog": "Editar catálogo OPDS",
|
||||
"Save Changes": "Guardar cambios",
|
||||
"Use Book Layout": "Usar el diseño del libro",
|
||||
"End of this section. Continue to the next.": "Fin de esta sección. Continuar a la siguiente.",
|
||||
"Auto-download": "Descarga automática",
|
||||
"Auto-download new items": "Descargar nuevos elementos automáticamente",
|
||||
"Automatically download new publications when the app syncs": "Descargar nuevas publicaciones automáticamente cuando la aplicación se sincroniza",
|
||||
"{{count}} new item(s) downloaded from OPDS_one": "Se descargó {{count}} nuevo elemento desde OPDS",
|
||||
"{{count}} new item(s) downloaded from OPDS_many": "Se descargaron {{count}} nuevos elementos desde OPDS",
|
||||
"{{count}} new item(s) downloaded from OPDS_other": "Se descargaron {{count}} nuevos elementos desde OPDS",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_one": "No se pudo sincronizar {{count}} catálogo OPDS",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_many": "No se pudieron sincronizar {{count}} catálogos OPDS",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_other": "No se pudieron sincronizar {{count}} catálogos OPDS",
|
||||
"Last synced {{when}}": "Última sincronización {{when}}",
|
||||
"Failed downloads": "Descargas fallidas",
|
||||
"No failed downloads": "No hay descargas fallidas",
|
||||
"Attempts: {{count}}_one": "Intentos: {{count}}",
|
||||
"Attempts: {{count}}_many": "Intentos: {{count}}",
|
||||
"Attempts: {{count}}_other": "Intentos: {{count}}",
|
||||
"Skip": "Omitir",
|
||||
"Skip all": "Omitir todos",
|
||||
"Retry all": "Reintentar todos",
|
||||
"{{count}} failed_one": "{{count}} fallida",
|
||||
"{{count}} failed_many": "{{count}} fallidas",
|
||||
"{{count}} failed_other": "{{count}} fallidas",
|
||||
"(none)": "(ninguno)",
|
||||
"Identifiers": "Identificadores",
|
||||
"Read (Stream)": "Leer (Streaming)",
|
||||
"Failed to start stream": "No se pudo iniciar el streaming",
|
||||
"No dictionaries enabled": "No hay diccionarios habilitados",
|
||||
"Enable a dictionary in Settings → Language → Dictionaries.": "Habilita un diccionario en Ajustes → Idioma → Diccionarios.",
|
||||
"Wiktionary": "Wiktionary",
|
||||
"Drag to reorder": "Arrastrar para reordenar",
|
||||
"Built-in": "Integrado",
|
||||
"Bundle is missing on this device. Re-import to use it.": "El paquete falta en este dispositivo. Vuelve a importarlo para usarlo.",
|
||||
"This dictionary format is not supported.": "Este formato de diccionario no es compatible.",
|
||||
"MDict": "MDict",
|
||||
"StarDict": "StarDict",
|
||||
"Imported {{count}} dictionary_one": "Se importó {{count}} diccionario",
|
||||
"Imported {{count}} dictionary_many": "Se importaron {{count}} diccionarios",
|
||||
"Imported {{count}} dictionary_other": "Se importaron {{count}} diccionarios",
|
||||
"Skipped incomplete bundles: {{names}}": "Se omitieron paquetes incompletos: {{names}}",
|
||||
"Failed to import dictionary: {{message}}": "Error al importar el diccionario: {{message}}",
|
||||
"Dictionaries": "Diccionarios",
|
||||
"Delete Dictionary": "Eliminar diccionario",
|
||||
"Importing…": "Importando…",
|
||||
"Import Dictionary": "Importar diccionario",
|
||||
"No dictionaries available.": "No hay diccionarios disponibles.",
|
||||
"StarDict bundles need .ifo, .idx, and .dict.dz files (.syn optional).": "Los paquetes StarDict requieren archivos .ifo, .idx y .dict.dz (.syn opcional).",
|
||||
"Select all the bundle files together when importing.": "Selecciona todos los archivos del paquete juntos al importar.",
|
||||
"Manage Dictionaries": "Administrar diccionarios",
|
||||
"Select Dictionary Files": "Seleccionar archivos de diccionario",
|
||||
"Read on Wikipedia →": "Leer en Wikipedia →",
|
||||
"This link can't be opened": "No se puede abrir este enlace",
|
||||
"The annotation link is missing required information. The original link may have been truncated.": "Al enlace de anotación le faltan datos necesarios. Es posible que el enlace original se haya truncado.",
|
||||
"Go to Readest": "Ir a Readest",
|
||||
"Open-source ebook reader for everyone, on every device.": "Lector de libros electrónicos de código abierto para todos, en cualquier dispositivo.",
|
||||
"Open in Readest": "Abrir en Readest",
|
||||
"If Readest didn't open automatically, choose an option below:": "Si Readest no se abrió automáticamente, elige una opción:",
|
||||
"Continue reading where you left off.": "Continúa leyendo donde lo dejaste.",
|
||||
"Readest logo": "Logotipo de Readest",
|
||||
"Opening Readest...": "Abriendo Readest…",
|
||||
"Open in Readest app": "Abrir en la app de Readest",
|
||||
"Continue in browser": "Continuar en el navegador",
|
||||
"Don't have Readest?": "¿No tienes Readest?",
|
||||
"Book not in your library": "El libro no está en tu biblioteca",
|
||||
"Share Book": "Compartir libro",
|
||||
"Could not create share link": "No se pudo crear el enlace de compartir",
|
||||
"Link copied": "Enlace copiado",
|
||||
"Could not copy link": "No se pudo copiar el enlace",
|
||||
"Share revoked": "Compartir revocado",
|
||||
"Could not revoke share": "No se pudo revocar el compartir",
|
||||
"Uploading book…": "Subiendo libro…",
|
||||
"Generating…": "Generando…",
|
||||
"Generate share link": "Generar enlace de compartir",
|
||||
"Share URL": "URL de compartir",
|
||||
"Copy link": "Copiar enlace",
|
||||
"Copied": "Copiado",
|
||||
"Share via…": "Compartir vía…",
|
||||
"Expires {{date}}": "Caduca el {{date}}",
|
||||
"Revoking…": "Revocando…",
|
||||
"Revoke share": "Revocar compartir",
|
||||
"Sign in to share books": "Inicia sesión para compartir libros",
|
||||
"Expiring soon": "Caduca pronto",
|
||||
"Missing share token": "Falta el token de compartir",
|
||||
"Could not add to your library": "No se pudo añadir a tu biblioteca",
|
||||
"This share link is no longer available": "Este enlace ya no está disponible",
|
||||
"The original link may have expired or been revoked.": "Es posible que el enlace original haya caducado o se haya revocado.",
|
||||
"Get Readest": "Obtener Readest",
|
||||
"Loading shared book…": "Cargando libro compartido…",
|
||||
"Adding…": "Añadiendo…",
|
||||
"Add to my library": "Añadir a mi biblioteca",
|
||||
"Could not load your shares": "No se pudieron cargar tus compartidos",
|
||||
"Revoked": "Revocado",
|
||||
"Expired": "Caducado",
|
||||
"Shared books": "Libros compartidos",
|
||||
"You haven't shared any books yet": "Aún no has compartido ningún libro",
|
||||
"Open a book and tap Share to send it to a friend.": "Abre un libro y pulsa Compartir para enviárselo a un amigo.",
|
||||
"{{count}} active_one": "{{count}} activo",
|
||||
"{{count}} active_many": "{{count}} activos",
|
||||
"{{count}} active_other": "{{count}} activos",
|
||||
"{{count}} downloads_one": "{{count}} descarga",
|
||||
"{{count}} downloads_many": "{{count}} descargas",
|
||||
"{{count}} downloads_other": "{{count}} descargas",
|
||||
"starts at saved page": "comienza en la página guardada",
|
||||
"More actions": "Más acciones",
|
||||
"Loading…": "Cargando…",
|
||||
"Load more": "Cargar más",
|
||||
"Could not load shared book": "No se pudo cargar el libro compartido",
|
||||
"The share link is missing required information.": "Al enlace de compartir le faltan datos necesarios.",
|
||||
"Please check your connection and try again.": "Comprueba tu conexión y vuelve a intentarlo.",
|
||||
"Sign in to import shared books": "Inicia sesión para importar libros compartidos",
|
||||
"Already in your library": "Ya está en tu biblioteca",
|
||||
"Added to your library": "Añadido a tu biblioteca",
|
||||
"Could not import shared book": "No se pudo importar el libro compartido",
|
||||
"Manage Shared Links": "Gestionar enlaces compartidos",
|
||||
"Expires in": "Caduca en",
|
||||
"{{count}} days_one": "1 día",
|
||||
"{{count}} days_many": "{{count}} días",
|
||||
"{{count}} days_other": "{{count}} días",
|
||||
"Expires in {{count}} days_one": "Caduca en 1 día",
|
||||
"Expires in {{count}} days_many": "Caduca en {{count}} días",
|
||||
"Expires in {{count}} days_other": "Caduca en {{count}} días",
|
||||
"Expires in {{count}} hours_one": "Caduca en 1 hora",
|
||||
"Expires in {{count}} hours_many": "Caduca en {{count}} horas",
|
||||
"Expires in {{count}} hours_other": "Caduca en {{count}} horas",
|
||||
"Open in app": "Abrir en la app",
|
||||
"Shared with you": "Compartido contigo",
|
||||
"Downloading… {{percent}}%": "Descargando… {{percent}}%",
|
||||
"Import progress": "Progreso de la importación",
|
||||
"Share reading progress": "Compartir el progreso de lectura",
|
||||
"Includes your reading progress": "Incluye tu progreso de lectura",
|
||||
"Synced {{time}}": "Sincronizado {{time}}",
|
||||
"Sync Info": "Información de sincronización",
|
||||
"Last Synced": "Última sincronización",
|
||||
"DICT": "DICT",
|
||||
"Slob": "Slob",
|
||||
"DICT bundles need a .index file and a .dict.dz file.": "Los paquetes DICT necesitan un archivo .index y un archivo .dict.dz.",
|
||||
"Slob bundles need a .slob file.": "Los paquetes Slob necesitan un archivo .slob.",
|
||||
"URL template must start with http(s):// and contain %WORD%.": "La plantilla de URL debe empezar con http(s):// y contener %WORD%.",
|
||||
"Web": "Web",
|
||||
"Add Web Search": "Añadir búsqueda web",
|
||||
"Edit Web Search": "Editar búsqueda web",
|
||||
"e.g. Google": "p. ej. Google",
|
||||
"URL Template": "Plantilla de URL",
|
||||
"Use %WORD% where the looked-up word should appear.": "Use %WORD% donde debe aparecer la palabra buscada.",
|
||||
"Open the search result in your browser:": "Abre el resultado de búsqueda en tu navegador:",
|
||||
"Open in {{name}}": "Abrir en {{name}}",
|
||||
"Book Fingerprint": "Huella del libro",
|
||||
"Search the web": "Buscar en la web",
|
||||
"App deeplink (readest://)": "Enlace profundo de la app (readest://)",
|
||||
"Universal web link (https://)": "Enlace web universal (https://)",
|
||||
"Name cannot be empty.": "El nombre no puede estar vacío.",
|
||||
"Replaced {{count}} existing dictionary_one": "Se reemplazó {{count}} diccionario existente",
|
||||
"Replaced {{count}} existing dictionary_many": "Se reemplazaron {{count}} diccionarios existentes",
|
||||
"Replaced {{count}} existing dictionary_other": "Se reemplazaron {{count}} diccionarios existentes",
|
||||
"Cancel Edit": "Cancelar edición",
|
||||
"Edit Dictionary": "Editar diccionario",
|
||||
"MDict bundles use .mdx files; companion .mdd and .css files are optional.": "Los paquetes MDict usan archivos .mdx; los archivos .mdd y .css complementarios son opcionales.",
|
||||
"Dictionary name": "Nombre del diccionario",
|
||||
"This audio can't play here — the dictionary uses an outdated format. Try one with Opus, MP3, or WAV audio.": "Este audio no se puede reproducir aquí — el diccionario usa un formato obsoleto. Prueba uno con audio Opus, MP3 o WAV.",
|
||||
"File uploaded: {{title}}": "Archivo subido: {{title}}",
|
||||
"File downloaded: {{title}}": "Archivo descargado: {{title}}",
|
||||
"Deleted cloud copy of the file: {{title}}": "Copia en la nube del archivo eliminada: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Error al subir archivo: {{title}}",
|
||||
"Failed to download file: {{title}}": "Error al descargar archivo: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Error al eliminar la copia en la nube del archivo: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% usado",
|
||||
"Set PIN…": "Establecer PIN…",
|
||||
"Require a 4-digit PIN to open Readest": "Requerir un PIN de 4 dígitos para abrir Readest",
|
||||
"Change PIN…": "Cambiar PIN…",
|
||||
"Disable PIN…": "Desactivar PIN…",
|
||||
"Sync passphrase ready": "Frase de contraseña de sincronización lista",
|
||||
"This permanently deletes the encrypted credentials we sync (e.g., OPDS catalog passwords) on every device. Local copies are preserved. You will need to re-enter the sync passphrase or set a new one. Continue?": "Esto elimina permanentemente las credenciales cifradas que sincronizamos (p. ej., contraseñas del catálogo OPDS) en cada dispositivo. Se conservan las copias locales. Tendrás que volver a introducir la frase de contraseña de sincronización o establecer una nueva. ¿Continuar?",
|
||||
"Sync passphrase forgotten — all encrypted fields cleared": "Frase de contraseña de sincronización olvidada — todos los campos cifrados se han borrado",
|
||||
"Sync passphrase": "Frase de contraseña de sincronización",
|
||||
"Set passphrase": "Establecer frase de contraseña",
|
||||
"Forgot passphrase": "Olvidé la frase de contraseña",
|
||||
"Incorrect PIN": "PIN incorrecto",
|
||||
"App locked": "Aplicación bloqueada",
|
||||
"Enter your PIN": "Ingresa tu PIN",
|
||||
"Readest is locked. Enter your 4-digit PIN to continue.": "Readest está bloqueado. Ingresa tu PIN de 4 dígitos para continuar.",
|
||||
"PIN code": "Código PIN",
|
||||
"Forgetting your PIN locks you out of this device. You'll need to clear the app's data to reset it.": "Si olvidas tu PIN, te bloquearás en este dispositivo. Tendrás que borrar los datos de la aplicación para restablecerlo.",
|
||||
"Passphrase must be at least 8 characters": "La frase de contraseña debe tener al menos 8 caracteres",
|
||||
"Passphrases do not match": "Las frases de contraseña no coinciden",
|
||||
"Set sync passphrase": "Establecer frase de contraseña de sincronización",
|
||||
"Enter sync passphrase": "Introduce la frase de contraseña de sincronización",
|
||||
"A sync passphrase encrypts your sensitive fields (like OPDS catalog credentials) before they sync. We never see this passphrase. Pick something memorable — there is no recovery without it.": "Una frase de contraseña de sincronización cifra tus campos sensibles (como las credenciales del catálogo OPDS) antes de sincronizarlos. Nunca vemos esta frase de contraseña. Elige algo memorable — no hay recuperación sin ella.",
|
||||
"Enter the sync passphrase you set on another device to decrypt your synced credentials.": "Introduce la frase de contraseña de sincronización que estableciste en otro dispositivo para descifrar tus credenciales sincronizadas.",
|
||||
"Confirm passphrase": "Confirmar frase de contraseña",
|
||||
"Unlock": "Desbloquear",
|
||||
"Set PIN": "Establecer PIN",
|
||||
"Change PIN": "Cambiar PIN",
|
||||
"Disable PIN": "Desactivar PIN",
|
||||
"Enter your current PIN, then choose a new 4-digit PIN.": "Ingresa tu PIN actual y, a continuación, elige un nuevo PIN de 4 dígitos.",
|
||||
"Enter your current PIN to disable the app lock.": "Ingresa tu PIN actual para desactivar el bloqueo de la aplicación.",
|
||||
"PIN must be {{length}} digits": "El PIN debe tener {{length}} dígitos",
|
||||
"PINs do not match": "Los PIN no coinciden",
|
||||
"Current PIN": "PIN actual",
|
||||
"New PIN": "Nuevo PIN",
|
||||
"Confirm new PIN": "Confirmar nuevo PIN",
|
||||
"Manage Sync": "Gestionar sincronización",
|
||||
"Imported book files and library metadata": "Archivos de libros importados y metadatos de la biblioteca",
|
||||
"Last-read position, bookmarks, and per-book preferences": "Última posición de lectura, marcadores y preferencias por libro",
|
||||
"Highlights and notes": "Resaltados y notas",
|
||||
"Fonts": "Fuentes",
|
||||
"Custom font files": "Archivos de fuentes personalizadas",
|
||||
"Backgrounds": "Fondos",
|
||||
"Custom background textures": "Texturas de fondo personalizadas",
|
||||
"OPDS catalogs": "Catálogos OPDS",
|
||||
"Saved catalog URLs and (encrypted) credentials": "URLs de catálogos guardadas y credenciales (cifradas)",
|
||||
"Wrong sync passphrase — synced credentials could not be decrypted": "Frase de contraseña de sincronización incorrecta — no se pudieron descifrar las credenciales sincronizadas",
|
||||
"Sync passphrase data on the server was reset. Re-encrypting your credentials under the new passphrase…": "Se restablecieron los datos de la frase de contraseña de sincronización en el servidor. Volviendo a cifrar tus credenciales con la nueva frase…",
|
||||
"Failed to decrypt synced credentials": "No se pudieron descifrar las credenciales sincronizadas",
|
||||
"Imported dictionary bundles and settings": "Paquetes de diccionarios importados y ajustes",
|
||||
"Choose what syncs across your devices. Disabling a category stops this device from sending or receiving rows of that kind. Anything already on the server is left alone, re-enabling resumes from where you stopped.": "Elige qué se sincroniza entre tus dispositivos. Al desactivar una categoría, este dispositivo deja de enviar o recibir entradas de ese tipo. Lo que ya está en el servidor se mantiene intacto, y al reactivarla se reanuda desde donde lo dejaste.",
|
||||
"Data Sync": "Sincronización de datos",
|
||||
"Manage Fonts": "Administrar fuentes",
|
||||
"App settings": "Ajustes de la aplicación",
|
||||
"Theme, highlight colours, integrations (KOSync, Readwise, Hardcover), and dictionary order": "Tema, colores de resaltado, integraciones (KOSync, Readwise, Hardcover) y orden de los diccionarios",
|
||||
"Required while Dictionaries sync is enabled": "Necesario mientras la sincronización de Diccionarios esté activada",
|
||||
"Resets in {{hours}} hr {{minutes}} min": "Se restablece en {{hours}} h {{minutes}} min",
|
||||
"Pick a 4-digit PIN. You will need to enter it every time you open Readest. There is no way to recover a forgotten PIN. Clearing the app data is the only way to reset it.": "Elige un PIN de 4 dígitos. Tendrás que introducirlo cada vez que abras Readest. No hay forma de recuperar un PIN olvidado. Borrar los datos de la aplicación es la única forma de restablecerlo.",
|
||||
"Credentials": "Credenciales",
|
||||
"Tokens, usernames, and passwords for OPDS, KOReader, Hardcover, and Readwise. When disabled, credentials remain on this device only and are never uploaded.": "Tokens, nombres de usuario y contraseñas de OPDS, KOReader, Hardcover y Readwise. Si está desactivado, las credenciales permanecen solo en este dispositivo y nunca se cargan.",
|
||||
"Sensitive synced fields are encrypted before upload. Set a passphrase now or later when encryption is needed.": "Los campos sincronizados sensibles se cifran antes de subirlos. Establece una frase de contraseña ahora o más tarde, cuando se necesite el cifrado.",
|
||||
"Saved to this account. You will be prompted for it when decrypting credentials.": "Guardada en esta cuenta. Se te pedirá cuando se necesite descifrar credenciales.",
|
||||
"Reading progress on this device differs from \"{{deviceName}}\".": "El progreso de lectura en este dispositivo difiere del de «{{deviceName}}».",
|
||||
"This device": "Este dispositivo",
|
||||
"Red": "Rojo",
|
||||
"Yellow": "Amarillo",
|
||||
"Green": "Verde",
|
||||
"Blue": "Azul",
|
||||
"Violet": "Violeta",
|
||||
"Edit color": "Editar color",
|
||||
"Add custom color": "Añadir color personalizado",
|
||||
"Add label": "Añadir etiqueta",
|
||||
"Choose color": "Elegir color",
|
||||
"Connected to Hardcover. Last synced {{time}}.": "Conectado a Hardcover. Última sincronización {{time}}.",
|
||||
"Integrations": "Integraciones",
|
||||
"Hardcover": "Hardcover",
|
||||
"Connected to Readwise. Last synced {{time}}.": "Conectado a Readwise. Última sincronización {{time}}.",
|
||||
"Readwise": "Readwise",
|
||||
"Browse and download books from online catalogs.": "Explora y descarga libros desde catálogos en línea.",
|
||||
"Connected as {{user}}": "Conectado como {{user}}",
|
||||
"Not connected": "No conectado",
|
||||
"{{count}} catalog_one": "{{count}} catálogo",
|
||||
"{{count}} catalog_many": "{{count}} catálogos",
|
||||
"{{count}} catalog_other": "{{count}} catálogos",
|
||||
"No catalogs": "Sin catálogos",
|
||||
"Connect Readest to external services for sync, highlights, and catalogs.": "Conecta Readest con servicios externos para sincronización, resaltados y catálogos.",
|
||||
"Reading Sync": "Sincronización de lectura",
|
||||
"Content Sources": "Fuentes de contenido",
|
||||
"Scroll tabs": "Desplazar pestañas",
|
||||
"Catalog actions": "Acciones del catálogo",
|
||||
"File Content": "Contenido del archivo",
|
||||
"Start your library": "Empieza tu biblioteca",
|
||||
"Pick a book from your device to add it to your library.": "Elige un libro de tu dispositivo para añadirlo a tu biblioteca.",
|
||||
"Drop a book anywhere on this window, or pick one from your computer.": "Suelta un libro en cualquier parte de esta ventana o elige uno desde tu computadora.",
|
||||
"Sign in to sync your library": "Inicia sesión para sincronizar tu biblioteca"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@
|
||||
"Auto Mode": "Mode automatique",
|
||||
"Behavior": "Comportement",
|
||||
"Book": "Livre",
|
||||
"Book Cover": "Couverture du livre",
|
||||
"Bookmark": "Signet",
|
||||
"Cancel": "Annuler",
|
||||
"Chapter": "Chapitre",
|
||||
@@ -103,11 +102,9 @@
|
||||
"Updated": "Mis à jour",
|
||||
"Version {{version}}": "Version {{version}}",
|
||||
"Vertical Direction": "Direction verticale",
|
||||
"Welcome to your library. You can import your books here and read them anytime.": "Bienvenue dans votre bibliothèque. Vous pouvez importer vos livres ici et les lire à tout moment.",
|
||||
"Wikipedia": "Wikipédia",
|
||||
"Writing Mode": "Mode écriture",
|
||||
"Your Library": "Votre bibliothèque",
|
||||
"TTS not supported for PDF": "La synthèse vocale n'est pas prise en charge pour les fichiers PDF",
|
||||
"Override Book Font": "Remplacer la police du livre",
|
||||
"Apply to All Books": "Appliquer à tous les livres",
|
||||
"Apply to This Book": "Appliquer à ce livre",
|
||||
@@ -173,9 +170,6 @@
|
||||
"Token": "Code de vérification",
|
||||
"Your OTP token": "Votre code OTP",
|
||||
"Verify token": "Vérifier le code",
|
||||
"Sign in with Google": "Se connecter avec Google",
|
||||
"Sign in with Apple": "Se connecter avec Apple",
|
||||
"Sign in with GitHub": "Se connecter avec GitHub",
|
||||
"Account": "Compte",
|
||||
"Failed to delete user. Please try again later.": "Échec de la suppression de l'utilisateur. Veuillez réessayer plus tard.",
|
||||
"Community Support": "Support communautaire",
|
||||
@@ -188,12 +182,11 @@
|
||||
"RTL Direction": "Direction RTL",
|
||||
"Maximum Column Height": "Hauteur maximale de colonne",
|
||||
"Maximum Column Width": "Largeur maximale de colonne",
|
||||
"Continuous Scroll": "Défilement continu",
|
||||
"Fullscreen": "Plein écran",
|
||||
"No supported files found. Supported formats: {{formats}}": "Aucun fichier pris en charge trouvé. Formats pris en charge : {{formats}}",
|
||||
"Drop to Import Books": "Déposez pour importer des livres",
|
||||
"Custom": "Personnalisé",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Le monde entier est un théâtre,\nEt tous les hommes et les femmes ne sont que des acteurs ;\nIls ont leurs sorties et leurs entrées,\nEt un homme, dans sa vie, joue de nombreux rôles,\nSes actes étant sept âges.\n\n— William Shakespeare",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "Le monde entier est un théâtre,\nEt tous les hommes et les femmes ne sont que des acteurs ;\nIls ont leurs sorties et leurs entrées,\nEt un homme, dans sa vie, joue de nombreux rôles,\nSes actes étant sept âges.\n\n— William Shakespeare",
|
||||
"Custom Theme": "Thème personnalisé",
|
||||
"Theme Name": "Nom du thème",
|
||||
"Text Color": "Couleur du texte",
|
||||
@@ -220,9 +213,9 @@
|
||||
"Auto Import on File Open": "Importation automatique à l'ouverture du fichier",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "Aucun chapitre détecté.",
|
||||
"Failed to parse the EPUB file.": "Échec de l'analyse du fichier EPUB.",
|
||||
"This book format is not supported.": "Ce format de livre n'est pas pris en charge.",
|
||||
"No chapters detected": "Aucun chapitre détecté",
|
||||
"Failed to parse the EPUB file": "Échec de l'analyse du fichier EPUB",
|
||||
"This book format is not supported": "Ce format de livre n'est pas pris en charge",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "Impossible de récupérer la traduction. Veuillez d'abord vous connecter et réessayer.",
|
||||
"Group": "Groupe",
|
||||
"Always on Top": "Toujours au-dessus",
|
||||
@@ -259,8 +252,6 @@
|
||||
"(from 'As You Like It', Act II)": "(de 'Comme il vous plaira', Acte II)",
|
||||
"Link Color": "Couleur du lien",
|
||||
"Volume Keys for Page Flip": "Boutons volume pour tourner la page",
|
||||
"Clicks for Page Flip": "Taps pour tourner la page",
|
||||
"Swap Clicks Area": "Inverser zones de tap",
|
||||
"Screen": "Écran",
|
||||
"Orientation": "Orientation",
|
||||
"Portrait": "Portrait",
|
||||
@@ -297,7 +288,6 @@
|
||||
"Disable Translation": " désactiver la traduction",
|
||||
"Scroll": "Défilement",
|
||||
"Overlap Pixels": "Pixels de chevauchement",
|
||||
"Click": "Cliquer",
|
||||
"System Language": "Langue du système",
|
||||
"Security": "Sécurité",
|
||||
"Allow JavaScript": "Autoriser JavaScript",
|
||||
@@ -309,7 +299,6 @@
|
||||
"No notes match your search": "Aucune note trouvée",
|
||||
"Search notes and excerpts...": "Rechercher des notes...",
|
||||
"Sign in to Sync": "Connectez-vous pour synchroniser",
|
||||
"Synced at {{time}}": "Synchronisé à {{time}}",
|
||||
"Never synced": "Jamais synchronisé",
|
||||
"Show Remaining Time": "Afficher le temps restant",
|
||||
"{{time}} min left in chapter": "{{time}} min restant dans le chapitre",
|
||||
@@ -344,12 +333,12 @@
|
||||
"Fit": "Adapter",
|
||||
"Reset {{settings}}": "Réinitialiser {{settings}}",
|
||||
"Reset Settings": "Réinitialiser les paramètres",
|
||||
"{{count}} pages left in chapter_one": "{{count}} page restant dans le chapitre",
|
||||
"{{count}} pages left in chapter_many": "{{count}} pages restantes dans le chapitre",
|
||||
"{{count}} pages left in chapter_other": "{{count}} pages restantes dans le chapitre",
|
||||
"{{count}} pages left in chapter_one": "<0>{{count}}</0><1> page restant dans le chapitre</1>",
|
||||
"{{count}} pages left in chapter_many": "<0>{{count}}</0><1> pages restantes dans le chapitre</1>",
|
||||
"{{count}} pages left in chapter_other": "<0>{{count}}</0><1> pages restantes dans le chapitre</1>",
|
||||
"Show Remaining Pages": "Voir restantes",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "Gérer l’abonnement",
|
||||
"Coming Soon": "Bientôt disponible",
|
||||
@@ -369,7 +358,6 @@
|
||||
"Email:": "E-mail :",
|
||||
"Plan:": "Abonnement :",
|
||||
"Amount:": "Montant :",
|
||||
"Subscription ID:": "ID d’abonnement :",
|
||||
"Go to Library": "Aller à la bibliothèque",
|
||||
"Need help? Contact our support team at support@readest.com": "Besoin d’aide ? Contactez notre équipe à support@readest.com",
|
||||
"Free Plan": "Abonnement gratuit",
|
||||
@@ -475,14 +463,12 @@
|
||||
"Remove from Cloud Only": "Supprimer uniquement du Cloud",
|
||||
"Remove from Device Only": "Supprimer uniquement de l'appareil",
|
||||
"Disconnected": "Déconnecté",
|
||||
"KOReader Sync Settings": "Paramètres de synchronisation KOReader",
|
||||
"Sync Strategy": "Stratégie de synchronisation",
|
||||
"Ask on conflict": "Demander en cas de conflit",
|
||||
"Always use latest": "Toujours utiliser la dernière version",
|
||||
"Send changes only": "Envoyer uniquement les modifications",
|
||||
"Receive changes only": "Recevoir uniquement les modifications",
|
||||
"Checksum Method": "Méthode de somme de contrôle",
|
||||
"File Content (recommended)": "Contenu du fichier (recommandé)",
|
||||
"File Name": "Nom du fichier",
|
||||
"Device Name": "Nom de l'appareil",
|
||||
"Connect to your KOReader Sync server.": "Connectez-vous à votre serveur de synchronisation KOReader.",
|
||||
@@ -493,10 +479,7 @@
|
||||
"Connect": "Connecter",
|
||||
"KOReader Sync": "Synchronisation KOReader",
|
||||
"Sync Conflict": "Conflit de synchronisation",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "Synchroniser la progression de lecture depuis \"{{deviceName}}\"?",
|
||||
"another device": "un autre appareil",
|
||||
"Local Progress": "Progression locale",
|
||||
"Remote Progress": "Progression distante",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "Page {{page}} sur {{total}} ({{percentage}}%)",
|
||||
"Current position": "Position actuelle",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "Environ page {{page}} sur {{total}} ({{percentage}}%)",
|
||||
@@ -610,5 +593,849 @@
|
||||
"Opacity": "Opacité",
|
||||
"Size": "Taille",
|
||||
"Cover": "Couverture",
|
||||
"Contain": "Contenir"
|
||||
"Contain": "Contenir",
|
||||
"{{number}} pages left in chapter": "<0>{{number}}</0><1> pages restantes dans le chapitre</1>",
|
||||
"Device": "Appareil",
|
||||
"E-Ink Mode": "Mode E-Ink",
|
||||
"Highlight Colors": "Couleurs de Surlignage",
|
||||
"Pagination": "Pagination",
|
||||
"Disable Double Tap": "Désactiver le Double Tap",
|
||||
"Tap to Paginate": "Tapoter pour Paginater",
|
||||
"Click to Paginate": "Cliquer pour Paginater",
|
||||
"Tap Both Sides": "Tapoter des Deux Côtés",
|
||||
"Click Both Sides": "Cliquer des Deux Côtés",
|
||||
"Swap Tap Sides": "Échanger les Côtés de Tap",
|
||||
"Swap Click Sides": "Échanger les Côtés de Clic",
|
||||
"Source and Translated": "Source et Traduit",
|
||||
"Translated Only": "Traduit Seulement",
|
||||
"Source Only": "Source Seulement",
|
||||
"TTS Text": "TTS Texte",
|
||||
"The book file is corrupted": "Le fichier du livre est corrompu",
|
||||
"The book file is empty": "Le fichier du livre est vide",
|
||||
"Failed to open the book file": "Échec de l'ouverture du fichier du livre",
|
||||
"On-Demand Purchase": "Achat à la Demande",
|
||||
"Full Customization": "Personnalisation Complète",
|
||||
"Lifetime Plan": "Plan de Vie",
|
||||
"One-Time Payment": "Paiement Unique",
|
||||
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "Effectuez un paiement unique pour profiter d'un accès à vie à des fonctionnalités spécifiques sur tous les appareils. Achetez des fonctionnalités ou des services spécifiques uniquement lorsque vous en avez besoin.",
|
||||
"Expand Cloud Sync Storage": "Étendre le Stockage de Synchronisation Cloud",
|
||||
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "Étendez votre stockage cloud pour toujours avec un achat unique. Chaque achat supplémentaire ajoute plus d'espace.",
|
||||
"Unlock All Customization Options": "Débloquer Toutes les Options de Personnalisation",
|
||||
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Débloquer des thèmes supplémentaires, des polices, des options de mise en page et des fonctions de lecture à haute voix, des traducteurs, des services de stockage cloud.",
|
||||
"Purchase Successful!": "Achat Réussi!",
|
||||
"lifetime": "à vie",
|
||||
"Thank you for your purchase! Your payment has been processed successfully.": "Merci pour votre achat ! Votre paiement a été traité avec succès.",
|
||||
"Order ID:": "ID de commande :",
|
||||
"TTS Highlighting": "Surbrillance TTS",
|
||||
"Style": "Style",
|
||||
"Underline": "Souligner",
|
||||
"Strikethrough": "Barré",
|
||||
"Squiggly": "Ondulé",
|
||||
"Outline": "Contour",
|
||||
"Quick Colors": "Couleurs Rapides",
|
||||
"Highlighter": "Surligneur",
|
||||
"Save Book Cover": "Enregistrer la Couverture du Livre",
|
||||
"Auto-save last book cover": "Enregistrer automatiquement la dernière couverture du livre",
|
||||
"Back": "Retour",
|
||||
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "E-mail de confirmation envoyé ! Veuillez vérifier vos anciennes et nouvelles adresses e-mail pour confirmer le changement.",
|
||||
"Failed to update email": "Échec de la mise à jour de l’e-mail",
|
||||
"New Email": "Nouvel e-mail",
|
||||
"Your new email": "Votre nouvel e-mail",
|
||||
"Updating email ...": "Mise à jour de l’e-mail ...",
|
||||
"Update email": "Mettre à jour l’e-mail",
|
||||
"Current email": "E-mail actuel",
|
||||
"Update Email": "Mettre à jour l’e-mail",
|
||||
"All": "Tous",
|
||||
"Unable to open book": "Impossible d'ouvrir le livre",
|
||||
"Punctuation": "Ponctuation",
|
||||
"Replace Quotation Marks": "Remplacer les guillemets",
|
||||
"Enabled only in vertical layout.": "Activé uniquement en disposition verticale.",
|
||||
"No Conversion": "Pas de conversion",
|
||||
"Simplified to Traditional": "Simp. → Trad.",
|
||||
"Traditional to Simplified": "Trad. → Simp.",
|
||||
"Simplified to Traditional (Taiwan)": "Simp. → Trad. (Taïwan)",
|
||||
"Simplified to Traditional (Hong Kong)": "Simp. → Trad. (Hong Kong)",
|
||||
"Simplified to Traditional (Taiwan), with phrases": "Simp. → Trad. (Taïwan • phrases)",
|
||||
"Traditional (Taiwan) to Simplified": "Trad. (Taïwan) → Simp.",
|
||||
"Traditional (Hong Kong) to Simplified": "Trad. (Hong Kong) → Simp.",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Trad. (Taïwan • phrases) → Simp.",
|
||||
"Convert Simplified and Traditional Chinese": "Convertir chinois simp./trad.",
|
||||
"Convert Mode": "Mode de conversion",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Échec de l'enregistrement automatique de la couverture du livre pour l'écran de verrouillage : {{error}}",
|
||||
"Download from Cloud": "Télécharger depuis le Cloud",
|
||||
"Upload to Cloud": "Téléverser vers le Cloud",
|
||||
"Clear Custom Fonts": "Effacer les Polices Personnalisées",
|
||||
"Columns": "Colonnes",
|
||||
"OPDS Catalogs": "Catalogues OPDS",
|
||||
"Adding LAN addresses is not supported in the web app version.": "L’ajout d’adresses LAN n’est pas pris en charge dans la version web.",
|
||||
"Invalid OPDS catalog. Please check the URL.": "Catalogue OPDS invalide. Veuillez vérifier l’URL.",
|
||||
"Browse and download books from online catalogs": "Parcourez et téléchargez des livres depuis des catalogues en ligne",
|
||||
"My Catalogs": "Mes catalogues",
|
||||
"Add Catalog": "Ajouter un catalogue",
|
||||
"No catalogs yet": "Aucun catalogue pour le moment",
|
||||
"Add your first OPDS catalog to start browsing books": "Ajoutez votre premier catalogue OPDS pour commencer à parcourir des livres.",
|
||||
"Add Your First Catalog": "Ajouter votre premier catalogue",
|
||||
"Browse": "Parcourir",
|
||||
"Popular Catalogs": "Catalogues populaires",
|
||||
"Add": "Ajouter",
|
||||
"Add OPDS Catalog": "Ajouter un catalogue OPDS",
|
||||
"Catalog Name": "Nom du catalogue",
|
||||
"My Calibre Library": "Ma bibliothèque Calibre",
|
||||
"OPDS URL": "URL OPDS",
|
||||
"Username (optional)": "Nom d’utilisateur (optionnel)",
|
||||
"Password (optional)": "Mot de passe (optionnel)",
|
||||
"Description (optional)": "Description (optionnelle)",
|
||||
"A brief description of this catalog": "Brève description de ce catalogue",
|
||||
"Validating...": "Validation...",
|
||||
"View All": "Tout afficher",
|
||||
"Forward": "Suivant",
|
||||
"Home": "Accueil",
|
||||
"{{count}} items_one": "{{count}} élément",
|
||||
"{{count}} items_many": "{{count}} éléments",
|
||||
"{{count}} items_other": "{{count}} éléments",
|
||||
"Download completed": "Téléchargement terminé",
|
||||
"Download failed": "Échec du téléchargement",
|
||||
"Open Access": "Accès libre",
|
||||
"Borrow": "Emprunter",
|
||||
"Buy": "Acheter",
|
||||
"Subscribe": "S’abonner",
|
||||
"Sample": "Extrait",
|
||||
"Download": "Télécharger",
|
||||
"Open & Read": "Ouvrir et lire",
|
||||
"Tags": "Tags",
|
||||
"Tag": "Tag",
|
||||
"First": "Premier",
|
||||
"Previous": "Précédent",
|
||||
"Next": "Suivant",
|
||||
"Last": "Dernier",
|
||||
"Cannot Load Page": "Impossible de charger la page",
|
||||
"An error occurred": "Une erreur est survenue",
|
||||
"Online Library": "Bibliothèque en ligne",
|
||||
"URL must start with http:// or https://": "URL doit commencer par http:// ou https://",
|
||||
"Title, Author, Tag, etc...": "Titre, Auteur, Tag, etc...",
|
||||
"Query": "Requête",
|
||||
"Subject": "Sujet",
|
||||
"Enter {{terms}}": "Entrez {{terms}}",
|
||||
"No search results found": "Aucun résultat trouvé",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "Échec du chargement du flux OPDS : {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "Rechercher dans {{title}}",
|
||||
"Manage Storage": "Gérer le stockage",
|
||||
"Failed to load files": "Échec du chargement des fichiers",
|
||||
"Deleted {{count}} file(s)_one": "{{count}} fichier supprimé",
|
||||
"Deleted {{count}} file(s)_many": "{{count}} fichiers supprimés",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} fichiers supprimés",
|
||||
"Failed to delete {{count}} file(s)_one": "Échec de la suppression de {{count}} fichier",
|
||||
"Failed to delete {{count}} file(s)_many": "Échec de la suppression de {{count}} fichiers",
|
||||
"Failed to delete {{count}} file(s)_other": "Échec de la suppression de {{count}} fichiers",
|
||||
"Failed to delete files": "Échec de la suppression des fichiers",
|
||||
"Total Files": "Nombre total de fichiers",
|
||||
"Total Size": "Taille totale",
|
||||
"Quota": "Quota",
|
||||
"Used": "Utilisé",
|
||||
"Files": "Fichiers",
|
||||
"Search files...": "Rechercher des fichiers...",
|
||||
"Newest First": "Les plus récents",
|
||||
"Oldest First": "Les plus anciens",
|
||||
"Largest First": "Les plus volumineux",
|
||||
"Smallest First": "Les moins volumineux",
|
||||
"Name A-Z": "Nom A-Z",
|
||||
"Name Z-A": "Nom Z-A",
|
||||
"{{count}} selected_one": "{{count}} sélectionné",
|
||||
"{{count}} selected_many": "{{count}} sélectionnés",
|
||||
"{{count}} selected_other": "{{count}} sélectionnés",
|
||||
"Delete Selected": "Supprimer la sélection",
|
||||
"Created": "Créé",
|
||||
"No files found": "Aucun fichier trouvé",
|
||||
"No files uploaded yet": "Aucun fichier téléchargé pour le moment",
|
||||
"files": "fichiers",
|
||||
"Page {{current}} of {{total}}": "Page {{current}} sur {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Voulez-vous vraiment supprimer {{count}} fichier sélectionné ?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Voulez-vous vraiment supprimer {{count}} fichiers sélectionnés ?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Voulez-vous vraiment supprimer {{count}} fichiers sélectionnés ?",
|
||||
"Cloud Storage Usage": "Utilisation du stockage cloud",
|
||||
"Rename Group": "Renommer le groupe",
|
||||
"From Directory": "Depuis le répertoire",
|
||||
"Successfully imported {{count}} book(s)_one": "Importation réussie de 1 livre",
|
||||
"Successfully imported {{count}} book(s)_many": "Importation réussie de {{count}} livres",
|
||||
"Successfully imported {{count}} book(s)_other": "Importation réussie de {{count}} livres",
|
||||
"Count": "Nombre",
|
||||
"Start Page": "Page de départ",
|
||||
"Search in OPDS Catalog...": "Rechercher dans le catalogue OPDS...",
|
||||
"Please log in to use advanced TTS features": "Veuillez vous connecter pour utiliser les fonctionnalités avancées de TTS",
|
||||
"Word limit of 30 words exceeded.": "La limite de 30 mots a été dépassée.",
|
||||
"Proofread": "Correction",
|
||||
"Current selection": "Sélection actuelle",
|
||||
"All occurrences in this book": "Toutes les occurrences dans ce livre",
|
||||
"All occurrences in your library": "Toutes les occurrences dans votre bibliothèque",
|
||||
"Selected text:": "Texte sélectionné :",
|
||||
"Replace with:": "Remplacer par :",
|
||||
"Enter text...": "Saisir du texte…",
|
||||
"Case sensitive:": "Respecter la casse :",
|
||||
"Scope:": "Portée :",
|
||||
"Selection": "Sélection",
|
||||
"Library": "Bibliothèque",
|
||||
"Yes": "Oui",
|
||||
"No": "Non",
|
||||
"Proofread Replacement Rules": "Règles de remplacement de correction",
|
||||
"Selected Text Rules": "Règles du texte sélectionné",
|
||||
"No selected text replacement rules": "Aucune règle de remplacement pour le texte sélectionné",
|
||||
"Book Specific Rules": "Règles spécifiques au livre",
|
||||
"No book-level replacement rules": "Aucune règle de remplacement au niveau du livre",
|
||||
"Disable Quick Action": "Désactiver l’action rapide",
|
||||
"Enable Quick Action on Selection": "Activer l’action rapide lors de la sélection",
|
||||
"None": "Aucune",
|
||||
"Annotation Tools": "Outils d’annotation",
|
||||
"Enable Quick Actions": "Activer les actions rapides",
|
||||
"Quick Action": "Action rapide",
|
||||
"Copy to Notebook": "Copier dans le carnet",
|
||||
"Copy text after selection": "Copier le texte après la sélection",
|
||||
"Highlight text after selection": "Mettre en surbrillance le texte après la sélection",
|
||||
"Annotate text after selection": "Annoter le texte après la sélection",
|
||||
"Search text after selection": "Rechercher le texte après la sélection",
|
||||
"Look up text in dictionary after selection": "Chercher le texte dans le dictionnaire après la sélection",
|
||||
"Translate text after selection": "Traduire le texte après la sélection",
|
||||
"Read text aloud after selection": "Lire le texte à haute voix après la sélection",
|
||||
"Proofread text after selection": "Relire le texte après la sélection",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} actifs, {{pendingCount}} en attente",
|
||||
"{{failedCount}} failed": "{{failedCount}} échoués",
|
||||
"Waiting...": "En attente...",
|
||||
"Failed": "Échoué",
|
||||
"Completed": "Terminé",
|
||||
"Cancelled": "Annulé",
|
||||
"Retry": "Réessayer",
|
||||
"Active": "Actifs",
|
||||
"Transfer Queue": "File de transfert",
|
||||
"Upload All": "Tout téléverser",
|
||||
"Download All": "Tout télécharger",
|
||||
"Resume Transfers": "Reprendre les transferts",
|
||||
"Pause Transfers": "Suspendre les transferts",
|
||||
"Pending": "En attente",
|
||||
"No transfers": "Aucun transfert",
|
||||
"Retry All": "Tout réessayer",
|
||||
"Clear Completed": "Effacer les terminés",
|
||||
"Clear Failed": "Effacer les échoués",
|
||||
"Upload queued: {{title}}": "Téléversement en file: {{title}}",
|
||||
"Download queued: {{title}}": "Téléchargement en file: {{title}}",
|
||||
"Book not found in library": "Livre non trouvé dans la bibliothèque",
|
||||
"Unknown error": "Erreur inconnue",
|
||||
"Please log in to continue": "Veuillez vous connecter pour continuer",
|
||||
"Cloud File Transfers": "Transferts de fichiers cloud",
|
||||
"Show Search Results": "Afficher les résultats",
|
||||
"Search results for '{{term}}'": "Résultats pour « {{term}} »",
|
||||
"Close Search": "Fermer la recherche",
|
||||
"Previous Result": "Résultat précédent",
|
||||
"Next Result": "Résultat suivant",
|
||||
"Bookmarks": "Signets",
|
||||
"Annotations": "Annotations",
|
||||
"Show Results": "Afficher les résultats",
|
||||
"Clear search": "Effacer la recherche",
|
||||
"Clear search history": "Effacer l'historique de recherche",
|
||||
"Tap to Toggle Footer": "Appuyez pour afficher/masquer le pied de page",
|
||||
"Show Current Time": "Afficher l'heure actuelle",
|
||||
"Use 24 Hour Clock": "Utiliser le format 24 heures",
|
||||
"Show Current Battery Status": "Afficher l'état actuel de la batterie",
|
||||
"Exported successfully": "Exporté avec succès",
|
||||
"Book exported successfully.": "Livre exporté avec succès.",
|
||||
"Failed to export the book.": "Échec de l'exportation du livre.",
|
||||
"Export Book": "Exporter le livre",
|
||||
"Whole word:": "Mot entier :",
|
||||
"Date Published": "Date de publication",
|
||||
"Only for TTS:": "Uniquement pour TTS :",
|
||||
"Uploaded": "Téléversé",
|
||||
"Downloaded": "Téléchargé",
|
||||
"Deleted": "Supprimé",
|
||||
"Note:": "Note :",
|
||||
"Time:": "Heure :",
|
||||
"Format Options": "Options de format",
|
||||
"Export Date": "Date d'export",
|
||||
"Chapter Titles": "Titres de chapitres",
|
||||
"Chapter Separator": "Séparateur de chapitres",
|
||||
"Highlights": "Surlignages",
|
||||
"Note Date": "Date de note",
|
||||
"Advanced": "Avancé",
|
||||
"Hide": "Masquer",
|
||||
"Show": "Afficher",
|
||||
"Use Custom Template": "Utiliser un modèle personnalisé",
|
||||
"Export Template": "Modèle d'export",
|
||||
"Template Syntax:": "Syntaxe du modèle :",
|
||||
"Insert value": "Insérer une valeur",
|
||||
"Format date (locale)": "Formater la date (locale)",
|
||||
"Format date (custom)": "Formater la date (personnalisé)",
|
||||
"Conditional": "Conditionnel",
|
||||
"Loop": "Boucle",
|
||||
"Available Variables:": "Variables disponibles :",
|
||||
"Book title": "Titre du livre",
|
||||
"Book author": "Auteur du livre",
|
||||
"Export date": "Date d'export",
|
||||
"Array of chapters": "Liste de chapitres",
|
||||
"Chapter title": "Titre du chapitre",
|
||||
"Array of annotations": "Liste d'annotations",
|
||||
"Highlighted text": "Texte surligné",
|
||||
"Annotation note": "Note d'annotation",
|
||||
"Date Format Tokens:": "Jetons de format de date :",
|
||||
"Year (4 digits)": "Année (4 chiffres)",
|
||||
"Month (01-12)": "Mois (01-12)",
|
||||
"Day (01-31)": "Jour (01-31)",
|
||||
"Hour (00-23)": "Heure (00-23)",
|
||||
"Minute (00-59)": "Minute (00-59)",
|
||||
"Second (00-59)": "Seconde (00-59)",
|
||||
"Show Source": "Afficher la source",
|
||||
"No content to preview": "Aucun contenu à prévisualiser",
|
||||
"Export": "Exporter",
|
||||
"Set Timeout": "Définir le délai",
|
||||
"Select Voice": "Sélectionner la voix",
|
||||
"Toggle Sticky Bottom TTS Bar": "Basculer la barre TTS fixée",
|
||||
"Display what I'm reading on Discord": "Afficher ce que je lis sur Discord",
|
||||
"Show on Discord": "Afficher sur Discord",
|
||||
"Instant {{action}}": "{{action}} instantané",
|
||||
"Instant {{action}} Disabled": "{{action}} instantané désactivé",
|
||||
"Annotation": "Annotation",
|
||||
"Reset Template": "Réinitialiser le modèle",
|
||||
"Annotation style": "Style d'annotation",
|
||||
"Annotation color": "Couleur d'annotation",
|
||||
"Annotation time": "Heure d'annotation",
|
||||
"AI": "IA",
|
||||
"Are you sure you want to re-index this book?": "Voulez-vous vraiment réindexer ce livre ?",
|
||||
"Enable AI in Settings": "Activer l'IA dans les paramètres",
|
||||
"Index This Book": "Indexer ce livre",
|
||||
"Enable AI search and chat for this book": "Activer la recherche IA et le chat pour ce livre",
|
||||
"Start Indexing": "Démarrer l'indexation",
|
||||
"Indexing book...": "Indexation du livre...",
|
||||
"Preparing...": "Préparation...",
|
||||
"Delete this conversation?": "Supprimer cette conversation ?",
|
||||
"No conversations yet": "Pas encore de conversations",
|
||||
"Start a new chat to ask questions about this book": "Démarrez une nouvelle conversation pour poser des questions sur ce livre",
|
||||
"Rename": "Renommer",
|
||||
"New Chat": "Nouvelle conversation",
|
||||
"Chat": "Chat",
|
||||
"Please enter a model ID": "Veuillez entrer un ID de modèle",
|
||||
"Model not available or invalid": "Modèle non disponible ou invalide",
|
||||
"Failed to validate model": "Échec de la validation du modèle",
|
||||
"Couldn't connect to Ollama. Is it running?": "Impossible de se connecter à Ollama. Est-il en cours d'exécution ?",
|
||||
"Invalid API key or connection failed": "Clé API invalide ou échec de connexion",
|
||||
"Connection failed": "Échec de connexion",
|
||||
"AI Assistant": "Assistant IA",
|
||||
"Enable AI Assistant": "Activer l'assistant IA",
|
||||
"Provider": "Fournisseur",
|
||||
"Ollama (Local)": "Ollama (Local)",
|
||||
"AI Gateway (Cloud)": "Passerelle IA (Cloud)",
|
||||
"Ollama Configuration": "Configuration Ollama",
|
||||
"Refresh Models": "Actualiser les modèles",
|
||||
"AI Model": "Modèle IA",
|
||||
"No models detected": "Aucun modèle détecté",
|
||||
"AI Gateway Configuration": "Configuration de la passerelle IA",
|
||||
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Choisissez parmi une sélection de modèles IA de haute qualité et économiques. Vous pouvez également utiliser votre propre modèle en sélectionnant \"Modèle personnalisé\" ci-dessous.",
|
||||
"API Key": "Clé API",
|
||||
"Get Key": "Obtenir la clé",
|
||||
"Model": "Modèle",
|
||||
"Custom Model...": "Modèle personnalisé...",
|
||||
"Custom Model ID": "ID du modèle personnalisé",
|
||||
"Validate": "Valider",
|
||||
"Model available": "Modèle disponible",
|
||||
"Connection": "Connexion",
|
||||
"Test Connection": "Tester la connexion",
|
||||
"Connected": "Connecté",
|
||||
"Custom Colors": "Couleurs personnalisées",
|
||||
"Color E-Ink Mode": "Mode E-Ink couleur",
|
||||
"Reading Ruler": "Règle de lecture",
|
||||
"Enable Reading Ruler": "Activer la règle de lecture",
|
||||
"Lines to Highlight": "Lignes à mettre en évidence",
|
||||
"Ruler Color": "Couleur de la règle",
|
||||
"Command Palette": "Palette de commandes",
|
||||
"Search settings and actions...": "Rechercher des paramètres et des actions...",
|
||||
"No results found for": "Aucun résultat trouvé pour",
|
||||
"Type to search settings and actions": "Saisissez pour rechercher des paramètres et des actions",
|
||||
"Recent": "Récent",
|
||||
"navigate": "naviguer",
|
||||
"select": "sélectionner",
|
||||
"close": "fermer",
|
||||
"Search Settings": "Rechercher dans les paramètres",
|
||||
"Page Margins": "Marges de page",
|
||||
"AI Provider": "Fournisseur d'IA",
|
||||
"Ollama URL": "URL Ollama",
|
||||
"Ollama Model": "Modèle Ollama",
|
||||
"AI Gateway Model": "Modèle AI Gateway",
|
||||
"Actions": "Actions",
|
||||
"Navigation": "Navigation",
|
||||
"Set status for {{count}} book(s)_one": "Définir le statut pour {{count}} livre",
|
||||
"Set status for {{count}} book(s)_other": "Définir le statut pour {{count}} livres",
|
||||
"Mark as Unread": "Marquer comme non lu",
|
||||
"Mark as Finished": "Marquer comme terminé",
|
||||
"Finished": "Terminé",
|
||||
"Unread": "Non lu",
|
||||
"Clear Status": "Effacer le statut",
|
||||
"Status": "Statut",
|
||||
"Set status for {{count}} book(s)_many": "Définir le statut pour {{count}} livres",
|
||||
"Loading": "Chargement...",
|
||||
"Exit Paragraph Mode": "Quitter le mode paragraphe",
|
||||
"Paragraph Mode": "Mode paragraphe",
|
||||
"Embedding Model": "Modèle d'incorporation",
|
||||
"{{count}} book(s) synced_one": "{{count}} livre synchronisé",
|
||||
"{{count}} book(s) synced_many": "{{count}} livres synchronisés",
|
||||
"{{count}} book(s) synced_other": "{{count}} livres synchronisés",
|
||||
"Unable to start RSVP": "Impossible de démarrer RSVP",
|
||||
"RSVP not supported for PDF": "RSVP non pris en charge pour PDF",
|
||||
"Select Chapter": "Sélectionner le chapitre",
|
||||
"Context": "Contexte",
|
||||
"Ready": "Prêt",
|
||||
"Chapter Progress": "Progression du chapitre",
|
||||
"words": "mots",
|
||||
"{{time}} left": "{{time}} restant",
|
||||
"Reading progress": "Progression de la lecture",
|
||||
"Skip back 15 words": "Reculer de 15 mots",
|
||||
"Back 15 words (Shift+Left)": "Reculer de 15 mots (Shift+Left)",
|
||||
"Pause (Space)": "Pause (Espace)",
|
||||
"Play (Space)": "Lecture (Espace)",
|
||||
"Skip forward 15 words": "Avancer de 15 mots",
|
||||
"Forward 15 words (Shift+Right)": "Avancer de 15 mots (Shift+Right)",
|
||||
"Decrease speed": "Diminuer la vitesse",
|
||||
"Slower (Left/Down)": "Plus lent (Gauche/Bas)",
|
||||
"Increase speed": "Augmenter la vitesse",
|
||||
"Faster (Right/Up)": "Plus rapide (Droite/Haut)",
|
||||
"Start RSVP Reading": "Démarrer la lecture RSVP",
|
||||
"Choose where to start reading": "Choisir où commencer la lecture",
|
||||
"From Chapter Start": "Depuis le début du chapitre",
|
||||
"Start reading from the beginning of the chapter": "Commencer la lecture au début du chapitre",
|
||||
"Resume": "Reprendre",
|
||||
"Continue from where you left off": "Continuer là où vous vous êtes arrêté",
|
||||
"From Current Page": "Depuis la page actuelle",
|
||||
"Start from where you are currently reading": "Commencer là où vous lisez actuellement",
|
||||
"From Selection": "Depuis la sélection",
|
||||
"Speed Reading Mode": "Mode lecture rapide",
|
||||
"Scroll left": "Défiler vers la gauche",
|
||||
"Scroll right": "Défiler vers la droite",
|
||||
"Library Sync Progress": "Progression de la synchronisation de la bibliothèque",
|
||||
"Back to library": "Retour à la bibliothèque",
|
||||
"Group by...": "Grouper par...",
|
||||
"Export as Plain Text": "Exporter en texte brut",
|
||||
"Export as Markdown": "Exporter en Markdown",
|
||||
"Show Page Navigation Buttons": "Boutons de navigation",
|
||||
"Page {{number}}": "Page {{number}}",
|
||||
"highlight": "surlignage",
|
||||
"underline": "soulignage",
|
||||
"squiggly": "ondulé",
|
||||
"red": "rouge",
|
||||
"violet": "violet",
|
||||
"blue": "bleu",
|
||||
"green": "vert",
|
||||
"yellow": "jaune",
|
||||
"Select {{style}} style": "Sélectionner le style {{style}}",
|
||||
"Select {{color}} color": "Sélectionner la couleur {{color}}",
|
||||
"Close Book": "Fermer le livre",
|
||||
"Speed Reading": "Lecture rapide",
|
||||
"Close Speed Reading": "Fermer la lecture rapide",
|
||||
"Authors": "Auteurs",
|
||||
"Books": "Livres",
|
||||
"Groups": "Groupes",
|
||||
"Back to TTS Location": "Retour à l'emplacement TTS",
|
||||
"Metadata": "Métadonnées",
|
||||
"Image viewer": "Visionneuse d'images",
|
||||
"Previous Image": "Image précédente",
|
||||
"Next Image": "Image suivante",
|
||||
"Zoomed": "Zoomé",
|
||||
"Zoom level": "Niveau de zoom",
|
||||
"Table viewer": "Visionneuse de tableaux",
|
||||
"Unable to connect to Readwise. Please check your network connection.": "Impossible de se connecter à Readwise. Veuillez vérifier votre connexion réseau.",
|
||||
"Invalid Readwise access token": "Jeton d'accès Readwise invalide",
|
||||
"Disconnected from Readwise": "Déconnecté de Readwise",
|
||||
"Never": "Jamais",
|
||||
"Sync Enabled": "Synchronisation activée",
|
||||
"Disconnect": "Déconnecter",
|
||||
"Connect your Readwise account to sync highlights.": "Connectez votre compte Readwise pour synchroniser les surlignages.",
|
||||
"Get your access token at": "Obtenez votre jeton d'accès sur",
|
||||
"Access Token": "Jeton d'accès",
|
||||
"Paste your Readwise access token": "Collez votre jeton d'accès Readwise",
|
||||
"Readwise Sync": "Synchronisation Readwise",
|
||||
"Push Highlights": "Pousser les surlignages",
|
||||
"Highlights synced to Readwise": "Surlignages synchronisés avec Readwise",
|
||||
"Readwise sync failed: no internet connection": "Échec de la synchronisation Readwise : pas de connexion internet",
|
||||
"Readwise sync failed: {{error}}": "Échec de la synchronisation Readwise : {{error}}",
|
||||
"System Screen Brightness": "Luminosité de l'écran système",
|
||||
"Page:": "Page :",
|
||||
"Page: {{number}}": "Page : {{number}}",
|
||||
"Annotation page number": "Numéro de page de l'annotation",
|
||||
"Translating...": "Traduction en cours...",
|
||||
"Show Battery Percentage": "Afficher le pourcentage de batterie",
|
||||
"Hide Scrollbar": "Masquer la barre de défilement",
|
||||
"Skip to last reading position": "Aller à la dernière position de lecture",
|
||||
"Show context": "Afficher le contexte",
|
||||
"Hide context": "Masquer le contexte",
|
||||
"Decrease font size": "Réduire la taille de police",
|
||||
"Increase font size": "Augmenter la taille de police",
|
||||
"Focus": "Focus",
|
||||
"Theme color": "Couleur du thème",
|
||||
"Import failed": "Échec de l'importation",
|
||||
"Available Formatters:": "Formateurs disponibles :",
|
||||
"Format date": "Formater la date",
|
||||
"Markdown block quote (> per line)": "Citation Markdown (> par ligne)",
|
||||
"Newlines to <br>": "Retours à la ligne en <br>",
|
||||
"Change case": "Changer la casse",
|
||||
"Trim whitespace": "Supprimer les espaces",
|
||||
"Truncate to n characters": "Tronquer à n caractères",
|
||||
"Replace text": "Remplacer le texte",
|
||||
"Fallback value": "Valeur par défaut",
|
||||
"Get length": "Obtenir la longueur",
|
||||
"First/last element": "Premier/dernier élément",
|
||||
"Join array": "Joindre le tableau",
|
||||
"Backup failed: {{error}}": "Échec de la sauvegarde : {{error}}",
|
||||
"Select Backup": "Sélectionner une sauvegarde",
|
||||
"Restore failed: {{error}}": "Échec de la restauration : {{error}}",
|
||||
"Backup & Restore": "Sauvegarde et restauration",
|
||||
"Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "Créez une sauvegarde de votre bibliothèque ou restaurez à partir d'une sauvegarde précédente. La restauration fusionnera avec votre bibliothèque actuelle.",
|
||||
"Backup Library": "Sauvegarder la bibliothèque",
|
||||
"Restore Library": "Restaurer la bibliothèque",
|
||||
"Creating backup...": "Création de la sauvegarde...",
|
||||
"Restoring library...": "Restauration de la bibliothèque...",
|
||||
"{{current}} of {{total}} items": "{{current}} sur {{total}} éléments",
|
||||
"Backup completed successfully!": "Sauvegarde terminée avec succès !",
|
||||
"Restore completed successfully!": "Restauration terminée avec succès !",
|
||||
"Your library has been saved to the selected location.": "Votre bibliothèque a été enregistrée à l'emplacement sélectionné.",
|
||||
"{{added}} books added, {{updated}} books updated.": "{{added}} livres ajoutés, {{updated}} livres mis à jour.",
|
||||
"Operation failed": "L'opération a échoué",
|
||||
"Loading library...": "Chargement de la bibliothèque...",
|
||||
"{{count}} books refreshed_one": "{{count}} livre rafraîchi",
|
||||
"{{count}} books refreshed_many": "{{count}} livres rafraîchis",
|
||||
"{{count}} books refreshed_other": "{{count}} livres rafraîchis",
|
||||
"Failed to refresh metadata": "Échec du rafraîchissement des métadonnées",
|
||||
"Refresh Metadata": "Rafraîchir les métadonnées",
|
||||
"Keyboard Shortcuts": "Raccourcis clavier",
|
||||
"View all keyboard shortcuts": "Voir tous les raccourcis clavier",
|
||||
"Switch Sidebar Tab": "Changer l'onglet de la barre latérale",
|
||||
"Toggle Notebook": "Afficher/masquer le carnet",
|
||||
"Search in Book": "Rechercher dans le livre",
|
||||
"Toggle Scroll Mode": "Basculer le mode défilement",
|
||||
"Toggle Select Mode": "Basculer le mode sélection",
|
||||
"Toggle Bookmark": "Basculer le signet",
|
||||
"Toggle Text to Speech": "Basculer la synthèse vocale",
|
||||
"Play / Pause TTS": "Lecture / Pause TTS",
|
||||
"Toggle Paragraph Mode": "Basculer le mode paragraphe",
|
||||
"Highlight Selection": "Surligner la sélection",
|
||||
"Underline Selection": "Souligner la sélection",
|
||||
"Annotate Selection": "Annoter la sélection",
|
||||
"Search Selection": "Rechercher la sélection",
|
||||
"Copy Selection": "Copier la sélection",
|
||||
"Translate Selection": "Traduire la sélection",
|
||||
"Dictionary Lookup": "Recherche dans le dictionnaire",
|
||||
"Read Aloud Selection": "Lire la sélection à voix haute",
|
||||
"Proofread Selection": "Relire la sélection",
|
||||
"Open Settings": "Ouvrir les paramètres",
|
||||
"Open Command Palette": "Ouvrir la palette de commandes",
|
||||
"Show Keyboard Shortcuts": "Afficher les raccourcis clavier",
|
||||
"Open Books": "Ouvrir les livres",
|
||||
"Toggle Fullscreen": "Basculer en plein écran",
|
||||
"Close Window": "Fermer la fenêtre",
|
||||
"Quit App": "Quitter l'application",
|
||||
"Go Left / Previous Page": "Aller à gauche / Page précédente",
|
||||
"Go Right / Next Page": "Aller à droite / Page suivante",
|
||||
"Go Up": "Aller en haut",
|
||||
"Go Down": "Aller en bas",
|
||||
"Previous Chapter": "Chapitre précédent",
|
||||
"Next Chapter": "Chapitre suivant",
|
||||
"Scroll Half Page Down": "Défiler d'une demi-page vers le bas",
|
||||
"Scroll Half Page Up": "Défiler d'une demi-page vers le haut",
|
||||
"Save Note": "Enregistrer la note",
|
||||
"Single Section Scroll": "Défilement par section",
|
||||
"General": "Général",
|
||||
"Text to Speech": "Synthèse vocale",
|
||||
"Zoom": "Zoom",
|
||||
"Window": "Fenêtre",
|
||||
"Your Bookshelf": "Votre bibliothèque",
|
||||
"TTS": "Synthèse vocale",
|
||||
"Media Info": "Infos média",
|
||||
"Update Frequency": "Fréquence de mise à jour",
|
||||
"Every Sentence": "Chaque phrase",
|
||||
"Every Paragraph": "Chaque paragraphe",
|
||||
"Every Chapter": "Chaque chapitre",
|
||||
"TTS Media Info Update Frequency": "Fréquence de mise à jour des infos média TTS",
|
||||
"Select reading speed": "Sélectionner la vitesse de lecture",
|
||||
"Drag to seek": "Glisser pour chercher",
|
||||
"Punctuation Delay": "Délai de ponctuation",
|
||||
"Please confirm that this OPDS connection will be proxied through Readest servers on the web app before continuing.": "Veuillez confirmer que cette connexion OPDS sera acheminée via les serveurs Readest sur l'application web avant de continuer.",
|
||||
"Custom Headers (optional)": "En-têtes personnalisés (facultatif)",
|
||||
"Add one header per line using \"Header-Name: value\".": "Ajoutez un en-tête par ligne en utilisant \"Header-Name: value\".",
|
||||
"I understand this OPDS connection will be proxied through Readest servers on the web app. If I do not trust Readest with these credentials or headers, I should use the native app instead.": "Je comprends que cette connexion OPDS sera acheminée via les serveurs Readest sur l'application web. Si je ne fais pas confiance à Readest pour ces identifiants ou en-têtes, je devrais utiliser l'application native.",
|
||||
"Unable to connect to Hardcover. Please check your network connection.": "Impossible de se connecter à Hardcover. Veuillez vérifier votre connexion réseau.",
|
||||
"Invalid Hardcover API token": "Jeton API Hardcover invalide",
|
||||
"Disconnected from Hardcover": "Déconnecté de Hardcover",
|
||||
"Connect your Hardcover account to sync reading progress and notes.": "Connectez votre compte Hardcover pour synchroniser la progression de lecture et les notes.",
|
||||
"Get your API token from hardcover.app → Settings → API.": "Obtenez votre jeton API depuis hardcover.app → Paramètres → API.",
|
||||
"API Token": "Jeton API",
|
||||
"Paste your Hardcover API token": "Collez votre jeton API Hardcover",
|
||||
"Hardcover Sync": "Synchronisation Hardcover",
|
||||
"Push Notes": "Envoyer les notes",
|
||||
"No annotations or excerpts to sync for this book.": "Aucune annotation ni extrait à synchroniser pour ce livre.",
|
||||
"Configure Hardcover in Settings first.": "Configurez d'abord Hardcover dans les paramètres.",
|
||||
"No new Hardcover note changes to sync.": "Aucun nouveau changement de notes Hardcover à synchroniser.",
|
||||
"Hardcover synced: {{inserted}} new, {{updated}} updated, {{skipped}} unchanged": "Hardcover synchronisé : {{inserted}} nouveaux, {{updated}} mis à jour, {{skipped}} inchangés",
|
||||
"Hardcover notes sync failed: {{error}}": "Échec de la synchronisation des notes Hardcover : {{error}}",
|
||||
"Reading progress synced to Hardcover": "Progression de lecture synchronisée avec Hardcover",
|
||||
"Hardcover progress sync failed: {{error}}": "Échec de la synchronisation de la progression Hardcover : {{error}}",
|
||||
"ISBN": "ISBN",
|
||||
"Keep existing source identifier": "Conserver l'identifiant source existant",
|
||||
"Toggle Toolbar": "Basculer la barre d'outils",
|
||||
"Split Hyphens": "Séparer les traits d'union",
|
||||
"Apply Theme Colors to PDF": "Appliquer les couleurs du thème au PDF",
|
||||
"Name": "Nom",
|
||||
"Remove": "Supprimer",
|
||||
"Edit OPDS Catalog": "Modifier le catalogue OPDS",
|
||||
"Save Changes": "Enregistrer les modifications",
|
||||
"Use Book Layout": "Utiliser la mise en page du livre",
|
||||
"End of this section. Continue to the next.": "Fin de cette section. Continuer à la suivante.",
|
||||
"Auto-download": "Téléchargement automatique",
|
||||
"Auto-download new items": "Télécharger automatiquement les nouveaux éléments",
|
||||
"Automatically download new publications when the app syncs": "Télécharger automatiquement les nouvelles publications lors de la synchronisation de l'application",
|
||||
"{{count}} new item(s) downloaded from OPDS_one": "{{count}} nouvel élément téléchargé depuis OPDS",
|
||||
"{{count}} new item(s) downloaded from OPDS_many": "{{count}} nouveaux éléments téléchargés depuis OPDS",
|
||||
"{{count}} new item(s) downloaded from OPDS_other": "{{count}} nouveaux éléments téléchargés depuis OPDS",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_one": "Échec de la synchronisation de {{count}} catalogue OPDS",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_many": "Échec de la synchronisation de {{count}} catalogues OPDS",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_other": "Échec de la synchronisation de {{count}} catalogues OPDS",
|
||||
"Last synced {{when}}": "Dernière synchronisation {{when}}",
|
||||
"Failed downloads": "Téléchargements échoués",
|
||||
"No failed downloads": "Aucun téléchargement échoué",
|
||||
"Attempts: {{count}}_one": "Tentatives : {{count}}",
|
||||
"Attempts: {{count}}_many": "Tentatives : {{count}}",
|
||||
"Attempts: {{count}}_other": "Tentatives : {{count}}",
|
||||
"Skip": "Ignorer",
|
||||
"Skip all": "Tout ignorer",
|
||||
"Retry all": "Tout réessayer",
|
||||
"{{count}} failed_one": "{{count}} échec",
|
||||
"{{count}} failed_many": "{{count}} échecs",
|
||||
"{{count}} failed_other": "{{count}} échecs",
|
||||
"(none)": "(aucun)",
|
||||
"Identifiers": "Identifiants",
|
||||
"Read (Stream)": "Lire (Flux)",
|
||||
"Failed to start stream": "Échec du démarrage du flux",
|
||||
"No dictionaries enabled": "Aucun dictionnaire activé",
|
||||
"Enable a dictionary in Settings → Language → Dictionaries.": "Activez un dictionnaire dans Paramètres → Langue → Dictionnaires.",
|
||||
"Wiktionary": "Wiktionnaire",
|
||||
"Drag to reorder": "Glisser pour réorganiser",
|
||||
"Built-in": "Intégré",
|
||||
"Bundle is missing on this device. Re-import to use it.": "Le paquet est absent sur cet appareil. Ré-importez-le pour l’utiliser.",
|
||||
"This dictionary format is not supported.": "Ce format de dictionnaire n’est pas pris en charge.",
|
||||
"MDict": "MDict",
|
||||
"StarDict": "StarDict",
|
||||
"Imported {{count}} dictionary_one": "{{count}} dictionnaire importé",
|
||||
"Imported {{count}} dictionary_many": "{{count}} dictionnaires importés",
|
||||
"Imported {{count}} dictionary_other": "{{count}} dictionnaires importés",
|
||||
"Skipped incomplete bundles: {{names}}": "Paquets incomplets ignorés : {{names}}",
|
||||
"Failed to import dictionary: {{message}}": "Échec de l’import du dictionnaire : {{message}}",
|
||||
"Dictionaries": "Dictionnaires",
|
||||
"Delete Dictionary": "Supprimer le dictionnaire",
|
||||
"Importing…": "Importation…",
|
||||
"Import Dictionary": "Importer un dictionnaire",
|
||||
"No dictionaries available.": "Aucun dictionnaire disponible.",
|
||||
"StarDict bundles need .ifo, .idx, and .dict.dz files (.syn optional).": "Les paquets StarDict nécessitent des fichiers .ifo, .idx et .dict.dz (.syn optionnel).",
|
||||
"Select all the bundle files together when importing.": "Sélectionnez tous les fichiers du paquet ensemble lors de l’import.",
|
||||
"Manage Dictionaries": "Gérer les dictionnaires",
|
||||
"Select Dictionary Files": "Sélectionner les fichiers du dictionnaire",
|
||||
"Read on Wikipedia →": "Lire sur Wikipédia →",
|
||||
"This link can't be opened": "Impossible d'ouvrir ce lien",
|
||||
"The annotation link is missing required information. The original link may have been truncated.": "Le lien d'annotation manque d'informations requises. Le lien d'origine a peut-être été tronqué.",
|
||||
"Go to Readest": "Aller sur Readest",
|
||||
"Open-source ebook reader for everyone, on every device.": "Lecteur d'e-books open source pour tout le monde, sur tous les appareils.",
|
||||
"Open in Readest": "Ouvrir dans Readest",
|
||||
"If Readest didn't open automatically, choose an option below:": "Si Readest ne s'est pas ouvert automatiquement, choisissez une option ci-dessous :",
|
||||
"Continue reading where you left off.": "Reprenez votre lecture là où vous l'aviez laissée.",
|
||||
"Readest logo": "Logo Readest",
|
||||
"Opening Readest...": "Ouverture de Readest…",
|
||||
"Open in Readest app": "Ouvrir dans l'application Readest",
|
||||
"Continue in browser": "Continuer dans le navigateur",
|
||||
"Don't have Readest?": "Vous n'avez pas Readest ?",
|
||||
"Book not in your library": "Livre absent de votre bibliothèque",
|
||||
"Share Book": "Partager le livre",
|
||||
"Could not create share link": "Impossible de créer le lien de partage",
|
||||
"Link copied": "Lien copié",
|
||||
"Could not copy link": "Impossible de copier le lien",
|
||||
"Share revoked": "Partage révoqué",
|
||||
"Could not revoke share": "Impossible de révoquer le partage",
|
||||
"Uploading book…": "Téléchargement du livre…",
|
||||
"Generating…": "Génération…",
|
||||
"Generate share link": "Créer un lien de partage",
|
||||
"Share URL": "URL de partage",
|
||||
"Copy link": "Copier le lien",
|
||||
"Copied": "Copié",
|
||||
"Share via…": "Partager via…",
|
||||
"Expires {{date}}": "Expire le {{date}}",
|
||||
"Revoking…": "Révocation…",
|
||||
"Revoke share": "Révoquer le partage",
|
||||
"Sign in to share books": "Connectez-vous pour partager des livres",
|
||||
"Expiring soon": "Expire bientôt",
|
||||
"Missing share token": "Jeton de partage manquant",
|
||||
"Could not add to your library": "Impossible d'ajouter à votre bibliothèque",
|
||||
"This share link is no longer available": "Ce lien de partage n'est plus disponible",
|
||||
"The original link may have expired or been revoked.": "Le lien original a peut-être expiré ou été révoqué.",
|
||||
"Get Readest": "Obtenir Readest",
|
||||
"Loading shared book…": "Chargement du livre partagé…",
|
||||
"Adding…": "Ajout…",
|
||||
"Add to my library": "Ajouter à ma bibliothèque",
|
||||
"Could not load your shares": "Impossible de charger vos partages",
|
||||
"Revoked": "Révoqué",
|
||||
"Expired": "Expiré",
|
||||
"Shared books": "Livres partagés",
|
||||
"You haven't shared any books yet": "Vous n’avez encore partagé aucun livre",
|
||||
"Open a book and tap Share to send it to a friend.": "Ouvrez un livre et appuyez sur Partager pour l’envoyer à un ami.",
|
||||
"{{count}} active_one": "{{count}} actif",
|
||||
"{{count}} active_many": "{{count}} actifs",
|
||||
"{{count}} active_other": "{{count}} actifs",
|
||||
"{{count}} downloads_one": "{{count}} téléchargement",
|
||||
"{{count}} downloads_many": "{{count}} téléchargements",
|
||||
"{{count}} downloads_other": "{{count}} téléchargements",
|
||||
"starts at saved page": "commence à la page enregistrée",
|
||||
"More actions": "Plus d’actions",
|
||||
"Loading…": "Chargement…",
|
||||
"Load more": "Charger plus",
|
||||
"Could not load shared book": "Impossible de charger le livre partagé",
|
||||
"The share link is missing required information.": "Il manque des informations nécessaires au lien de partage.",
|
||||
"Please check your connection and try again.": "Vérifiez votre connexion et réessayez.",
|
||||
"Sign in to import shared books": "Connectez-vous pour importer des livres partagés",
|
||||
"Already in your library": "Déjà dans votre bibliothèque",
|
||||
"Added to your library": "Ajouté à votre bibliothèque",
|
||||
"Could not import shared book": "Impossible d'importer le livre partagé",
|
||||
"Manage Shared Links": "Gérer les liens de partage",
|
||||
"Expires in": "Expire dans",
|
||||
"{{count}} days_one": "1 jour",
|
||||
"{{count}} days_many": "{{count}} jours",
|
||||
"{{count}} days_other": "{{count}} jours",
|
||||
"Expires in {{count}} days_one": "Expire dans 1 jour",
|
||||
"Expires in {{count}} days_many": "Expire dans {{count}} jours",
|
||||
"Expires in {{count}} days_other": "Expire dans {{count}} jours",
|
||||
"Expires in {{count}} hours_one": "Expire dans 1 heure",
|
||||
"Expires in {{count}} hours_many": "Expire dans {{count}} heures",
|
||||
"Expires in {{count}} hours_other": "Expire dans {{count}} heures",
|
||||
"Open in app": "Ouvrir dans l'app",
|
||||
"Shared with you": "Partagé avec vous",
|
||||
"Downloading… {{percent}}%": "Téléchargement… {{percent}} %",
|
||||
"Import progress": "Progression de l’importation",
|
||||
"Share reading progress": "Partager la progression de lecture",
|
||||
"Includes your reading progress": "Inclut votre progression de lecture",
|
||||
"Synced {{time}}": "Synchronisé {{time}}",
|
||||
"Sync Info": "Infos de synchronisation",
|
||||
"Last Synced": "Dernière synchronisation",
|
||||
"DICT": "DICT",
|
||||
"Slob": "Slob",
|
||||
"DICT bundles need a .index file and a .dict.dz file.": "Les paquets DICT nécessitent un fichier .index et un fichier .dict.dz.",
|
||||
"Slob bundles need a .slob file.": "Les paquets Slob nécessitent un fichier .slob.",
|
||||
"URL template must start with http(s):// and contain %WORD%.": "Le modèle d'URL doit commencer par http(s):// et contenir %WORD%.",
|
||||
"Web": "Web",
|
||||
"Add Web Search": "Ajouter une recherche web",
|
||||
"Edit Web Search": "Modifier la recherche web",
|
||||
"e.g. Google": "ex. Google",
|
||||
"URL Template": "Modèle d'URL",
|
||||
"Use %WORD% where the looked-up word should appear.": "Utilisez %WORD% à l'endroit où le mot recherché doit apparaître.",
|
||||
"Open the search result in your browser:": "Ouvrir le résultat de la recherche dans votre navigateur :",
|
||||
"Open in {{name}}": "Ouvrir dans {{name}}",
|
||||
"Book Fingerprint": "Empreinte du livre",
|
||||
"Search the web": "Rechercher sur le web",
|
||||
"App deeplink (readest://)": "Lien profond de l’application (readest://)",
|
||||
"Universal web link (https://)": "Lien web universel (https://)",
|
||||
"Name cannot be empty.": "Le nom ne peut pas être vide.",
|
||||
"Replaced {{count}} existing dictionary_one": "{{count}} dictionnaire existant remplacé",
|
||||
"Replaced {{count}} existing dictionary_many": "{{count}} dictionnaires existants remplacés",
|
||||
"Replaced {{count}} existing dictionary_other": "{{count}} dictionnaires existants remplacés",
|
||||
"Cancel Edit": "Annuler la modification",
|
||||
"Edit Dictionary": "Modifier le dictionnaire",
|
||||
"MDict bundles use .mdx files; companion .mdd and .css files are optional.": "Les bundles MDict utilisent des fichiers .mdx ; les fichiers .mdd et .css associés sont facultatifs.",
|
||||
"Dictionary name": "Nom du dictionnaire",
|
||||
"This audio can't play here — the dictionary uses an outdated format. Try one with Opus, MP3, or WAV audio.": "Cet audio ne peut pas être lu ici — le dictionnaire utilise un format obsolète. Essayez-en un avec un audio Opus, MP3 ou WAV.",
|
||||
"File uploaded: {{title}}": "Fichier téléchargé : {{title}}",
|
||||
"File downloaded: {{title}}": "Fichier téléchargé : {{title}}",
|
||||
"Deleted cloud copy of the file: {{title}}": "Suppression de la copie cloud du fichier : {{title}}",
|
||||
"Failed to upload file: {{title}}": "Échec du téléchargement du fichier : {{title}}",
|
||||
"Failed to download file: {{title}}": "Échec du téléchargement du fichier : {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Échec de la suppression de la copie cloud du fichier : {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}} % utilisés",
|
||||
"Set PIN…": "Définir le PIN…",
|
||||
"Require a 4-digit PIN to open Readest": "Exiger un PIN à 4 chiffres pour ouvrir Readest",
|
||||
"Change PIN…": "Modifier le PIN…",
|
||||
"Disable PIN…": "Désactiver le PIN…",
|
||||
"Sync passphrase ready": "Phrase secrète de synchronisation prête",
|
||||
"This permanently deletes the encrypted credentials we sync (e.g., OPDS catalog passwords) on every device. Local copies are preserved. You will need to re-enter the sync passphrase or set a new one. Continue?": "Cela supprime définitivement les identifiants chiffrés que nous synchronisons (par exemple, les mots de passe du catalogue OPDS) sur chaque appareil. Les copies locales sont conservées. Vous devrez ressaisir la phrase secrète de synchronisation ou en définir une nouvelle. Continuer ?",
|
||||
"Sync passphrase forgotten — all encrypted fields cleared": "Phrase secrète de synchronisation oubliée — tous les champs chiffrés ont été effacés",
|
||||
"Sync passphrase": "Phrase secrète de synchronisation",
|
||||
"Set passphrase": "Définir la phrase secrète",
|
||||
"Forgot passphrase": "Phrase secrète oubliée",
|
||||
"Incorrect PIN": "PIN incorrect",
|
||||
"App locked": "Application verrouillée",
|
||||
"Enter your PIN": "Saisissez votre PIN",
|
||||
"Readest is locked. Enter your 4-digit PIN to continue.": "Readest est verrouillé. Saisissez votre PIN à 4 chiffres pour continuer.",
|
||||
"PIN code": "Code PIN",
|
||||
"Forgetting your PIN locks you out of this device. You'll need to clear the app's data to reset it.": "Si vous oubliez votre PIN, vous serez verrouillé hors de cet appareil. Vous devrez effacer les données de l'application pour le réinitialiser.",
|
||||
"Passphrase must be at least 8 characters": "La phrase secrète doit comporter au moins 8 caractères",
|
||||
"Passphrases do not match": "Les phrases secrètes ne correspondent pas",
|
||||
"Set sync passphrase": "Définir la phrase secrète de synchronisation",
|
||||
"Enter sync passphrase": "Saisir la phrase secrète de synchronisation",
|
||||
"A sync passphrase encrypts your sensitive fields (like OPDS catalog credentials) before they sync. We never see this passphrase. Pick something memorable — there is no recovery without it.": "Une phrase secrète de synchronisation chiffre vos champs sensibles (comme les identifiants du catalogue OPDS) avant qu'ils ne soient synchronisés. Nous ne voyons jamais cette phrase secrète. Choisissez quelque chose de mémorable — il n'y a pas de récupération sans elle.",
|
||||
"Enter the sync passphrase you set on another device to decrypt your synced credentials.": "Saisissez la phrase secrète de synchronisation que vous avez définie sur un autre appareil pour déchiffrer vos identifiants synchronisés.",
|
||||
"Confirm passphrase": "Confirmer la phrase secrète",
|
||||
"Unlock": "Déverrouiller",
|
||||
"Set PIN": "Définir le PIN",
|
||||
"Change PIN": "Modifier le PIN",
|
||||
"Disable PIN": "Désactiver le PIN",
|
||||
"Enter your current PIN, then choose a new 4-digit PIN.": "Saisissez votre PIN actuel, puis choisissez un nouveau PIN à 4 chiffres.",
|
||||
"Enter your current PIN to disable the app lock.": "Saisissez votre PIN actuel pour désactiver le verrouillage de l'application.",
|
||||
"PIN must be {{length}} digits": "Le PIN doit comporter {{length}} chiffres",
|
||||
"PINs do not match": "Les PIN ne correspondent pas",
|
||||
"Current PIN": "PIN actuel",
|
||||
"New PIN": "Nouveau PIN",
|
||||
"Confirm new PIN": "Confirmer le nouveau PIN",
|
||||
"Manage Sync": "Gérer la synchronisation",
|
||||
"Imported book files and library metadata": "Fichiers de livres importés et métadonnées de la bibliothèque",
|
||||
"Last-read position, bookmarks, and per-book preferences": "Dernière position de lecture, signets et préférences par livre",
|
||||
"Highlights and notes": "Surlignages et notes",
|
||||
"Fonts": "Polices",
|
||||
"Custom font files": "Fichiers de polices personnalisées",
|
||||
"Backgrounds": "Arrière-plans",
|
||||
"Custom background textures": "Textures d'arrière-plan personnalisées",
|
||||
"OPDS catalogs": "Catalogues OPDS",
|
||||
"Saved catalog URLs and (encrypted) credentials": "URLs de catalogues enregistrées et identifiants (chiffrés)",
|
||||
"Wrong sync passphrase — synced credentials could not be decrypted": "Phrase secrète de synchronisation incorrecte — les identifiants synchronisés n'ont pas pu être déchiffrés",
|
||||
"Sync passphrase data on the server was reset. Re-encrypting your credentials under the new passphrase…": "Les données de phrase secrète de synchronisation sur le serveur ont été réinitialisées. Rechiffrement de vos identifiants avec la nouvelle phrase secrète…",
|
||||
"Failed to decrypt synced credentials": "Impossible de déchiffrer les identifiants synchronisés",
|
||||
"Imported dictionary bundles and settings": "Paquets de dictionnaires importés et paramètres",
|
||||
"Choose what syncs across your devices. Disabling a category stops this device from sending or receiving rows of that kind. Anything already on the server is left alone, re-enabling resumes from where you stopped.": "Choisissez ce qui est synchronisé entre vos appareils. Désactiver une catégorie empêche cet appareil d'envoyer ou de recevoir des entrées de ce type. Ce qui est déjà sur le serveur reste intact, la réactivation reprend là où vous vous étiez arrêté.",
|
||||
"Data Sync": "Synchronisation des données",
|
||||
"Manage Fonts": "Gérer les polices",
|
||||
"App settings": "Paramètres de l’application",
|
||||
"Theme, highlight colours, integrations (KOSync, Readwise, Hardcover), and dictionary order": "Thème, couleurs de surlignage, intégrations (KOSync, Readwise, Hardcover) et ordre des dictionnaires",
|
||||
"Required while Dictionaries sync is enabled": "Requis tant que la synchronisation des dictionnaires est activée",
|
||||
"Resets in {{hours}} hr {{minutes}} min": "Réinitialisation dans {{hours}} h {{minutes}} min",
|
||||
"Pick a 4-digit PIN. You will need to enter it every time you open Readest. There is no way to recover a forgotten PIN. Clearing the app data is the only way to reset it.": "Choisissez un code PIN à 4 chiffres. Vous devrez le saisir chaque fois que vous ouvrirez Readest. Il n'existe aucun moyen de récupérer un code PIN oublié. Effacer les données de l'application est le seul moyen de le réinitialiser.",
|
||||
"Credentials": "Identifiants",
|
||||
"Tokens, usernames, and passwords for OPDS, KOReader, Hardcover, and Readwise. When disabled, credentials remain on this device only and are never uploaded.": "Jetons, noms d'utilisateur et mots de passe pour OPDS, KOReader, Hardcover et Readwise. Lorsqu'ils sont désactivés, les identifiants restent uniquement sur cet appareil et ne sont jamais téléversés.",
|
||||
"Sensitive synced fields are encrypted before upload. Set a passphrase now or later when encryption is needed.": "Les champs sensibles synchronisés sont chiffrés avant l'envoi. Définissez une phrase de passe maintenant ou plus tard, lorsque le chiffrement sera nécessaire.",
|
||||
"Saved to this account. You will be prompted for it when decrypting credentials.": "Enregistrée sur ce compte. Elle vous sera demandée lors du déchiffrement des identifiants.",
|
||||
"Reading progress on this device differs from \"{{deviceName}}\".": "La progression de lecture sur cet appareil diffère de « {{deviceName}} ».",
|
||||
"This device": "Cet appareil",
|
||||
"Red": "Rouge",
|
||||
"Yellow": "Jaune",
|
||||
"Green": "Vert",
|
||||
"Blue": "Bleu",
|
||||
"Violet": "Violet",
|
||||
"Edit color": "Modifier la couleur",
|
||||
"Add custom color": "Ajouter une couleur personnalisée",
|
||||
"Add label": "Ajouter une étiquette",
|
||||
"Choose color": "Choisir une couleur",
|
||||
"Connected to Hardcover. Last synced {{time}}.": "Connecté à Hardcover. Dernière synchronisation {{time}}.",
|
||||
"Integrations": "Intégrations",
|
||||
"Hardcover": "Hardcover",
|
||||
"Connected to Readwise. Last synced {{time}}.": "Connecté à Readwise. Dernière synchronisation {{time}}.",
|
||||
"Readwise": "Readwise",
|
||||
"Browse and download books from online catalogs.": "Parcourez et téléchargez des livres depuis des catalogues en ligne.",
|
||||
"Connected as {{user}}": "Connecté en tant que {{user}}",
|
||||
"Not connected": "Non connecté",
|
||||
"{{count}} catalog_one": "{{count}} catalogue",
|
||||
"{{count}} catalog_many": "{{count}} catalogues",
|
||||
"{{count}} catalog_other": "{{count}} catalogues",
|
||||
"No catalogs": "Aucun catalogue",
|
||||
"Connect Readest to external services for sync, highlights, and catalogs.": "Connectez Readest à des services externes pour la synchronisation, les surlignages et les catalogues.",
|
||||
"Reading Sync": "Synchronisation de lecture",
|
||||
"Content Sources": "Sources de contenu",
|
||||
"Scroll tabs": "Faire défiler les onglets",
|
||||
"Catalog actions": "Actions du catalogue",
|
||||
"File Content": "Contenu du fichier",
|
||||
"Start your library": "Commencez votre bibliothèque",
|
||||
"Pick a book from your device to add it to your library.": "Choisissez un livre depuis votre appareil à ajouter à votre bibliothèque.",
|
||||
"Drop a book anywhere on this window, or pick one from your computer.": "Déposez un livre n'importe où sur cette fenêtre, ou choisissez-en un depuis votre ordinateur.",
|
||||
"Sign in to sync your library": "Connectez-vous pour synchroniser votre bibliothèque"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@
|
||||
"Auto Mode": "स्वचालित मोड",
|
||||
"Behavior": "व्यवहार",
|
||||
"Book": "पुस्तक",
|
||||
"Book Cover": "पुस्तक का कवर",
|
||||
"Bookmark": "बुकमार्क",
|
||||
"Cancel": "रद्द करें",
|
||||
"Chapter": "अध्याय",
|
||||
@@ -103,11 +102,9 @@
|
||||
"Updated": "अपडेट किया गया",
|
||||
"Version {{version}}": "संस्करण {{version}}",
|
||||
"Vertical Direction": "ऊर्ध्वाधर दिशा",
|
||||
"Welcome to your library. You can import your books here and read them anytime.": "आपकी लाइब्रेरी में आपका स्वागत है। आप यहां अपनी पुस्तकें आयात कर सकते हैं और कभी भी पढ़ सकते हैं।",
|
||||
"Wikipedia": "विकिपीडिया",
|
||||
"Writing Mode": "लेखन मोड",
|
||||
"Your Library": "आपकी लाइब्रेरी",
|
||||
"TTS not supported for PDF": "PDF के लिए TTS समर्थित नहीं है",
|
||||
"Override Book Font": "पुस्तक फ़ॉन्ट ओवरराइड करें",
|
||||
"Apply to All Books": "सभी पुस्तकों पर लागू करें",
|
||||
"Apply to This Book": "इस पुस्तक पर लागू करें",
|
||||
@@ -174,9 +171,6 @@
|
||||
"Token": "टोकन",
|
||||
"Your OTP token": "आपका ओटीपी टोकन",
|
||||
"Verify token": "टोकन सत्यापित करें",
|
||||
"Sign in with Google": "Google के साथ साइन इन करें",
|
||||
"Sign in with Apple": "Apple के साथ साइन इन करें",
|
||||
"Sign in with GitHub": "GitHub के साथ साइन इन करें",
|
||||
"Account": "खाता",
|
||||
"Failed to delete user. Please try again later.": "उपयोगकर्ता को हटाने में विफल। कृपया बाद में पुनः प्रयास करें।",
|
||||
"Community Support": "सामुदायिक सहायता",
|
||||
@@ -189,12 +183,11 @@
|
||||
"RTL Direction": "RTL दिशा",
|
||||
"Maximum Column Height": "कॉलम की अधिकतम ऊँचाई",
|
||||
"Maximum Column Width": "कॉलम की अधिकतम चौड़ाई",
|
||||
"Continuous Scroll": "निरंतर स्क्रॉल",
|
||||
"Fullscreen": "पूर्णस्क्रीन",
|
||||
"No supported files found. Supported formats: {{formats}}": "कोई समर्थित फ़ाइलें नहीं मिलीं। समर्थित प्रारूप: {{formats}}",
|
||||
"Drop to Import Books": "पुस्तकें आयात करने के लिए छोड़ें",
|
||||
"Custom": "कस्टम",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "पूरी दुनिया एक स्टेज है,\nऔर सभी आदमी और महिलाएँ केवल अभिनेता हैं;\nउनके निकास और प्रवेश होते हैं,\nऔर एक आदमी अपने समय में कई भूमिकाएँ निभाता है,\nउसके कार्य सात युग होते हैं।\n\n— विलियम शेक्सपियर",
|
||||
"All the world's a stage,\nAnd all the men and women merely players;\nThey have their exits and their entrances,\nAnd one man in his time plays many parts,\nHis acts being seven ages.\n\n— William Shakespeare": "पूरी दुनिया एक स्टेज है,\nऔर सभी आदमी और महिलाएँ केवल अभिनेता हैं;\nउनके निकास और प्रवेश होते हैं,\nऔर एक आदमी अपने समय में कई भूमिकाएँ निभाता है,\nउसके कार्य सात युग होते हैं।\n\n— विलियम शेक्सपियर",
|
||||
"Custom Theme": "कस्टम थीम",
|
||||
"Theme Name": "थीम का नाम",
|
||||
"Text Color": "टेक्स्ट रंग",
|
||||
@@ -221,9 +214,9 @@
|
||||
"Auto Import on File Open": "फ़ाइल खोलने पर स्वचालित आयात",
|
||||
"LXGW WenKai GB Screen": "LXGW WenKai SC",
|
||||
"LXGW WenKai TC": "LXGW WenKai TC",
|
||||
"No chapters detected.": "कोई अध्याय नहीं मिला।",
|
||||
"Failed to parse the EPUB file.": "EPUB फ़ाइल को पार्स करने में विफल।",
|
||||
"This book format is not supported.": "यह पुस्तक प्रारूप समर्थित नहीं है।",
|
||||
"No chapters detected": "कोई अध्याय नहीं मिला।",
|
||||
"Failed to parse the EPUB file": "EPUB फ़ाइल को पार्स करने में विफल।",
|
||||
"This book format is not supported": "यह पुस्तक प्रारूप समर्थित नहीं है।",
|
||||
"Unable to fetch the translation. Please log in first and try again.": "अनुवाद प्राप्त करने में असमर्थ। कृपया पहले लॉग इन करें और पुनः प्रयास करें।",
|
||||
"Group": "समूह",
|
||||
"Always on Top": "सर्वोच्च पर हमेशा",
|
||||
@@ -260,8 +253,6 @@
|
||||
"(from 'As You Like It', Act II)": "('जैसा आप इसे पसंद करते हैं', अधिनियम II)",
|
||||
"Link Color": "लिंक रंग",
|
||||
"Volume Keys for Page Flip": "पृष्ठ फ़्लिप के लिए वॉल्यूम कुंजियाँ",
|
||||
"Clicks for Page Flip": "पृष्ठ फ़्लिप के लिए क्लिक करें",
|
||||
"Swap Clicks Area": "क्लिक क्षेत्र स्वैप करें",
|
||||
"Screen": "स्क्रीन",
|
||||
"Orientation": "अवयव",
|
||||
"Portrait": "पोर्ट्रेट",
|
||||
@@ -297,7 +288,6 @@
|
||||
"Disable Translation": "अनुवाद अक्षम करें",
|
||||
"Scroll": "स्क्रॉल",
|
||||
"Overlap Pixels": "ओवरलैप पिक्सेल",
|
||||
"Click": "क्लिक करें",
|
||||
"System Language": "सिस्टम भाषा",
|
||||
"Security": "सुरक्षा",
|
||||
"Allow JavaScript": "जावास्क्रिप्ट की अनुमति दें",
|
||||
@@ -308,7 +298,6 @@
|
||||
"No notes match your search": "कोई नोट्स नहीं मिले",
|
||||
"Search notes and excerpts...": "नोट्स खोजें...",
|
||||
"Sign in to Sync": "सिंक करने के लिए साइन इन करें",
|
||||
"Synced at {{time}}": "{{time}} पर सिंक किया गया",
|
||||
"Never synced": "कभी सिंक नहीं किया गया",
|
||||
"Show Remaining Time": "शेष समय दिखाएं",
|
||||
"{{time}} min left in chapter": "{{time}} मिनट शेष अध्याय में",
|
||||
@@ -342,11 +331,11 @@
|
||||
"Fit": "फिट",
|
||||
"Reset {{settings}}": "रीसेट {{settings}}",
|
||||
"Reset Settings": "सेटिंग्स रीसेट करें",
|
||||
"{{count}} pages left in chapter_one": "इस अध्याय में {{count}} पृष्ठ शेष हैं",
|
||||
"{{count}} pages left in chapter_other": "इस अध्याय में {{count}} पृष्ठ शेष हैं",
|
||||
"{{count}} pages left in chapter_one": "<1>इस अध्याय में </1><0>{{count}}</0><1> पृष्ठ शेष हैं</1>",
|
||||
"{{count}} pages left in chapter_other": "<1>इस अध्याय में </1><0>{{count}}</0><1> पृष्ठ शेष हैं</1>",
|
||||
"Show Remaining Pages": "शेष पृष्ठ दिखाएँ",
|
||||
"Source Han Serif CN VF": "Source Han Serif",
|
||||
"Huiwen-mincho": "Huiwen Mincho",
|
||||
"Source Han Serif CN": "Source Han Serif",
|
||||
"Huiwen-MinchoGBK": "Huiwen Mincho",
|
||||
"KingHwa_OldSong": "KingHwa Song",
|
||||
"Manage Subscription": "सदस्यता प्रबंधित करें",
|
||||
"Coming Soon": "जल्द आ रहा है",
|
||||
@@ -366,7 +355,6 @@
|
||||
"Email:": "ईमेल:",
|
||||
"Plan:": "योजना:",
|
||||
"Amount:": "राशि:",
|
||||
"Subscription ID:": "सदस्यता आईडी:",
|
||||
"Go to Library": "लाइब्रेरी पर जाएँ",
|
||||
"Need help? Contact our support team at support@readest.com": "मदद चाहिए? हमारी सहायता टीम से संपर्क करें: support@readest.com",
|
||||
"Free Plan": "मुफ्त योजना",
|
||||
@@ -471,14 +459,12 @@
|
||||
"Remove from Cloud Only": "केवल क्लाउड से हटाएँ",
|
||||
"Remove from Device Only": "केवल डिवाइस से हटाएँ",
|
||||
"Disconnected": "अविचलित",
|
||||
"KOReader Sync Settings": "KOReader सिंक सेटिंग्स",
|
||||
"Sync Strategy": "सिंक रणनीति",
|
||||
"Ask on conflict": "संघर्ष पर पूछें",
|
||||
"Always use latest": "हमेशा नवीनतम का उपयोग करें",
|
||||
"Send changes only": "केवल परिवर्तन भेजें",
|
||||
"Receive changes only": "केवल परिवर्तन प्राप्त करें",
|
||||
"Checksum Method": "चेकसम विधि",
|
||||
"File Content (recommended)": "फाइल सामग्री (अनुशंसित)",
|
||||
"File Name": "फाइल नाम",
|
||||
"Device Name": "डिवाइस नाम",
|
||||
"Connect to your KOReader Sync server.": "अपने KOReader सिंक सर्वर से कनेक्ट करें।",
|
||||
@@ -489,10 +475,7 @@
|
||||
"Connect": "कनेक्ट करें",
|
||||
"KOReader Sync": "KOReader सिंक",
|
||||
"Sync Conflict": "सिंक संघर्ष",
|
||||
"Sync reading progress from \"{{deviceName}}\"?": "क्या आप \"{{deviceName}}\" से पढ़ने की प्रगति को सिंक करना चाहते हैं?",
|
||||
"another device": "दूसरा डिवाइस",
|
||||
"Local Progress": "स्थानीय प्रगति",
|
||||
"Remote Progress": "दूरस्थ प्रगति",
|
||||
"Page {{page}} of {{total}} ({{percentage}}%)": "पृष्ठ {{page}} कुल {{total}} ({{percentage}}%)",
|
||||
"Current position": "वर्तमान स्थिति",
|
||||
"Approximately page {{page}} of {{total}} ({{percentage}}%)": "लगभग पृष्ठ {{page}} कुल {{total}} ({{percentage}}%)",
|
||||
@@ -606,5 +589,828 @@
|
||||
"Opacity": "अस्पष्टता",
|
||||
"Size": "आकार",
|
||||
"Cover": "कवर",
|
||||
"Contain": "समाहित करें"
|
||||
"Contain": "समाहित करें",
|
||||
"{{number}} pages left in chapter": "<1>इस अध्याय में </1><0>{{number}}</0><1> पृष्ठ शेष हैं</1>",
|
||||
"Device": "डिवाइस",
|
||||
"E-Ink Mode": "ई-इंक मोड",
|
||||
"Highlight Colors": "हाइलाइट रंग",
|
||||
"Pagination": "पृष्ठांकन",
|
||||
"Disable Double Tap": "डबल टैप अक्षम करें",
|
||||
"Tap to Paginate": "पृष्ठांकन के लिए टैप करें",
|
||||
"Click to Paginate": "पृष्ठांकन के लिए क्लिक करें",
|
||||
"Tap Both Sides": "दोनों पक्षों पर टैप करें",
|
||||
"Click Both Sides": "दोनों पक्षों पर क्लिक करें",
|
||||
"Swap Tap Sides": "स्वैप टैप साइड्स",
|
||||
"Swap Click Sides": "स्वैप क्लिक साइड्स",
|
||||
"Source and Translated": "स्रोत और अनुवादित",
|
||||
"Translated Only": "केवल अनुवादित",
|
||||
"Source Only": "केवल स्रोत",
|
||||
"TTS Text": "TTS टेक्स्ट",
|
||||
"The book file is corrupted": "पुस्तक फ़ाइल भ्रष्ट है।",
|
||||
"The book file is empty": "पुस्तक फ़ाइल खाली है।",
|
||||
"Failed to open the book file": "पुस्तक फ़ाइल खोलने में विफल।",
|
||||
"On-Demand Purchase": "आवश्यकता के अनुसार खरीदारी",
|
||||
"Full Customization": "पूर्ण अनुकूलन",
|
||||
"Lifetime Plan": "जीवनकाल योजना",
|
||||
"One-Time Payment": "एक बार का भुगतान",
|
||||
"Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "सभी उपकरणों पर विशिष्ट सुविधाओं तक जीवनकाल पहुंच का आनंद लेने के लिए एक बार का भुगतान करें। जब आपको उनकी आवश्यकता हो, तो केवल विशिष्ट सुविधाओं या सेवाओं को खरीदें।",
|
||||
"Expand Cloud Sync Storage": "क्लाउड सिंक स्टोरेज का विस्तार करें",
|
||||
"Expand your cloud storage forever with a one-time purchase. Each additional purchase adds more space.": "एक बार के खरीदारी के साथ अपने क्लाउड स्टोरेज का विस्तार करें। प्रत्येक अतिरिक्त खरीदारी अधिक स्थान जोड़ती है।",
|
||||
"Unlock All Customization Options": "सभी अनुकूलन विकल्प अनलॉक करें",
|
||||
"Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "अतिरिक्त थीम, फ़ॉन्ट, लेआउट विकल्प और पढ़ने के लिए, अनुवादक, क्लाउड स्टोरेज सेवाएँ अनलॉक करें।",
|
||||
"Purchase Successful!": "खरीदारी सफल!",
|
||||
"lifetime": "जीवनकाल",
|
||||
"Thank you for your purchase! Your payment has been processed successfully.": "आपकी खरीदारी के लिए धन्यवाद! आपका भुगतान सफलतापूर्वक संसाधित हो गया है।",
|
||||
"Order ID:": "ऑर्डर आईडी:",
|
||||
"TTS Highlighting": "TTS हाइलाइटिंग",
|
||||
"Style": "शैली",
|
||||
"Underline": "रेखांकित",
|
||||
"Strikethrough": "रेखांकित करें",
|
||||
"Squiggly": "स्क्विगली",
|
||||
"Outline": "आउटलाइन",
|
||||
"Quick Colors": "त्वरित रंग",
|
||||
"Highlighter": "हाइलाइटर",
|
||||
"Save Book Cover": "बुक कवर सहेजें",
|
||||
"Auto-save last book cover": "अंतिम पुस्तक कवर को स्वचालित रूप से सहेजें",
|
||||
"Back": "वापस",
|
||||
"Confirmation email sent! Please check your old and new email addresses to confirm the change.": "पुष्टिकरण ईमेल भेज दिया गया है! कृपया परिवर्तन की पुष्टि करने के लिए अपना पुराना और नया ईमेल पता जांचें।",
|
||||
"Failed to update email": "ईमेल अपडेट करने में विफल",
|
||||
"New Email": "नया ईमेल",
|
||||
"Your new email": "आपका नया ईमेल",
|
||||
"Updating email ...": "ईमेल अपडेट किया जा रहा है ...",
|
||||
"Update email": "ईमेल अपडेट करें",
|
||||
"Current email": "वर्तमान ईमेल",
|
||||
"Update Email": "ईमेल अपडेट करें",
|
||||
"All": "सभी",
|
||||
"Unable to open book": "पुस्तक खोलने में असमर्थ",
|
||||
"Punctuation": "विराम चिह्न",
|
||||
"Replace Quotation Marks": "उद्धरण चिह्न बदलें",
|
||||
"Enabled only in vertical layout.": "केवल ऊर्ध्वाधर लेआउट में सक्षम।",
|
||||
"No Conversion": "कोई रूपांतरण नहीं",
|
||||
"Simplified to Traditional": "सरलीकृत → पारंपरिक",
|
||||
"Traditional to Simplified": "पारंपरिक → सरलीकृत",
|
||||
"Simplified to Traditional (Taiwan)": "सरलीकृत → पारंपरिक (ताइवान)",
|
||||
"Simplified to Traditional (Hong Kong)": "सरलीकृत → पारंपरिक (हांगकांग)",
|
||||
"Simplified to Traditional (Taiwan), with phrases": "सरलीकृत → पारंपरिक (ताइवान • वाक्यांश)",
|
||||
"Traditional (Taiwan) to Simplified": "पारंपरिक (ताइवान) → सरलीकृत",
|
||||
"Traditional (Hong Kong) to Simplified": "पारंपरिक (हांगकांग) → सरलीकृत",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "पारंपरिक (ताइवान • वाक्यांश) → सरलीकृत",
|
||||
"Convert Simplified and Traditional Chinese": "सरलीकृत/पारंपरिक चीनी रूपांतरण",
|
||||
"Convert Mode": "रूपांतरण मोड",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "लॉक स्क्रीन के लिए पुस्तक कवर स्वचालित रूप से सहेजने में विफल: {{error}}",
|
||||
"Download from Cloud": "क्लाउड से डाउनलोड करें",
|
||||
"Upload to Cloud": "क्लाउड पर अपलोड करें",
|
||||
"Clear Custom Fonts": "कस्टम फ़ॉन्ट साफ़ करें",
|
||||
"Columns": "कॉलम",
|
||||
"OPDS Catalogs": "OPDS कैटलॉग",
|
||||
"Adding LAN addresses is not supported in the web app version.": "वेब ऐप संस्करण में LAN पता जोड़ना समर्थित नहीं है।",
|
||||
"Invalid OPDS catalog. Please check the URL.": "अमान्य OPDS कैटलॉग। कृपया URL जांचें।",
|
||||
"Browse and download books from online catalogs": "ऑनलाइन कैटलॉग से किताबें ब्राउज़ और डाउनलोड करें",
|
||||
"My Catalogs": "मेरे कैटलॉग",
|
||||
"Add Catalog": "कैटलॉग जोड़ें",
|
||||
"No catalogs yet": "अभी कोई कैटलॉग नहीं",
|
||||
"Add your first OPDS catalog to start browsing books": "किताबें ब्राउज़ करने के लिए अपना पहला OPDS कैटलॉग जोड़ें",
|
||||
"Add Your First Catalog": "अपना पहला कैटलॉग जोड़ें",
|
||||
"Browse": "ब्राउज़",
|
||||
"Popular Catalogs": "लोकप्रिय कैटलॉग",
|
||||
"Add": "जोड़ें",
|
||||
"Add OPDS Catalog": "OPDS कैटलॉग जोड़ें",
|
||||
"Catalog Name": "कैटलॉग नाम",
|
||||
"My Calibre Library": "मेरी Calibre लाइब्रेरी",
|
||||
"OPDS URL": "OPDS URL",
|
||||
"Username (optional)": "यूज़रनेम (वैकल्पिक)",
|
||||
"Password (optional)": "पासवर्ड (वैकल्पिक)",
|
||||
"Description (optional)": "विवरण (वैकल्पिक)",
|
||||
"A brief description of this catalog": "इस कैटलॉग का संक्षिप्त विवरण",
|
||||
"Validating...": "मान्य किया जा रहा है...",
|
||||
"View All": "सभी देखें",
|
||||
"Forward": "आगे",
|
||||
"Home": "होम",
|
||||
"{{count}} items_one": "{{count}} आइटम",
|
||||
"{{count}} items_other": "{{count}} आइटम",
|
||||
"Download completed": "डाउनलोड पूरा हुआ",
|
||||
"Download failed": "डाउनलोड विफल हुआ",
|
||||
"Open Access": "ओपन एक्सेस",
|
||||
"Borrow": "उधार लें",
|
||||
"Buy": "खरीदें",
|
||||
"Subscribe": "सदस्यता लें",
|
||||
"Sample": "नमूना",
|
||||
"Download": "डाउनलोड",
|
||||
"Open & Read": "खोलें और पढ़ें",
|
||||
"Tags": "टैग",
|
||||
"Tag": "टैग",
|
||||
"First": "पहला",
|
||||
"Previous": "पिछला",
|
||||
"Next": "अगला",
|
||||
"Last": "अंतिम",
|
||||
"Cannot Load Page": "पृष्ठ लोड नहीं किया जा सका",
|
||||
"An error occurred": "एक त्रुटि हुई",
|
||||
"Online Library": "ऑनलाइन लाइब्रेरी",
|
||||
"URL must start with http:// or https://": "URL http:// या https:// से शुरू होना चाहिए",
|
||||
"Title, Author, Tag, etc...": "शीर्षक, लेखक, टैग, आदि...",
|
||||
"Query": "प्रश्न",
|
||||
"Subject": "विषय",
|
||||
"Enter {{terms}}": "{{terms}} दर्ज करें",
|
||||
"No search results found": "कोई खोज परिणाम नहीं मिला",
|
||||
"Failed to load OPDS feed: {{status}} {{statusText}}": "OPDS फ़ीड लोड करने में विफल: {{status}} {{statusText}}",
|
||||
"Search in {{title}}": "{{title}} में खोजें",
|
||||
"Manage Storage": "स्टोरेज प्रबंधित करें",
|
||||
"Failed to load files": "फ़ाइलें लोड करने में विफल",
|
||||
"Deleted {{count}} file(s)_one": "{{count}} फ़ाइल हटाई गई",
|
||||
"Deleted {{count}} file(s)_other": "{{count}} फ़ाइलें हटाई गईं",
|
||||
"Failed to delete {{count}} file(s)_one": "{{count}} फ़ाइल हटाने में विफल",
|
||||
"Failed to delete {{count}} file(s)_other": "{{count}} फ़ाइलें हटाने में विफल",
|
||||
"Failed to delete files": "फ़ाइलें हटाने में विफल",
|
||||
"Total Files": "कुल फ़ाइलें",
|
||||
"Total Size": "कुल आकार",
|
||||
"Quota": "कोटा",
|
||||
"Used": "उपयोग किया गया",
|
||||
"Files": "फ़ाइलें",
|
||||
"Search files...": "फ़ाइलें खोजें...",
|
||||
"Newest First": "नवीनतम पहले",
|
||||
"Oldest First": "सबसे पुराने पहले",
|
||||
"Largest First": "सबसे बड़ी पहले",
|
||||
"Smallest First": "सबसे छोटी पहले",
|
||||
"Name A-Z": "नाम A-Z",
|
||||
"Name Z-A": "नाम Z-A",
|
||||
"{{count}} selected_one": "{{count}} चयनित",
|
||||
"{{count}} selected_other": "{{count}} चयनित",
|
||||
"Delete Selected": "चयनित हटाएँ",
|
||||
"Created": "बनाई गई",
|
||||
"No files found": "कोई फ़ाइल नहीं मिली",
|
||||
"No files uploaded yet": "अभी तक कोई फ़ाइल अपलोड नहीं की गई",
|
||||
"files": "फ़ाइलें",
|
||||
"Page {{current}} of {{total}}": "पृष्ठ {{current}} / {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "क्या आप वाकई {{count}} चयनित फ़ाइल हटाना चाहते हैं?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "क्या आप वाकई {{count}} चयनित फ़ाइलें हटाना चाहते हैं?",
|
||||
"Cloud Storage Usage": "क्लाउड स्टोरेज उपयोग",
|
||||
"Rename Group": "समूह का नाम बदलें",
|
||||
"From Directory": "निर्देशिका से",
|
||||
"Successfully imported {{count}} book(s)_one": "सफलतापूर्वक 1 पुस्तक आयात की गई",
|
||||
"Successfully imported {{count}} book(s)_other": "सफलतापूर्वक {{count}} पुस्तकों का आयात किया गया",
|
||||
"Count": "गणना",
|
||||
"Start Page": "प्रारंभ पृष्ठ",
|
||||
"Search in OPDS Catalog...": "OPDS कैटलॉग में खोजें...",
|
||||
"Please log in to use advanced TTS features": "उन्नत TTS सुविधाओं का उपयोग करने के लिए कृपया लॉग इन करें।",
|
||||
"Word limit of 30 words exceeded.": "30 शब्दों की सीमा पार हो गई है।",
|
||||
"Proofread": "प्रूफ़रीड",
|
||||
"Current selection": "वर्तमान चयन",
|
||||
"All occurrences in this book": "इस पुस्तक में सभी स्थान",
|
||||
"All occurrences in your library": "आपकी लाइब्रेरी में सभी स्थान",
|
||||
"Selected text:": "चयनित पाठ:",
|
||||
"Replace with:": "से बदलें:",
|
||||
"Enter text...": "पाठ दर्ज करें…",
|
||||
"Case sensitive:": "अक्षर संवेदनशील:",
|
||||
"Scope:": "क्षेत्र:",
|
||||
"Selection": "चयन",
|
||||
"Library": "लाइब्रेरी",
|
||||
"Yes": "हाँ",
|
||||
"No": "नहीं",
|
||||
"Proofread Replacement Rules": "प्रूफ़रीड प्रतिस्थापन नियम",
|
||||
"Selected Text Rules": "चयनित पाठ के नियम",
|
||||
"No selected text replacement rules": "चयनित पाठ के लिए कोई प्रतिस्थापन नियम नहीं हैं",
|
||||
"Book Specific Rules": "पुस्तक-विशिष्ट नियम",
|
||||
"No book-level replacement rules": "पुस्तक स्तर पर कोई प्रतिस्थापन नियम नहीं हैं",
|
||||
"Disable Quick Action": "त्वरित क्रिया अक्षम करें",
|
||||
"Enable Quick Action on Selection": "चयन पर त्वरित क्रिया सक्षम करें",
|
||||
"None": "कोई नहीं",
|
||||
"Annotation Tools": "टिप्पणी उपकरण",
|
||||
"Enable Quick Actions": "त्वरित क्रियाएँ सक्षम करें",
|
||||
"Quick Action": "त्वरित क्रिया",
|
||||
"Copy to Notebook": "नोटबुक में कॉपी करें",
|
||||
"Copy text after selection": "पाठ कॉपी करें चयन के बाद",
|
||||
"Highlight text after selection": "चयन के बाद पाठ हाइलाइट करें",
|
||||
"Annotate text after selection": "चयन के बाद पाठ पर टिप्पणी करें",
|
||||
"Search text after selection": "चयन के बाद पाठ खोजें",
|
||||
"Look up text in dictionary after selection": "चयन के बाद शब्दकोश में पाठ देखें",
|
||||
"Translate text after selection": "चयन के बाद पाठ का अनुवाद करें",
|
||||
"Read text aloud after selection": "चयन के बाद पाठ को जोर से पढ़ें",
|
||||
"Proofread text after selection": "चयन के बाद पाठ को प्रूफरीड करें",
|
||||
"{{activeCount}} active, {{pendingCount}} pending": "{{activeCount}} सक्रिय, {{pendingCount}} लंबित",
|
||||
"{{failedCount}} failed": "{{failedCount}} विफल",
|
||||
"Waiting...": "प्रतीक्षा कर रहे हैं...",
|
||||
"Failed": "विफल",
|
||||
"Completed": "पूर्ण",
|
||||
"Cancelled": "रद्द",
|
||||
"Retry": "पुनः प्रयास करें",
|
||||
"Active": "सक्रिय",
|
||||
"Transfer Queue": "स्थानांतरण कतार",
|
||||
"Upload All": "सभी अपलोड करें",
|
||||
"Download All": "सभी डाउनलोड करें",
|
||||
"Resume Transfers": "स्थानांतरण जारी रखें",
|
||||
"Pause Transfers": "स्थानांतरण रोकें",
|
||||
"Pending": "लंबित",
|
||||
"No transfers": "कोई स्थानांतरण नहीं",
|
||||
"Retry All": "सभी पुनः प्रयास करें",
|
||||
"Clear Completed": "पूर्ण साफ़ करें",
|
||||
"Clear Failed": "विफल साफ़ करें",
|
||||
"Upload queued: {{title}}": "अपलोड कतार में: {{title}}",
|
||||
"Download queued: {{title}}": "डाउनलोड कतार में: {{title}}",
|
||||
"Book not found in library": "पुस्तकालय में पुस्तक नहीं मिली",
|
||||
"Unknown error": "अज्ञात त्रुटि",
|
||||
"Please log in to continue": "जारी रखने के लिए लॉगिन करें",
|
||||
"Cloud File Transfers": "क्लाउड फ़ाइल स्थानांतरण",
|
||||
"Show Search Results": "खोज परिणाम दिखाएं",
|
||||
"Search results for '{{term}}'": "'{{term}}' के परिणाम",
|
||||
"Close Search": "खोज बंद करें",
|
||||
"Previous Result": "पिछला परिणाम",
|
||||
"Next Result": "अगला परिणाम",
|
||||
"Bookmarks": "बुकमार्क",
|
||||
"Annotations": "टिप्पणियाँ",
|
||||
"Show Results": "परिणाम दिखाएं",
|
||||
"Clear search": "खोज साफ़ करें",
|
||||
"Clear search history": "खोज इतिहास साफ़ करें",
|
||||
"Tap to Toggle Footer": "फुटर टॉगल करने के लिए टैप करें",
|
||||
"Show Current Time": "वर्तमान समय दिखाएं",
|
||||
"Use 24 Hour Clock": "24 घंटे की घड़ी का उपयोग करें",
|
||||
"Show Current Battery Status": "वर्तमान बैटरी स्थिति दिखाएं",
|
||||
"Exported successfully": "सफलतापूर्वक निर्यात किया गया",
|
||||
"Book exported successfully.": "पुस्तक सफलतापूर्वक निर्यात की गई।",
|
||||
"Failed to export the book.": "पुस्तक निर्यात करने में विफल।",
|
||||
"Export Book": "पुस्तक निर्यात करें",
|
||||
"Whole word:": "पूरा शब्द:",
|
||||
"Date Published": "प्रकाशन तिथि",
|
||||
"Only for TTS:": "केवल TTS के लिए:",
|
||||
"Uploaded": "अपलोड किया गया",
|
||||
"Downloaded": "डाउनलोड किया गया",
|
||||
"Deleted": "हटाया गया",
|
||||
"Note:": "नोट:",
|
||||
"Time:": "समय:",
|
||||
"Format Options": "प्रारूप विकल्प",
|
||||
"Export Date": "निर्यात तिथि",
|
||||
"Chapter Titles": "अध्याय शीर्षक",
|
||||
"Chapter Separator": "अध्याय विभाजक",
|
||||
"Highlights": "हाइलाइट्स",
|
||||
"Note Date": "नोट तिथि",
|
||||
"Advanced": "उन्नत",
|
||||
"Hide": "छिपाएं",
|
||||
"Show": "दिखाएं",
|
||||
"Use Custom Template": "कस्टम टेम्पलेट उपयोग करें",
|
||||
"Export Template": "निर्यात टेम्पलेट",
|
||||
"Template Syntax:": "टेम्पलेट सिंटैक्स:",
|
||||
"Insert value": "मान डालें",
|
||||
"Format date (locale)": "तिथि फ़ॉर्मेट करें (स्थानीय)",
|
||||
"Format date (custom)": "तिथि फ़ॉर्मेट करें (कस्टम)",
|
||||
"Conditional": "सशर्त",
|
||||
"Loop": "लूप",
|
||||
"Available Variables:": "उपलब्ध चर:",
|
||||
"Book title": "पुस्तक शीर्षक",
|
||||
"Book author": "पुस्तक लेखक",
|
||||
"Export date": "निर्यात तिथि",
|
||||
"Array of chapters": "अध्यायों की सूची",
|
||||
"Chapter title": "अध्याय शीर्षक",
|
||||
"Array of annotations": "एनोटेशन की सूची",
|
||||
"Highlighted text": "हाइलाइट किया गया पाठ",
|
||||
"Annotation note": "एनोटेशन नोट",
|
||||
"Date Format Tokens:": "तिथि प्रारूप टोकन:",
|
||||
"Year (4 digits)": "वर्ष (4 अंक)",
|
||||
"Month (01-12)": "माह (01-12)",
|
||||
"Day (01-31)": "दिन (01-31)",
|
||||
"Hour (00-23)": "घंटा (00-23)",
|
||||
"Minute (00-59)": "मिनट (00-59)",
|
||||
"Second (00-59)": "सेकंड (00-59)",
|
||||
"Show Source": "स्रोत दिखाएं",
|
||||
"No content to preview": "पूर्वावलोकन के लिए कोई सामग्री नहीं",
|
||||
"Export": "निर्यात करें",
|
||||
"Set Timeout": "टाइमआउट सेट करें",
|
||||
"Select Voice": "आवाज़ चुनें",
|
||||
"Toggle Sticky Bottom TTS Bar": "स्थिर TTS बार टॉगल करें",
|
||||
"Display what I'm reading on Discord": "Discord पर पढ़ रही किताब दिखाएं",
|
||||
"Show on Discord": "Discord पर दिखाएं",
|
||||
"Instant {{action}}": "तत्काल {{action}}",
|
||||
"Instant {{action}} Disabled": "तत्काल {{action}} अक्षम",
|
||||
"Annotation": "टिप्पणी",
|
||||
"Reset Template": "टेम्पलेट रीसेट करें",
|
||||
"Annotation style": "एनोटेशन शैली",
|
||||
"Annotation color": "एनोटेशन रंग",
|
||||
"Annotation time": "एनोटेशन समय",
|
||||
"AI": "AI",
|
||||
"Are you sure you want to re-index this book?": "क्या आप वाकई इस पुस्तक को फिर से इंडेक्स करना चाहते हैं?",
|
||||
"Enable AI in Settings": "सेटिंग्स में AI सक्षम करें",
|
||||
"Index This Book": "इस पुस्तक को इंडेक्स करें",
|
||||
"Enable AI search and chat for this book": "इस पुस्तक के लिए AI खोज और चैट सक्षम करें",
|
||||
"Start Indexing": "इंडेक्सिंग शुरू करें",
|
||||
"Indexing book...": "पुस्तक इंडेक्स हो रही है...",
|
||||
"Preparing...": "तैयारी हो रही है...",
|
||||
"Delete this conversation?": "इस वार्तालाप को हटाएं?",
|
||||
"No conversations yet": "अभी तक कोई वार्तालाप नहीं",
|
||||
"Start a new chat to ask questions about this book": "इस पुस्तक के बारे में प्रश्न पूछने के लिए नई चैट शुरू करें",
|
||||
"Rename": "नाम बदलें",
|
||||
"New Chat": "नई चैट",
|
||||
"Chat": "चैट",
|
||||
"Please enter a model ID": "कृपया मॉडल ID दर्ज करें",
|
||||
"Model not available or invalid": "मॉडल उपलब्ध नहीं या अमान्य है",
|
||||
"Failed to validate model": "मॉडल सत्यापित करने में विफल",
|
||||
"Couldn't connect to Ollama. Is it running?": "Ollama से कनेक्ट नहीं हो सका। क्या यह चल रहा है?",
|
||||
"Invalid API key or connection failed": "अमान्य API कुंजी या कनेक्शन विफल",
|
||||
"Connection failed": "कनेक्शन विफल",
|
||||
"AI Assistant": "AI सहायक",
|
||||
"Enable AI Assistant": "AI सहायक सक्षम करें",
|
||||
"Provider": "प्रदाता",
|
||||
"Ollama (Local)": "Ollama (स्थानीय)",
|
||||
"AI Gateway (Cloud)": "AI गेटवे (क्लाउड)",
|
||||
"Ollama Configuration": "Ollama कॉन्फ़िगरेशन",
|
||||
"Refresh Models": "मॉडल रीफ्रेश करें",
|
||||
"AI Model": "AI मॉडल",
|
||||
"No models detected": "कोई मॉडल नहीं मिला",
|
||||
"AI Gateway Configuration": "AI गेटवे कॉन्फ़िगरेशन",
|
||||
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "उच्च-गुणवत्ता, किफायती AI मॉडल के चयन में से चुनें। आप नीचे \"कस्टम मॉडल\" का चयन करके अपना मॉडल भी उपयोग कर सकते हैं।",
|
||||
"API Key": "API कुंजी",
|
||||
"Get Key": "कुंजी प्राप्त करें",
|
||||
"Model": "मॉडल",
|
||||
"Custom Model...": "कस्टम मॉडल...",
|
||||
"Custom Model ID": "कस्टम मॉडल ID",
|
||||
"Validate": "सत्यापित करें",
|
||||
"Model available": "मॉडल उपलब्ध",
|
||||
"Connection": "कनेक्शन",
|
||||
"Test Connection": "कनेक्शन परीक्षण",
|
||||
"Connected": "कनेक्टेड",
|
||||
"Custom Colors": "कस्टम रंग",
|
||||
"Color E-Ink Mode": "कलर ई-इंक मोड",
|
||||
"Reading Ruler": "पठन रूलर",
|
||||
"Enable Reading Ruler": "पठन रूलर सक्षम करें",
|
||||
"Lines to Highlight": "हाइलाइट करने के लिए पंक्तियाँ",
|
||||
"Ruler Color": "रूलर का रंग",
|
||||
"Command Palette": "कमांड पैलेट",
|
||||
"Search settings and actions...": "सेटिंग और क्रियाएं खोजें...",
|
||||
"No results found for": "के लिए कोई परिणाम नहीं मिला",
|
||||
"Type to search settings and actions": "सेटिंग और क्रियाएं खोजने के लिए टाइप करें",
|
||||
"Recent": "हालिया",
|
||||
"navigate": "नेविगेट",
|
||||
"select": "चुनें",
|
||||
"close": "बंद करें",
|
||||
"Search Settings": "सेटिंग खोजें",
|
||||
"Page Margins": "पेज मार्जिन",
|
||||
"AI Provider": "AI प्रदाता",
|
||||
"Ollama URL": "Ollama URL",
|
||||
"Ollama Model": "Ollama मॉडल",
|
||||
"AI Gateway Model": "AI गेटवे मॉडल",
|
||||
"Actions": "क्रियाएं",
|
||||
"Navigation": "नेविगेशन",
|
||||
"Set status for {{count}} book(s)_one": "{{count}} किताब की स्थिति सेट करें",
|
||||
"Set status for {{count}} book(s)_other": "{{count}} किताबों की स्थिति सेट करें",
|
||||
"Mark as Unread": "अपठित के रूप में चिह्नित करें",
|
||||
"Mark as Finished": "समाप्त के रूप में चिह्नित करें",
|
||||
"Finished": "समाप्त",
|
||||
"Unread": "अपठित",
|
||||
"Clear Status": "स्थिति साफ़ करें",
|
||||
"Status": "स्थिति",
|
||||
"Loading": "लोड हो रहा है...",
|
||||
"Exit Paragraph Mode": "पैराग्राफ मोड से बाहर निकलें",
|
||||
"Paragraph Mode": "पैराग्राफ मोड",
|
||||
"Embedding Model": "एंबेडिंग मॉडल",
|
||||
"{{count}} book(s) synced_one": "{{count}} पुस्तक सिंक की गई",
|
||||
"{{count}} book(s) synced_other": "{{count}} पुस्तकें सिंक की गईं",
|
||||
"Unable to start RSVP": "RSVP शुरू करने में असमर्थ",
|
||||
"RSVP not supported for PDF": "PDF के लिए RSVP समर्थित नहीं है",
|
||||
"Select Chapter": "अध्याय चुनें",
|
||||
"Context": "संदर्भ",
|
||||
"Ready": "तैयार",
|
||||
"Chapter Progress": "अध्याय प्रगति",
|
||||
"words": "शब्द",
|
||||
"{{time}} left": "{{time}} शेष",
|
||||
"Reading progress": "पठन प्रगति",
|
||||
"Skip back 15 words": "15 शब्द पीछे",
|
||||
"Back 15 words (Shift+Left)": "15 शब्द पीछे (Shift+Left)",
|
||||
"Pause (Space)": "विराम (Space)",
|
||||
"Play (Space)": "चलाएं (Space)",
|
||||
"Skip forward 15 words": "15 शब्द आगे",
|
||||
"Forward 15 words (Shift+Right)": "15 शब्द आगे (Shift+Right)",
|
||||
"Decrease speed": "गति कम करें",
|
||||
"Slower (Left/Down)": "धीमी (Left/Down)",
|
||||
"Increase speed": "गति बढ़ाएं",
|
||||
"Faster (Right/Up)": "तेज़ (Right/Up)",
|
||||
"Start RSVP Reading": "RSVP पठन शुरू करें",
|
||||
"Choose where to start reading": "चुनें कि कहाँ से पढ़ना शुरू करना है",
|
||||
"From Chapter Start": "अध्याय के आरंभ से",
|
||||
"Start reading from the beginning of the chapter": "अध्याय की शुरुआत से पढ़ना शुरू करें",
|
||||
"Resume": "फिर से शुरू करें",
|
||||
"Continue from where you left off": "वहीं से जारी रखें जहाँ आपने छोड़ा था",
|
||||
"From Current Page": "वर्तमान पृष्ठ से",
|
||||
"Start from where you are currently reading": "जहाँ आप अभी पढ़ रहे हैं वहीं से शुरू करें",
|
||||
"From Selection": "चयन से",
|
||||
"Speed Reading Mode": "त्वरित पठन मोड",
|
||||
"Scroll left": "बाएं स्क्रॉल करें",
|
||||
"Scroll right": "दाएं स्क्रॉल करें",
|
||||
"Library Sync Progress": "पुस्तकालय सिंक प्रगति",
|
||||
"Back to library": "लाइब्रेरी पर वापस जाएं",
|
||||
"Group by...": "इसके द्वारा समूहबद्ध करें...",
|
||||
"Export as Plain Text": "प्लेन टेक्स्ट के रूप में एक्सपोर्ट करें",
|
||||
"Export as Markdown": "Markdown के रूप में एक्सपोर्ट करें",
|
||||
"Show Page Navigation Buttons": "नेविगेशन बटन",
|
||||
"Page {{number}}": "पृष्ठ {{number}}",
|
||||
"highlight": "हाइलाइट",
|
||||
"underline": "अंडरलाइन",
|
||||
"squiggly": "टेढ़ी-मेढ़ी रेखा",
|
||||
"red": "लाल",
|
||||
"violet": "बैंगनी",
|
||||
"blue": "नीला",
|
||||
"green": "हरा",
|
||||
"yellow": "पीला",
|
||||
"Select {{style}} style": "{{style}} शैली चुनें",
|
||||
"Select {{color}} color": "{{color}} रंग चुनें",
|
||||
"Close Book": "किताब बंद करें",
|
||||
"Speed Reading": "तीव्र पठन",
|
||||
"Close Speed Reading": "तीव्र पठन बंद करें",
|
||||
"Authors": "लेखक",
|
||||
"Books": "पुस्तकें",
|
||||
"Groups": "समूह",
|
||||
"Back to TTS Location": "टीटीएस स्थान पर वापस जाएं",
|
||||
"Metadata": "मेटाडाटा",
|
||||
"Image viewer": "छवि दर्शक",
|
||||
"Previous Image": "पिछली छवि",
|
||||
"Next Image": "अगली छवि",
|
||||
"Zoomed": "ज़ूम किया गया",
|
||||
"Zoom level": "ज़ूम स्तर",
|
||||
"Table viewer": "तालिका दर्शक",
|
||||
"Unable to connect to Readwise. Please check your network connection.": "Readwise से कनेक्ट करने में असमर्थ। कृपया अपना नेटवर्क कनेक्शन जांचें।",
|
||||
"Invalid Readwise access token": "अमान्य Readwise एक्सेस टोकन",
|
||||
"Disconnected from Readwise": "Readwise से डिस्कनेक्ट हो गया",
|
||||
"Never": "कभी नहीं",
|
||||
"Sync Enabled": "सिंक सक्षम",
|
||||
"Disconnect": "डिस्कनेक्ट करें",
|
||||
"Connect your Readwise account to sync highlights.": "हाइलाइट्स सिंक करने के लिए अपना Readwise खाता कनेक्ट करें।",
|
||||
"Get your access token at": "अपना एक्सेस टोकन यहाँ प्राप्त करें",
|
||||
"Access Token": "एक्सेस टोकन",
|
||||
"Paste your Readwise access token": "अपना Readwise एक्सेस टोकन पेस्ट करें",
|
||||
"Readwise Sync": "Readwise सिंक",
|
||||
"Push Highlights": "हाइलाइट्स भेजें",
|
||||
"Highlights synced to Readwise": "हाइलाइट्स Readwise में सिंक हो गए",
|
||||
"Readwise sync failed: no internet connection": "Readwise सिंक विफल रहा: कोई इंटरनेट कनेक्शन नहीं",
|
||||
"Readwise sync failed: {{error}}": "Readwise सिंक विफल रहा: {{error}}",
|
||||
"System Screen Brightness": "सिस्टम स्क्रीन चमक",
|
||||
"Page:": "पृष्ठ:",
|
||||
"Page: {{number}}": "पृष्ठ: {{number}}",
|
||||
"Annotation page number": "व्याख्या पृष्ठ संख्या",
|
||||
"Translating...": "अनुवाद हो रहा है...",
|
||||
"Show Battery Percentage": "बैटरी प्रतिशत दिखाएं",
|
||||
"Hide Scrollbar": "स्क्रॉलबार छुपाएँ",
|
||||
"Skip to last reading position": "अंतिम पढ़ने की स्थिति पर जाएँ",
|
||||
"Show context": "संदर्भ दिखाएँ",
|
||||
"Hide context": "संदर्भ छुपाएँ",
|
||||
"Decrease font size": "फ़ॉन्ट आकार घटाएँ",
|
||||
"Increase font size": "फ़ॉन्ट आकार बढ़ाएँ",
|
||||
"Focus": "फ़ोकस",
|
||||
"Theme color": "थीम रंग",
|
||||
"Import failed": "आयात विफल",
|
||||
"Available Formatters:": "उपलब्ध फ़ॉर्मेटर:",
|
||||
"Format date": "तारीख फ़ॉर्मेट करें",
|
||||
"Markdown block quote (> per line)": "Markdown ब्लॉक कोट (> प्रति पंक्ति)",
|
||||
"Newlines to <br>": "नई पंक्तियाँ <br> में",
|
||||
"Change case": "केस बदलें",
|
||||
"Trim whitespace": "रिक्त स्थान हटाएँ",
|
||||
"Truncate to n characters": "n अक्षरों तक छोटा करें",
|
||||
"Replace text": "टेक्स्ट बदलें",
|
||||
"Fallback value": "फ़ॉलबैक मान",
|
||||
"Get length": "लंबाई प्राप्त करें",
|
||||
"First/last element": "पहला/अंतिम तत्व",
|
||||
"Join array": "ऐरे जोड़ें",
|
||||
"Backup failed: {{error}}": "बैकअप विफल: {{error}}",
|
||||
"Select Backup": "बैकअप चुनें",
|
||||
"Restore failed: {{error}}": "पुनर्स्थापना विफल: {{error}}",
|
||||
"Backup & Restore": "बैकअप और पुनर्स्थापना",
|
||||
"Create a backup of your library or restore from a previous backup. Restoring will merge with your current library.": "अपनी लाइब्रेरी का बैकअप बनाएं या पिछले बैकअप से पुनर्स्थापित करें। पुनर्स्थापना आपकी वर्तमान लाइब्रेरी के साथ मर्ज होगी।",
|
||||
"Backup Library": "लाइब्रेरी बैकअप",
|
||||
"Restore Library": "लाइब्रेरी पुनर्स्थापित करें",
|
||||
"Creating backup...": "बैकअप बनाया जा रहा है...",
|
||||
"Restoring library...": "लाइब्रेरी पुनर्स्थापित हो रही है...",
|
||||
"{{current}} of {{total}} items": "{{current}} / {{total}} आइटम",
|
||||
"Backup completed successfully!": "बैकअप सफलतापूर्वक पूर्ण!",
|
||||
"Restore completed successfully!": "पुनर्स्थापना सफलतापूर्वक पूर्ण!",
|
||||
"Your library has been saved to the selected location.": "आपकी लाइब्रेरी चयनित स्थान पर सहेजी गई है।",
|
||||
"{{added}} books added, {{updated}} books updated.": "{{added}} पुस्तकें जोड़ी गईं, {{updated}} पुस्तकें अपडेट की गईं।",
|
||||
"Operation failed": "ऑपरेशन विफल",
|
||||
"Loading library...": "लाइब्रेरी लोड हो रही है...",
|
||||
"{{count}} books refreshed_one": "{{count}} पुस्तक रिफ्रेश हुई",
|
||||
"{{count}} books refreshed_other": "{{count}} पुस्तकें रिफ्रेश हुईं",
|
||||
"Failed to refresh metadata": "मेटाडेटा रिफ्रेश विफल",
|
||||
"Refresh Metadata": "मेटाडेटा रिफ्रेश करें",
|
||||
"Keyboard Shortcuts": "कीबोर्ड शॉर्टकट",
|
||||
"View all keyboard shortcuts": "सभी कीबोर्ड शॉर्टकट देखें",
|
||||
"Switch Sidebar Tab": "साइडबार टैब बदलें",
|
||||
"Toggle Notebook": "नोटबुक दिखाएँ/छुपाएँ",
|
||||
"Search in Book": "पुस्तक में खोजें",
|
||||
"Toggle Scroll Mode": "स्क्रॉल मोड टॉगल करें",
|
||||
"Toggle Select Mode": "चयन मोड टॉगल करें",
|
||||
"Toggle Bookmark": "बुकमार्क टॉगल करें",
|
||||
"Toggle Text to Speech": "टेक्स्ट टू स्पीच टॉगल करें",
|
||||
"Play / Pause TTS": "TTS चलाएँ / रोकें",
|
||||
"Toggle Paragraph Mode": "अनुच्छेद मोड टॉगल करें",
|
||||
"Highlight Selection": "चयन हाइलाइट करें",
|
||||
"Underline Selection": "चयन अंडरलाइन करें",
|
||||
"Annotate Selection": "चयन पर टिप्पणी करें",
|
||||
"Search Selection": "चयन खोजें",
|
||||
"Copy Selection": "चयन कॉपी करें",
|
||||
"Translate Selection": "चयन का अनुवाद करें",
|
||||
"Dictionary Lookup": "शब्दकोश में खोजें",
|
||||
"Read Aloud Selection": "चयन को ज़ोर से पढ़ें",
|
||||
"Proofread Selection": "चयन की प्रूफरीडिंग करें",
|
||||
"Open Settings": "सेटिंग्स खोलें",
|
||||
"Open Command Palette": "कमांड पैलेट खोलें",
|
||||
"Show Keyboard Shortcuts": "कीबोर्ड शॉर्टकट दिखाएँ",
|
||||
"Open Books": "पुस्तकें खोलें",
|
||||
"Toggle Fullscreen": "पूर्ण स्क्रीन टॉगल करें",
|
||||
"Close Window": "विंडो बंद करें",
|
||||
"Quit App": "ऐप से बाहर निकलें",
|
||||
"Go Left / Previous Page": "बाएँ जाएँ / पिछला पृष्ठ",
|
||||
"Go Right / Next Page": "दाएँ जाएँ / अगला पृष्ठ",
|
||||
"Go Up": "ऊपर जाएँ",
|
||||
"Go Down": "नीचे जाएँ",
|
||||
"Previous Chapter": "पिछला अध्याय",
|
||||
"Next Chapter": "अगला अध्याय",
|
||||
"Scroll Half Page Down": "आधा पृष्ठ नीचे स्क्रॉल करें",
|
||||
"Scroll Half Page Up": "आधा पृष्ठ ऊपर स्क्रॉल करें",
|
||||
"Save Note": "नोट सहेजें",
|
||||
"Single Section Scroll": "एकल अनुभाग स्क्रॉल",
|
||||
"General": "सामान्य",
|
||||
"Text to Speech": "पाठ से वाक्",
|
||||
"Zoom": "ज़ूम",
|
||||
"Window": "विंडो",
|
||||
"Your Bookshelf": "आपकी किताबों की अलमारी",
|
||||
"TTS": "पाठ से वाक्",
|
||||
"Media Info": "मीडिया जानकारी",
|
||||
"Update Frequency": "अपडेट आवृत्ति",
|
||||
"Every Sentence": "हर वाक्य",
|
||||
"Every Paragraph": "हर पैराग्राफ",
|
||||
"Every Chapter": "हर अध्याय",
|
||||
"TTS Media Info Update Frequency": "TTS मीडिया जानकारी अपडेट आवृत्ति",
|
||||
"Select reading speed": "पढ़ने की गति चुनें",
|
||||
"Drag to seek": "खोजने के लिए खींचें",
|
||||
"Punctuation Delay": "विराम चिह्न विलंब",
|
||||
"Please confirm that this OPDS connection will be proxied through Readest servers on the web app before continuing.": "कृपया जारी रखने से पहले पुष्टि करें कि यह OPDS कनेक्शन वेब ऐप पर Readest सर्वर के माध्यम से प्रॉक्सी किया जाएगा।",
|
||||
"Custom Headers (optional)": "कस्टम हेडर (वैकल्पिक)",
|
||||
"Add one header per line using \"Header-Name: value\".": "\"Header-Name: value\" का उपयोग करके प्रति पंक्ति एक हेडर जोड़ें।",
|
||||
"I understand this OPDS connection will be proxied through Readest servers on the web app. If I do not trust Readest with these credentials or headers, I should use the native app instead.": "मैं समझता/समझती हूँ कि यह OPDS कनेक्शन वेब ऐप पर Readest सर्वर के माध्यम से प्रॉक्सी किया जाएगा। यदि मुझे इन क्रेडेंशियल या हेडर के साथ Readest पर भरोसा नहीं है, तो मुझे नेटिव ऐप का उपयोग करना चाहिए।",
|
||||
"Unable to connect to Hardcover. Please check your network connection.": "Hardcover से कनेक्ट करने में असमर्थ। कृपया अपना नेटवर्क कनेक्शन जाँचें।",
|
||||
"Invalid Hardcover API token": "अमान्य Hardcover API टोकन",
|
||||
"Disconnected from Hardcover": "Hardcover से डिस्कनेक्ट हो गया",
|
||||
"Connect your Hardcover account to sync reading progress and notes.": "पढ़ने की प्रगति और नोट्स सिंक करने के लिए अपना Hardcover खाता कनेक्ट करें।",
|
||||
"Get your API token from hardcover.app → Settings → API.": "hardcover.app → सेटिंग्स → API से अपना API टोकन प्राप्त करें।",
|
||||
"API Token": "API टोकन",
|
||||
"Paste your Hardcover API token": "अपना Hardcover API टोकन पेस्ट करें",
|
||||
"Hardcover Sync": "Hardcover सिंक",
|
||||
"Push Notes": "नोट्स भेजें",
|
||||
"No annotations or excerpts to sync for this book.": "इस पुस्तक के लिए सिंक करने हेतु कोई एनोटेशन या अंश नहीं हैं।",
|
||||
"Configure Hardcover in Settings first.": "पहले सेटिंग्स में Hardcover कॉन्फ़िगर करें।",
|
||||
"No new Hardcover note changes to sync.": "सिंक करने के लिए कोई नया Hardcover नोट परिवर्तन नहीं है।",
|
||||
"Hardcover synced: {{inserted}} new, {{updated}} updated, {{skipped}} unchanged": "Hardcover सिंक हुआ: {{inserted}} नए, {{updated}} अपडेटेड, {{skipped}} अपरिवर्तित",
|
||||
"Hardcover notes sync failed: {{error}}": "Hardcover नोट्स सिंक विफल: {{error}}",
|
||||
"Reading progress synced to Hardcover": "पढ़ने की प्रगति Hardcover से सिंक हो गई",
|
||||
"Hardcover progress sync failed: {{error}}": "Hardcover प्रगति सिंक विफल: {{error}}",
|
||||
"ISBN": "ISBN",
|
||||
"Keep existing source identifier": "मौजूदा स्रोत पहचानकर्ता रखें",
|
||||
"Toggle Toolbar": "टूलबार टॉगल करें",
|
||||
"Split Hyphens": "हाइफ़न विभाजित करें",
|
||||
"Apply Theme Colors to PDF": "PDF पर थीम रंग लागू करें",
|
||||
"Name": "नाम",
|
||||
"Remove": "हटाएँ",
|
||||
"Edit OPDS Catalog": "OPDS कैटलॉग संपादित करें",
|
||||
"Save Changes": "परिवर्तन सहेजें",
|
||||
"Use Book Layout": "पुस्तक का लेआउट उपयोग करें",
|
||||
"End of this section. Continue to the next.": "इस अनुभाग का अंत। अगले पर जारी रखें।",
|
||||
"Auto-download": "स्वचालित डाउनलोड",
|
||||
"Auto-download new items": "नई आइटम स्वचालित रूप से डाउनलोड करें",
|
||||
"Automatically download new publications when the app syncs": "ऐप सिंक होने पर नए प्रकाशन स्वचालित रूप से डाउनलोड करें",
|
||||
"{{count}} new item(s) downloaded from OPDS_one": "OPDS से {{count}} नया आइटम डाउनलोड किया गया",
|
||||
"{{count}} new item(s) downloaded from OPDS_other": "OPDS से {{count}} नई आइटम डाउनलोड की गईं",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_one": "{{count}} OPDS कैटलॉग सिंक करने में विफल",
|
||||
"Failed to sync {{count}} OPDS catalog(s)_other": "{{count}} OPDS कैटलॉग सिंक करने में विफल",
|
||||
"Last synced {{when}}": "अंतिम सिंक्रनाइज़ेशन {{when}}",
|
||||
"Failed downloads": "असफल डाउनलोड",
|
||||
"No failed downloads": "कोई असफल डाउनलोड नहीं",
|
||||
"Attempts: {{count}}_one": "प्रयास: {{count}}",
|
||||
"Attempts: {{count}}_other": "प्रयास: {{count}}",
|
||||
"Skip": "छोड़ें",
|
||||
"Skip all": "सब छोड़ें",
|
||||
"Retry all": "सब पुनः प्रयास करें",
|
||||
"{{count}} failed_one": "{{count}} असफल",
|
||||
"{{count}} failed_other": "{{count}} असफल",
|
||||
"(none)": "(कोई नहीं)",
|
||||
"Identifiers": "पहचानकर्ता",
|
||||
"Read (Stream)": "पढ़ें (स्ट्रीम)",
|
||||
"Failed to start stream": "स्ट्रीम प्रारंभ करने में विफल",
|
||||
"No dictionaries enabled": "कोई शब्दकोश सक्षम नहीं है",
|
||||
"Enable a dictionary in Settings → Language → Dictionaries.": "सेटिंग्स → भाषा → शब्दकोश में जाकर शब्दकोश सक्षम करें।",
|
||||
"Wiktionary": "विक्षनरी",
|
||||
"Drag to reorder": "पुनः क्रमित करने के लिए खींचें",
|
||||
"Built-in": "अंतर्निर्मित",
|
||||
"Bundle is missing on this device. Re-import to use it.": "इस डिवाइस पर बंडल मौजूद नहीं है। उपयोग के लिए पुनः आयात करें।",
|
||||
"This dictionary format is not supported.": "यह शब्दकोश प्रारूप समर्थित नहीं है।",
|
||||
"MDict": "MDict",
|
||||
"StarDict": "StarDict",
|
||||
"Imported {{count}} dictionary_one": "{{count}} शब्दकोश आयात किया गया",
|
||||
"Imported {{count}} dictionary_other": "{{count}} शब्दकोश आयात किए गए",
|
||||
"Skipped incomplete bundles: {{names}}": "अधूरे बंडल छोड़े गए: {{names}}",
|
||||
"Failed to import dictionary: {{message}}": "शब्दकोश आयात विफल: {{message}}",
|
||||
"Dictionaries": "शब्दकोश",
|
||||
"Delete Dictionary": "शब्दकोश हटाएँ",
|
||||
"Importing…": "आयात हो रहा है…",
|
||||
"Import Dictionary": "शब्दकोश आयात करें",
|
||||
"No dictionaries available.": "कोई शब्दकोश उपलब्ध नहीं है।",
|
||||
"StarDict bundles need .ifo, .idx, and .dict.dz files (.syn optional).": "StarDict बंडल को .ifo, .idx और .dict.dz फ़ाइलें चाहिए (.syn वैकल्पिक)।",
|
||||
"Select all the bundle files together when importing.": "आयात करते समय बंडल की सभी फ़ाइलें एक साथ चुनें।",
|
||||
"Manage Dictionaries": "शब्दकोश प्रबंधित करें",
|
||||
"Select Dictionary Files": "शब्दकोश फ़ाइलें चुनें",
|
||||
"Read on Wikipedia →": "विकिपीडिया पर पढ़ें →",
|
||||
"This link can't be opened": "यह लिंक नहीं खोला जा सकता",
|
||||
"The annotation link is missing required information. The original link may have been truncated.": "एनोटेशन लिंक में आवश्यक जानकारी नहीं है। मूल लिंक छोटा हो गया हो सकता है।",
|
||||
"Go to Readest": "Readest पर जाएँ",
|
||||
"Open-source ebook reader for everyone, on every device.": "हर किसी के लिए, हर डिवाइस पर — ओपन-सोर्स ईबुक रीडर।",
|
||||
"Open in Readest": "Readest में खोलें",
|
||||
"If Readest didn't open automatically, choose an option below:": "अगर Readest स्वतः नहीं खुला, तो नीचे से कोई विकल्प चुनें:",
|
||||
"Continue reading where you left off.": "जहाँ छोड़ा था, वहीं से पढ़ना जारी रखें।",
|
||||
"Readest logo": "Readest का लोगो",
|
||||
"Opening Readest...": "Readest खुल रहा है…",
|
||||
"Open in Readest app": "Readest ऐप में खोलें",
|
||||
"Continue in browser": "ब्राउज़र में जारी रखें",
|
||||
"Don't have Readest?": "Readest नहीं है?",
|
||||
"Book not in your library": "यह किताब आपकी लाइब्रेरी में नहीं है",
|
||||
"Share Book": "पुस्तक साझा करें",
|
||||
"Could not create share link": "साझा लिंक नहीं बनाया जा सका",
|
||||
"Link copied": "लिंक कॉपी किया गया",
|
||||
"Could not copy link": "लिंक कॉपी नहीं किया जा सका",
|
||||
"Share revoked": "साझा रद्द किया गया",
|
||||
"Could not revoke share": "साझा रद्द नहीं किया जा सका",
|
||||
"Uploading book…": "पुस्तक अपलोड हो रही है…",
|
||||
"Generating…": "बना रहा है…",
|
||||
"Generate share link": "साझा लिंक बनाएँ",
|
||||
"Share URL": "साझा URL",
|
||||
"Copy link": "लिंक कॉपी करें",
|
||||
"Copied": "कॉपी किया गया",
|
||||
"Share via…": "के माध्यम से साझा करें…",
|
||||
"Expires {{date}}": "{{date}} को समाप्त",
|
||||
"Revoking…": "रद्द कर रहा है…",
|
||||
"Revoke share": "साझा रद्द करें",
|
||||
"Sign in to share books": "पुस्तकें साझा करने के लिए साइन इन करें",
|
||||
"Expiring soon": "जल्द ही समाप्त होगा",
|
||||
"Missing share token": "साझा टोकन गायब है",
|
||||
"Could not add to your library": "आपकी लाइब्रेरी में जोड़ा नहीं जा सका",
|
||||
"This share link is no longer available": "यह साझा लिंक अब उपलब्ध नहीं है",
|
||||
"The original link may have expired or been revoked.": "मूल लिंक समाप्त हो गया हो सकता है या रद्द कर दिया गया हो।",
|
||||
"Get Readest": "Readest प्राप्त करें",
|
||||
"Loading shared book…": "साझा पुस्तक लोड हो रही है…",
|
||||
"Adding…": "जोड़ रहा है…",
|
||||
"Add to my library": "मेरी लाइब्रेरी में जोड़ें",
|
||||
"Could not load your shares": "आपके साझा लोड नहीं किए जा सके",
|
||||
"Revoked": "रद्द",
|
||||
"Expired": "समाप्त",
|
||||
"Shared books": "साझा पुस्तकें",
|
||||
"You haven't shared any books yet": "आपने अभी तक कोई पुस्तक साझा नहीं की है",
|
||||
"Open a book and tap Share to send it to a friend.": "किसी पुस्तक को खोलें और मित्र को भेजने के लिए साझा पर टैप करें।",
|
||||
"{{count}} active_one": "{{count}} सक्रिय",
|
||||
"{{count}} active_other": "{{count}} सक्रिय",
|
||||
"{{count}} downloads_one": "{{count}} डाउनलोड",
|
||||
"{{count}} downloads_other": "{{count}} डाउनलोड",
|
||||
"starts at saved page": "सहेजे गए पृष्ठ से शुरू",
|
||||
"More actions": "अधिक क्रियाएँ",
|
||||
"Loading…": "लोड हो रहा है…",
|
||||
"Load more": "और लोड करें",
|
||||
"Could not load shared book": "साझा पुस्तक लोड नहीं की जा सकी",
|
||||
"The share link is missing required information.": "साझा लिंक में आवश्यक जानकारी नहीं है।",
|
||||
"Please check your connection and try again.": "कृपया अपना कनेक्शन जांचें और पुनः प्रयास करें।",
|
||||
"Sign in to import shared books": "साझा पुस्तकें आयात करने के लिए साइन इन करें",
|
||||
"Already in your library": "पहले से ही आपकी लाइब्रेरी में है",
|
||||
"Added to your library": "आपकी लाइब्रेरी में जोड़ा गया",
|
||||
"Could not import shared book": "साझा पुस्तक आयात नहीं की जा सकी",
|
||||
"Manage Shared Links": "साझा लिंक प्रबंधित करें",
|
||||
"Expires in": "समाप्त होगा",
|
||||
"{{count}} days_one": "1 दिन",
|
||||
"{{count}} days_other": "{{count}} दिन",
|
||||
"Expires in {{count}} days_one": "1 दिन में समाप्त",
|
||||
"Expires in {{count}} days_other": "{{count}} दिनों में समाप्त",
|
||||
"Expires in {{count}} hours_one": "1 घंटे में समाप्त",
|
||||
"Expires in {{count}} hours_other": "{{count}} घंटों में समाप्त",
|
||||
"Open in app": "ऐप में खोलें",
|
||||
"Shared with you": "आपके साथ साझा किया गया",
|
||||
"Downloading… {{percent}}%": "डाउनलोड हो रहा है… {{percent}}%",
|
||||
"Import progress": "आयात प्रगति",
|
||||
"Share reading progress": "पढ़ने की प्रगति साझा करें",
|
||||
"Includes your reading progress": "आपकी पढ़ने की प्रगति शामिल है",
|
||||
"Synced {{time}}": "{{time}} सिंक हुआ",
|
||||
"Sync Info": "सिंक जानकारी",
|
||||
"Last Synced": "अंतिम सिंक",
|
||||
"DICT": "DICT",
|
||||
"Slob": "Slob",
|
||||
"DICT bundles need a .index file and a .dict.dz file.": "DICT बंडल को एक .index फ़ाइल और एक .dict.dz फ़ाइल की आवश्यकता होती है।",
|
||||
"Slob bundles need a .slob file.": "Slob बंडल को एक .slob फ़ाइल की आवश्यकता होती है।",
|
||||
"URL template must start with http(s):// and contain %WORD%.": "URL टेम्पलेट http(s):// से शुरू होना चाहिए और उसमें %WORD% होना चाहिए।",
|
||||
"Web": "वेब",
|
||||
"Add Web Search": "वेब खोज जोड़ें",
|
||||
"Edit Web Search": "वेब खोज संपादित करें",
|
||||
"e.g. Google": "उदा. Google",
|
||||
"URL Template": "URL टेम्पलेट",
|
||||
"Use %WORD% where the looked-up word should appear.": "%WORD% का उपयोग वहां करें जहां खोजा गया शब्द दिखाई देना चाहिए।",
|
||||
"Open the search result in your browser:": "अपने ब्राउज़र में खोज परिणाम खोलें:",
|
||||
"Open in {{name}}": "{{name}} में खोलें",
|
||||
"Book Fingerprint": "पुस्तक फिंगरप्रिंट",
|
||||
"Search the web": "वेब पर खोजें",
|
||||
"App deeplink (readest://)": "ऐप डीप लिंक (readest://)",
|
||||
"Universal web link (https://)": "सार्वभौमिक वेब लिंक (https://)",
|
||||
"Name cannot be empty.": "नाम खाली नहीं हो सकता।",
|
||||
"Replaced {{count}} existing dictionary_one": "{{count}} मौजूदा शब्दकोश बदला गया",
|
||||
"Replaced {{count}} existing dictionary_other": "{{count}} मौजूदा शब्दकोश बदले गए",
|
||||
"Cancel Edit": "संपादन रद्द करें",
|
||||
"Edit Dictionary": "शब्दकोश संपादित करें",
|
||||
"MDict bundles use .mdx files; companion .mdd and .css files are optional.": "MDict बंडल .mdx फ़ाइलों का उपयोग करते हैं; साथ की .mdd और .css फ़ाइलें वैकल्पिक हैं।",
|
||||
"Dictionary name": "शब्दकोश का नाम",
|
||||
"This audio can't play here — the dictionary uses an outdated format. Try one with Opus, MP3, or WAV audio.": "यह ऑडियो यहाँ नहीं चलाया जा सकता — शब्दकोश पुराने प्रारूप का उपयोग करता है। Opus, MP3 या WAV ऑडियो वाला कोई आज़माएँ।",
|
||||
"File uploaded: {{title}}": "फ़ाइल अपलोड की गई: {{title}}",
|
||||
"File downloaded: {{title}}": "फ़ाइल डाउनलोड की गई: {{title}}",
|
||||
"Deleted cloud copy of the file: {{title}}": "फ़ाइल की क्लाउड प्रति हटाई गई: {{title}}",
|
||||
"Failed to upload file: {{title}}": "फ़ाइल अपलोड करने में विफल: {{title}}",
|
||||
"Failed to download file: {{title}}": "फ़ाइल डाउनलोड करने में विफल: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "फ़ाइल की क्लाउड प्रति हटाने में विफल: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% उपयोग किया गया",
|
||||
"Set PIN…": "PIN सेट करें…",
|
||||
"Require a 4-digit PIN to open Readest": "Readest खोलने के लिए 4-अंकों का PIN आवश्यक करें",
|
||||
"Change PIN…": "PIN बदलें…",
|
||||
"Disable PIN…": "PIN अक्षम करें…",
|
||||
"Sync passphrase ready": "सिंक पासफ़्रेज़ तैयार",
|
||||
"This permanently deletes the encrypted credentials we sync (e.g., OPDS catalog passwords) on every device. Local copies are preserved. You will need to re-enter the sync passphrase or set a new one. Continue?": "यह हर डिवाइस पर हमारे सिंक किए गए एन्क्रिप्टेड क्रेडेंशियल (जैसे, OPDS कैटलॉग पासवर्ड) को स्थायी रूप से हटा देता है। स्थानीय प्रतियां सुरक्षित रहती हैं। आपको सिंक पासफ़्रेज़ फिर से दर्ज करना होगा या एक नया सेट करना होगा। जारी रखें?",
|
||||
"Sync passphrase forgotten — all encrypted fields cleared": "सिंक पासफ़्रेज़ भूल गए — सभी एन्क्रिप्टेड फ़ील्ड हटा दिए गए",
|
||||
"Sync passphrase": "सिंक पासफ़्रेज़",
|
||||
"Set passphrase": "पासफ़्रेज़ सेट करें",
|
||||
"Forgot passphrase": "पासफ़्रेज़ भूल गए",
|
||||
"Incorrect PIN": "गलत PIN",
|
||||
"App locked": "ऐप लॉक है",
|
||||
"Enter your PIN": "अपना PIN दर्ज करें",
|
||||
"Readest is locked. Enter your 4-digit PIN to continue.": "Readest लॉक है। जारी रखने के लिए अपना 4-अंकों का PIN दर्ज करें।",
|
||||
"PIN code": "PIN कोड",
|
||||
"Forgetting your PIN locks you out of this device. You'll need to clear the app's data to reset it.": "PIN भूलने पर आप इस डिवाइस से बाहर हो जाएंगे। इसे रीसेट करने के लिए आपको ऐप का डेटा मिटाना होगा।",
|
||||
"Passphrase must be at least 8 characters": "पासफ़्रेज़ कम से कम 8 वर्णों का होना चाहिए",
|
||||
"Passphrases do not match": "पासफ़्रेज़ मेल नहीं खाते",
|
||||
"Set sync passphrase": "सिंक पासफ़्रेज़ सेट करें",
|
||||
"Enter sync passphrase": "सिंक पासफ़्रेज़ दर्ज करें",
|
||||
"A sync passphrase encrypts your sensitive fields (like OPDS catalog credentials) before they sync. We never see this passphrase. Pick something memorable — there is no recovery without it.": "एक सिंक पासफ़्रेज़ आपके संवेदनशील फ़ील्ड (जैसे OPDS कैटलॉग क्रेडेंशियल) को सिंक होने से पहले एन्क्रिप्ट करता है। हम यह पासफ़्रेज़ कभी नहीं देखते। ऐसा कुछ चुनें जो याद रहे — इसके बिना कोई पुनर्प्राप्ति नहीं है।",
|
||||
"Enter the sync passphrase you set on another device to decrypt your synced credentials.": "अपने सिंक किए गए क्रेडेंशियल को डिक्रिप्ट करने के लिए वह सिंक पासफ़्रेज़ दर्ज करें जो आपने किसी अन्य डिवाइस पर सेट किया था।",
|
||||
"Confirm passphrase": "पासफ़्रेज़ की पुष्टि करें",
|
||||
"Unlock": "अनलॉक करें",
|
||||
"Set PIN": "PIN सेट करें",
|
||||
"Change PIN": "PIN बदलें",
|
||||
"Disable PIN": "PIN अक्षम करें",
|
||||
"Enter your current PIN, then choose a new 4-digit PIN.": "अपना वर्तमान PIN दर्ज करें, फिर एक नया 4-अंकों का PIN चुनें।",
|
||||
"Enter your current PIN to disable the app lock.": "ऐप लॉक अक्षम करने के लिए अपना वर्तमान PIN दर्ज करें।",
|
||||
"PIN must be {{length}} digits": "PIN {{length}} अंकों का होना चाहिए",
|
||||
"PINs do not match": "PIN मेल नहीं खाते",
|
||||
"Current PIN": "वर्तमान PIN",
|
||||
"New PIN": "नया PIN",
|
||||
"Confirm new PIN": "नए PIN की पुष्टि करें",
|
||||
"Manage Sync": "सिंक प्रबंधित करें",
|
||||
"Imported book files and library metadata": "आयातित पुस्तक फ़ाइलें और लाइब्रेरी मेटाडेटा",
|
||||
"Last-read position, bookmarks, and per-book preferences": "अंतिम पठन स्थिति, बुकमार्क और प्रति-पुस्तक प्राथमिकताएँ",
|
||||
"Highlights and notes": "हाइलाइट और नोट्स",
|
||||
"Fonts": "फ़ॉन्ट",
|
||||
"Custom font files": "कस्टम फ़ॉन्ट फ़ाइलें",
|
||||
"Backgrounds": "पृष्ठभूमियाँ",
|
||||
"Custom background textures": "कस्टम पृष्ठभूमि बनावट",
|
||||
"OPDS catalogs": "OPDS कैटलॉग",
|
||||
"Saved catalog URLs and (encrypted) credentials": "सहेजे गए कैटलॉग URL और (एन्क्रिप्टेड) क्रेडेंशियल्स",
|
||||
"Wrong sync passphrase — synced credentials could not be decrypted": "गलत सिंक पासफ़्रेज़ — सिंक की गई क्रेडेंशियल्स को डिक्रिप्ट नहीं किया जा सका",
|
||||
"Sync passphrase data on the server was reset. Re-encrypting your credentials under the new passphrase…": "सर्वर पर सिंक पासफ़्रेज़ डेटा रीसेट कर दिया गया। आपकी क्रेडेंशियल्स को नए पासफ़्रेज़ से पुनः एन्क्रिप्ट किया जा रहा है…",
|
||||
"Failed to decrypt synced credentials": "सिंक की गई क्रेडेंशियल्स को डिक्रिप्ट करने में विफल",
|
||||
"Imported dictionary bundles and settings": "आयातित शब्दकोश बंडल और सेटिंग्स",
|
||||
"Choose what syncs across your devices. Disabling a category stops this device from sending or receiving rows of that kind. Anything already on the server is left alone, re-enabling resumes from where you stopped.": "चुनें कि आपके डिवाइसों के बीच क्या सिंक हो। किसी श्रेणी को अक्षम करने पर यह डिवाइस उस प्रकार की प्रविष्टियाँ भेजना या प्राप्त करना बंद कर देता है। सर्वर पर पहले से मौजूद कुछ भी नहीं बदलता, फिर से सक्षम करने पर वहीं से जारी होता है जहाँ आपने रोका था।",
|
||||
"Data Sync": "डेटा सिंक",
|
||||
"Manage Fonts": "फ़ॉन्ट प्रबंधित करें",
|
||||
"App settings": "ऐप सेटिंग्स",
|
||||
"Theme, highlight colours, integrations (KOSync, Readwise, Hardcover), and dictionary order": "थीम, हाइलाइट रंग, एकीकरण (KOSync, Readwise, Hardcover) और शब्दकोश क्रम",
|
||||
"Required while Dictionaries sync is enabled": "शब्दकोश सिंक सक्षम होने पर आवश्यक",
|
||||
"Resets in {{hours}} hr {{minutes}} min": "{{hours}} घं {{minutes}} मि में रीसेट",
|
||||
"Pick a 4-digit PIN. You will need to enter it every time you open Readest. There is no way to recover a forgotten PIN. Clearing the app data is the only way to reset it.": "एक 4-अंकों का पिन चुनें। Readest खोलने पर आपको हर बार इसे दर्ज करना होगा। भूले हुए पिन को पुनः प्राप्त करने का कोई तरीका नहीं है। ऐप डेटा मिटाना इसे रीसेट करने का एकमात्र तरीका है।",
|
||||
"Credentials": "क्रेडेंशियल",
|
||||
"Tokens, usernames, and passwords for OPDS, KOReader, Hardcover, and Readwise. When disabled, credentials remain on this device only and are never uploaded.": "OPDS, KOReader, Hardcover और Readwise के लिए टोकन, उपयोगकर्ता नाम और पासवर्ड। अक्षम होने पर, क्रेडेंशियल केवल इसी डिवाइस पर रहते हैं और कभी अपलोड नहीं किए जाते।",
|
||||
"Sensitive synced fields are encrypted before upload. Set a passphrase now or later when encryption is needed.": "संवेदनशील सिंक किए गए फ़ील्ड अपलोड से पहले एन्क्रिप्ट किए जाते हैं। अभी पासफ़्रेज़ सेट करें या बाद में जब एन्क्रिप्शन की आवश्यकता हो।",
|
||||
"Saved to this account. You will be prompted for it when decrypting credentials.": "इस खाते में सहेजा गया। क्रेडेंशियल डिक्रिप्ट करते समय आपसे पूछा जाएगा।",
|
||||
"Reading progress on this device differs from \"{{deviceName}}\".": "इस डिवाइस पर पढ़ने की प्रगति \"{{deviceName}}\" से भिन्न है।",
|
||||
"This device": "यह डिवाइस",
|
||||
"Red": "लाल",
|
||||
"Yellow": "पीला",
|
||||
"Green": "हरा",
|
||||
"Blue": "नीला",
|
||||
"Violet": "बैंगनी",
|
||||
"Edit color": "रंग संपादित करें",
|
||||
"Add custom color": "कस्टम रंग जोड़ें",
|
||||
"Add label": "लेबल जोड़ें",
|
||||
"Choose color": "रंग चुनें",
|
||||
"Connected to Hardcover. Last synced {{time}}.": "Hardcover से कनेक्ट किया गया। अंतिम सिंक {{time}}।",
|
||||
"Integrations": "एकीकरण",
|
||||
"Hardcover": "Hardcover",
|
||||
"Connected to Readwise. Last synced {{time}}.": "Readwise से कनेक्ट किया गया। अंतिम सिंक {{time}}।",
|
||||
"Readwise": "Readwise",
|
||||
"Browse and download books from online catalogs.": "ऑनलाइन कैटलॉग से पुस्तकें ब्राउज़ करें और डाउनलोड करें।",
|
||||
"Connected as {{user}}": "{{user}} के रूप में कनेक्ट किया गया",
|
||||
"Not connected": "कनेक्ट नहीं है",
|
||||
"{{count}} catalog_one": "{{count}} कैटलॉग",
|
||||
"{{count}} catalog_other": "{{count}} कैटलॉग",
|
||||
"No catalogs": "कोई कैटलॉग नहीं",
|
||||
"Connect Readest to external services for sync, highlights, and catalogs.": "सिंक, हाइलाइट और कैटलॉग के लिए Readest को बाहरी सेवाओं से कनेक्ट करें।",
|
||||
"Reading Sync": "रीडिंग सिंक",
|
||||
"Content Sources": "सामग्री स्रोत",
|
||||
"Scroll tabs": "टैब स्क्रॉल करें",
|
||||
"Catalog actions": "कैटलॉग क्रियाएँ",
|
||||
"File Content": "फ़ाइल सामग्री",
|
||||
"Start your library": "अपनी लाइब्रेरी शुरू करें",
|
||||
"Pick a book from your device to add it to your library.": "अपनी लाइब्रेरी में जोड़ने के लिए अपने डिवाइस से एक किताब चुनें।",
|
||||
"Drop a book anywhere on this window, or pick one from your computer.": "इस विंडो में कहीं भी एक किताब छोड़ें, या अपने कंप्यूटर से कोई एक चुनें।",
|
||||
"Sign in to sync your library": "अपनी लाइब्रेरी सिंक करने के लिए साइन इन करें"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user