Compare commits

...
Author SHA1 Message Date
Frosted 007047babf Translated using Weblate (Turkish)
Currently translated at 100.0% (968 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
2026-10-16 19:51:25 +02:00
Frosted dd1cb22c28 Translated using Weblate (Turkish)
Currently translated at 99.7% (966 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
2026-10-16 15:51:23 +02:00
Frosted 1d01e021e5 Translated using Weblate (Turkish)
Currently translated at 97.9% (948 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
2026-10-16 14:51:21 +02:00
karigane 60313a6294 Translated using Weblate (Japanese)
Currently translated at 95.4% (924 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
2026-10-15 18:51:24 +02:00
karigane 87604cae3b Translated using Weblate (Japanese)
Currently translated at 93.8% (908 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
2026-10-15 17:51:25 +02:00
Nicola Bortoletto fa0c70e90d Translated using Weblate (Italian)
Currently translated at 92.5% (896 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
2026-10-15 13:51:32 +02:00
Dexroneum 10ac6262bd Translated using Weblate (Russian)
Currently translated at 100.0% (968 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
2026-10-14 14:51:26 +02:00
Nicola Bortoletto ac6d891d6f Translated using Weblate (Italian)
Currently translated at 91.9% (890 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
2026-10-14 07:51:26 +02:00
Димитър Георгиев b1fdc936be Translated using Weblate (Bulgarian)
Currently translated at 81.5% (789 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bg/
2026-10-14 07:51:23 +02:00
Daniil Paschenko 91dec0bddd Translated using Weblate (Russian)
Currently translated at 100.0% (23 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ru/
2026-10-14 04:51:24 +02:00
Daniil Paschenko 6b67181016 Translated using Weblate (Russian)
Currently translated at 98.8% (957 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
2026-10-14 04:51:22 +02:00
Димитър Георгиев 4defecf82e Translated using Weblate (Bulgarian)
Currently translated at 80.8% (783 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bg/
2026-10-13 22:51:22 +00:00
Szafranek13 d604a2778f Translated using Weblate (Esperanto)
Currently translated at 100.0% (968 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
2026-10-13 17:51:22 +02:00
Szafranek13 1a3dc49e48 Translated using Weblate (Esperanto)
Currently translated at 95.9% (929 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
2026-10-13 16:51:28 +02:00
Adam Havránek 5f6c12ac22 Translated using Weblate (Czech)
Currently translated at 100.0% (968 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cs/
2026-10-13 16:51:23 +02:00
abc0922001 3603bbcdd9 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (968 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
2026-10-13 15:51:25 +02:00
Szafranek13 a9e5ac8301 Translated using Weblate (Esperanto)
Currently translated at 95.1% (921 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
2026-10-13 15:51:22 +02:00
Szafranek13 f3ad489440 Translated using Weblate (Esperanto)
Currently translated at 94.6% (916 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
2026-10-13 14:52:06 +02:00
Димитър Георгиев 9efc88b525 Translated using Weblate (Bulgarian)
Currently translated at 81.4% (788 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bg/
2026-10-13 11:51:22 +00:00
ZerOriSama 9a3c37bbc3 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.1% (960 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
2026-10-13 12:51:28 +02:00
Димитър Георгиев d6662e7e52 Translated using Weblate (Bulgarian)
Currently translated at 76.0% (736 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bg/
2026-10-13 12:51:25 +02:00
GpixeL 440b92a563 Translated using Weblate (Indonesian)
Currently translated at 98.6% (955 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
2026-10-13 10:51:29 +02:00
Infy Milliaria 09ec594adb Translated using Weblate (Filipino)
Currently translated at 100.0% (968 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
2026-10-13 10:51:25 +02:00
GpixeL b9eac64b40 Translated using Weblate (Indonesian)
Currently translated at 92.4% (895 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
2026-10-13 07:51:23 +00:00
Hosted Weblate a2a559c01a Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/
2026-10-13 06:46:25 +02:00
Hosted Weblate 5910c87aa9 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/
2026-10-13 06:46:20 +02:00
Anonymous 6028742a41 Translated using Weblate (Azerbaijani)
Currently translated at 0.5% (5 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/az/
2026-10-13 06:46:02 +02:00
icarus suraci f1b2a9be69 Translated using Weblate (Kurdish (Kurmanji))
Currently translated at 26.6% (258 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kmr/
2026-10-13 06:45:52 +02:00
Anonymous 415be3f88d Translated using Weblate (Kurdish (Kurmanji))
Currently translated at 26.6% (258 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kmr/
2026-10-13 06:45:48 +02:00
Anonymous c702f3dff1 Translated using Weblate (Tatar)
Currently translated at 0.5% (5 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tt/
2026-10-13 06:45:40 +02:00
Anonymous 974f1bcd71 Translated using Weblate (French)
Currently translated at 92.5% (896 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/
2026-10-13 06:45:28 +02:00
Anonymous 6303293a27 Translated using Weblate (Tamil)
Currently translated at 90.0% (872 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ta/
2026-10-13 06:45:11 +02:00
Horace Johnson 9181833174 Translated using Weblate (Burmese)
Currently translated at 27.0% (262 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/my/
2026-10-13 06:45:07 +02:00
Anonymous 33e93ea96c Translated using Weblate (Burmese)
Currently translated at 27.0% (262 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/my/
2026-10-13 06:45:04 +02:00
Anonymous 5770ae52e3 Translated using Weblate (Assamese)
Currently translated at 91.4% (885 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/as/
2026-10-13 06:44:49 +02:00
Anonymous c7fc76902c Translated using Weblate (Malayalam)
Currently translated at 78.2% (18 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ml/
2026-10-13 06:44:46 +02:00
Anonymous cc869ce6f1 Translated using Weblate (Malayalam)
Currently translated at 23.3% (226 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/
2026-10-13 06:44:40 +02:00
Anonymous b56ca7464c Translated using Weblate (Uzbek)
Currently translated at 13.0% (3 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/uz/
2026-10-13 06:44:31 +02:00
Anonymous 110a20a2e2 Translated using Weblate (Ukrainian)
Currently translated at 78.2% (18 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/uk/
2026-10-13 06:44:29 +02:00
Anonymous 5f194c5584 Translated using Weblate (Thai)
Currently translated at 69.5% (16 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/th/
2026-10-13 06:44:27 +02:00
Anonymous cbfd9b0e0e Translated using Weblate (Telugu)
Currently translated at 4.3% (1 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/te/
2026-10-13 06:44:26 +02:00
Anonymous 065592839e Translated using Weblate (Serbian)
Currently translated at 73.9% (17 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sr/
2026-10-13 06:44:24 +02:00
Anonymous 17389adf99 Translated using Weblate (Albanian)
Currently translated at 60.8% (14 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sq/
2026-10-13 06:44:22 +02:00
Anonymous 054a59d15d Translated using Weblate (Slovak)
Currently translated at 69.5% (16 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sk/
2026-10-13 06:44:20 +02:00
Anonymous 97b6f63531 Translated using Weblate (Kurdish (Southern))
Currently translated at 8.6% (2 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sdh/
2026-10-13 06:44:19 +02:00
Anonymous 6f0521f935 Translated using Weblate (Sardinian)
Currently translated at 78.2% (18 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sc/
2026-10-13 06:44:17 +02:00
Anonymous 76f397df82 Translated using Weblate (Yakut)
Currently translated at 30.4% (7 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sah/
2026-10-13 06:44:16 +02:00
Anonymous c665af039d Translated using Weblate (Sanskrit)
Currently translated at 82.6% (19 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sa/
2026-10-13 06:44:14 +02:00
Anonymous 4222cb1904 Translated using Weblate (Portuguese)
Currently translated at 91.3% (21 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt/
2026-10-13 06:44:12 +02:00
ZenVinny b1468af4f6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (23 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt_BR/
2026-10-13 06:44:11 +02:00
Anonymous 8bd4234382 Translated using Weblate (Polish)
Currently translated at 82.6% (19 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pl/
2026-10-13 06:44:09 +02:00
Anonymous 5b3a019fd8 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 17.3% (4 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/nn/
2026-10-13 06:44:07 +02:00
Anonymous be907fff58 Translated using Weblate (Nepali)
Currently translated at 91.3% (21 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ne/
2026-10-13 06:44:05 +02:00
Anonymous c00f31207d Translated using Weblate (Norwegian Bokmål)
Currently translated at 69.5% (16 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/nb/
2026-10-13 06:44:04 +02:00
Anonymous f668e3c7ea Translated using Weblate (Marathi)
Currently translated at 8.6% (2 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/mr/
2026-10-13 06:44:02 +02:00
Anonymous 8fb0e4607c Translated using Weblate (Latvian)
Currently translated at 69.5% (16 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/lv/
2026-10-13 06:44:01 +02:00
Anonymous 39c9240f4e Translated using Weblate (Lithuanian)
Currently translated at 56.5% (13 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/lt/
2026-10-13 06:43:59 +02:00
Anonymous b939948fd5 Translated using Weblate (Kannada)
Currently translated at 47.8% (11 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/kn/
2026-10-13 06:43:57 +02:00
Anonymous e857902133 Translated using Weblate (Khmer (Central))
Currently translated at 8.6% (2 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/km/
2026-10-13 06:43:56 +02:00
Anonymous 5cac1660f5 Translated using Weblate (Kazakh)
Currently translated at 69.5% (16 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/kk/
2026-10-13 06:43:54 +02:00
Anonymous 7d29b904d5 Translated using Weblate (Georgian)
Currently translated at 78.2% (18 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ka/
2026-10-13 06:43:53 +02:00
Anonymous a20d28566a Translated using Weblate (Javanese)
Currently translated at 91.3% (21 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/jv/
2026-10-13 06:43:51 +02:00
Anonymous ffd5e1e985 Translated using Weblate (Italian)
Currently translated at 91.3% (21 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/it/
2026-10-13 06:43:49 +02:00
Anonymous 4ae33bac27 Translated using Weblate (Hungarian)
Currently translated at 78.2% (18 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/hu/
2026-10-13 06:43:47 +02:00
Anonymous c5defb1e01 Translated using Weblate (Hindi)
Currently translated at 91.3% (21 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/hi/
2026-10-13 06:43:45 +02:00
Anonymous 974d25fdcc Translated using Weblate (Galician)
Currently translated at 78.2% (18 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/gl/
2026-10-13 06:43:43 +02:00
Anonymous 823819e14f Translated using Weblate (Finnish)
Currently translated at 69.5% (16 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fi/
2026-10-13 06:43:40 +02:00
Anonymous 9bfb6844a8 Translated using Weblate (Basque)
Currently translated at 73.9% (17 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/eu/
2026-10-13 06:43:38 +02:00
Anonymous 1960eccf12 Translated using Weblate (Esperanto)
Currently translated at 91.3% (21 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/eo/
2026-10-13 06:43:36 +02:00
Anonymous c082f68614 Translated using Weblate (Danish)
Currently translated at 17.3% (4 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/da/
2026-10-13 06:43:34 +02:00
Anonymous 57e3992ac8 Translated using Weblate (Chuvash)
Currently translated at 69.5% (16 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/cv/
2026-10-13 06:43:32 +02:00
Anonymous 6c047b3a0c Translated using Weblate (Cebuano)
Currently translated at 26.0% (6 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ceb/
2026-10-13 06:43:30 +02:00
Anonymous e78aaf3753 Translated using Weblate (Bengali)
Currently translated at 86.9% (20 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/bn/
2026-10-13 06:43:28 +02:00
Anonymous 11d6a0ca07 Translated using Weblate (Bulgarian)
Currently translated at 78.2% (18 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/bg/
2026-10-13 06:43:26 +02:00
Anonymous b01df8fd3b Translated using Weblate (Amharic)
Currently translated at 13.0% (3 of 23 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/am/
2026-10-13 06:43:24 +02:00
Anonymous 19071dfafd Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 95.7% (927 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
2026-10-13 06:43:22 +02:00
abc0922001 47d561dd60 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 95.7% (927 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
2026-10-13 06:43:14 +02:00
Anonymous ef07c14566 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 98.8% (957 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
2026-10-13 06:43:10 +02:00
nay nai 9c12032182 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 98.8% (957 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
2026-10-13 06:43:02 +02:00
Anonymous 33b8d1f131 Translated using Weblate (Vietnamese)
Currently translated at 93.4% (905 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
2026-10-13 06:42:56 +02:00
Anonymous 0411a3de4e Translated using Weblate (Uzbek)
Currently translated at 35.5% (344 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/uz/
2026-10-13 06:42:49 +02:00
Anonymous 0913f43d3e Translated using Weblate (Ukrainian)
Currently translated at 84.6% (819 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/uk/
2026-10-13 06:42:37 +02:00
Anonymous 93052ca226 Translated using Weblate (Turkish)
Currently translated at 93.4% (905 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
2026-10-13 06:42:22 +02:00
Anonymous 195dacd8de Translated using Weblate (Thai)
Currently translated at 83.8% (812 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/th/
2026-10-13 06:42:15 +02:00
Anonymous 6110ed7bf3 Translated using Weblate (Telugu)
Currently translated at 20.1% (195 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/te/
2026-10-13 06:42:05 +02:00
Anonymous 815b8d84d8 Translated using Weblate (Swedish)
Currently translated at 92.6% (897 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sv/
2026-10-13 06:41:52 +02:00
David Katrinka 9f1d5cfa08 Translated using Weblate (Serbian)
Currently translated at 77.6% (752 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sr/
2026-10-13 06:41:44 +02:00
Anonymous 94ce482eb1 Translated using Weblate (Serbian)
Currently translated at 77.6% (752 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sr/
2026-10-13 06:41:42 +02:00
Anonymous 0f8c1c4292 Translated using Weblate (Albanian)
Currently translated at 69.4% (672 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sq/
2026-10-13 06:41:32 +02:00
KraXen f6682fb4b1 Translated using Weblate (Slovak)
Currently translated at 73.4% (711 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sk/
2026-10-13 06:41:22 +02:00
Anonymous f14798dd9f Translated using Weblate (Slovak)
Currently translated at 73.4% (711 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sk/
2026-10-13 06:41:20 +02:00
Anonymous 528437b204 Translated using Weblate (Kurdish (Southern))
Currently translated at 24.0% (233 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sdh/
2026-10-13 06:41:11 +02:00
Anonymous 650aa1dc84 Translated using Weblate (Sardinian)
Currently translated at 85.7% (830 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sc/
2026-10-13 06:40:58 +02:00
Anonymous f5e5aca8d0 Translated using Weblate (Yakut)
Currently translated at 41.1% (398 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sah/
2026-10-13 06:40:49 +02:00
Anonymous dec49748b9 Translated using Weblate (Sanskrit)
Currently translated at 57.2% (554 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sa/
2026-10-13 06:40:38 +02:00
Anonymous 0e40cc46a3 Translated using Weblate (Russian)
Currently translated at 98.4% (953 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
2026-10-13 06:40:27 +02:00
Dexroneum 84ff9f2b3d Translated using Weblate (Russian)
Currently translated at 98.4% (953 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
2026-10-13 06:40:18 +02:00
Anonymous 8ad828decd Translated using Weblate (Romanian)
Currently translated at 90.9% (880 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ro/
2026-10-13 06:40:13 +02:00
Anonymous 647e7cb761 Translated using Weblate (Portuguese)
Currently translated at 91.4% (885 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt/
2026-10-13 06:40:02 +02:00
ZenVinny 92a3c77b8a Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.6% (965 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
2026-10-13 06:39:50 +02:00
Anonymous a1e8ff7568 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.6% (965 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
2026-10-13 06:39:48 +02:00
Lucas Correia c911bde669 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.6% (965 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
2026-10-13 06:39:41 +02:00
Anonymous 1125c09eb9 Translated using Weblate (Polish)
Currently translated at 83.5% (809 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pl/
2026-10-13 06:39:37 +02:00
Anonymous 1a250fd983 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 26.8% (260 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nn/
2026-10-13 06:39:28 +02:00
Anonymous 3a56d0c2ae Translated using Weblate (Dutch)
Currently translated at 92.8% (899 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nl/
2026-10-13 06:39:13 +02:00
Anonymous 0e975390ee Translated using Weblate (Nepali)
Currently translated at 88.9% (861 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/
2026-10-13 06:39:02 +02:00
Anonymous 40175ee915 Translated using Weblate (Norwegian Bokmål)
Currently translated at 77.5% (751 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nb/
2026-10-13 06:38:52 +02:00
Anonymous bbb92d5bc3 Translated using Weblate (Malay)
Currently translated at 93.4% (905 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ms/
2026-10-13 06:38:40 +02:00
Anonymous 9090f07099 Translated using Weblate (Marathi)
Currently translated at 21.7% (211 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/mr/
2026-10-13 06:38:32 +02:00
Anonymous ec44d2ccad Translated using Weblate (Latvian)
Currently translated at 74.1% (718 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/lv/
2026-10-13 06:38:22 +02:00
Anonymous c182c11258 Translated using Weblate (Lithuanian)
Currently translated at 67.9% (658 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/lt/
2026-10-13 06:38:11 +02:00
Anonymous 30d576ce2c Translated using Weblate (Korean)
Currently translated at 94.1% (911 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/
2026-10-13 06:37:58 +02:00
Anonymous 16cf125650 Translated using Weblate (Kannada)
Currently translated at 49.8% (483 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kn/
2026-10-13 06:37:49 +02:00
Anonymous 7cfe9ec259 Translated using Weblate (Khmer (Central))
Currently translated at 21.3% (207 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/km/
2026-10-13 06:37:39 +02:00
Anonymous 019b64b14b Translated using Weblate (Kazakh)
Currently translated at 70.1% (679 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kk/
2026-10-13 06:37:28 +02:00
Anonymous 54a9d3609b Translated using Weblate (Georgian)
Currently translated at 42.5% (412 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ka/
2026-10-13 06:37:18 +02:00
Anonymous 3287001548 Translated using Weblate (Javanese)
Currently translated at 34.5% (334 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/jv/
2026-10-13 06:37:08 +02:00
Anonymous 77145c63e2 Translated using Weblate (Japanese)
Currently translated at 93.4% (905 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
2026-10-13 06:36:54 +02:00
Anonymous 7323609742 Translated using Weblate (Italian)
Currently translated at 91.3% (884 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
2026-10-13 06:36:45 +02:00
Anonymous c5593d91ff Translated using Weblate (Indonesian)
Currently translated at 92.1% (892 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
2026-10-13 06:36:34 +02:00
Anonymous f82d80a751 Translated using Weblate (Hungarian)
Currently translated at 83.8% (812 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hu/
2026-10-13 06:36:24 +02:00
Anonymous eceb0d07c8 Translated using Weblate (Croatian)
Currently translated at 91.4% (885 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
2026-10-13 06:36:11 +02:00
Anonymous 36b3007225 Translated using Weblate (Hindi)
Currently translated at 96.4% (934 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hi/
2026-10-13 06:36:02 +02:00
Throw Away 34cd561ee8 Translated using Weblate (Hindi)
Currently translated at 96.4% (934 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hi/
2026-10-13 06:35:52 +02:00
Anonymous c34f801831 Translated using Weblate (Hebrew)
Currently translated at 77.5% (751 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/he/
2026-10-13 06:35:46 +02:00
Anonymous 61556f222e Translated using Weblate (Galician)
Currently translated at 84.1% (815 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/gl/
2026-10-13 06:35:36 +02:00
Anonymous 81eb3eabf3 Translated using Weblate (Filipino)
Currently translated at 98.5% (954 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
2026-10-13 06:35:25 +02:00
Infy Milliaria 2c408d4866 Translated using Weblate (Filipino)
Currently translated at 98.5% (954 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
2026-10-13 06:35:19 +02:00
Anonymous 352304c2d8 Translated using Weblate (Finnish)
Currently translated at 66.4% (643 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fi/
2026-10-13 06:35:13 +02:00
Anonymous c5a92d4e90 Translated using Weblate (Persian)
Currently translated at 96.4% (934 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fa/
2026-10-13 06:35:01 +02:00
KING APPS 88dfcdf505 Translated using Weblate (Persian)
Currently translated at 96.4% (934 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fa/
2026-10-13 06:34:55 +02:00
Anonymous 951cc60ff5 Translated using Weblate (Basque)
Currently translated at 62.0% (601 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eu/
2026-10-13 06:34:50 +02:00
Anonymous 84d6a384f9 Translated using Weblate (Spanish)
Currently translated at 92.7% (898 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
2026-10-13 06:34:35 +02:00
Anonymous 3ea504070b Translated using Weblate (Esperanto)
Currently translated at 93.5% (906 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
2026-10-13 06:34:24 +02:00
Anonymous b26eff3bfb Translated using Weblate (Greek)
Currently translated at 92.7% (898 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/
2026-10-13 06:34:17 +02:00
Anonymous fc9856a8d9 Translated using Weblate (German)
Currently translated at 97.8% (947 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
2026-10-13 06:34:03 +02:00
MajorTanya 37a34eb884 Translated using Weblate (German)
Currently translated at 97.8% (947 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
2026-10-13 06:33:55 +02:00
Anonymous 56d79e6b78 Translated using Weblate (Danish)
Currently translated at 32.1% (311 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/da/
2026-10-13 06:33:53 +02:00
Anonymous dcb62b9b07 Translated using Weblate (Chuvash)
Currently translated at 64.4% (624 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cv/
2026-10-13 06:33:39 +02:00
Anonymous 97a5c03ccf Translated using Weblate (Czech)
Currently translated at 96.4% (934 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cs/
2026-10-13 06:33:29 +02:00
Adam Havránek 78c23410bf Translated using Weblate (Czech)
Currently translated at 96.4% (934 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cs/
2026-10-13 06:33:22 +02:00
Anonymous cf97d5b02c Translated using Weblate (Cebuano)
Currently translated at 44.2% (428 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ceb/
2026-10-13 06:33:19 +02:00
Anonymous 60a342f87c Translated using Weblate (Catalan)
Currently translated at 100.0% (968 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/
2026-10-13 06:33:04 +02:00
Eduard Ereza Martínez 44df8f8a4e Translated using Weblate (Catalan)
Currently translated at 100.0% (968 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/
2026-10-13 06:32:56 +02:00
Anonymous b80e1312b8 Translated using Weblate (Bengali)
Currently translated at 84.6% (819 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
2026-10-13 06:32:49 +02:00
Димитър Георгиев cd7d024c50 Translated using Weblate (Bulgarian)
Currently translated at 75.5% (731 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bg/
2026-10-13 06:32:42 +02:00
Anonymous e3238238b6 Translated using Weblate (Bulgarian)
Currently translated at 75.5% (731 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bg/
2026-10-13 06:32:40 +02:00
Anonymous c5df3b6e4a Translated using Weblate (Belarusian)
Currently translated at 96.4% (934 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/be/
2026-10-13 06:32:28 +02:00
nadevko cfbc8f69a4 Translated using Weblate (Belarusian)
Currently translated at 96.4% (934 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/be/
2026-10-13 06:32:26 +02:00
Anonymous 8fbb5046dd Translated using Weblate (Arabic)
Currently translated at 91.4% (885 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
2026-10-13 06:32:13 +02:00
Anonymous 6b4a19f23e Translated using Weblate (Amharic)
Currently translated at 27.5% (267 of 968 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/am/
2026-10-13 06:32:05 +02:00
Mend Renovate 446f6df82c Update androidx.paging to v3.5.3 (#4099) 2026-10-13 10:30:48 +06:00
AntsyLich 3100dceddd Release v0.21.2
Keeps the changelog entries that f45d0ad31d removed for the release under
Unreleased, since main still has the WebGPU reader and the new image
decoder.

(cherry picked from commit 9612d955db)
2026-10-13 10:27:57 +06:00
AntsyLichandNGB-Was-Taken 771ba33a33 Stream entries when creating and restoring backups
Closes #3850

Co-authored-by: NGB-Was-Taken <76197326+NGB-Was-Taken@users.noreply.github.com>
2026-10-13 01:51:35 +06:00
AntsyLichandtigeryu8900 5a5ff22c37 Fix concurrent Cloudflare challenges each opening a WebView
Closes #3858

Co-authored-by: tigeryu8900 <tigeryu8900@gmail.com>
2026-10-13 01:51:35 +06:00
AntsyLich f0f1e4de5a Fix some downloads not being recognized 2026-10-12 22:33:31 +06:00
AntsyLich c371d6331d Ignore long strip taps confirmed after the reader is gone 2026-10-12 22:33:31 +06:00
AntsyLich 2df48e19a1 Fail the install when nothing can confirm it instead of crashing 2026-10-12 22:33:30 +06:00
AntsyLich c9e1b6e96e Show invalid filters from extensions instead of crashing 2026-10-12 22:33:30 +06:00
AntsyLich 3156b4a1c4 Insert split pages only into the chapter they belong to 2026-10-12 22:33:30 +06:00
hazptr ee59394542 Fix tracker start date being shifted by the UTC offset (#4049)
Closes #1340
Closes #4049
2026-10-12 22:33:30 +06:00
AntsyLich 7ae858e602 Solve Cloudflare challenges of non-GET requests on the site's root
Closes #3909
2026-10-12 22:33:30 +06:00
AntsyLich 43de88c718 Update navigationevent to stop back gestures crashing closed screens 2026-10-12 22:33:30 +06:00
AntsyLich ea2b73ad78 Guard the database update against duplicates and damaged indexes 2026-10-12 22:33:30 +06:00
MajorTanya 1ca9b0efb8 Don't send full Doubles to Comick for progress (#4090)
Previously, user could end up with progress displays like

35.019996643066406/100

because Mihon was sending raw Doubles/simply stringified Doubles for
progress data.

We now round to 2 decimal places & trim off any trailing zeroes such
that integers are sent as "1" instead of "1.0" while retaining "25.5"
and "41.02" as such.
2026-10-12 12:27:25 +00:00
AntsyLich 66e736222d Release v0.21.1
Keeps the changelog entries that f45d0ad31d removed for the release under
Unreleased, since main still has the WebGPU reader and the new image
decoder.

(cherry picked from commit e9670d8a12)
2026-10-12 14:22:38 +06:00
86 changed files with 1468 additions and 282 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- label: I have updated the app to version **[0.21.0](https://github.com/mihonapp/mihon/releases/latest)**.
- label: I have updated the app to version **[0.21.2](https://github.com/mihonapp/mihon/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+2 -2
View File
@@ -52,7 +52,7 @@ body:
label: Mihon version
description: You can find your Mihon version in **More → About**.
placeholder: |
Example: "0.21.0"
Example: "0.21.2"
validations:
required: true
@@ -95,7 +95,7 @@ body:
required: true
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
required: true
- label: I have updated the app to version **[0.21.0](https://github.com/mihonapp/mihon/releases/latest)**.
- label: I have updated the app to version **[0.21.2](https://github.com/mihonapp/mihon/releases/latest)**.
required: true
- label: I have filled out all of the requested information in this form, including specific version numbers.
required: true
+32 -12
View File
@@ -32,21 +32,39 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
### Changed
- Use a new decoder for images Android can't decode itself, like AVIF, JPEG XL and HEIF, and support JPEG 2000 images ([@wwww-wwww](https://github.com/wwww-wwww)) ([#3786](https://github.com/mihonapp/mihon/pull/3786))
### Improved
- Create and restore backups faster and with less memory ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken), [@AntsyLich](https://github.com/AntsyLich))
### Removed
- Remove "Custom hardware bitmap threshold" and "Custom display profile" advanced settings ([@wwww-wwww](https://github.com/wwww-wwww)) ([#3786](https://github.com/mihonapp/mihon/pull/3786))
## [v0.21.2] - 2026-10-13
### Fixed
- Fixed refreshing chapters failing with the app language set to Arabic or another language with its own digits ([@AntsyLich](https://github.com/AntsyLich))
- Fixed a crash on launch on some Samsung devices running Android 9 to 11 ([@AntsyLich](https://github.com/AntsyLich))
- Fixed the start and resume button going under the taskbar on tablets ([@AntsyLich](https://github.com/AntsyLich))
- Fixed a crash when updating an extension while others were installing ([@AntsyLich](https://github.com/AntsyLich))
- Fixed library entries without a date added getting their last update time as date added when updating from v0.20.x ([@AntsyLich](https://github.com/AntsyLich))
- Fixed the app closing when WebView's page process crashed ([@AntsyLich](https://github.com/AntsyLich))
- Fixed installing or updating an extension sometimes cancelling the install of another one ([@AntsyLich](https://github.com/AntsyLich))
- Fixed updating from v0.20.x taking up to hours on libraries with many duplicate entries ([@AntsyLich](https://github.com/AntsyLich))
- Fixed the app staying on the updating screen for good when updating its data failed, it now shows what went wrong, and updating failing on leftover data of deleted entries ([@AntsyLich](https://github.com/AntsyLich))
- Fixed library updates ignoring the smart update restrictions for entries whose details were never fetched ([@AntsyLich](https://github.com/AntsyLich))
- Fixed resuming a chapter sometimes opening the next chapter partway through, and saving progress to it, in the right to left reader ([@AntsyLich](https://github.com/AntsyLich))
- Fix long floating point numbers showing up in Comick progress ([@MajorTanya](https://github.com/MajorTanya)) ([#4090](https://github.com/mihonapp/mihon/pull/4090))
- Fixed updating from v0.20.x failing when the database had duplicate history or tracking entries, or damaged indexes ([@AntsyLich](https://github.com/AntsyLich)) ([`ea2b73a`](https://github.com/mihonapp/mihon/commit/ea2b73a))
- Fixed a crash when a back gesture was still in progress as the reader or a dialog closed ([@AntsyLich](https://github.com/AntsyLich)) ([`43de88c`](https://github.com/mihonapp/mihon/commit/43de88c))
- Fixed Kitsu login and other requests that send data failing with "Failed to bypass Cloudflare" ([@AntsyLich](https://github.com/AntsyLich)) ([`7ae858e`](https://github.com/mihonapp/mihon/commit/7ae858e))
- Fixed tracker start date being off by a day when adding tracking to an entry with read history in some timezones ([@hazptr](https://github.com/hazptr)) ([#4049](https://github.com/mihonapp/mihon/pull/4049))
- Fixed the paged reader crashing when moving to another chapter after a wide page of a neighbouring chapter was split ([@AntsyLich](https://github.com/AntsyLich)) ([`3156b4a`](https://github.com/mihonapp/mihon/commit/3156b4a))
- Fixed a crash when opening the filters of a source with an invalid filter ([@AntsyLich](https://github.com/AntsyLich)) ([`c9e1b6e`](https://github.com/mihonapp/mihon/commit/c9e1b6e))
- Fixed a crash when installing an extension on a device where the system installer can't ask for confirmation ([@AntsyLich](https://github.com/AntsyLich)) ([`2df48e1`](https://github.com/mihonapp/mihon/commit/2df48e1))
- Fixed a crash when tapping in the long strip reader as it closed ([@AntsyLich](https://github.com/AntsyLich)) ([`c371d63`](https://github.com/mihonapp/mihon/commit/c371d63))
- Fixed chapters downloaded before v0.21.0 showing as not downloaded when their name started with a space or dot, was long, or had a non-ASCII scanlator ([@AntsyLich](https://github.com/AntsyLich)) ([`f0f1e4d`](https://github.com/mihonapp/mihon/commit/f0f1e4d))
- Fixed slowdowns and running out of memory when many requests to a site hit a Cloudflare challenge at once ([@tigeryu8900](https://github.com/tigeryu8900), [@AntsyLich](https://github.com/AntsyLich)) ([`5a5ff22`](https://github.com/mihonapp/mihon/commit/5a5ff22))
## [v0.21.1] - 2026-10-12
### Fixed
- Fixed refreshing chapters failing with the app language set to Arabic or another language with its own digits ([@AntsyLich](https://github.com/AntsyLich)) ([`8457fb9`](https://github.com/mihonapp/mihon/commit/8457fb9))
- Fixed a crash on launch on some Samsung devices running Android 9 to 11 ([@AntsyLich](https://github.com/AntsyLich)) ([`abb126a`](https://github.com/mihonapp/mihon/commit/abb126a))
- Fixed the start and resume button going under the taskbar on tablets ([@AntsyLich](https://github.com/AntsyLich)) ([`c279291`](https://github.com/mihonapp/mihon/commit/c279291))
- Fixed a crash when updating an extension while others were installing ([@AntsyLich](https://github.com/AntsyLich)) ([`07b9fb6`](https://github.com/mihonapp/mihon/commit/07b9fb6))
- Fixed library entries without a date added getting their last update time as date added when updating from v0.20.x ([@AntsyLich](https://github.com/AntsyLich)) ([`6dffca1`](https://github.com/mihonapp/mihon/commit/6dffca1))
- Fixed the app closing when WebView's page process crashed ([@AntsyLich](https://github.com/AntsyLich)) ([`649fc83`](https://github.com/mihonapp/mihon/commit/649fc83))
- Fixed installing or updating an extension sometimes cancelling the install of another one ([@AntsyLich](https://github.com/AntsyLich)) ([`ae749bb`](https://github.com/mihonapp/mihon/commit/ae749bb))
- Fixed updating from v0.20.x taking up to hours on libraries with many duplicate entries ([@AntsyLich](https://github.com/AntsyLich)) ([`a999e49`](https://github.com/mihonapp/mihon/commit/a999e49))
- Fixed the app staying on the updating screen for good when updating its data failed, it now shows what went wrong, and updating failing on leftover data of deleted entries ([@AntsyLich](https://github.com/AntsyLich)) ([`9dbb714`](https://github.com/mihonapp/mihon/commit/9dbb714))
- Fixed library updates ignoring the smart update restrictions for entries whose details were never fetched ([@AntsyLich](https://github.com/AntsyLich)) ([`010ac71`](https://github.com/mihonapp/mihon/commit/010ac71))
- Fixed resuming a chapter sometimes opening the next chapter partway through, and saving progress to it, in the right to left reader ([@AntsyLich](https://github.com/AntsyLich)) ([`51b61a3`](https://github.com/mihonapp/mihon/commit/51b61a3))
## [v0.21.0] - 2026-10-12
### Added
@@ -794,7 +812,9 @@ Same as v0.19.6
- Branding to Mihon ([@AntsyLich](https://github.com/AntsyLich))
- Minimum supported Android version to 8 ([@AntsyLich](https://github.com/AntsyLich)) ([`dfb3091`](https://github.com/mihonapp/mihon/commit/dfb3091e380dda3e9bfb64bf5c9a685cf3a03d0e))
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.21.0...main
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.21.2...main
[v0.21.2]: https://github.com/mihonapp/mihon/compare/v0.21.1...v0.21.2
[v0.21.1]: https://github.com/mihonapp/mihon/compare/v0.21.0...v0.21.1
[v0.21.0]: https://github.com/mihonapp/mihon/compare/v0.20.4...v0.21.0
[v0.20.4]: https://github.com/mihonapp/mihon/compare/v0.20.3...v0.20.4
[v0.20.3]: https://github.com/mihonapp/mihon/compare/v0.20.2...v0.20.3
+3 -2
View File
@@ -37,8 +37,8 @@ android {
defaultConfig {
applicationId = "app.mihon"
versionCode = 36
versionName = "0.21.0"
versionCode = 38
versionName = "0.21.2"
buildConfigField("boolean", "TELEMETRY_INCLUDED", "${Config.includeTelemetry}")
buildConfigField("boolean", "UPDATER_ENABLED", "${Config.enableUpdater}")
@@ -229,6 +229,7 @@ dependencies {
debugImplementation(libs.androidx.compose.uiTooling)
implementation(libs.androidx.compose.uiToolingPreview)
implementation(libs.androidx.compose.uiUtil)
implementation(libs.androidx.navigationEvent)
implementation(libs.androidx.interpolator)
@@ -8,10 +8,8 @@ import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.datetime.TimeZone
import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
@@ -61,10 +59,7 @@ class AddTracks(
.minOrNull()
firstReadChapterDate?.let {
val startDate = firstReadChapterDate.toEpochMilliseconds().convertEpochMillisZone(
TimeZone.currentSystemDefault(),
TimeZone.UTC,
)
val startDate = firstReadChapterDate.toEpochMilliseconds()
track = track.copy(
startDate = Instant.fromEpochMilliseconds(startDate),
)
@@ -4,8 +4,11 @@ import android.content.Context
import android.net.Uri
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.models.Backup
import kotlinx.serialization.SerializationException
import eu.kanade.tachiyomi.data.backup.models.BackupManga
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.serialization.protobuf.ProtoBuf
import okio.BufferedSource
import okio.buffer
import okio.gzip
import okio.source
@@ -19,9 +22,38 @@ class BackupDecoder(
private val parser: ProtoBuf,
) {
/**
* Decode a potentially-gzipped backup.
* Decode everything but the entries of a potentially-gzipped backup, counting the entries without decoding them.
*/
fun decode(uri: Uri): Backup {
fun decodeMetadata(uri: Uri): Metadata {
return read(uri) { reader ->
orInvalid {
var entryCount = 0
while (reader.nextEntry()) {
reader.skipEntry()
entryCount++
}
Metadata(entryCount, reader.readMetadata())
}
}
}
/**
* Decode the entries of a potentially-gzipped backup one at a time, skipping those whose index [include] rejects.
*/
fun decodeEntries(uri: Uri, include: (index: Int) -> Boolean = { true }): Flow<BackupManga> = flow {
read(uri) { reader ->
var index = 0
while (orInvalid { reader.nextEntry() }) {
if (include(index++)) {
emit(orInvalid { reader.readEntry() })
} else {
orInvalid { reader.skipEntry() }
}
}
}
}
private inline fun <T> read(uri: Uri, block: (BackupReader) -> T): T {
return context.contentResolver.openInputStream(uri)!!.use { inputStream ->
val source = inputStream.source().buffer()
@@ -29,22 +61,32 @@ class BackupDecoder(
require(2)
}
val id1id2 = peeked.readShort()
val backupString = when (id1id2.toInt()) {
val backupSource: BufferedSource = when (id1id2.toInt()) {
0x1f8b -> source.gzip().buffer() // 0x1f8b is gzip magic bytes
MAGIC_JSON_SIGNATURE1, MAGIC_JSON_SIGNATURE2, MAGIC_JSON_SIGNATURE3 -> {
throw IOException(context.stringResource(MR.strings.invalid_backup_file_json))
}
else -> source
}.use { it.readByteArray() }
try {
parser.decodeFromByteArray(Backup.serializer(), backupString)
} catch (_: SerializationException) {
throw IOException(context.stringResource(MR.strings.invalid_backup_file_unknown))
}
backupSource.use { block(BackupReader(it, parser)) }
}
}
// Only around reading, so the JSON error above and anything thrown downstream of emit keep their own messages
private inline fun <T> orInvalid(block: () -> T): T {
return try {
block()
} catch (e: IOException) {
throw IOException(context.stringResource(MR.strings.invalid_backup_file_unknown), e)
}
}
data class Metadata(
val entryCount: Int,
val backup: Backup,
)
companion object {
private const val MAGIC_JSON_SIGNATURE1 = 0x7b7d // `{}`
private const val MAGIC_JSON_SIGNATURE2 = 0x7b22 // `{"`
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.backup
import android.net.Uri
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.track.TrackerManager
import kotlinx.coroutines.CancellationException
import tachiyomi.domain.source.service.SourceManager
@Inject
@@ -18,8 +19,14 @@ class BackupFileValidator(
* @return List of missing sources or missing trackers.
*/
suspend fun validate(uri: Uri): Results {
val trackerIds = mutableSetOf<Int>()
val backup = try {
backupDecoder.decode(uri)
backupDecoder.decodeEntries(uri).collect { manga ->
manga.tracking.mapTo(trackerIds) { it.syncId }
}
backupDecoder.decodeMetadata(uri).backup
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
throw IllegalStateException(e)
}
@@ -38,11 +45,7 @@ class BackupFileValidator(
.distinct()
.sorted()
val trackers = backup.backupManga
.flatMap { it.tracking }
.map { it.syncId }
.distinct()
val missingTrackers = trackers
val missingTrackers = trackerIds
.mapNotNull { trackerManager.get(it.toLong()) }
.filter { !it.isLoggedIn }
.map { it.name }
@@ -0,0 +1,153 @@
package eu.kanade.tachiyomi.data.backup
import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.BackupManga
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationException
import kotlinx.serialization.protobuf.ProtoBuf
import okio.Buffer
import okio.BufferedSink
import okio.BufferedSource
import java.io.IOException
/**
* Writes the same bytes as encoding a whole [Backup], without holding all of its entries at once.
*/
class BackupWriter(
private val sink: BufferedSink,
private val parser: ProtoBuf,
) {
var isEmpty = true
private set
fun writeEntry(entry: BackupManga) {
val bytes = parser.encodeToByteArray(BackupManga.serializer(), entry)
sink.writeVarint(ENTRY_TAG)
sink.writeVarint(bytes.size.toLong())
sink.write(bytes)
isEmpty = false
}
/**
* Writes the other fields after the entries, as [metadata] without entries of its own.
*/
fun writeMetadata(metadata: Backup) {
val bytes = parser.encodeToByteArray(Backup.serializer(), metadata)
sink.write(bytes)
if (bytes.isNotEmpty()) isEmpty = false
}
}
/**
* Reads an encoded [Backup] one entry at a time. Every other field is set aside for [readMetadata], wherever it
* appears.
*
* Malformed or truncated input throws an [IOException].
*/
class BackupReader(
private val source: BufferedSource,
private val parser: ProtoBuf,
) {
private val metadata = Buffer()
private var entryLength = NO_ENTRY
/**
* Moves to the next entry, which [readEntry] or [skipEntry] then consumes.
*
* @return false once the input is exhausted.
*/
fun nextEntry(): Boolean {
check(entryLength == NO_ENTRY)
while (!source.exhausted()) {
val tag = source.readVarint()
val fieldNumber = tag ushr 3
val wireType = (tag and 0x7).toInt()
if (fieldNumber == 0L) throw IOException("Invalid field number 0")
if (tag == ENTRY_TAG) {
entryLength = source.readLength()
return true
}
metadata.writeVarint(tag)
when (wireType) {
WIRE_TYPE_VARINT -> metadata.writeVarint(source.readVarint())
WIRE_TYPE_FIXED64 -> metadata.write(source, 8)
WIRE_TYPE_LENGTH_DELIMITED -> {
val length = source.readLength()
metadata.writeVarint(length)
metadata.write(source, length)
}
WIRE_TYPE_FIXED32 -> metadata.write(source, 4)
else -> throw IOException("Unsupported wire type $wireType")
}
}
return false
}
fun readEntry(): BackupManga {
val bytes = source.readByteArray(takeEntryLength())
return decode(BackupManga.serializer(), bytes)
}
fun skipEntry() {
source.skip(takeEntryLength())
}
/**
* Skips the remaining entries and decodes everything else as a [Backup] without entries.
*/
fun readMetadata(): Backup {
if (entryLength != NO_ENTRY) skipEntry()
while (nextEntry()) skipEntry()
return decode(Backup.serializer(), metadata.readByteArray())
}
private fun takeEntryLength(): Long {
check(entryLength != NO_ENTRY)
return entryLength.also { entryLength = NO_ENTRY }
}
private fun <T> decode(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
return try {
parser.decodeFromByteArray(deserializer, bytes)
} catch (e: SerializationException) {
throw IOException(e)
}
}
}
private fun BufferedSource.readVarint(): Long {
var result = 0L
for (shift in 0 until Long.SIZE_BITS step 7) {
val byte = readByte().toInt()
result = result or ((byte and 0x7F).toLong() shl shift)
if ((byte and 0x80) == 0) return result
}
throw IOException("Malformed varint")
}
private fun BufferedSource.readLength(): Long {
val length = readVarint()
if (length !in 0..Int.MAX_VALUE) throw IOException("Invalid length $length")
return length
}
private fun BufferedSink.writeVarint(value: Long) {
var remaining = value
while ((remaining and 0x7FL.inv()) != 0L) {
writeByte((remaining and 0x7F).toInt() or 0x80)
remaining = remaining ushr 7
}
writeByte(remaining.toInt())
}
private const val WIRE_TYPE_VARINT = 0
private const val WIRE_TYPE_FIXED64 = 1
private const val WIRE_TYPE_LENGTH_DELIMITED = 2
private const val WIRE_TYPE_FIXED32 = 5
// Backup.backupManga is field 1, and each entry is length-delimited
private const val ENTRY_TAG = 1L shl 3 or WIRE_TYPE_LENGTH_DELIMITED.toLong()
private const val NO_ENTRY = -1L
@@ -8,6 +8,7 @@ import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
import eu.kanade.tachiyomi.data.backup.BackupWriter
import eu.kanade.tachiyomi.data.backup.create.creators.CategoriesBackupCreator
import eu.kanade.tachiyomi.data.backup.create.creators.ExtensionStoresBackupCreator
import eu.kanade.tachiyomi.data.backup.create.creators.MangaBackupCreator
@@ -20,6 +21,8 @@ import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.serialization.protobuf.ProtoBuf
import logcat.LogPriority
import okio.buffer
@@ -85,27 +88,31 @@ class BackupCreator(
val nonFavoriteManga = if (options.readEntries) mangaRepository.getReadMangaNotInLibrary() else emptyList()
val backupManga = backupMangas(getFavorites.await() + nonFavoriteManga, options)
val backup = Backup(
backupManga = backupManga,
backupCategories = backupCategories(options),
backupSources = backupSources(backupManga),
backupPreferences = backupAppPreferences(options),
backupExtensionStores = backupExtensionStores(options),
backupSourcePreferences = backupSourcePreferences(options),
)
val byteArray = parser.encodeToByteArray(Backup.serializer(), backup)
if (byteArray.isEmpty()) {
throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error))
}
file.openOutputStream()
.also {
// Force overwrite old file
(it as? FileOutputStream)?.channel?.truncate(0)
}
.sink().gzip().buffer().use {
it.write(byteArray)
val writer = BackupWriter(it, parser)
val sourceIds = mutableSetOf<Long>()
backupManga.collect { manga ->
writer.writeEntry(manga)
sourceIds += manga.source
}
writer.writeMetadata(
Backup(
backupManga = emptyList(),
backupCategories = backupCategories(options),
backupSources = backupSources(sourceIds),
backupPreferences = backupAppPreferences(options),
backupExtensionStores = backupExtensionStores(options),
backupSourcePreferences = backupSourcePreferences(options),
),
)
if (writer.isEmpty) {
throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error))
}
}
val fileUri = file.uri
@@ -130,14 +137,14 @@ class BackupCreator(
return categoriesBackupCreator()
}
private suspend fun backupMangas(mangas: List<Manga>, options: BackupOptions): List<BackupManga> {
if (!options.libraryEntries) return emptyList()
private fun backupMangas(mangas: List<Manga>, options: BackupOptions): Flow<BackupManga> {
if (!options.libraryEntries) return emptyFlow()
return mangaBackupCreator(mangas, options)
}
private suspend fun backupSources(mangas: List<BackupManga>): List<BackupSource> {
return sourcesBackupCreator(mangas)
private suspend fun backupSources(sourceIds: Set<Long>): List<BackupSource> {
return sourcesBackupCreator(sourceIds)
}
private fun backupAppPreferences(options: BackupOptions): List<BackupPreference> {
@@ -7,11 +7,13 @@ import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.toBackupChapter
import eu.kanade.tachiyomi.data.backup.models.toBackupTracking
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import mihon.core.common.extensions.toByteArray
import mihon.core.common.extensions.toEpochMillisOrZero
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.repository.CategoryRepository
import tachiyomi.domain.chapter.repository.ChapterRepository
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.history.repository.HistoryRepository
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.track.repository.TrackRepository
@@ -20,62 +22,71 @@ import tachiyomi.domain.track.repository.TrackRepository
class MangaBackupCreator(
private val mangaRepository: MangaRepository,
private val chapterRepository: ChapterRepository,
private val categoryRepository: CategoryRepository,
private val historyRepository: HistoryRepository,
private val trackRepository: TrackRepository,
private val getCategories: GetCategories,
private val getHistory: GetHistory,
) {
suspend operator fun invoke(mangas: List<Manga>, options: BackupOptions): List<BackupManga> {
return mangas.map {
backupManga(it, options)
/**
* Builds the entries a chunk at a time, so only one chunk's chapters are held at once.
*/
operator fun invoke(mangas: List<Manga>, options: BackupOptions): Flow<BackupManga> = flow {
mangas.chunked(CHUNK_SIZE).forEach { chunk ->
backupMangas(chunk, options).forEach { emit(it) }
}
}
private suspend fun backupManga(manga: Manga, options: BackupOptions): BackupManga {
// Entry for this manga
val mangaObject = manga.toBackupManga()
private suspend fun backupMangas(mangas: List<Manga>, options: BackupOptions): List<BackupManga> {
val mangaIds = mangas.map { it.id }
mangaObject.excludedScanlators = mangaRepository.getExcludedScanlators(manga.id).toList()
if (options.chapters) {
// Backup all the chapters
chapterRepository.getChapterByMangaId(manga.id, applyScanlatorFilter = false)
.map { it.toBackupChapter() }
.takeUnless { it.isEmpty() }
?.let { mangaObject.chapters = it }
val excludedScanlatorsByMangaId = mangaRepository.getExcludedScanlatorsByMangaIds(mangaIds)
// History is backed up by chapter URL, so it needs the chapters too
val chaptersByMangaId = if (options.chapters || options.history) {
chapterRepository.getChaptersByMangaIds(mangaIds)
} else {
emptyMap()
}
val categoriesByMangaId = if (options.categories) {
categoryRepository.getCategoriesByMangaIds(mangaIds)
} else {
emptyMap()
}
val tracksByMangaId = if (options.tracking) trackRepository.getTracksByMangaIds(mangaIds) else emptyMap()
val historyByMangaId = if (options.history) historyRepository.getHistoryByMangaIds(mangaIds) else emptyMap()
if (options.categories) {
// Backup categories for this manga
val categoriesForManga = getCategories.await(manga.id)
if (categoriesForManga.isNotEmpty()) {
mangaObject.categories = categoriesForManga.map { it.order }
return mangas.map { manga ->
val mangaObject = manga.toBackupManga()
val chapters = chaptersByMangaId[manga.id].orEmpty()
mangaObject.excludedScanlators = excludedScanlatorsByMangaId[manga.id].orEmpty()
if (options.chapters) {
mangaObject.chapters = chapters.map { it.toBackupChapter() }
}
}
if (options.tracking) {
val tracks = trackRepository.getTracksByMangaId(manga.id).map { it.toBackupTracking() }
if (tracks.isNotEmpty()) {
mangaObject.tracking = tracks
if (options.categories) {
mangaObject.categories = categoriesByMangaId[manga.id].orEmpty().map { it.order }
}
}
if (options.history) {
val historyByMangaId = getHistory.await(manga.id)
if (historyByMangaId.isNotEmpty()) {
val chapterUrlsById = chapterRepository.getChapterByMangaId(manga.id).associate { it.id to it.url }
val history = historyByMangaId.mapNotNull { history ->
if (options.tracking) {
mangaObject.tracking = tracksByMangaId[manga.id].orEmpty().map { it.toBackupTracking() }
}
if (options.history) {
val chapterUrlsById = chapters.associate { it.id to it.url }
mangaObject.history = historyByMangaId[manga.id].orEmpty().mapNotNull { history ->
// A chapter removed since its history was read takes that history with it
val url = chapterUrlsById[history.chapterId] ?: return@mapNotNull null
BackupHistory(url, history.readAt.toEpochMillisOrZero(), history.readDuration)
}
if (history.isNotEmpty()) {
mangaObject.history = history
}
}
}
return mangaObject
mangaObject
}
}
private companion object {
const val CHUNK_SIZE = 100
}
}
@@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.data.backup.create.creators
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.source.Source
import tachiyomi.domain.source.service.SourceManager
@@ -11,11 +10,8 @@ class SourcesBackupCreator(
private val sourceManager: SourceManager,
) {
suspend operator fun invoke(mangas: List<BackupManga>): List<BackupSource> {
return mangas
.map(BackupManga::source)
.distinct()
.map { sourceManager.getOrStub(it).toBackupSource() }
suspend operator fun invoke(sourceIds: Set<Long>): List<BackupSource> {
return sourceIds.map { sourceManager.getOrStub(it).toBackupSource() }
}
}
@@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.data.backup.BackupDecoder
import eu.kanade.tachiyomi.data.backup.BackupNotifier
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionStore
import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
@@ -23,6 +22,12 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.chunked
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.withIndex
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.common.i18n.stringResource
@@ -32,6 +37,7 @@ import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import java.io.File
import java.text.SimpleDateFormat
import java.util.BitSet
import java.util.Date
import java.util.Locale
import java.util.concurrent.CopyOnWriteArrayList
@@ -97,7 +103,7 @@ class BackupRestorer(
}
private suspend fun restoreFromFile(uri: Uri, options: RestoreOptions) {
val backup = backupDecoder.decode(uri)
val (entryCount, backup) = backupDecoder.decodeMetadata(uri)
// Store source mapping for error messages
val backupMaps = backup.backupSources
@@ -105,7 +111,7 @@ class BackupRestorer(
if (options.libraryEntries) {
restoreSourceNames(backupMaps)
restoreAmount += backup.backupManga.size
restoreAmount += entryCount
}
if (options.categories) {
restoreAmount += 1
@@ -138,7 +144,7 @@ class BackupRestorer(
}
if (options.libraryEntries) {
restoreManga(
backup.backupManga,
uri,
if (options.categories) backup.backupCategories else emptyList(),
restoreCategoriesJob,
)
@@ -175,14 +181,31 @@ class BackupRestorer(
}
private fun CoroutineScope.restoreManga(
backupMangas: List<BackupManga>,
uri: Uri,
backupCategories: List<BackupCategory>,
categoriesRestoreJob: Job?,
) = launch {
categoriesRestoreJob?.join()
mangaRestorer.sortByNew(backupMangas)
// New entries go first. Rather than hold the others back in memory, a second pass reads them again.
val isExisting = mangaRestorer.getExistingMatcher()
val existingIndices = BitSet()
val newMangas = backupDecoder.decodeEntries(uri)
.withIndex()
.filter { (index, manga) ->
val exists = isExisting(manga)
if (exists) existingIndices.set(index)
!exists
}
.map { it.value }
val backupMangas = flow {
emitAll(newMangas)
emitAll(backupDecoder.decodeEntries(uri, existingIndices::get))
}
backupMangas
.chunked(100)
.forEach { chunk ->
.collect { chunk ->
val restoredAsBatch = try {
ensureActive()
mangaRestorer.restore(chunk, backupCategories)
@@ -23,11 +23,13 @@ class MangaRestorer(
private val now = Clock.System.now().toLocalDateTime(timeZone)
private val currentFetchWindow = fetchInterval.getWindow(now.date, timeZone)
suspend fun sortByNew(backupMangas: List<BackupManga>): List<BackupManga> {
/**
* Returns whether an entry was already in the database at the time of this call.
*/
suspend fun getExistingMatcher(): (BackupManga) -> Boolean {
val urlsBySource = restoreRepository.getMangaUrlsBySourceId()
return backupMangas
.sortedBy { it.url in urlsBySource[it.source].orEmpty() }
return { it.url in urlsBySource[it.source].orEmpty() }
}
/**
@@ -231,7 +231,7 @@ class DownloadProvider(
enableChapterNameHash: Boolean = libraryPreferences.enableChapterNameHash.get(),
): String {
return ChapterDirNameParts(chapterName, chapterScanlator, chapterUrl)
.dirName(disallowNonAsciiFilenames, enableChapterNameHash, sanitizeScanlator = true)
.dirName(disallowNonAsciiFilenames, enableChapterNameHash)
}
/**
@@ -254,14 +254,18 @@ class DownloadProvider(
val booleanPairPermutation = listOf(false to false, false to true, true to false, true to true)
val othersChapterDirNames = booleanPairPermutation
.map { (disallowNonAsciiFilenames, enableChapterNameHash) ->
parts.dirName(disallowNonAsciiFilenames, enableChapterNameHash, sanitizeScanlator = true)
parts.dirName(disallowNonAsciiFilenames, enableChapterNameHash)
}
val splitChapterDirNames = booleanPairPermutation
.map { (disallowNonAsciiFilenames, enableChapterNameHash) ->
parts.splitDirName(disallowNonAsciiFilenames, enableChapterNameHash, sanitizeScanlator = true)
}
// Scanlators weren't sanitized for a while. One with a "/" nested its folder, so it was never found anyway.
val scanlator = parts.chapterScanlator
val unsanitizedScanlatorChapterDirNames = if (scanlator != null && '/' !in scanlator) {
booleanPairPermutation.map { (disallowNonAsciiFilenames, enableChapterNameHash) ->
parts.dirName(disallowNonAsciiFilenames, enableChapterNameHash, sanitizeScanlator = false)
parts.splitDirName(disallowNonAsciiFilenames, enableChapterNameHash, sanitizeScanlator = false)
}
} else {
emptyList()
@@ -272,6 +276,7 @@ class DownloadProvider(
// chapter names)
add(chapterNameV1)
addAll(othersChapterDirNames)
addAll(splitChapterDirNames)
addAll(unsanitizedScanlatorChapterDirNames)
}
}
@@ -291,7 +296,6 @@ class DownloadProvider(
val chapterDirName = parts.dirName(
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames.get(),
enableHash = libraryPreferences.enableChapterNameHash.get(),
sanitizeScanlator = true,
)
val legacyChapterDirNames = getLegacyChapterDirNames(parts)
@@ -330,9 +334,29 @@ private class ChapterDirNameParts(chapterName: String, val chapterScanlator: Str
chapterScanlator?.let { DiskUtil.buildValidFilename(it) }
}
private val validDirNames = arrayOfNulls<String>(2)
private val validChapterNames = arrayOfNulls<String>(2)
fun dirName(disallowNonAscii: Boolean, enableHash: Boolean, sanitizeScanlator: Boolean): String {
fun dirName(disallowNonAscii: Boolean, enableHash: Boolean): String {
val index = if (disallowNonAscii) 1 else 0
val dirName = validDirNames[index]
// The scanlator and the name are made valid together, as every version before 0.21.0 did
?: DiskUtil.buildValidFilename(
if (chapterScanlator.isNullOrBlank()) chapterName else "${chapterScanlator}_$chapterName",
// Subtract 7 bytes for hash and underscore, 4 bytes for .cbz
DiskUtil.MAX_FILE_NAME_BYTES - 11,
disallowNonAscii,
)
.also { validDirNames[index] = it }
return if (enableHash) dirName + hashSuffix else dirName
}
/**
* The name 0.21.0 and 0.21.1 gave a chapter, which made only the chapter name valid and so differs for names
* starting with a space or dot, long names, and non-ASCII scanlators.
*/
fun splitDirName(disallowNonAscii: Boolean, enableHash: Boolean, sanitizeScanlator: Boolean): String {
return buildString {
if (!chapterScanlator.isNullOrBlank()) {
// A "/" in the scanlator would otherwise nest the chapter in a folder of its own
@@ -33,6 +33,7 @@ import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import uy.kohesive.injekt.injectLazy
import java.math.RoundingMode
import java.security.SecureRandom
import java.util.Base64
import kotlin.time.Duration.Companion.minutes
@@ -77,7 +78,14 @@ class ComickApi(
put("status", track.status.toApiListStatus())
if (track.last_chapter_read > 0) {
putJsonObject("progress") {
put("number", track.last_chapter_read)
put(
"number",
track.last_chapter_read
.toBigDecimal()
.setScale(2, RoundingMode.HALF_UP)
.stripTrailingZeros()
.toPlainString(),
)
}
} else {
put("progress", null)
@@ -140,7 +148,14 @@ class ComickApi(
put("rating", track.score.toInt().takeIf { it > 0 })
put("status", track.status.toApiListStatus())
putJsonObject("progress") {
put("number", track.last_chapter_read.toString())
put(
"number",
track.last_chapter_read
.toBigDecimal()
.setScale(2, RoundingMode.HALF_UP)
.stripTrailingZeros()
.toPlainString(),
)
}
}
.toString()
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.installer
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.Service
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -57,7 +58,16 @@ class PackageInstallerInstaller(private val service: Service) : Installer(servic
return
}
userAction.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
service.startActivity(userAction)
try {
service.startActivity(userAction)
} catch (e: ActivityNotFoundException) {
// Some devices have the system installer's confirmation screen disabled or removed
logcat(LogPriority.WARN, e) { "Nothing can confirm the install" }
activeSession?.let { (_, sessionId) ->
runCatching { packageInstaller.abandonSession(sessionId) }
}
continueQueue(InstallStep.Error)
}
}
PackageInstaller.STATUS_FAILURE_ABORTED -> {
continueQueue(InstallStep.Idle)
@@ -169,7 +169,7 @@ private fun Int.toTriStateFilter(): TriState {
Filter.TriState.STATE_IGNORE -> TriState.DISABLED
Filter.TriState.STATE_INCLUDE -> TriState.ENABLED_IS
Filter.TriState.STATE_EXCLUDE -> TriState.ENABLED_NOT
else -> throw IllegalStateException("Unknown TriState state: $this")
else -> TriState.DISABLED
}
}
@@ -26,7 +26,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
/**
* Holds preprocessed items so they don't get removed when changing chapter
*/
private var preprocessed: MutableMap<Int, InsertPage> = mutableMapOf()
private var preprocessed: List<InsertPage> = emptyList()
var nextTransition: ChapterTransition.Next? = null
private set
@@ -68,13 +68,15 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
val lastPage = pages.last()
// Insert preprocessed pages into current page list
preprocessed.keys.sortedDescending()
.forEach { key ->
if (lastPage.index == key) {
insertPageLastPage = preprocessed[key]
// Insert preprocessed pages into current page list. Pages of the adjacent chapters are split too, and
// placed by their index, so only this chapter's belong here.
preprocessed.filter { it.chapter.chapter.id == chapters.currChapter.chapter.id }
.sortedByDescending { it.index }
.forEach { page ->
if (lastPage.index == page.index) {
insertPageLastPage = page
}
preprocessed[key]?.let { pages.add(key + 1, it) }
pages.add(page.index + 1, page)
}
newItems.addAll(pages)
@@ -105,7 +107,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
newItems.reverse()
}
preprocessed = mutableMapOf()
val adjacentChapterIds = setOfNotNull(chapters.prevChapter?.chapter?.id, chapters.nextChapter?.chapter?.id)
preprocessed = preprocessed.filter { it.chapter.chapter.id in adjacentChapterIds }
items = newItems
notifyDataSetChanged()
@@ -161,9 +164,12 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
val currentIndex = items.indexOf(currentPage)
// Put aside preprocessed pages for next chapter so they don't get removed when changing chapter
// Put aside pages of an adjacent chapter until it becomes the current one, so they are not lost on the change
if (currentPage.chapter.chapter.id != currentChapter?.chapter?.id) {
preprocessed[newPage.index] = newPage
val chapterId = newPage.chapter.chapter.id
if (preprocessed.none { it.chapter.chapter.id == chapterId && it.index == newPage.index }) {
preprocessed = preprocessed + newPage
}
return
}
@@ -243,7 +243,9 @@ class WebtoonRecyclerView @JvmOverloads constructor(
inner class GestureListener : GestureDetectorWithLongTap.Listener() {
override fun onSingleTapConfirmed(ev: MotionEvent): Boolean {
if (!tapDuringManualScroll) {
// A tap is confirmed after a delay, by when the reader can be gone, and scrolling a detached list
// lays out pages that can't compose
if (!tapDuringManualScroll && isAttachedToWindow) {
tapListener?.invoke(ev)
}
return false
@@ -0,0 +1,202 @@
package eu.kanade.tachiyomi.data.backup
import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupChapter
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionStore
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
import eu.kanade.tachiyomi.data.backup.models.BackupTracking
import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
import io.kotest.matchers.shouldBe
import kotlinx.serialization.protobuf.ProtoBuf
import okio.Buffer
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.parallel.Execution
import org.junit.jupiter.api.parallel.ExecutionMode
import java.io.IOException
@Execution(ExecutionMode.CONCURRENT)
class BackupStreamTest {
private val parser = ProtoBuf
@Test
fun `writer output is the plain encoding`() {
val backup = backup()
val bytes = write(backup)
bytes shouldBe encode(backup)
encode(parser.decodeFromByteArray(Backup.serializer(), bytes)) shouldBe encode(backup)
}
@Test
fun `reader reads the plain encoding`() {
val backup = backup()
val (entries, metadata) = read(encode(backup))
entries.map(::encode) shouldBe backup.backupManga.map(::encode)
encode(metadata) shouldBe encode(backup.copy(backupManga = emptyList()))
}
@Test
fun `reader counts entries without decoding them`() {
val backup = backup()
val reader = BackupReader(Buffer().write(encode(backup)), parser)
var count = 0
while (reader.nextEntry()) {
reader.skipEntry()
count++
}
count shouldBe backup.backupManga.size
encode(reader.readMetadata()) shouldBe encode(backup.copy(backupManga = emptyList()))
}
@Test
fun `reader takes fields in any order`() {
val backup = backup()
val (first, second, third) = backup.backupManga
val input = Buffer()
.write(encode(Backup(emptyList(), backupSources = backup.backupSources)))
.write(encode(Backup(listOf(first))))
// Fields Backup doesn't know, one of each remaining wire type
.writeByte(3 shl 3 or 0).writeByte(0x96).writeByte(0x01)
.writeByte(4 shl 3 or 1).writeLongLe(42)
.write(encode(Backup(listOf(second), backupCategories = backup.backupCategories)))
.writeByte(5 shl 3 or 5).writeIntLe(42)
.write(encode(Backup(emptyList(), backupPreferences = backup.backupPreferences)))
.write(encode(Backup(listOf(third), backupExtensionStores = backup.backupExtensionStores)))
.write(encode(Backup(emptyList(), backupSourcePreferences = backup.backupSourcePreferences)))
.readByteArray()
val (entries, metadata) = read(input)
entries.map(::encode) shouldBe backup.backupManga.map(::encode)
encode(metadata) shouldBe encode(backup.copy(backupManga = emptyList()))
}
@Test
fun `empty backup`() {
val sink = Buffer()
val writer = BackupWriter(sink, parser)
writer.writeMetadata(Backup(emptyList()))
writer.isEmpty shouldBe true
sink.size shouldBe 0L
val (entries, metadata) = read(ByteArray(0))
entries shouldBe emptyList()
encode(metadata) shouldBe ByteArray(0)
}
@Test
fun `truncated input throws`() {
val bytes = encode(backup())
// Cut inside the last entry, inside the metadata, and right after a tag
val entryEnd = encode(Backup(backup().backupManga)).size
listOf(entryEnd - 1, bytes.size - 1, entryEnd + 1).forEach { size ->
assertThrows<IOException> { read(bytes.copyOf(size)) }
}
}
@Test
fun `garbage input throws`() {
listOf(
// Wire type 7 doesn't exist
byteArrayOf(0x0F),
// Field number 0
byteArrayOf(0x02, 0x00),
// A varint that never ends
ByteArray(11) { 0xFF.toByte() },
// An entry longer than the input
byteArrayOf(0x0A, 0x10, 0x01),
// An entry that isn't a BackupManga
byteArrayOf(0x0A, 0x02, 0x0F, 0x00),
).forEach { input ->
assertThrows<IOException> { read(input) }
}
}
private fun write(backup: Backup): ByteArray {
val sink = Buffer()
val writer = BackupWriter(sink, parser)
backup.backupManga.forEach(writer::writeEntry)
writer.writeMetadata(backup.copy(backupManga = emptyList()))
writer.isEmpty shouldBe false
return sink.readByteArray()
}
private fun read(bytes: ByteArray): Pair<List<BackupManga>, Backup> {
val reader = BackupReader(Buffer().write(bytes), parser)
val entries = buildList {
while (reader.nextEntry()) add(reader.readEntry())
}
return entries to reader.readMetadata()
}
private fun encode(backup: Backup) = parser.encodeToByteArray(Backup.serializer(), backup)
private fun encode(manga: BackupManga) = parser.encodeToByteArray(BackupManga.serializer(), manga)
private fun backup() = Backup(
backupManga = listOf(
BackupManga(
source = 1L,
url = "/manga/1",
title = "First",
genre = listOf("Action", "Drama"),
chapters = listOf(
BackupChapter(url = "/chapter/1", name = "Chapter 1", read = true, chapterNumber = 1f),
BackupChapter(url = "/chapter/2", name = "Chapter 2", bookmark = true, lastPageRead = 4),
),
categories = listOf(0L, 1L),
tracking = listOf(BackupTracking(syncId = 2, libraryId = 3L, title = "First", mediaId = 7L)),
history = listOf(BackupHistory(url = "/chapter/1", lastRead = 1_700_000_000_000L, readDuration = 60)),
excludedScanlators = listOf("Group"),
),
BackupManga(source = 2L, url = "/manga/2", title = "Second", favorite = false),
BackupManga(
source = 1L,
url = "/manga/3",
title = "Third",
description = "x".repeat(300),
chapters = List(200) { BackupChapter(url = "/chapter/3/$it", name = "Chapter $it") },
),
),
backupCategories = listOf(
BackupCategory(name = "Reading", order = 0),
BackupCategory(name = "Done", order = 1),
),
backupSources = listOf(BackupSource(name = "One", sourceId = 1L), BackupSource(name = "Two", sourceId = 2L)),
backupPreferences = listOf(
BackupPreference("flag", BooleanPreferenceValue(true)),
BackupPreference("tags", StringSetPreferenceValue(setOf("a", "b"))),
),
backupSourcePreferences = listOf(
BackupSourcePreferences("source_1", listOf(BackupPreference("lang", StringPreferenceValue("en")))),
),
backupExtensionStores = listOf(
BackupExtensionStore(
indexUrl = "https://example.org/index.min.json",
name = "Store",
badgeLabel = null,
signingKey = "key",
contactWebsite = "https://example.org",
contactDiscord = null,
isLegacy = false,
extensionListUrl = null,
),
),
)
}
@@ -0,0 +1,73 @@
package eu.kanade.tachiyomi.data.download
import eu.kanade.tachiyomi.util.lang.Hash.md5
import eu.kanade.tachiyomi.util.storage.DiskUtil
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.shouldBe
import io.mockk.mockk
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import tachiyomi.core.common.preference.InMemoryPreferenceStore
import tachiyomi.domain.library.service.LibraryPreferences
class DownloadProviderTest {
private fun provider(disallowNonAscii: Boolean): DownloadProvider {
val preferences = LibraryPreferences(InMemoryPreferenceStore())
preferences.disallowNonAsciiFilenames.set(disallowNonAscii)
preferences.enableChapterNameHash.set(true)
return DownloadProvider(mockk(relaxed = true), mockk(relaxed = true), preferences)
}
private fun dirNameBefore0210(name: String, scanlator: String?, url: String, disallowNonAscii: Boolean): String {
val dirName = if (scanlator.isNullOrBlank()) name else "${scanlator}_$name"
return DiskUtil.buildValidFilename(dirName, DiskUtil.MAX_FILE_NAME_BYTES - 11, disallowNonAscii) +
"_" + md5(url).take(6)
}
private fun dirName0210(name: String, scanlator: String?, url: String, disallowNonAscii: Boolean): String {
return DiskUtil.buildValidFilename(scanlator!!) + "_" +
DiskUtil.buildValidFilename(name, DiskUtil.MAX_FILE_NAME_BYTES - 11, disallowNonAscii) +
"_" + md5(url).take(6)
}
@ParameterizedTest
@MethodSource("chapters")
fun `finds chapters downloaded before 0_21_0 and names new ones the same`(
name: String,
scanlator: String?,
disallowNonAscii: Boolean,
) {
val provider = provider(disallowNonAscii)
val expected = dirNameBefore0210(name, scanlator, URL, disallowNonAscii)
provider.getValidChapterDirNames(name, scanlator, URL) shouldContain expected
provider.getChapterDirName(name, scanlator, URL) shouldBe expected
}
@ParameterizedTest
@MethodSource("chapters")
fun `finds chapters downloaded by 0_21_0 and 0_21_1`(name: String, scanlator: String?, disallowNonAscii: Boolean) {
if (scanlator.isNullOrBlank()) return
provider(disallowNonAscii).getValidChapterDirNames(name, scanlator, URL) shouldContain
dirName0210(name, scanlator, URL, disallowNonAscii)
}
companion object {
private const val URL = "/chapter/1"
@JvmStatic
fun chapters() = listOf(
Arguments.of("Chapter 1", "Group", false),
Arguments.of(" Chapter 1", "Group", false),
Arguments.of(".Chapter 1", "Group", false),
Arguments.of("Chapter 1", "Group ", false),
Arguments.of("Chapter 1", "A/B ", false),
Arguments.of("Chapter ".repeat(40), "Group", false),
Arguments.of("Chapter 1", "翻訳グループ", true),
Arguments.of(" Chapter 1", null, false),
)
}
}
@@ -10,13 +10,15 @@ import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.webkit.ScriptHandler
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewFeature
import eu.kanade.tachiyomi.network.AndroidCookieJar
import eu.kanade.tachiyomi.util.system.isOutdated
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.CoroutineScope
import logcat.LogPriority
import okhttp3.Cookie
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
@@ -24,7 +26,11 @@ import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.system.logcat
import tachiyomi.i18n.MR
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
class CloudflareInterceptor(
private val context: Context,
@@ -35,6 +41,18 @@ class CloudflareInterceptor(
private val executor = ContextCompat.getMainExecutor(context)
// Requests to a host share its lock while a challenge is solved under it alone, so concurrent requests wait for
// one WebView instead of each opening their own
private val hostLocks = ConcurrentHashMap<String, ReentrantReadWriteLock>()
private val listenerScript = """
addEventListener("message", ({data}) => {
if (data?.source === "cloudflare-challenge") {
mihon?.postMessage(data.event);
}
})
""".trimIndent()
override fun shouldIntercept(response: Response): Boolean {
// Check if Cloudflare anti-bot is on
// Checking the cf-mitigated header is the official way to detect a Cloudflare challenge:
@@ -42,19 +60,50 @@ class CloudflareInterceptor(
return response.header("cf-mitigated") == "challenge" && response.header("Server") in SERVER_CHECK
}
override fun intercept(
chain: Interceptor.Chain,
request: Request,
response: Response,
): Response {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val lock = hostLocks.computeIfAbsent(request.url.host) { ReentrantReadWriteLock() }
val (response, clearance) = lock.read {
val response = chain.proceed(request)
if (!shouldIntercept(response)) return response
response to clearance(request.url)
}
// write releases the read hold of a request made from within another to the same host, like an extension
// refreshing a token, which would otherwise wait on itself
lock.write {
// Another request may have solved the challenge while this one waited
if (isCleared(request.url, clearance)) {
response.close()
} else {
if (!prepareWebView()) return response
solve(request, response, clearance)
}
}
return chain.proceed(request)
}
override fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response {
solve(request, response, clearance(request.url))
return chain.proceed(request)
}
private fun clearance(url: HttpUrl): String? {
return cookieManager.get(url).firstOrNull { it.name == "cf_clearance" }?.value
}
private fun isCleared(url: HttpUrl, previousClearance: String?): Boolean {
val clearance = clearance(url)
return !clearance.isNullOrBlank() && clearance != previousClearance
}
private fun solve(request: Request, response: Response, clearance: String?) {
try {
response.close()
cookieManager.remove(request.url, COOKIE_NAMES, 0)
val oldCookie = cookieManager.get(request.url)
.firstOrNull { it.name == "cf_clearance" }
resolveWithWebView(request, oldCookie)
return chain.proceed(request)
resolveWithWebView(request, clearance)
}
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
// we don't crash the entire app
@@ -66,65 +115,84 @@ class CloudflareInterceptor(
}
@SuppressLint("SetJavaScriptEnabled")
private fun resolveWithWebView(originalRequest: Request, oldCookie: Cookie?) {
private fun resolveWithWebView(originalRequest: Request, originalClearance: String?) {
// We need to lock this thread until the WebView finds the challenge solution url, because
// OkHttp doesn't support asynchronous interceptors.
val latch = CountDownLatch(1)
var webview: WebView? = null
var listenerScriptHandler: ScriptHandler? = null
val useIsolatedWorld = WebViewFeature.isFeatureSupported(WebViewFeature.JS_INJECTION_IN_FRAME_AND_WORLD)
var challengeFound = false
var cloudflareBypassed = false
var challengeFailed = false
var isWebViewOutdated = false
var isRenderProcessGone = false
val origRequestUrl = originalRequest.url.toString()
// A WebView can only load a page with GET, and loading the url of another method usually reaches an
// endpoint that answers GET without a challenge to solve. The clearance covers the whole site, so it's
// earned on the site's root page instead.
val challengeUrl = if (originalRequest.method == "GET") {
origRequestUrl
} else {
originalRequest.url.newBuilder().encodedPath("/").query(null).fragment(null).build().toString()
}
val headers = parseHeaders(originalRequest.headers)
executor.execute {
webview = createWebView(originalRequest)
webview.addJavascriptInterface(
object {
@Suppress("unused")
@JavascriptInterface
fun interactiveDetected() {
// The challenge cannot be solved non-interactively, abort.
fun onChallengeEvent(event: String) {
when (event) {
// The challenge cannot be solved non-interactively, abort.
"interactiveBegin" -> latch.countDown()
"fail" -> {
challengeFailed = true
latch.countDown()
}
},
"mihon",
)
}
}
if (useIsolatedWorld) {
// An isolated world keeps the bridge out of the page's reach
val world = WebViewCompat.getExecutionWorld(webview, "mihon")
val origins = setOf(originalRequest.url.origin())
WebViewCompat.addWebMessageListener(webview, "mihon", origins, world) { _, message, _, isMainFrame, _ ->
if (isMainFrame) message.data?.let(::onChallengeEvent)
}
listenerScriptHandler = WebViewCompat.addJavaScriptOnEvent(
webview,
listenerScript,
WebViewCompat.INJECTION_EVENT_DOCUMENT_START,
origins,
world,
)
} else {
webview.addJavascriptInterface(
object {
@Suppress("unused")
@JavascriptInterface
fun postMessage(event: String) = onChallengeEvent(event)
},
"mihon",
)
}
webview.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
fun isCloudFlareBypassed(): Boolean {
return cookieManager.get(origRequestUrl.toHttpUrl())
.firstOrNull { it.name == "cf_clearance" }
.let { it != null && it != oldCookie }
}
if (isCloudFlareBypassed()) {
if (!challengeFailed && isCleared(originalRequest.url, originalClearance)) {
cloudflareBypassed = true
latch.countDown()
}
if (url == origRequestUrl) {
if (url == challengeUrl) {
if (!challengeFound) {
// The first request didn't return the challenge, abort.
latch.countDown()
} else {
// Listen for an interactiveBegin event
view.evaluateJavascript(
"""
addEventListener("message", ({data}) => {
if (data?.source === "cloudflare-challenge" && data?.event === "interactiveBegin") {
mihon.interactiveDetected();
}
})
""".trimIndent(),
null,
)
} else if (!useIsolatedWorld) {
view.evaluateJavascript(listenerScript, null)
}
}
}
@@ -157,7 +225,7 @@ class CloudflareInterceptor(
}
}
webview.loadUrl(origRequestUrl, headers)
webview.loadUrl(challengeUrl, headers)
}
latch.awaitFor30Seconds()
@@ -169,13 +237,27 @@ class CloudflareInterceptor(
}
webview?.run {
if (!isRenderProcessGone) stopLoading()
if (!isRenderProcessGone) {
if (useIsolatedWorld) {
WebViewCompat.removeWebMessageListener(
this,
WebViewCompat.getExecutionWorld(this, "mihon"),
"mihon",
)
} else {
removeJavascriptInterface("mihon")
}
listenerScriptHandler?.remove()
stopLoading()
}
destroy()
}
}
// Throw exception if we failed to bypass Cloudflare
if (!cloudflareBypassed) {
// A clearance from a failed challenge would only be rejected again
cookieManager.remove(originalRequest.url, COOKIE_NAMES, 0)
// Prompt user to update WebView if it seems too outdated
if (isWebViewOutdated) {
context.toast(MR.strings.information_webview_outdated, Toast.LENGTH_LONG)
@@ -186,6 +268,11 @@ class CloudflareInterceptor(
}
}
// An origin rule without a port only matches the scheme's default one
private fun HttpUrl.origin(): String {
return if (port == HttpUrl.defaultPort(scheme)) "$scheme://$host" else "$scheme://$host:$port"
}
private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
private val COOKIE_NAMES = listOf("cf_clearance")
@@ -59,15 +59,23 @@ abstract class WebViewInterceptor(
return response
}
if (!prepareWebView()) return response
return intercept(chain, request, response)
}
/**
* Returns whether a WebView can be used, initializing it if so.
*/
protected fun prepareWebView(): Boolean {
if (!WebViewUtil.supportsWebView(context)) {
scope.launch(Dispatchers.Main) {
context.toast(MR.strings.information_webview_required, Toast.LENGTH_LONG)
}
return response
return false
}
initWebView
return intercept(chain, request, response)
return true
}
fun parseHeaders(headers: Headers): Map<String, String> {
@@ -50,6 +50,15 @@ class CategoryRepositoryImpl(
.subscribeToList()
}
override suspend fun getCategoriesByMangaIds(mangaIds: List<Long>): Map<Long, List<Category>> {
return database.categoryQueries
.getCategoriesByMangaIds(mangaIds) { mangaId, id, name, order, flags ->
mangaId to mapCategory(id, name, order, flags)
}
.awaitAsList()
.groupBy({ it.first }, { it.second })
}
override suspend fun insert(category: NewCategory) {
database.categoryQueries.insert(name = category.name, flags = category.flags)
}
@@ -113,6 +113,13 @@ class ChapterRepositoryImpl(
.awaitAsList()
}
override suspend fun getChaptersByMangaIds(mangaIds: List<Long>): Map<Long, List<Chapter>> {
return database.chapterQueries
.getChaptersByMangaIds(mangaIds, ::mapChapter)
.awaitAsList()
.groupBy { it.mangaId }
}
override suspend fun getChapterSummaryByMangaId(mangaId: Long): ChapterSummary {
return database.chapterQueries
.getChapterSummaryByMangaId(mangaId) { total, latestChapterNumber ->
@@ -48,6 +48,15 @@ class HistoryRepositoryImpl(
.awaitAsList()
}
override suspend fun getHistoryByMangaIds(mangaIds: List<Long>): Map<Long, List<History>> {
return database.historyQueries
.getHistoryByMangaIds(mangaIds) { mangaId, id, chapterId, readAt, readDuration ->
mangaId to HistoryMapper.mapHistory(id, chapterId, readAt, readDuration)
}
.awaitAsList()
.groupBy({ it.first }, { it.second })
}
override suspend fun resetHistory(historyId: Long) {
try {
database.historyQueries.resetHistoryById(historyId)
@@ -144,6 +144,13 @@ class MangaRepositoryImpl(
.toSet()
}
override suspend fun getExcludedScanlatorsByMangaIds(mangaIds: List<Long>): Map<Long, List<String>> {
return database.excluded_scanlatorQueries
.getExcludedScanlatorsByMangaIds(mangaIds)
.awaitAsList()
.groupBy({ it.manga_id }, { it.scanlator })
}
override fun getExcludedScanlatorsAsFlow(mangaId: Long): Flow<Set<String>> {
return database.excluded_scanlatorQueries
.getExcludedScanlatorsByMangaId(mangaId)
@@ -31,6 +31,13 @@ class TrackRepositoryImpl(
.awaitAsList()
}
override suspend fun getTracksByMangaIds(mangaIds: List<Long>): Map<Long, List<Track>> {
return database.manga_trackQueries
.getTracksByMangaIds(mangaIds, TrackMapper::mapTrack)
.awaitAsList()
.groupBy { it.mangaId }
}
override suspend fun getTracks(): List<Track> {
return database.manga_trackQueries
.getTracks(TrackMapper::mapTrack)
@@ -34,6 +34,14 @@ ON C.id = MC.category_id
WHERE MC.manga_id = :mangaId
ORDER BY C.`order`;
getCategoriesByMangaIds:
SELECT MC.manga_id, C.id, C.name, C.`order`, C.flags
FROM category C
JOIN manga_category MC
ON C.id = MC.category_id
WHERE MC.manga_id IN :mangaIds
ORDER BY MC.manga_id, C.`order`;
insert:
-- The position is taken inside the insert, so two categories created at once cannot claim the same one
INSERT INTO category(name, `order`, flags)
@@ -48,6 +48,16 @@ AND (
OR ES.scanlator IS NULL
);
getChaptersByMangaIds:
-- The unique index's order, which getChaptersByMangaId returns too, so backups keep their chapter order
SELECT
id, manga_id, remote_url, remote_name, remote_scanlator, remote_chapter_number,
remote_date_upload, remote_order, remote_memo, user_read, user_bookmark,
user_last_page_read, state_date_fetch
FROM chapter
WHERE manga_id IN :mangaIds
ORDER BY manga_id, remote_url;
getChapterSummaryByMangaId:
SELECT count(*) AS total, max(remote_chapter_number) AS latestChapterNumber
FROM chapter
@@ -21,3 +21,9 @@ getExcludedScanlatorsByMangaId:
SELECT scanlator
FROM excluded_scanlator
WHERE manga_id = :mangaId;
getExcludedScanlatorsByMangaIds:
SELECT manga_id, scanlator
FROM excluded_scanlator
WHERE manga_id IN :mangaIds
ORDER BY manga_id, scanlator;
@@ -20,6 +20,12 @@ SELECT id, chapter_id, read_at, read_duration
FROM history
WHERE manga_id = :mangaId;
getHistoryByMangaIds:
SELECT manga_id, id, chapter_id, read_at, read_duration
FROM history
WHERE manga_id IN :mangaIds
ORDER BY manga_id, id;
resetHistoryById:
UPDATE history
SET read_at = NULL
@@ -48,6 +48,14 @@ SELECT
FROM manga_track
WHERE manga_id = :mangaId;
getTracksByMangaIds:
SELECT
id, manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read,
total_chapters, status, score, start_date, finish_date, private
FROM manga_track
WHERE manga_id IN :mangaIds
ORDER BY manga_id, tracker_id;
upsert:
INSERT INTO manga_track(
manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read,
@@ -4,6 +4,64 @@ import kotlin.String;
import kotlin.collections.List;
import kotlinx.serialization.json.JsonObject;
-- Every view reads `mangas`, so they are dropped here and recreated against the new tables at the end
DROP VIEW historyView;
DROP VIEW libraryView;
DROP VIEW updatesView;
-- ---------------------------------------------------------------------------------------------------------
-- damaged indexes
-- ---------------------------------------------------------------------------------------------------------
-- Some databases hold rows their unique constraints should have rejected, which a damaged index explains. A
-- write to a table also writes its indexes and fails on a damaged one, and a read through one can miss rows,
-- so each table changed in place below is first copied from its rows alone, leaving its indexes behind.
CREATE TABLE mangas_copy AS
SELECT
_id, source, url, artist, author, description, genre, title, status, thumbnail_url, last_update, next_update,
initialized, viewer, chapter_flags, cover_last_modified, update_strategy, calculate_interval, notes, memo,
favorite_at
FROM mangas NOT INDEXED;
DROP TABLE mangas;
ALTER TABLE mangas_copy RENAME TO mangas;
CREATE TABLE chapters_copy AS
SELECT
_id, manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch,
date_upload, memo
FROM chapters NOT INDEXED;
DROP TABLE chapters;
ALTER TABLE chapters_copy RENAME TO chapters;
CREATE TABLE history_copy AS
SELECT _id, chapter_id, last_read, time_read
FROM history NOT INDEXED;
DROP TABLE history;
ALTER TABLE history_copy RENAME TO history;
CREATE TABLE mangas_categories_copy AS
SELECT _id, manga_id, category_id
FROM mangas_categories NOT INDEXED;
DROP TABLE mangas_categories;
ALTER TABLE mangas_categories_copy RENAME TO mangas_categories;
CREATE TABLE manga_sync_copy AS
SELECT
_id, manga_id, sync_id, remote_id, library_id, title, last_chapter_read, total_chapters, status, score,
remote_url, start_date, finish_date, private
FROM manga_sync NOT INDEXED;
DROP TABLE manga_sync;
ALTER TABLE manga_sync_copy RENAME TO manga_sync;
CREATE TABLE excluded_scanlators_copy AS
SELECT manga_id, scanlator
FROM excluded_scanlators NOT INDEXED;
DROP TABLE excluded_scanlators;
ALTER TABLE excluded_scanlators_copy RENAME TO excluded_scanlators;
-- Merging duplicate chapters looks their history up by chapter
CREATE INDEX history_chapter_id_index ON history(chapter_id);
-- ---------------------------------------------------------------------------------------------------------
-- orphans
-- ---------------------------------------------------------------------------------------------------------
@@ -21,11 +79,6 @@ DELETE FROM excluded_scanlators WHERE manga_id NOT IN (SELECT _id FROM mangas);
-- table - manga
-- ---------------------------------------------------------------------------------------------------------
-- Every view reads `mangas`, so they are dropped here and recreated against the new table below
DROP VIEW historyView;
DROP VIEW libraryView;
DROP VIEW updatesView;
-- Entries sharing a source and url are merged into one, since the new table won't hold both. The copies are
-- ranked with the ones in the library first, since that is the entry the user has, then by age. The first
-- one stays, takes each detail it is missing from the first copy that has it, and takes over everything
@@ -326,8 +379,7 @@ CREATE INDEX manga_category_category_id_index ON manga_category(category_id);
-- ---------------------------------------------------------------------------------------------------------
-- `last_read` is when the chapter was last read and `time_read` is how long it has been read for, so
-- both are renamed to say so. The chapter it belongs to is already unique, which leaves
-- `history_history_chapter_id_index` a prefix of that constraint.
-- both are renamed to say so. The chapter it belongs to is already unique, so the constraint indexes it.
-- The table keeps its name, so the original is renamed aside and the new one built under
-- the real name; renaming a table into place makes SQLite rewrite its stored definition,
-- leaving it spelled differently from the same table on a fresh install. The views were
@@ -349,11 +401,14 @@ CREATE TABLE history(
-- Reaching the entry a row belongs to meant going through its chapter; it names the entry
-- itself now, taken from that same chapter. A row whose chapter is already gone cannot name
-- one and is dropped, which is what the foreign key would do to it once it is enforced again.
-- Some databases hold more than one row for a chapter despite the old unique constraint, and the new one
-- rejects them, so they merge the way the history of duplicate chapters does above.
INSERT INTO history(id, manga_id, chapter_id, read_at, read_duration)
SELECT H._id, C.manga_id, H.chapter_id, H.last_read, H.time_read
SELECT min(H._id), C.manga_id, H.chapter_id, max(H.last_read), sum(H.time_read)
FROM history_temp H
JOIN chapter C
ON C.id = H.chapter_id;
ON C.id = H.chapter_id
GROUP BY H.chapter_id;
DROP TABLE history_temp;
@@ -388,19 +443,20 @@ DROP TABLE excluded_scanlators;
-- ---------------------------------------------------------------------------------------------------------
-- `manga_sync` is what tracking was called when it was still called syncing; `sync_id` is the tracker.
-- An entry that merged can leave two rows for the same tracker on the survivor, which the unique constraint
-- rejects, so the one to keep is chosen first: one on a copy in the library, then the one furthest read, then
-- the one on the copy ranked first, then the oldest. A track is taken whole rather than merged field by field,
-- because two rows on the same tracker can point at different remote entries.
-- An entry that merged can leave two rows for the same tracker on the survivor, and an entry that didn't can
-- still hold two despite the old unique constraint. The new one rejects both, so the one to keep is chosen
-- first: one on a copy in the library, then the one furthest read, then the one on the copy ranked first, then
-- the oldest. A track is taken whole rather than merged field by field, because two rows on the same tracker
-- can point at different remote entries.
CREATE TEMP TABLE duplicate_tracks AS
SELECT
T._id,
first_value(T._id) OVER (
PARTITION BY D.keep_id, T.sync_id
PARTITION BY coalesce(D.keep_id, T.manga_id), T.sync_id
ORDER BY D.favorite DESC, T.last_chapter_read DESC, D.precedence ASC, T._id ASC
) AS keep_track_id
FROM manga_sync T
JOIN duplicate_mangas D
LEFT JOIN duplicate_mangas D
ON D._id = T.manga_id;
DELETE FROM manga_sync
@@ -16,6 +16,8 @@ interface CategoryRepository {
fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>>
suspend fun getCategoriesByMangaIds(mangaIds: List<Long>): Map<Long, List<Category>>
suspend fun insert(category: NewCategory)
suspend fun insertAll(categories: List<NewCategory>)
@@ -20,6 +20,8 @@ interface ChapterRepository {
suspend fun getChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean = false): List<Chapter>
suspend fun getChaptersByMangaIds(mangaIds: List<Long>): Map<Long, List<Chapter>>
suspend fun getChapterSummaryByMangaId(mangaId: Long): ChapterSummary
suspend fun getScanlatorsByMangaId(mangaId: Long): List<String>
@@ -15,6 +15,8 @@ interface HistoryRepository {
suspend fun getHistoryByMangaId(mangaId: Long): List<History>
suspend fun getHistoryByMangaIds(mangaIds: List<Long>): Map<Long, List<History>>
suspend fun resetHistory(historyId: Long)
suspend fun resetHistoryByMangaId(mangaId: Long)
@@ -43,6 +43,8 @@ interface MangaRepository {
suspend fun getExcludedScanlators(mangaId: Long): Set<String>
suspend fun getExcludedScanlatorsByMangaIds(mangaIds: List<Long>): Map<Long, List<String>>
fun getExcludedScanlatorsAsFlow(mangaId: Long): Flow<Set<String>>
suspend fun setExcludedScanlators(mangaId: Long, excludedScanlators: Set<String>)
@@ -9,6 +9,8 @@ interface TrackRepository {
suspend fun getTracksByMangaId(mangaId: Long): List<Track>
suspend fun getTracksByMangaIds(mangaIds: List<Long>): Map<Long, List<Track>>
suspend fun getTracks(): List<Track>
fun getTracksAsFlow(): Flow<List<Track>>
+3 -1
View File
@@ -15,7 +15,8 @@ androidx-coreSplashScreen = "1.2.0"
androidx-glance = "1.2.0"
androidx-interpolator = "1.0.0"
androidx-lifecycle = "2.11.0"
androidx-paging = "3.5.2"
androidx-navigationEvent = "1.2.0"
androidx-paging = "3.5.3"
androidx-preference = "1.2.1"
androidx-profileInstaller = "1.4.1"
androidx-recyclerView = "1.4.0"
@@ -110,6 +111,7 @@ androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process",
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "androidx-lifecycle" }
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
androidx-lifecycle-viewmodelCompose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
androidx-navigationEvent = { module = "androidx.navigationevent:navigationevent", version.ref = "androidx-navigationEvent" }
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" }
androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "androidx-paging" }
androidx-preference = { module = "androidx.preference:preference-ktx", version.ref = "androidx-preference" }
@@ -744,7 +744,6 @@
<string name="onboarding_permission_action_grant">منح</string>
<string name="ext_permission_install_apps_warning">تثبيت الإضافات يتطلب أذونات. انقر هنا لمنحها.</string>
<string name="ext_revoke_trust">إبطال الإضافات غير الموثوق بها</string>
<string name="action_open_repo">مستودع مفتوح المصدر</string>
<string name="invalid_backup_file_error">الخطأ الكامل:</string>
<string name="private_settings">تضمين إعدادات حساسة (على سبيل المثال، رموز تسجيل دخول المتتبع)</string>
<string name="manga_interval_expected_update_soon">قريباً</string>
@@ -34,7 +34,6 @@
<string name="ext_obsolete">অচল</string>
<string name="ext_installer_pref">ইন্সটলাৰ</string>
<string name="ext_installer_legacy">লিগাচি</string>
<string name="action_open_repo">উৎস ৰেপ\' খোলক</string>
<string name="pref_show_navigation_mode_summary">পাঠক খোলোঁতে চমুকৈ দেখুৱাওক</string>
<string name="pref_grayscale">গ্ৰেস্কেল</string>
<string name="pref_inverted_colors">বিপৰীত ৰং</string>
@@ -424,7 +424,6 @@
<string name="ext_permission_install_apps_warning">Для ўсталёўкі пашырэнняў патрабуюцца дазволы. Націсніце сюды, каб даць іх.</string>
<string name="ext_installer_shizuku_unavailable_dialog">Усталюйце і запусціце Shizuku, каб выкарыстоўваць яго як усталёўнік пашырэнняў.</string>
<string name="ext_revoke_trust">Адклікаць давер да ненадзейных пашырэнняў</string>
<string name="action_open_repo">Адкрыць рэпазітар крыніцы</string>
<string name="pref_page_rotate">Паварочваць шырокія старонкі пад памер экрана</string>
<string name="pref_page_rotate_invert">Адлюстроўваць арыентацыю павернутых шырокіх старонак</string>
<string name="pref_double_tap_zoom">Падвойны дотык для павелічэння</string>
@@ -547,7 +547,7 @@
<string name="not_installed">Не е инсталиран</string>
<string name="rotation_reverse_portrait">Обърнат портрет</string>
<string name="ext_info_language">Език</string>
<string name="crash_screen_title">Случи се неочаквана грешка</string>
<string name="crash_screen_title">Опаа!</string>
<string name="auto_download_while_reading">Автоматично изтегляне при четене</string>
<string name="description_placeholder">Няма описание</string>
<string name="empty_backup_error">Няма заглавия в библиотеката за резервно копие</string>
@@ -598,7 +598,7 @@
<string name="source_filter_empty_screen">Не беше намерен инсталиран източник</string>
<string name="action_remove_everything">Премахни всичко</string>
<string name="ext_info_version">Версия</string>
<string name="on_hiatus">В хиатус</string>
<string name="on_hiatus">Прекъсната за неопределен период</string>
<string name="custom_cover">Корица по избор</string>
<string name="action_search_hint">Търсене…</string>
<string name="disabled_nav">Деактивирано</string>
@@ -650,7 +650,7 @@
<string name="onboarding_permission_notifications_description">Получавай известия за обновления в библиотеката и други</string>
<string name="pref_library_update_smart_update">Умно обновяване</string>
<string name="pref_mark_duplicate_read_chapter_read_existing">След прочитане на глава</string>
<string name="has_results">Има резултати</string>
<string name="has_results">Имащи резулатати</string>
<string name="unknown_title">Непознато заглавие</string>
<string name="possible_duplicates_title">Възможни дубликати</string>
<string name="action_add_anyway">Добави все пак</string>
@@ -731,4 +731,64 @@
<string name="onboarding_permission_install_apps">Разрешение за инсталиране на приложения</string>
<string name="onboarding_permission_ignore_battery_opts">Фоново потребление на батерията</string>
<string name="onboarding_storage_info">Избери папка, където %1$s ще съхранява изтеглени глави, резервни копия и други.\n\nПрепоръчва се специална папка.\nИзбрана папка: %2$s</string>
<string name="action_copy_to_clipboard">Копирай на клипборда</string>
<string name="logging_in">Влизане…</string>
<string name="label_support_us">Подкрепи ни</string>
<string name="supportUsScreen.perks">Патроните получават достъп до ексклузивни канали, като предстоят още предимства.</string>
<string name="supportUsScreen.donationPlatform.patreon">Patreon (предпочитан)</string>
<string name="supportUsScreen.donationPlatform.opencollective">OpenCollective</string>
<string name="supportUsScreen.currentlySupportedBy">В момента Mihon се подкрепя от %d+ патрони. Искаме да благодарим на всеки един от вас. Нека заедно направим Mihon още по-добър.</string>
<string name="supportUsScreen.contactPlatform">Discord</string>
<string name="donationCampaign.paragraph1">Здравейте! Аз съм основният разработчик на Mihon и бих искал да ви помоля да обмислите финансова подкрепа за проекта. При достатъчно подкрепа ще мога да се занимавам с Mihon на пълен работен ден и да се съсредоточа както върху отдавна съществуващи проблеми, така и върху силно желани от потребителите функции.</string>
<string name="donationCampaign.paragraph2">Можете да направите дарение чрез Patreon (за предпочитане) или OpenCollective. Ако имате въпроси или желаете да окажете подкрепа чрез алтернативни методи, не се колебайте да се свържете с мен в Discord сървъра на Mihon.</string>
<string name="ext_unsupported_message">Това разширение е създадено за версия на библиотеката с разширения, която тази версия на приложението не може да зареди. Актуализирането на приложението или на разширението може да помогне.</string>
<string name="migrationListScreenTitle">Миграция</string>
<string name="migrationListScreenTitleWithProgress">Миграция (%1$d/%2$d)</string>
<string name="migrationListScreen.copyActionLabel">Копирай</string>
<string name="migrationListScreen.migrateActionLabel">Мигрирай</string>
<string name="migrationListScreen.noMatchFoundText">Няма намерени алтернативи</string>
<string name="migrationListScreen.latestChapterLabel">Последно: %1$s</string>
<string name="migrationListScreen.unknownLatestChapter">Неизвестно</string>
<string name="migrationListScreen.searchManuallyActionLabel">Търси ръчно</string>
<string name="migrationListScreen.skipActionLabel">Не мигрирай</string>
<string name="migrationListScreen.migrateNowActionLabel">Мигрирай сега</string>
<string name="migrationListScreen.copyNowActionLabel">Копирай сега</string>
<string name="migrationListScreen.exitDialogTitle">Спри миграцията?</string>
<string name="migrationListScreen.exitDialog.stopLabel">Спри</string>
<string name="migrationListScreen.exitDialog.cancelLabel">Прекрати</string>
<string name="migrationListScreen.migrateDialog.copyLabel">Копирай</string>
<string name="migrationListScreen.migrateDialog.migrateLabel">Мигрирай</string>
<string name="migrationListScreen.migrateDialog.cancelLabel">Прекрати</string>
<string name="migrationListScreen.progressDialog.cancelLabel">Прекрати</string>
<string name="migrationListScreen.matchWithoutChapterToast">Не са открити глави; този запис не може да се използва за миграция.</string>
<string name="donationCampaign.title">Харесва ли ти приложението?</string>
<string name="donationCampaign.paragraph3">Въпреки това, Mihon ще продължи да се поддържа от мен и доброволците и ще остане с отворен код.</string>
<string name="donationCampaign.contactPlatform">Discord</string>
<string name="donationCampaign.dismiss">Отхвърли</string>
<string name="scanlator">Скано-преводач</string>
<string name="action_filter_interval_custom">Персонализирана честота на актуализиране</string>
<string name="action_apply">Приложи</string>
<string name="action_webview_close_tab">Затвори раздела</string>
<string name="pref_onboarding_guide">Въвеждащо ръководство</string>
<string name="app_migrating_title">Завършване на актуализацията</string>
<string name="app_migrating_summary">Подготвяне на всичко необходимо след актуализацията. Това може да отнеме няколко минути, затова дръжте приложението отворено.</string>
<string name="onboarding_permission_ignore_battery_opts_description">Избягвайте прекъсвания на продължителни актуализации на библиотеки, изтегляния и възстановявания от резервни копия.</string>
<string name="onboarding_permission_crashlytics">Изпрати записи от сривове</string>
<string name="onboarding_permission_crashlytics_description">Изпрати анонимни записи от сривове на разработчиците.</string>
<string name="onboarding_permission_analytics">Разрешаване на анализи</string>
<string name="onboarding_permission_analytics_description">Изпращайте анонимизирани данни за използването, за да подобрите функциите на приложението.</string>
<string name="onboarding_permission_action_grant">Позволи</string>
<string name="onboarding_guides_new_user">Нови си в %s? Препоръчваме ти да разгледаш ръководството за начало.</string>
<string name="theme_catppuccin">Catppuccin</string>
<string name="theme_tokyonight">Токийска нощ</string>
<string name="pref_relative_format">Относителни времеви отпечатъци</string>
<string name="pref_display_images_description">Изобразяване на изображения в описанията на манга</string>
<string name="pref_security">Сигурност</string>
<string name="pref_firebase">Анализи и записи от сривове</string>
<string name="firebase_summary">Изпращането на записи от сривове и данни за анализ ще ни позволи да откриваме и отстраняваме проблеми, да подобряваме производителността и да правим бъдещите актуализации по-съобразени с вашите нужди.</string>
<string name="pref_category_extensions">Разширения</string>
<string name="file_null_uri_error">Не е избран файл</string>
<string name="update_check_skip">Пропусни тази версия</string>
<string name="update_check_skip_title">Пропусни%s?</string>
<string name="tracked_privately">Проследявани в поверителен режим</string>
</resources>
@@ -689,7 +689,6 @@
<string name="ext_info_language">ভাষা</string>
<string name="ext_permission_install_apps_warning">এক্সটেনশন ইনস্টল করতে অনুমতি লাগবে। অনুমতি দিতে এখানে ট্যাপ করুন।</string>
<string name="ext_revoke_trust">অজানা এক্সটেনশনের ট্রাস্ট বাতিল</string>
<string name="action_open_repo">রিপোর সোর্সে যান</string>
<string name="pref_page_rotate">চওড়া পেজ রোটেট করে ফিট হবে</string>
<string name="pref_page_rotate_invert">রোটেট করা চওড়া পেজের অরিয়েন্টেশন উল্টানো হবে</string>
<string name="pref_double_tap_zoom">জুম করতে ডাবল ট্যাপ</string>
@@ -755,7 +755,6 @@
<string name="ext_revoke_trust">Revoca les extensions de confiança desconegudes</string>
<string name="manga_interval_expected_update_soon">Aviat</string>
<string name="manga_interval_custom_amount">Freqüència d’actualització personalitzada:</string>
<string name="action_open_repo">Obre el repositori d’origen</string>
<string name="file_picker_uri_permission_unsupported">No s’ha pogut obtenir accés persistent a la carpeta. És possible que l’aplicació es comporti de manera inesperada.</string>
<string name="label_upcoming">Pròxim</string>
<string name="upcoming_guide">Guia de pròximes actualitzacions</string>
@@ -953,4 +952,15 @@
<string name="refresh_tracker_profile">Actualitza el perfil del servei de seguiment</string>
<string name="refresh_tracker_success">S’ha actualitzat el perfil de %s</string>
<string name="refresh_tracker_error">S’ha produït un error en actualitzar %1$s: %2$s</string>
<string name="update_check_skip_title">Voleu ometre %s?</string>
<string name="update_check_skip_message">Les actualitzacions sovint contenen solucions d’errors i millores de seguretat. No tornareu a rebre cap recordatori d’aquesta versió, però sí que en rebreu si se’n publica una altra de més nova.</string>
<string name="update_check_skip">Omet aquesta versió</string>
<string name="app_migrating_summary">S’estan enllestint algunes coses després de l’actualització. Pot tardar uns minuts, mantingueu oberta l’aplicació.</string>
<string name="pref_enable_chapter_name_hash_details">Assegura que s’afegeixi un sufix amb un resum únic al nom dels capítols per a evitar conflictes de noms. Si es desactiva, els capítols amb noms idèntics no es poden baixar.</string>
<string name="pref_enable_chapter_name_hash">Activa el sufix del resum al nom del capítol</string>
<string name="ext_not_loaded_details">Per què no s’ha carregat</string>
<string name="pref_load_ahead_only_over_wifi">Precarrega només amb Wi-Fi</string>
<string name="app_migrating_title">S’està finalitzant l’actualització</string>
<string name="pref_load_ahead_only_over_wifi_summary">Estalvia dades mòbils carregant només les pàgines pròximes a la que esteu llegint</string>
<string name="information_webview_crashed">El WebView ha deixat de funcionar i s’ha tancat la pàgina</string>
</resources>
@@ -753,7 +753,6 @@
<string name="onboarding_storage_help_action">Průvodce uložištěm</string>
<string name="theme_nord">Nord</string>
<string name="ext_revoke_trust">Zrušit důvěryhodná neznámá rozšíření</string>
<string name="action_open_repo">Open source repozitář</string>
<string name="manga_interval_expected_update_soon">Brzy</string>
<string name="onboarding_storage_help_info">Aktualizujete ze starší verze a nevíte, co vybrat? Další informace naleznete v průvodci úložištěm.</string>
<string name="action_add_anyway">Přesto přidat</string>
@@ -930,4 +929,38 @@
<string name="transition_animation_stack_left">Balíčky (vlevo)</string>
<string name="pref_cutout_mode_dual">Režim výřezu displeje (duální)</string>
<string name="cutout_mode_shift">Posun</string>
<string name="app_migrating_title">Dokončuji aktualizaci</string>
<string name="app_migrating_summary">Připravuji vše po aktualizaci. Může to trvat několik minut, proto nechte aplikaci otevřenou.</string>
<string name="pref_category_extensions">Rozšíření</string>
<string name="pref_allowed_content_warnings">Povolená varování o obsahu</string>
<string name="pref_apply_content_warnings_to_installed">Použít také na nainstalovaná rozšíření</string>
<string name="pref_apply_content_warnings_to_installed_summary">I když je funkce vypnutá, již nainstalovaná rozšíření se nadále načítají a aktualizují bez ohledu na jejich varování</string>
<string name="content_warnings_info">Nainstalovaná rozšíření, která nejsou načtena, zůstávají v seznamu uvedena bez odkazu na zdroj; ta, která jste nenainstalovali, jsou skrytá. To však nezabrání tomu, aby neoficiální nebo potenciálně nesprávně označená rozšíření zobrazovala v aplikaci obsah pro osoby starší 18 let.</string>
<string name="pref_enable_chapter_name_hash">Zapnout hashovou příponu názvů kapitol</string>
<string name="pref_enable_chapter_name_hash_details">Zajišťuje, že k názvům kapitol bude přidána jedinečná hashová přípona, aby se předešlo konfliktům při pojmenování. Je-li tato funkce deaktivována, nelze stáhnout kapitoly se stejnými názvy.</string>
<string name="ext_filtered">Filtrováno</string>
<string name="ext_not_loaded">Nenačteno</string>
<string name="ext_not_loaded_dialog">Nepodařilo se načíst</string>
<string name="ext_not_loaded_details">Proč se to nenačte</string>
<string name="ext_copy_stacktrace">Zkopírovat trasu zásobníku</string>
<string name="ext_filtered_message">Varování ohledně obsahu tohoto rozšíření jste si nevybrali sami, takže jeho zdroje nejsou k dispozici.</string>
<string name="ext_unsigned_message">Toto rozšíření není podepsané, takže neexistuje žádný podpis, proti kterému by se dalo ověřit.</string>
<string name="ext_unsupported_message">Toto rozšíření bylo vytvořeno pro verzi knihovny rozšíření, kterou tato verze aplikace nedokáže načíst. Pomocí může být aktualizace aplikace nebo rozšíření.</string>
<string name="ext_malformed_message">V tomto rozšíření chybí informace, které aplikace potřebuje k jeho načtení.</string>
<string name="ext_load_failed_message">Toto rozšíření vyhodilo při načítání chybu, takže nejsou k dispozici žádné jeho zdrojové kódy.</string>
<string name="ext_info_warning">Varování</string>
<string name="ext_content_warning_safe">Bezpečné</string>
<string name="ext_content_warning_mixed">Smíšené</string>
<string name="ext_content_warning_nsfw">18+</string>
<string name="ext_content_warning_mixed_description">Zdroje z tohoto rozšíření mohou obsahovat jak bezpečný obsah, tak obsah určený pro osoby starší 18 let.</string>
<string name="ext_content_warning_nsfw_description">Zdroje tohoto rozšíření se zaměřují především na obsah pro osoby starší 18 let.</string>
<string name="pref_load_ahead_only_over_wifi">Načíst dopředu pouze přes Wi-Fi</string>
<string name="pref_load_ahead_only_over_wifi_summary">Šetří mobilní data tím, že načítá pouze stránky v blízkosti té, kterou právě čtete</string>
<string name="refresh_tracker_profile">Obnovit profil sledování</string>
<string name="refresh_tracker_success">Profil %s byl aktualizován</string>
<string name="refresh_tracker_error">Chyba při aktualizaci %1$s: %2$s</string>
<string name="update_check_skip">Přeskočit tuto verzi</string>
<string name="update_check_skip_title">Přeskočit %s?</string>
<string name="update_check_skip_message">Aktualizace často obsahují opravy chyb a vylepšení zabezpečení. Na tuto verzi již nebudete znovu upozorněni, ale budete informováni, jakmile vyjde novější verze.</string>
<string name="information_webview_crashed">WebView přestal fungovat a stránka byla zavřena</string>
</resources>
@@ -610,7 +610,6 @@
<string name="remove_private_extension_message">«%s» хушмана катертесшӗн-и?</string>
<string name="ext_permission_install_apps_warning">Хушмасене лартма ирӗксем кирлӗ. Ирӗксене пама кунта пус.</string>
<string name="ext_revoke_trust">Шанчӑклӑ мар хушмасенчен шанчӑк туртса илмелле</string>
<string name="action_open_repo">Хушмасен ҫӳпҫин ҫӑл куҫне уҫ</string>
<string name="pref_double_tap_zoom">Пысӑклатма икӗ хут пусмалла</string>
<string name="pref_flash_duration_summary">%1$s мҫ</string>
<string name="pref_flash_style_black">Хура</string>
@@ -753,7 +753,6 @@
<string name="pref_library_update_smart_update">Intelligentes Aktualisieren</string>
<string name="manga_interval_expected_update">Ca. %1$s bis zur Veröffentlichung neuer Kapitel, wird ca. alle %2$s überprüft.</string>
<string name="theme_nord">Nord</string>
<string name="action_open_repo">Open-Source-Repository</string>
<string name="manga_interval_expected_update_soon">Bald</string>
<string name="ext_revoke_trust">Zertifikatsbestätigungen zurücknehmen</string>
<string name="pref_webtoon_disable_zoom_out">Rauszoomen deaktivieren</string>
@@ -755,7 +755,6 @@
<string name="pref_library_update_smart_update">Έξυπνη ενημέρωση</string>
<string name="manga_interval_expected_update_soon">Σύντομα</string>
<string name="ext_revoke_trust">Ανάκληση αξιόπιστων άγνωστων επεκτάσεων</string>
<string name="action_open_repo">Αποθετήριο ανοικτού κώδικα</string>
<string name="pref_webtoon_disable_zoom_out">Απενεργοποίηση σμίκρυνσης εικόνας</string>
<string name="action_add_anyway">Προσθέστε ούτως ή άλλως</string>
<string name="label_upcoming">Ανερχόμενο</string>
@@ -578,7 +578,6 @@
<string name="pref_flash_duration">Fulma daŭro</string>
<string name="pref_flash_style_black">Nigro</string>
<string name="ext_installer_shizuku_unavailable_dialog">Instali kaj lanĉi Shizuku-n por uzi Shizuku-n kiel etendaĵa instalilo.</string>
<string name="action_open_repo">Malfermitkoda deponejo</string>
<string name="pref_page_rotate_invert">Inversigi orientiĝon de turnitaj larĝaj paĝoj</string>
<string name="ext_revoke_trust">Eksvalidigi fidindajn nekonatajn etendaĵojn</string>
<string name="pref_show_reading_mode_summary">Koncize montri aktualan reĝimon kiam legilo estas malfermita</string>
@@ -902,4 +901,66 @@
<string name="pref_filter_update_categories_details">Ĝisdatigoj en ekskluzivitaj kategorioj ne estos montrotaj, eĉ se ili estas ankaŭ en inkluzivitaj kategorioj.</string>
<string name="pref_filter_upcoming_categories_details">Titoloj en ekskluzivitaj kategorioj ne estos montrotaj, eĉ se ili estas ankaŭ en inkluzivitaj kategorioj.</string>
<string name="pref_category_extensions">Etendaĵoj</string>
<string name="pref_apply_content_warnings_to_installed">Ankaŭ apliki al instalitaj etendaĵoj</string>
<string name="pref_apply_content_warnings_to_installed_summary">Kiam malŝaltita, etendaĵoj kiuj vi jam instalis daŭras ŝargi kaj ĝisdatigi malgraŭ iliaj avertoj</string>
<string name="content_warnings_info">Instalitaj etendaĵoj kiuj ne estas ŝargitaj restas listitaj sen iliaj fontoj; tiuj kiujn vi ne instalis estas kaŝitaj. Ĉi tio ne preventas neoficialajn aŭ eventuale malprave markitajn etendaĵojn montri 18+-enhavon ene de la apo.</string>
<string name="pref_enable_chapter_name_hash">Ŝalti haketaĵan sufikson de ĉapitra nomo</string>
<string name="pref_enable_chapter_name_hash_details">Certigas, ke unika haketaĵa sufikso estas aldonita al ĉapitraj nomoj por preventi nomajn konfliktojn. Se malŝaltita, ĉapitroj kun indentaj nomoj ne eblas esti elŝutitaj.</string>
<string name="ext_filtered">Filtrita</string>
<string name="ext_not_loaded_dialog">Ne ŝargeblis</string>
<string name="ext_not_loaded_details">Kial ĝi ne estas ŝargita</string>
<string name="ext_copy_stacktrace">Kopii stakan spuron</string>
<string name="ext_filtered_message">La etendaĵa enhava averto ne estas tiu, kiun vi elektis ŝargi, do ĝiaj fontoj ne disponeblas.</string>
<string name="ext_unsigned_message">Ĉi tiu etendaĵo ne estas subskribita, do estas neniu subskribo kontraŭ kiu ĝi povas esti kontrolita.</string>
<string name="app_migrating_title">Finado de la ĝisdatigo</string>
<string name="app_migrating_summary">Pretigas aferojn post la ĝisdatigo. Ĉi tio eble daŭros kelkajn minutojn, do teni la apon malfermitan.</string>
<string name="pref_allowed_content_warnings">Atentoj pri permesita enhavo</string>
<string name="ext_not_loaded">Ne ŝargita</string>
<string name="ext_unsupported_message">Ĉi tiu etendaĵo estis kunmetita por versio de la etendaĵa biblioteko kiun ĉi tiu apo ne povas ŝargi. Ĝisdatigo de la apo aŭ la etendaĵo eble helpos.</string>
<string name="ext_load_failed_message">Ĉi tiu etendaĵo eraris dum ŝargado, do neniu de ĝiaj fontoj disponeblas.</string>
<string name="ext_content_warning_safe">Sendanĝera</string>
<string name="ext_content_warning_mixed">Miksa</string>
<string name="ext_content_warning_nsfw">18+</string>
<string name="ext_content_warning_mixed_description">Fontoj de ĉi tiu etendaĵo eble enhavas kaj sendanĝeran kaj 18+-an enhavon.</string>
<string name="ext_content_warning_nsfw_description">Fontoj de ĉi tiu etendaĵo estas ĉefe koncentrita pri 18+-enhavo.</string>
<string name="pref_load_ahead_only_over_wifi">Antaŭŝargi nur per vifio</string>
<string name="pref_load_ahead_only_over_wifi_summary">Ŝparas poŝtelefonretajn datumojn per ŝargado de nur la paĝoj proksimaj al tiu, kiun vi legas</string>
<string name="pref_high_quality_renderer">Uzi altkvalitan bildigilon</string>
<string name="webgpu_viewer">WebGPU</string>
<string name="pref_transition_animation">Transpasa animacio</string>
<string name="pref_transition_animation_dual">Transpasa animacio (duobla)</string>
<string name="transition_animation_basic">Baza</string>
<string name="transition_animation_flip_right">Paĝa turno (dekstren)</string>
<string name="transition_animation_flip_left">Paĝa turno (maldekstren)</string>
<string name="transition_animation_stack_left">Stako (maldesktren)</string>
<string name="transition_animation_stack_right">Stako (desktren)</string>
<string name="transition_animation_stack_up">Stako (supren)</string>
<string name="transition_animation_stack_down">Stako (malsupren)</string>
<string name="transition_animation_sphere">Sfero</string>
<string name="transition_animation_cube_inside">Kubo (enen)</string>
<string name="transition_animation_cube_outside">Kubo (elen)</string>
<string name="transition_animation_fade">Dissolvo</string>
<string name="transition_animation_fade_white">Dissolvi al blanko</string>
<string name="transition_animation_none">Neniu</string>
<string name="transition_animation_flip">Paĝa turno</string>
<string name="pref_cutout_mode">Montri fortondan reĝimon</string>
<string name="pref_cutout_mode_dual">Montri fortondan reĝimon (duobla)</string>
<string name="cutout_mode_ignore">Ignori</string>
<string name="cutout_mode_avoid">Eviti</string>
<string name="cutout_mode_shift">Ŝovi</string>
<string name="pref_dual_page_view">Dupaĝa vido</string>
<string name="dual_page_view_never">Neniam</string>
<string name="dual_page_view_always">Ĉiam</string>
<string name="dual_page_view_wide">Kiam larĝa</string>
<string name="pref_continuous_minwidth">Minimuma larĝo</string>
<string name="pref_continuous_gap">Interspaco</string>
<string name="refresh_tracker_profile">Aktualigi sekvilan profilon</string>
<string name="refresh_tracker_success">Profilo de %s aktualigita</string>
<string name="refresh_tracker_error">Eraris aktualigante %1$s: %2$s</string>
<string name="update_check_skip">Preterpasi ĉi tiun version</string>
<string name="update_check_skip_title">Ĉu preterpasi %s?</string>
<string name="update_check_skip_message">Ĝisdatigoj ofte enhavas cimoriparoj kaj sekurecaj plibonigoj. Vi ne estos memorigita denove pri ĉi tiu versio, sed vi daŭre estos sciigita, kiam pli nova estas eldonita.</string>
<string name="information_webview_crashed">WebView ĉesis funkcii kaj la paĝo estis fermita</string>
<string name="ext_malformed_message">Al ĉi tiu etendaĵo mankas informo, kiun la apo bezonas por ŝargi ĝin.</string>
<string name="ext_info_warning">Averto</string>
</resources>
@@ -755,7 +755,6 @@
<string name="theme_nord">Norteño</string>
<string name="manga_interval_expected_update_soon">Pronto</string>
<string name="ext_revoke_trust">Dejar de marcar todas la extensiones desconocidas como de confianza</string>
<string name="action_open_repo">Abrir repositorio</string>
<string name="pref_webtoon_disable_zoom_out">Desactivar alejar</string>
<string name="pref_display_profile">Perfil de visualización personalizado</string>
<string name="action_add_anyway">Añadir de todos modos</string>
@@ -619,7 +619,6 @@
<string name="onboarding_action_finish">شروع کنید</string>
<string name="onboarding_action_skip">پرش</string>
<string name="ext_permission_install_apps_warning">مجوز برای نصب افزونه ها لازم است. به اینجا ضربه بزنید تا اعطا کنید.</string>
<string name="action_open_repo">مخزن منبع باز</string>
<string name="pref_flash_page_summ">مقدار رد باقی مانده در نمایش گر های E-ink کاهش میابد</string>
<string name="onboarding_storage_action_select">انتخاب یک پوشه</string>
<string name="onboarding_storage_selection_required">یک پوشه باید انتخاب شود</string>
@@ -755,7 +755,6 @@
<string name="theme_nord">Nord</string>
<string name="manga_interval_expected_update_soon">Malapit na</string>
<string name="ext_revoke_trust">Bawiin ang mga pinagkakatiwalaang hindi kilalang extension</string>
<string name="action_open_repo">Open source na repo</string>
<string name="pref_webtoon_disable_zoom_out">Patayin ang pag-zoom out</string>
<string name="action_add_anyway">Idagdag pa rin</string>
<string name="pref_display_profile">Custom na profile sa display</string>
@@ -950,4 +949,18 @@
<string name="transition_animation_stack_left">Pa-stack (kaliwa)</string>
<string name="pref_cutout_mode_dual">Modo ng display cutout (pa-doble)</string>
<string name="cutout_mode_shift">Pag-usog</string>
<string name="app_migrating_title">Tinatapos ang update</string>
<string name="app_migrating_summary">Inihahanda pa ang lahat pagkatapos ng update. Maaari itong tumagal nang ilang minuto, kaya pabayaang nakabukas ang app.</string>
<string name="pref_enable_chapter_name_hash">Paganahin ang hash suffix sa pangalan ng kabanata</string>
<string name="pref_enable_chapter_name_hash_details">Sinisigurong may natatanging hash suffix na idinaragdag sa pangalan ng mga kabanata upang maiwasan ang conflict sa pangalan. Kapag naka-off, hindi maida-download ang mga kabanatang may magkakaparehong pangalan.</string>
<string name="ext_not_loaded_details">Bakit hindi ito na-load</string>
<string name="pref_load_ahead_only_over_wifi">Mag-load lang sa Wi-Fi</string>
<string name="pref_load_ahead_only_over_wifi_summary">Makatitipid sa mobile data sa pag-load lang ng mga pahinang malapit sa kasalukuyan mong binabasa</string>
<string name="refresh_tracker_profile">I-refresh ang tracker profile</string>
<string name="refresh_tracker_success">%s na profile ay na-refresh</string>
<string name="refresh_tracker_error">%1$s error sa pag-refresh: %2$s</string>
<string name="update_check_skip">Laktawan ang bersyon na ito</string>
<string name="update_check_skip_title">Laktawan ang %s?</string>
<string name="update_check_skip_message">Ang mga update ay karaniwang may kasamang mga bug fix at pagpapahusay sa seguridad. Hindi ka na muling ipapaalala tungkol sa bersyong ito, ngunit makakatanggap ka pa rin ng abiso kapag may lumabas na mas bagong bersyon.</string>
<string name="information_webview_crashed">Huminto sa paggana ang WebView at isinarado ang pahina</string>
</resources>
@@ -746,7 +746,6 @@
<string name="invalid_backup_file_error">Erreur complète :</string>
<string name="ext_revoke_trust">Révoquer les extensions provenant d\'un répertoire additionnel</string>
<string name="manga_interval_expected_update_soon">Bientôt</string>
<string name="action_open_repo">Répertoire open source</string>
<string name="theme_nord">Nord</string>
<string name="action_create">Créer</string>
<string name="track_delete_text">Cela arrêtera le suivi pour cette série.</string>
@@ -720,7 +720,6 @@
<string name="pref_update_only_in_release_period">Predicir cando sae o seguinte capítulo</string>
<string name="ext_permission_install_apps_warning">Preme aquí para conceder os permisos para instalar extensións.</string>
<string name="ext_revoke_trust">Revogar as extensións de confianza de orixe descoñecida</string>
<string name="action_open_repo">Abrir o repositorio</string>
<string name="pref_flash_page">Tremelucir ao cambiar de páxina</string>
<string name="pref_flash_page_summ">Reducir o efecto de retención de imaxe nas pantallas de tinta electrónica</string>
<string name="no_location_set">Aínda non seleccionaches ningunha localización de almacenamento</string>
@@ -710,7 +710,6 @@
<string name="pref_library_update_smart_update">स्मार्ट अपडेट</string>
<string name="pref_hide_missing_chapter_indicators">गुम हुए अध्याय के संकेत छुपाएँ</string>
<string name="ext_revoke_trust">विश्वसनीय अज्ञात एक्सटेंशन्स रद्द करें</string>
<string name="action_open_repo">ओपन सोर्स रिपॉ</string>
<string name="pref_page_rotate_invert">घुमाए गए चौड़े पृष्ठों की ओरिएंटेशन उलटें</string>
<string name="pref_double_tap_zoom">दुबारा टैप करके ज़ूम करें</string>
<string name="pref_flash_page">पृष्ठ बदलने पर फ्लैश करें</string>
@@ -751,7 +751,6 @@
<string name="manga_interval_custom_amount">Prilagođena učestalost aktualiziranja:</string>
<string name="pref_library_update_smart_update">Pametno aktualiziranje</string>
<string name="ext_revoke_trust">Opozovi pouzdana nepoznata proširenja</string>
<string name="action_open_repo">Otvori repozitorij izvora</string>
<string name="private_settings">Uključi osjetljive postavke (kao što su tokeni za prijavu za usluge praćenja)</string>
<string name="manga_interval_expected_update">Predviđa se da će nova poglavlja biti izdana za oko %1$s, provjera se svakih %2$s.</string>
<string name="manga_interval_expected_update_soon">Uskoro</string>
@@ -731,7 +731,6 @@
<string name="pref_library_update_smart_update">Intelligens frissítés</string>
<string name="ext_permission_install_apps_warning">A bővítmények telepítéséhez engedélyek szükségesek. Az engedélyezéshez koppintson ide.</string>
<string name="ext_revoke_trust">Megbízható ismeretlen kiterjesztések visszavonása</string>
<string name="action_open_repo">Nyílt forráskódú tároló</string>
<string name="no_location_set">Nincs tárolási hely beállítva</string>
<string name="action_create">Létrehoz</string>
<string name="invalid_backup_file_error">Teljes hiba:</string>
@@ -145,7 +145,7 @@
<string name="pref_clear_cookies">Hapus cookies</string>
<string name="cookies_cleared">Cookies dihapus</string>
<string name="pref_clear_database">Hapus database</string>
<string name="pref_clear_database_summary">Hapus riwayat untuk entri yang tidak disimpan di perpustakaan Anda</string>
<string name="pref_clear_database_summary">Hapus riwayat untuk entri yang tidak disimpan di pustaka Anda</string>
<string name="clear_database_completed">Entri dihapus</string>
<string name="version">Versi</string>
<string name="pref_enable_acra">Kirim laporan kerusakan</string>
@@ -277,8 +277,8 @@
<string name="action_webview_forward">Teruskan</string>
<string name="action_webview_refresh">Muat ulang</string>
<string name="pref_category_library">Pustaka</string>
<string name="ext_obsolete">Usang</string>
<string name="obsolete_extension_message">Ekstensi ini tidak lagi tersedia. Ini mungkin tidak berfungsi dengan baik dan dapat menyebabkan masalah dengan aplikasi. Disarankan untuk menghapus instalasi.</string>
<string name="ext_obsolete">Terputus</string>
<string name="obsolete_extension_message">Ekstensi ini tidak terikat dengan toko apa pun. Ekstensi ini tidak akan menerima pembaruan, mungkin tidak berfungsi sebagaimana mestinya, dan dapat menyebabkan masalah pada aplikasi. Menghapus pemasangannya sangat disarankan.</string>
<string name="pref_date_format">Format tanggal</string>
<string name="action_sort_latest_chapter">Bab terbaru</string>
<string name="action_view_chapters">Lihat bab</string>
@@ -329,8 +329,8 @@
<string name="add_tracking">Tambahkan pelacak</string>
<string name="manga_info_collapse">Lebih sedikit</string>
<string name="manga_info_expand">Selengkapnya</string>
<string name="in_library">Di perpustakaan</string>
<string name="add_to_library">Tambahkan ke perpustakaan</string>
<string name="in_library">Di pustaka</string>
<string name="add_to_library">Tambah ke pustaka</string>
<string name="licenses">Lisensi terbuka</string>
<string name="restoring_backup_error">Gagal memulihkan data dari cadangan</string>
<string name="creating_backup_error">Pencadangan data gagal</string>
@@ -345,7 +345,7 @@
<string name="vertical_plus_viewer">Strip panjang dengan celah</string>
<string name="local_source_help_guide">Petunjuk sumber lokal</string>
<string name="last_used_source">Terakhir digunakan</string>
<string name="downloaded_only_summary">Saring semua entri di perpustakaan Anda</string>
<string name="downloaded_only_summary">Saring semua entri di pustaka Anda</string>
<string name="check_for_updates">Periksa pembaruan</string>
<string name="restoring_backup_canceled">Pemulihan dibatalkan</string>
<string name="restore_in_progress">Pemulihan masih dalam proses</string>
@@ -362,7 +362,7 @@
<string name="action_migrate">Pindah</string>
<string name="label_data">Data</string>
<string name="backup_restore_missing_sources">Sumber yang hilang:</string>
<string name="invalid_backup_file_missing_manga">Cadangan tidak berisi entri perpustakaan apa pun.</string>
<string name="invalid_backup_file_missing_manga">Cadangan tidak berisi entri pustaka apa pun.</string>
<string name="invalid_backup_file">File cadangan tidak valid:</string>
<string name="sort_by_upload_date">Menurut tanggal pengunggahan</string>
<string name="action_display_comfortable_grid">Grid nyaman</string>
@@ -485,7 +485,7 @@
<string name="action_start_downloading_now">Mulai mengunduh sekarang</string>
<string name="about_dont_kill_my_app">Beberapa pabrikan mempunyai batasan aplikasi tambahan yang mematikan layanan latar belakang. Website ini memiliki info lebih lanjut untuk memperbaikinya.</string>
<string name="restore_miui_warning">Pencadangan/pemulihan mungkin tidak berfungsi jika Optimisasi MIUI dimatikan.</string>
<string name="enhanced_tracking_info">Menyediakan fitur yang disempurnakan untuk sumber tertentu. Entri secara otomatis dilacak ketika ditambahkan ke perpustakaan Anda.</string>
<string name="enhanced_tracking_info">Menyediakan fitur yang disempurnakan untuk sumber tertentu. Entri secara otomatis dilacak ketika ditambahkan ke pustaka Anda.</string>
<string name="enhanced_services">Pelacak yang ditingkatkan</string>
<string name="pref_dark_theme_pure_black">Mode gelap hitam pekat</string>
<string name="theme_yotsuba">Empat Daun</string>
@@ -534,7 +534,7 @@
<string name="ext_update_all">Perbarui semuanya</string>
<string name="pref_auto_clear_chapter_cache">Bersihkan cache bab saat aplikasi dibuka</string>
<string name="database_clean">Tidak ada yang perlu dibersihkan</string>
<string name="clear_database_source_item_count">%1$d entri non-perpustakaan dalam database</string>
<string name="clear_database_source_item_count">%1$d entri non-pustaka dalam database</string>
<string name="extension_api_error">Gagal mendapatkan ekstensi yang tersedia</string>
<string name="pref_update_only_completely_read">Lewati daftar dengan chapter yang belum dibaca</string>
<string name="privacy_policy">Kebijakan privasi</string>
@@ -556,7 +556,7 @@
<string name="action_move_to_top_all_for_series">Pindahkan seri ke atas</string>
<string name="disabled_nav">Dinonaktifkan</string>
<string name="error_saving_picture">Gagal menyimpan gambar</string>
<string name="empty_backup_error">Tidak ada entri perpustakaan untuk dicadangkan</string>
<string name="empty_backup_error">Tidak ada entri pustaka untuk dicadangkan</string>
<string name="update_check_open">Buka di Github</string>
<string name="pref_clear_webview_data">Membersihkan data WebView</string>
<string name="webview_data_deleted">Data WebView telah dibersihkan</string>
@@ -663,7 +663,7 @@
<string name="pref_skip_dupe_chapters">Melewati bab terduplikasi</string>
<string name="track_error">%1$s eror: %2$s</string>
<string name="information_required_plain">*dibutuhkan</string>
<string name="pref_hide_in_library_items">Semua catatan tersembunyi sudah ada di perpustakaan</string>
<string name="pref_hide_in_library_items">Semua catatan tersembunyi sudah ada di pustaka</string>
<string name="action_copy_to_clipboard">Salin ke papan klip</string>
<string name="action_update_category">Perbarui kategori</string>
<string name="split_tall_images">Potong gambar tinggi</string>
@@ -755,7 +755,6 @@
<string name="theme_nord">Nord</string>
<string name="manga_interval_expected_update_soon">Segera</string>
<string name="ext_revoke_trust">Cabut izin ekstensi tidak dikenal yang tepercaya</string>
<string name="action_open_repo">Repo sumber terbuka</string>
<string name="pref_webtoon_disable_zoom_out">Menonaktifkan zoom out</string>
<string name="label_upcoming">Yang akan datang</string>
<string name="action_add_anyway">Tambahkan saja</string>
@@ -816,13 +815,13 @@
<string name="pref_display_images_description">Render gambar dalam deskripsi manga</string>
<string name="storage_failed_to_create_directory">Gagal membuat direktori: %s</string>
<string name="clear_database_text">Anda akan menghapus entri dari database</string>
<string name="clear_database_history_warning">Membaca bab dan kemajuan entri non-perpustakaan akan hilang</string>
<string name="clear_database_history_warning">Membaca bab dan kemajuan entri non-pustaka akan hilang</string>
<string name="clear_db_exclude_read">Simpan entri dengan bab yang sudah dibaca</string>
<string name="pref_update_library_manga_titles">Perbarui judul manga perpustakaan agar sesuai dengan sumbernya</string>
<string name="pref_update_library_manga_titles">Perbarui judul manga pustaka agar sesuai dengan sumbernya</string>
<string name="pref_update_library_manga_titles_summary">Peringatan: Jika sebuah manga diganti namanya, maka manga tersebut akan dihapus dari antrean unduhan (jika ada).</string>
<string name="logging_in">Sedang masuk…</string>
<string name="possible_duplicates_title">Duplikat yang mungkin</string>
<string name="possible_duplicates_summary">Anda memiliki entri di perpustakaan Anda dengan nama yang serupa.\n\nPilih entri yang ingin Anda pindahkan atau tambahkan tetap.</string>
<string name="possible_duplicates_summary">Anda memiliki entri di pustaka Anda dengan nama yang serupa.\n\nPilih entri yang ingin Anda pindahkan atau tambahkan tetap.</string>
<string name="notes_placeholder">Suka bagian di mana…</string>
<string name="migrationConfigScreen.selectedHeader">Dipilih</string>
<string name="migrationConfigScreen.availableHeader">Tersedia</string>
@@ -871,7 +870,7 @@
<string name="action_filter_excluded_scanlators">Filter yang mengecualikan pemindai</string>
<string name="label_support_us">Dukung Kami</string>
<string name="extensionStores">Tempat ekstensi</string>
<string name="action.addExtensionStore">Tambah ekstensi</string>
<string name="action.addExtensionStore">Tambah toko ekstensi</string>
<string name="extensionStoresScreen.emptyLabel">Anda belum menambahkan ekstensi</string>
<string name="extensionStoresScreen.addStore.title">Tambah ekstensi</string>
<string name="extensionStoresScreen.addStore.alreadyExists">Ekstensi ini sudah ditambahkan</string>
@@ -895,4 +894,65 @@
<string name="donationCampaign.contactPlatform">Discord</string>
<string name="donationCampaign.dismiss">Tutup</string>
<string name="considering">Mempertimbangkan</string>
<string name="app_migrating_title">Menyelesaikan pembaruan</string>
<string name="app_migrating_summary">Mempersiapkan segala sesuatu setelah pembaruan. Proses ini dapat memakan waktu beberapa menit, jadi biarkan aplikasi tetap terbuka.</string>
<string name="theme_tokyonight">Tokyo Night</string>
<string name="pref_category_extensions">Ekstensi</string>
<string name="pref_allowed_content_warnings">Peringatan konten yang diizinkan</string>
<string name="pref_apply_content_warnings_to_installed">Berlaku juga untuk ekstensi yang terpasang</string>
<string name="pref_apply_content_warnings_to_installed_summary">Saat dimatikan, ekstensi yang sudah Anda instal akan terus memuat dan memperbarui terlepas dari peringatan yang diberikan</string>
<string name="content_warnings_info">Ekstensi terpasang yang tidak dimuat tetap tercantum tanpa sumbernya; yang belum Anda instal disembunyikan. Ini tidak mencegah ekstensi yang tidak resmi atau yang berpotensi ditandai salah untuk memunculkan konten 18+ di dalam aplikasi.</string>
<string name="pref_enable_chapter_name_hash">Aktifkan sufiks hash nama bab</string>
<string name="pref_enable_chapter_name_hash_details">Memastikan sufiks hash unik ditambahkan ke nama bab untuk mencegah konflik penamaan. Jika dinonaktifkan, bab dengan nama yang identik tidak dapat diunduh.</string>
<string name="ext_filtered">Tersaring</string>
<string name="ext_not_loaded">Tidak dimuat</string>
<string name="ext_not_loaded_dialog">Tidak dapat dimuat</string>
<string name="ext_not_loaded_details">Mengapa ini tidak dimuat</string>
<string name="ext_copy_stacktrace">Salin jejak tumpukan</string>
<string name="ext_filtered_message">Peringatan konten ekstensi ini bukan salah satu yang Anda pilih untuk dimuat, sehingga sumbernya tidak tersedia.</string>
<string name="ext_unsigned_message">Ekstensi ini tidak ditandatangani, sehingga tidak ada tanda tangan yang dapat dicocokkan.</string>
<string name="ext_unsupported_message">Ekstensi ini dibuat untuk versi pustaka ekstensi yang tidak dapat dimuat oleh versi aplikasi ini. Memperbarui aplikasi atau ekstensi dapat membantu.</string>
<string name="ext_malformed_message">Ekstensi ini kehilangan informasi yang dibutuhkan aplikasi untuk memuatnya.</string>
<string name="ext_load_failed_message">Ekstensi ini mengalami kesalahan saat memuat, sehingga tidak ada sumbernya yang tersedia.</string>
<string name="ext_info_warning">Peringatan</string>
<string name="ext_content_warning_safe">Aman</string>
<string name="ext_content_warning_mixed">Campuran</string>
<string name="ext_content_warning_mixed_description">Sumber dari ekstensi ini mungkin berisi konten aman maupun 18+.</string>
<string name="ext_content_warning_nsfw_description">Sumber dari ekstensi ini utamanya berfokus pada konten 18+.</string>
<string name="downloading_with_progress">Mengunduh… (%d%%)</string>
<string name="pref_load_ahead_only_over_wifi">Muat data lebih awal hanya melalui Wi-Fi</string>
<string name="pref_load_ahead_only_over_wifi_summary">Menghemat data seluler dengan hanya memuat halaman yang berdekatan dengan halaman yang sedang Anda baca</string>
<string name="pref_vertical_navigator">Gunakan navigator bab vertikal di</string>
<string name="pref_vertical_navigator_height">Tinggi navigator vertikal</string>
<string name="pref_high_quality_renderer">Gunakan perender berkualitas tinggi</string>
<string name="pref_transition_animation">Animasi transisi</string>
<string name="pref_transition_animation_dual">Animasi transisi (ganda)</string>
<string name="transition_animation_basic">Dasar</string>
<string name="transition_animation_flip_left">Balik halaman (kiri)</string>
<string name="transition_animation_flip_right">Balik halaman (kanan)</string>
<string name="transition_animation_stack_left">Tumpuk (kiri)</string>
<string name="transition_animation_stack_right">Tumpuk (kanan)</string>
<string name="transition_animation_stack_up">Tumpuk (atas)</string>
<string name="transition_animation_stack_down">Tumpuk (bawah)</string>
<string name="transition_animation_sphere">Melengkung</string>
<string name="transition_animation_cube_inside">Kubus (Dalam)</string>
<string name="transition_animation_cube_outside">Kubus (Luar)</string>
<string name="transition_animation_fade">Memudar</string>
<string name="transition_animation_fade_white">Memudar menjadi putih</string>
<string name="transition_animation_none">Tidak ada</string>
<string name="transition_animation_flip">Balik halaman</string>
<string name="pref_cutout_mode">Mode potongan layar</string>
<string name="pref_cutout_mode_dual">Mode potongan layar (ganda)</string>
<string name="cutout_mode_ignore">Abaikan</string>
<string name="cutout_mode_avoid">Hindari</string>
<string name="cutout_mode_shift">Geser</string>
<string name="pref_dual_page_view">Tampilan halaman ganda</string>
<string name="dual_page_view_never">Jangan pernah</string>
<string name="dual_page_view_always">Selalu</string>
<string name="dual_page_view_wide">Saat lebar</string>
<string name="pref_continuous_minwidth">Lebar minimum</string>
<string name="pref_continuous_gap">Jarak</string>
<string name="refresh_tracker_profile">Perbarui profil pelacak</string>
<string name="refresh_tracker_success">Profil %s diperbarui</string>
<string name="refresh_tracker_error">Gagal memperbarui %1$s: %2$s</string>
</resources>
@@ -279,7 +279,7 @@
<string name="action_webview_forward">Avanti</string>
<string name="action_webview_refresh">Aggiorna</string>
<string name="pref_category_library">Libreria</string>
<string name="ext_obsolete">Obsoleta</string>
<string name="ext_obsolete">Orfana</string>
<string name="obsolete_extension_message">Questa estensione non è più disponibile. Potrebbe non funzionare correttamente e causare problemi con l\'app. Si consiglia di disinstallarla.</string>
<string name="pref_date_format">Formato data</string>
<string name="pref_category_library_update">Aggiornamenti globali</string>
@@ -747,7 +747,6 @@
<string name="onboarding_permission_action_grant">Consenti</string>
<string name="exclude_scanlators">Escludi scanlator</string>
<string name="onboarding_storage_help_action">Guida all\'archiviazione</string>
<string name="action_open_repo">Repository open source</string>
<string name="manga_interval_expected_update_soon">Presto</string>
<string name="manga_interval_custom_amount">Frequenza di aggiornamento personalizzata:</string>
<string name="onboarding_storage_help_info">Sti aggiornando da una vecchia versione e non sai cosa selezionare? Consulta la guida all\'archiviazione per info.</string>
@@ -882,5 +881,15 @@
<string name="pref_category_extensions">Estensioni</string>
<string name="ext_content_warning_mixed">Misto</string>
<string name="ext_content_warning_safe">Sicuro</string>
<string name="content_warnings_info">Per rendere effettive le modifiche è necessario riavviare l\'app. Le estensioni che non sono caricate non forniranno le loro fonti in nessuna parte dell\'app. Ciò non impedisce alle estensioni non ufficiali o potenzialmente contrassegnate in modo errato di mostrare contenuti 18+ all\'interno dell\'app.</string>
<string name="content_warnings_info">Le estensioni installate ma non caricate restano nell\'elenco senza le loro sorgenti; quelle non installate vengono nascoste. Questo non impedisce che estensioni non ufficiali o segnalate in modo errato mostrino contenuti 18+ nell\'app.</string>
<string name="pref_apply_content_warnings_to_installed_summary">Quando è disattivata, le estensioni già installate continuano a caricarsi e ad aggiornarsi a prescindere dal loro avviso</string>
<string name="app_migrating_title">Completamento dell\'aggiornamento</string>
<string name="app_migrating_summary">Stiamo completando i preparativi dopo l\'aggiornamento. Può richiedere qualche minuto, quindi tieni l\'app aperta.</string>
<string name="pref_apply_content_warnings_to_installed">Applica anche alle estensioni installate</string>
<string name="pref_enable_chapter_name_hash">Attiva il suffisso hash nei nomi dei capitoli</string>
<string name="ext_filtered">Filtrata</string>
<string name="ext_not_loaded">Non caricata</string>
<string name="ext_not_loaded_dialog">Impossibile da caricare</string>
<string name="ext_not_loaded_details">Perché non è caricata</string>
<string name="ext_copy_stacktrace">Copia traccia di stack</string>
</resources>
@@ -6,7 +6,7 @@
<string name="chapters">章</string>
<string name="history">履歴</string>
<string name="label_settings">設定</string>
<string name="label_download_queue">ダウンロードキュー</string>
<string name="label_download_queue">ダウンロード キュー</string>
<string name="label_library">ライブラリ</string>
<string name="label_recent_manga">履歴</string>
<string name="label_recent_updates">新着</string>
@@ -25,7 +25,7 @@
<string name="action_download">ダウンロード</string>
<string name="action_bookmark">章にブックマークを追加</string>
<string name="action_remove_bookmark">章のブックマークを削除</string>
<string name="action_display_grid">コンパクトグリッド</string>
<string name="action_display_grid">コンパクト グリッド</string>
<string name="action_display_list">リスト</string>
<string name="action_sort">ソート</string>
<string name="track">トラッキング</string>
@@ -86,7 +86,7 @@
<string name="ext_trust">信用する</string>
<string name="ext_untrusted">信用できない</string>
<string name="ext_uninstall">アンインストール</string>
<string name="untrusted_extension">信頼できない拡張</string>
<string name="untrusted_extension">信頼できない拡張機能</string>
<string name="pref_fullscreen">フルスクリーン</string>
<string name="pref_page_transitions">ページ遷移エフェクトを表示</string>
<string name="pref_double_tap_anim_speed">ダブルタップアニメーション速度</string>
@@ -250,7 +250,7 @@
<string name="download_notifier_unknown_error">予期せぬエラーが発生したため、ダウンロードは失敗しました</string>
<string name="download_notifier_text_only_wifi">Wi-Fi 接続は利用できません</string>
<string name="download_notifier_no_network">ネットワーク接続が利用できません</string>
<string name="download_notifier_download_paused">ダウンロード一時停止済み</string>
<string name="download_notifier_download_paused">ダウンロードが一時停止されました</string>
<string name="channel_common">一般設定</string>
<string name="repeating">読み返し中</string>
<string name="source_not_installed">未インストールのソース: %1$s</string>
@@ -305,8 +305,8 @@
<string name="downloaded_only_summary">ライブラリにある項目はフィルターされます</string>
<string name="label_downloaded_only">ダウンロード済みのみ</string>
<string name="check_for_updates">更新を確認</string>
<string name="licenses">オープンソースライセンス</string>
<string name="website">Web サイト</string>
<string name="licenses">オープン ソース ライセンス</string>
<string name="website">ウェブサイト</string>
<string name="battery_optimization_setting_activity_not_found">デバイス設定を開きませんでした</string>
<string name="battery_optimization_disabled">バッテリー最適化は既に無効です</string>
<string name="pref_disable_battery_optimization_summary">バックグラウンドでのライブラリの更新とバックアップに役立ちます</string>
@@ -370,7 +370,7 @@
<string name="sort_by_upload_date">アップロード日付順</string>
<string name="manga_info_expand">詳細を表示</string>
<string name="in_library">ライブラリ内</string>
<string name="local_source_help_guide">ローカルソースガイド</string>
<string name="local_source_help_guide">ローカル ソース ガイド</string>
<string name="pinned_sources">ピン留め済み</string>
<string name="last_used_source">最近使用</string>
<string name="pref_show_reading_mode_summary">現在のビューアモードを、ビューアが立ち上がるとしばらく表示します</string>
@@ -392,7 +392,7 @@
<string name="channel_complete">完了</string>
<string name="channel_progress">進捗</string>
<string name="download_insufficient_space">空き領域不足のため、章をダウンロードできませんでした</string>
<string name="no_pinned_sources">ピンソースがありません</string>
<string name="no_pinned_sources">ピン留めされたソースはありません</string>
<string name="action_global_search_query">グローバルで「%1$s」を探す</string>
<string name="pref_category_theme">テーマ</string>
<string name="action_sort_date_added">ライブラリへの追加日付順</string>
@@ -411,7 +411,7 @@
<string name="downloaded_chapters">ダウンロードした章の数</string>
<string name="manga_from_library">ライブラリ内から</string>
<string name="pref_incognito_mode_summary">閲覧履歴の記録を一時停止します</string>
<string name="pref_incognito_mode">シークレットモード</string>
<string name="pref_incognito_mode">シークレット モード</string>
<string name="pref_clear_history">履歴を消去</string>
<string name="clear_history_confirmation">続行してもよろしいですか?すべての履歴が削除されます。</string>
<string name="clear_history_completed">履歴が削除されました</string>
@@ -488,13 +488,13 @@
<string name="theme_yotsuba">四つ葉</string>
<string name="theme_yinyang">陰陽</string>
<string name="theme_tako">Tako</string>
<string name="theme_strawberrydaiquiri">ストロベリーダイキリ</string>
<string name="theme_strawberrydaiquiri">ストロベリー・ダイキリ</string>
<string name="theme_midnightdusk">黄昏</string>
<string name="theme_greenapple">青りんご</string>
<string name="pref_app_theme">アプリテーマ</string>
<string name="action_start_downloading_now">ダウンロードを開始します</string>
<string name="theme_monet">ダイナミック</string>
<string name="about_dont_kill_my_app">一部のメーカーは、バックグラウンドサービスを停止させる追加のアプリ制限を設けています。この問題を解決する方法については、こちらのウェブサイトに詳しい情報が掲載されています。</string>
<string name="about_dont_kill_my_app">一部のメーカーは、バックグラウンド サービスを停止させる追加のアプリ制限を設けています。この問題を解決する方法については、こちらのウェブサイトに詳しい情報が掲載されています。</string>
<string name="label_background_activity">バックグラウンドアクティビティ</string>
<string name="restore_miui_warning">MIUI 最適化が無効になっている場合、バックアップおよび復元が正常に機能しない可能性があります。</string>
<string name="pref_lowest">最低</string>
@@ -536,7 +536,7 @@
<string name="pref_auto_clear_chapter_cache">アプリ起動時に章のキャッシュを消去</string>
<string name="database_clean">消去できるものはありませんでした</string>
<string name="extension_api_error">利用可能な拡張機能を取得できませんでした</string>
<string name="privacy_policy">プライバシーポリシー</string>
<string name="privacy_policy">プライバシー ポリシー</string>
<string name="pref_update_only_completely_read">未読の章がある作品をスキップ</string>
<string name="library_errors_help">ライブラリ更新エラーの修正については、%1$sをご覧ください</string>
<string name="save_chapter_as_cbz">CBZ アーカイブとして保存</string>
@@ -615,16 +615,16 @@
<string name="error_user_agent_string_invalid">ユーザーエージェント文字列が無効です</string>
<string name="crash_screen_title">おっと!</string>
<string name="updates_last_update_info_just_now">ちょうど今</string>
<string name="crash_screen_restart_application">アプリケーションを再起動</string>
<string name="crash_screen_restart_application">アプリを再起動</string>
<string name="pref_reader_summary">ビューアモード、表示、ナビゲーション</string>
<string name="pref_downloads_summary">自動ダウンロード、事前ダウンロード</string>
<string name="pref_tracking_summary">一方同期、高度な同期</string>
<string name="pref_browse_summary">ソース、拡張機能、グローバル検索</string>
<string name="pref_backup_summary">手動・自動バックアップ、ストレージ領域</string>
<string name="pref_security_summary">アプリロック、セキュア画面</string>
<string name="pref_advanced_summary">クラッシュログのダンプ、バッテリー最適化</string>
<string name="pref_advanced_summary">クラッシュ ログのダンプ、バッテリー最適化</string>
<string name="pref_appearance_summary">テーマ、日付と時刻の形式</string>
<string name="pref_library_summary">カテゴリー、グローバルアップデート、章間スワイプ</string>
<string name="pref_library_summary">カテゴリー、グローバル アップデート、章間スワイプ</string>
<string name="action_open_random_manga">おまかせ閲覧</string>
<string name="information_no_entries_found">このカテゴリーには該当する項目が見つかりませんでした</string>
<string name="download_notifier_cache_renewal">ダウンロード内容を検査しています</string>
@@ -752,8 +752,7 @@
<string name="private_settings">トラッカーログイン情報などの機密性の高い情報を含む</string>
<string name="manga_interval_expected_update_soon">間もなく</string>
<string name="ext_revoke_trust">不明な拡張機能の信頼を取り消す</string>
<string name="action_open_repo">ソースリポジトリを開く</string>
<string name="onboarding_storage_help_info">古いバージョンからアップデートする場合で、どのオプションを選択すればよいか分からない場合は、ストレージガイドを参照してください。</string>
<string name="onboarding_storage_help_info">古いバージョンからアップデートする場合で、どのオプションを選択すればよいか分からない場合は、ストレージ ガイドを参照してください。</string>
<string name="theme_nord">Nord</string>
<string name="pref_library_update_smart_update">スマート・アップデート</string>
<string name="pref_webtoon_disable_zoom_out">ズームアウトを無効にする</string>
@@ -807,7 +806,7 @@
<string name="author">著者</string>
<string name="artist">アーティスト</string>
<string name="action_display_unread_badge">未読の章</string>
<string name="storage_failed_to_create_download_directory">ダウンロードディレクトリの作成に失敗しました</string>
<string name="storage_failed_to_create_download_directory">ダウンロード ディレクトリの作成に失敗しました</string>
<string name="storage_failed_to_create_directory">ディレクトリの作成に失敗しました: %s</string>
<string name="clear_database_text">データベースから作品が削除されます</string>
<string name="clear_database_history_warning">ライブラリ以外の書籍の章の閲覧履歴や進捗状況は失われます</string>
@@ -901,4 +900,23 @@
<string name="downloading_with_progress">ダウンロード中… (%d%%)</string>
<string name="pref_filter_update_categories_details">除外対象カテゴリーに含まれる更新情報は、たとえそれらが対象カテゴリーにも含まれている場合でも表示されません。</string>
<string name="pref_filter_upcoming_categories_details">除外対象カテゴリーに含まれるタイトルは、たとえそれらが対象カテゴリーにも含まれている場合でも表示されません。</string>
<string name="app_migrating_title">アップデートを完了</string>
<string name="app_migrating_summary">アップデート後の準備を行っています。数分かかる場合がありますので、アプリを開いたままにしておいてください。</string>
<string name="pref_category_extensions">拡張機能</string>
<string name="pref_allowed_content_warnings">許可されているコンテンツ警告</string>
<string name="pref_apply_content_warnings_to_installed">インストール済みの拡張機能にも適用</string>
<string name="pref_apply_content_warnings_to_installed_summary">オフにすると、すでにインストール済みの拡張機能は、警告が表示されていても読み込みや更新が継続されます</string>
<string name="content_warnings_info">インストール済みであっても読み込まれていない拡張機能は、ソース情報なしで一覧表示されます。インストールしていない拡張機能は非表示になります。ただし、これは非公式の拡張機能や、誤ってフラグ付けされた可能性のある拡張機能がアプリ内で 18 歳以上向けコンテンツを表示することを防ぐものではありません。</string>
<string name="pref_enable_chapter_name_hash">章名のハッシュ接尾辞を有効にする</string>
<string name="pref_enable_chapter_name_hash_details">名前付けの競合を防ぐため、章名に一意のハッシュ接尾辞を追加します。この機能を無効にすると、同じ名前の章はダウンロードできなくなります。</string>
<string name="ext_info_warning">Warning</string>
<string name="ext_content_warning_safe">Safe</string>
<string name="ext_content_warning_mixed">Mixed</string>
<string name="ext_content_warning_nsfw">18 歳以上</string>
<string name="ext_content_warning_mixed_description">この拡張機能のソースには、安全なコンテンツと 18 歳以上向けのコンテンツの両方が含まれている可能性があります。</string>
<string name="ext_content_warning_nsfw_description">この拡張機能のソースは、主に 18 歳以上向けのコンテンツに焦点を当てています。</string>
<string name="update_check_skip">このバージョンをスキップ</string>
<string name="update_check_skip_title">%s をスキップしますか?</string>
<string name="update_check_skip_message">アップデートには、多くの場合、バグの修正やセキュリティの強化が含まれています。このバージョンに関する通知は今後表示されなくなりますが、新しいバージョンがリリースされた際には引き続き通知が届きます。</string>
<string name="information_webview_crashed">WebView が動作を停止し、ページが閉じられました</string>
</resources>
@@ -726,7 +726,6 @@
<string name="ext_revoke_trust">신뢰한 알수없는 확장 앱 신뢰 취소</string>
<string name="pref_storage_location_info">자동 백업, 챕터 다운로드 및 로컬 소스에 사용됩니다.</string>
<string name="manga_interval_custom_amount">사용자 지정 업데이트 빈도:</string>
<string name="action_open_repo">소스 저장소 열기</string>
<string name="manga_interval_expected_update_soon">곧 출시</string>
<string name="onboarding_description">먼저 몇 가지 사항을 설정해 보겠습니다. 나중에 언제든지 설정에서 변경할 수도 있습니다.</string>
<string name="onboarding_action_finish">시작하기</string>
@@ -742,7 +742,6 @@
<string name="ext_permission_install_apps_warning">Kebenaran diperlukan untuk memasang sambungan. Ketik di sini untuk membenarkan.</string>
<string name="action_sort_tracker_score">Skor penjejak</string>
<string name="ext_revoke_trust">Batalkan sambungan dipercayakan yang tidak diketahui</string>
<string name="action_open_repo">Buka repositori sumber</string>
<string name="manga_interval_expected_update_soon">Tidak lama lagi</string>
<string name="scanlator">Scanlator</string>
<string name="exclude_scanlators">Kecualikan scanlator</string>
@@ -755,7 +755,6 @@
<string name="theme_nord">Nord</string>
<string name="manga_interval_expected_update_soon">Snart</string>
<string name="ext_revoke_trust">Fjern tilliten til ukjente utvidelser</string>
<string name="action_open_repo">Åpne pakkebrønnen</string>
<string name="label_upcoming">Kommende</string>
<string name="action_add_anyway">Legg til likevel</string>
</resources>
@@ -742,7 +742,6 @@
<string name="ext_permission_install_apps_warning">एक्सटेन्शनहरू स्थापना गर्न अनुमति आवश्यक छ। प्रदान गर्न यहाँ ट्याप गर्नुहोस्।</string>
<string name="ext_revoke_trust">विश्वसनीय अज्ञात एक्सटेन्शनहरू रद्द गर्नुहोस्</string>
<string name="action_apply">अप्लाई</string>
<string name="action_open_repo">खुला स्रोत रिपो</string>
<string name="pref_storage_location">भण्डारण स्थान</string>
<string name="available_disk_space_info">उपलब्ध: %1$s / कुल: %2$s</string>
<string name="manga_interval_expected_update">नयाँ अध्यायहरू लगभग %1$s मा रिलीज हुने भविष्यवाणी गरिएको छ, हरेक %2$s को वरिपरि जाँच गर्दै।</string>
@@ -739,7 +739,6 @@
<string name="onboarding_guides_returning_user">%s aan het herinstalleren?</string>
<string name="onboarding_permission_action_grant">Toestaan</string>
<string name="onboarding_guides_new_user">Nieuw bij %s? Wij raden je aan om de opstartgids te lezen.</string>
<string name="action_open_repo">Bronrepo openen</string>
<string name="pref_flash_duration">Flitsduur</string>
<string name="ext_remove">Verwijderen</string>
<string name="ext_confirm_remove">Extensie verwijderen?</string>
@@ -739,7 +739,6 @@
<string name="ext_permission_install_apps_warning">Uprawnienia są wymagane aby zainstalować rozszerzenia. Kliknij tutaj aby je przyznać.</string>
<string name="onboarding_storage_info">Wybierz folder gdzie %1$s będzie zapisywał pobrane rozdziały, kopie zapasowe i inne. \n \nZalecany jest dedykowany folder. \n \nWybrany folder:%2$s</string>
<string name="ext_revoke_trust">Unieważnij zaufanie nieznanym rozszerzeniom</string>
<string name="action_open_repo">Repozytorium open source</string>
<string name="pref_flash_page_summ">Redukuje ghosting na wyświetlaczach e-ink</string>
<string name="no_location_set">Brak ustawionej ścieżki pamięci</string>
<string name="pref_storage_location">Miejsce przechowywania danych</string>
@@ -76,7 +76,7 @@
<item quantity="other">Faltando %1$s capítulos</item>
</plurals>
<plurals name="day">
<item quantity="one">%d dia(s)</item>
<item quantity="one">%d dia</item>
<item quantity="many">%d dias</item>
<item quantity="other">%d dias</item>
</plurals>
@@ -335,7 +335,7 @@
<string name="action_pin">Fixar</string>
<string name="add_tracking">Adicionar monitoramento</string>
<string name="add_to_library">Adicionar à biblioteca</string>
<string name="in_library">Na biblioteca</string>
<string name="in_library">Na Biblioteca</string>
<string name="manga_info_collapse">Menos</string>
<string name="manga_info_expand">Mais</string>
<string name="information_webview_required">A WebView é necessária para o funcionamento do app</string>
@@ -755,7 +755,6 @@
<string name="manga_interval_expected_update_soon">Em breve</string>
<string name="theme_nord">Nord</string>
<string name="ext_revoke_trust">Revogar a confiabilidade de extensões desconhecidas</string>
<string name="action_open_repo">Abrir repositório da fonte</string>
<string name="pref_webtoon_disable_zoom_out">Desativar redução de zoom</string>
<string name="action_add_anyway">Adicionar de qualquer maneira</string>
<string name="label_upcoming">Próximo</string>
@@ -950,4 +949,15 @@
<string name="dual_page_view_wide">Em telas largas</string>
<string name="pref_continuous_minwidth">Largura mínima</string>
<string name="pref_continuous_gap">Espaçamento</string>
<string name="update_check_skip_title">Pular %s?</string>
<string name="update_check_skip_message">Atualizações geralmente contêm correções de bugs e melhorias de segurança. Você não será lembrado sobre esta versão novamente, mas ainda será notificado quando uma versão mais recente for lançada.</string>
<string name="update_check_skip">Pular esta versão</string>
<string name="app_migrating_summary">Deixando as coisas prontas depois da atualização. Isso pode levar alguns minutos, então por favor mantenha o aplicativo aberto.</string>
<string name="pref_enable_chapter_name_hash_details">Garante que um sufixo hash exclusivo seja adicionado aos nomes dos capítulos para evitar conflitos de nomenclatura. Se desativado, os capítulos com nomes idênticos não poderão ser baixados.</string>
<string name="pref_enable_chapter_name_hash">Habilitar sufixo hash no nome do capitulo</string>
<string name="refresh_tracker_profile">Atualizar perfil do monitorador</string>
<string name="refresh_tracker_success">%s perfil atualizado</string>
<string name="ext_not_loaded_details">Por que não está carregado</string>
<string name="app_migrating_title">Finalizando a atualização</string>
<string name="information_webview_crashed">WebView parou de funcionar e a página foi fechada</string>
</resources>
@@ -730,7 +730,6 @@
<string name="onboarding_guides_new_user">Novo no %s? Recomendamos dar uma olhada no guia de introdução.</string>
<string name="theme_nord">Nord</string>
<string name="pref_relative_format_summary">\"%1$s\" ao invés de \"%2$s\"</string>
<string name="action_open_repo">Abrir repositório da fonte</string>
<string name="pref_flash_page_summ">Reduz o efeito fantasma em ecrãs de e-ink</string>
<string name="invalid_backup_file_error">Erro completo:</string>
<string name="source_settings">Configurações das fontes</string>
@@ -735,7 +735,6 @@
<string name="onboarding_permission_action_grant">Acordă</string>
<string name="onboarding_guides_returning_user">Reinstalare %s?</string>
<string name="pref_library_update_smart_update">Actualizare inteligentă</string>
<string name="action_open_repo">Repozitoriu open source</string>
<string name="no_location_set">Nici o locație pentru stocare nu a fost setată</string>
<string name="invalid_backup_file_error">Eroarea completă:</string>
<string name="private_settings">Includeți setări sensibile (ex. token-ul serviciilor de urmărire al logări)</string>
@@ -104,9 +104,9 @@
</plurals>
<plurals name="upcoming_relative_time">
<item quantity="one">Завтра</item>
<item quantity="few">В течении %1$d дней</item>
<item quantity="many">В течении %1$d дней</item>
<item quantity="other">В течении %1$d дней</item>
<item quantity="few">В течение %1$d дней</item>
<item quantity="many">В течение %1$d дней</item>
<item quantity="other">В течение %1$d дней</item>
</plurals>
<plurals name="pref_pages">
<item quantity="one">1 страница</item>
@@ -755,7 +755,6 @@
<string name="manga_interval_expected_update_soon">Скоро</string>
<string name="theme_nord">Север</string>
<string name="ext_revoke_trust">Убрать доверие к ненадёжным расширениям</string>
<string name="action_open_repo">Открыть репозиторий источника</string>
<string name="pref_webtoon_disable_zoom_out">Отключить уменьшение масштаба</string>
<string name="action_add_anyway">Добавить в любом случае</string>
<string name="pref_display_profile">Пользовательский профиль отображения</string>
@@ -926,7 +925,7 @@
<string name="pref_category_extensions">Расширения</string>
<string name="pref_apply_content_warnings_to_installed">Также применять на установленные расширения</string>
<string name="pref_apply_content_warnings_to_installed_summary">Когда выключенно, расширения которые вы уже установили будут загружаться и обновляться независимо от предупреждения</string>
<string name="content_warnings_info">Перезапустите приложение для применения изменений. Установленные расширения, которые не загружены, остаются в списке без указания источников. Расширения, которые вы не устанавливали, скрываются. Это не предотвращает появления в приложении контента категории «18+» со стороны неофициальных или потенциально неправильно помеченных расширений.</string>
<string name="content_warnings_info">Установленные расширения, которые не загружены, остаются в списке без указания источников. Расширения, которые вы не устанавливали, скрываются. Это не предотвращает появления в приложении контента категории «18+» со стороны неофициальных или потенциально неправильно помеченных расширений.</string>
<string name="ext_filtered">Отфильтровано</string>
<string name="ext_not_loaded">Не загружено</string>
<string name="ext_not_loaded_dialog">Не удалось загрузить</string>
@@ -950,4 +949,18 @@
<string name="pref_continuous_minwidth">Минимальная ширина</string>
<string name="pref_continuous_gap">Зазор</string>
<string name="pref_allowed_content_warnings">Разрешенное предупреждение об контенте</string>
<string name="app_migrating_summary">Подготовка после обновления. Это может занять несколько минут, поэтому не закрывайте приложение.</string>
<string name="pref_enable_chapter_name_hash">Включить суффикс хэша в название главы</string>
<string name="pref_enable_chapter_name_hash_details">Обеспечивает добавление уникального хэш-суффикса к названиям глав во избежание конфликтов имён. Если эта функция отключена, загрузка глав с одинаковыми названиями будет невозможна.</string>
<string name="ext_not_loaded_details">Почему это не загрузилось</string>
<string name="app_migrating_title">Завершение обновления</string>
<string name="pref_load_ahead_only_over_wifi">Загрузить наперёд только с Wi-Fi</string>
<string name="pref_load_ahead_only_over_wifi_summary">Экономит мобильный трафик, загружая только те страницы, которые находятся рядом с той, которую вы читаете</string>
<string name="refresh_tracker_success">%s профилей обновлено</string>
<string name="refresh_tracker_error">%1$s ошибка при обновлении: %2$s</string>
<string name="update_check_skip">Пропустить эту версию</string>
<string name="update_check_skip_title">Пропустить %s?</string>
<string name="update_check_skip_message">Обновления часто могут иметь исправления ошибок и улучшения стабильности. Вам больше не будут напоминать об этой версии, но вы по прежнему будете получать уведомления об новой версии, когда она будет выпущена.</string>
<string name="information_webview_crashed">WebView перестало работать, и страница была закрыта</string>
<string name="refresh_tracker_profile">Обновить профиль сервиса отслеживания</string>
</resources>
@@ -727,7 +727,6 @@
<string name="manga_interval_expected_update_null">Custu manga est acabbadu o non b\'at una data de publicatzione previdida.</string>
<string name="file_picker_uri_permission_unsupported">Non s\'est pòdidu otènnere s\'atzessu permanente a sa cartella. S\'aplicatzione si diat pòdere cumportare in manera no isetada.</string>
<string name="ext_permission_install_apps_warning">B\'at bisòngiu de sos permissos pro installare estensiones. Toca inoghe pro los frunire.</string>
<string name="action_open_repo">Depòsitu a còdighe abertu</string>
<string name="no_location_set">Perunu logu de archiviatzione isseberadu</string>
<string name="pref_flash_duration">Durada de su flash</string>
<string name="pref_flash_duration_summary">%1$s ms</string>
@@ -706,7 +706,6 @@
<string name="ext_remove">Odstrániť</string>
<string name="ext_permission_install_apps_warning">Na inštaláciu rozšírení sú potrebné povolenia. Ťuknite sem, aby ste ich udelili.</string>
<string name="ext_revoke_trust">Odstrániť status \"dôveryhodné\" z neznámych rozšírení</string>
<string name="action_open_repo">Open-source repozitár</string>
<string name="pref_page_rotate">Otočiť široké strany, aby sa zmestili</string>
<string name="pref_page_rotate_invert">Preklopiť orientáciu otočených širokých strán</string>
<string name="pref_double_tap_zoom">Dvojťuk pre priblíženie</string>
@@ -740,7 +740,6 @@
<string name="ext_permission_install_apps_warning">За инсталацију додатака потребне су дозволе. Додирни овде за одобрење</string>
<string name="invalid_backup_file_error">Грешка:</string>
<string name="manga_interval_custom_amount">Прилагођена учесталост ажурирања:</string>
<string name="action_open_repo">Отвори репозиторију</string>
<string name="not_selected">Није одабрано</string>
<string name="selected">Одабрано</string>
<string name="action_menu_overflow_description">Више опција</string>
@@ -753,7 +753,6 @@
<string name="onboarding_storage_help_action">Lagerhanteringsguide</string>
<string name="theme_nord">Nord</string>
<string name="ext_revoke_trust">Återkalla tillförlitliga okända tillägg</string>
<string name="action_open_repo">Öppenkällkods förråd</string>
<string name="manga_interval_expected_update_soon">Snart</string>
<string name="manga_interval_custom_amount">Anpassad uppdateringsfrekvens:</string>
<string name="pref_webtoon_disable_zoom_out">Inaktivera utzoomning</string>
@@ -337,7 +337,6 @@
<string name="ext_installer_shizuku_unavailable_dialog">சிசுகுவை நீட்டிப்பு நிறுவியாகப் பயன்படுத்த சிசுகுவை நிறுவி தொடங்கவும்.</string>
<string name="ext_revoke_trust">நம்பகமான அறியப்படாத நீட்டிப்புகளைத் திரும்பப் பெறுங்கள்</string>
<string name="untrusted_extension_message">தீங்கிழைக்கும் நீட்டிப்புகள் சேமிக்கப்பட்ட உள்நுழைவு நற்சான்றிதழ்களைப் படிக்கலாம் அல்லது தன்னிச்சையான குறியீட்டை இயக்கலாம்.\n\n இந்த நீட்டிப்பை நம்புவதன் மூலம், இந்த அபாயங்களை நீங்கள் ஏற்றுக்கொள்கிறீர்கள்.</string>
<string name="action_open_repo">திறந்த மூல ரெப்போ</string>
<string name="pref_dual_page_split">பரந்த பக்கங்களைப் பிரிக்கவும்</string>
<string name="pref_dual_page_invert">பிளவு பக்க வேலைவாய்ப்பு தலைகீழ்</string>
<string name="pref_dual_page_invert_summary">பிளவு அகலமான பக்கங்களின் இடம் வாசிப்பு திசையுடன் பொருந்தவில்லை என்றால்</string>
@@ -755,7 +755,6 @@
<string name="manga_interval_expected_update_soon">เร็ว ๆ นี้</string>
<string name="onboarding_storage_help_info">กำลังอัปเดตจากเวอร์ชันเก่าและไม่แน่ใจว่าจะเลือกอะไรใช่ไหม? โปรดดูคู่มือการจัดเก็บข้อมูลสำหรับข้อมูลเพิ่มเติม</string>
<string name="theme_nord">นอร์ด</string>
<string name="action_open_repo">เปิดที่มาของรีโพ</string>
<string name="action_copy_link">คัดลอกลิงก์</string>
<string name="action_sort_random">สุ่ม</string>
<string name="onboarding_permission_analytics">อนุญาตให้เก็บข้อมูลไปวิเคราะห์</string>
@@ -755,7 +755,6 @@
<string name="theme_nord">Nord</string>
<string name="ext_revoke_trust">Güvenilmiş bilinmeyen uzantıları geri al</string>
<string name="manga_interval_expected_update_soon">Yakında</string>
<string name="action_open_repo">Açık kaynaklı depo</string>
<string name="action_copy_link">Bağlantıyı kopyala</string>
<string name="pref_flash_with">Işık rengi</string>
<string name="pref_flash_style_black">Siyah</string>
@@ -901,4 +900,67 @@
<string name="downloading_with_progress">İndiriliyor… (%d%%)</string>
<string name="pref_filter_update_categories_details">Hariç tutulan kategorilerdeki güncellemeler, dahil edilen kategorilerde olsalar bile gösterilmeyecektir.</string>
<string name="pref_filter_upcoming_categories_details">Hariç tutulan kategorilerdeki girdiler gizlendi. Bu girdiler dahil edilen kategorilerde olsalar bile gösterilmez.</string>
<string name="app_migrating_title">Güncelleme tamamlanıyor</string>
<string name="app_migrating_summary">Güncelleme sonrası her şey hazır hâle getiriliyor. Bu birkaç dakika sürebilir. Lütfen uygulamayı açık tutun.</string>
<string name="pref_category_extensions">Uzantılar</string>
<string name="pref_allowed_content_warnings">İzin verilen içerik uyarıları</string>
<string name="pref_apply_content_warnings_to_installed">Yüklenmiş uzantılar için de uygula</string>
<string name="pref_apply_content_warnings_to_installed_summary">Devre dışı olduğunda yüklediğiniz uzantılar, uyarılara bakılmaksızın çalışmaya ve güncellenmeye devam eder</string>
<string name="content_warnings_info">Yüklenmiş ama çalışmayan uzantılar, kaynakları gösterilmeden listelenmeye devam eder. Yüklemediğiniz uzantılar gizlenir. Resmî olmayan veya yanlış işaretlenmiş uzantılar yine de uygulama içinde 18+ içerik gösterebilir.</string>
<string name="pref_enable_chapter_name_hash">Bölüm isimleri sonuna benzersiz ek ekle</string>
<string name="pref_enable_chapter_name_hash_details">Bölüm isimlerinin birbirleriyle çakışmasını engellemek için isimlerin sonuna benzersiz bir metin ekler. Devre dışı olduğunda aynı ismi paylaşan bölümler indirilemez.</string>
<string name="ext_filtered">Filtrelenmiş</string>
<string name="ext_not_loaded">Çalışmıyor</string>
<string name="ext_not_loaded_dialog">Çalıştırılamadı</string>
<string name="ext_not_loaded_details">Neden çalıştırılamadı</string>
<string name="ext_copy_stacktrace">Yığın izini kopyala</string>
<string name="ext_filtered_message">Bu uzantının içerik uyarısı, çalıştırmak istediğiniz uyarıyla uyuşmuyor, bu yüzden uzantının kaynakları kullanılamıyor.</string>
<string name="ext_unsigned_message">Bu uzantı imzalanmadığı için imza kontrolü yapılamıyor.</string>
<string name="ext_unsupported_message">Bu uzantının kullandığı uzantı kitaplığı uygulamanın bu sürümünde yüklenemiyor. Uygulamayı ya da uzantıyı güncellemeyi deneyin.</string>
<string name="ext_malformed_message">Bu uzantının çalıştırılabilmesi için gereken bazı bilgiler uzantı tarafından sağlanmıyor.</string>
<string name="ext_load_failed_message">Bu uzantı çalıştırılırken bir hata bildirdi. Uzantının kaynakları kullanılamıyor.</string>
<string name="ext_info_warning">Uyarı</string>
<string name="ext_content_warning_safe">Güvenli</string>
<string name="ext_content_warning_mixed">Karışık</string>
<string name="ext_content_warning_nsfw">18+</string>
<string name="ext_content_warning_mixed_description">Bu uzantının sağladığı kaynaklar güvenli ve 18+ içerik içerebilir.</string>
<string name="ext_content_warning_nsfw_description">Bu uzantının sağladığı kaynaklar 18+ içeriklere odaklıdır.</string>
<string name="pref_load_ahead_only_over_wifi">Yalnızı Wi-Fi ile önden yükle</string>
<string name="pref_load_ahead_only_over_wifi_summary">Yalnızca okuduğunuz sayfaya yakın olan sayfaları yükleyerek mobil veri tasarruf sağlar</string>
<string name="pref_high_quality_renderer">Yüksek kalite işleyici kullan</string>
<string name="webgpu_viewer">WebGPU</string>
<string name="pref_transition_animation">Geçiş animasyonu</string>
<string name="pref_transition_animation_dual">Geçiş animasyonu (çift)</string>
<string name="transition_animation_basic">Basit</string>
<string name="transition_animation_flip_left">Sayfa çevirme (sol)</string>
<string name="transition_animation_flip_right">Sayfa çevirme (sağ)</string>
<string name="transition_animation_stack_left">Yığın (sol)</string>
<string name="transition_animation_stack_right">Yığın (sağ)</string>
<string name="transition_animation_stack_up">Yığın (yukarı)</string>
<string name="transition_animation_stack_down">Yığın (aşağı)</string>
<string name="transition_animation_sphere">Küre</string>
<string name="transition_animation_cube_inside">Küp (iç)</string>
<string name="transition_animation_cube_outside">Küp (dış)</string>
<string name="transition_animation_fade">Soldur</string>
<string name="transition_animation_fade_white">Beyaza kayarak</string>
<string name="transition_animation_none">Hiçbiri</string>
<string name="transition_animation_flip">Sayfa çevirme</string>
<string name="pref_cutout_mode">Ekran kesimi modu</string>
<string name="pref_cutout_mode_dual">Ekran kesimi modu (çift)</string>
<string name="cutout_mode_ignore">Görmezden gel</string>
<string name="cutout_mode_avoid">Kullanma</string>
<string name="cutout_mode_shift">Kaydır</string>
<string name="pref_dual_page_view">Çift sayfa görünümü</string>
<string name="dual_page_view_never">Asla</string>
<string name="dual_page_view_always">Her zaman</string>
<string name="dual_page_view_wide">Geniş modda</string>
<string name="pref_continuous_minwidth">En az genişlik</string>
<string name="pref_continuous_gap">Aralık</string>
<string name="refresh_tracker_profile">İzleyici profilini yenile</string>
<string name="refresh_tracker_success">%s profil yenilendi</string>
<string name="refresh_tracker_error">%1$s yenileme hatası: %2$s</string>
<string name="update_check_skip">Bu sürümü atla</string>
<string name="update_check_skip_title">%s atla?</string>
<string name="update_check_skip_message">Güncellemeler, hata düzeltmeleri ve güvenlik geliştirmeleri sağlar. Bu sürüm için tekrar hatırlatılmayacaksınız ancak daha yeni bir sürüm yayınlandığında size bildirim gösterilecek.</string>
<string name="information_webview_crashed">WebView çalışmayı durdurdu ve sayfa kapatıldı</string>
</resources>
@@ -741,7 +741,6 @@
<string name="pref_storage_location_info">Використовується для автоматичних бекапів, завантажень розділів, та локальних джерел.</string>
<string name="manga_interval_expected_update_soon">Незабаром</string>
<string name="ext_revoke_trust">Скинути довірені невідомі розширення</string>
<string name="action_open_repo">Репозиторій з відкритим початковим кодом</string>
<string name="selected">Обрано</string>
<string name="not_selected">Не обрано</string>
<string name="action_menu_overflow_description">Більше опцій</string>
@@ -754,7 +754,6 @@
<string name="onboarding_storage_help_info">Cập nhật từ phiên bản cũ hơn và không biết nên chọn gì? Tham khảo hướng dẫn lưu trữ để biết thêm thông tin.</string>
<string name="ext_revoke_trust">Thu hồi quyền truy cập của tiện ích không xác định</string>
<string name="onboarding_storage_help_action">Hướng dẫn lưu trữ</string>
<string name="action_open_repo">Nguồn mở</string>
<string name="manga_interval_expected_update_soon">Sớm</string>
<string name="manga_interval_custom_amount">Tần suất cập nhật tùy chỉnh:</string>
<string name="pref_display_profile">Tùy chỉnh hồ sơ màn hình</string>
@@ -755,7 +755,6 @@
<string name="theme_nord">Nord</string>
<string name="manga_interval_expected_update_soon">即将更新</string>
<string name="ext_revoke_trust">撤销已信任的未知插件</string>
<string name="action_open_repo">打开图源仓库</string>
<string name="pref_webtoon_disable_zoom_out">禁用缩小</string>
<string name="action_add_anyway">依然添加</string>
<string name="pref_display_profile">自定义校色文件</string>
@@ -953,4 +952,7 @@
<string name="refresh_tracker_profile">刷新跟踪器配置</string>
<string name="refresh_tracker_success">刷新 %s 配置</string>
<string name="refresh_tracker_error">%1$s 刷新错误: %2$s</string>
<string name="app_migrating_title">正在完成更新</string>
<string name="app_migrating_summary">更新后正在进行准备工作。这可能需要几分钟,请保持应用处于开启状态。</string>
<string name="pref_enable_chapter_name_hash">启用章节名称哈希后缀</string>
</resources>
@@ -755,7 +755,6 @@
<string name="theme_nord">北風</string>
<string name="ext_revoke_trust">復原對不明擴充套件的信任</string>
<string name="manga_interval_expected_update_soon">即將</string>
<string name="action_open_repo">開啟來源儲存庫</string>
<string name="pref_webtoon_disable_zoom_out">停用縮小</string>
<string name="pref_display_profile">自訂顯示設定檔</string>
<string name="action_add_anyway">仍要新增</string>
@@ -923,4 +922,45 @@
<string name="dual_page_view_wide">寬螢幕時</string>
<string name="pref_filter_update_categories_details">類別同時屬於「排除」及「包含」的更新將不會顯示。</string>
<string name="pref_filter_upcoming_categories_details">類別同時屬於「排除」及「包含」的作品將不會顯示。</string>
<string name="app_migrating_title">完成更新</string>
<string name="app_migrating_summary">正在準備更新後的相關設定。這可能需要幾分鐘的時間,請保持應用程式開啟。</string>
<string name="pref_category_extensions">擴充套件</string>
<string name="pref_allowed_content_warnings">允許的內容警告</string>
<string name="pref_apply_content_warnings_to_installed">同時套用於已安裝的擴充套件</string>
<string name="pref_apply_content_warnings_to_installed_summary">關閉時,已安裝的擴充套件將繼續載入及更新,不受其內容警告影響</string>
<string name="content_warnings_info">已安裝但未載入的擴充套件仍會顯示於清單中,但無法使用其來源;未安裝的擴充套件將被隱藏。這無法防止非官方或可能標記錯誤的擴充套件在應用程式內顯示 18+ 內容。</string>
<string name="pref_enable_chapter_name_hash">啟用章節名稱雜湊後綴</string>
<string name="pref_enable_chapter_name_hash_details">確保在章節名稱中加入獨特的雜湊後綴以防止命名衝突。若停用,將無法下載名稱相同的章節。</string>
<string name="ext_filtered">已過濾</string>
<string name="ext_not_loaded">未載入</string>
<string name="ext_not_loaded_dialog">無法載入</string>
<string name="ext_not_loaded_details">為何未載入</string>
<string name="ext_copy_stacktrace">複製堆疊追蹤</string>
<string name="ext_filtered_message">此擴充套件的內容警告並非您選擇載入的項目,因此其來源無法使用。</string>
<string name="ext_unsigned_message">此擴充套件未經簽署,因此沒有可供核對的簽章。</string>
<string name="ext_unsupported_message">此擴充套件是為此應用程式無法載入的擴充套件資料庫版本所建立。更新應用程式或擴充套件或許會有幫助。</string>
<string name="ext_malformed_message">此擴充套件缺少應用程式載入所需的資訊。</string>
<string name="ext_load_failed_message">此擴充套件在載入時發生錯誤,因此其所有來源皆無法使用。</string>
<string name="ext_info_warning">警告</string>
<string name="ext_content_warning_safe">安全</string>
<string name="ext_content_warning_mixed">混合</string>
<string name="ext_content_warning_nsfw">18+</string>
<string name="ext_content_warning_mixed_description">來自此擴充套件的來源可能同時包含安全與 18+ 內容。</string>
<string name="ext_content_warning_nsfw_description">來自此擴充套件的來源主要以 18+ 內容為主。</string>
<string name="pref_load_ahead_only_over_wifi">僅在 Wi-Fi 下預先載入</string>
<string name="pref_load_ahead_only_over_wifi_summary">僅載入您正在閱讀的頁面附近內容,以節省行動數據</string>
<string name="pref_transition_animation_dual">過場動畫 (雙頁)</string>
<string name="transition_animation_basic">基本</string>
<string name="transition_animation_none">無</string>
<string name="transition_animation_flip">翻頁</string>
<string name="pref_cutout_mode_dual">顯示挖孔模式 (雙頁)</string>
<string name="pref_continuous_minwidth">最小寬度</string>
<string name="pref_continuous_gap">間距</string>
<string name="refresh_tracker_profile">重新整理追蹤器個人檔案</string>
<string name="refresh_tracker_success">%s 個人檔案已重新整理</string>
<string name="refresh_tracker_error">%1$s 重新整理錯誤:%2$s</string>
<string name="update_check_skip">略過此版本</string>
<string name="update_check_skip_title">略過 %s?</string>
<string name="update_check_skip_message">更新通常包含錯誤修復與安全性改善。您不會再收到關於此版本的提醒,但當有較新版本釋出時,您仍會收到通知。</string>
<string name="information_webview_crashed">WebView 停止運作,且頁面已關閉</string>
</resources>
@@ -300,7 +300,7 @@ fun SelectItem(
vertical = SettingsItemsPaddings.Vertical,
),
label = { Text(text = label) },
value = options[selectedIndex].toString(),
value = options.getOrNull(selectedIndex)?.toString().orEmpty(),
onValueChange = {},
readOnly = true,
singleLine = true,