Compare commits

...

257 Commits

Author SHA1 Message Date
188d33199e Merge pull request #1365 from heuwerk/patch-1
Update de.json
2024-02-03 04:29:36 -05:00
24928261bb Merge pull request #1367 from ImranR98/dev
Updated a package
2024-02-03 04:29:26 -05:00
4c7bda8343 Updated a package 2024-02-03 04:24:59 -05:00
d88709c999 Update de.json
Added forgotten translation
2024-02-02 11:30:20 +01:00
29e1481a3b Update de.json
Changed "Update" in translated text to "Aktualisierung", since it is used everywhere else.
2024-02-02 11:24:47 +01:00
9d76359543 Merge pull request #1364 from ImranR98/dev
Add DeepL translations as placeholders
2024-02-01 20:34:46 -05:00
f50e791221 Add DeepL translations as placeholders 2024-02-01 20:34:32 -05:00
7d08e5225c Merge pull request #1363 from ImranR98/dev
Correctly report and auto-delete bad APKs (#1251)
2024-02-01 19:32:15 -05:00
3842c1e2df Update packages, increment version 2024-02-01 19:31:32 -05:00
21b1990991 Merge remote-tracking branch 'origin/main' into dev 2024-02-01 19:31:19 -05:00
e278c9fb5a Correctly report and auto-delete bad APKs (#1251) 2024-02-01 19:26:36 -05:00
bfa29bb7c2 Merge pull request #1361 from DwainZwerg/patch-12
Translation of the Note of the sources window
2024-02-01 19:00:41 -05:00
efa55a9696 Update de.json
Okay → OK because it’s shorter and more common
2024-01-31 14:55:23 +00:00
8888cd6264 Update de.json
Translation of the note of the Sources Menu
2024-01-31 14:51:08 +00:00
34e2c014e3 Merge pull request #1357 from jont4/main
Update pt translation
2024-01-29 18:44:36 -05:00
5d92a6d013 Update pt translation 2024-01-28 22:58:17 -03:00
fb03b2e95c Merge pull request #1356 from ImranR98/dev
Better support for SourceForge (#1352), Remove READ_EXTERNAL_STORAGE again (#1309) + fix typo
2024-01-28 17:00:10 -05:00
5a1e09564c Merge pull request #1350 from gidano/main
Update hu.json
2024-01-28 16:58:53 -05:00
3783eba401 Merge pull request #1351 from unbranched/patch-5
Update it.json
2024-01-28 16:58:39 -05:00
a3530ce6bb Remove READ_EXTERNAL_STORAGE again (#1309) + fix typo 2024-01-28 16:58:09 -05:00
27d8655d58 Better support for SourceForge (#1352) 2024-01-28 16:32:58 -05:00
fb845ce601 Update it.json 2024-01-28 10:59:15 +00:00
dbd433df9d Update hu.json 2024-01-28 09:15:28 +01:00
badf32ff11 Merge pull request #1349 from ImranR98/dev
- Allow existing '/refs' in SourceHut URLs (#1347)
- Revert to showing App URL separately again (#1336)
- F-Droid: Don't pull changelog text if it isn't a raw file from GitHub/GitLab (#1340)
- Add a note on self-hosted instances of sources (#1342)
- versionCode changes
2024-01-27 22:00:54 -05:00
5e40f3264e Merge pull request #1337 from jont4/main
Translation: pt update
2024-01-27 21:58:41 -05:00
71bb6d9410 Merge pull request #1338 from teaminh/main
Update vi.json
2024-01-27 21:58:14 -05:00
731b682fc9 Merge pull request #1339 from DwainZwerg/patch-11
Update de.json
2024-01-27 21:57:03 -05:00
c9751227a5 Flutter upgrade + versionCode changes + increment version 2024-01-27 21:56:26 -05:00
6ef2a26e94 Add a note on self-hosted instances of sources (#1342) 2024-01-27 21:26:32 -05:00
5a8efa2388 F-Droid: Don't pull changelog text if it isn't a raw file from GitHub/GitLab (#1340) 2024-01-27 21:01:44 -05:00
d25895fa28 Revert to showing App URL separately again (#1336) 2024-01-27 20:13:01 -05:00
de09f3ece2 Allow existing '/refs' in SourceHut URLs (#1347) 2024-01-27 20:08:05 -05:00
1135ffb30f Update de.json
Better Translation of "latest"
2024-01-25 13:49:57 +00:00
5379cb31e8 Update vi.json 2024-01-25 18:31:46 +07:00
7d41ab44b7 Minor update 2024-01-24 23:21:42 -03:00
b239f9bd05 Merge pull request #1335 from ImranR98/dev
Don't include prereleases in default app entry
2024-01-24 20:15:35 -05:00
1acc923ec2 Don't include prereleases in default app entry 2024-01-24 20:15:13 -05:00
8d6edfb91f Update release.yml 2024-01-24 20:12:56 -05:00
491b62bb0d Update release.yml 2024-01-24 20:10:02 -05:00
d1413fc478 Update release.yml 2024-01-24 20:05:47 -05:00
761f7039e9 Merge pull request #1334 from ImranR98/dev
- De-emphasize 'reset install status' button to avoid confusion
- Minor bugfix (#1331)
2024-01-24 19:54:16 -05:00
59783a341d Upgrade packages, increment version (1.0.0) 2024-01-24 19:53:31 -05:00
0100fa4236 Merge remote-tracking branch 'origin/main' into dev 2024-01-24 19:52:42 -05:00
f6f9ed68dc Merge pull request #1332 from palopezv/patch-1
Update es.json
2024-01-24 19:50:46 -05:00
8adb5e84d1 Reordered a button for consistency 2024-01-24 19:50:11 -05:00
3940b65156 De-emphasize 'reset install status' button to avoid confusion 2024-01-24 19:49:07 -05:00
98e7f27eab Merge remote-tracking branch 'origin/main' into dev 2024-01-24 19:33:45 -05:00
7cf5cafd75 Minor bugfix (#1331) 2024-01-24 19:28:40 -05:00
d750e35273 Update release.yml 2024-01-24 19:22:56 -05:00
1f12e12130 Update es.json
I'm submitting a revision (call it heartless copy editing :😉:) of the Spanish translation file. This revision corrects several grammatical errors, most are syntactical and semantic, some stylistic in order to correct literal (and intellegible) translations and typos.
2024-01-24 16:11:54 -05:00
4a4fb4716e Merge pull request #1328 from ImranR98/dev
Updated modules
2024-01-23 11:37:29 -05:00
efe6ec0d43 Updated modules 2024-01-23 11:37:08 -05:00
00d605e195 Merge pull request #1325 from ImranR98/dev
Update Flutter submodule and make GitHub actions error out if it doesn't match latest/stable
2024-01-22 12:22:15 -05:00
f06de8d19f Update Flutter submodule and make GitHub actions error out if it doesn't match latest/stable 2024-01-22 12:21:38 -05:00
fac335c849 Merge pull request #1324 from p1gp1g/reproducible-build
Add flutter as a submodule (again)
2024-01-22 12:07:43 -05:00
5bef546ae4 Merge pull request #1323 from DwainZwerg/patch-10
Update de.json
2024-01-21 14:13:07 -05:00
b690cd6a67 Update de.json
Edit german translation
2024-01-21 18:59:22 +00:00
dbee4a2657 Merge pull request #1316 from bluefly000/japanese-translation
Update ja.json
2024-01-21 12:35:30 -05:00
67420f293b Merge pull request #1319 from teaminh/main
Update vi.json
2024-01-21 12:35:23 -05:00
d1d6b0792b Merge pull request #1322 from ygxbnet/patch-1
Update zh.json
2024-01-21 12:35:17 -05:00
2ec039ba0f Update zh.json 2024-01-21 23:38:20 +08:00
4b9b9a10ad Update vi.json 2024-01-21 10:00:00 +07:00
4193d8ada1 Update ja.json 2024-01-21 10:19:35 +09:00
4701b090b0 Update ja.json 2024-01-21 10:18:45 +09:00
8b0c85827c Merge pull request #1315 from ClockGen/fastlane
Added fastlane structure for F-Droid
2024-01-20 19:26:31 -05:00
57e02d1c80 Added initial fastlane structure 2024-01-21 01:51:57 +03:00
4446929b10 Merge pull request #1314 from ImranR98/dev
Remove unnecessary padding from badge graphic
2024-01-20 16:42:25 -05:00
c31f65b0e4 Remove unnecessary padding from badge graphic 2024-01-20 16:42:01 -05:00
f6c3b8dceb Merge pull request #1313 from ImranR98/dev
Increment version
2024-01-20 16:31:49 -05:00
10bd825972 Increment version 2024-01-20 16:31:30 -05:00
303bee7c57 Merge pull request #1312 from ImranR98/dev
Bugfix: don't show 'Apps Updated' toast when nothing was updated (#1307), Disable app page buttons while app info is being updated
2024-01-20 16:29:47 -05:00
369ae43062 Disable app page buttons while app info is being updated 2024-01-20 16:28:54 -05:00
62dc7fd88b Bugfix: don't show 'Apps Updated' toast when nothing was updated (#1307) 2024-01-20 16:18:19 -05:00
4aace63cd7 Merge pull request #1311 from ImranR98/dev
Removed unnecessary READ_EXTERNAL_STORAGE permission (#1309)
2024-01-20 16:05:33 -05:00
5d588a05a7 Removed unnecessary READ_EXTERNAL_STORAGE permission (#1309) 2024-01-20 16:04:17 -05:00
cab6f9d026 Merge pull request #1310 from ImranR98/dev
"Standardize" translation files + use machine-translations as placeholders for missing translations
2024-01-20 15:42:24 -05:00
fc94d553eb Merge remote-tracking branch 'origin/main' into dev 2024-01-20 15:41:44 -05:00
2952345585 Use machine-translations as placeholders for missing translations 2024-01-20 15:39:10 -05:00
becc3828b7 Remove some unused translations 2024-01-20 14:58:37 -05:00
2a62248991 "Standardize" translation files 2024-01-20 14:45:28 -05:00
eb2b31219c Merge pull request #1306 from gidano/main
Update hu.json
2024-01-20 14:31:51 -05:00
55053f9627 Merge pull request #1304 from bluefly000/japanese-translation
Update ja.json
2024-01-20 14:31:19 -05:00
5ff6c64938 Update hu.json 2024-01-20 10:35:18 +01:00
c1c517cf38 Update ja.json 2024-01-20 18:21:00 +09:00
b3de2e5d6b Merge pull request #1303 from ImranR98/dev
- Better partial APK hash as pseudo-version (related to #1101)
- Support Direct APK Links (#1292)
- Easier to understand version detection settings
- Bugfix in automatic source selection for URL input
- Various UI improvements and wording changes
- Add invert option to apk filter regex 
- Filter out fdroid APKs from default Obtainium entry (for later)
2024-01-19 23:41:27 -05:00
0183070b2b Merge remote-tracking branch 'origin/main' into dev 2024-01-19 23:38:23 -05:00
089bbc3ee9 Merge pull request #1291 from Bardesss/patch-1
Update nl.json
2024-01-19 23:29:49 -05:00
b085801c2c Merge pull request #1298 from jont4/main
Translation: Some fixes and updates in pt.json
2024-01-19 23:29:40 -05:00
ad4cae4288 Add F-Droid build to GitHub action 2024-01-19 23:29:22 -05:00
95c285e1f7 Filter out fdroid APKs from default Obtainium entry 2024-01-19 23:25:00 -05:00
c31a1912a5 Add invert option to apk filter regex 2024-01-19 23:22:00 -05:00
99da1f8481 Upgrade packages, increment version 2024-01-19 20:41:07 -05:00
bf6b3a7da0 UI bug 2024-01-19 20:36:40 -05:00
4ab4f58f65 Cleaner 'add app' page 2024-01-19 20:35:44 -05:00
29a76ac8e6 Removed unused divider and commented code on settings page 2024-01-19 19:59:30 -05:00
e2761ce284 UI improvements on app page 2024-01-19 19:57:28 -05:00
dc45334cb9 Support Direct APK Links (#1292) 2024-01-19 18:54:01 -05:00
56052bfd79 Bugfix in version detection toggles 2024-01-19 00:43:02 -05:00
f5067d636a More UI tweaks 2024-01-19 00:21:57 -05:00
b173b1300a Clearer wording 2024-01-19 00:12:43 -05:00
15a34e53bf Bugfix in automatic source selection for URL input 2024-01-19 00:00:47 -05:00
183fb26988 Clearer version detection settings 2024-01-18 23:46:23 -05:00
2da9a8cd59 Remove unnecessary print() 2024-01-18 20:45:34 -05:00
bb07000280 Better partial APK hash as pseudo-version (related to #1101) 2024-01-18 20:45:06 -05:00
3a46ad2957 Some fixes and updates 2024-01-16 15:48:59 -03:00
6b631d119c Merge branch 'ImranR98:main' into main 2024-01-16 15:24:02 -03:00
051762fcc1 Update nl.json
Added new strings.
2024-01-15 11:38:00 +00:00
595d5dc283 Merge pull request #1289 from ImranR98/dev
Added badge graphic (#1287)
2024-01-14 15:14:41 -05:00
1d328f07e2 Merge remote-tracking branch 'origin/main' into dev 2024-01-14 15:14:01 -05:00
b1c8ac6f2a Added badge graphic (#1287) 2024-01-14 15:09:16 -05:00
da619d37f7 Merge pull request #1288 from iDazai/main
Update de.json
2024-01-14 14:56:54 -05:00
236c4722e5 Update de.json
added 2 missing strings, translated new strings
2024-01-14 15:36:08 +01:00
92f59116a0 Merge remote-tracking branch 'origin/main' into dev 2024-01-14 01:37:17 -05:00
c28040f0cb Merge remote-tracking branch 'origin/main' into dev 2024-01-14 01:36:43 -05:00
e8b9654320 Update release.yml 2024-01-14 01:36:35 -05:00
be1a793a37 Update release.yml 2024-01-14 01:34:32 -05:00
440720afb6 Update release.yml 2024-01-14 01:31:42 -05:00
900d3e734e Update release.yml 2024-01-14 01:27:13 -05:00
0bf096abb5 Temporary GitHub action change 2024-01-14 01:26:17 -05:00
632efb9e22 Merge remote-tracking branch 'origin/main' into dev 2024-01-14 01:24:50 -05:00
bfc506e450 Merge pull request #1285 from ImranR98/dev
- Improve loading time (parallelize version detection)
- Newest asset upload date as release date for GitHub (#1284)
- Enable 'start on boot' for BG task (#1234)
- Remove the need to hardcode Obtainium's version number
2024-01-14 01:24:25 -05:00
ffe612708c Remove the need to hardcode Obtainium's version number 2024-01-14 01:22:35 -05:00
5d161160aa Increment version 2024-01-14 00:56:25 -05:00
eadf3e5a29 Enable 'start on boot' for BG task (#1234) 2024-01-14 00:22:25 -05:00
6b6b4084a0 Newest asset upload date as release date for GitHub (#1284) 2024-01-14 00:14:44 -05:00
30a4633f72 Improve loading time (parallelize version detection) 2024-01-13 23:26:57 -05:00
d44139dd72 Merge pull request #1281 from ImranR98/dev
Allow entire app config to be shared as link (see https://github.com/ImranR98/apps.obtainium.imranr.dev/issues/5)
2024-01-12 23:47:31 -05:00
60869a0490 Allow entire app config to be shared as link (see https://github.com/ImranR98/apps.obtainium.imranr.dev/issues/5) 2024-01-12 23:46:50 -05:00
102be5fa2e Merge pull request #1280 from ImranR98/dev
- Fix form input issues (related to #1272)
- HTML Source bugfix (related to #1259)
2024-01-12 22:32:49 -05:00
a25c04b390 Update packages, increment version, dart fix 2024-01-12 22:32:33 -05:00
d46f0a1c33 HTML Source bugfix (related to #1259) 2024-01-12 22:16:06 -05:00
5c7f5a99e1 Attempt to fix form input issues for recursive forms (NOT FULLY TESTED) 2024-01-12 21:51:48 -05:00
d949a80f19 Merge pull request #1279 from gidano/main
Update hu.json
2024-01-12 20:29:33 -05:00
6b5d4e2027 Merge pull request #1278 from mehdeej/main
Update fa.json
2024-01-12 20:29:25 -05:00
199d071ee8 Update hu.json 2024-01-12 15:45:26 +01:00
5b78de5d61 Merge branch 'ImranR98:main' into main 2024-01-12 05:46:33 -03:00
db4f13e2e1 Update fa.json 2024-01-12 07:32:52 +03:30
5e869b7e03 Update release.yml 2024-01-11 21:58:48 -05:00
34fee36132 Merge pull request #1277 from ImranR98/dev
- Fix APKPure downloads (#1250)
- F-Droid bugfix (#1264)
- Allow versionCode to be used as the version for comparison (#1269)
- Add custom header support to HTML (#709, #1272)
- Disabled Mullvad source (#1276)
- Bugfixes: Don't make URLs lowercase, never auto-select Jenkins
2024-01-11 21:44:36 -05:00
ea09bc36a5 Merge branch 'main' into dev 2024-01-11 21:44:24 -05:00
f7e0678cb2 Remove unused variable 2024-01-11 21:39:41 -05:00
223ae378a9 Update packages, increment version 2024-01-11 21:39:26 -05:00
6f52a48991 Merge remote-tracking branch 'origin/main' into dev 2024-01-11 21:36:15 -05:00
0280935955 Merge pull request #1268 from bluefly000/japanese-translation
Update ja.json
2024-01-11 21:36:03 -05:00
eebc7d9ab3 Merge pull request #1271 from pmtpro/main
update vi.json
2024-01-11 21:35:53 -05:00
aa7b5652ff Fix APKPure downloads (#1250) 2024-01-11 21:34:46 -05:00
ec6683a198 Bugfixes: Don't make URLs lowercase, never auto-select Jenkins 2024-01-11 21:28:15 -05:00
05372924a0 F-Droid bugfix (#1264) 2024-01-11 21:16:25 -05:00
e63c1399dd Allow versionCode to be used as the version for comparison (#1269) 2024-01-11 20:10:20 -05:00
4fcad92e1d Disabled Mullvad source (#1276) 2024-01-11 19:50:51 -05:00
e18e7298bc GeneratedFormSubForm UI improvements 2024-01-11 19:43:17 -05:00
76a6a509cd Header usage bugfix 2024-01-11 19:23:59 -05:00
7a03561ff6 Add custom header support to HTML (#709, #1272) 2024-01-11 19:01:17 -05:00
70e54ce14a F-Droid small fix 2024-01-10 18:51:22 -05:00
b5f86f0e79 update vi.json 2024-01-10 10:20:59 +07:00
4ebca49ef7 Update ja.json 2024-01-09 22:26:11 +09:00
49cfa95463 Merge pull request #1265 from ImranR98/dev
- Multi-host support + add '.net' host to APKPure source (#1250)
- HTML link parsing bugfix (#1259)
- APKMirror version extraction bugfix (#1264)
2024-01-08 19:28:31 -05:00
4600ab0593 Increment version 2024-01-08 19:20:08 -05:00
7f2ca98bde Multi-host support + add '.net' host to APKPure source (#1250) 2024-01-08 19:17:50 -05:00
6511485bcf APKMirror version extraction bugfix (#1264) 2024-01-08 18:33:24 -05:00
fd22113e44 HTML link parsing bugfix (#1259) 2024-01-08 18:18:57 -05:00
f2ae078723 Merge branch 'ImranR98:main' into main 2024-01-08 18:20:27 -03:00
c3dacf2b9b Translation: tiny bug 2024-01-08 18:18:57 -03:00
e8580dc1d5 Merge pull request #1257 from ImranR98/dev
Add F-Droid changelogs (#1255), Fix corrupt pt.json (#1256)
2024-01-07 21:59:17 -05:00
daffff7eb0 Add F-Droid changelogs (#1255) 2024-01-07 21:57:23 -05:00
751fda5e37 Increment version 2024-01-07 21:45:41 -05:00
1e38abc500 Fix corrupt pt.json (#1256) 2024-01-07 21:40:56 -05:00
8e7137815b Merge pull request #1254 from ImranR98/dev
URL parsing bugfix for HTML source (#1253)
2024-01-07 12:37:51 -05:00
4226ee453f Merge remote-tracking branch 'origin/main' into dev 2024-01-07 12:37:14 -05:00
568a110443 Merge pull request #1249 from LilligantMatsuri/main
Update Chinese translation
2024-01-07 12:37:00 -05:00
7fd7a6ca8d Merge pull request #1252 from jont4/main
Translation: many fixes in PT translation
2024-01-07 12:36:47 -05:00
3092c854ff Increment version 2024-01-07 12:36:20 -05:00
a9566f4b23 Remove redundant function call 2024-01-07 12:34:09 -05:00
7a5aa3c11d URL parsing bugfix for HTML source (#1253) 2024-01-07 12:33:06 -05:00
f2454bb028 Translation: many fixes in PT translation 2024-01-07 12:28:24 -03:00
2c1023a6fd Update zh.json
- Add & translate new strings
- Correct inaccurate translations

Signed-off-by: Matsuri <matsuri@vmoe.info>
2024-01-07 16:55:00 +08:00
9187ac6684 Merge pull request #1248 from ImranR98/dev
Removed Steam Source (existing apps auto-switched to HTML) (#1244) + Bugfixes
2024-01-06 23:39:11 -05:00
9640e43bc5 Upgrade packages, increment version 2024-01-06 23:38:36 -05:00
09b24b36f2 Removed Steam Source (existing apps auto-switched to HTML) (#1244) + bugfixes 2024-01-06 23:37:27 -05:00
06a761c0d0 Merge pull request #1247 from CertainBot/main
Update Spanish
2024-01-06 22:46:06 -05:00
6ff7d7fcd7 Merge pull request #1246 from bluefly000/japanese-translation
Update ja.json
2024-01-06 22:45:47 -05:00
0f27c79303 Merge pull request #1243 from pmtpro/patch-1
Update vi.json
2024-01-06 22:45:25 -05:00
9961ad081f Update Spanish
- English: new strings, typos, minor changes
- Español: strings nuevos, correcciones, algunas mejoras
2024-01-07 02:55:18 +01:00
02056b4f78 Update ja.json 2024-01-06 01:05:52 +09:00
0492588c25 Update vi.json 2024-01-04 09:26:37 +07:00
ad8463ac8b Update README.md 2024-01-03 20:28:49 -05:00
7843de240a Merge pull request #1241 from ImranR98/dev
Don't use numpad for version extraction match group
2024-01-03 20:19:18 -05:00
0037a08017 Don't use numpad for version extraction match group 2024-01-03 20:18:59 -05:00
92ba432992 Merge pull request #1240 from ImranR98/dev
- Remove reference to old plugin to avoid crashes (#1221)
- Confirmation box for link-based imports (#1232)
- Construct extracted versions from match groups (#1235)
- Fix the WebView 'cleartext' error caused by HTTP sites (#1235)
- Make GitHub's (and Codeberg's) "fallback" work with the APK filter (#1236)
- Make version extraction universal (#1237)
- HTML Bugfix (#1238)
2024-01-03 20:16:19 -05:00
3a521f4014 Update packages, increment version 2024-01-03 20:13:47 -05:00
e8bbc9799c Fix the WebView 'cleartext' error caused by HTTP sites 2024-01-03 20:11:52 -05:00
96a48b4813 Construct extracted versions from match groups (#1235) 2024-01-03 20:02:44 -05:00
355450e82d Merge duplicate version extraction code into one function 2024-01-03 19:28:45 -05:00
99307c5b0c Make version extraction universal (#1237) 2024-01-03 19:20:03 -05:00
ace8be6514 Remove reference to old plugin to avoid crashes (#1221) 2024-01-03 19:04:36 -05:00
1b538931b1 Confirmation box for link-based imports (#1232) 2024-01-03 19:03:05 -05:00
7ccd08ee2b Make GitHub's (and Codeberg's) "fallback" work with the APK filter (#1236) 2024-01-03 18:32:09 -05:00
104beed594 HTML Bugfix (#1238) 2024-01-03 18:25:02 -05:00
e8df594edb Merge pull request #1233 from iDazai/main
added missing strings in some languages, translated for DE
2024-01-03 18:06:29 -05:00
6e922a84f8 Merge pull request #1230 from re7gog/re7gog
Fixes and system font
2024-01-03 18:06:04 -05:00
b74bf86277 Corrections 2024-01-03 14:39:33 +03:00
6cbe50c9ef Update vi.json 2024-01-02 07:57:32 +01:00
14fcca040b Update tr.json 2024-01-02 07:57:21 +01:00
7bb545cff9 Update sv.json 2024-01-02 07:57:07 +01:00
f52b1a246c Update pl.json 2024-01-02 07:56:41 +01:00
273dfd89cc Update nl.json 2024-01-02 07:56:17 +01:00
52d72d287b Update ja.json 2024-01-02 07:56:01 +01:00
e8d8fcbe69 Update hu.json 2024-01-02 07:55:44 +01:00
30c6c1afd3 Update fr.json 2024-01-02 07:55:16 +01:00
c8c8093b3a Update fa.json 2024-01-02 07:55:03 +01:00
c970b2cf2e Update es.json 2024-01-02 07:54:23 +01:00
0815283d31 Update bs.json 2024-01-02 07:53:32 +01:00
55ee2d93b8 Update de.json
added missing strings and translated
2024-01-02 07:53:07 +01:00
c9e17ed42b Merge branch 'main' into re7gog 2024-01-01 18:50:24 +03:00
76a91b7fe0 Don't load font if loaded 2024-01-01 18:42:38 +03:00
f9b3169b6a Update README.md 2024-01-01 07:55:00 -05:00
4f42b5a3ee Merge pull request #1228 from ImranR98/dev
Save Search Preferences (#1226)
2023-12-31 23:42:22 -05:00
5d9645eaff Save Search Preferences (#1226) 2023-12-31 23:41:46 -05:00
c032808d82 Merge pull request #1227 from ImranR98/dev
- Add www subdomain support for various sources (#1222)
- Disable standard version detection for WhatsApp (should never have been enabled)
2023-12-31 23:29:31 -05:00
282c94266e Merge remote-tracking branch 'origin/main' into dev 2023-12-31 23:28:09 -05:00
e2f7d52bee Increment version 2023-12-31 23:27:23 -05:00
6317f0162a Disable standard version detection for WhatsApp (should never have been enabled) 2023-12-31 22:05:50 -05:00
436a6310d7 Add www subdomain support for various sources (#1222) 2023-12-31 15:59:44 -05:00
0f74195255 Replace okay with ok 2023-12-31 19:42:28 +03:00
5ba33786ab System font and newer dependencies 2023-12-31 19:33:33 +03:00
70fcfc1753 Merge remote-tracking branch 'origin/main' into dev 2023-12-30 10:50:22 -05:00
1e34048c0c Merge pull request #1218 from LilligantMatsuri/main
Update Chinese translation
2023-12-30 10:50:12 -05:00
45b1b23262 Merge pull request #1215 from oSoWoSo/cz
Update cs.json
2023-12-30 10:50:04 -05:00
07842ace4e Merge pull request #1217 from gidano/main
Update hu.json
2023-12-30 10:49:34 -05:00
8e75280093 Merge pull request #1220 from ImranR98/dev
Fix intermediate link bug (#1219)
2023-12-30 10:49:21 -05:00
029b9ef498 Fix intermediate link bug (#1219) 2023-12-30 10:49:05 -05:00
51970abce7 Update zh.json
- Add & translate new strings
- Minor improvements

Signed-off-by: Matsuri <matsuri@vmoe.info>
2023-12-30 16:55:01 +08:00
f6faa19e5f Update hu.json 2023-12-30 09:45:46 +01:00
dd7217ca54 Update cs.json 2023-12-30 04:31:05 +01:00
6e13457eb2 Merge pull request #1210 from iDazai/main
Update de.json
2023-12-29 12:44:09 -06:00
3b319ee19b Merge pull request #1206 from gidano/main
Update hu.json
2023-12-29 12:44:02 -06:00
8ea8daa811 Merge pull request #1212 from ImranR98/dev
HTML intermediate link bugfix (#1211)
2023-12-29 12:43:49 -06:00
eb29b908c2 Increment version 2023-12-29 13:43:06 -05:00
9935cb482e HTML intermediate link bugfix (#1211) 2023-12-29 13:39:56 -05:00
0d6e7181cf Update de.json 2023-12-29 17:35:25 +01:00
d225650e15 Update hu.json 2023-12-29 10:12:13 +01:00
bfe09791d5 Merge pull request #1205 from ImranR98/dev
Bugfix in HTML intermediate link UI (incomplete)
2023-12-28 18:24:03 -06:00
85d103f3f6 Bugfix in HTML intermediate link UI (incomplete) 2023-12-28 19:16:16 -05:00
9e6dbe2465 Merge pull request #1189 from iDazai/main
Update de.json
2023-12-28 16:09:12 -06:00
355e5ccda6 Merge branch 'main' into main 2023-12-28 16:09:03 -06:00
a5f6f05e10 Merge pull request #1190 from unbranched/patch-4
Update it.json
2023-12-28 16:08:29 -06:00
db0d35d80b Merge pull request #1196 from LucasTavaresA/main
Update pt.json
2023-12-28 16:08:21 -06:00
6fca2a3931 Merge pull request #1197 from CertainBot/main
Update Spanish 3rd pass
2023-12-28 16:08:11 -06:00
0305a42b02 Merge branch 'main' into main 2023-12-28 16:08:04 -06:00
77d81716ed Switch to a New Background Task Plugin (#608), Add Link Text Filter for HTML Links (#1182), Add Support for Multiple Intermediate Links to HTML Source (#1204)
- Switch to a New Background Task Plugin (#608)
- Add Link Text Filter for HTML Links (#1182)
- Add Support for Multiple Intermediate Links to HTML Source
2023-12-28 17:06:11 -05:00
1fc8ee6fee Fix all 2023-12-28 17:50:12 +03:00
3e54e80eb6 Update it.json
Added missing strings.
2023-12-27 09:15:22 +00:00
3c9bb63d32 Update es.json
- English: updated new strings, few minor corrections.
- Español: actualizacion de frases nuevas, correcciones menores.
2023-12-25 16:04:51 +01:00
617ab9efab Update pt.json 2023-12-25 10:45:12 -03:00
bc574097e2 Update it.json 2023-12-25 09:40:29 +00:00
4cc64dc233 Update de.json
translated three new strings, fixed on with hyphen missing
2023-12-25 10:30:44 +01:00
sim
7987083555 Add flutter as a submodule 2023-02-02 21:56:32 +01:00
80 changed files with 3946 additions and 2468 deletions

1
.flutter Submodule

Submodule .flutter added at 41456452f2

View File

@ -2,6 +2,10 @@ name: Build and Release
on:
workflow_dispatch:
inputs:
beta:
type: boolean
description: Is beta?
jobs:
build:
@ -19,11 +23,34 @@ jobs:
gpg_private_key: ${{ secrets.PGP_KEY_BASE64 }}
passphrase: ${{ secrets.PGP_PASSPHRASE }}
- name: Check submodule
id: check_submodule
run: |
SUBMODULE_COMMIT_LONG="$(git submodule status | head -1 | tail -c +2 | awk '{print $1}')"
FLUTTER_COMMIT_SHORT="$(flutter --version | head -2 | tail -1 | awk '{print $4}')"
echo "SUBMODULE_COMMIT_LONG=$SUBMODULE_COMMIT_LONG, FLUTTER_COMMIT_SHORT=$FLUTTER_COMMIT_SHORT"
if ! [[ "$SUBMODULE_COMMIT_LONG" =~ ^$FLUTTER_COMMIT_SHORT ]]; then
echo "Your submodule has not been updated!"
exit 1
fi
- name: Extract Version
id: extract_version
run: |
VERSION=$(grep -oP "^version: [^\+]+" pubspec.yaml | tail -c +10)
echo "version=$VERSION" >> $GITHUB_OUTPUT
if [ ${{ inputs.beta }} == true ]; then BETA=true; else BETA=false; fi
echo "beta=$BETA" >> $GITHUB_OUTPUT
TAG="v$VERSION"
if [ $BETA == true ]; then TAG="$TAG"-beta; fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Build APKs
run: |
sed -i 's/signingConfig signingConfigs.release//g' android/app/build.gradle
flutter build apk --flavor normal && flutter build apk --split-per-abi --flavor normal
for file in build/app/outputs/flutter-apk/app-*normal*.apk*; do mv "$file" "${file//-normal/}"; done
flutter build apk --flavor fdroid -t lib/main_fdroid.dart && flutter build apk --split-per-abi --flavor fdroid -t lib/main_fdroid.dart
rm ./build/app/outputs/flutter-apk/*.sha1
ls -l ./build/app/outputs/flutter-apk/
@ -37,23 +64,13 @@ jobs:
for apk in ./build/app/outputs/flutter-apk/*-release*.apk; do
unsignedFn=${apk/-release/-unsigned}
mv "$apk" "$unsignedFn"
${ANDROID_HOME}/build-tools/30.0.2/apksigner sign --ks apksign.keystore --ks-pass pass:"${KEYSTORE_PASSWORD}" --out "${apk}" "${unsignedFn}"
${ANDROID_HOME}/build-tools/$(ls ${ANDROID_HOME}/build-tools/ | tail -1)/apksigner sign --ks apksign.keystore --ks-pass pass:"${KEYSTORE_PASSWORD}" --out "${apk}" "${unsignedFn}"
sha256sum ${apk} | cut -d " " -f 1 > "$apk".sha256
gpg --batch --pinentry-mode loopback --passphrase "${PGP_PASSPHRASE}" --sign --detach-sig "$apk".sha256
done
rm apksign.keystore
PGP_KEY_FINGERPRINT="${{ steps.import_pgp_key.outputs.fingerprint }}"
- name: Extract Version
id: extract_version
run: |
VERSION=$(grep -oP "currentVersion = '\K[^']+" lib/main.dart)
echo "version=$VERSION" >> $GITHUB_OUTPUT
TAG=$(grep -oP "'.*\\\$currentVersion.*'" lib/main.dart | head -c -2 | tail -c +2 | sed "s/\$currentVersion/$VERSION/g")
echo "tag=$TAG" >> $GITHUB_OUTPUT
if [ -n "$(echo $TAG | grep -oP '\-beta$')" ]; then BETA=true; else BETA=false; fi
echo "beta=$BETA" >> $GITHUB_OUTPUT
- name: Create Tag
uses: mathieudutour/github-tag-action@v6.1
with:

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule ".flutter"]
path = .flutter
url = https://github.com/flutter/flutter/

View File

@ -1,12 +1,14 @@
# ![Obtainium Icon](./assets/graphics/icon_small.png) Obtainium
[![Ceasefire Now](https://badge.techforpalestine.org/default)](https://techforpalestine.org/learn-more)
Get Android App Updates Directly From the Source.
Obtainium allows you to install and update Apps directly from their releases pages, and receive notifications when new releases are made available.
Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0)
Wiki: [https://github.com/ImranR98/Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki)
Read the Wiki: [https://github.com/ImranR98/Obtainium/wiki](https://github.com/ImranR98/Obtainium/wiki)
Currently supported App sources:
- Open Source - General:
@ -19,7 +21,7 @@ Currently supported App sources:
- [SourceForge](https://sourceforge.net/)
- [SourceHut](https://git.sr.ht/)
- Other - General:
- [APKPure](https://apkpure.com/)
- [APKPure](https://apkpure.net/)
- [Aptoide](https://aptoide.com/)
- [Uptodown](https://uptodown.com/)
- [APKMirror](https://apkmirror.com/) (Track-Only)
@ -30,8 +32,8 @@ Currently supported App sources:
- [Signal](https://signal.org/)
- [VLC](https://videolan.org/)
- Other - App-Specific:
- [WhatsApp](https://whatsapp.com)
- [Telegram App](https://telegram.org)
- [Steam Mobile Apps](https://store.steampowered.com/mobile)
- [Neutron Code](https://neutroncode.com)
- "HTML" (Fallback): Any other URL that returns an HTML page with links to APK files

View File

@ -33,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
compileSdkVersion 34
compileSdkVersion rootProject.ext.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
@ -54,7 +54,7 @@ android {
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 24
targetSdkVersion 34
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
@ -96,19 +96,28 @@ repositories {
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
def shizuku_version = '13.1.5'
implementation "dev.rikka.shizuku:api:$shizuku_version"
implementation "dev.rikka.shizuku:provider:$shizuku_version"
def hidden_api_version = '4.1.0'
// DO NOT UPDATE Hidden API without updating the Android tools
// and do not update Android tools without updating the whole Flutter
// (also in android/build.gradle)
def hidden_api_version = '4.3.1'
implementation "dev.rikka.tools.refine:runtime:$hidden_api_version"
implementation "dev.rikka.hidden:compat:$hidden_api_version"
compileOnly "dev.rikka.hidden:stub:$hidden_api_version"
implementation "org.lsposed.hiddenapibypass:hiddenapibypass:4.3"
implementation "com.github.topjohnwu.libsu:core:5.2.2"
}
ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
if (abiVersionCode != null) {
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
} else {
output.versionCodeOverride = variant.versionCode * 10
}
}
}

View File

@ -1,10 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="dev.imranr.obtainium">
<application
android:label="Obtainium"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true">
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
@ -43,21 +45,6 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<service
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
<receiver
android:name="dev.fluttercommunity.plus.androidalarmmanager.AlarmBroadcastReceiver"
android:exported="false" />
<receiver
android:name="dev.fluttercommunity.plus.androidalarmmanager.RebootBroadcastReceiver"
android:enabled="false"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="dev.imranr.obtainium"
@ -77,7 +64,6 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
@ -85,5 +71,7 @@
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
tools:node="remove" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
</manifest>

View File

@ -0,0 +1,44 @@
package dev.imranr.obtainium
import android.util.Xml
import org.xmlpull.v1.XmlPullParser
import java.io.File
import java.io.FileInputStream
class DefaultSystemFont {
fun get(): String {
return try {
val file = File("/system/etc/fonts.xml")
val fileStream = FileInputStream(file)
parseFontsFileStream(fileStream)
} catch (e: Exception) {
e.message ?: "Unknown fonts.xml parsing exception"
}
}
private fun parseFontsFileStream(fileStream: FileInputStream): String {
fileStream.use { stream ->
val parser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(stream, null)
parser.nextTag()
return parseFonts(parser)
}
}
private fun parseFonts(parser: XmlPullParser): String {
while (!((parser.next() == XmlPullParser.END_TAG) && (parser.name == "family"))) {
if ((parser.eventType == XmlPullParser.START_TAG) && (parser.name == "font")
&& (parser.getAttributeValue(null, "style") == "normal")
&& (parser.getAttributeValue(null, "weight") == "400")) {
break
}
}
parser.next()
val fontFile = parser.text.trim()
if (fontFile == "") {
throw NoSuchFieldException("The font filename couldn't be found in fonts.xml")
}
return "/system/fonts/$fontFile"
}
}

View File

@ -22,12 +22,13 @@ import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.Result
import java.io.IOException
import java.util.concurrent.CountDownLatch
import org.lsposed.hiddenapibypass.HiddenApiBypass
import rikka.shizuku.Shizuku
import rikka.shizuku.Shizuku.OnRequestPermissionResultListener
import rikka.shizuku.ShizukuBinderWrapper
class MainActivity: FlutterActivity() {
private var installersChannel: MethodChannel? = null
private var nativeChannel: MethodChannel? = null
private val SHIZUKU_PERMISSION_REQUEST_CODE = (10..200).random()
private fun shizukuCheckPermission(result: Result) {
@ -51,7 +52,7 @@ class MainActivity: FlutterActivity() {
requestCode: Int, grantResult: Int ->
if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) {
val res = if (grantResult == PackageManager.PERMISSION_GRANTED) 1 else 0
installersChannel!!.invokeMethod("resPermShizuku", mapOf("res" to res))
nativeChannel!!.invokeMethod("resPermShizuku", mapOf("res" to res))
}
}
@ -76,7 +77,8 @@ class MainActivity: FlutterActivity() {
val params =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
var installFlags: Int = PackageInstallerUtils.getInstallFlags(params)
installFlags = installFlags or 0x00000004 // PackageManager.INSTALL_ALLOW_TEST
installFlags = installFlags or (0x00000002/*PackageManager.INSTALL_REPLACE_EXISTING*/
or 0x00000004 /*PackageManager.INSTALL_ALLOW_TEST*/)
PackageInstallerUtils.setInstallFlags(params, installFlags)
val sessionId = packageInstaller.createSession(params)
val iSession = IPackageInstallerSession.Stub.asInterface(
@ -136,7 +138,7 @@ class MainActivity: FlutterActivity() {
}
private fun rootInstallApk(apkFilePath: String, result: Result) {
Shell.sh("pm install -R -t " + apkFilePath).submit { out ->
Shell.sh("pm install -r -t " + apkFilePath).submit { out ->
val builder = StringBuilder()
for (data in out.getOut()) { builder.append(data) }
result.success(builder.toString().endsWith("Success"))
@ -145,12 +147,18 @@ class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
HiddenApiBypass.addHiddenApiExemptions("")
}
Shizuku.addRequestPermissionResultListener(shizukuRequestPermissionResultListener)
installersChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger, "installers")
installersChannel!!.setMethodCallHandler {
nativeChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger, "native")
nativeChannel!!.setMethodCallHandler {
call, result ->
if (call.method == "checkPermissionShizuku") {
if (call.method == "getSystemFont") {
val res = DefaultSystemFont().get()
result.success(res)
} else if (call.method == "checkPermissionShizuku") {
shizukuCheckPermission(result)
} else if (call.method == "checkPermissionRoot") {
rootCheckPermission(result)

View File

@ -1,14 +1,19 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '1.8.10'
ext {
compileSdkVersion = 34 // or latest
targetSdkVersion = 34 // or latest
appCompatVersion = "1.4.2" // or latest
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.0'
classpath "com.android.tools.build:gradle:7.4.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'dev.rikka.tools.refine:gradle-plugin:4.1.0' // Do not update!
classpath "dev.rikka.tools.refine:gradle-plugin:4.3.1"
}
}
@ -16,6 +21,10 @@ allprojects {
repositories {
google()
mavenCentral()
maven {
// [required] background_fetch
url "${project(':background_fetch').projectDir}/libs"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -51,9 +51,8 @@
"percentProgress": "Napredak: {}%",
"pleaseWait": "Molimo sačekajte",
"updateAvailable": "Ažuriranje dostupno",
"estimateInBracketsShort": "(Procjena)",
"notInstalled": "Nije instalirano",
"estimateInBrackets": "(Procjena)",
"pseudoVersion": "pseudo-verzija",
"selectAll": "Označi sve",
"deselectX": "Poništi odabir {}",
"xWillBeRemovedButRemainInstalled": "{} će biti uklonjen iz Obtainiuma, ali će ostati instaliran na uređaju.",
@ -73,6 +72,8 @@
"unpinFromTop": "Otkvači sa vrha",
"resetInstallStatusForSelectedAppsQuestion": "Resetujte status instalacije za odabrane aplikacije?",
"installStatusOfXWillBeResetExplanation": "Status instalacije bilo koje odabrane aplikacije će se resetovati.\n\nTo može pomoći kada je verzija aplikacije prikazana u Obtainiumu netačna zbog neuspjelih ažuriranja ili drugih problema.",
"customLinkMessage": "Ove veze rade na uređajima s instaliranim Obtainiumom",
"shareAppConfigLinks": "Podijelite konfiguraciju aplikacije kao HTML vezu",
"shareSelectedAppURLs": "Podijeli odabrane URL-ove aplikacija",
"resetInstallStatus": "Resetujte status instalacije",
"more": "Više",
@ -103,7 +104,6 @@
"importErrors": "Uvezi greške",
"importedXOfYApps": "{} od {} aplikacija uvezeno.",
"followingURLsHadErrors": "Sljedeći URL-ovi su imali greške:",
"okay": "Dobro",
"selectURL": "Odaberite URL",
"selectURLs": "Odaberite URL-ove",
"pick": "Odaberi",
@ -111,8 +111,6 @@
"dark": "Tamna",
"light": "Svijetla",
"followSystem": "Pratite sistem",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Koristite čisto crnu tamnu temu",
"appSortBy": "Aplikacije sortirane po",
"authorName": "Autor/Ime",
@ -170,13 +168,12 @@
"lastUpdateCheckX": "Posljednja provjera ažuriranja: {}",
"remove": "Izbriši",
"yesMarkUpdated": "Da, označi kao ažurirano",
"fdroid": "F-Droid Official",
"fdroid": "Službeni F-Droid",
"appIdOrName": "ID ili ime aplikacije",
"appId": "Apl ID",
"appWithIdOrNameNotFound": "Nije pronađena aplikacija s tim ID-om ili imenom",
"reposHaveMultipleApps": "Repo-i mogu sadržavati više aplikacija",
"fdroidThirdPartyRepo": "F-Droid Repo treće strane",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Razgovor na Steamu (chat)",
"install": "Instaliraj",
@ -212,6 +209,7 @@
"changes": "Promjene",
"releaseDate": "Datum izdavanja",
"importFromURLsInFile": "Uvoz iz URL-ova u datoteci (kao što je OPML)",
"versionDetectionExplanation": "Pomiri niz verzije sa verzijom otkrivenom iz OS-a",
"versionDetection": "Otkrivanje verzije",
"standardVersionDetection": "Detekcija standardne verzije",
"groupByCategory": "Grupiši po kategoriji",
@ -236,7 +234,7 @@
"addInfoInSettings": "Dodajte ove informacije u Postavkama.",
"githubSourceNote": "GitHub ograničavanje se može izbjeći korišćenjem tokena za lični pristup.",
"gitlabSourceNote": "GitLab APK preuzimanje možda neće raditi bez tokena za lični pristup.",
"sortByFileNamesNotLinks": "Sortirajte po imenima datoteka umjesto po punim linkovima",
"sortByLastLinkSegment": "Sortiraj samo po zadnjem segmentu veze",
"filterReleaseNotesByRegEx": "Filtirajte promjene u izdanju po regularnom izrazu",
"customLinkFilterRegex": "Prilagođeni APK link filtrira se po regularnom izrazu (Zadano '.apk$')",
"appsPossiblyUpdated": "Pokušano ažuriranje aplikacija",
@ -246,8 +244,10 @@
"backgroundUpdateReqsExplanation": "Ažuriranja u pozadini možda neće raditi za sve aplikacije.",
"backgroundUpdateLimitsExplanation": "Uspjeh ažuriranja u pozadini se može provjeriti tek kada otvorite Obtainium.",
"verifyLatestTag": "Provjerite 'posljednu' ('latest') oznaku",
"intermediateLinkRegex": "Filtrirajte da prvo posjetite 'Intemediate' link",
"intermediateLinkRegex": "Filter za 'srednju' vezu za posjetu",
"filterByLinkText": "Filtriraj linkove prema tekstu linka",
"intermediateLinkNotFound": "Intermediate link nije nađen",
"intermediateLink": "srednja karika",
"exemptFromBackgroundUpdates": "Izuzmi iz ažuriranja u pozadini (ako su uključeni)",
"bgUpdatesOnWiFiOnly": "Isključite ažuriranje u pozadini kada niste na WiFi-ju",
"autoSelectHighestVersionCode": "Automatski izaberite najveću (verziju) versionCode APK-a",
@ -256,13 +256,13 @@
"highlightTouchTargets": "Istaknite manje vidljive touch mete",
"pickExportDir": "Izaberite datoteku za izvoz",
"autoExportOnChanges": "Automatski izvezite pri promjenama",
"includeSettings": "Include settings",
"includeSettings": "Uključi postavke",
"filterVersionsByRegEx": "Filtrirajte verzije po regulatnom izrazu",
"trySelectingSuggestedVersionCode": "Probajte izabrati preloženu (verziju) versionCode APK-a",
"dontSortReleasesList": "Zadrži redosled izdanja iz API-a",
"reverseSort": "Obrni redosled",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "Uzmi prvi link",
"skipSort": "Preskoči sortiranje",
"debugMenu": "Meni za otkrivanje grešaka",
"bgTaskStarted": "Rad u pozadini pokrenut - provjerite log-ove.",
"runBgCheckNow": "Pokrenite pozadinsku provjeru ažuriranja sad",
@ -280,7 +280,27 @@
"onlyCheckInstalledOrTrackOnlyApps": "Isključivo provjerite ažuriranje za instalirane i aplikacije 'samo za praćenje'",
"supportFixedAPKURL": "Podržite fiksne APK URL-ove",
"selectX": "Izaberite {}",
"parallelDownloads": "Allow parallel downloads",
"parallelDownloads": "Dozvoli paralelna preuzimanja",
"installMethod": "Način instalacije",
"normal": "normalno",
"root": "korijen",
"shizukuBinderNotFound": "Shizuku is not running",
"useSystemFont": "Koristite sistemski font",
"systemFontError": "Greška pri učitavanju sistemskog fonta: {}",
"useVersionCodeAsOSVersion": "Koristite kod verzije aplikacije kao verziju koju je otkrio OS",
"requestHeader": "Zaglavlje zahtjeva",
"useLatestAssetDateAsReleaseDate": "Koristite najnovije otpremanje materijala kao datum izdavanja",
"defaultPseudoVersioningMethod": "Zadana metoda pseudo-verzija",
"partialAPKHash": "Djelomični APK Hash",
"APKLinkHash": "APK Link Hash",
"directAPKLink": "Direktna APK veza",
"pseudoVersionInUse": "Pseudo-verzija je u upotrebi",
"installed": "Instalirano",
"latest": "Najnoviji",
"invertRegEx": "Obrni regularni izraz",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"badDownload": "The APK could not be parsed (incompatible or partial download)",
"removeAppQuestion": {
"one": "Želite li ukloniti aplikaciju?",
"other": "Želite li ukloniti aplikacije?"

View File

@ -9,36 +9,36 @@
"placeholder": "Zástupce",
"someErrors": "Vyskytly se nějaké chyby",
"unexpectedError": "Neočekávaná chyba",
"ok": "Okay",
"ok": "Ok",
"and": "a",
"githubPATLabel": "GitHub Personal Access Token (Raises Rate Limit)",
"includePrereleases": "includepreleases",
"fallbackToOlderReleases": "Fallback to older releases",
"filterReleaseTitlesByRegEx": "Názvy vydání podle regulárního výrazu\filtr",
"githubPATLabel": "GitHub Personal Access Token (zvyšuje limit rychlosti)",
"includePrereleases": "Zahrnout předběžné verze",
"fallbackToOlderReleases": "Přechod na starší verze",
"filterReleaseTitlesByRegEx": "Filtrovat názvy verzí podle regulárního výrazu",
"invalidRegEx": "Neplatný regulární výraz",
"noDescription": "Žádný popis",
"cancel": "Zrušit",
"continue": "Pokračovat",
"requiredInBracets": "(Required)",
"requiredInBrackets": "(Požadované)",
"dropdownNoOptsError": "ERROR: DROPDOWN MUSÍ MÍT AŽ JEDNU MOŽNOST",
"color": "barva",
"githubStarredRepos": "GitHub Starred Repos",
"uname": "username",
"wrongArgNum": "Špatný počet předložených argumentů",
"colour": "Barva",
"githubStarredRepos": "GitHub označená hvězdičkou",
"uname": "Uživatelské jméno",
"wrongArgNum": "Nesprávný počet zadaných argumentů",
"xIsTrackOnly": "{} je určeno pouze pro sledování",
"source": "zdroj",
"app": "App",
"appsFromSourceAreTrackOnly": "Aplikace z tohoto zdroje jsou 'Jen sledovány'.",
"youPickedTrackOnly": "Vybrali jste možnost 'Jen sledovat'.",
"source": "Zdroj",
"app": "Aplikace",
"appsFromSourceAreTrackOnly": "Aplikace z tohoto zdroje jsou Jen sledovány.",
"youPickedTrackOnly": "Vybrali jste možnost Jen sledovat.",
"trackOnlyAppDescription": "Aplikace je sledována kvůli aktualizacím, ale Obtainium ji nebude stahovat ani instalovat.",
"cancelled": "Zrušeno",
"appAlreadyAdded": "Aplikace již přidána",
"alreadyUpToDateQuestion": "App already up to date?",
"addApp": "Přidat aplikaci",
"appSourceURL": "zdrojová adresa URL aplikace",
"appSourceURL": "Zdrojová adresa URL aplikace",
"error": "Chyba",
"add": "Přidat",
"searchSomeSourcesLabel": "Vyhledávání (pouze konkrétní zdroje)",
"searchSomeSourcesLabel": "Vyhledávání (pouze pro určité zdroje)",
"search": "Hledat",
"additionalOptsFor": "Další možnosti pro {}",
"supportedSources": "Podporované zdroje",
@ -46,241 +46,261 @@
"searchableInBrackets": "(s možností vyhledávání)",
"appsString": "Apky",
"noApps": "Žádné aplikace",
"noAppsForFilter": "žádné aplikace pro vybraný filtr",
"byX": "By {}",
"noAppsForFilter": "Žádné aplikace pro vybraný filtr",
"byX": "Od {}",
"percentProgress": "Pokrok: {}%",
"pleaseWait": "Počkejte prosím",
"updateAvailable": "Aktualizace je k dispozici",
"estimateInBracketsShort": "(approx.)",
"notInstalled": "Není nainstalováno",
"estimateInBrackets": "(přibližně)",
"selectAll": "Vybrat Vše",
"pseudoVersion": "pseudoverze",
"selectAll": "Vybrat vše",
"deselectX": "{} deselected",
"xWillBeRemovedButRemainInstalled": "{} bude odstraněn z Obtainium, ale zůstane nainstalován v zařízení.",
"removeSelectedAppsQuestion": "Odebrat vybrané aplikace?",
"removeSelectedApps": "Odebrat vybrané aplikace",
"updateX": "Aktualizovat {}",
"installX": "Instalovat {}",
"markXTrackOnlyAsUpdated": "Označit {}\n(Track-Only)\njako aktualizované",
"markXTrackOnlyAsUpdated": "Označit {}\n(Jen sledované)\njako aktualizované",
"changeX": "Změnit {}",
"installUpdateApps": "Instalovat/aktualizovat aplikace",
"installUpdateSelectedApps": "Instalovat/aktualizovat vybrané aplikace",
"markXSelectedAppsAsUpdated": "označit {} vybrané aplikace jako aktuální?",
"markXSelectedAppsAsUpdated": "Označit {} vybrané aplikace jako aktuální?",
"no": "Ne",
"yes": "ano",
"markSelectedAppsUpdated": "označit vybrané aplikace jako aktuální",
"yes": "Ano",
"markSelectedAppsUpdated": "Označit vybrané aplikace jako aktuální",
"pinToTop": "Připnout nahoru",
"unpinFromTop": "'Unpin Top'",
"unpinFromTop": "Odepnout shora",
"resetInstallStatusForSelectedAppsQuestion": "Obnovit stav instalace vybraných aplikací?",
"installStatusOfXWillBeResetExplanation": "Stav instalace vybraných aplikací bude resetován. To může být užitečné, pokud je verze aplikace zobrazená v Obtainium nesprávná z důvodu neúspěšných aktualizací nebo jiných problémů.",
"customLinkMessage": "Tyto odkazy fungují na zařízeních s nainstalovaným Obtainium",
"shareAppConfigLinks": "Sdílet konfiguraci aplikace jako odkaz HTML",
"shareSelectedAppURLs": "Sdílet adresy URL vybraných aplikací",
"resetInstallStatus": "Obnovení stavu instalace",
"more": "more",
"removeOutdatedFilter": "Odstranit filtr aplikace 'Not Current'",
"showOutdatedOnly": "Zobrazit pouze aplikace, které nejsou aktuální",
"resetInstallStatus": "Obnovit stav instalace",
"more": "Více",
"removeOutdatedFilter": "Odstranit filtr Neaktuální",
"showOutdatedOnly": "Zobrazovat pouze zastaralé aplikace",
"filter": "Filtr",
"filterActive": "Filtr *",
"filterApps": "Filtrovat aplikace",
"appName": "název aplikace",
"appName": "Název aplikace",
"author": "Autor",
"upToDateApps": "Apps with current version",
"nonInstalledApps": "Apps not installed",
"importExport": "Import/Export",
"upToDateApps": "Aktuální apky",
"nonInstalledApps": "Neinstalované apky",
"importExport": "Import Export",
"settings": "Nastavení",
"exportedTo": "Exportováno do {}",
"obtainiumExport": "Obtainium Export",
"obtainiumExport": "Export obtainia",
"invalidInput": "Neplatný vstup",
"importedX": "Importováno {}",
"obtainiumImport": "Obtainium Import",
"obtainiumImport": "Dovoz obtainia",
"importFromURLList": "Import ze seznamu URL",
"searchQuery": "Search Query",
"appURLList": "App URL List",
"line": "line",
"searchX": "Search {}",
"searchQuery": "Vyhledávací dotaz",
"appURLList": "Seznam adres aplikací",
"line": "Linka",
"searchX": "Vyhledávání {}",
"noResults": "Nebyly nalezeny žádné výsledky",
"importX": "Import {}",
"importedAppsIdDisclaimer": "Importované aplikace mohou být nesprávně zobrazeny jako \"Neinstalované\". Chcete-li to opravit, nainstalujte je znovu prostřednictvím Obtainium. To nemá vliv na data aplikací. Ovlivňuje pouze metody importu URL a třetích stran.",
"importErrors": "Import Errors",
"importedXOfYApps": "{}importováno {}aplikací.",
"followingURLsHadErrors": "U následujících adres URL došlo k chybám:",
"okay": "Okay",
"selectURL": "Select URL",
"selectURLs": "Select URLs",
"importedAppsIdDisclaimer": "Importované aplikace mohou být nesprávně zobrazeny jako \"Neinstalovány\". Chcete-li to opravit, nainstalujte je znovu prostřednictvím Obtainium. To nemá vliv na data aplikací. Ovlivňuje pouze metody importu URL a třetích stran.",
"importErrors": "Chyba importu",
"importedXOfYApps": "{}importováno z {} aplikací.",
"followingURLsHadErrors": "U následujících adres došlo k chybám:",
"selectURL": "Vybrat adresu",
"selectURLs": "Select adresy",
"pick": "Vybrat",
"theme": "Téma",
"dark": "Tmavé",
"light": "Světlé",
"followSystem": "Follow System",
"obtainium": "Obtainium",
"materialYou": "Material You",
"followSystem": "Jako systém",
"useBlackTheme": "Použít čistě černé tmavé téma",
"appSortBy": "Seřadit aplikaci podle",
"authorName": "autor/jméno",
"nameAuthor": "jméno/autor",
"asAdded": "AsAdded",
"appSortOrder": "Sort App By",
"appSortBy": "Seřadit podle",
"authorName": "Autor/Jméno",
"nameAuthor": "Jméno/Autor",
"asAdded": "Přidáno",
"appSortOrder": "Seřadit",
"ascending": "Vzestupně",
"descending": "Sestupně",
"bgUpdateCheckInterval": "Background Update Check Interval",
"bgUpdateCheckInterval": "Interval kontroly aktualizace na pozadí",
"neverManualOnly": "Nikdy - pouze ručně",
"appearance": "Vzhled",
"showWebInAppView": "Zobrazit zdrojové webové stránky v zobrazení aplikace",
"pinUpdates": "Připnout aplikace s aktualizacemi nahoře",
"updates": "Updates",
"sourceSpecific": "source specific",
"appSource": "zdroj aplikace",
"pinUpdates": "Připnout aplikace s aktualizacemi nahoru",
"updates": "Aktualizace",
"sourceSpecific": "Specifické pro zdroj",
"appSource": "Zdroj aplikace",
"noLogs": "Žádné protokoly",
"appLogs": "App Logs",
"appLogs": "Záznamy apky",
"close": "Zavřít",
"share": "Sdílet",
"appNotFound": "App not found",
"appNotFound": "Aplikace nenalezena",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Vybrat APK",
"appHasMoreThanOnePackage": "{} má více než jeden balíček:",
"deviceSupportsXArch": "Vaše zařízení podporuje architekturu CPU {}.",
"deviceSupportsFollowingArchs": "Vaše zařízení podporuje následující architektury CPU:",
"warning": "Varování",
"sourceIsXButPackageFromYPrompt": "The app source is '{}' but the release package is from '{}'. Pokračovat?",
"updatesAvailable": "dostupné aktualizace",
"sourceIsXButPackageFromYPrompt": "Zdroj aplikace je '{}', ale balíček pro vydání je z '{}'. Pokračovat?",
"updatesAvailable": "Dostupné aktualizace",
"updatesAvailableNotifDescription": "Upozorňuje uživatele, že jsou k dispozici aktualizace pro jednu nebo více aplikací sledovaných Obtainium",
"noNewUpdates": "Žádné nové aktualizace.",
"xHasAnUpdate": "{} má aktualizaci.",
"appsUpdated": "Aplikace aktualizovány",
"appsUpdatedNotifDescription": "Upozorňuje uživatele, že byly provedeny aktualizace jedné nebo více aplikací na pozadí",
"xWasUpdatedToY": "{} byl aktualizován na {}",
"errorCheckingUpdates": "Chybová kontrola aktualizací",
"errorCheckingUpdatesNotifDescription": "Oznámení zobrazené při neúspěšné kontrole aktualizací na pozadí",
"appsUpdatedNotifDescription": "Upozornit, že byly provedeny aktualizace jedné nebo více aplikací na pozadí",
"xWasUpdatedToY": "{} byla aktualizována na {}",
"errorCheckingUpdates": "Chyba kontroly aktualizací",
"errorCheckingUpdatesNotifDescription": "Zobrazit oznámení při neúspěšné kontrole aktualizací na pozadí",
"appsRemoved": "Odstraněné aplikace",
"appsRemovedNotifDescription": "Oznámení uživateli, že jedna nebo více aplikací byly odstraněny z důvodu chyb při načítání",
"appsRemovedNotifDescription": "Oznámit, že jedna nebo více aplikací bylo odstraněno z důvodu chyb při načítání",
"xWasRemovedDueToErrorY": "{} byla odstraněna z důvodu následující chyby: {}",
"completeAppInstallation": "Dokončit instalaci aplikace",
"obtainiumMustBeOpenToInstallApps": "Obtainium musí být otevřeno, aby bylo možné instalovat aplikace",
"completeAppInstallationNotifDescription": "Vyzvat uživatele k návratu do Obtainium pro dokončení instalace aplikací",
"completeAppInstallationNotifDescription": "Vyzvat k návratu do Obtainium pro dokončení instalace aplikací",
"checkingForUpdates": "Zkontrolovat aktualizace",
"checkingForUpdatesNotifDescription": "Dočasné oznámení zobrazené při kontrole aktualizací",
"pleaseAllowInstallPerm": "Povolte prosím Obtainium instalovat aplikace",
"trackOnly": "Jen sledovat",
"errorWithHttpStatusCode": "error {}",
"errorWithHttpStatusCode": "Chyba {}",
"versionCorrectionDisabled": "Oprava verze zakázána (zásuvný modul zřejmě nefunguje)",
"unknown": "Unknown",
"none": "None",
"unknown": "Neznám",
"none": "Žádný",
"never": "Nikdy",
"latestVersionX": "Nejnovější verze: {}",
"installedVersionX": "Nainstalovaná verze: {}",
"lastUpdateCheckX": "Poslední kontrola aktualizace: {}",
"remove": "Odebrat",
"yesMarkUpdated": "Ano, označit jako aktualizované",
"fdroid": "F-Droid Official",
"appIdOrName": "App ID or Name",
"appId": "App ID",
"fdroid": "Oficiální repozitář F-Droid",
"appIdOrName": "ID nebo název apky",
"appId": "ID aplikace",
"appWithIdOrNameNotFound": "Žádná aplikace s tímto ID nebo názvem nebyla nalezena",
"reposHaveMultipleApps": "Repozitáře mohou obsahovat více aplikací",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
"steam": "Steam",
"fdroidThirdPartyRepo": "F-Droid repozitář třetí strany",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"install": "Install",
"install": "Nainstalujte",
"markInstalled": "Označit jako nainstalovaný",
"update": "Aktualizovat",
"markUpdated": "Označit jako aktuální",
"additionalOptions": "Additional Options",
"disableVersionDetection": "Zakázat detekci verze",
"noVersionDetectionExplanation": "Tato volba by měla být použita pouze u aplikací, kde detekce verzí nefunguje správně.",
"downloadingX": "download {}",
"additionalOptions": "Další možnosti",
"disableVersionDetection": "Deaktivovat detekci verze",
"noVersionDetectionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně.",
"downloadingX": "Stáhnout {}",
"downloadNotifDescription": "Informuje uživatele o průběhu stahování aplikace",
"noAPKFound": "Žádná APK nebyla nalezena",
"noVersionDetection": "Žádná detekce verze",
"categorize": "Kategorizovat",
"categories": "Kategorie",
"category": "kategorie",
"category": "Kategorie",
"noCategory": "Žádná kategorie",
"noCategories": "Žádné kategorie",
"deleteCategoriesQuestion": "Smazat kategorie?",
"categoryDeleteWarning": "Všechny aplikace v odstraněných kategoriích budou nastaveny na nekategorizované.",
"addCategory": "přidat kategorii",
"label": "štítek",
"addCategory": "Přidat kategorii",
"label": "Štítek",
"language": "Jazyk",
"copiedToClipboard": "zkopírováno do schránky",
"storagePermissionDenied": "povolení k ukládání odepřeno",
"copiedToClipboard": "Zkopírováno do schránky",
"storagePermissionDenied": "Oprávnění k ukládání odepřeno",
"selectedCategorizeWarning": "Toto nahradí všechna stávající nastavení kategorií pro vybrané aplikace.",
"filterAPKsByRegEx": "Filtrovat APK podle regulárního výrazu",
"removeFromObtainium": "Odebrat z Obtainium",
"uninstallFromDevice": "Odinstalovat ze zařízení",
"onlyWorksWithNonVersionDetectApps": "Funguje pouze pro aplikace s vypnutou detekcí verze.",
"releaseDateAsVersion": "Použít datum vydání jako verzi",
"releaseDateAsVersionExplanation": "Tato možnost by měla být použita pouze u aplikací, u kterých detekce verze nefunguje správně, ale je k dispozici datum vydání.",
"releaseDateAsVersionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně, ale je k dispozici datum vydání.",
"changes": "Změny",
"releaseDate": "datum vydání",
"releaseDate": "Datum vydání",
"importFromURLsInFile": "Importovat adresy URL ze souboru (např. OPML)",
"versionDetection": "detekce verze",
"standardVersionDetection": "standardní detekce verze",
"versionDetectionExplanation": "Sloučit řetězec verze s verzí zjištěnou z OS",
"versionDetection": "Detekce verze",
"standardVersionDetection": "Standardní detekce verze",
"groupByCategory": "Seskupit podle kategorie",
"autoApkFilterByArch": "Pokud je to možné, pokuste se filtrovat soubory APK podle architektury procesoru",
"overrideSource": "Přepsat zdroj",
"dontShowAgain": "Nezobrazovat znovu",
"dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Track Only'",
"dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Jen sledované'",
"dontShowAPKOriginWarnings": "Nezobrazovat varování pro původ APK",
"moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace",
"gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)",
"about": "About",
"about": "O",
"requiresCredentialsInSettings": "{}: Vyžaduje další pověření (v nastavení)",
"checkOnStart": "Zkontrolovat jednou při spuštění",
"tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu",
"removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace",
"pickHighestVersionCode": "Automaticky vybrat APK s kódem nejvyšší verze",
"checkUpdateOnDetailPage": "Zkontrolovat aktualizace při otevření stránky s podrobnostmi aplikace",
"pickHighestVersionCode": "Automaticky vybrat nejvyšší verzi APK",
"checkUpdateOnDetailPage": "Zkontrolovat aktualizaci při otevření stránky s podrobnostmi aplikace",
"disablePageTransitions": "Zakázat animace pro přechody stránek",
"reversePageTransitions": "Obrátit animace pro přechody stránek",
"minStarCount": "Minimální počet hvězdiček",
"addInfoBelow": "Přidat tuto informaci na konec stránky",
"addInfoBelow": "Přidat tuto informaci na konec stránky.",
"addInfoInSettings": "Přidat tuto informaci do nastavení.",
"githubSourceNote": "Omezení rychlosti GitHub lze obejít pomocí klíče API.",
"gitlabSourceNote": "Extrakce GitLab APK nemusí fungovat bez klíče API",
"sortByFileNamesNotLinks": "Řadit podle názvů souborů místo celých odkazů",
"sortByLastLinkSegment": "Seřadit pouze podle poslední části odkazu",
"filterReleaseNotesByRegEx": "Filtrovat poznámky k vydání podle regulárního výrazu",
"customLinkFilterRegex": "Vlastní filtr odkazů APK podle regulárního výrazu (výchozí '.apk$')",
"appsPossiblyUpdated": "Byly provedeny pokusy o aktualizaci aplikací",
"appsPossiblyUpdatedNotifDescription": "Upozorňuje uživatele, že na pozadí mohly být provedeny aktualizace jedné nebo více aplikací",
"xWasPossiblyUpdatedToY": "{} mohlo být aktualizováno na {}.",
"enableBackgroundUpdates": "Povolit aktualizace na pozadí",
"backgroundUpdateReqsExplanation": "Aktualizace na pozadí nemusí být možné pro všechny aplikace.",
"backgroundUpdateLimitsExplanation": "Úspěšnost instalace na pozadí lze určit pouze v případě, že je otevřen Obtainium.",
"verifyLatestTag": "Ověřit značku 'latest'",
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
"intermediateLinkNotFound": "Intermediate link not found",
"exemptFromBackgroundUpdates": "Vyloučit aktualizace na pozadí (pokud jsou povoleny)",
"bgUpdatesOnWiFiOnly": "Zakázat aktualizace na pozadí, pokud není přítomna Wi-Fi",
"autoSelectHighestVersionCode": "Automatický výběr nejvyššího kódu verze APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"backgroundUpdateReqsExplanation": "Aktualizace na pozadí nemusí být možná pro všechny aplikace.",
"backgroundUpdateLimitsExplanation": "Úspěšnost instalace na pozadí lze určit pouze v případě, že je otevřeno Obtainium.",
"verifyLatestTag": "Zkontrolovat značku latest",
"intermediateLinkRegex": "Filtrovat mezipropojení, které by mělo být navštíveno jako první",
"filterByLinkText": "Filtrovat odkazy podle textu odkazu",
"intermediateLinkNotFound": "Připojený odkaz nenalezen",
"intermediateLink": "Připojený odkaz",
"exemptFromBackgroundUpdates": "Vyloučit z aktualizací na pozadí (je-li povoleno)",
"bgUpdatesOnWiFiOnly": "Deaktivovat aktualizace na pozadí, pokud není k dispozici Wi-Fi",
"autoSelectHighestVersionCode": "Automaticky vybrat nejvyšší verzi APK",
"versionExtractionRegEx": "Extrakce verze pomocí RegEx",
"matchGroupToUse": "Odpovídá použité skupině",
"highlightTouchTargets": "Zvýraznit méně zjevné cíle dotyku",
"pickExportDir": "Vybrat adresář pro export",
"autoExportOnChanges": "Automatický export při změnách",
"includeSettings": "Include settings",
"filterVersionsByRegEx": "Filtrovat verze podle regulárního výrazu",
"trySelectingSuggestedVersionCode": "Zkusit vybrat navrhovaný kód verze APK",
"dontSortReleasesList": "Retain release order from API",
"reverseSort": "Reverse sorting",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"debugMenu": "Debug Menu",
"bgTaskStarted": "Background task started - check logs.",
"runBgCheckNow": "Run Background Update Check Now",
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
"installing": "Installing",
"skipUpdateNotifications": "Skip update notifications",
"updatesAvailableNotifChannel": "dostupné aktualizace",
"appsUpdatedNotifChannel": "Aplikace aktualizovány",
"appsPossiblyUpdatedNotifChannel": "Byly provedeny pokusy o aktualizaci aplikací",
"errorCheckingUpdatesNotifChannel": "Chybová kontrola aktualizací",
"appsRemovedNotifChannel": "Odstraněné aplikace",
"downloadingXNotifChannel": "download {}",
"autoExportOnChanges": "Automatický export při změně",
"includeSettings": "Zahrnout nastavení",
"filterVersionsByRegEx": "Filtrovat verze podle regulárních výrazů",
"trySelectingSuggestedVersionCode": "Zkusit vybrat navrhovanou verzi APK",
"dontSortReleasesList": "Seřadit vydání z rozhraní API",
"reverseSort": "Obrácené třídění",
"takeFirstLink": "Použít první odkaz",
"skipSort": "Přeskočit třídění",
"debugMenu": "Nabídka ladění",
"bgTaskStarted": "Spuštěna úloha na pozadí - zkontrolujte protokoly.",
"runBgCheckNow": "Spustit kontrolu aktualizací na pozadí nyní",
"versionExtractWholePage": "Použít extrakci verze pomocí RegEx na celou stránku",
"installing": "Instaluji",
"skipUpdateNotifications": "Neposkytovat oznámení o aktualizaci",
"updatesAvailableNotifChannel": "Dostupné aktualizace",
"appsUpdatedNotifChannel": "Apky aktualizovány",
"appsPossiblyUpdatedNotifChannel": "Byly provedeny pokusy o aktualizace aplikací",
"errorCheckingUpdatesNotifChannel": "Chyba při kontrole aktualizací",
"appsRemovedNotifChannel": "Odstraněné apky",
"downloadingXNotifChannel": "Stáhnout {}",
"completeAppInstallationNotifChannel": "Dokončit instalaci aplikace",
"checkingForUpdatesNotifChannel": "Zkontrolovat aktualizace",
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"onlyCheckInstalledOrTrackOnlyApps": "Na aktualizace kontrolovat pouze nainstalované aplikace a aplikace označené Track only",
"supportFixedAPKURL": "Odhadnout novější verzi na základě prvních třiceti číslic kontrolního součtu adresy URL APK, pokud není podporována jinak",
"selectX": "Vybrat {}",
"parallelDownloads": "Povolit souběžné stahování",
"installMethod": "Metoda instalace",
"normal": "Normální",
"root": "Správce",
"shizukuBinderNotFound": "Shizuku neběží",
"useSystemFont": "Použít systémové písmo",
"systemFontError": "Chyba při načítání systémového písma: {}",
"useVersionCodeAsOSVersion": "Použít kód verze aplikace jako verzi zjištěnou OS",
"requestHeader": "Hlavička požadavku",
"useLatestAssetDateAsReleaseDate": "Použít poslední nahrané dílo jako datum vydání",
"defaultPseudoVersioningMethod": "Výchozí metoda pseudoverze",
"partialAPKHash": "Částečný hash APK",
"APKLinkHash": "Odkaz APK Hash",
"directAPKLink": "Přímý odkaz APK",
"pseudoVersionInUse": "Pseudoverze se používá",
"installed": "Instalováno",
"latest": "Nejnovější",
"invertRegEx": "Invertovat regulární výraz",
"note": "Poznámka",
"selfHostedNote": "Rozbalovací seznam \"{}\" lze použít k dosažení vlastních/obvyklých instancí libovolného zdroje.",
"badDownload": "APK nelze analyzovat (nekompatibilní nebo částečné stažení)",
"removeAppQuestion": {
"one": "Odstranit Apku?",
"other": "Odstranit Apky?"
@ -290,47 +310,47 @@
"other": "Příliš mnoho požadavků (omezená rychlost) - zkuste to znovu za {} minut"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "Při kontrole aktualizace na pozadí byla zjištěna chyba {}, opakování pokusu bude naplánováno za {} minut",
"other": "Během kontroly aktualizace na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut"
"one": "Při kontrole aktualizace na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut",
"other": "Při kontrole aktualizací na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "Při kontrole aktualizací na pozadí nalezena {}aktualizace - v případě potřeby upozorní uživatele",
"other": "Kontrola aktualizací na pozadí našla {} aktualizací - v případě potřeby upozorní uživatele"
"other": "Kontrola aktualizací na pozadí nalezla {} aktualizací - v případě potřeby upozorní uživatele"
},
"apps": {
"one": "{} App",
"other": "{} apps"
"one": "{} Apka",
"other": "{} Apky"
},
"url": {
"jedna": "{} URL",
"other": "{} URLs"
"one": "{} Adresa",
"other": "{} Adres"
},
"minute": {
"one": "{} minute",
"other": "{} minutes"
"one": "{} Minuta",
"other": "{} Minut"
},
"hour": {
"jedna": "{} hodina",
"other": "{} hours"
"one": "{} Hodina",
"other": "{} Hodin"
},
"day": {
"jedna": "{} den",
"other": "{} dny"
"one": "{} Den",
"other": "{} Dnů"
},
"clearedNLogsBeforeXAfterY": {
"one": "{n} log vymazán (před = {před}, po = {po})",
"other": "{n} logů vymazáno (před = {před}, po = {po})"
"one": "{n} Záznam vymazán (před = {before}, po = {after})",
"other": "{n} Záznamů vymazáno (před = {before}, po = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} a 1 další aplikace mají aktualizace.",
"other": "{} a {} další aplikace mají aktualizace."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} a {} další aplikace mají aktualizace.",
"další": "{} a {} další aplikace byly aktualizovány."
"one": "{} a 1 další aplikace mají aktualizace.",
"other": "{} a {} další aplikace byly aktualizovány."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} a {} další aplikace byly možná aktualizovány",
"other": "{} a {} další aplikace mohly být aktualizovány."
"one": "{} a 1 další aplikace možno aktualizovat",
"other": "{} a {} další aplikace mohou být aktualizovány."
}
}

View File

@ -9,7 +9,7 @@
"placeholder": "Platzhalter",
"someErrors": "Es traten einige Fehler auf",
"unexpectedError": "Unerwarteter Fehler",
"ok": "Okay",
"ok": "OK",
"and": "und",
"githubPATLabel": "GitHub Personal Access Token (Erhöht das Ratenlimit)",
"includePrereleases": "Vorabversionen einbeziehen",
@ -30,7 +30,7 @@
"app": "App",
"appsFromSourceAreTrackOnly": "Apps aus dieser Quelle sind nur zum Nachverfolgen.",
"youPickedTrackOnly": "Sie haben die Option „Nur Nachverfolgen“ gewählt.",
"trackOnlyAppDescription": "Die App wird auf Updates überwacht, aber Obtainium wird sie nicht herunterladen oder installieren.",
"trackOnlyAppDescription": "Die App wird auf Aktualisierungen überwacht, aber Obtainium wird sie nicht herunterladen oder installieren.",
"cancelled": "Abgebrochen",
"appAlreadyAdded": "App bereits hinzugefügt",
"alreadyUpToDateQuestion": "App bereits auf dem neuesten Stand?",
@ -51,9 +51,8 @@
"percentProgress": "Fortschritt: {}%",
"pleaseWait": "Bitte warten",
"updateAvailable": "Aktualisierung verfügbar",
"estimateInBracketsShort": "(ca.)",
"notInstalled": "Nicht installiert",
"estimateInBrackets": "(Ungefähr)",
"pseudoVersion": "Pseudoversion",
"selectAll": "Alle auswählen",
"deselectX": "{} abgewählt",
"xWillBeRemovedButRemainInstalled": "{} wird aus Obtainium entfernt, bleibt aber auf dem Gerät installiert.",
@ -73,6 +72,8 @@
"unpinFromTop": "„Oben anheften“ aufheben",
"resetInstallStatusForSelectedAppsQuestion": "Installationsstatus für ausgewählte Apps zurücksetzen?",
"installStatusOfXWillBeResetExplanation": "Der Installationsstatus der ausgewählten Apps wird zurückgesetzt. Dies kann hilfreich sein, wenn die in Obtainium angezeigte App-Version aufgrund fehlgeschlagener Aktualisierungen oder anderer Probleme falsch ist.",
"customLinkMessage": "Diese Links funktionieren auf Geräten, wo Obtainium installiert ist",
"shareAppConfigLinks": "Teile die Appkonfiguration als HTML-Link",
"shareSelectedAppURLs": "Ausgewählte App-URLs teilen",
"resetInstallStatus": "Installationsstatus zurücksetzen",
"more": "Mehr",
@ -88,22 +89,21 @@
"importExport": "Import/Export",
"settings": "Einstellungen",
"exportedTo": "Exportiert zu {}",
"obtainiumExport": "Obtainium Export",
"obtainiumExport": "Obtainium-Export",
"invalidInput": "Ungültige Eingabe",
"importedX": "Importiert {}",
"obtainiumImport": "Obtainium Import",
"obtainiumImport": "Obtainium-Import",
"importFromURLList": "Importieren aus URL-Liste",
"searchQuery": "Suchanfrage",
"appURLList": "App URL-Liste",
"line": "Linie",
"searchX": "Suche {}",
"noResults": "Keine Ergebnisse gefunden",
"importX": "Import {}",
"importX": "Importieren {}",
"importedAppsIdDisclaimer": "Importierte Apps werden möglicherweise fälschlicherweise als \"Nicht installiert\" angezeigt. Um dies zu beheben, installieren Sie sie erneut über Obtainium. Dies hat keine Auswirkungen auf App-Daten. Es betrifft nur URL- und Drittanbieter-Importmethoden.",
"importErrors": "Importfehler",
"importedXOfYApps": "{} von {} Apps importiert.",
"followingURLsHadErrors": "Bei folgenden URLs traten Fehler auf:",
"okay": "Okay",
"selectURL": "URL auswählen",
"selectURLs": "URLs auswählen",
"pick": "Auswählen",
@ -111,8 +111,6 @@
"dark": "Dunkel",
"light": "Hell",
"followSystem": "System folgen",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Verwende Pure Black Dark Theme",
"appSortBy": "App sortieren nach",
"authorName": "Autor/Name",
@ -134,7 +132,7 @@
"close": "Schließen",
"share": "Teilen",
"appNotFound": "App nicht gefunden",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"obtainiumExportHyphenatedLowercase": "Obtainium-Export",
"pickAnAPK": "APK auswählen",
"appHasMoreThanOnePackage": "{} verfügt über mehr als ein Paket:",
"deviceSupportsXArch": "Ihr Gerät unterstützt die CPU-Architektur {}.",
@ -176,9 +174,8 @@
"appWithIdOrNameNotFound": "Es wurde keine App mit dieser ID oder diesem Namen gefunden",
"reposHaveMultipleApps": "Repos können mehrere Apps enthalten",
"fdroidThirdPartyRepo": "F-Droid Drittparteienrepo",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamChat": "Steam-Chat",
"install": "Installieren",
"markInstalled": "Als Installiert markieren",
"update": "Aktualisieren",
@ -212,6 +209,7 @@
"changes": "Änderungen",
"releaseDate": "Veröffentlichungsdatum",
"importFromURLsInFile": "Importieren von URLs aus Datei (z. B. OPML)",
"versionDetectionExplanation": "Abgleich der Versionsnummer mit der vom Betriebssystem erkannten Version",
"versionDetection": "Versionserkennung",
"standardVersionDetection": "Standardversionserkennung",
"groupByCategory": "Nach Kategorie gruppieren",
@ -228,7 +226,7 @@
"tryInferAppIdFromCode": "Versuche, die App-ID aus dem Quellcode zu ermitteln",
"removeOnExternalUninstall": "Automatisches Entfernen von extern deinstallierten Apps",
"pickHighestVersionCode": "Automatische Auswahl des APK mit höchstem Versionscode",
"checkUpdateOnDetailPage": "Nach Updates suchen, wenn eine App-Detailseite geöffnet wird",
"checkUpdateOnDetailPage": "Nach Aktualisierungen suchen, wenn eine App-Detailseite geöffnet wird",
"disablePageTransitions": "Animationen für Seitenübergänge deaktivieren",
"reversePageTransitions": "Umgekehrte Animationen für Seitenübergänge",
"minStarCount": "Minimale Anzahl von Sternen",
@ -236,18 +234,20 @@
"addInfoInSettings": "Fügen Sie diese Info in den Einstellungen hinzu.",
"githubSourceNote": "Die GitHub-Ratenbegrenzung kann mit einem API-Schlüssel umgangen werden.",
"gitlabSourceNote": "GitLab APK-Extraktion funktioniert möglicherweise nicht ohne API-Schlüssel",
"sortByFileNamesNotLinks": "Sortiere nach Dateinamen, anstelle von ganzen Links",
"sortByLastLinkSegment": "Sortiere nur nach dem letzten Teil des Links",
"filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern",
"customLinkFilterRegex": "Benutzerdefinierter APK Link Filter nach Regulärem Ausdruck (Standard '.apk$')",
"appsPossiblyUpdated": "App Aktualisierungen wurden versucht",
"appsPossiblyUpdatedNotifDescription": "Benachrichtigt den Benutzer, dass Updates für eine oder mehrere Apps möglicherweise im Hintergrund durchgeführt wurden",
"appsPossiblyUpdatedNotifDescription": "Benachrichtigt den Benutzer, dass Aktualisierungen für eine oder mehrere Apps möglicherweise im Hintergrund durchgeführt wurden",
"xWasPossiblyUpdatedToY": "{} wurde möglicherweise aktualisiert auf {}.",
"enableBackgroundUpdates": "Aktiviere Hintergrundaktualisierungen",
"backgroundUpdateReqsExplanation": "Die Hintergrundaktualisierung ist möglicherweise nicht für alle Apps möglich.",
"backgroundUpdateLimitsExplanation": "Der Erfolg einer Hintergrundinstallation kann nur festgestellt werden, wenn Obtainium geöffnet wird.",
"verifyLatestTag": "Überprüfe das „latest“ Tag",
"intermediateLinkRegex": "Filter für einen „Zwischen“-Link, der zuerst besucht werden soll",
"intermediateLinkNotFound": "„Zwischen“link nicht gefunden",
"filterByLinkText": "Filtere Links durch Linktext",
"intermediateLinkNotFound": "„Zwischen“-Link nicht gefunden",
"intermediateLink": "„Zwischen“-Link",
"exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)",
"bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist",
"autoSelectHighestVersionCode": "Automatisch höchste APK-Version auswählen",
@ -256,19 +256,19 @@
"highlightTouchTargets": "Weniger offensichtliche Touch-Ziele hervorheben",
"pickExportDir": "Export-Verzeichnis wählen",
"autoExportOnChanges": "Automatischer Export bei Änderung(en)",
"includeSettings": "Include settings",
"includeSettings": "Einstellungen einbeziehen",
"filterVersionsByRegEx": "Versionen nach regulären Ausdrücken filtern",
"trySelectingSuggestedVersionCode": "Versuchen, den vorgeschlagenen APK-Versionscode auszuwählen",
"dontSortReleasesList": "Freigaberelease von der API ordern",
"reverseSort": "Umgekehrtes Sortieren",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "Verwende den ersten Link",
"skipSort": "Überspringe Sortieren",
"debugMenu": "Debug-Menü",
"bgTaskStarted": "Hintergrundaufgabe gestartet Logs prüfen.",
"runBgCheckNow": "Hintergrundaktualisierungsprüfung jetzt durchführen",
"versionExtractWholePage": "Versions-Extraktion per RegEx auf die gesamte Seite anwenden",
"installing": "Installiere",
"skipUpdateNotifications": "Keine Benachrichtigung zu App-Updates geben",
"skipUpdateNotifications": "Keine Benachrichtigung zu App-Aktualisierungen geben",
"updatesAvailableNotifChannel": "Aktualisierungen verfügbar",
"appsUpdatedNotifChannel": "Apps aktualisiert",
"appsPossiblyUpdatedNotifChannel": "App Aktualisierungen wurden versucht",
@ -280,14 +280,34 @@
"onlyCheckInstalledOrTrackOnlyApps": "Überprüfe nur installierte und mit „nur Nachverfolgen“ markierte Apps auf Aktualisierungen",
"supportFixedAPKURL": "neuere Version anhand der ersten dreißig Zahlen der Checksumme der APK URL erraten, wenn anderweitig nicht unterstützt",
"selectX": "Wähle {}",
"parallelDownloads": "Allow parallel downloads",
"parallelDownloads": "Erlaube parallele Downloads",
"installMethod": "Installationsmethode",
"normal": "Normal",
"root": "Root",
"shizukuBinderNotFound": "Kompatibler Shizukudienst wurde nicht gefunden",
"useSystemFont": "Verwende die Systemschriftart",
"systemFontError": "Fehler beim Laden der Systemschriftart: {}",
"useVersionCodeAsOSVersion": "Verwende die Appversion als erkannte Version vom Betriebssystem",
"requestHeader": "Request Header",
"useLatestAssetDateAsReleaseDate": "Den letzten Asset-Upload als Veröffentlichungsdatum verwenden",
"defaultPseudoVersioningMethod": "Standardmäßiges Verfahren zur Pseudo-Versionierung",
"partialAPKHash": "partieller APK-Hash",
"APKLinkHash": "APK-Link-Hash",
"directAPKLink": "Direkter APK-Link",
"pseudoVersionInUse": "Es werden Pseudoversionen verwendet",
"installed": "Installiert",
"latest": "Neueste Version",
"invertRegEx": "Regulären Ausdruck invertieren",
"note": "Hinweis",
"selfHostedNote": "Das „{}“-Dropdown-Menü kann verwendet werden, um selbst gehostete/angepasste Instanzen einer beliebigen Quelle zu erreichen.",
"badDownload": "Die APK konnte nicht geparst werden (inkompatibler oder teilweiser Download)",
"removeAppQuestion": {
"one": "App entfernen?",
"other": "Apps entfernen?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Zu viele Anfragen (Rate begrenzt) versuchen Sie es in {} Minute erneut",
"other": "Zu viele Anfragen (Rate begrenzt) versuchen Sie es in {} Minuten erneut"
"one": "Zu viele Anfragen (Rate begrenzt) versuche es in {} Minute erneut",
"other": "Zu viele Anfragen (Rate begrenzt) versuche es in {} Minuten erneut"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "Bei der Aktualisierungsprüfung im Hintergrund wurde ein {} festgestellt, eine erneute Prüfung wird in {} Minute geplant",
@ -298,12 +318,12 @@
"other": "Die Hintergrundaktualisierungsprüfung fand {} Aktualisierungen benachrichtigt den Benutzer, falls erforderlich"
},
"apps": {
"one": "{} App",
"other": "{} Apps"
"eine": "{} App",
"andere": "{} Apps"
},
"url": {
"one": "{} URL",
"other": "{} URLs"
"eine": "{} URL",
"andere": "{} URLs"
},
"minute": {
"one": "{} Minute",

View File

@ -51,9 +51,8 @@
"percentProgress": "Progress: {}%",
"pleaseWait": "Please Wait",
"updateAvailable": "Update Available",
"estimateInBracketsShort": "(Est.)",
"notInstalled": "Not Installed",
"estimateInBrackets": "(Estimate)",
"pseudoVersion": "pseudo-version",
"selectAll": "Select All",
"deselectX": "Deselect {}",
"xWillBeRemovedButRemainInstalled": "{} will be removed from Obtainium but remain installed on device.",
@ -73,13 +72,14 @@
"unpinFromTop": "Unpin from top",
"resetInstallStatusForSelectedAppsQuestion": "Reset Install Status for Selected Apps?",
"installStatusOfXWillBeResetExplanation": "The install status of any selected Apps will be reset.\n\nThis can help when the App version shown in Obtainium is incorrect due to failed updates or other issues.",
"customLinkMessage": "These links work on devices with Obtainium installed",
"shareAppConfigLinks": "Share app configuration as HTML link",
"shareSelectedAppURLs": "Share Selected App URLs",
"resetInstallStatus": "Reset Install Status",
"more": "More",
"removeOutdatedFilter": "Remove Out-of-Date App Filter",
"showOutdatedOnly": "Show Out-of-Date Apps Only",
"filter": "Filter",
"filterActive": "Filter *",
"filterApps": "Filter Apps",
"appName": "App Name",
"author": "Author",
@ -103,7 +103,6 @@
"importErrors": "Import Errors",
"importedXOfYApps": "{} of {} Apps imported.",
"followingURLsHadErrors": "The following URLs had errors:",
"okay": "Okay",
"selectURL": "Select URL",
"selectURLs": "Select URLs",
"pick": "Pick",
@ -111,8 +110,6 @@
"dark": "Dark",
"light": "Light",
"followSystem": "Follow System",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme",
"appSortBy": "App Sort By",
"authorName": "Author/Name",
@ -165,8 +162,8 @@
"unknown": "Unknown",
"none": "None",
"never": "Never",
"latestVersionX": "Latest Version: {}",
"installedVersionX": "Installed Version: {}",
"latestVersionX": "Latest: {}",
"installedVersionX": "Installed: {}",
"lastUpdateCheckX": "Last Update Check: {}",
"remove": "Remove",
"yesMarkUpdated": "Yes, Mark as Updated",
@ -176,7 +173,6 @@
"appWithIdOrNameNotFound": "No App was found with that ID or Name",
"reposHaveMultipleApps": "Repos may contain multiple Apps",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"install": "Install",
@ -207,11 +203,12 @@
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"releaseDateAsVersion": "Use Release Date as Version",
"releaseDateAsVersion": "Use release date as version string",
"releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.",
"changes": "Changes",
"releaseDate": "Release Date",
"importFromURLsInFile": "Import from URLs in File (like OPML)",
"versionDetectionExplanation": "Reconcile version string with version detected from OS",
"versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection",
"groupByCategory": "Group by Category",
@ -236,7 +233,7 @@
"addInfoInSettings": "Add this info in the Settings.",
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"sortByLastLinkSegment": "Sort by only the last segment of the link",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
@ -246,13 +243,15 @@
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
"filterByLinkText": "Filter links by link text",
"intermediateLinkNotFound": "Intermediate link not found",
"intermediateLink": "Intermediate link",
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use for Version Extraction Regex",
"versionExtractionRegEx": "Version String Extraction RegEx",
"matchGroupToUse": "Match Group to Use for Version String Extraction Regex",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
@ -266,7 +265,7 @@
"debugMenu": "Debug Menu",
"bgTaskStarted": "Background task started - check logs.",
"runBgCheckNow": "Run Background Update Check Now",
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
"versionExtractWholePage": "Apply version string extraction Regex to entire page",
"installing": "Installing",
"skipUpdateNotifications": "Skip update notifications",
"updatesAvailableNotifChannel": "Updates Available",
@ -283,9 +282,24 @@
"parallelDownloads": "Allow parallel downloads",
"installMethod": "Installation method",
"normal": "Normal",
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku is not running",
"shizukuBinderNotFound": "Сompatible Shizuku service wasn't found",
"useSystemFont": "Use the system font",
"systemFontError": "Error loading the system font: {}",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"useLatestAssetDateAsReleaseDate": "Use latest asset upload as release date",
"defaultPseudoVersioningMethod": "Default Pseudo-Versioning Method",
"partialAPKHash": "Partial APK Hash",
"APKLinkHash": "APK Link Hash",
"directAPKLink": "Direct APK Link",
"pseudoVersionInUse": "A Pseudo-Version is in Use",
"installed": "Installed",
"latest": "Latest",
"invertRegEx": "Invert regular expression",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"badDownload": "The APK could not be parsed (incompatible or partial download)",
"removeAppQuestion": {
"one": "Remove App?",
"other": "Remove Apps?"

View File

@ -1,20 +1,20 @@
{
"invalidURLForSource": "URL de la aplicación {} no válida",
"invalidURLForSource": "URL de la aplicación {} no es válida",
"noReleaseFound": "No se ha podido encontrar una versión válida",
"noVersionFound": "No se ha podido determinar la versión de la publicación",
"noVersionFound": "No se ha podido determinar la versión",
"urlMatchesNoSource": "La URL no coincide con ninguna fuente conocida",
"cantInstallOlderVersion": "No se puede instalar una versión previa de la aplicación",
"appIdMismatch": "La ID del paquete descargado no coincide con la ID de la aplicación instalada",
"functionNotImplemented": "Esta clase no ha implementado esta función",
"placeholder": "Espacio reservado",
"someErrors": "Han ocurrido algunos errores",
"unexpectedError": "Error Inesperado",
"ok": "Correcto",
"unexpectedError": "Error inesperado",
"ok": "OK",
"and": "y",
"githubPATLabel": "Token Github de Acceso Personal\n(Reduce tiempos de espera)",
"githubPATLabel": "Token de acceso personal a GitHub\n(reduce tiempos de espera)",
"includePrereleases": "Incluir versiones preliminares",
"fallbackToOlderReleases": "Retroceder a versiones previas",
"filterReleaseTitlesByRegEx": "Filtrar Títulos de Versiones",
"filterReleaseTitlesByRegEx": "Filtrar por título de versión",
"invalidRegEx": "Expresión inválida",
"noDescription": "Sin descripción",
"cancel": "Cancelar",
@ -22,38 +22,37 @@
"requiredInBrackets": "(Requerido)",
"dropdownNoOptsError": "ERROR: EL DESPLEGABLE DEBE TENER AL MENOS UNA OPCIÓN",
"colour": "Color",
"githubStarredRepos": "Repositorios favoritos GitHub",
"githubStarredRepos": "Repositorios favoritos en GitHub",
"uname": "Nombre de usuario",
"wrongArgNum": "Número de argumentos provistos inválido",
"xIsTrackOnly": "{} es de 'Solo Seguimiento'",
"xIsTrackOnly": "{} es de 'lo seguimiento'",
"source": "Origen",
"app": "Aplicación",
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'Solo Seguimiento'.",
"youPickedTrackOnly": "Debes seleccionar la opción de 'Solo Seguimiento'.",
"trackOnlyAppDescription": "Se hará el seguimiento de actualizaciones para la aplicación, pero Obtainium no será capaz de descargarla o actalizarla.",
"appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'solo seguimiento'.",
"youPickedTrackOnly": "Debe seleccionar la opción de 'solo seguimiento'.",
"trackOnlyAppDescription": "Se hará el seguimiento de actualizaciones para la aplicación, pero Obtainium no será capaz de descargar o actualizarla.",
"cancelled": "Cancelado",
"appAlreadyAdded": "Aplicación ya añadida",
"alreadyUpToDateQuestion": "¿Aplicación ya actualizada?",
"appAlreadyAdded": "Aplicación añadida anteriormente",
"alreadyUpToDateQuestion": "¿Aplicación actualizada previamente?",
"addApp": "Añadir Aplicación",
"appSourceURL": "URL de Origen de la Aplicación",
"appSourceURL": "URL de origen de la aplicación",
"error": "Error",
"add": "Añadir",
"searchSomeSourcesLabel": "Buscar (solo algunas fuentes)",
"search": "Buscar",
"additionalOptsFor": "Opciones Adicionales para {}",
"supportedSources": "Fuentes Soportadas",
"trackOnlyInBrackets": "(Solo Seguimiento)",
"trackOnlyInBrackets": "(Solo seguimiento)",
"searchableInBrackets": "(soporta búsqueda)",
"appsString": "Aplicaciones",
"noApps": "Sin Aplicaciones",
"noAppsForFilter": "Sin Aplicaciones para Filtrar",
"byX": "Por {}",
"noAppsForFilter": "Sin aplicaciones para filtrar",
"byX": "por: {}",
"percentProgress": "Progreso: {}%",
"pleaseWait": "Por favor, espere",
"updateAvailable": "Actualización Disponible",
"estimateInBracketsShort": "(Aprox.)",
"notInstalled": "No Instalado",
"estimateInBrackets": "(Aproximado)",
"pseudoVersion": "pseudoversión",
"selectAll": "Seleccionar Todo",
"deselectX": "Deseleccionar {}",
"xWillBeRemovedButRemainInstalled": "{} será eliminada de Obtainium pero continuará instalada en el dispositivo.",
@ -61,27 +60,29 @@
"removeSelectedApps": "Eliminar Aplicaciones Seleccionadas",
"updateX": "Actualizar {}",
"installX": "Instalar {}",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo Seguimiento)\ncomo Actualizada",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Solo seguimiento)\ncomo actualizada",
"changeX": "Cambiar {}",
"installUpdateApps": "Instalar/Actualizar Aplicaciones",
"installUpdateSelectedApps": "Instalar/Actualizar Aplicaciones Seleccionadas",
"markXSelectedAppsAsUpdated": "¿Marcar {} Aplicaciones Seleccionadas como Actualizadas?",
"installUpdateApps": "Instalar/Actualizar aplicaciones",
"installUpdateSelectedApps": "Instalar/Actualizar aplicaciones seleccionadas",
"markXSelectedAppsAsUpdated": "¿Marcar {} aplicaciones seleccionadas como actualizadas?",
"no": "No",
"yes": "Sí",
"markSelectedAppsUpdated": "Marcar Aplicaciones Seleccionadas como Actualizadas",
"markSelectedAppsUpdated": "Marcar aplicaciones seleccionadas como actualizadas",
"pinToTop": "Fijar arriba",
"unpinFromTop": "Desfijar de arriba",
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar Estado de Instalación para las Aplicaciones Seleccionadas?",
"installStatusOfXWillBeResetExplanation": "El estado de instalación de las aplicaciones seleccionadas será restaurado.\n\nEsto puede ser de útil cuando la versión de la aplicación mostrada en Obtainium es incorrecta por actualizaciones fallidas u otros motivos.",
"shareSelectedAppURLs": "Compartir URLs de las Aplicaciones Seleccionadas",
"resetInstallStatus": "Restaurar Estado de Instalación",
"resetInstallStatusForSelectedAppsQuestion": "¿Restuarar estado de instalación para las aplicaciones seleccionadas?",
"installStatusOfXWillBeResetExplanation": "Se restaurará el estado de instalación de las aplicaciones seleccionadas.\n\nEsto puede ser de útil cuando la versión de la aplicación mostrada en Obtainium es incorrecta por actualizaciones fallidas u otros motivos.",
"customLinkMessage": "Estos enlaces funcionan en dispositivos con Obtainium instalado",
"shareAppConfigLinks": "Compartir la configuración de la aplicación como enlace HTML",
"shareSelectedAppURLs": "Compartir URLs de las aplicaciones seleccionadas",
"resetInstallStatus": "Restaurar estado de instalación",
"more": "Más",
"removeOutdatedFilter": "Elimiar Filtro de Aplicaciones Desactualizado",
"showOutdatedOnly": "Mostrar solo Aplicaciones Desactualizadas",
"removeOutdatedFilter": "Elimiar filtro de aplicaciones desactualizado",
"showOutdatedOnly": "Mostrar solo aplicaciones desactualizadas",
"filter": "Filtrar",
"filterActive": "Filtrar *",
"filterApps": "Filtrar Actualizaciones",
"appName": "Nombre de la Aplicación",
"appName": "Nombre de la aplicación",
"author": "Autor",
"upToDateApps": "Aplicaciones Actualizadas",
"nonInstalledApps": "Aplicaciones No Instaladas",
@ -102,18 +103,15 @@
"importedAppsIdDisclaimer": "Las aplicaciones importadas podrían mostrarse incorrectamente como \"No Instalada\".\nPara solucionarlo, reinstálalas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a las URLs y a los métodos de importación mediante terceros.",
"importErrors": "Errores de Importación",
"importedXOfYApps": "{} de {} Aplicaciones importadas.",
"followingURLsHadErrors": "Las siguientes URLs tuvieron problemas:",
"okay": "Correcto",
"followingURLsHadErrors": "Las siguientes URLs han tenido problemas:",
"selectURL": "Seleccionar URL",
"selectURLs": "Seleccionar URLs",
"pick": "Escoger",
"theme": "Tema",
"dark": "Oscuro",
"light": "Claro",
"followSystem": "Seguir al Sistema",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Usar negros puros en tema oscuro",
"followSystem": "Seguir al sistema",
"useBlackTheme": "Negro puro en tema Oscuro",
"appSortBy": "Ordenar Apps Por",
"authorName": "Autor/Nombre",
"nameAuthor": "Nombre/Autor",
@ -121,166 +119,188 @@
"appSortOrder": "Orden de Clasificación",
"ascending": "Ascendente",
"descending": "Descendente",
"bgUpdateCheckInterval": "Intervalo de Comprobación de Actualizaciones en Segundo Plano",
"neverManualOnly": "Nunca - Solo Manual",
"bgUpdateCheckInterval": "Comprobación actualizaciones en segundo plano",
"neverManualOnly": "Nunca, solo manual",
"appearance": "Apariencia",
"showWebInAppView": "Mostrar Vista de la Web de Origen",
"pinUpdates": "Fijar Actualizaciones en la Parte Superior de la Vista de Aplicaciones",
"showWebInAppView": "Mostrar vista de la web de origen",
"pinUpdates": "Fijar actualizaciones al principio",
"updates": "Actualizaciones",
"sourceSpecific": "Fuente Específica",
"appSource": "Fuente de la Aplicación",
"appSource": "Obtainium en GitHub",
"noLogs": "Sin Logs",
"appLogs": "Logs de la Aplicación",
"appLogs": "Logs",
"close": "Cerrar",
"share": "Compartir",
"appNotFound": "Aplicación no encontrada",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Selecciona una APK",
"obtainiumExportHyphenatedLowercase": "obtainium-exportación",
"pickAnAPK": "Seleccione una APK",
"appHasMoreThanOnePackage": "{} tiene más de un paquete:",
"deviceSupportsXArch": "Tu dispositivo soporta las siguientes arquitecturas de procesador: {}.",
"deviceSupportsFollowingArchs": "Tu dispositivo soporta las siguientes arquitecturas de procesador:",
"deviceSupportsXArch": "Su dispositivo soporta las siguientes arquitecturas de procesador: {}.",
"deviceSupportsFollowingArchs": "Su dispositivo soporta las siguientes arquitecturas de procesador:",
"warning": "Aviso",
"sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es '{}' pero el paquete de la actualización viene de '{}'. ¿Desea continuar?",
"updatesAvailable": "Actualizaciones Disponibles",
"updatesAvailableNotifDescription": "Notifica al usuario de que hay actualizaciones para una o más aplicaciones monitorizadas por Obtainium",
"updatesAvailableNotifDescription": "Notifica al usuario de que hay actualizaciones para una o más aplicaciones monitoreadas por Obtainium",
"noNewUpdates": "No hay nuevas actualizaciones.",
"xHasAnUpdate": "{} tiene una actualización.",
"appsUpdated": "Aplicaciones Actualizadas",
"appsUpdated": "Aplicaciones actualizadas",
"appsUpdatedNotifDescription": "Notifica al usuario de que una o más aplicaciones han sido actualizadas en segundo plano",
"xWasUpdatedToY": "{} ha sido actualizada a {}.",
"errorCheckingUpdates": "Error Buscando Actualizaciones",
"errorCheckingUpdates": "Error buscando ectualizaciones",
"errorCheckingUpdatesNotifDescription": "Una notificación que muestra cuándo la comprobación de actualizaciones en segundo plano falla",
"appsRemoved": "Aplicaciones Eliminadas",
"appsRemoved": "Aplicaciones eliminadas",
"appsRemovedNotifDescription": "Notifica al usuario que una o más aplicaciones fueron eliminadas por problemas al cargarlas",
"xWasRemovedDueToErrorY": "{} ha sido eliminada por: {}",
"completeAppInstallation": "Instalación Completa de la Aplicación",
"completeAppInstallation": "Instalación completa de la aplicación",
"obtainiumMustBeOpenToInstallApps": "Obtainium debe estar abierto para instalar aplicaciones",
"completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para terminar de instalar una aplicación",
"checkingForUpdates": "Buscando Actualizaciones",
"checkingForUpdates": "Buscando actualizaciones...",
"checkingForUpdatesNotifDescription": "Notificación temporal que aparece al buscar actualizaciones",
"pleaseAllowInstallPerm": "Por favor, permite a Obtainium instalar aplicaciones",
"trackOnly": "Solo Seguimiento",
"pleaseAllowInstallPerm": "Por favor, permita que Obtainium instale aplicaciones",
"trackOnly": "Solo para seguimiento",
"errorWithHttpStatusCode": "Error {}",
"versionCorrectionDisabled": "Corrección de versiones desactivada (el plugin parece no funcionar)",
"unknown": "Desconocido",
"none": "Ninguno",
"never": "Nunca",
"latestVersionX": "Última Versión: {}",
"installedVersionX": "Versión Instalada: {}",
"lastUpdateCheckX": "Última Comprobación: {}",
"latestVersionX": "Última versión: {}",
"installedVersionX": "Versión instalada: {}",
"lastUpdateCheckX": "Última comprobación: {}",
"remove": "Eliminar",
"yesMarkUpdated": "Sí, Marcar como Actualizada",
"yesMarkUpdated": "Sí, marcar como actualizada",
"fdroid": "Repositorio oficial F-Droid",
"appIdOrName": "ID o Nombre de la Aplicación",
"appId": "ID de la Aplicación",
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre",
"reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones",
"fdroidThirdPartyRepo": "Rpositorios de terceros F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"fdroidThirdPartyRepo": "Repositorio de tercera parte F-Droid",
"steamMobile": "Móvil de vapor",
"steamChat": "Chat de vapor",
"install": "Instalar",
"markInstalled": "Marcar como Instalada",
"markInstalled": "Marcar como instalada",
"update": "Actualizar",
"markUpdated": "Marcar como Actualizada",
"additionalOptions": "Opciones Adicionales",
"disableVersionDetection": "Descativar Detección de Versiones",
"noVersionDetectionExplanation": "Esta opción solo se debe usar en aplicaciones en las que la deteción de versiones pueda no funcionar correctamente.",
"markUpdated": "Marcar como actualizada",
"additionalOptions": "Opciones adicionales",
"disableVersionDetection": "Desactivar la detección de versiones",
"noVersionDetectionExplanation": "Esta opción solo se debe usar en aplicaciones en las que la deteción de versiones pueda que no funcionar correctamente.",
"downloadingX": "Descargando {}",
"downloadNotifDescription": "Notifica al usuario del progreso de descarga de una aplicación",
"noAPKFound": "APK no encontrada",
"noAPKFound": "No se encontró el paquete de instalación APK",
"noVersionDetection": "Sin detección de versiones",
"categorize": "Catogorizar",
"categories": "Categorías",
"category": "Categoría",
"noCategory": "Sin Categoría",
"noCategories": "Sin Categorías",
"deleteCategoriesQuestion": "¿Eliminar Categorías?",
"categoryDeleteWarning": "Todas las aplicaciones en las categorías eliminadas serán marcadas como 'Sin Categoría'.",
"addCategory": "Añadir Categoría",
"noCategory": "Sin categoría",
"noCategories": "Sin categorías",
"deleteCategoriesQuestion": "¿Eliminar categorías?",
"categoryDeleteWarning": "Todas las aplicaciones en las categorías eliminadas serán marcadas como 'Sin categoría'.",
"addCategory": "Añadir categoría",
"label": "Nombre",
"language": "Idioma",
"copiedToClipboard": "Copiado al Portapapeles",
"storagePermissionDenied": "Permiso de Almacenamiento rechazado",
"copiedToClipboard": "Copiado al portapapeles",
"storagePermissionDenied": "Permiso de almacenamiento rechazado",
"selectedCategorizeWarning": "Esto reemplazará cualquier ajuste de categoría para las aplicaciones seleccionadas.",
"filterAPKsByRegEx": "Filtrar por APKs",
"removeFromObtainium": "Eliminar de Obtainium",
"uninstallFromDevice": "Desinstalar del Dispositivo",
"uninstallFromDevice": "Desinstalar del dispositivo",
"onlyWorksWithNonVersionDetectApps": "Solo funciona para aplicaciones con la detección de versiones desactivada.",
"releaseDateAsVersion": "Usar Fecha de Publicación como Versión",
"releaseDateAsVersion": "Por fecha de publicación",
"releaseDateAsVersionExplanation": "Esta opción solo se debería usar con aplicaciones en las que la detección de versiones no funciona pero hay disponible una fecha de publicación.",
"changes": "Cambios",
"releaseDate": "Fecha de Publicación",
"releaseDate": "Fecha de publicación",
"importFromURLsInFile": "Importar URLs desde archivo (como OPML)",
"versionDetection": "Detección de Versiones",
"standardVersionDetection": "Detección de versiones estándar",
"groupByCategory": "Agrupar por Categoría",
"autoApkFilterByArch": "Filtrar las APKs mediante arquitecturas de procesador, si es posible",
"versionDetectionExplanation": "Conciliar la cadena de versión con la versión detectada desde el sistema operativo",
"versionDetection": "Detección de versiones",
"standardVersionDetection": "Por versión",
"groupByCategory": "Agrupar por categoría",
"autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador (si es posible)",
"overrideSource": "Sobrescribir Fuente",
"dontShowAgain": "No mostrar de nuevo",
"dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'",
"dontShowAPKOriginWarnings": "No mostrar avisos de las fuentes de las APKs",
"moveNonInstalledAppsToBottom": "Mover Apps no instaladas en la Parte Inferior de la Vista de Aplicaciones",
"gitlabPATLabel": "Token GitLab de Acceso Personal\n(Habilita la Búsqueda y Mejor Detección de APKs)",
"dontShowTrackOnlyWarnings": "No mostrar avisos sobre apps en 'solo seguimiento'",
"dontShowAPKOriginWarnings": "No mostrar avisos sobre las fuentes de las APKs",
"moveNonInstalledAppsToBottom": "Mover Apps no instaladas al final",
"gitlabPATLabel": "Token de acceso personal a GitLab\n(habilita la búsqueda y mejor detección de APKs)",
"about": "Acerca",
"requiresCredentialsInSettings": "{}: Esto requiere credenciales adicionales (en Ajustes)",
"checkOnStart": "Comprobar actualizaciones durante el inicio",
"tryInferAppIdFromCode": "Intentar deducir la ID de la APP por el código fuente",
"removeOnExternalUninstall": "Auto eliminar Apps desinstaladas externamente",
"pickHighestVersionCode": "Auto selección versión superior del código APK",
"checkUpdateOnDetailPage": "Comprobar actualizaciones al abrir detalles de la App",
"disablePageTransitions": "Deshabilitar animaciones de transición de la página",
"reversePageTransitions": "Invertir animaciones de transición de la página",
"requiresCredentialsInSettings": "{}: Esto requiere credenciales adicionales (en ajustes)",
"checkOnStart": "Comprobar actualizaciones al inicio",
"tryInferAppIdFromCode": "Intentar deducir la ID de la app por el código fuente",
"removeOnExternalUninstall": "Auto eliminar apps desinstaladas externamente",
"pickHighestVersionCode": "Auto selección de versión superior del paquete APK",
"checkUpdateOnDetailPage": "Comprobar actualizaciones al abrir detalles de la app",
"disablePageTransitions": "Deshabilitar animaciones de transición",
"reversePageTransitions": "Invertir animaciones de transición",
"minStarCount": "Número Mínimo de Estrellas",
"addInfoBelow": "Añadir esta información debajo.",
"addInfoInSettings": "Añadir esta información en Ajustes.",
"githubSourceNote": "La limitación de velocidad de GitHub puede evitarse con una clave API.",
"gitlabSourceNote": "La extracción de APK de GitLab podría no funcionar sin una clave API.",
"sortByFileNamesNotLinks": "Ordenar por nombres de fichero en vez de por enlaces completos",
"filterReleaseNotesByRegEx": "Filtrar por Notas de Versión (Release Notes)",
"customLinkFilterRegex": "Filtro personalizado de Enlace APK (por defecto '.apk$')",
"appsPossiblyUpdated": "Actualización de Apps intentada",
"addInfoInSettings": "Puede añadir esta información en Ajustes.",
"githubSourceNote": "La limitación de velocidad de GitHub puede evitarse con un 'token de acceso personal'.",
"gitlabSourceNote": "La extracción de APK de GitLab podría no funcionar sin un 'token de acceso personal'.",
"sortByLastLinkSegment": "Ordenar sólo por el último segmento del enlace",
"filterReleaseNotesByRegEx": "Filtrar por notas de versión (release notes)",
"customLinkFilterRegex": "Filtro personalizado de enlace APK (por defecto '.apk$')",
"appsPossiblyUpdated": "Actualización de apps intentada",
"appsPossiblyUpdatedNotifDescription": "Notifica al usuario que las actualizaciones en segundo plano podrían haberse realizado para una o más aplicaciones",
"xWasPossiblyUpdatedToY": "{} podría estar actualizada a {}.",
"enableBackgroundUpdates": "Habilitar actualizaciones en segundo plano",
"backgroundUpdateReqsExplanation": "Las actualizaciones en segundo plano pueden no estar disponibles para todas las aplicaciones.",
"backgroundUpdateLimitsExplanation": "El éxito de las instalaciones en segundo plano solo se puede comprobar con Obtainium abierto.",
"verifyLatestTag": "Comprueba la etiqueta 'latest'",
"intermediateLinkRegex": "Filtrar por Enlace 'Intermedio' para Visitar Primero",
"intermediateLinkNotFound": "Enlace Intermedio no encontrado",
"exemptFromBackgroundUpdates": "Exento de actualizciones en segundo plano (si están habilitadas)",
"verifyLatestTag": "Comprobar la etiqueta 'Latest'",
"intermediateLinkRegex": "Filtrar por enlace 'intermedio' para visitar primero",
"filterByLinkText": "Filtrar enlaces por texto del enlace",
"intermediateLinkNotFound": "Enlace intermedio no encontrado",
"intermediateLink": "Enlace intermedio",
"exemptFromBackgroundUpdates": "Exenta de actualizciones en segundo plano (si están habilitadas)",
"bgUpdatesOnWiFiOnly": "Deshabilitar las actualizaciones en segundo plano sin WiFi",
"autoSelectHighestVersionCode": "Auto Selección de la versionCode APK superior",
"versionExtractionRegEx": "Versión de Extracción de RegEx",
"matchGroupToUse": "Coincidir en Grupo a Usar",
"autoSelectHighestVersionCode": "Auto selección del paquete APK con versión más reciente",
"versionExtractionRegEx": "Versión de extracción regex",
"matchGroupToUse": "Grupo a usar para versión de extracción regex",
"highlightTouchTargets": "Resaltar objetivos menos obvios",
"pickExportDir": "Selecciona el Directorio para Exportar",
"autoExportOnChanges": "Auto Exportar cuando haya cambios",
"pickExportDir": "Directorio para exportar",
"autoExportOnChanges": "Auto exportar cuando haya cambios",
"includeSettings": "Incluir ajustes",
"filterVersionsByRegEx": "Filtrar por Versiones",
"trySelectingSuggestedVersionCode": "Prueba seleccionando la versionCode APK sugerida",
"dontSortReleasesList": "Mantener el order de publicación desde API",
"filterVersionsByRegEx": "Filtrar por versiones",
"trySelectingSuggestedVersionCode": "Pruebe seleccionando la versión del paquete APK sugerida",
"dontSortReleasesList": "Mantener el order de publicación de la fuente original",
"reverseSort": "Orden inverso",
"takeFirstLink": "Usar primer enlace",
"skipSort": "Omitir orden",
"debugMenu": "Menu Depurar",
"bgTaskStarted": "Iniciada tarea en segundo plano - revisa los logs.",
"runBgCheckNow": "Ejecutar verficiación de actualizaciones en segundo plano",
"versionExtractWholePage": "Aplicar la Versión de Extracción Regex a la Página Entera",
"versionExtractWholePage": "Aplicar la versión de extracción regex a la página entera",
"installing": "Instalando",
"skipUpdateNotifications": "Omitir notificaciones sobre actualizaciones",
"updatesAvailableNotifChannel": "Actualizaciones Disponibles",
"appsUpdatedNotifChannel": "Aplicaciones Actualizadas",
"appsPossiblyUpdatedNotifChannel": "Se ha Intentado Actualizar la Aplicación",
"errorCheckingUpdatesNotifChannel": "Error Buscando Actualizaciones",
"appsRemovedNotifChannel": "Aplicaciones Eliminadas",
"skipUpdateNotifications": "No notificar sobre actualizaciones",
"updatesAvailableNotifChannel": "Actualizaciones disponibles",
"appsUpdatedNotifChannel": "Aplicaciones actualizadas",
"appsPossiblyUpdatedNotifChannel": "Se ha intentado actualizar la aplicación",
"errorCheckingUpdatesNotifChannel": "Error buscando actualizaciones",
"appsRemovedNotifChannel": "Aplicaciones eliminadas",
"downloadingXNotifChannel": "Descargando {}",
"completeAppInstallationNotifChannel": "Instalación Completa de la Aplicación",
"checkingForUpdatesNotifChannel": "Buscando Actualizaciones",
"onlyCheckInstalledOrTrackOnlyApps": "Comprobar actualizaciones solo para apps instaladas y en seguimiento",
"completeAppInstallationNotifChannel": "Instalación completada",
"checkingForUpdatesNotifChannel": "Buscando actualizaciones",
"onlyCheckInstalledOrTrackOnlyApps": "Comprobar actualizaciones solo para apps instaladas o en seguimiento",
"supportFixedAPKURL": "Soporte para URLs fijas de APK",
"selectX": "Selecciona {}",
"parallelDownloads": "Permitir descargas paralelas",
"installMethod": "Método de instalación",
"normal": "Normal",
"root": "Raíz",
"shizukuBinderNotFound": "Shizuku no funciona",
"useSystemFont": "Usar la fuente de impresión del sistema",
"systemFontError": "Error al cargar la fuente de impresión del sistema: {}",
"useVersionCodeAsOSVersion": "Usar la versión de la aplicación como versión detectada por el sistema operativo",
"requestHeader": "Encabezado de solicitud",
"useLatestAssetDateAsReleaseDate": "Usar la última carga de recursos como fecha de lanzamiento",
"defaultPseudoVersioningMethod": "Método de pseudoversionado predeterminado",
"partialAPKHash": "Hash de APK parcial",
"APKLinkHash": "Hash de enlace APK",
"directAPKLink": "Enlace APK directo",
"pseudoVersionInUse": "Se está utilizando una pseudoversión",
"installed": "Instalado",
"latest": "Versión más reciente",
"invertRegEx": "Invertir expresión regular",
"note": "Nota",
"selfHostedNote": "El desplegable \"{}\" puede utilizarse para acceder a instancias autoalojadas/personalizadas de cualquier fuente.",
"badDownload": "No se ha podido analizar el APK (incompatible o descarga parcial)",
"removeAppQuestion": {
"one": "¿Eliminar Aplicación?",
"other": "¿Eliminar Aplicaciones?"
@ -302,20 +322,20 @@
"other": "{} Aplicaciones"
},
"url": {
"one": "{} URL",
"other": "{} URLs"
"uno": "{} URL",
"otro": "{} URL"
},
"minute": {
"one": "{} Minuto",
"other": "{} Minutos"
"one": "{} minuto",
"other": "{} minutos"
},
"hour": {
"one": "{} Hora",
"other": "{} Horas"
"one": "{} hora",
"other": "{} horas"
},
"day": {
"one": "{} Día",
"other": "{} Días"
"one": "{} día",
"other": "{} días"
},
"clearedNLogsBeforeXAfterY": {
"one": "Eliminado {n} log (previo a = {before}, posterior a = {after})",

View File

@ -51,9 +51,8 @@
"percentProgress": "پیش رفتن: {}%",
"pleaseWait": "لطفا صبر کنید",
"updateAvailable": "بروزرسانی در دسترس",
"estimateInBracketsShort": "(تخمین)",
"notInstalled": "نصب نشده",
"estimateInBrackets": "(تخمین زدن)",
"pseudoVersion": "نسخه شبه",
"selectAll": "انتخاب همه",
"deselectX": "لغو انتخاب {}",
"xWillBeRemovedButRemainInstalled": "{} از Obtainium حذف می‌شود اما روی دستگاه نصب می‌ماند.",
@ -73,6 +72,8 @@
"unpinFromTop": "برداشتن پین از بالا",
"resetInstallStatusForSelectedAppsQuestion": "وضعیت نصب برنامه‌های انتخابی بازنشانی شود؟",
"installStatusOfXWillBeResetExplanation": "وضعیت نصب برنامه‌های انتخاب‌شده بازنشانی می‌شود.\n\nاگر نسخه برنامه نشان‌داده‌شده در Obtainium به دلیل به‌روزرسانی‌های ناموفق یا مشکلات دیگر نادرست باشد، می‌تواند کمک کند.",
"customLinkMessage": "این پیوندها بر روی دستگاه هایی با Obtainium نصب شده کار می کنند",
"shareAppConfigLinks": "اشتراک گذاری پیکربندی برنامه به عنوان پیوند HTML",
"shareSelectedAppURLs": "اشتراک گذاری آدرس اینترنتی برنامه های انتخاب شده",
"resetInstallStatus": "بازنشانی وضعیت نصب",
"more": "بیشتر",
@ -85,25 +86,24 @@
"author": "سازنده",
"upToDateApps": "برنامه های به روز",
"nonInstalledApps": "برنامه های نصب نشده",
"importExport": "وادر کردن/صادر کردن",
"importExport": "درون ریزی/برون ریزی",
"settings": "تنظیمات",
"exportedTo": "صادر کردن به{}",
"exportedTo": "برون ریزی به{}",
"obtainiumExport": "صادرکردن Obtainium",
"invalidInput": "ورودی نامعتبر",
"importedX": "وارد شده {}",
"obtainiumImport": "واردکردن Obtainium",
"importFromURLList": "وارد کردن از فهرست آدرس اینترنتی",
"importFromURLList": "درون ریزی از فهرست آدرس اینترنتی",
"searchQuery": "جستجوی سوال",
"appURLList": "فهرست آدرس اینترنتی برنامه",
"line": "خط",
"searchX": "جستجو {}",
"noResults": "نتیجه ای پیدا نشد",
"importX": "وارد کردن {}",
"importedAppsIdDisclaimer": "ممکن است برنامه‌های وارد شده به اشتباه به‌عنوان \"نصب نشده\" نشان داده شوند.\nبرای رفع این مشکل، آنها را دوباره از طریق Obtainium نصب کنید.\nاین نباید روی داده‌های برنامه تأثیر بگذارد.\n\nفقط بر روی آدرس اینترنتی و روش‌های وارد کردن شخص ثالث تأثیر می‌گذارد.",
"importErrors": "خطاهای وارد کردن",
"importX": "درون ریزی {}",
"importedAppsIdDisclaimer": "ممکن است برنامه‌های وارد شده به اشتباه به‌عنوان \"نصب نشده\" نشان داده شوند.\nبرای رفع این مشکل، آنها را دوباره از طریق Obtainium نصب کنید.\nاین نباید روی داده‌های برنامه تأثیر بگذارد.\n\nفقط بر روی آدرس اینترنتی و روش‌های درون ریزی شخص ثالث تأثیر می‌گذارد.",
"importErrors": "خطاهای درون ریزی",
"importedXOfYApps": "{} از {} برنامه وارد شد.",
"followingURLsHadErrors": "آدرس های اینترنتی زیر دارای خطا بودند:",
"okay": "باشه",
"selectURL": "آدرس اینترنتی انتخاب شده",
"selectURLs": "آدرس های اینترنتی انتخاب شده",
"pick": "انتخاب",
@ -111,8 +111,6 @@
"dark": "تاریک",
"light": "روشن",
"followSystem": "هماهنگ با سیستم",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "استفاده از تم تیره سیاه خالص",
"appSortBy": "مرتب سازی برنامه بر اساس",
"authorName": "سازنده/اسم",
@ -134,7 +132,7 @@
"close": "بستن",
"share": "اشتراک گذاری",
"appNotFound": "برنامه پیدا نشد",
"obtainiumExportHyphenatedLowercase": "صادر کردن-obtainium",
"obtainiumExportHyphenatedLowercase": "برون ریزی-obtainium",
"pickAnAPK": "یک APK انتخاب کنید",
"appHasMoreThanOnePackage": "{} بیش از یک بسته دارد:",
"deviceSupportsXArch": "دستگاه شما از معماری پردازنده {} پشتیبانی میکند",
@ -172,13 +170,12 @@
"yesMarkUpdated": "بله، علامت گذاری به عنوان به روز شده",
"fdroid": "F-Droid Official",
"appIdOrName": "شناسه یا نام برنامه",
"appId": "App ID",
"appId": "شناسه برنامه",
"appWithIdOrNameNotFound": "هیچ برنامه ای با آن شناسه یا نام یافت نشد",
"reposHaveMultipleApps": "مخازن ممکن است شامل چندین برنامه باشد",
"fdroidThirdPartyRepo": "مخازن شخص ثالث F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "استیم موبایل",
"steamChat": "چت استیم",
"install": "نصب",
"markInstalled": "علامت گذاری به عنوان نصب شده",
"update": "به روز رسانی",
@ -211,7 +208,8 @@
"releaseDateAsVersionExplanation": "این گزینه فقط باید برای برنامه هایی استفاده شود که تشخیص نسخه به درستی کار نمی کند، اما تاریخ انتشار در دسترس است.",
"changes": "تغییرات",
"releaseDate": "تاریخ انتشار",
"importFromURLsInFile": "وارد کردن از آدرس های اینترنتی موجود در فایل (مانند OPML)",
"importFromURLsInFile": "درون ریزی از آدرس های اینترنتی موجود در فایل (مانند OPML)",
"versionDetectionExplanation": "تطبیق رشته نسخه با نسخه شناسایی شده از سیستم عامل",
"versionDetection": "تشخیص نسخه",
"standardVersionDetection": "تشخیص نسخه استاندارد",
"groupByCategory": "گروه بر اساس دسته",
@ -236,7 +234,7 @@
"addInfoInSettings": "این اطلاعات را در تنظیمات اضافه کنید.",
"githubSourceNote": "با استفاده از کلید API می توان از محدودیت نرخ GitHub جلوگیری کرد.",
"gitlabSourceNote": "استخراج APK GitLab ممکن است بدون کلید API کار نکند.",
"sortByFileNamesNotLinks": "مرتب سازی بر اساس نام فایل به جای پیوندهای کامل",
"sortByLastLinkSegment": "فقط بر اساس آخرین بخش پیوند مرتب کنید",
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیش‌فرض '.apk$')",
"appsPossiblyUpdated": "به‌روزرسانی برنامه انجام شد",
@ -246,23 +244,25 @@
"backgroundUpdateReqsExplanation": "به روز رسانی پس زمینه ممکن است برای همه برنامه ها امکان پذیر نباشد.",
"backgroundUpdateLimitsExplanation": "موفقیت نصب پس‌زمینه تنها زمانی مشخص می‌شود که Obtainium باز شود.",
"verifyLatestTag": "برچسب \"آخرین\" را تأیید کنید",
"intermediateLinkRegex": "برای اولین بار بازدید از لینک \"متوسط\" را فیلتر کنید",
"intermediateLinkRegex": "برای بازدید از پیوند «میانگین» فیلتر کنید",
"filterByLinkText": "لینک ها را بر اساس متن پیوند فیلتر کنید",
"intermediateLinkNotFound": "لینک میانی پیدا نشد",
"intermediateLink": "پیوند میانی",
"exemptFromBackgroundUpdates": "معاف از به‌روزرسانی‌های پس‌زمینه (در صورت فعال بودن)",
"bgUpdatesOnWiFiOnly": "به‌روزرسانی‌های پس‌زمینه را در صورت عدم اتصال به WiFi غیرفعال کنید",
"autoSelectHighestVersionCode": "انتخاب خودکار بالاترین نسخه کد APK",
"versionExtractionRegEx": "نسخه استخراج RegEx",
"matchGroupToUse": "گروه مورد استفاده را مطابقت دهید",
"highlightTouchTargets": "اهداف لمسی کمتر واضح را برجسته کنید",
"pickExportDir": "فهرست صادرات را انتخاب کنید",
"autoExportOnChanges": "صادرات خودکار تغییرات",
"includeSettings": "Include settings",
"pickExportDir": "فهرست برون ریزی را انتخاب کنید",
"autoExportOnChanges": "برون ریزی خودکار تغییرات",
"includeSettings": "شامل تنظیمات",
"filterVersionsByRegEx": "فیلتر کردن نسخه ها با RegEx",
"trySelectingSuggestedVersionCode": "نسخه پیشنهادی APK نسخه کد را انتخاب کنید",
"dontSortReleasesList": "حفظ سفارش انتشار از API",
"reverseSort": "مرتب سازی معکوس",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "لینک اول را بگیرید",
"skipSort": "از مرتب سازی صرف نظر کنید",
"debugMenu": "منوی اشکال زدایی",
"bgTaskStarted": "کار پس زمینه شروع شد - لاگ های مربوط را بررسی کنید.",
"runBgCheckNow": "اکنون به‌روزرسانی پس‌زمینه را بررسی کنید",
@ -280,7 +280,27 @@
"onlyCheckInstalledOrTrackOnlyApps": "فقط برنامه های نصب شده و فقط ردیابی را برای به روز رسانی بررسی کنید",
"supportFixedAPKURL": "پشتیبانی از URL های APK ثابت",
"selectX": "انتخاب کنید {}",
"parallelDownloads": "Allow parallel downloads",
"parallelDownloads": "اجازه دانلود موازی",
"installMethod": "روش نصب",
"normal": "طبیعی",
"root": "ریشه",
"shizukuBinderNotFound": "Shizuku در حال اجرا نیست",
"useSystemFont": "استفاده از فونت سیستم",
"systemFontError": "خطا در بارگیری فونت سیستم: {}",
"useVersionCodeAsOSVersion": "استفاده کد نسخه برنامه به جای نسخه شناسایی شده توسط سیستم عامل استفاده کنید",
"requestHeader": "درخواست سطر بالایی",
"useLatestAssetDateAsReleaseDate": "استفاده از آخرین بارگذاری دارایی به عنوان تاریخ انتشار",
"defaultPseudoVersioningMethod": "روش پیش فرض شبه نسخه",
"partialAPKHash": "هش جزئی APK",
"APKLinkHash": "هش پیوند APK",
"directAPKLink": "لینک مستقیم APK",
"pseudoVersionInUse": "یک نسخه شبه در حال استفاده است",
"installed": "نصب شده است",
"latest": "آخرین",
"invertRegEx": "معکوس کردن عبارت منظم",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"badDownload": "The APK could not be parsed (incompatible or partial download)",
"removeAppQuestion": {
"one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟"

View File

@ -9,7 +9,7 @@
"placeholder": "Espace réservé",
"someErrors": "Des erreurs se sont produites",
"unexpectedError": "Erreur inattendue",
"ok": "Okay",
"ok": "D'accord",
"and": "et",
"githubPATLabel": "Jeton d'Accès Personnel GitHub (Augmente la limite de débit)",
"includePrereleases": "Inclure les avant-premières",
@ -51,9 +51,8 @@
"percentProgress": "Progrès: {}%",
"pleaseWait": "Veuillez patienter",
"updateAvailable": "Mise à jour disponible",
"estimateInBracketsShort": "(Est.)",
"notInstalled": "Pas installé",
"estimateInBrackets": "(Estimation)",
"pseudoVersion": "pseudo-version",
"selectAll": "Tout sélectionner",
"deselectX": "Déselectionner {}",
"xWillBeRemovedButRemainInstalled": "{} sera supprimé d'Obtainium mais restera installé sur l'appareil.",
@ -73,6 +72,8 @@
"unpinFromTop": "Détacher du haut",
"resetInstallStatusForSelectedAppsQuestion": "Réinitialiser l'état d'installation des applications sélectionnées ?",
"installStatusOfXWillBeResetExplanation": "L'état d'installation de toutes les applications sélectionnées sera réinitialisé.\n\nCela peut aider lorsque la version de l'application affichée dans Obtainium est incorrecte en raison d'échecs de mises à jour ou d'autres problèmes.",
"customLinkMessage": "Ces liens fonctionnent sur les appareils sur lesquels Obtenirium est installé",
"shareAppConfigLinks": "Partager la configuration de l'application sous forme de lien HTML",
"shareSelectedAppURLs": "Partager les URL d'application sélectionnées",
"resetInstallStatus": "Réinitialiser le statut d'installation",
"more": "Plus",
@ -103,7 +104,6 @@
"importErrors": "Erreurs d'importation",
"importedXOfYApps": "{} sur {} applications importées.",
"followingURLsHadErrors": "Les URL suivantes comportaient des erreurs :",
"okay": "Okay",
"selectURL": "Sélectionnez l'URL",
"selectURLs": "Sélectionnez les URLs",
"pick": "Prendre",
@ -111,9 +111,7 @@
"dark": "Sombre",
"light": "Clair",
"followSystem": "Suivre le système",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme",
"useBlackTheme": "Utilisez le thème noir pur et sombre",
"appSortBy": "Applications triées par",
"authorName": "Auteur/Nom",
"nameAuthor": "Nom/Auteur",
@ -134,7 +132,7 @@
"close": "Fermer",
"share": "Partager",
"appNotFound": "Application introuvable",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"obtainiumExportHyphenatedLowercase": "exportation d'obtainium",
"pickAnAPK": "Choisissez un APK",
"appHasMoreThanOnePackage": "{} a plus d'un paquet :",
"deviceSupportsXArch": "Votre appareil prend en charge l'architecture de processeur {}.",
@ -170,15 +168,14 @@
"lastUpdateCheckX": "Vérification de la dernière mise à jour : {}",
"remove": "Retirer",
"yesMarkUpdated": "Oui, marquer comme mis à jour",
"fdroid": "F-Droid Official",
"fdroid": "F-Droïde Officiel",
"appIdOrName": "ID ou nom de l'application",
"appId": "ID de l'application",
"appWithIdOrNameNotFound": "Aucune application n'a été trouvée avec cet identifiant ou ce nom",
"reposHaveMultipleApps": "Les dépôts peuvent contenir plusieurs applications",
"fdroidThirdPartyRepo": "Dépôt tiers F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "Vapeur Mobile",
"steamChat": "Chat sur Steam",
"install": "Installer",
"markInstalled": "Marquer installée",
"update": "Mettre à jour",
@ -193,14 +190,14 @@
"categorize": "Catégoriser",
"categories": "Catégories",
"category": "Catégorie",
"noCategory": "No Category",
"noCategory": "Aucune catégorie",
"noCategories": "Aucune catégorie",
"deleteCategoriesQuestion": "Supprimer les catégories ?",
"categoryDeleteWarning": "Toutes les applications dans les catégories supprimées seront définies sur non catégorisées.",
"addCategory": "Ajouter une catégorie",
"label": "Étiquette",
"language": "Langue",
"copiedToClipboard": "Copied to Clipboard",
"copiedToClipboard": "Copié dans le presse-papier",
"storagePermissionDenied": "Autorisation de stockage refusée",
"selectedCategorizeWarning": "Cela remplacera tous les paramètres de catégorie existants pour les applications sélectionnées.",
"filterAPKsByRegEx": "Filtrer les APK par expression régulière",
@ -212,75 +209,98 @@
"changes": "Changements",
"releaseDate": "Date de sortie",
"importFromURLsInFile": "Importer à partir d'URL dans un fichier (comme OPML)",
"versionDetectionExplanation": "Réconcilier la chaîne de version avec la version détectée à partir du système d'exploitation",
"versionDetection": "Détection des versions",
"standardVersionDetection": "Détection de version standard",
"groupByCategory": "Group by Category",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"overrideSource": "Override Source",
"dontShowAgain": "Don't show this again",
"groupByCategory": "Regrouper par catégorie",
"autoApkFilterByArch": "Essayez de filtrer les APK par architecture CPU si possible",
"overrideSource": "Remplacer la source",
"dontShowAgain": "Ne montre plus ça",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Don't show APK origin warnings",
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "About",
"dontShowAPKOriginWarnings": "Ne pas afficher les avertissements sur l'origine de l'APK",
"moveNonInstalledAppsToBottom": "Déplacer les applications non installées vers le bas de la vue Applications",
"gitlabPATLabel": "Jeton d'accès personnel GitLab\\n (permet la recherche et une meilleure découverte d'APK)",
"about": "À propos de",
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Check for updates on startup",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
"pickHighestVersionCode": "Auto-select highest version code APK",
"checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
"disablePageTransitions": "Disable page transition animations",
"reversePageTransitions": "Reverse page transition animations",
"minStarCount": "Minimum Star Count",
"addInfoBelow": "Add this info below.",
"addInfoInSettings": "Add this info in the Settings.",
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"enableBackgroundUpdates": "Enable background updates",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
"intermediateLinkNotFound": "Intermediate link not found",
"exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)",
"bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"checkOnStart": "Vérifier les mises à jour au démarrage",
"tryInferAppIdFromCode": "Essayez de déduire l'ID de l'application à partir du code source",
"removeOnExternalUninstall": "Supprimer automatiquement les applications désinstallées en externe",
"pickHighestVersionCode": "Sélectionner automatiquement le code APK de la version la plus élevée",
"checkUpdateOnDetailPage": "Vérifier les mises à jour lors de l'ouverture d'une page de détails d'application",
"disablePageTransitions": "Désactiver les animations de transition de page",
"reversePageTransitions": "Animations de transition de page inversée",
"minStarCount": "Nombre minimum d'étoiles",
"addInfoBelow": "Ajoutez ces informations ci-dessous.",
"addInfoInSettings": "Ajoutez ces informations dans les paramètres.",
"githubSourceNote": "La limitation du débit GitHub peut être évitée à l'aide d'une clé API.",
"gitlabSourceNote": "L'extraction d'APK GitLab peut ne pas fonctionner sans clé API.",
"sortByLastLinkSegment": "Trier uniquement sur le dernier segment du lien",
"filterReleaseNotesByRegEx": "Filtrer les notes de version par expression régulière",
"customLinkFilterRegex": "Filtre de lien APK personnalisé par expression régulière (par défaut '.apk$')",
"appsPossiblyUpdated": "Tentative de mise à jour de l'application",
"appsPossiblyUpdatedNotifDescription": "Avertit l'utilisateur que des mises à jour d'une ou plusieurs applications ont été potentiellement appliquées en arrière-plan",
"xWasPossiblyUpdatedToY": "{} a peut-être été mis à jour vers {}.",
"enableBackgroundUpdates": "Activer les mises à jour en arrière-plan",
"backgroundUpdateReqsExplanation": "Les mises à jour en arrière-plan peuvent ne pas être possibles pour toutes les applications.",
"backgroundUpdateLimitsExplanation": "Le succès d'une installation en arrière-plan ne peut être déterminé qu'à l'ouverture d'Obetium.",
"verifyLatestTag": "Vérifiez la balise 'dernière'",
"intermediateLinkRegex": " Filtrer un lien \" intermédiaire \" à visiter ",
"filterByLinkText": "Filtrer les liens par texte de lien",
"intermediateLinkNotFound": "Lien intermédiaire introuvable",
"intermediateLink": "Lien intermédiaire",
"exemptFromBackgroundUpdates": "Exempt des mises à jour en arrière-plan (si activé)",
"bgUpdatesOnWiFiOnly": "Désactiver les mises à jour en arrière-plan lorsque vous n'êtes pas connecté au WiFi",
"autoSelectHighestVersionCode": "Sélection automatique de la version la plus élevéeCode APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"pickExportDir": "Pick Export Directory",
"autoExportOnChanges": "Auto-export on changes",
"includeSettings": "Include settings",
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
"dontSortReleasesList": "Retain release order from API",
"reverseSort": "Reverse sorting",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"debugMenu": "Debug Menu",
"bgTaskStarted": "Background task started - check logs.",
"runBgCheckNow": "Run Background Update Check Now",
"highlightTouchTargets": "Mettez en évidence les cibles tactiles moins évidentes",
"pickExportDir": "Choisir le répertoire d'exportation",
"autoExportOnChanges": "Exportation automatique sur modifications",
"includeSettings": "Inclure les paramètres",
"filterVersionsByRegEx": "Filtrer les versions par expression régulière",
"trySelectingSuggestedVersionCode": "Essayez de sélectionner la version suggéréeCode APK",
"dontSortReleasesList": "Conserver la commande de version de l'API",
"reverseSort": "Tri inversé",
"takeFirstLink": "Prendre le premier lien",
"skipSort": "Sauter le tri",
"debugMenu": "Menu de débogage",
"bgTaskStarted": "Tâche en arrière-plan démarrée - vérifier les journaux.",
"runBgCheckNow": "Exécuter la vérification de la mise à jour en arrière-plan maintenant",
"versionExtractWholePage": "Apply Version Extraction Regex to Entire Page",
"installing": "Installing",
"skipUpdateNotifications": "Skip update notifications",
"installing": "Installation",
"skipUpdateNotifications": "Ignorer les notifications de mise à jour",
"updatesAvailableNotifChannel": "Mises à jour disponibles",
"appsUpdatedNotifChannel": "Applications mises à jour",
"appsPossiblyUpdatedNotifChannel": "App Updates Attempted",
"appsPossiblyUpdatedNotifChannel": "Tentative de mise à jour de l'application",
"errorCheckingUpdatesNotifChannel": "Erreur lors de la vérification des mises à jour",
"appsRemovedNotifChannel": "Applications supprimées",
"downloadingXNotifChannel": "Téléchargement {}",
"completeAppInstallationNotifChannel": "Installation complète de l'application",
"checkingForUpdatesNotifChannel": "Vérification des mises à jour",
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"onlyCheckInstalledOrTrackOnlyApps": "Vérifiez uniquement les mises à jour des applications installées et de suivi uniquement",
"supportFixedAPKURL": "Prise en charge des URL APK fixes",
"selectX": "Sélectionner {}",
"parallelDownloads": "Autoriser les téléchargements parallèles",
"installMethod": "Méthode d'installation",
"normal": "Normale",
"root": "Racine",
"shizukuBinderNotFound": "Shizuku is not running",
"useSystemFont": "Utiliser la police système",
"systemFontError": "Erreur de chargement de la police système : {}",
"useVersionCodeAsOSVersion": "Utiliser le code de version de l'application comme version détectée par le système d'exploitation",
"requestHeader": "En-tête de demande",
"useLatestAssetDateAsReleaseDate": "Utiliser le dernier téléchargement d'élément comme date de sortie",
"defaultPseudoVersioningMethod": "Méthode de pseudo-version par défaut",
"partialAPKHash": "Hash APK partiel",
"APKLinkHash": "Hash de lien APK",
"directAPKLink": "Lien APK direct",
"pseudoVersionInUse": "Une pseudo-version est utilisée",
"installed": "Installée",
"latest": "Dernier",
"invertRegEx": "Inverser l'expression régulière",
"note": "Note",
"selfHostedNote": "La liste déroulante \"{}\" peut être utilisée pour accéder aux instances auto-hébergées/personnalisées de n'importe quelle source.",
"badDownload": "L'APK n'a pas pu être analysé (téléchargement incompatible ou partiel)",
"removeAppQuestion": {
"one": "Supprimer l'application ?",
"other": "Supprimer les applications ?"
@ -303,10 +323,10 @@
},
"url": {
"one": "{} URL",
"other": "{} URLs"
"other": "{} URL"
},
"minute": {
"one": "{} Minute",
"one": "{} Minutes",
"other": "{} Minutes"
},
"hour": {
@ -330,7 +350,7 @@
"other": "{} et {} autres applications ont été mises à jour."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
"une": "{} et 1 application supplémentaire ont peut-être été mises à jour.",
"other": "{} et {} autres applications peuvent avoir été mises à jour."
}
}

View File

@ -27,7 +27,7 @@
"wrongArgNum": "Rossz számú argumentumot adott meg",
"xIsTrackOnly": "A(z) {} csak nyomonkövethető",
"source": "Forrás",
"app": "App",
"app": "Alkalmazás",
"appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.",
"youPickedTrackOnly": "A 'Csak követés' opciót választotta.",
"trackOnlyAppDescription": "Az alkalmazás frissítéseit nyomon követi, de az Obtainium nem tudja letölteni vagy telepíteni.",
@ -51,9 +51,8 @@
"percentProgress": "Folyamat: {}%",
"pleaseWait": "Kis türelmet",
"updateAvailable": "Frissítés érhető el",
"estimateInBracketsShort": "(Becsült)",
"notInstalled": "Nem telepített",
"estimateInBrackets": "(Becslés)",
"pseudoVersion": "ál-verzió",
"selectAll": "Mindet kiválaszt",
"deselectX": "Törölje {} kijelölését",
"xWillBeRemovedButRemainInstalled": "A(z) {} el lesz távolítva az Obtainiumból, de továbbra is telepítve marad az eszközön.",
@ -73,6 +72,8 @@
"unpinFromTop": "Eltávolít felülről",
"resetInstallStatusForSelectedAppsQuestion": "Visszaállítja a kiválasztott appok telepítési állapotát?",
"installStatusOfXWillBeResetExplanation": "A kiválasztott appok telepítési állapota visszaáll.\n\nEz akkor segíthet, ha az Obtainiumban megjelenített app verzió hibás, frissítések vagy egyéb problémák miatt.",
"customLinkMessage": "Ezek a hivatkozások a telepített Obtainium-mal rendelkező eszközökön működnek",
"shareAppConfigLinks": "App konfiguráció megosztása HTML linkként",
"shareSelectedAppURLs": "Ossza meg a kiválasztott app URL címeit",
"resetInstallStatus": "Telepítési állapot visszaállítása",
"more": "További",
@ -85,7 +86,7 @@
"author": "Szerző",
"upToDateApps": "Naprakész appok",
"nonInstalledApps": "Nem telepített appok",
"importExport": "Import/Export",
"importExport": "Import Export",
"settings": "Beállítások",
"exportedTo": "Exportálva ide {}",
"obtainiumExport": "Obtainium Adat Exportálás",
@ -98,12 +99,11 @@
"line": "Sor",
"searchX": "Keresés {}",
"noResults": "Nincs találat",
"importX": "Import {}",
"importX": "Importálás: {}",
"importedAppsIdDisclaimer": "Előfordulhat, hogy az importált appok helytelenül \"Nincs telepítve\" jelzéssel jelennek meg.\nA probléma megoldásához telepítse újra őket az Obtainiumon keresztül.\nEz nem érinti az alkalmazásadatokat.\n\nCsak az URL-ekre és a harmadik féltől származó importálási módszerekre vonatkozik..",
"importErrors": "Importálási hibák",
"importedXOfYApps": "{}/{} app importálva.",
"followingURLsHadErrors": "A következő URL-ek hibákat tartalmaztak:",
"okay": "Oké",
"selectURL": "Válassza ki az URL-t",
"selectURLs": "Kiválasztott URL-ek",
"pick": "Válasszon",
@ -111,8 +111,6 @@
"dark": "Sötét",
"light": "Világos",
"followSystem": "Rendszer szerint",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Használjon teljesen fekete sötét témát",
"appSortBy": "App rendezés...",
"authorName": "Szerző/Név",
@ -170,14 +168,13 @@
"lastUpdateCheckX": "Frissítés ellenőrizve: {}",
"remove": "Eltávolítás",
"yesMarkUpdated": "Igen, megjelölés frissítettként",
"fdroid": "F-Droid Official",
"fdroid": "F-Droid hivatalos",
"appIdOrName": "App ID vagy név",
"appId": "App ID",
"appId": "Alkalmazásazonosító",
"appWithIdOrNameNotFound": "Nem található app ezzel az azonosítóval vagy névvel",
"reposHaveMultipleApps": "A repók több alkalmazást is tartalmazhatnak",
"fdroidThirdPartyRepo": "F-Droid Harmadik-fél Repo",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamMobile": "Steam mobil",
"steamChat": "Steam Chat",
"install": "Telepít",
"markInstalled": "Telepítettnek jelöl",
@ -195,7 +192,7 @@
"category": "Kategória",
"noCategory": "Nincs kategória",
"noCategories": "Nincsenek kategóriák",
"deleteCategoryQuestion": "Törli a kategóriát?",
"deleteCategoriesQuestion": "Törli a kategóriákat?",
"categoryDeleteWarning": "A(z) {} összes app kategorizálatlan állapotba kerül.",
"addCategory": "Új kategória",
"label": "Címke",
@ -212,6 +209,7 @@
"changes": "Változtatások",
"releaseDate": "Kiadás dátuma",
"importFromURLsInFile": "Importálás fájlban található URL-ből (mint pl. OPML)",
"versionDetectionExplanation": "A verzió karakterlánc egyeztetése az OS által észlelt verzióval",
"versionDetection": "Verzió érzékelés",
"standardVersionDetection": "Alapért. verzió érzékelés",
"groupByCategory": "Csoportosítás Kategória alapján",
@ -236,17 +234,20 @@
"addInfoInSettings": "Adja hozzá ezt az infót a Beállításokban.",
"githubSourceNote": "A GitHub sebességkorlátozás elkerülhető API-kulcs használatával.",
"gitlabSourceNote": "Előfordulhat, hogy a GitLab APK kibontása nem működik API-kulcs nélkül.",
"sortByFileNamesNotLinks": "Fájlnevek szerinti elrendezés teljes linkek helyett",
"sortByLastLinkSegment": "Rendezés csak a link utolsó szegmense szerint",
"filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel",
"customLinkFilterRegex": "Egyéni APK hivatkozásszűrő reguláris kifejezéssel (Alapérték '.apk$')",
"appsPossiblyUpdated": "App frissítési kísérlet",
"appsPossiblyUpdatedNotifDescription": "Értesíti a felhasználót, hogy egy vagy több alkalmazás frissítése lehetséges a háttérben",
"xWasPossiblyUpdatedToY": "{} frissítve lehet erre {}.",
"enableBackgroundUpdates": "Frissítések a háttérben",
"backgroundUpdateReqsExplanation": "Előfordulhat, hogy nem minden appnál lehetséges a háttérbeli frissítés.",
"backgroundUpdateLimitsExplanation": "A háttérben történő telepítés sikeressége csak az Obtainium megnyitásakor állapítható meg.",
"verifyLatestTag": "Ellenőrizze a „legújabb” címkét",
"intermediateLinkRegex": "Szűrés egy 'közvetítő' linkre, amelyet először meg kell látogatni",
"intermediateLinkNotFound": "Közvetítő link nem található",
"intermediateLinkRegex": "Szűrés egy 'köztes' látogatási linkre",
"filterByLinkText": "A hivatkozások szűrése linkszöveg alapján",
"intermediateLinkNotFound": "Köztes link nem található",
"intermediateLink": "Köztes link",
"exemptFromBackgroundUpdates": "Mentes a háttérben történő frissítések alól (ha engedélyezett)",
"bgUpdatesOnWiFiOnly": "Tiltsa le a háttérben frissítéseket, ha nincs Wi-Fi-n",
"autoSelectHighestVersionCode": "A legmagasabb verziószámú APK auto. kiválasztása",
@ -255,16 +256,15 @@
"highlightTouchTargets": "Emelje ki a kevésbé nyilvánvaló érintési célokat",
"pickExportDir": "Válassza az Exportálási könyvtárat",
"autoExportOnChanges": "Auto-exportálás a változások után",
"includeSettings": "Include settings",
"includeSettings": "Tartalmazza a beállításokat",
"filterVersionsByRegEx": "Verziók szűrése reguláris kifejezéssel",
"trySelectingSuggestedVersionCode": "Próbálja ki a javasolt verziókódú APK-t",
"dontSortReleasesList": "Az API-ból származó kiadási sorrend megőrzése",
"reverseSort": "Fordított rendezés",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "Vegye az első linket",
"skipSort": "A válogatás kihagyása",
"debugMenu": "Hibakereső menü",
"bgTaskStarted": "A háttérfeladat elindult ellenőrizze a naplókat.",
"enableBackgroundUpdates": "Frissítések a háttérben",
"runBgCheckNow": "Futtassa a Háttérben frissítés ellenőrzését most",
"versionExtractWholePage": "Alkalmazza a Version Extraction Regex-et az egész oldalra",
"installing": "Telepítés",
@ -280,7 +280,27 @@
"onlyCheckInstalledOrTrackOnlyApps": "Csak a telepített és a csak követhető appokat ellenőrizze frissítésekért",
"supportFixedAPKURL": "Támogatja a rögzített APK URL-eket",
"selectX": "Kiválaszt {}",
"parallelDownloads": "Allow parallel downloads",
"parallelDownloads": "Párhuzamos letöltéseket enged",
"installMethod": "Telepítési mód",
"normal": "Normál",
"root": "Gyökér",
"shizukuBinderNotFound": "A Shizuku nem fut",
"useSystemFont": "Használja a rendszer betűtípusát",
"systemFontError": "Hiba a rendszer betűtípusának betöltésekor: {}",
"useVersionCodeAsOSVersion": "Az app verziókód használata a rendszer által észlelt verzióként",
"requestHeader": "Kérelem fejléc",
"useLatestAssetDateAsReleaseDate": "Használja a legújabb tartalomfeltöltést megjelenési dátumként",
"defaultPseudoVersioningMethod": "Alapértelmezett álversziós módszer",
"partialAPKHash": "Részleges APK Hash",
"APKLinkHash": "APK Link Hash",
"directAPKLink": "Közvetlen APK Link",
"pseudoVersionInUse": "Egy álverzió van használatban",
"installed": "Telepített",
"latest": "Legújabb",
"invertRegEx": "Invertált reguláris kifejezés",
"note": "Megjegyzés:",
"selfHostedNote": "A \"{}\" legördülő menü használható bármely forrás saját üzemeltetésű/egyéni példányainak eléréséhez.",
"badDownload": "Az APK-t nem lehetett elemezni (inkompatibilis vagy részleges letöltés)",
"removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?"

View File

@ -51,9 +51,8 @@
"percentProgress": "Avanzamento: {}%",
"pleaseWait": "In attesa",
"updateAvailable": "Aggiornamento disponibile",
"estimateInBracketsShort": "(stim.)",
"notInstalled": "Non installato",
"estimateInBrackets": "(stimato)",
"pseudoVersion": "pseudo-versione",
"selectAll": "Seleziona tutto",
"deselectX": "Deseleziona {}",
"xWillBeRemovedButRemainInstalled": "Verà effettuata la rimozione di {}, ma non la disinstallazione.",
@ -66,13 +65,15 @@
"installUpdateApps": "Installa/Aggiorna app",
"installUpdateSelectedApps": "Installa/Aggiorna le app selezionate",
"markXSelectedAppsAsUpdated": "Contrassegnare le {} app selezionate come aggiornate?",
"no": "No",
"no": "NO",
"yes": "Sì",
"markSelectedAppsUpdated": "Contrassegna le app selezionate come aggiornate",
"pinToTop": "Fissa in alto",
"unpinFromTop": "Rimuovi dall'alto",
"resetInstallStatusForSelectedAppsQuestion": "Ripristinare lo stato d'installazione delle app selezionate?",
"installStatusOfXWillBeResetExplanation": "Lo stato d'installazione di ogni app selezionata sarà ripristinato.\n\nCiò può essere d'aiuto nel caso in cui la versione mostrata dell'app in Obtainium non sia corretta a causa di un aggiornamento fallito o di altri problemi.",
"customLinkMessage": "Questi collegamenti funzionano sui dispositivi con Obtainium installato",
"shareAppConfigLinks": "Condividi la configurazione dell'app come collegamento HTML",
"shareSelectedAppURLs": "Condividi gli URL delle app selezionate",
"resetInstallStatus": "Ripristina lo stato d'installazione",
"more": "Altro",
@ -103,7 +104,6 @@
"importErrors": "Errori di importazione",
"importedXOfYApps": "{} app di {} importate.",
"followingURLsHadErrors": "I seguenti URL contengono errori:",
"okay": "Va bene",
"selectURL": "Seleziona l'URL",
"selectURLs": "Seleziona gli URL",
"pick": "Seleziona",
@ -111,9 +111,7 @@
"dark": "Scuro",
"light": "Chiaro",
"followSystem": "Segui il sistema",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Usa il tema Nero puro",
"useBlackTheme": "Usa il tema nero puro",
"appSortBy": "App ordinate per",
"authorName": "Autore/Nome",
"nameAuthor": "Nome/Autore",
@ -176,9 +174,8 @@
"appWithIdOrNameNotFound": "Non è stata trovata alcuna app con quell'ID o nome",
"reposHaveMultipleApps": "I repository possono contenere più app",
"fdroidThirdPartyRepo": "Repository F-Droid di terze parti",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "Mobile a vapore",
"steamChat": "Chat di vapore",
"install": "Installa",
"markInstalled": "Contrassegna come installata",
"update": "Aggiorna",
@ -212,6 +209,7 @@
"changes": "Novità",
"releaseDate": "Data di rilascio",
"importFromURLsInFile": "Importa da URL in file (come OPML)",
"versionDetectionExplanation": "Riconcilia la stringa della versione con la versione rilevata dal sistema operativo",
"versionDetection": "Rilevamento di versione",
"standardVersionDetection": "Rilevamento di versione standard",
"groupByCategory": "Raggruppa per categoria",
@ -236,7 +234,7 @@
"addInfoInSettings": "Aggiungi questa info nelle impostazioni.",
"githubSourceNote": "Il limite di ricerca GitHub può essere evitato usando una chiave API.",
"gitlabSourceNote": "L'estrazione di APK da GitLab potrebbe non funzionare senza chiave API.",
"sortByFileNamesNotLinks": "Ordina per nome del file invece dei link completi",
"sortByLastLinkSegment": "Ordina solo in base all'ultimo segmento del collegamento",
"filterReleaseNotesByRegEx": "Filtra le note di rilascio con espressione regolare",
"customLinkFilterRegex": "Filtra link APK personalizzato con espressione regolare (predefinito '.apk$')",
"appsPossiblyUpdated": "Aggiornamenti app tentati",
@ -246,8 +244,10 @@
"backgroundUpdateReqsExplanation": "Gli aggiornamenti in secondo piano potrebbero non essere possibili per tutte le app.",
"backgroundUpdateLimitsExplanation": "La riuscita di un'installazione in secondo piano può essere determinata solo quando viene aperto Obtainium.",
"verifyLatestTag": "Verifica l'etichetta 'Latest'",
"intermediateLinkRegex": "Filtra un link 'Intermedio' da visitare prima",
"intermediateLinkRegex": "Filtro per un collegamento 'intermedio' da visitare",
"filterByLinkText": "Filtra i collegamenti in base al testo del collegamento",
"intermediateLinkNotFound": "Link intermedio non trovato",
"intermediateLink": "Collegamento intermedio",
"exemptFromBackgroundUpdates": "Esente da aggiornamenti in secondo piano (se attivo)",
"bgUpdatesOnWiFiOnly": "Disattiva aggiornamenti in secondo piano quando non si usa il WiFi",
"autoSelectHighestVersionCode": "Auto-seleziona APK con versionCode più alto",
@ -256,13 +256,13 @@
"highlightTouchTargets": "Evidenzia elementi toccabili meno ovvi",
"pickExportDir": "Scegli cartella esp.",
"autoExportOnChanges": "Auto-esporta dopo modifiche",
"includeSettings": "Include settings",
"includeSettings": "Includi impostazioni",
"filterVersionsByRegEx": "Filtra versioni con espressione regolare",
"trySelectingSuggestedVersionCode": "Prova a selezionare APK con versionCode suggerito",
"dontSortReleasesList": "Conserva l'ordine di release da API",
"reverseSort": "Ordine inverso",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "Prendi il primo link",
"skipSort": "Salta ordinamento",
"debugMenu": "Menu di debug",
"bgTaskStarted": "Attività in secondo piano iniziata - controllo log.",
"runBgCheckNow": "Inizia aggiornamento in secondo piano ora",
@ -278,9 +278,29 @@
"completeAppInstallationNotifChannel": "Completa l'installazione dell'app",
"checkingForUpdatesNotifChannel": "Controllo degli aggiornamenti in corso",
"onlyCheckInstalledOrTrackOnlyApps": "Cerca aggiornamenti solo per app installate e app in Solo-Monitoraggio",
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"supportFixedAPKURL": "Supporta URL fissi di APK",
"selectX": "Seleziona {}",
"parallelDownloads": "Permetti download paralleli",
"installMethod": "Metodo d'installazione",
"normal": "Normale",
"root": "Root",
"shizukuBinderNotFound": "Shizuku non è in esecuzione",
"useSystemFont": "Usa i caratteri di sistema",
"systemFontError": "Errore durante il caricamento dei caratteri di sistema: {}",
"useVersionCodeAsOSVersion": "Usa il codice versione dell'app come versione rilevata dal sistema operativo",
"requestHeader": "Intestazione della richiesta",
"useLatestAssetDateAsReleaseDate": "Usa l'ultimo caricamento della risorsa come data di rilascio",
"defaultPseudoVersioningMethod": "Metodo di pseudoversione predefinito",
"partialAPKHash": "Hash APK parziale",
"APKLinkHash": "Hash collegamento APK",
"directAPKLink": "Collegamento APK diretto",
"pseudoVersionInUse": "È in uso una pseudo-versione",
"installed": "Installato",
"latest": "Ultimo",
"invertRegEx": "Inverti espressione regolare",
"note": "Nota",
"selfHostedNote": "Il menu a tendina \"{}\" può essere usato per raggiungere istanze autogestite/personali di qualsiasi fonte.",
"badDownload": "Non è stato possibile analizzare l'APK (download incompatibile o parziale).",
"removeAppQuestion": {
"one": "Rimuovere l'app?",
"other": "Rimuovere le app?"

View File

@ -51,9 +51,8 @@
"percentProgress": "ダウンロード中: {}%",
"pleaseWait": "しばらくお待ちください",
"updateAvailable": "アップデートが利用可能",
"estimateInBracketsShort": "(推定)",
"notInstalled": "未インストール",
"estimateInBrackets": "(推定)",
"pseudoVersion": "疑似バージョン",
"selectAll": "すべて選択",
"deselectX": "{}件の選択を解除",
"xWillBeRemovedButRemainInstalled": "{} はObtainiumから削除されますが、デバイスにはインストールされたままです。",
@ -73,6 +72,8 @@
"unpinFromTop": "トップから固定解除",
"resetInstallStatusForSelectedAppsQuestion": "選択したアプリのインストール状態をリセットしますか?",
"installStatusOfXWillBeResetExplanation": "選択したアプリのインストール状態がリセットされます。\n\nアップデートに失敗した場合など、Obtainiumに表示されるアプリのバージョンが正しくない場合に有効です。",
"customLinkMessage": "これらのリンクはObtainiumがインストールされたデバイスで動作します",
"shareAppConfigLinks": "アプリの設定をHTMLリンクとして共有する",
"shareSelectedAppURLs": "選択したアプリのURLを共有する",
"resetInstallStatus": "インストール状態をリセットする",
"more": "もっと見る",
@ -103,7 +104,6 @@
"importErrors": "インポートエラー",
"importedXOfYApps": "{} / {} アプリをインポートしました",
"followingURLsHadErrors": "以下のURLでエラーが発生しました:",
"okay": "OK",
"selectURL": "URLを選択",
"selectURLs": "URLを選択",
"pick": "選択",
@ -111,8 +111,6 @@
"dark": "ダーク",
"light": "ライト",
"followSystem": "システムに従う",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "ピュアブラックダークテーマを使用する",
"appSortBy": "アプリの並び方",
"authorName": "作者名/アプリ名",
@ -154,8 +152,8 @@
"appsRemovedNotifDescription": "アプリの読み込み中にエラーが発生したため、1つまたは複数のアプリが削除されたことをユーザーに通知する",
"xWasRemovedDueToErrorY": "このエラーのため、{} は削除されました: {}",
"completeAppInstallation": "アプリのインストールを完了する",
"obtainiumMustBeOpenToInstallApps": "アプリをインストールするにはObtainium開いている必要があります",
"completeAppInstallationNotifDescription": "アプリのインストールを完了するために、Obtainiumに戻る必要があります",
"obtainiumMustBeOpenToInstallApps": "アプリをインストールするにはObtainium開いている必要があります",
"completeAppInstallationNotifDescription": "アプリのインストールを完了するために、Obtainiumに戻る必要があります",
"checkingForUpdates": "アップデートを確認中",
"checkingForUpdatesNotifDescription": "アップデートを確認する際に表示される一時的な通知",
"pleaseAllowInstallPerm": "Obtainiumによるアプリのインストールを許可してください。",
@ -170,15 +168,14 @@
"lastUpdateCheckX": "最終アップデート確認: {}",
"remove": "削除",
"yesMarkUpdated": "はい、アップデート済みとしてマークします",
"fdroid": "F-Droid Official",
"fdroid": "F-Droid公式",
"appIdOrName": "アプリのIDまたは名前",
"appId": "App ID",
"appId": "アプリID",
"appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした",
"reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります",
"fdroidThirdPartyRepo": "F-Droid サードパーティリポジトリ",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "Steamモバイル",
"steamChat": "Steamチャット",
"install": "インストール",
"markInstalled": "インストール済みとしてマークする",
"update": "アップデート",
@ -212,10 +209,11 @@
"changes": "変更点",
"releaseDate": "リリース日",
"importFromURLsInFile": "ファイルOPMLなど内のURLからインポート",
"versionDetectionExplanation": "バージョン文字列とOSから検出されたバージョンを照合する",
"versionDetection": "バージョン検出",
"standardVersionDetection": "標準のバージョン検出",
"groupByCategory": "カテゴリ別にグループ化する",
"autoApkFilterByArch": "可能であればCPUアーキテクチャによるAPKのフィルタリングを試みる",
"autoApkFilterByArch": "可能であればCPUアーキテクチャによるAPKのフィルタリングを試みる",
"overrideSource": "ソースの上書き",
"dontShowAgain": "二度と表示しない",
"dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない",
@ -236,7 +234,7 @@
"addInfoInSettings": "設定でこの情報を追加してください。",
"githubSourceNote": "GitHubのレート制限はAPIキーを使うことで回避できます。",
"gitlabSourceNote": "GitLabのAPK抽出はAPIキーがないと動作しない場合があります。",
"sortByFileNamesNotLinks": "フルのリンクではなくファイル名でソートする",
"sortByLastLinkSegment": "リンクの最後のセグメントのみでソートする",
"filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする",
"customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')",
"appsPossiblyUpdated": "アプリのアップデートを試行",
@ -246,23 +244,25 @@
"backgroundUpdateReqsExplanation": "バックグラウンドアップデートは、すべてのアプリで可能とは限りません。",
"backgroundUpdateLimitsExplanation": "バックグラウンドアップデートが成功したかどうかは、Obtainiumを起動したときにしか判断できません。",
"verifyLatestTag": "'latest'タグを確認する",
"intermediateLinkRegex": "最初にアクセスする「中間」リンクフィルタリングする",
"intermediateLinkRegex": "訪問する「中間」リンクフィルタ",
"filterByLinkText": "テキストでリンクをフィルタリングする",
"intermediateLinkNotFound": "中間リンクが見つかりませんでした",
"intermediateLink": "中間リンク",
"exemptFromBackgroundUpdates": "バックグラウンドアップデートを行わない (有効な場合)",
"bgUpdatesOnWiFiOnly": "WiFiを使用していない場合バックグラウンドアップデートを無効にする",
"bgUpdatesOnWiFiOnly": "WiFiを使用していない場合バックグラウンドアップデートを無効にする",
"autoSelectHighestVersionCode": "最も高いバージョンコードのAPKを自動で選択する",
"versionExtractionRegEx": "バージョン抽出の正規表現",
"matchGroupToUse": "使用するマッチしたグループ",
"highlightTouchTargets": "目立たないタップ可能な対象をハイライトする",
"pickExportDir": "エクスポートディレクトリを選択",
"autoExportOnChanges": "変更があった際に自動でエクスポートする",
"includeSettings": "Include settings",
"includeSettings": "設定を含む",
"filterVersionsByRegEx": "正規表現でバージョンをフィルタリングする",
"trySelectingSuggestedVersionCode": "提案されたバージョンコードのAPKを選択する",
"dontSortReleasesList": "APIからのリリース順を保持する",
"reverseSort": "逆順ソート",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "最初のリンクを取得する",
"skipSort": "ソートをスキップする",
"debugMenu": "デバッグメニュー",
"bgTaskStarted": "バックグラウンドタスクが開始されました - ログを確認してください。",
"runBgCheckNow": "今すぐバックグラウンドでのアップデート確認を開始する",
@ -278,9 +278,29 @@
"completeAppInstallationNotifChannel": "アプリのインストールを完了する",
"checkingForUpdatesNotifChannel": "アップデートを確認中",
"onlyCheckInstalledOrTrackOnlyApps": "インストール済みのアプリと「追跡のみ」のアプリのアップデートのみを確認する",
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"supportFixedAPKURL": "固定されたAPKURLをサポートする",
"selectX": "{} 選択",
"parallelDownloads": "並行ダウンロードを許可する",
"installMethod": "インストール方法",
"normal": "通常",
"root": "Root",
"shizukuBinderNotFound": "Shizukuが起動していません",
"useSystemFont": "システムフォントを使用する",
"systemFontError": "システムフォントの読み込みエラー: {}",
"useVersionCodeAsOSVersion": "アプリのバージョンコードをOSで検出されたバージョンとして使用する",
"requestHeader": "リクエストヘッダー",
"useLatestAssetDateAsReleaseDate": "最新のアセットアップロードをリリース日として使用する",
"defaultPseudoVersioningMethod": "デフォルトの疑似バージョン管理方法",
"partialAPKHash": "部分的なAPKのハッシュ",
"APKLinkHash": "APKリンクのハッシュ",
"directAPKLink": "APKのダイレクトリンク",
"pseudoVersionInUse": "疑似バージョンが使用されています",
"installed": "インストール済み",
"latest": "最新",
"invertRegEx": "正規表現を反転",
"note": "注",
"selfHostedNote": "ドロップダウン\"{}\"を使用すると、あらゆるソースのセルフホスト/カスタムインスタンスにアクセスできます。",
"badDownload": "APK を解析できませんでした(互換性がないか、部分的にダウンロードされています)。",
"removeAppQuestion": {
"one": "アプリを削除しますか?",
"other": "アプリを削除しますか?"

View File

@ -22,10 +22,10 @@
"requiredInBrackets": "(Verplicht)",
"dropdownNoOptsError": "FOUTMELDING: DROPDOWN MOET TENMINSTE ÉÉN OPT HEBBEN",
"colour": "Kleur",
"githubStarredRepos": "GitHub Starred Repos",
"githubStarredRepos": "GitHub-repo's met ster",
"uname": "Gebruikersnaam",
"wrongArgNum": "Onjuist aantal argumenten verstrekt.",
"xIsTrackOnly": "{} is Track-Only",
"xIsTrackOnly": "{} is alleen tracken",
"source": "Bron",
"app": "App",
"appsFromSourceAreTrackOnly": "Apps van deze bron zijn 'Track-Only'.",
@ -42,7 +42,7 @@
"search": "Zoeken",
"additionalOptsFor": "Aanvullende opties voor {}",
"supportedSources": "Ondersteunde bronnen",
"trackOnlyInBrackets": "(Track-Only)",
"trackOnlyInBrackets": "(Alleen track)",
"searchableInBrackets": "(Doorzoekbaar)",
"appsString": "Apps",
"noApps": "Geen Apps",
@ -51,9 +51,8 @@
"percentProgress": "Vooruitgang: {}%",
"pleaseWait": "Even geduld",
"updateAvailable": "Update beschikbaar",
"estimateInBracketsShort": "(Ong.)",
"notInstalled": "Niet geinstalleerd",
"estimateInBrackets": "(Ongeveer)",
"pseudoVersion": "pseudo-versie",
"selectAll": "Selecteer alles",
"deselectX": "Deselecteer {}",
"xWillBeRemovedButRemainInstalled": "{} zal worden verwijderd uit Obtainium, maar blijft geïnstalleerd op het apparaat.",
@ -73,19 +72,21 @@
"unpinFromTop": "Losmaken van de bovenkant",
"resetInstallStatusForSelectedAppsQuestion": "Installatiestatus resetten voor geselecteerde apps?",
"installStatusOfXWillBeResetExplanation": "De installatiestatus van alle geselecteerde apps zal worden gereset.\n\nDit kan helpen wanneer de versie van de app die in Obtainium wordt weergegeven onjuist is vanwege mislukte updates of andere problemen.",
"customLinkMessage": "Deze links werken op apparaten waarop Obtainium is geïnstalleerd",
"shareAppConfigLinks": "App-configuratie delen als HTML-link",
"shareSelectedAppURLs": "Deel geselecteerde app URL's",
"resetInstallStatus": "Reset installatiestatus",
"more": "Meer",
"removeOutdatedFilter": "Verwijder out-of-date app filter",
"showOutdatedOnly": "Toon alleen out-of-date apps",
"filter": "Filter",
"filterActive": "Filter *",
"filterActive": "Filteren *",
"filterApps": "Filter apps",
"appName": "App naam",
"author": "Auteur",
"upToDateApps": "Up-to-date apps",
"nonInstalledApps": "Niet-geïnstalleerde apps",
"importExport": "Import/Export",
"importExport": "Importeren/Exporteren",
"settings": "Instellingen",
"exportedTo": "Geëxporteerd naar {}",
"obtainiumExport": "Obtainium export",
@ -98,12 +99,11 @@
"line": "Lijn",
"searchX": "Zoek {}",
"noResults": "Geen resultaten gevonden",
"importX": "Import {}",
"importX": "Importeer {}",
"importedAppsIdDisclaimer": "Geïmporteerde apps kunnen mogelijk onjuist worden weergegeven als \"Niet geïnstalleerd\".\nOm dit op te lossen, herinstalleer ze via Obtainium.\nDit zou geen invloed moeten hebben op app-gegevens.\n\nDit heeft alleen invloed op URL- en importmethoden van derden.",
"importErrors": "Import foutmeldingen",
"importedXOfYApps": "{} van {} apps geïmporteerd.",
"followingURLsHadErrors": "De volgende URL's bevatten fouten:",
"okay": "Ok",
"selectURL": "Selecteer URL",
"selectURLs": "Selecteer URL's",
"pick": "Kies",
@ -111,8 +111,6 @@
"dark": "Donker",
"light": "Licht",
"followSystem": "Volg systeem",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Gebruik zwart thema",
"appSortBy": "App sorteren op",
"authorName": "Auteur/Naam",
@ -159,7 +157,7 @@
"checkingForUpdates": "Controleren op updates",
"checkingForUpdatesNotifDescription": "Tijdelijke melding die verschijnt tijdens het controleren op updates",
"pleaseAllowInstallPerm": "Sta Obtainium toe om apps te installeren",
"trackOnly": "Track-Only",
"trackOnly": "Alleen track",
"errorWithHttpStatusCode": "Foutmelding {}",
"versionCorrectionDisabled": "Versiecorrectie uitgeschakeld (de plug-in lijkt niet te werken)",
"unknown": "Onbekend",
@ -170,15 +168,14 @@
"lastUpdateCheckX": "Laatste updatecontrole: {}",
"remove": "Verwijderen",
"yesMarkUpdated": "Ja, markeer als bijgewerkt",
"fdroid": "F-Droid Official",
"fdroid": "F-Droid-ambtenaar",
"appIdOrName": "App ID of naam",
"appId": "App ID",
"appId": "App-ID",
"appWithIdOrNameNotFound": "Er werd geen app gevonden met dat ID of die naam",
"reposHaveMultipleApps": "Repositories kunnen meerdere apps bevatten",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"fdroidThirdPartyRepo": "F-Droid Repository van derden",
"steamMobile": "Stoommobiel",
"steamChat": "Steamchat",
"install": "Installeren",
"markInstalled": "Als geïnstalleerd markere",
"update": "Update",
@ -198,7 +195,7 @@
"deleteCategoriesQuestion": "Categorieën verwijderen?",
"categoryDeleteWarning": "Alle apps in verwijderde categorieën worden teruggezet naar 'ongecategoriseerd'.",
"addCategory": "Categorie toevoegen",
"label": "Label",
"label": "Etiket",
"language": "Taal",
"copiedToClipboard": "Gekopieerd naar klembord",
"storagePermissionDenied": "Toegang tot opslag geweigerd",
@ -212,12 +209,13 @@
"changes": "Veranderingen",
"releaseDate": "Releasedatum",
"importFromURLsInFile": "Importeren vanaf URL's in een bestand (zoals OPML)",
"versionDetectionExplanation": "Versiereeks afstemmen met versie gedetecteerd door besturingssysteem",
"versionDetection": "Versieherkenning",
"standardVersionDetection": "Standaard versieherkenning",
"groupByCategory": "Groepeer op categorie",
"autoApkFilterByArch": "Poging om APK's te filteren op CPU-architectuur indien mogelijk",
"overrideSource": "Bron overschrijven",
"dontShowAgain": "Don't show this again",
"dontShowAgain": "Laat dit niet meer zien",
"dontShowTrackOnlyWarnings": "Geen waarschuwingen voor 'Track-Only' weergeven",
"dontShowAPKOriginWarnings": "APK-herkomstwaarschuwingen niet weergeven",
"moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave",
@ -236,7 +234,7 @@
"addInfoInSettings": "Voeg deze informatie toe in de instellingen.",
"githubSourceNote": "Beperkingen van GitHub kunnen worden vermeden door het gebruik van een API-sleutel.",
"gitlabSourceNote": "GitLab APK-extractie werkt mogelijk niet zonder een API-sleutel.",
"sortByFileNamesNotLinks": "Sorteren op bestandsnamen in plaats van volledige links.",
"sortByLastLinkSegment": "Sorteren op alleen het laatste segment van de link",
"filterReleaseNotesByRegEx": "Filter release-opmerkingen met een reguliere expressie.",
"customLinkFilterRegex": "Aangepaste APK-linkfilter met een reguliere expressie (Standaard '.apk$').",
"appsPossiblyUpdated": "Poging tot app-updates",
@ -246,8 +244,10 @@
"backgroundUpdateReqsExplanation": "Achtergrondupdates zijn mogelijk niet voor alle apps mogelijk.",
"backgroundUpdateLimitsExplanation": "Het succes van een installatie in de achtergrond kan alleen worden bepaald wanneer Obtainium is geopend.",
"verifyLatestTag": "Verifieer de 'Laatste'-tag",
"intermediateLinkRegex": "Filter voor een 'tussenliggende' link om eerst te bezoeken",
"intermediateLinkRegex": "Filter voor een 'Intermediaire' link om te bezoeken",
"filterByLinkText": "Links filteren op linktekst",
"intermediateLinkNotFound": "Tussenliggende link niet gevonden",
"intermediateLink": "Intermediaire link",
"exemptFromBackgroundUpdates": "Vrijgesteld van achtergrondupdates (indien ingeschakeld)",
"bgUpdatesOnWiFiOnly": "Achtergrondupdates uitschakelen wanneer niet verbonden met WiFi",
"autoSelectHighestVersionCode": "Automatisch de APK met de hoogste versiecode selecteren",
@ -256,13 +256,13 @@
"highlightTouchTargets": "Markeer minder voor de hand liggende aanraakdoelen.",
"pickExportDir": "Kies de exportmap",
"autoExportOnChanges": "Automatisch exporteren bij wijzigingen",
"includeSettings": "Include settings",
"includeSettings": "Instellingen opnemen",
"filterVersionsByRegEx": "Filter versies met een reguliere expressie",
"trySelectingSuggestedVersionCode": "Probeer de voorgestelde versiecode APK te selecteren",
"dontSortReleasesList": "Volgorde van releases behouden vanuit de API",
"reverseSort": "Sortering omkeren",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "Neem de eerste link",
"skipSort": "Sorteren overslaan",
"debugMenu": "Debug menu",
"bgTaskStarted": "Achtergrondtaak gestart - controleer de logs.",
"runBgCheckNow": "Voer nu een achtergrondupdatecontrole uit",
@ -278,9 +278,29 @@
"completeAppInstallationNotifChannel": "Voltooien van de app-installatie",
"checkingForUpdatesNotifChannel": "Controleren op updates",
"onlyCheckInstalledOrTrackOnlyApps": "Alleen geïnstalleerde en Track-Only apps controleren op updates",
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"supportFixedAPKURL": "Ondersteuning vaste APK URL's",
"selectX": "Selecteer {}",
"parallelDownloads": "Parallelle downloads toestaan",
"installMethod": "Installatiemethode",
"normal": "Normaal",
"root": "Wortel",
"shizukuBinderNotFound": "Shizuku draait niet",
"useSystemFont": "Gebruik het systeemlettertype",
"systemFontError": "Fout bij het laden van het systeemlettertype: {}",
"useVersionCodeAsOSVersion": "Gebruik app versieCode als door OS gedetecteerde versie",
"requestHeader": "Verzoekkoptekst",
"useLatestAssetDateAsReleaseDate": "Gebruik laatste upload als releasedatum",
"defaultPseudoVersioningMethod": "Standaard pseudo-versiebeheermethode",
"partialAPKHash": "Gedeeltelijke APK-hash",
"APKLinkHash": "APK-link-hash",
"directAPKLink": "Directe APK-link",
"pseudoVersionInUse": "Er is een pseudo-versie in gebruik",
"installed": "Geïnstalleerd",
"latest": "Laatste",
"invertRegEx": "Reguliere expressie omkeren",
"note": "Opmerking",
"selfHostedNote": "De \"{}\" dropdown kan gebruikt worden om zelf gehoste/aangepaste instanties van elke bron te bereiken.",
"badDownload": "De APK kon niet worden verwerkt (incompatibele of gedeeltelijke download)",
"removeAppQuestion": {
"one": "App verwijderen?",
"other": "Apps verwijderen?"
@ -303,7 +323,7 @@
},
"url": {
"one": "{} URL",
"other": "{} URLs"
"other": "{} URL's"
},
"minute": {
"one": "{} minuut",

View File

@ -6,7 +6,7 @@
"cantInstallOlderVersion": "Nie można zainstalować starszej wersji aplikacji",
"appIdMismatch": "Pobrane ID pakietu nie pasuje do istniejącego ID aplikacji",
"functionNotImplemented": "Ta klasa nie zaimplementowała tej funkcji",
"placeholder": "Placeholder",
"placeholder": "Zbiornik",
"someErrors": "Wystąpiły pewne błędy",
"unexpectedError": "Nieoczekiwany błąd",
"ok": "Okej",
@ -51,9 +51,8 @@
"percentProgress": "Postęp: {}%",
"pleaseWait": "Proszę czekać",
"updateAvailable": "Dostępna aktualizacja",
"estimateInBracketsShort": "(Szac.)",
"notInstalled": "Nie zainstalowano",
"estimateInBrackets": "(Szacunkowo)",
"pseudoVersion": "pseudowersja",
"selectAll": "Zaznacz wszystkie",
"deselectX": "Odznacz {}",
"xWillBeRemovedButRemainInstalled": "{} zostanie usunięty z Obtainium, ale pozostanie zainstalowany na urządzeniu.",
@ -73,6 +72,8 @@
"unpinFromTop": "Odepnij",
"resetInstallStatusForSelectedAppsQuestion": "Zresetować status instalacji dla wybranych aplikacji?",
"installStatusOfXWillBeResetExplanation": "Stan instalacji wybranych aplikacji zostanie zresetowany.\n\nMoże być to pomocne, gdy wersja aplikacji wyświetlana w Obtainium jest nieprawidłowa z powodu nieudanych aktualizacji lub innych problemów.",
"customLinkMessage": "Te linki działają na urządzeniach z zainstalowanym Obtainium",
"shareAppConfigLinks": "Udostępnij konfigurację aplikacji jako link HTML",
"shareSelectedAppURLs": "Udostępnij wybrane adresy URL aplikacji",
"resetInstallStatus": "Zresetuj stan instalacji",
"more": "Więcej",
@ -103,7 +104,6 @@
"importErrors": "Błędy importowania",
"importedXOfYApps": "Zaimportowano {} z {} aplikacji.",
"followingURLsHadErrors": "Następujące adresy URL zawierały błędy:",
"okay": "Okej",
"selectURL": "Wybierz adres URL",
"selectURLs": "Wybierz adresy URL",
"pick": "Wybierz",
@ -111,8 +111,6 @@
"dark": "Ciemny",
"light": "Jasny",
"followSystem": "Zgodny z systemem",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Użyj czarnego motywu",
"appSortBy": "Sortuj aplikacje według",
"authorName": "Autor/Nazwa",
@ -176,9 +174,8 @@
"appWithIdOrNameNotFound": "Nie znaleziono aplikacji o tym identyfikatorze lub nazwie",
"reposHaveMultipleApps": "Repozytoria mogą zawierać wiele aplikacji",
"fdroidThirdPartyRepo": "Zewnętrzne repo F-Droid",
"steam": "Steam",
"steamMobile": "Mobilny Steam",
"steamChat": "Steam Chat",
"steamChat": "Czat Steam",
"install": "Instaluj",
"markInstalled": "Oznacz jako zainstalowane",
"update": "Zaktualizuj",
@ -212,6 +209,7 @@
"changes": "Zmiany",
"releaseDate": "Data wydania",
"importFromURLsInFile": "Importuj z adresów URL w pliku (typu OPML)",
"versionDetectionExplanation": "Uzgodnij ciąg wersji z wersją wykrytą w systemie operacyjnym",
"versionDetection": "Wykrywanie wersji",
"standardVersionDetection": "Standardowe wykrywanie wersji",
"groupByCategory": "Grupuj według kategorii",
@ -236,7 +234,7 @@
"addInfoInSettings": "Dodaj tę informację w Ustawieniach.",
"githubSourceNote": "Limit żądań GitHub można ominąć za pomocą klucza API.",
"gitlabSourceNote": "Pozyskiwanie pliku APK z GitLab może nie działać bez klucza API.",
"sortByFileNamesNotLinks": "Sortuj wg nazw plików zamiast pełnych linków",
"sortByLastLinkSegment": "Sortuj tylko według ostatniego segmentu łącza",
"filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego",
"customLinkFilterRegex": "Filtruj linki APK według wyrażenia regularnego (domyślnie \".apk$\")",
"appsPossiblyUpdated": "Aplikacje mogły zostać zaktualizowane",
@ -246,8 +244,10 @@
"backgroundUpdateReqsExplanation": "Aktualizacje w tle mogą nie być możliwe dla wszystkich aplikacji.",
"backgroundUpdateLimitsExplanation": "Powodzenie instalacji w tle można określić dopiero po otwarciu Obtainium.",
"verifyLatestTag": "Zweryfikuj najnowszy tag",
"intermediateLinkRegex": "Filtr linków \"pośrednich\" do odwiedzenia w pierwszej kolejności",
"intermediateLinkRegex": "Filtruj link \"pośredni\" do odwiedzenia",
"filterByLinkText": "Filtruj linki według tekstu linku",
"intermediateLinkNotFound": "Nie znaleziono linku pośredniego",
"intermediateLink": "Łącze pośrednie",
"exemptFromBackgroundUpdates": "Wyklucz z uaktualnień w tle (jeśli są włączone)",
"bgUpdatesOnWiFiOnly": "Wyłącz aktualizacje w tle, gdy nie ma połączenia z Wi-Fi",
"autoSelectHighestVersionCode": "Automatycznie wybierz najwyższy kod wersji APK",
@ -256,13 +256,13 @@
"highlightTouchTargets": "Wyróżnij mniej oczywiste elementy dotykowe",
"pickExportDir": "Wybierz katalog eksportu",
"autoExportOnChanges": "Automatyczny eksport po wprowadzeniu zmian",
"includeSettings": "Include settings",
"includeSettings": "Dołącz ustawienia",
"filterVersionsByRegEx": "Filtruj wersje według wyrażenia regularnego",
"trySelectingSuggestedVersionCode": "Spróbuj wybierać sugerowany kod wersji APK",
"dontSortReleasesList": "Utrzymaj kolejność wydań z interfejsu API",
"reverseSort": "Odwrotne sortowanie",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "Weź pierwszy link",
"skipSort": "Pomiń sortowanie",
"debugMenu": "Menu debugowania",
"bgTaskStarted": "Uruchomiono zadanie w tle - sprawdź logi.",
"runBgCheckNow": "Wymuś sprawdzenie aktualizacji w tle",
@ -280,7 +280,27 @@
"onlyCheckInstalledOrTrackOnlyApps": "Sprawdzaj tylko zainstalowane i obserwowane aplikacje pod kątem aktualizacji",
"supportFixedAPKURL": "Obsługuj stałe adresy URL APK",
"selectX": "Wybierz {}",
"parallelDownloads": "Allow parallel downloads",
"parallelDownloads": "Zezwól na pobieranie równoległe",
"installMethod": "Metoda instalacji",
"normal": "Normalna",
"root": "Źródło",
"shizukuBinderNotFound": "Shizuku is not running",
"useSystemFont": "Użyj czcionki systemowej",
"systemFontError": "Błąd podczas ładowania czcionki systemowej: {}",
"useVersionCodeAsOSVersion": "Użyj kodu wersji aplikacji jako wersji wykrytej przez system operacyjny",
"requestHeader": "Nagłówek żądania",
"useLatestAssetDateAsReleaseDate": "Użyj najnowszego przesłanego zasobu jako daty wydania",
"defaultPseudoVersioningMethod": "Domyślna metoda pseudowersji",
"partialAPKHash": "Częściowy skrót APK",
"APKLinkHash": "Skrót łącza APK",
"directAPKLink": "Bezpośredni link APK",
"pseudoVersionInUse": "Pseudowersja jest w użyciu",
"installed": "Zainstalowano",
"latest": "Najnowszy",
"invertRegEx": "Odwróć wyrażenie regularne",
"note": "Uwaga",
"selfHostedNote": "Lista rozwijana \"{}\" może być używana do uzyskiwania dostępu do samodzielnie hostowanych / niestandardowych instancji dowolnego źródła.",
"badDownload": "Nie można przeanalizować pliku APK (niekompatybilny lub częściowo pobrany).",
"removeAppQuestion": {
"one": "Usunąć aplikację?",
"few": "Usunąć aplikacje?",

View File

@ -1,305 +1,325 @@
{
"invalidURLForSource": "URL {} inválida",
"noReleaseFound": "Não foi possivel encontrar uma versão adequada",
"noVersionFound": "Não foi possivel encontrar uma versão lançada",
"urlMatchesNoSource": "URL não corresponde a uma fonte conhecida",
"cantInstallOlderVersion": "Não pode instalar uma versão anterior de um App",
"appIdMismatch": "ID do pacote baixado não é igual ao ID do App instalado",
"noReleaseFound": "Não foi possível encontrar uma versão adequada",
"noVersionFound": "Não foi possível encontrar uma versão",
"urlMatchesNoSource": "A URL não corresponde a uma fonte conhecida",
"cantInstallOlderVersion": "Não é permitido instalar uma versão anterior de um aplicativo",
"appIdMismatch": "ID do pacote baixado não é igual ao ID do aplicativo instalado",
"functionNotImplemented": "Esta classe não implementou essa função",
"placeholder": "Espaço Reservado",
"someErrors": "Alguns Erros Ocorreram",
"unexpectedError": "Erro Inesperado",
"ok": "Ok",
"placeholder": "Espaço reservado",
"someErrors": "Alguns erros ocorreram",
"unexpectedError": "Erro inesperado",
"ok": "OK",
"and": "e",
"githubPATLabel": "Token de Acceso Pessoal do GitHub (Reduz tempos de espera)",
"githubPATLabel": "Token de acesso pessoal do GitHub\n(Reduz tempos de espera)",
"includePrereleases": "Incluir pré-lançamentos",
"fallbackToOlderReleases": "Retornar para versões anteriores",
"filterReleaseTitlesByRegEx": "Filtrar Titulos de Versões por Expressão Regular",
"invalidRegEx": "Expressão Regular Inválida",
"filterReleaseTitlesByRegEx": "Filtrar tulos de versões usando expressão regular",
"invalidRegEx": "Expressão regular inválida",
"noDescription": "Sem descrição",
"cancel": "Cancelar",
"continue": "Continuar",
"requiredInBrackets": "(Necessário)",
"dropdownNoOptsError": "ERRO: O DROPDOWN DEVE TER PELO MENOS UMA OPÇÃO",
"colour": "Cor",
"githubStarredRepos": "Favoritados no GitHub",
"githubStarredRepos": "repositórios favoritos no GitHub",
"uname": "Nome de usuário",
"wrongArgNum": "Número de argumentos errado",
"xIsTrackOnly": "{} é 'Apenas Seguir'",
"xIsTrackOnly": "{} é 'Apenas monitorar'",
"source": "Fonte",
"app": "App",
"appsFromSourceAreTrackOnly": "Os apps desta fonte são 'Apenas Seguir'.",
"youPickedTrackOnly": "Você selecionou a opção 'Apenas Seguir'.",
"trackOnlyAppDescription": "Esse App vai ser seguido por atualizações, mais o Obtainium não poderá baixa-lo ou instala-lo.",
"app": "Aplicativo",
"appsFromSourceAreTrackOnly": "Os aplicativos desta fonte são 'Apenas monitorar'.",
"youPickedTrackOnly": "Você selecionou a opção 'Apenas monitorar'.",
"trackOnlyAppDescription": "As atualizações desse aplicativo serão monitoradas, mas o Obtainium não poderá baixá-lo ou instalá-lo.",
"cancelled": "Cancelado",
"appAlreadyAdded": "App já adicionado",
"alreadyUpToDateQuestion": "App já atualizado?",
"addApp": "Adicionar App",
"appSourceURL": "URL de origem do App",
"appAlreadyAdded": "Aplicativo já adicionado",
"alreadyUpToDateQuestion": "Aplicativo já foi atualizado?",
"addApp": "Adicionar aplicativo",
"appSourceURL": "URL de origem do aplicativo",
"error": "Erro",
"add": "Adicionar",
"searchSomeSourcesLabel": "Procurar (Apenas Algumas Fontes)",
"searchSomeSourcesLabel": "Procurar (apenas algumas fontes)",
"search": "Procurar",
"additionalOptsFor": "Opções Adicionais para {}",
"supportedSources": "Fontes Compatíveis",
"trackOnlyInBrackets": "(Apenas Seguir)",
"searchableInBrackets": "(Pesquisável)",
"appsString": "Apps",
"noApps": "Sem Apps",
"noAppsForFilter": "Sem Apps para Filtrar",
"additionalOptsFor": "Opções adicionais para {}",
"supportedSources": "Fontes compatíveis",
"trackOnlyInBrackets": "(apenas monitorar)",
"searchableInBrackets": "(pesquisável)",
"appsString": "Aplicativos",
"noApps": "Não há aplicativos",
"noAppsForFilter": "Sem aplicativos para filtrar",
"byX": "Por {}",
"percentProgress": "Progresso: {}%",
"pleaseWait": "Por Favor Espere",
"updateAvailable": "Atualização Disponível",
"estimateInBracketsShort": "(Aprox.)",
"notInstalled": "Não Instalado",
"estimateInBrackets": "(Aproximado)",
"selectAll": "Selecionar All",
"pleaseWait": "Por favor, espere",
"updateAvailable": "Atualização disponível",
"notInstalled": "Não instalado",
"pseudoVersion": "pseudo-versão",
"selectAll": "Selecionar todos",
"deselectX": "Deselecionar {}",
"xWillBeRemovedButRemainInstalled": "{} sera removido do Obtainium mais permanecerá instalado no dispositivo.",
"removeSelectedAppsQuestion": "Remover Apps Selecionados?",
"removeSelectedApps": "Remover Apps Selecionados",
"xWillBeRemovedButRemainInstalled": "{} será removido do Obtainium mais permanecerá instalado no dispositivo.",
"removeSelectedAppsQuestion": "Remover aplicativos selecionados?",
"removeSelectedApps": "Remover aplicativos selecionados",
"updateX": "Atualizar {}",
"installX": "Instalar {}",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Apenas Seguir)\ncomo Atualizado",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Apenas monitorar)\ncomo Atualizado",
"changeX": "Mudar {}",
"installUpdateApps": "Instalar/Atualizar Apps",
"installUpdateSelectedApps": "Instalar/Atualizar Apps Selecionados",
"markXSelectedAppsAsUpdated": "Marcar {} Apps Delecionados como Atualizados?",
"installUpdateApps": "Instalar/Atualizar aplicativos",
"installUpdateSelectedApps": "Instalar/Atualizar aplicativos selecionados",
"markXSelectedAppsAsUpdated": "Marcar {} aplicativos selecionados como atualizados?",
"no": "Não",
"yes": "Sim",
"markSelectedAppsUpdated": "Marcar Apps Selecionados como Atualizados",
"markSelectedAppsUpdated": "Marcar aplicativos selecionados como Atualizados",
"pinToTop": "Fixar no topo",
"unpinFromTop": "Desafixar do topo",
"resetInstallStatusForSelectedAppsQuestion": "Reiniciar Status de Instalação para Apps Seleciondos?",
"installStatusOfXWillBeResetExplanation": "O status de instalação de qualquer app selecionado sera reiniciado.\n\nIsso pode ajudar quando uma versão de um App mostrada no Obtainium é incorreta devido a falhas ao atualizar ou outros problemas.",
"shareSelectedAppURLs": "Compartilhar URLs de Apps Selecionados",
"resetInstallStatus": "Reiniciar Status de Instalação",
"resetInstallStatusForSelectedAppsQuestion": "Reiniciar status de instalação nos aplicativos selecionados?",
"installStatusOfXWillBeResetExplanation": "O status de instalação de todos os aplicativos selecionados será reiniciado.\n\nIsso pode ajudar quando uma versão de um aplicativo mostrada no Obtainium é incorreta devido a falhas ao atualizar ou outros problemas.",
"customLinkMessage": "Esses links funcionam em dispositivos com o Obtainium instalado",
"shareAppConfigLinks": "Compartilhar configuração do aplicativo como link HTML",
"shareSelectedAppURLs": "Compartilhar URLs de aplicativos selecionados",
"resetInstallStatus": "Reiniciar status de instalação",
"more": "Mais",
"removeOutdatedFilter": "Remover Filtro de Apps Desatualizados",
"showOutdatedOnly": "Mostrar Apenas Apps Desatualizados",
"removeOutdatedFilter": "Remover filtro de aplicativos desatualizados",
"showOutdatedOnly": "Mostrar apenas aplicativos desatualizados",
"filter": "Filtro",
"filterActive": "Filtro *",
"filterApps": "Filtrar Apps",
"appName": "Nome do App",
"filterApps": "Filtrar aplicativos",
"appName": "Nome do aplicativo",
"author": "Autor",
"upToDateApps": "Apps Atualizados",
"nonInstalledApps": "Apps Não Instalados",
"upToDateApps": "Aplicativos atualizados",
"nonInstalledApps": "Aplicativos não instalados",
"importExport": "Importar/Exportar",
"settings": "Configurações",
"exportedTo": "Exportado para {}",
"obtainiumExport": "Exportar Obtainium",
"invalidInput": "Input Inválido",
"obtainiumExport": "Exportar dados do Obtainium",
"invalidInput": "Entrada inválida",
"importedX": "Importado {}",
"obtainiumImport": "Importar Obtainium",
"importFromURLList": "Importar de Lista de URLs",
"obtainiumImport": "Importar dados do Obtainium",
"importFromURLList": "Importar de lista de URLs",
"searchQuery": "Pesquisa",
"appURLList": "Lista de URLs de Apps",
"appURLList": "Lista de URLs de aplicativos",
"line": "Linha",
"searchX": "Pesquisa {}",
"searchX": "Pesquisar na/o {}",
"noResults": "Nenhum resultado encontrado",
"importX": "Importar {}",
"importedAppsIdDisclaimer": "Apps Importados podem ser mostrados incorretamente como \"Não Instalado\".\nPara consertar, reinstale-os usando o Obtainium.\nIsso não deve afetar dados do App.\n\nAfeta apenas métodos de importação de URL e de terceiros.",
"importErrors": "Erros de Importação",
"importedXOfYApps": "{} de {} Apps importados.",
"importedAppsIdDisclaimer": "Aplicativos Importados podem ser mostrados incorretamente como \"Não Instalado\".\nPara consertar, reinstale-os usando o Obtainium.\nIsso não deve afetar dados do aplicativo.\n\nAfeta apenas métodos de importação de URL e de terceiros.",
"importErrors": "Erros de importação",
"importedXOfYApps": "{} de {} aplicativos importados.",
"followingURLsHadErrors": "As seguintes URLs apresentaram erros:",
"okay": "Ok",
"selectURL": "Selecionar URL",
"selectURLs": "Selecionar URLs",
"pick": "Escolher",
"theme": "Tema",
"dark": "Escuro",
"light": "Claro",
"followSystem": "Seguir o Sistema",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Usar tema preto completamente escuro",
"appSortBy": "Classificar App por",
"followSystem": "Padrão do sistema",
"useBlackTheme": "Usar tema preto AMOLED",
"appSortBy": "Classificar aplicativo por",
"authorName": "Autor/Nome",
"nameAuthor": "Nome/Autor",
"asAdded": "Como Adicionado",
"appSortOrder": "Ordem de classificação de Apps",
"asAdded": "Como adicionado",
"appSortOrder": "Ordem de classificação de aplicativos",
"ascending": "Ascendente",
"descending": "Descendente",
"bgUpdateCheckInterval": "Intervalo de verificação de atualizações em segundo plano",
"neverManualOnly": "Nunca - Apenas Manual",
"bgUpdateCheckInterval": "Intervalo de verificação de atualizações em segundo-plano",
"neverManualOnly": "Nunca - apenas manual",
"appearance": "Aparência",
"showWebInAppView": "Mostrar páginas da internet em App view",
"pinUpdates": "Fixar atualizações no topo da visão de Apps",
"showWebInAppView": "Mostrar página web do aplicativo em informações do aplicativo",
"pinUpdates": "Fixar atualizações no topo da janela de aplicativos",
"updates": "Atualizações",
"sourceSpecific": "Específico a fonte",
"appSource": "Fonte do App",
"noLogs": "Sem Logs",
"appLogs": "Logs do App",
"sourceSpecific": "Token de acesso",
"appSource": "Fonte do aplicativo",
"noLogs": "Sem logs",
"appLogs": "Logs do aplicativo",
"close": "Fechar",
"share": "Compartilhar",
"appNotFound": "App não encontrado",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"appNotFound": "Aplicativo não encontrado",
"obtainiumExportHyphenatedLowercase": "exportação de obtainium",
"pickAnAPK": "Selecionar um APK",
"appHasMoreThanOnePackage": "{} tem mais de um pacote:",
"deviceSupportsXArch": "Seu dispositivo suporta a arquitetura de CPU {}.",
"deviceSupportsFollowingArchs": "Seu dispositivo suporta as seguintes arquiteturas de CPU:",
"warning": "Aviso",
"sourceIsXButPackageFromYPrompt": "A Fonte do App é '{}' mais o pacote lançado vem de '{}'. Continuar?",
"updatesAvailable": "Atualizações Disponíveis",
"updatesAvailableNotifDescription": "Notifica o usuário quando atualizações estão disponíveis um ou mais Apps seguidos pelo Obtainium",
"sourceIsXButPackageFromYPrompt": "A fonte do aplicativo é '{}' mas a origem do pacote é '{}'. Continuar?",
"updatesAvailable": "Atualizações disponíveis",
"updatesAvailableNotifDescription": "Notifica o usuário quando atualizações de um ou mais aplicativos monitorados pelo Obtainium estão disponíveis",
"noNewUpdates": "Sem novas atualizações.",
"xHasAnUpdate": "{} tem uma atualização.",
"appsUpdated": "Apps Atualizados",
"appsUpdatedNotifDescription": "Notifica o usuário quando atualizações para um ou mais Apps foram aplicadas em segundo plano",
"appsUpdated": "Aplicativos atualizados",
"appsUpdatedNotifDescription": "Notifica o usuário quando atualizações foram aplicadas em segundo-plano para um ou mais aplicativos ",
"xWasUpdatedToY": "{} foi atualizado para {}.",
"errorCheckingUpdates": "Erro ao Procurar por Atualizações",
"errorCheckingUpdatesNotifDescription": "Uma notificação que mostra quando a checagem por atualizações em segundo plano falha",
"appsRemoved": "Apps Removidos",
"appsRemovedNotifDescription": "Notifica o usuário quando um ou mais Apps foram removidos devido a erros ao carregá-los",
"errorCheckingUpdates": "Erro ao procurar por atualizações",
"errorCheckingUpdatesNotifDescription": "Uma notificação que mostra quando a checagem por atualizações em segundo-plano falha",
"appsRemoved": "Aplicativos removidos",
"appsRemovedNotifDescription": "Notifica o usuário quando um ou mais aplicativos foram removidos devido a erros de carregamento",
"xWasRemovedDueToErrorY": "{} foi removido devido a este erro: {}",
"completeAppInstallation": "Instalação completa do App",
"obtainiumMustBeOpenToInstallApps": "Obtainium deve estar aberto para instalar Apps",
"completeAppInstallationNotifDescription": "Pede ao usuário que retorne ao Obtainium para finalizar a instalação de um App",
"checkingForUpdates": "Checando por Atualizações",
"checkingForUpdatesNotifDescription": "Notificação transiente que aparece quando checando por atualizações",
"pleaseAllowInstallPerm": "Por favor, permita o Obtainium instalar Apps",
"trackOnly": "Apenas Seguir",
"completeAppInstallation": "Instalação do aplicativo concluída",
"obtainiumMustBeOpenToInstallApps": "Obtainium deve estar aberto para instalar os aplicativos",
"completeAppInstallationNotifDescription": "Pede ao usuário que retorne ao Obtainium para finalizar a instalação de um aplicativo",
"checkingForUpdates": "Verificando atualizações",
"checkingForUpdatesNotifDescription": "Notificação transiente que aparece quando o Obtainium está verificando se há atualizações",
"pleaseAllowInstallPerm": "Por favor, permita que o Obtainium possa instalar aplicativos",
"trackOnly": "Apenas monitorar",
"errorWithHttpStatusCode": "Erro {}",
"versionCorrectionDisabled": "Correção de versão desativada (plugin parece não funcionar)",
"unknown": "Desconhecido",
"none": "Nenhum",
"never": "Nunca",
"latestVersionX": "Última versão: {}",
"installedVersionX": "Versão Instalada: {}",
"lastUpdateCheckX": "Última Checagem por Atualização: {}",
"installedVersionX": "Versão instalada: {}",
"lastUpdateCheckX": "Última verificação de atualizações: {}",
"remove": "Remover",
"yesMarkUpdated": "Sim, Marcar como Atualizado",
"fdroid": "F-Droid Official",
"appIdOrName": "ID do App ou Nome",
"appId": "ID do App",
"appWithIdOrNameNotFound": "Nenhum App foi encontrado com esse ID ou nome",
"reposHaveMultipleApps": "Repositórios podem conter multiplos Apps",
"yesMarkUpdated": "Sim, marcar como atualizado",
"fdroid": "Oficial F-Droid",
"appIdOrName": "ID do aplicativo ou nome",
"appId": "ID do aplicativo",
"appWithIdOrNameNotFound": "Nenhum aplicativo foi encontrado com esse ID ou nome",
"reposHaveMultipleApps": "Repositórios podem conter múltiplos aplicativos",
"fdroidThirdPartyRepo": "Repositórios de terceiros F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "Steam para celular",
"steamChat": "Chat do Steam",
"install": "Instalar",
"markInstalled": "Marcar Instalado",
"markInstalled": "Marcar instalado",
"update": "Atualizar",
"markUpdated": "Marcar Atualizado",
"additionalOptions": "Opções Adicionais",
"disableVersionDetection": "Desativar Detecção de Versão",
"noVersionDetectionExplanation": "Essa opção deve apenas ser usada por Apps onde detecção de versão não funciona corretamente.",
"markUpdated": "Marcar como atualizado",
"additionalOptions": "Opções adicionais",
"disableVersionDetection": "Desativar detecção de versão",
"noVersionDetectionExplanation": "Essa opção deve apenas ser usada por aplicativos onde a detecção de versão não funciona corretamente.",
"downloadingX": "Baixando {}",
"downloadNotifDescription": "Notifica o usuário do progresso ao baixar um App",
"downloadNotifDescription": "Notifica o usuário o progresso do download de um aplicativo",
"noAPKFound": "APK não encontrado",
"noVersionDetection": "Sem Detecção de versão",
"noVersionDetection": "Sem detecção de versão",
"categorize": "Categorizar",
"categories": "Categorias",
"category": "Categoria",
"noCategory": "Sem Categoria",
"noCategories": "Sem Categoria",
"deleteCategoriesQuestion": "Deletar Categorias?",
"categoryDeleteWarning": "Todos os Apps em categorias removidas serão descategorizados.",
"addCategory": "Adicionar Categoria",
"noCategory": "Sem categoria",
"noCategories": "Sem categorias",
"deleteCategoriesQuestion": "Deletar categorias?",
"categoryDeleteWarning": "Todos os aplicativos em categorias removidas serão descategorizados.",
"addCategory": "Adicionar categoria",
"label": "Etiqueta",
"language": "Linguagem",
"copiedToClipboard": "Copiado para a área de transferência",
"storagePermissionDenied": "Permição ao armazenamento negada",
"selectedCategorizeWarning": "Isso vai substituir qualquer confirução de categoria para os Apps selecionados.",
"filterAPKsByRegEx": "Filtrar APKs por Expressão Regular",
"storagePermissionDenied": "Permissão de armazenamento negada",
"selectedCategorizeWarning": "Isso vai substituir qualquer configuração de categoria para os aplicativos selecionados.",
"filterAPKsByRegEx": "Filtrar APKs usando expressão regular",
"removeFromObtainium": "Remover do Obtainium",
"uninstallFromDevice": "Desinstalar do dispositivo",
"onlyWorksWithNonVersionDetectApps": "Apenas funciona para Apps com detecção de versão desativada.",
"releaseDateAsVersion": "Usar Data de Lançamento como Versão",
"onlyWorksWithNonVersionDetectApps": "Apenas funciona para aplicativos com detecção de versão desativada.",
"releaseDateAsVersion": "Usar data de lançamento como versão",
"releaseDateAsVersionExplanation": "Esta opção só deve ser usada para aplicativos onde a detecção de versão não funciona corretamente, mas há uma data de lançamento disponível.",
"changes": "Mudanças",
"releaseDate": "Data de Lançamento",
"importFromURLsInFile": "Importar de URLs em Arquivo (como OPML)",
"versionDetection": "Detecção de Versão",
"changes": "Alterações",
"releaseDate": "Data de lançamento",
"importFromURLsInFile": "Importar de URLs em arquivo (formato OPML)",
"versionDetectionExplanation": "Reconciliar string de versão com versão detectada no sistema operacional",
"versionDetection": "Detecção de versão",
"standardVersionDetection": "Detecção de versão padrão",
"groupByCategory": "Agroupar por Categoria",
"groupByCategory": "Agroupar por categoria",
"autoApkFilterByArch": "Tente filtrar APKs por arquitetura de CPU, se possível",
"overrideSource": "Substituir Fonte",
"overrideSource": "Substituir fonte",
"dontShowAgain": "Não mostrar isso novamente",
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas Seguir'",
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas monitorar'",
"dontShowAPKOriginWarnings": "Não mostrar avisos de origem da APK",
"moveNonInstalledAppsToBottom": "Mover Apps não instalados para o fundo da visão de Apps",
"gitlabPATLabel": "Token de Acceso Pessoal do Gitlab\n(Ativa Pesquisa e Melhor Descoberta de APKs)",
"moveNonInstalledAppsToBottom": "Mover aplicativos não instalados para o fundo da lista de aplicativos",
"gitlabPATLabel": "Token de acesso pessoal do Gitlab\n(Ativa pesquisa e melhora a descoberta de APKs)",
"about": "Sobre",
"requiresCredentialsInSettings": "{}: Isso requer credenciais adicionais (em Configurações)",
"checkOnStart": "Checar por atualizações ao iniciar ",
"tryInferAppIdFromCode": "Tente inferir o ID do App pelo código fonte",
"removeOnExternalUninstall": "Remover automaticamente Apps desinstalados externamente",
"pickHighestVersionCode": "Auto-selecionar o maior numero de versão do APK",
"checkUpdateOnDetailPage": "Checar por atualizações ao abrir a pagina de detalhes de um App",
"disablePageTransitions": "Desativar animações de transição de pagina",
"reversePageTransitions": "Reverter animações de transição de pagina",
"minStarCount": "Contagem Minima de Estrelas",
"checkOnStart": "Verificar se há atualizações ao iniciar",
"tryInferAppIdFromCode": "Tente inferir o ID do aplicativo pelo código-fonte",
"removeOnExternalUninstall": "Remover automaticamente aplicativos desinstalados externamente",
"pickHighestVersionCode": "Auto-selecionar o maior número de versão do APK",
"checkUpdateOnDetailPage": "Checar por atualizações ao abrir a página de detalhes de um aplicativo",
"disablePageTransitions": "Desativar animações de transição de página",
"reversePageTransitions": "Animações de transição de página invertidas",
"minStarCount": "Contagem nima de estrelas",
"addInfoBelow": "Adicionar essa informação abaixo.",
"addInfoInSettings": "Adicionar essa informação nas configurações.",
"githubSourceNote": "A limitação de taxa do GitHub pode ser evitada usando uma chave de API.",
"gitlabSourceNote": "A extração de APK do GitLab pode não funcionar sem uma chave de API.",
"sortByFileNamesNotLinks": "Classifique por nomes de arquivos em vez de links completos",
"filterReleaseNotesByRegEx": "Filtrar Notas de Lançamento por Expressão Regular",
"customLinkFilterRegex": "Filtro de Link Personalizado por Expressão Regular (Padrão '.apk$')",
"appsPossiblyUpdated": "Tentativas de atualização de Apps",
"appsPossiblyUpdatedNotifDescription": "Notifica o usuário de que atualizações de um ou mais Apps foram potencialmente aplicadas em segundo plano",
"gitlabSourceNote": "A extração de endereço de download do APK no GitLab provavelmente não funcione sem que seja fornecido uma chave de API.",
"sortByLastLinkSegment": "Ordenar apenas usando o último segmento do link",
"filterReleaseNotesByRegEx": "Filtrar notas de versão usando Regex",
"customLinkFilterRegex": "Filtro de link personalizado usando expressão regular (Padrão '.apk$')",
"appsPossiblyUpdated": "Tentativas de atualização de aplicativos",
"appsPossiblyUpdatedNotifDescription": "Notifica o usuário de que atualizações de um ou mais aplicativos foram potencialmente aplicadas em segundo-plano",
"xWasPossiblyUpdatedToY": "{} pode ter sido atualizado para {}.",
"enableBackgroundUpdates": "Ativar atualizações em segundo plano",
"backgroundUpdateReqsExplanation": "Atualizações em segundo plano podem não ser possíveis para todos os Apps.",
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo plano só pode ser determinado quando o Obtainium é aberto.",
"verifyLatestTag": "Verifique a 'ultima' etiqueta",
"intermediateLinkRegex": "Filtre por um Link 'Intermediário' para Visitar Primeiro",
"enableBackgroundUpdates": "Ativar atualizações em segundo-plano",
"backgroundUpdateReqsExplanation": "Atualizações em segundo-plano podem não ser possíveis para todos os aplicativos.",
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo-plano só pode ser determinado quando o Obtainium é aberto.",
"verifyLatestTag": "Verifique a 'última' etiqueta",
"intermediateLinkRegex": "Filtrar um link 'intermediário' para visitar",
"filterByLinkText": "Filtrar links pelo texto do link",
"intermediateLinkNotFound": "Link intermediário não encontrado",
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo plano (se ativadas)",
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo plano quando não estiver em WiFi",
"autoSelectHighestVersionCode": "Auto-selecionar o maior codigo de versão",
"versionExtractionRegEx": "RegEx para Extração de Versão",
"matchGroupToUse": "Grupo de Seleção para Usar",
"highlightTouchTargets": "Destaque areas de toque menos óbvias",
"pickExportDir": "Escolher Diretorio de Exportação",
"intermediateLink": "Link intermediário",
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo-plano (se ativadas)",
"bgUpdatesOnWiFiOnly": "Desative as atualizações em segundo-plano quando não estiver conectado no Wi-Fi",
"autoSelectHighestVersionCode": "Auto-selecionar a versão mais recente",
"versionExtractionRegEx": "Regex de extração de versão",
"matchGroupToUse": "Grupo correspondente a ser usado no Regex de extração de versão",
"highlightTouchTargets": "Realçar áreas sensíveis ao toque que são menos óbvias",
"pickExportDir": "Escolher diretório para exportação",
"autoExportOnChanges": "Auto-exportar em mudanças",
"includeSettings": "Include settings",
"filterVersionsByRegEx": "Filtrar Versões por Expressão Regular",
"includeSettings": "Incluir configurações",
"filterVersionsByRegEx": "Filtrar versões por expressão regular",
"trySelectingSuggestedVersionCode": "Tente selecionar a versão sugerida",
"dontSortReleasesList": "Reter a ordem de lançamento da API",
"reverseSort": "Ordenação reversa",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"debugMenu": "Menu Debug",
"bgTaskStarted": "Tarefa em segundo plano iniciada - verifique os logs.",
"runBgCheckNow": "Execute a verificação de atualização em segundo plano agora",
"versionExtractWholePage": "Aplicar Regex de Extração de Versão à Página Inteira",
"takeFirstLink": "Obter primeiro link",
"skipSort": "Ignorar ordenação",
"debugMenu": "Menu debug",
"bgTaskStarted": "Tarefa em segundo-plano iniciada - verifique os logs.",
"runBgCheckNow": "Execute agora em segundo-plano a verificação de atualizações",
"versionExtractWholePage": "Aplicar regex de extração de versão à página inteira",
"installing": "Instalando",
"skipUpdateNotifications": "Pular notificações de update",
"updatesAvailableNotifChannel": "Atualizações Disponíveis",
"appsUpdatedNotifChannel": "Apps Atualizados",
"appsPossiblyUpdatedNotifChannel": "Tentativas de atualização de Apps",
"errorCheckingUpdatesNotifChannel": "Erro ao Procurar por Atualizações",
"appsRemovedNotifChannel": "Apps Removidos",
"updatesAvailableNotifChannel": "Atualizações disponíveis",
"appsUpdatedNotifChannel": "Aplicativos atualizados",
"appsPossiblyUpdatedNotifChannel": "Tentativas de atualização de aplicativos",
"errorCheckingUpdatesNotifChannel": "Erro ao procurar por atualizações",
"appsRemovedNotifChannel": "Aplicativos removidos",
"downloadingXNotifChannel": "Baixando {}",
"completeAppInstallationNotifChannel": "Instalação completa do App",
"checkingForUpdatesNotifChannel": "Checando por Atualizações",
"onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates",
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"completeAppInstallationNotifChannel": "Instalação completa do aplicativo",
"checkingForUpdatesNotifChannel": "Checando por atualizações",
"onlyCheckInstalledOrTrackOnlyApps": "Apenas verificar atualizações de aplicativos instalados e 'Apenas monitorar'",
"supportFixedAPKURL": "Suporte a APK com URLs fixas",
"selectX": "Selecionar {}",
"parallelDownloads": "Permitir downloads paralelos",
"installMethod": "Método de instalação",
"normal": "Normal",
"root": "Root",
"shizukuBinderNotFound": "O Shizuku não está rodando",
"useSystemFont": "Usar fonte padrão do sistema",
"systemFontError": "Erro ao carregar a fonte do sistema: {}",
"useVersionCodeAsOSVersion": "Usar versionCode do aplicativo como versão detectada pelo sistema operacional",
"requestHeader": "Requisitar cabeçalho",
"useLatestAssetDateAsReleaseDate": "Use o último upload de recursos como data de lançamento",
"defaultPseudoVersioningMethod": "Método de pseudo-versionamento padrão",
"partialAPKHash": "Hash APK parcial",
"APKLinkHash": "Hash de links APK",
"directAPKLink": "Link direto do APK",
"pseudoVersionInUse": "Uma pseudo-versão está em uso",
"installed": "Instalado",
"latest": "Mais recente",
"invertRegEx": "Inverter expressão regular",
"note": "Nota",
"selfHostedNote": "O menu suspenso \"{}\" pode ser usado para acessar instâncias auto-hospedadas/personalizadas de qualquer fonte.",
"badDownload": "Não foi possível analisar o APK (transferência incompatível ou parcial)",
"removeAppQuestion": {
"one": "Remover App?",
"other": "Remover Apps?"
"one": "Remover aplicativo?",
"other": "Remover aplicativos?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Muitas solicitações (taxa limitada) - tente novamente em {} minuto",
"one": "Muitas solicitações (taxa de solicitações limitada) - tente novamente em {} minuto",
"other": "Muitas solicitações (taxa limitada) - tente novamente em {} minutos"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "A verificação de atualizações em segundo plano encontrou um {}, agendada uma nova verificação em {} minuto",
"other": "A verificação de atualizações em segundo plano encontrou um {}, agendada uma nova verificação em {} minutos"
"one": "A verificação de atualizações em segundo-plano encontrou um {}, agendada uma nova verificação em {} minuto",
"other": "A verificação de atualizações em segundo-plano encontrou um {}, agendada uma nova verificação em {} minutos"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "A verificação de atualizações em segundo plano encontrou {} atualização, o usuário sera notificado caso necessário",
"other": "A verificação de atualizações em segundo plano encontrou {} atualizações, o usuário sera notificado caso necessário"
"one": "A verificação de atualizações em segundo-plano encontrou {} atualização, o usuário sera notificado caso necessário",
"other": "A verificação de atualizações em segundo-plano encontrou {} atualizações, o usuário sera notificado caso necessário"
},
"apps": {
"one": "{} App",
"other": "{} Apps"
"one": "{} Aplicativo",
"other": "{} Aplicativos"
},
"url": {
"one": "{} URL",
@ -318,19 +338,19 @@
"other": "{} Dias"
},
"clearedNLogsBeforeXAfterY": {
"one": "Limpo {n} log (antes = {antes}, depois = {depois})",
"other": "Limpados {n} logs (antes = {antes}, depois = {depois})"
"one": "Foi limpo {n} log (antes = {antes}, depois = {depois})",
"other": "Foram limpos {n} logs (antes = {antes}, depois = {depois})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} e 1 outro app tem atualizações.",
"other": "{} e {} outros apps tem atualizações."
"one": "{} e um outro aplicativo possui atualizações.",
"other": "{} e {} outros aplicativo possuem atualizações."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} e 1 outro app foi atualizado.",
"other": "{} e {} outros apps foram atualizados."
"one": "{} e um outro aplicativo foram atualizado.",
"other": "{} e {} outros aplicativos foram atualizados."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} e 1 outro app pode ter sido atualizado.",
"other": "{} e {} outros apps podem ter sido atualizados."
"one": "{} e um outro aplicativo podem ter sido atualizados.",
"other": "{} e {} outros aplicativos podem ter sido atualizados."
}
}

View File

@ -51,9 +51,8 @@
"percentProgress": "Прогресс: {}%",
"pleaseWait": "Пожалуйста, подождите",
"updateAvailable": "Доступно обновление",
"estimateInBracketsShort": "(Оценка)",
"notInstalled": "Не установлено",
"estimateInBrackets": "(Оценка)",
"pseudoVersion": "псевдоверсия",
"selectAll": "Выбрать всё",
"deselectX": "Отменить выбор {}",
"xWillBeRemovedButRemainInstalled": "{} будет удалено из Obtainium, но останется на устройстве",
@ -73,6 +72,8 @@
"unpinFromTop": "Открепить",
"resetInstallStatusForSelectedAppsQuestion": "Сбросить статус установки для выбранных приложений?",
"installStatusOfXWillBeResetExplanation": "Статус установки для выбранных приложений будет сброшен.\n\nЭто может помочь, если версия приложения, отображаемая в Obtainium, некорректная — из-за неудачных обновлений или других проблем",
"customLinkMessage": "Эти ссылки работают на устройствах с установленным Obtainium",
"shareAppConfigLinks": "Поделиться конфигурацией приложения в виде HTML-ссылки",
"shareSelectedAppURLs": "Поделиться выбранными URL-адресами приложений",
"resetInstallStatus": "Сбросить статус установки",
"more": "Ещё",
@ -103,7 +104,6 @@
"importErrors": "Ошибка импорта",
"importedXOfYApps": "Импортировано приложений: {} из {}",
"followingURLsHadErrors": "При импорте следующие URL-адреса содержали ошибки:",
"okay": "Ok",
"selectURL": "Выбрать URL-адрес",
"selectURLs": "Выбрать URL-адреса",
"pick": "Выбрать",
@ -111,8 +111,6 @@
"dark": "Тёмная",
"light": "Светлая",
"followSystem": "Системная",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Использовать чёрную тему",
"appSortBy": "Сортировка приложений",
"authorName": "Автор/Название",
@ -134,7 +132,7 @@
"close": "Закрыть",
"share": "Поделиться",
"appNotFound": "Приложение не найдено",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"obtainiumExportHyphenatedLowercase": "получение-экспорт",
"pickAnAPK": "Выберите APK-файл",
"appHasMoreThanOnePackage": "{} имеет более одного пакета:",
"deviceSupportsXArch": "Ваше устройство поддерживает архитектуру процессора {}",
@ -176,9 +174,8 @@
"appWithIdOrNameNotFound": "Приложение с таким ID или названием не было найдено",
"reposHaveMultipleApps": "В хранилище несколько приложений",
"fdroidThirdPartyRepo": "Сторонние репозитории F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "Стим Мобайл",
"steamChat": "Стим-чат",
"install": "Установить",
"markInstalled": "Пометить как установленное",
"update": "Обновить",
@ -212,6 +209,7 @@
"changes": "Изменения",
"releaseDate": "Дата выпуска",
"importFromURLsInFile": "Импорт из файла URL-адресов (например: OPML)",
"versionDetectionExplanation": "Согласовать строку версии с версией, обнаруженной в ОС",
"versionDetection": "Определение версии",
"standardVersionDetection": "Стандартное",
"groupByCategory": "Группировать по категориям",
@ -236,7 +234,7 @@
"addInfoInSettings": "Добавьте эту информацию в Настройки",
"githubSourceNote": "Используя ключ API можно обойти лимит запросов GitHub",
"gitlabSourceNote": "Без ключа API может не работать извлечение APK с GitLab",
"sortByFileNamesNotLinks": "Сортировать по именам файлов, а не ссылкам целиком",
"sortByLastLinkSegment": "Сортировать только по последнему сегменту ссылки",
"filterReleaseNotesByRegEx": "Фильтровать примечания к выпуску\n(регулярное выражение)",
"customLinkFilterRegex": "Пользовательский фильтр ссылок APK\n(регулярное выражение, по умолчанию: '.apk$')",
"appsPossiblyUpdated": "Попытки обновления приложений",
@ -246,8 +244,10 @@
"backgroundUpdateReqsExplanation": "Фоновые обновления могут быть возможны не для всех приложений",
"backgroundUpdateLimitsExplanation": "Успешность фоновой установки можно определить только после открытия Obtainium",
"verifyLatestTag": "Проверять тег 'latest'",
"intermediateLinkRegex": "Фильтр промежуточных ссылок для первоочередного посещения\n(регулярное выражение)",
"intermediateLinkRegex": "Фильтр для \"промежуточной\" ссылки для посещения",
"filterByLinkText": "Фильтрация ссылок по тексту ссылки",
"intermediateLinkNotFound": "Промежуточная ссылка не найдена",
"intermediateLink": "Промежуточное звено",
"exemptFromBackgroundUpdates": "Исключить из фоновых обновлений (если включено)",
"bgUpdatesOnWiFiOnly": "Отключить фоновые обновления, если нет соединения с Wi-Fi",
"autoSelectHighestVersionCode": "Автоматически выбирать APK с актуальной версией кода",
@ -256,13 +256,13 @@
"highlightTouchTargets": "Выделить менее очевидные элементы управления касанием",
"pickExportDir": "Выбрать каталог для экспорта",
"autoExportOnChanges": "Автоэкспорт при изменениях",
"includeSettings": "Include settings",
"includeSettings": "Включить настройки",
"filterVersionsByRegEx": "Фильтровать версии по регулярному выражению",
"trySelectingSuggestedVersionCode": "Попробуйте выбрать предложенный код версии APK",
"dontSortReleasesList": "Сохранить порядок релизов от API",
"reverseSort": "Обратная сортировка",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "Взять первую ссылку",
"skipSort": "Пропустить сортировку",
"debugMenu": "Меню отладки",
"bgTaskStarted": "Фоновая задача начата — проверьте журналы",
"runBgCheckNow": "Запустить проверку фонового обновления сейчас",
@ -283,9 +283,24 @@
"parallelDownloads": "Разрешить параллельные загрузки",
"installMethod": "Метод установки",
"normal": "Нормальный",
"shizuku": "Shizuku",
"root": "Суперпользователь",
"shizukuBinderNotFound": "Shizuku не запущен",
"shizukuBinderNotFound": "Совместимый сервис Shizuku не найден",
"useSystemFont": "Использовать системный шрифт",
"systemFontError": "Ошибка загрузки системного шрифта: {}",
"useVersionCodeAsOSVersion": "Использовать код версии приложения как версию, обнаруженную ОС",
"requestHeader": "Заголовок запроса",
"useLatestAssetDateAsReleaseDate": "Использовать последнюю загрузку ресурса в качестве даты выпуска",
"defaultPseudoVersioningMethod": "Метод псевдоверсионирования по умолчанию",
"partialAPKHash": "Частичный хэш APK",
"APKLinkHash": "Хеш ссылки APK",
"directAPKLink": "Прямая ссылка на APK",
"pseudoVersionInUse": "Используется псевдоверсия",
"installed": "Установлен",
"latest": "Последний",
"invertRegEx": "Инвертировать регулярное выражение",
"note": "Примечание",
"selfHostedNote": "Выпадающий список \"{}\" можно использовать для доступа к самостоятельно размещенным/настроенным экземплярам любого источника.",
"badDownload": "APK не удалось разобрать (несовместимая или неполная загрузка)",
"removeAppQuestion": {
"one": "Удалить приложение?",
"other": "Удалить приложения?"

View File

@ -0,0 +1,119 @@
// Take one (hardcoded) translation file and ensure that all other translation files have the same keys in the same order
// Then report which other translation files have identical items (or auto-translate them if a DeepL API key is provided)
const fs = require('fs')
const https = require('https')
const deeplAPIKey = process.argv[2]
const neverAutoTranslate = {
steamMobile: ['*'],
steamChat: ['*'],
root: ['*'],
obtainiumExportHyphenatedLowercase: ['*'],
theme: ['de'],
appId: ['de']
}
const translateText = async (text, targetLang, authKey) => {
return new Promise((resolve, reject) => {
const postData = `text=${encodeURIComponent(text)}&target_lang=${encodeURIComponent(targetLang)}&source_lang=EN`
const options = {
hostname: 'api-free.deepl.com',
port: 443,
path: '/v2/translate',
method: 'POST',
headers: {
'Authorization': `DeepL-Auth-Key ${authKey}`,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
}
const req = https.request(options, (res) => {
let responseData = ''
res.on('data', (chunk) => {
responseData += chunk
})
res.on('end', () => {
try {
const jsonResponse = JSON.parse(responseData)
resolve(jsonResponse)
} catch (error) {
reject(error)
}
})
})
req.on('error', (error) => {
reject(error)
})
req.write(postData)
req.end()
})
}
const main = async () => {
const translationsDir = __dirname
const templateFile = `${translationsDir}/en.json`
const otherFiles = fs.readdirSync(translationsDir).map(f => {
return `${translationsDir}/${f}`
}).filter(f => f.endsWith('.json') && f != templateFile)
const templateTranslation = require(templateFile)
otherFiles.forEach(file => {
const thisTranslationOriginal = require(file)
const thisTranslationNew = {}
Object.keys(templateTranslation).forEach(k => {
thisTranslationNew[k] = thisTranslationOriginal[k] || templateTranslation[k]
})
fs.writeFileSync(file, `${JSON.stringify(thisTranslationNew, null, ' ')}\n`)
})
for (let i in otherFiles) {
const file = otherFiles[i]
const thisTranslation = require(file)
const translationKeys = Object.keys(templateTranslation)
for (let j in translationKeys) {
const k = translationKeys[j]
if (JSON.stringify(thisTranslation[k]) == JSON.stringify(templateTranslation[k])) {
const lang = file.split('/').pop().split('.')[0]
if (!neverAutoTranslate[k] || (neverAutoTranslate[k].indexOf('*') < 0 && neverAutoTranslate[k].indexOf(lang) < 0)) {
const reportLine = `${file} :::: ${k} :::: ${JSON.stringify(thisTranslation[k])}`
if (deeplAPIKey) {
const translateFunc = async (str) => {
const response = await translateText(str, lang, deeplAPIKey)
if (response.translations) {
return response.translations[0].text
} else {
throw JSON.stringify(response)
}
}
try {
if (typeof templateTranslation[k] == 'string') {
thisTranslation[k] = await translateFunc(thisTranslation[k])
} else {
const subKeys = Object.keys(templateTranslation[k])
for (let n in subKeys) {
const kk = subKeys[n]
thisTranslation[k][kk] = await translateFunc(thisTranslation[k][kk])
}
}
} catch (e) {
if (typeof e == 'string') {
console.log(`${reportLine} :::: ${e}`)
} else {
throw e
}
}
} else {
console.log(reportLine)
}
}
}
}
fs.writeFileSync(file, `${JSON.stringify(thisTranslation, null, ' ')}\n`)
}
}
main().catch(e => console.error)

View File

@ -5,26 +5,26 @@
"urlMatchesNoSource": "URL matchar inte känd källa",
"cantInstallOlderVersion": "Kan inte installera en äldre version av en app",
"appIdMismatch": "Nerladdat paket-ID matchar inte nuvarande App-ID",
"functionNotImplemented": "This class has not implemented this function",
"functionNotImplemented": "Den här klassen har inte implementerat den här funktionen",
"placeholder": "Platshållare",
"someErrors": "Några fel uppstod",
"unexpectedError": "Oväntat fel",
"ok": "Okej",
"and": "och",
"githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)",
"githubPATLabel": "GitHub Personal Access Token (ökar hastighetsgräns)",
"includePrereleases": "Inkludera förreleaser",
"fallbackToOlderReleases": "Fall tillbaka till äldre releaser",
"filterReleaseTitlesByRegEx": "Filter Release Titles by Regular Expression",
"invalidRegEx": "Invalid regular expression",
"filterReleaseTitlesByRegEx": "Filtrera utgivningstitlar efter reguljärt uttryck",
"invalidRegEx": "Ogiltigt reguljärt uttryck",
"noDescription": "Ingen beskrivning",
"cancel": "Avbryt",
"continue": "Fortsätt",
"requiredInBrackets": "(Kräver)",
"dropdownNoOptsError": "ERROR: DROPDOWN MUST HAVE AT LEAST ONE OPT",
"dropdownNoOptsError": "FEL: DROPDOWN MÅSTE HA MINST ETT OPT",
"colour": "Färg",
"githubStarredRepos": "GitHub Stjärnmärkta Förråd",
"uname": "Användarnamn",
"wrongArgNum": "Wrong number of arguments provided",
"wrongArgNum": "Fel antal argument har angetts",
"xIsTrackOnly": "{} är 'Följ-Endast'",
"source": "Källa",
"app": "App",
@ -48,12 +48,11 @@
"noApps": "Inga Appar",
"noAppsForFilter": "Inga Appar för Filter",
"byX": "Av {}",
"percentProgress": "Progress: {}%",
"percentProgress": "Framsteg: {}%",
"pleaseWait": "Vänta",
"updateAvailable": "Uppdatering Tillgänglig",
"estimateInBracketsShort": "(Est.)",
"notInstalled": "Inte Installerad",
"estimateInBrackets": "(Uppskattning)",
"pseudoVersion": "pseudoversion",
"selectAll": "Välj Alla",
"deselectX": "Avmarkera {}",
"xWillBeRemovedButRemainInstalled": "{} kommer tas bort från Obtainium men kommer vara fortsatt installerad på enheten.",
@ -73,12 +72,14 @@
"unpinFromTop": "Avnåla",
"resetInstallStatusForSelectedAppsQuestion": "Återställ Installationsstatus för valda Appar?",
"installStatusOfXWillBeResetExplanation": "Installationsstatusen för de markerade apparna kommer återställas.\n\n Detta kan hjälpa när appversionen visad i Obtanium är fel på grund av misslyckade uppdateringar eller andra orsaker.",
"customLinkMessage": "Dessa länkar fungerar på enheter med Obtainium installerat",
"shareAppConfigLinks": "Dela appkonfiguration som HTML-länk",
"shareSelectedAppURLs": "Dela Valda Appars URL:er",
"resetInstallStatus": "Återställ Installationstatus",
"more": "Mer",
"removeOutdatedFilter": "Ta bort Utgånga App-filtret",
"showOutdatedOnly": "Visa Endast Utgånga Appar",
"filter": "Filter",
"filter": "Filtrera",
"filterActive": "Filter *",
"filterApps": "Filtrera Appar",
"appName": "Appnamn",
@ -94,7 +95,7 @@
"obtainiumImport": "Obtainium Import",
"importFromURLList": "Importera från URL-lista",
"searchQuery": "Sökförfrågan",
"appURLList": "App URL List",
"appURLList": "App URL-lista",
"line": "Linje",
"searchX": "Sök {}",
"noResults": "Inga resultat",
@ -103,7 +104,6 @@
"importErrors": "Importfel",
"importedXOfYApps": "{} av {} Appar importerade.",
"followingURLsHadErrors": "Följande URL:er hade fel:",
"okay": "Okej",
"selectURL": "Välj URL",
"selectURLs": "Välj URL:er",
"pick": "Välj",
@ -111,18 +111,16 @@
"dark": "Mörkt",
"light": "Ljust",
"followSystem": "Följ System",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Använd svart tema",
"appSortBy": "Sortera Appar via",
"authorName": "Utvecklare/Namn",
"nameAuthor": "Namn/Utvecklare",
"asAdded": "As Added",
"appSortOrder": "App Sort Order",
"asAdded": "Som tillagd",
"appSortOrder": "Appsorteringsordning",
"ascending": "Stigande",
"descending": "Fallande",
"bgUpdateCheckInterval": "Bakgrundsuppdateringskollfrekvens",
"neverManualOnly": "Never - Manual Only",
"neverManualOnly": "Aldrig - Endast manuell",
"appearance": "Utseende",
"showWebInAppView": "Visa källans hemsida i appvyn",
"pinUpdates": "Fäst uppdateringar högst upp i appvyn",
@ -146,7 +144,7 @@
"noNewUpdates": "Inga nya uppdateringar.",
"xHasAnUpdate": "{} har en uppdatering.",
"appsUpdated": "Appar Uppdaterade",
"appsUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were applied in the background",
"appsUpdatedNotifDescription": "Meddelar användaren att uppdateringar av en eller flera appar har tillämpats i bakgrunden",
"xWasUpdatedToY": "{} uppdaterades till {}.",
"errorCheckingUpdates": "Fel vid uppdateringskoll",
"errorCheckingUpdatesNotifDescription": "En aviserings som visar när bakgrundsuppdateringarkollar misslyckas",
@ -157,13 +155,13 @@
"obtainiumMustBeOpenToInstallApps": "Obtainium måste vara öppet för att installera Appar",
"completeAppInstallationNotifDescription": "Frågar användaren att återvända till Obtaiunium när appinstallation är klar",
"checkingForUpdates": "Kollar efter Uppdateringar",
"checkingForUpdatesNotifDescription": "Transient notification that appears when checking for updates",
"checkingForUpdatesNotifDescription": "Övergående meddelande som visas när du söker efter uppdateringar",
"pleaseAllowInstallPerm": "Tillåt Obtanium att installera Appar",
"trackOnly": "Följ-Endast",
"errorWithHttpStatusCode": "Fel {}",
"versionCorrectionDisabled": "Versionskorrigering inaktiverat (plugin verkar inte fungera)",
"unknown": "Okänd",
"none": "None",
"none": "Ingen",
"never": "Aldrig",
"latestVersionX": "Senaste Version: {}",
"installedVersionX": "Installerad Version: {}",
@ -176,7 +174,6 @@
"appWithIdOrNameNotFound": "Ingen App funnen med det namnet eller ID",
"reposHaveMultipleApps": "Förråd kan innehålla flera ApparR",
"fdroidThirdPartyRepo": "F-Droid Tredjeparts Förråd",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"install": "Installera",
@ -185,9 +182,9 @@
"markUpdated": "Märk Uppdaterad",
"additionalOptions": "Ytterligare Alternativ",
"disableVersionDetection": "Inaktivera versionsdetektering",
"noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.",
"noVersionDetectionExplanation": "Det här alternativet bör endast användas för appar där versionsidentifiering inte fungerar korrekt.",
"downloadingX": "Laddar ner {}",
"downloadNotifDescription": "Notifies the user of the progress in downloading an App",
"downloadNotifDescription": "Meddelar användaren om framstegen med att ladda ner en app",
"noAPKFound": "Ingen APK funnen",
"noVersionDetection": "Ingen versiondetektering",
"categorize": "Kategorisera",
@ -198,90 +195,127 @@
"deleteCategoriesQuestion": "Ta Bort Kategorier?",
"categoryDeleteWarning": "Alla Appar i de borttagna kategorierna kommer att märkas som okategoriserade.",
"addCategory": "Lägg till Kategori",
"label": "Label",
"label": "Märka",
"language": "Språk",
"copiedToClipboard": "Kopierat till Urklipp",
"storagePermissionDenied": "Lagringsbehörighet nekad",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"selectedCategorizeWarning": "Detta kommer att ersätta alla befintliga kategoriinställningar för de valda apparna.",
"filterAPKsByRegEx": "Filtrera APK-filer efter reguljärt uttryck",
"removeFromObtainium": "Ta bort från Obtainium",
"uninstallFromDevice": "Avinstallera från Enheten",
"onlyWorksWithNonVersionDetectApps": "Fungerar bara för Appar med versionsdetektering inaktiverat..",
"releaseDateAsVersion": "Använd releasedatum som version",
"releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.",
"releaseDateAsVersionExplanation": "Det här alternativet bör endast användas för appar där versionsidentifiering inte fungerar korrekt, men ett releasedatum är tillgängligt.",
"changes": "Ändringar",
"releaseDate": "Releasedatum",
"importFromURLsInFile": "Importera från URL:er i fil (som OPML)",
"versionDetectionExplanation": "Sammanställ versionssträng med version upptäckt från OS",
"versionDetection": "Versionsdetektering",
"standardVersionDetection": "Standardversionsdetektering",
"groupByCategory": "Gruppera via Kategori",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"autoApkFilterByArch": "Försök att filtrera APK-filer efter CPU-arkitektur om möjligt",
"overrideSource": "Överskrid Källa",
"dontShowAgain": "Visa inte detta igen",
"dontShowTrackOnlyWarnings": "Visa inte 'Följ-Endast' varningar",
"dontShowAPKOriginWarnings": "Visa inte APK-ursprung varningar",
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"moveNonInstalledAppsToBottom": "Flytta icke-installerade appar till botten av appvyn",
"gitlabPATLabel": "GitLab Personal Access Token\\n(Möjliggör sökning och bättre APK-upptäckt)",
"about": "Om",
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Kolla efter uppdateringar vid start",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
"pickHighestVersionCode": "Auto-select highest version code APK",
"checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
"disablePageTransitions": "Disable page transition animations",
"reversePageTransitions": "Reverse page transition animations",
"tryInferAppIdFromCode": "Försök härleda app-id från källkoden",
"removeOnExternalUninstall": "Ta bort externt avinstallerade appar automatiskt",
"pickHighestVersionCode": "Välj automatiskt högsta versionskod APK",
"checkUpdateOnDetailPage": "Sök efter uppdateringar om att öppna en app-detaljsida",
"disablePageTransitions": "Inaktivera sidövergångsanimationer",
"reversePageTransitions": "Animeringar för omvänd sidövergång",
"minStarCount": "Minsta antal stjärnmarkeringar",
"addInfoBelow": "Lägg till denna information nedanför.",
"addInfoInSettings": "Lägg till denna information i Inställningar.",
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
"sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"githubSourceNote": "GitHub-hastighetsbegränsning kan undvikas med en API-nyckel.",
"gitlabSourceNote": "GitLab APK-extraktion kanske inte fungerar utan en API-nyckel.",
"sortByLastLinkSegment": "Sortera endast efter det sista segmentet av länken",
"filterReleaseNotesByRegEx": "Filtrera versionskommentarer efter reguljärt uttryck",
"customLinkFilterRegex": "Anpassad APK-länkfiltrera efter reguljärt uttryck (standard '.apk$')",
"appsPossiblyUpdated": "Appuppdateringar försökte",
"appsPossiblyUpdatedNotifDescription": "Meddelar användaren om att uppdateringar av en eller flera appar potentiellt har tillämpats i bakgrunden",
"xWasPossiblyUpdatedToY": "{} kan ha uppdaterats till {}.",
"enableBackgroundUpdates": "Aktivera Bakgrundsuppdateringar",
"backgroundUpdateReqsExplanation": "Bakgrundsuppdateringar är inte möjligt för alla appar.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"backgroundUpdateLimitsExplanation": "Framgången för en bakgrundsinstallation kan bara avgöras när Obtainium öppnas.",
"verifyLatestTag": "Verifiera 'senaste'-taggen",
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit First",
"intermediateLinkNotFound": "Intermediate link not found",
"intermediateLinkRegex": "Filtrera för en \"mellanliggande\" länk att besöka",
"filterByLinkText": "Filtrera länkar efter länktext",
"intermediateLinkNotFound": "Mellanlänk hittades inte",
"intermediateLink": "Mellanlänk",
"exemptFromBackgroundUpdates": "Undta från bakgrundsuppdateringar (om aktiverad)",
"bgUpdatesOnWiFiOnly": "Inaktivera Bakgrundsuppdateringar utan WiFi",
"autoSelectHighestVersionCode": "Auto-select highest versionCode APK",
"autoSelectHighestVersionCode": "Välj automatiskt högsta versionskod APK",
"versionExtractionRegEx": "Version Extraction RegEx",
"matchGroupToUse": "Match Group to Use",
"highlightTouchTargets": "Highlight less obvious touch targets",
"highlightTouchTargets": "Markera mindre uppenbara beröringsobjekt",
"pickExportDir": "Välj Exportsökväg",
"autoExportOnChanges": "Automatisk export vid ändringar",
"includeSettings": "Include settings",
"filterVersionsByRegEx": "Filter Versions by Regular Expression",
"trySelectingSuggestedVersionCode": "Try selecting suggested versionCode APK",
"dontSortReleasesList": "Retain release order from API",
"includeSettings": "Inkludera inställningar",
"filterVersionsByRegEx": "Filtrera versioner efter reguljärt uttryck",
"trySelectingSuggestedVersionCode": "Försök att välja föreslagen versionCode APK",
"dontSortReleasesList": "Behåll släpporder från API",
"reverseSort": "Omvänd sortering",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "Ta första länken",
"skipSort": "Hoppa över sortering",
"debugMenu": "Felsökningsmeny",
"bgTaskStarted": "Background task started - check logs.",
"bgTaskStarted": "Bakgrundsuppgift startat - kontrollera loggar.",
"runBgCheckNow": "Kör Bakgrundsuppdateringskoll Nu",
"parallelDownloads": "Allow parallel downloads",
"versionExtractWholePage": "Tillämpa Regex för versionssträngextraktion på hela sidan",
"installing": "Installerar",
"skipUpdateNotifications": "Hoppa över uppdateringsmeddelanden",
"updatesAvailableNotifChannel": "Uppdateringar tillgängliga",
"appsUpdatedNotifChannel": "Appar uppdaterade",
"appsPossiblyUpdatedNotifChannel": "Appuppdateringar försökte",
"errorCheckingUpdatesNotifChannel": "Fel vid sökning efter uppdateringar",
"appsRemovedNotifChannel": "Appar borttagna",
"downloadingXNotifChannel": "Laddar ner {}",
"completeAppInstallationNotifChannel": "Fullständig appinstallation",
"checkingForUpdatesNotifChannel": "Kollar efter uppdateringar",
"onlyCheckInstalledOrTrackOnlyApps": "Kontrollera bara installerade appar och endast spårbara appar för uppdateringar",
"supportFixedAPKURL": "Stöd fasta APK-webbadresser",
"selectX": "Välj {}",
"parallelDownloads": "Tillåt parallella nedladdningar",
"installMethod": "Installationsmetod",
"normal": "Vanligt",
"root": "Rot",
"shizukuBinderNotFound": "Shizuku is not running",
"useSystemFont": "Använd systemteckensnittet",
"systemFontError": "Fel vid laddning av systemteckensnittet: {}",
"useVersionCodeAsOSVersion": "Använd appversionskoden som OS-upptäckt version",
"requestHeader": "Rubrik för begäran",
"useLatestAssetDateAsReleaseDate": "Använd senaste tillgångsuppladdning som releasedatum",
"defaultPseudoVersioningMethod": "Standard pseudoversionsmetod",
"partialAPKHash": "Delvis APK-hash",
"APKLinkHash": "APK-länk Hash",
"directAPKLink": "Direkt APK-länk",
"pseudoVersionInUse": "En pseudoversion används",
"installed": "Installerad",
"latest": "Senast",
"invertRegEx": "Invertera reguljärt uttryck",
"note": "Anmärkning",
"selfHostedNote": "Rullgardinsmenyn \"{}\" kan användas för att nå självhostade/anpassade instanser av valfri källa.",
"badDownload": "APK kunde inte analyseras (inkompatibel eller partiell nedladdning)",
"removeAppQuestion": {
"one": "Ta Bort App?",
"other": "Ta Bort Appar?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Too many requests (rate limited) - try again in {} minute",
"other": "Too many requests (rate limited) - try again in {} minutes"
"one": "För många förfrågningar (begränsat pris) - försök igen om {} minut",
"other": "För många förfrågningar (begränsat pris) - försök igen om {} minuter"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "BG update checking encountered a {}, will schedule a retry check in {} minute",
"other": "BG update checking encountered a {}, will schedule a retry check in {} minutes"
"one": "BG-uppdateringskontroll påträffade en {}, kommer att schemalägga ett nytt försök om {} minut",
"other": "BG-uppdateringskontroll påträffade en {}, kommer att schemalägga ett nytt försök om {} minuter"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "BG update checking found {} update - will notify user if needed",
"other": "BG update checking found {} updates - will notify user if needed"
"one": "BG-uppdateringskontroll hittades {} uppdatering - kommer att meddela användaren om det behövs",
"other": "BG-uppdateringskontroll hittades {} uppdateringar - kommer att meddela användaren om det behövs"
},
"apps": {
"one": "{} App",

View File

@ -51,9 +51,8 @@
"percentProgress": "İlerleme: {}%",
"pleaseWait": "Lütfen Bekleyin",
"updateAvailable": "Güncelleme Var",
"estimateInBracketsShort": "(Est.)",
"notInstalled": "Yüklenmedi",
"estimateInBrackets": "(Tahmini)",
"pseudoVersion": "sözde versiyon",
"selectAll": "Hepsini Seç",
"deselectX": "{}'yi Seçimden Kaldır",
"xWillBeRemovedButRemainInstalled": "{} Obtainium'dan kaldırılacak ancak cihazınızda yüklü kalacaktır.",
@ -73,6 +72,8 @@
"unpinFromTop": "Üstten Kaldır",
"resetInstallStatusForSelectedAppsQuestion": "Seçilen Uygulamaların Yükleme Durumunu Sıfırlamak İstiyor musunuz?",
"installStatusOfXWillBeResetExplanation": "Seçilen Uygulamaların yükleme durumu sıfırlanacak.\n\nBu, Obtainium'da gösterilen uygulama sürümünün başarısız güncellemeler veya diğer sorunlar nedeniyle yanlış olması durumunda yardımcı olabilir.",
"customLinkMessage": "Bu bağlantılar Obtainium'un kurulu olduğu cihazlarda çalışır",
"shareAppConfigLinks": "Uygulama yapılandırmasını HTML bağlantısı olarak paylaş",
"shareSelectedAppURLs": "Seçilen Uygulama URL'larını Paylaş",
"resetInstallStatus": "Yükleme Durumunu Sıfırla",
"more": "Daha Fazla",
@ -103,7 +104,6 @@
"importErrors": "İçe Aktarma Hataları",
"importedXOfYApps": "{}'den {} Uygulama İçe Aktarıldı.",
"followingURLsHadErrors": "Aşağıdaki URL'lerde hatalar oluştu:",
"okay": "Tamam",
"selectURL": "URL Seç",
"selectURLs": "URL'leri Seç",
"pick": "Seç",
@ -111,8 +111,6 @@
"dark": "Koyu",
"light": "Aydınlık",
"followSystem": "Sistemi Takip Et",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Saf siyah koyu temasını kullan",
"appSortBy": "Uygulama Sıralama Ölçütü",
"authorName": "Yazar/Ad",
@ -176,8 +174,7 @@
"appWithIdOrNameNotFound": "Bu kimlik veya ada sahip bir uygulama bulunamadı",
"reposHaveMultipleApps": "Depolar birden fazla uygulama içerebilir",
"fdroidThirdPartyRepo": "F-Droid Üçüncü Taraf Depo",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamMobile": "Buhar Mobil",
"steamChat": "Steam Sohbet",
"install": "Yükle",
"markInstalled": "Yüklendi olarak İşaretle",
@ -212,6 +209,7 @@
"changes": "Değişiklikler",
"releaseDate": "Yayın Tarihi",
"importFromURLsInFile": "Dosyadaki URL'lerden İçe Aktar",
"versionDetectionExplanation": "Sürüm dizesini işletim sistemi tarafından algılanan sürümle uzlaştır",
"versionDetection": "Sürüm Tespiti",
"standardVersionDetection": "Standart sürüm tespiti",
"groupByCategory": "Kategoriye Göre Grupla",
@ -236,7 +234,7 @@
"addInfoInSettings": "Bu bilgiyi Ayarlar'da ekleyin.",
"githubSourceNote": "GitHub hız sınırlaması bir API anahtarı kullanılarak atlanabilir.",
"gitlabSourceNote": "GitLab APK çıkarma işlemi bir API anahtarı olmadan çalışmayabilir.",
"sortByFileNamesNotLinks": "Bağlantılar yerine dosya adlarına göre sırala",
"sortByLastLinkSegment": "Bağlantının yalnızca son bölümüne göre sırala",
"filterReleaseNotesByRegEx": "Sürüm Notlarını Düzenli İfade ile Filtrele",
"customLinkFilterRegex": "Özel APK Bağlantı Filtresi Düzenli İfade ile (Varsayılan '.apk$')",
"appsPossiblyUpdated": "Uygulama Güncellemeleri Denendi",
@ -246,8 +244,10 @@
"backgroundUpdateReqsExplanation": "Arka plan güncellemeleri tüm uygulamalar için mümkün olmayabilir.",
"backgroundUpdateLimitsExplanation": "Arka plan kurulumunun başarısı, Obtainium'un açıldığında ancak belirlenebilir.",
"verifyLatestTag": "'latest' etiketini doğrula",
"intermediateLinkRegex": "İlk Ziyaret Edilecek 'Ara' Bağlantısını Filtrele",
"intermediateLinkRegex": "Ziyaret Edilecek 'Orta Düzey' Bağlantıyı Filtrele",
"filterByLinkText": "Bağlantıları bağlantı metnine göre filtrele",
"intermediateLinkNotFound": "Ara bağlantı bulunamadı",
"intermediateLink": "Ara bağlantı",
"exemptFromBackgroundUpdates": "Arka plan güncellemelerinden muaf tut (etkinse)",
"bgUpdatesOnWiFiOnly": "WiFi olmadığında arka plan güncellemelerini devre dışı bırak",
"autoSelectHighestVersionCode": "Otomatik olarak en yüksek sürüm kodunu seç",
@ -256,13 +256,13 @@
"highlightTouchTargets": "Daha az belirgin dokunma hedeflerini vurgula",
"pickExportDir": "Dışa Aktarılacak Klasörü Seç",
"autoExportOnChanges": "Değişikliklerde otomatik olarak dışa aktar",
"includeSettings": "Include settings",
"includeSettings": "Ayarları dahil et",
"filterVersionsByRegEx": "Sürümleri Düzenli İfade ile Filtrele",
"trySelectingSuggestedVersionCode": "Önerilen sürüm kodunu seçmeyi dene",
"dontSortReleasesList": "API'den sıralama düzenini koru",
"reverseSort": "Ters sıralama",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "İlk bağlantıyı al",
"skipSort": "Sıralamayı atla",
"debugMenu": "Hata Ayıklama Menüsü",
"bgTaskStarted": "Arka plan görevi başladı - günlükleri kontrol et.",
"runBgCheckNow": "Arka Plan Güncelleme Kontrolünü Şimdi Çalıştır",
@ -278,9 +278,29 @@
"completeAppInstallationNotifChannel": "Uygulama Kurulumu Tamamlandı",
"checkingForUpdatesNotifChannel": "Güncellemeler Kontrol Ediliyor",
"onlyCheckInstalledOrTrackOnlyApps": "Yalnızca yüklü ve Yalnızca İzleme Uygulamalarını güncelleme",
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"supportFixedAPKURL": "Sabit APK URL'lerini destekleyin",
"selectX": "Seçme {}",
"parallelDownloads": "Paralel indirmelere izin ver",
"installMethod": "Kurulum yöntemi",
"normal": "Normal",
"root": "Kök",
"shizukuBinderNotFound": "Shizuku is not running",
"useSystemFont": "Sistem yazı tipini kullan",
"systemFontError": "Sistem yazı tipi yüklenirken hata oluştu: {}",
"useVersionCodeAsOSVersion": "Uygulama versionCode'unu işletim sistemi tarafından algılanan sürüm olarak kullan",
"requestHeader": "Başlık talep et",
"useLatestAssetDateAsReleaseDate": "Yayın tarihi olarak en son öğe yüklemesini kullan",
"defaultPseudoVersioningMethod": "Varsayılan Sözde Sürüm Oluşturma Yöntemi",
"partialAPKHash": "Kısmi APK Karması",
"APKLinkHash": "APK Bağlantı Karması",
"directAPKLink": "Doğrudan APK Bağlantısı",
"pseudoVersionInUse": "Sözde Versiyon Kullanımda",
"installed": "Kurulmuş",
"latest": "En sonuncu",
"invertRegEx": "Normal ifadeyi ters çevir",
"note": "Not",
"selfHostedNote": "\"{}\" açılır menüsü, herhangi bir kaynağın kendi kendine barındırılan/özel örneklerine ulaşmak için kullanılabilir.",
"badDownload": "APK ayrıştırılamadı (uyumsuz veya kısmi indirme)",
"removeAppQuestion": {
"one": "Uygulamayı Kaldır?",
"other": "Uygulamaları Kaldır?"

View File

@ -9,15 +9,15 @@
"placeholder": "Giữ chỗ",
"someErrors": "Đã xảy ra một số lỗi",
"unexpectedError": "Lỗi không mong đợi",
"ok": "Ôkê",
"ok": "OK",
"and": "và",
"githubPATLabel": "Mã thông báo truy cập cá nhân GitHub (Tăng tốc độ giới hạn)",
"githubPATLabel": "GitHub Token (Tăng tốc độ, giới hạn)",
"includePrereleases": "Bao gồm các bản phát hành trước",
"fallbackToOlderReleases": "Dự phòng về bản phát hành cũ hơn",
"filterReleaseTitlesByRegEx": "Lọc tiêu đề bản phát hành theo biểu thức chính quy",
"invalidRegEx": "Biểu thức chính quy không hợp lệ",
"noDescription": "Không có mô tả",
"cancel": "Hủy bỏ",
"cancel": "Hủy",
"continue": "Tiếp tục",
"requiredInBrackets": "(Yêu cầu)",
"dropdownNoOptsError": "LỖI: TẢI XUỐNG PHẢI CÓ ÍT NHẤT MỘT LỰA CHỌN",
@ -25,16 +25,16 @@
"githubStarredRepos": "Kho lưu trữ có gắn dấu sao GitHub",
"uname": "Tên người dùng",
"wrongArgNum": "Số lượng đối số được cung cấp sai",
"xIsTrackOnly": "{}là Chỉ-Theo dõi",
"xIsTrackOnly": "{} là Chỉ theo dõi",
"source": "Nguồn",
"app": "Ứng dụng",
"appsFromSourceAreTrackOnly": "Các ứng dụng từ nguồn này là 'Chỉ-Theo dõi'.",
"youPickedTrackOnly": "Bạn đã chọn tùy chọn 'Chỉ-Theo dõi'.",
"appsFromSourceAreTrackOnly": "Các ứng dụng từ nguồn này là 'Chỉ theo dõi'.",
"youPickedTrackOnly": "Bạn đã chọn tùy chọn 'Chỉ theo dõi'.",
"trackOnlyAppDescription": "Ứng dụng sẽ được theo dõi để cập nhật, nhưng Obtainium sẽ không thể tải xuống hoặc cài đặt nó.",
"cancelled": "Đã hủy",
"appAlreadyAdded": "Ứng dụng được thêm rồi",
"alreadyUpToDateQuestion": "Ứng dụng đã được cập nhật?",
"addApp": "Thêm ứng dụng",
"addApp": "Thêm",
"appSourceURL": "URL nguồn ứng dụng",
"error": "Lỗi",
"add": "Thêm",
@ -42,18 +42,17 @@
"search": "Tìm kiếm",
"additionalOptsFor": "Tùy chọn bổ sung cho {}",
"supportedSources": "Nguồn được hỗ trợ",
"trackOnlyInBrackets": "(Chỉ-Theo dõi)",
"trackOnlyInBrackets": "(Chỉ theo dõi)",
"searchableInBrackets": "(Có thể tìm kiếm)",
"appsString": "Ứng dụng",
"noApps": "Không có ứng dụng",
"noAppsForFilter": "Không có ứng dụng cho bộ lọc",
"byX": "Bởi {}",
"percentProgress": "Tiến triển: {}%",
"percentProgress": "Đang tải {}%",
"pleaseWait": "Vui lòng chờ",
"updateAvailable": "Có sẵn bản cập nhật",
"estimateInBracketsShort": "(Ước lượng.)",
"notInstalled": "Chưa cài đặt",
"estimateInBrackets": "(Ước lượng)",
"pseudoVersion": "phiên bản giả",
"selectAll": "Chọn tất cả",
"deselectX": "Bỏ chọn {}",
"xWillBeRemovedButRemainInstalled": "{} sẽ bị xóa khỏi Obtainium nhưng vẫn còn cài đặt trên thiết bị.",
@ -61,7 +60,7 @@
"removeSelectedApps": "Xóa ứng dụng đã chọn",
"updateX": "Cập nhật {}",
"installX": "Cài đặt {}",
"markXTrackOnlyAsUpdated": "Đánh dấu {}\n(Chỉ-Theo dõi)\nnhư là đã cập nhật",
"markXTrackOnlyAsUpdated": "Đánh dấu {}\n(Chỉ theo dõi)\nnhư là đã cập nhật",
"changeX": "Thay đổi {}",
"installUpdateApps": "Cài đặt/Cập nhật ứng dụng",
"installUpdateSelectedApps": "Cài đặt/Cập nhật ứng dụng đã chọn",
@ -73,6 +72,8 @@
"unpinFromTop": "Bỏ ghim khỏi đầu trang",
"resetInstallStatusForSelectedAppsQuestion": "Đặt lại trạng thái cài đặt cho ứng dụng đã chọn?",
"installStatusOfXWillBeResetExplanation": "Trạng thái cài đặt của mọi Ứng dụng đã chọn sẽ được đặt lại.\n\nĐiều này có thể hữu ích khi phiên bản Ứng dụng hiển thị trong Obtainium không chính xác do cập nhật không thành công hoặc các sự cố khác.",
"customLinkMessage": "Các liên kết này hoạt động trên các thiết bị có cài đặt Gainium",
"shareAppConfigLinks": "Chia sẻ cấu hình ứng dụng dưới dạng liên kết HTML",
"shareSelectedAppURLs": "Chia sẻ URL ứng dụng đã chọn",
"resetInstallStatus": "Đặt lại trạng thái cài đặt",
"more": "Nhiều hơn",
@ -88,10 +89,10 @@
"importExport": "Nhập/Xuất",
"settings": "Cài đặt",
"exportedTo": "Đã xuất sang {}",
"obtainiumExport": "Xuất Obtainium",
"obtainiumExport": "Xuất",
"invalidInput": "Đầu vào không hợp lệ",
"importedX": "Đã nhập {}",
"obtainiumImport": "Nhập Obtainium",
"obtainiumImport": "Nhập",
"importFromURLList": "Nhập từ danh sách URL",
"searchQuery": "Truy vấn tìm kiếm",
"appURLList": "Danh sách URL ứng dụng",
@ -103,7 +104,6 @@
"importErrors": "Lỗi nhập",
"importedXOfYApps": "{} trong số {} Ứng dụng đã được nhập.",
"followingURLsHadErrors": "Các URL sau có lỗi:",
"okay": "Ôkê",
"selectURL": "Chọn URL",
"selectURLs": "Chọn URL",
"pick": "Chọn",
@ -111,23 +111,21 @@
"dark": "Tối",
"light": "Sáng",
"followSystem": "Theo hệ thống",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Sử dụng chủ đề tối màu đen thuần túy",
"appSortBy": "Sắp xếp ứng dụng theo",
"authorName": "Tác giả/Tên",
"nameAuthor": "Tên/Tác giả",
"useBlackTheme": "Nền đen",
"appSortBy": "Sắp xếp ứng dụng",
"authorName": "Tác giả",
"nameAuthor": "Tên",
"asAdded": "Như đã thêm",
"appSortOrder": "Thứ tự sắp xếp ứng dụng",
"appSortOrder": "Thứ tự sắp xếp",
"ascending": "Tăng dần",
"descending": "Giảm dần",
"bgUpdateCheckInterval": "Khoảng thời gian kiểm tra cập nhật nền",
"neverManualOnly": "Không bao giờ - Chỉ thủ công",
"appearance": "Vẻ ngoài",
"showWebInAppView": "Hiển thị trang web Nguồn trong chế độ xem Ứng dụng",
"pinUpdates": "Ghim nội dung cập nhật lên đầu chế độ xem Ứng dụng",
"bgUpdateCheckInterval": "Thời gian tự động kiểm tra cập nhật",
"neverManualOnly": "Không bao giờ",
"appearance": "Hiển thị",
"showWebInAppView": "Hiển thị trang web Nguồn trong chế độ xem chi tiết Ứng dụng",
"pinUpdates": "Chuyển ứng dng có phiên bản mới lên đầu danh sách",
"updates": "Cập nhật",
"sourceSpecific": "Nguồn cụ thể",
"sourceSpecific": "Cài đặt Nguồn",
"appSource": "Nguồn ứng dụng",
"noLogs": "Không có nhật ký",
"appLogs": "Nhật ký ứng dụng",
@ -159,7 +157,7 @@
"checkingForUpdates": "Đang kiểm tra cập nhật",
"checkingForUpdatesNotifDescription": "Thông báo tạm thời xuất hiện khi kiểm tra bản cập nhật",
"pleaseAllowInstallPerm": "Vui lòng cho phép Obtainium cài đặt Ứng dụng",
"trackOnly": "Chỉ-Theo dõi",
"trackOnly": "Chỉ theo dõi",
"errorWithHttpStatusCode": "Lỗi {}",
"versionCorrectionDisabled": "Tính năng sửa phiên bản bị vô hiệu hóa (plugin dường như không hoạt động)",
"unknown": "Không xác định",
@ -176,9 +174,8 @@
"appWithIdOrNameNotFound": "Không tìm thấy ứng dụng nào có ID hoặc tên đó",
"reposHaveMultipleApps": "Kho có thể chứa nhiều Ứng dụng",
"fdroidThirdPartyRepo": "Kho lưu trữ bên thứ ba F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "Điện thoại di động hơi nước",
"steamChat": "Trò chuyện hơi nước",
"install": "Cài đặt",
"markInstalled": "Đánh dấu là đã cài đặt",
"update": "Cập nhật",
@ -212,16 +209,17 @@
"changes": "Thay đổi",
"releaseDate": "Ngày phát hành",
"importFromURLsInFile": "Nhập từ URL trong Tệp (như OPML)",
"versionDetectionExplanation": "Đối chiếu chuỗi phiên bản với phiên bản được phát hiện từ hệ điều hành",
"versionDetection": "Phát hiện phiên bản",
"standardVersionDetection": "Phát hiện phiên bản tiêu chuẩn",
"groupByCategory": "Nhóm theo thể loại",
"autoApkFilterByArch": "Cố gắng lọc APK theo kiến trúc CPU nếu có thể",
"overrideSource": "Ghi đè nguồn",
"dontShowAgain": "Đừng hiển thị thông tin này nữa",
"dontShowTrackOnlyWarnings": "Không hiển thị cảnh báo 'Chỉ-Theo dõi'",
"dontShowTrackOnlyWarnings": "Không hiển thị cảnh báo 'Chỉ theo dõi'",
"dontShowAPKOriginWarnings": "Không hiển thị cảnh báo nguồn gốc APK",
"moveNonInstalledAppsToBottom": "Di chuyển Ứng dụng chưa được cài đặt xuống cuối chế độ xem Ứng dụng",
"gitlabPATLabel": "Mã thông báo truy cập cá nhân GitLab\n(Cho phép tìm kiếm và khám phá APK tốt hơn)",
"moveNonInstalledAppsToBottom": "Chuyển Ứng dụng chưa được cài đặt xuống cuối danh sách",
"gitlabPATLabel": "GitLab Token\n(Cho phép tìm kiếm và lọc APK tốt hơn)",
"about": "Giới thiệu",
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Cài đặt)",
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
@ -229,25 +227,27 @@
"removeOnExternalUninstall": "Tự động xóa ứng dụng đã gỡ cài đặt bên ngoài",
"pickHighestVersionCode": "Tự động chọn APK mã phiên bản cao nhất",
"checkUpdateOnDetailPage": "Kiểm tra các bản cập nhật khi mở trang chi tiết Ứng dụng",
"disablePageTransitions": "Tắt hoạt ảnh chuyển trang",
"disablePageTransitions": "Tắt hiệu ứng chuyển trang",
"reversePageTransitions": "Hoạt ảnh chuyển đổi trang đảo ngược",
"minStarCount": "Số lượng sao tối thiểu",
"addInfoBelow": "Thêm thông tin này vào bên dưới.",
"addInfoInSettings": "Thêm thông tin này vào Cài đặt.",
"githubSourceNote": "Có thể tránh được việc giới hạn tốc độ GitHub bằng cách sử dụng khóa API.",
"gitlabSourceNote": "Trích xuất APK GitLab có thể không hoạt động nếu không có khóa API.",
"sortByFileNamesNotLinks": "Sắp xếp theo tên tệp thay vì liên kết đầy đủ",
"sortByLastLinkSegment": "Chỉ sắp xếp theo đoạn cuối của liên kết",
"filterReleaseNotesByRegEx": "Lọc ghi chú phát hành theo biểu thức chính quy",
"customLinkFilterRegex": "Bộ lọc liên kết APK tùy chỉnh theo biểu thức chính quy (Mặc định '.apk$')",
"appsPossiblyUpdated": "Đã cố gắng cập nhật ứng dụng",
"appsPossiblyUpdatedNotifDescription": "Thông báo cho người dùng rằng các bản cập nhật cho một hoặc nhiều Ứng dụng có khả năng được áp dụng trong nền",
"xWasPossiblyUpdatedToY": "{} có thể đã được cập nhật thành {}.",
"enableBackgroundUpdates": "Bật cập nhật nền",
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật nền cho tất cả ứng dụng.",
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt nền chỉ có thể được xác định khi mở Obtainium.",
"enableBackgroundUpdates": "Tự động cập nhật trong nền",
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật trong nền cho tất cả ứng dụng.",
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt trong nền chỉ có thể được xác định khi mở Obtainium.",
"verifyLatestTag": "Xác minh thẻ 'mới nhất'",
"intermediateLinkRegex": "Lọc tìm liên kết 'Trung gian' để truy cập trước",
"intermediateLinkRegex": "Lọc tìm liên kết 'Trung cấp' để truy cập",
"filterByLinkText": "Lọc liên kết theo văn bản liên kết",
"intermediateLinkNotFound": "Không tìm thấy liên kết trung gian",
"intermediateLink": "Liên kết trung gian",
"exemptFromBackgroundUpdates": "Miễn cập nhật nền (nếu được bật)",
"bgUpdatesOnWiFiOnly": "Tắt cập nhật nền khi không có WiFi",
"autoSelectHighestVersionCode": "Tự động chọn APK mã phiên bản cao nhất",
@ -255,14 +255,14 @@
"matchGroupToUse": "Nhóm đối sánh để sử dụng cho Regex trích xuất phiên bản",
"highlightTouchTargets": "Đánh dấu các mục tiêu cảm ứng ít rõ ràng hơn",
"pickExportDir": "Chọn thư mục xuất",
"autoExportOnChanges": "Tự động xuất khi thay đổi",
"includeSettings": "Include settings",
"autoExportOnChanges": "Tự động xuất",
"includeSettings": "Bao gồm cài đặt ứng dụng",
"filterVersionsByRegEx": "Lọc phiên bản theo biểu thức chính quy",
"trySelectingSuggestedVersionCode": "Thử chọn APK Mã phiên bản được đề xuất",
"dontSortReleasesList": "Giữ lại thứ tự phát hành từ API",
"reverseSort": "Sắp xếp ngược",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "Lấy liên kết đầu tiên",
"skipSort": "Bỏ qua việc sắp xếp",
"debugMenu": "Danh sách gỡ lỗi",
"bgTaskStarted": "Tác vụ nền đã bắt đầu - kiểm tra nhật ký.",
"runBgCheckNow": "Chạy kiểm tra cập nhật nền ngay bây giờ",
@ -277,10 +277,30 @@
"downloadingXNotifChannel": "Đang tải xuống {}",
"completeAppInstallationNotifChannel": "Hoàn tất cài đặt ứng dụng",
"checkingForUpdatesNotifChannel": "Đang kiểm tra cập nhật",
"onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra các ứng dụng đã cài đặt và Chỉ-Theo dõi để biết các bản cập nhật",
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra cập nhật các ứng dụng đã cài đặt và Chỉ theo dõi",
"supportFixedAPKURL": "Hỗ trợ URL APK cố định",
"selectX": "Lựa chọn {}",
"parallelDownloads": "Cho phép tải đa luồng",
"installMethod": "Phương thức cài đặt",
"normal": "Mặc định",
"root": "Root",
"shizukuBinderNotFound": "Shizuku chưa khởi động",
"useSystemFont": "Sử dụng phông chữ hệ thống",
"systemFontError": "Lỗi tải phông chữ hệ thống: {}",
"useVersionCodeAsOSVersion": "Sử dụng Mã phiên bản ứng dụng làm phiên bản do hệ điều hành phát hiện",
"requestHeader": "Tiêu đề yêu cầu",
"useLatestAssetDateAsReleaseDate": "Sử dụng nội dung tải lên mới nhất làm ngày phát hành",
"defaultPseudoVersioningMethod": "Phương pháp tạo phiên bản giả mặc định",
"partialAPKHash": "Băm APK một phần",
"APKLinkHash": "Băm liên kết APK",
"directAPKLink": "Liên kết APK trực tiếp",
"pseudoVersionInUse": "Phiên bản giả đang được sử dụng",
"installed": "Đã cài đặt",
"latest": "Mới nhất",
"invertRegEx": "Đảo ngược biểu thức chính quy",
"note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.",
"badDownload": "The APK could not be parsed (incompatible or partial download)",
"removeAppQuestion": {
"one": "Gỡ ứng dụng?",
"other": "Gỡ ứng dụng?"

View File

@ -22,11 +22,11 @@
"requiredInBrackets": "(必填)",
"dropdownNoOptsError": "错误:下拉菜单必须包含至少一个选项",
"colour": "配色",
"githubStarredRepos": "GitHub 已星标仓库",
"githubStarredRepos": "已星标的 GitHub 仓库",
"uname": "用户名",
"wrongArgNum": "参数数量错误",
"xIsTrackOnly": "{}为“仅追踪”模式",
"source": "源代码",
"xIsTrackOnly": "{}为“仅追踪”模式",
"source": "源",
"app": "应用",
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
"youPickedTrackOnly": "您选择了“仅追踪”。",
@ -51,17 +51,16 @@
"percentProgress": "进度:{}%",
"pleaseWait": "请稍候",
"updateAvailable": "更新可用",
"estimateInBracketsShort": "(推测)",
"notInstalled": "未安装",
"estimateInBrackets": "(推测)",
"pseudoVersion": "伪版本",
"selectAll": "全选",
"deselectX": "取消选择 {}",
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
"xWillBeRemovedButRemainInstalled": "{}将从 Obtainium 中删除,但仍安装在您的设备中。",
"removeSelectedAppsQuestion": "是否删除选中的应用?",
"removeSelectedApps": "删除选中的应用",
"updateX": "更新 {}",
"installX": "安装 {}",
"markXTrackOnlyAsUpdated": "将 {}\n仅追踪\n标记为已更新",
"markXTrackOnlyAsUpdated": "将{}\n仅追踪\n标记为已更新",
"changeX": "更改 {}",
"installUpdateApps": "安装/更新应用",
"installUpdateSelectedApps": "安装/更新选中的应用",
@ -73,6 +72,8 @@
"unpinFromTop": "取消置顶",
"resetInstallStatusForSelectedAppsQuestion": "是否重置选中应用的安装状态?",
"installStatusOfXWillBeResetExplanation": "选中应用的安装状态将会被重置。\n\n当更新安装失败或其他问题导致 Obtainium 中的应用版本显示错误时,可以尝试通过此方法解决。",
"customLinkMessage": "这些链接适用于安装了 Gettingium 的设备",
"shareAppConfigLinks": "将应用程序配置共享为 HTML 链接",
"shareSelectedAppURLs": "分享选中应用的 URL",
"resetInstallStatus": "重置安装状态",
"more": "更多",
@ -88,10 +89,10 @@
"importExport": "导入/导出",
"settings": "设置",
"exportedTo": "已导出至 {}",
"obtainiumExport": "Obtainium 导出",
"obtainiumExport": "导出 Obtainium",
"invalidInput": "无效的输入",
"importedX": "已导入 {}",
"obtainiumImport": "Obtainium 导入",
"obtainiumImport": "导入 Obtainium",
"importFromURLList": "从 URL 列表导入",
"searchQuery": "搜索查询",
"appURLList": "应用 URL 列表",
@ -103,7 +104,6 @@
"importErrors": "导入错误",
"importedXOfYApps": "已导入 {} 中的 {} 个应用。",
"followingURLsHadErrors": "下列 URL 存在错误:",
"okay": "好的",
"selectURL": "选择 URL",
"selectURLs": "选择 URL",
"pick": "选择",
@ -111,8 +111,6 @@
"dark": "深色",
"light": "浅色",
"followSystem": "跟随系统",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "使用纯黑深色主题",
"appSortBy": "排序依据",
"authorName": "作者 / 应用名称",
@ -124,7 +122,7 @@
"bgUpdateCheckInterval": "后台更新检查间隔",
"neverManualOnly": "手动",
"appearance": "外观",
"showWebInAppView": "应用详情页显示来源网页",
"showWebInAppView": "应用详情页显示来源网页",
"pinUpdates": "将待更新应用置顶",
"updates": "更新",
"sourceSpecific": "来源",
@ -134,9 +132,9 @@
"close": "关闭",
"share": "分享",
"appNotFound": "未找到应用",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"obtainiumExportHyphenatedLowercase": "获取出口",
"pickAnAPK": "选择一个 APK 文件",
"appHasMoreThanOnePackage": "{} 有多个架构可用:",
"appHasMoreThanOnePackage": "{}有多个架构可用:",
"deviceSupportsXArch": "您的设备支持 {} 架构。",
"deviceSupportsFollowingArchs": "您的设备支持下列架构:",
"warning": "警告",
@ -144,15 +142,15 @@
"updatesAvailable": "更新可用",
"updatesAvailableNotifDescription": "Obtainium 追踪的应用有更新时发送通知",
"noNewUpdates": "全部应用已是最新。",
"xHasAnUpdate": "{} 可以更新了。",
"xHasAnUpdate": "{}可以更新了。",
"appsUpdated": "应用已更新",
"appsUpdatedNotifDescription": "当应用在后台安装更新时发送通知",
"xWasUpdatedToY": "{} 已更新至 {}。",
"xWasUpdatedToY": "{}已更新至 {}。",
"errorCheckingUpdates": "检查更新出错",
"errorCheckingUpdatesNotifDescription": "当后台检查更新失败时显示的通知",
"appsRemoved": "应用已删除",
"appsRemovedNotifDescription": "当应用因加载出错而被删除时发送通知",
"xWasRemovedDueToErrorY": "{} 由于以下错误被删除:{}",
"xWasRemovedDueToErrorY": "{}由于以下错误被删除:{}",
"completeAppInstallation": "完成应用安装",
"obtainiumMustBeOpenToInstallApps": "必须启动 Obtainium 才能安装应用",
"completeAppInstallationNotifDescription": "提示返回 Obtainium 以完成应用的安装",
@ -160,7 +158,7 @@
"checkingForUpdatesNotifDescription": "检查更新时短暂显示的通知",
"pleaseAllowInstallPerm": "请授予 Obtainium 安装应用的权限",
"trackOnly": "仅追踪",
"errorWithHttpStatusCode": "错误 {}",
"errorWithHttpStatusCode": "{} 错误",
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
"unknown": "未知",
"none": "无",
@ -176,9 +174,8 @@
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
"reposHaveMultipleApps": "存储库中可能包含多个应用",
"fdroidThirdPartyRepo": "F-Droid 第三方存储库",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"steamMobile": "蒸汽手机",
"steamChat": "蒸汽聊天",
"install": "安装",
"markInstalled": "标记为已安装",
"update": "更新",
@ -186,11 +183,11 @@
"additionalOptions": "附加选项",
"disableVersionDetection": "禁用版本检测",
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
"downloadingX": "正在下载{}",
"downloadingX": "正在下载{}",
"downloadNotifDescription": "提示应用的下载进度",
"noAPKFound": "未找到 APK 文件",
"noVersionDetection": "禁用版本检测",
"categorize": "类",
"categorize": "类",
"categories": "类别",
"category": "类别",
"noCategory": "无类别",
@ -212,23 +209,24 @@
"changes": "更新日志",
"releaseDate": "发行日期",
"importFromURLsInFile": "从文件中的 URL 导入(如 OPML",
"versionDetectionExplanation": "将版本字符串与操作系统检测到的版本进行协调",
"versionDetection": "版本检测",
"standardVersionDetection": "常规版本检测",
"groupByCategory": "按类别分组显示",
"autoApkFilterByArch": "如果可能,尝试按设备支持的 CPU 架构筛选 APK 文件",
"overrideSource": "覆盖来源",
"dontShowAgain": "不再显示",
"dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
"dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
"dontShowTrackOnlyWarnings": "忽略“仅追踪”模式警告",
"dontShowAPKOriginWarnings": "忽略 APK 文件来源警告",
"moveNonInstalledAppsToBottom": "将未安装应用置底",
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
"about": "相关文档",
"requiresCredentialsInSettings": "{}: 此功能需要额外的凭据(在“设置”中添加)",
"requiresCredentialsInSettings": "{}此功能需要额外的凭据(在“设置”中添加)",
"checkOnStart": "启动时进行一次检查",
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
"removeOnExternalUninstall": "自动删除已卸载的外部应用",
"removeOnExternalUninstall": "自动删除列表中已卸载的应用",
"pickHighestVersionCode": "自动选择版本号最高的 APK 文件",
"checkUpdateOnDetailPage": "打开应用详情页时检查更新",
"checkUpdateOnDetailPage": "打开应用详情页时进行检查",
"disablePageTransitions": "禁用页面过渡动画效果",
"reversePageTransitions": "反转页面过渡动画效果",
"minStarCount": "最小星标数",
@ -236,9 +234,9 @@
"addInfoInSettings": "在“设置”中添加此凭据。",
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
"gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。",
"sortByFileNamesNotLinks": "使用文件名代替链接进行排序",
"sortByLastLinkSegment": "仅根据链接的末尾部分进行筛选",
"filterReleaseNotesByRegEx": "筛选发行说明(正则表达式)",
"customLinkFilterRegex": "筛选自定义来源 APK 文件链接\n正则表达式默认匹配模式为“.apk$”)",
"customLinkFilterRegex": "筛选自定义来源 APK 文件链接\n正则表达式默认匹配模式为“.apk$”)",
"appsPossiblyUpdated": "已尝试更新应用",
"appsPossiblyUpdatedNotifDescription": "当应用已尝试在后台更新时发送通知",
"xWasPossiblyUpdatedToY": "已尝试将“{}”更新至 {}。",
@ -246,27 +244,29 @@
"backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。",
"backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。",
"verifyLatestTag": "验证“Latest”标签",
"intermediateLinkRegex": "筛选首先访问的“中转链接(正则表达式)",
"intermediateLinkNotFound": "未找到“中转”链接",
"exemptFromBackgroundUpdates": "禁用后台更新\n如果已经全局启用",
"intermediateLinkRegex": "筛选中转链接(正则表达式)",
"filterByLinkText": "根据链接文本进行筛选",
"intermediateLinkNotFound": "未找到中转链接",
"intermediateLink": "中转链接",
"exemptFromBackgroundUpdates": "禁用后台更新(如果已经全局启用)",
"bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新",
"autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件",
"versionExtractionRegEx": "提取版本号(正则表达式)",
"versionExtractionRegEx": "版本号提取规则(正则表达式)",
"matchGroupToUse": "引用的捕获组",
"highlightTouchTargets": "突出展示不明显的触摸区域",
"pickExportDir": "选择导出文件夹",
"autoExportOnChanges": "数据变更时自动导出",
"includeSettings": "Include settings",
"includeSettings": "同时导出应用设置",
"filterVersionsByRegEx": "筛选版本号(正则表达式)",
"trySelectingSuggestedVersionCode": "尝试选择推荐版本的 APK 文件",
"dontSortReleasesList": "保持来自 API 的发行顺序",
"reverseSort": "反转排序",
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"takeFirstLink": "选取第一个链接",
"skipSort": "不进行排序",
"debugMenu": "调试选项",
"bgTaskStarted": "后台任务已启动 - 详见日志",
"runBgCheckNow": "立即进行后台更新检查",
"versionExtractWholePage": "将提取版本号的正则表达式应用于整页面",
"versionExtractWholePage": "将版本号提取规则应用于整页面",
"installing": "正在安装",
"skipUpdateNotifications": "忽略更新通知",
"updatesAvailableNotifChannel": "更新可用",
@ -274,13 +274,33 @@
"appsPossiblyUpdatedNotifChannel": "已尝试更新应用",
"errorCheckingUpdatesNotifChannel": "检查更新出错",
"appsRemovedNotifChannel": "应用已删除",
"downloadingXNotifChannel": "正在下载{}",
"downloadingXNotifChannel": "正在下载{}",
"completeAppInstallationNotifChannel": "完成应用安装",
"checkingForUpdatesNotifChannel": "正在检查更新",
"onlyCheckInstalledOrTrackOnlyApps": "只已安装和“仅追踪”的应用进行更新检查",
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"onlyCheckInstalledOrTrackOnlyApps": "只检查已安装和“仅追踪”的应用",
"supportFixedAPKURL": "支持固定的 APK 文件链接",
"selectX": "选择{}",
"parallelDownloads": "启用并行下载",
"installMethod": "安装方式",
"normal": "常规",
"root": "root",
"shizukuBinderNotFound": "未发现兼容的 Shizuku 服务",
"useSystemFont": "使用系统字体",
"systemFontError": "加载系统字体出错:{}",
"useVersionCodeAsOSVersion": "使用应用程序版本代码作为操作系统检测到的版本",
"requestHeader": "请求标头",
"useLatestAssetDateAsReleaseDate": "使用最新资产上传作为发布日期",
"defaultPseudoVersioningMethod": "默认伪版本控制方法",
"partialAPKHash": "部分 APK 哈希值",
"APKLinkHash": "APK 链接哈希",
"directAPKLink": "直接 APK 链接",
"pseudoVersionInUse": "伪版本正在使用",
"installed": "已安装",
"latest": "最新的",
"invertRegEx": "反转正则表达式",
"note": "备注",
"selfHostedNote": "{}\"下拉菜单可用于访问任何来源的自托管/自定义实例。",
"badDownload": "无法解析 APK不兼容或部分下载",
"removeAppQuestion": {
"one": "是否删除应用?",
"other": "是否删除应用?"
@ -322,15 +342,15 @@
"other": "清除了 {n} 个日志({before} 之前,{after} 之后)"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} 和另外 1 个应用可以更新了。",
"other": "{} 和另外 {} 个应用可以更新了。"
"one": "{}和另外 1 个应用可以更新了。",
"other": "{}和另外 {} 个应用可以更新了。"
},
"xAndNMoreUpdatesInstalled": {
"one": "{} 和另外 1 个应用已更新。",
"other": "{} 和另外 {} 个应用已更新。"
"other": "{}和另外 {} 个应用已更新。"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} 和另外 1 个应用已尝试更新。",
"other": "{} 和另外 {} 个应用已尝试更新。"
"other": "{}和另外 {} 个应用已尝试更新。"
}
}

View File

@ -7,6 +7,10 @@ trap "cd "$CURR_DIR"" EXIT
if [ -z "$1" ]; then
git fetch && git merge origin/main && git push # Typically run after a PR to main, so bring dev up to date
fi
cd .flutter
git fetch
git checkout "$(flutter --version | head -2 | tail -1 | awk '{print $4}')" # Ensure included Flutter submodule version equals my environment
cd ..
rm ./build/app/outputs/flutter-apk/* 2>/dev/null # Get rid of older builds if any
flutter build apk --flavor normal && flutter build apk --split-per-abi --flavor normal # Build (both split and combined APKs)
for file in ./build/app/outputs/flutter-apk/app-*normal*.apk*; do mv "$file" "${file//-normal/}"; done

View File

@ -0,0 +1,54 @@
<p>Obtainium allows you to install and update Apps directly from their releases pages, and receive notifications when new releases are made available.</p>
<p>Read the <a href="https://github.com/ImranR98/Obtainium/wiki">Wiki</a></p>
<p>
<b>Currently supported App sources:</b>
</p>
<ul>
<li>
<p>Open Source - General:</p>
<ul>
<li>GitHub</li>
<li>GitLab</li>
<li>Codeberg</li>
<li>F-Droid</li>
<li>Third Party F-Droid Repos</li>
<li>IzzyOnDroid</li>
<li>SourceForge</li>
<li>SourceHut</li>
</ul>
</li>
<li>
<p>Other - General:</p>
<ul>
<li>APKPure</li>
<li>Aptoide</li>
<li>Uptodowng</li>
<li>APKMirror (Track-Only)</li>
<li>Huawei AppGallery</li>
<li>Jenkins Jobs</li>
</ul>
</li>
<li>
<p>Open Source - App-Specific:</p>
<ul>
<li>Mullvad</li>
<li>Signal</li>
<li>VLC</li>
</ul>
</li>
<li>
<p>Other - App-Specific:</p>
<ul>
<li>WhatsApp</li>
<li>Telegram App</li>
<li>Neutron Code</li>
</ul>
</li>
<li><p>"HTML" (Fallback): Any other URL that returns an HTML page with links to APK files</p></li>
</ul>
<p>
<b>Limitations:</b>
</p>
<p>
For some sources, data is gathered using Web scraping and can easily break due to changes in website design. In such cases, more reliable methods may be unavailable.
</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

View File

@ -0,0 +1 @@
Get android app updates directly from the source

View File

@ -0,0 +1 @@
Obtainium

View File

@ -5,17 +5,20 @@ import 'package:obtainium/providers/source_provider.dart';
class APKCombo extends AppSource {
APKCombo() {
host = 'apkcombo.com';
hosts = ['apkcombo.com'];
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/+[^/]+/+[^/]+');
var match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+',
caseSensitive: false);
var match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -26,18 +29,19 @@ class APKCombo extends AppSource {
@override
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) async {
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
return {
"User-Agent": "curl/8.0.1",
"Accept": "*/*",
"Connection": "keep-alive",
"Host": "$host"
"Host": hosts[0]
};
}
Future<List<MapEntry<String, String>>> getApkUrls(String standardUrl) async {
var res = await sourceRequest('$standardUrl/download/apk');
Future<List<MapEntry<String, String>>> getApkUrls(
String standardUrl, Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest('$standardUrl/download/apk', {});
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -70,9 +74,9 @@ class APKCombo extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
var freshURLs = await getApkUrls(standardUrl);
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
var freshURLs = await getApkUrls(standardUrl, additionalSettings);
var path2Match = Uri.parse(apkUrl).path;
for (var url in freshURLs) {
if (Uri.parse(url.value).path == path2Match) {
@ -88,7 +92,7 @@ class APKCombo extends AppSource {
Map<String, dynamic> additionalSettings,
) async {
String appId = (await tryInferringAppId(standardUrl))!;
var preres = await sourceRequest(standardUrl);
var preres = await sourceRequest(standardUrl, additionalSettings);
if (preres.statusCode != 200) {
throw getObtainiumHttpError(preres);
}
@ -112,7 +116,9 @@ class APKCombo extends AppSource {
}
}
return APKDetails(
version, await getApkUrls(standardUrl), AppNames(author, appName),
version,
await getApkUrls(standardUrl, additionalSettings),
AppNames(author, appName),
releaseDate: releaseDate);
}
}

View File

@ -9,8 +9,9 @@ import 'package:obtainium/providers/source_provider.dart';
class APKMirror extends AppSource {
APKMirror() {
host = 'apkmirror.com';
hosts = ['apkmirror.com'];
enforceTrackOnly = true;
showReleaseDateAsVersionToggle = true;
additionalSourceAppSpecificSettingFormItems = [
[
@ -32,13 +33,14 @@ class APKMirror extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx =
RegExp('^https?://(www\\.)?$host/apk/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/apk/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -58,7 +60,7 @@ class APKMirror extends AppSource {
true
? additionalSettings['filterReleaseTitlesByRegEx']
: null;
Response res = await sourceRequest('$standardUrl/feed');
Response res = await sourceRequest('$standardUrl/feed', additionalSettings);
if (res.statusCode == 200) {
var items = parse(res.body).querySelectorAll('item');
dynamic targetRelease;
@ -84,7 +86,7 @@ class APKMirror extends AppSource {
dateString != null ? HttpDate.parse('$dateString GMT') : null;
String? version = titleString
?.substring(RegExp('[0-9]').firstMatch(titleString)?.start ?? 0,
RegExp(' by ').firstMatch(titleString)?.start ?? 0)
RegExp(' by ').allMatches(titleString).last.start)
.trim();
if (version == null || version.isEmpty) {
version = titleString;

View File

@ -20,26 +20,29 @@ parseDateTimeMMMddCommayyyy(String? dateString) {
class APKPure extends AppSource {
APKPure() {
host = 'apkpure.com';
hosts = ['apkpure.net', 'apkpure.com'];
allowSubDomains = true;
naiveStandardVersionDetection = true;
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB =
RegExp('^https?://m.$host/+[^/]+/+[^/]+(/+[^/]+)?');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://m.${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
if (match != null) {
url = 'https://$host${Uri.parse(url).path}';
url = 'https://${getSourceRegex(hosts)}${Uri.parse(url).path}';
}
RegExp standardUrlRegExA =
RegExp('^https?://$host/+[^/]+/+[^/]+(/+[^/]+)?');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?',
caseSensitive: false);
match = standardUrlRegExA.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -55,8 +58,8 @@ class APKPure extends AppSource {
) async {
String appId = (await tryInferringAppId(standardUrl))!;
String host = Uri.parse(standardUrl).host;
var res = await sourceRequest('$standardUrl/download');
var resChangelog = await sourceRequest(standardUrl);
var res = await sourceRequest('$standardUrl/download', additionalSettings);
var resChangelog = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200 && resChangelog.statusCode == 200) {
var html = parse(res.body);
var htmlChangelog = parse(resChangelog.body);
@ -69,7 +72,8 @@ class APKPure extends AppSource {
DateTime? releaseDate = parseDateTimeMMMddCommayyyy(dateString);
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
List<MapEntry<String, String>> apkUrls = [
MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest')
MapEntry('$appId.apk',
'https://d.${hosts.contains(host) ? 'cdnpure.com' : host}/b/$type/$appId?version=latest')
];
String author = html
.querySelector('span.info-sdk')

View File

@ -6,30 +6,35 @@ import 'package:obtainium/providers/source_provider.dart';
class Aptoide extends AppSource {
Aptoide() {
host = 'aptoide.com';
name = tr('Aptoide');
hosts = ['aptoide.com'];
name = 'Aptoide';
allowSubDomains = true;
naiveStandardVersionDetection = true;
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://([^\\.]+\\.){2,}$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
return (await getAppDetailsJSON(standardUrl))['package'];
return (await getAppDetailsJSON(
standardUrl, additionalSettings))['package'];
}
Future<Map<String, dynamic>> getAppDetailsJSON(String standardUrl) async {
var res = await sourceRequest(standardUrl);
Future<Map<String, dynamic>> getAppDetailsJSON(
String standardUrl, Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -40,8 +45,8 @@ class Aptoide extends AppSource {
} else {
throw NoReleasesError();
}
var res2 =
await sourceRequest('https://ws2.aptoide.com/api/7/getApp/app_id/$id');
var res2 = await sourceRequest(
'https://ws2.aptoide.com/api/7/getApp/app_id/$id', additionalSettings);
if (res2.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -53,7 +58,7 @@ class Aptoide extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var appDetails = await getAppDetailsJSON(standardUrl);
var appDetails = await getAppDetailsJSON(standardUrl, additionalSettings);
String appName = appDetails['name'] ?? tr('app');
String author = appDetails['developer']?['name'] ?? name;
String? dateStr = appDetails['updated'];

View File

@ -5,7 +5,7 @@ import 'package:obtainium/providers/source_provider.dart';
class Codeberg extends AppSource {
GitHub gh = GitHub();
Codeberg() {
host = 'codeberg.org';
hosts = ['codeberg.org'];
additionalSourceAppSpecificSettingFormItems =
gh.additionalSourceAppSpecificSettingFormItems;
@ -16,12 +16,14 @@ class Codeberg extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -35,7 +37,7 @@ class Codeberg extends AppSource {
) async {
return await gh.getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
(bool useTagUrl) async {
return 'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
return 'https://${hosts[0]}/api/v1/repos${standardUrl.substring('https://${hosts[0]}'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
}, null);
}
@ -50,7 +52,7 @@ class Codeberg extends AppSource {
{Map<String, dynamic> querySettings = const {}}) async {
return gh.searchCommon(
query,
'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100',
'https://${hosts[0]}/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100',
'data',
querySettings: querySettings);
}

View File

@ -0,0 +1,44 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/providers/source_provider.dart';
class DirectAPKLink extends AppSource {
HTML html = HTML();
DirectAPKLink() {
neverAutoSelect = true;
name = tr('directAPKLink');
additionalSourceAppSpecificSettingFormItems = html
.additionalSourceAppSpecificSettingFormItems
.where((element) => element
.where((element) => element.key == 'requestHeader')
.isNotEmpty)
.toList();
excludeCommonSettingKeys = [
'versionExtractionRegEx',
'matchGroupToUse',
'versionDetection',
'useVersionCodeAsOSVersion',
'apkFilterRegEx',
'autoApkFilterByArch'
];
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var additionalSettingsNew =
getDefaultValuesFromFormItems(html.combinedAppSpecificSettingFormItems);
for (var s in additionalSettings.keys) {
if (additionalSettingsNew.containsKey(s)) {
additionalSettingsNew[s] = additionalSettings[s];
}
}
additionalSettingsNew['defaultPseudoVersioningMethod'] = 'partialAPKHash';
additionalSettingsNew['directAPKLink'] = true;
additionalSettings['versionDetection'] = false;
return html.getLatestAPKDetails(standardUrl, additionalSettingsNew);
}
}

View File

@ -3,13 +3,15 @@ import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/app_sources/gitlab.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class FDroid extends AppSource {
FDroid() {
host = 'f-droid.org';
hosts = ['f-droid.org'];
name = tr('fdroid');
naiveStandardVersionDetection = true;
canSearch = true;
@ -37,19 +39,22 @@ class FDroid extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB =
RegExp('^https?://$host/+[^/]+/+packages/+[^/]+');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+packages/+[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
if (match != null) {
url =
'https://${Uri.parse(url.substring(0, match.end)).host}/packages/${Uri.parse(url).pathSegments.last}';
'https://${Uri.parse(match.group(0)!).host}/packages/${Uri.parse(url).pathSegments.last}';
}
RegExp standardUrlRegExA = RegExp('^https?://$host/+packages/+[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+packages/+[^/]+',
caseSensitive: false);
match = standardUrlRegExA.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -66,7 +71,8 @@ class FDroid extends AppSource {
String? appId = await tryInferringAppId(standardUrl);
String host = Uri.parse(standardUrl).host;
var details = getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest('https://$host/api/v1/packages/$appId'),
await sourceRequest(
'https://$host/api/v1/packages/$appId', additionalSettings),
'https://$host/repo/$appId',
standardUrl,
name,
@ -83,18 +89,47 @@ class FDroid extends AppSource {
if (!hostChanged) {
try {
var res = await sourceRequest(
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml');
String author = res.body
.split('\n')
.where((l) => l.startsWith('AuthorName: '))
.first
.split(': ')
.sublist(1)
.join(': ');
details.names.author = author;
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml',
additionalSettings);
var lines = res.body.split('\n');
var authorLines = lines.where((l) => l.startsWith('AuthorName: '));
if (authorLines.isNotEmpty) {
details.names.author =
authorLines.first.split(': ').sublist(1).join(': ');
}
var changelogUrls = lines
.where((l) => l.startsWith('Changelog: '))
.map((e) => e.split(' ').sublist(1).join(' '));
if (changelogUrls.isNotEmpty) {
details.changeLog = changelogUrls.first;
bool isGitHub = false;
bool isGitLab = false;
try {
GitHub().sourceSpecificStandardizeURL(details.changeLog!);
isGitHub = true;
} catch (e) {
//
}
try {
GitLab().sourceSpecificStandardizeURL(details.changeLog!);
isGitLab = true;
} catch (e) {
//
}
if ((isGitHub || isGitLab) &&
(details.changeLog?.indexOf('/blob/') ?? -1) >= 0) {
details.changeLog = (await sourceRequest(
details.changeLog!.replaceFirst('/blob/', '/raw/'),
additionalSettings))
.body;
}
}
} catch (e) {
// Fail silently
}
if ((details.changeLog?.length ?? 0) > 2048) {
details.changeLog = '${details.changeLog!.substring(0, 2048)}...';
}
}
return details;
}
@ -103,7 +138,7 @@ class FDroid extends AppSource {
Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) async {
Response res = await sourceRequest(
'https://search.$host/?q=${Uri.encodeQueryComponent(query)}');
'https://search.${hosts[0]}/?q=${Uri.encodeQueryComponent(query)}', {});
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
parse(res.body).querySelectorAll('.package-header').forEach((e) {

View File

@ -10,6 +10,7 @@ class FDroidRepo extends AppSource {
canSearch = true;
excludeFromMassSearch = true;
neverAutoSelect = true;
showReleaseDateAsVersionToggle = true;
additionalSourceAppSpecificSettingFormItems = [
[
@ -59,7 +60,7 @@ class FDroidRepo extends AppSource {
throw NoReleasesError();
}
url = removeQueryParamsFromUrl(standardizeUrl(url));
var res = await sourceRequest('$url/index.xml');
var res = await sourceRequest('$url/index.xml', {});
if (res.statusCode == 200) {
var body = parse(res.body);
Map<String, List<String>> results = {};
@ -117,7 +118,8 @@ class FDroidRepo extends AppSource {
throw NoReleasesError();
}
var res = await sourceRequest(
'$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}');
'$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}',
additionalSettings);
if (res.statusCode == 200) {
var body = parse(res.body);
var foundApps = body.querySelectorAll('application').where((element) {

View File

@ -14,8 +14,9 @@ import 'package:url_launcher/url_launcher_string.dart';
class GitHub extends AppSource {
GitHub() {
host = 'github.com';
hosts = ['github.com'];
appIdInferIsOptional = true;
showReleaseDateAsVersionToggle = true;
sourceConfigSettingFormItems = [
GeneratedFormTextField('github-creds',
@ -76,6 +77,10 @@ class GitHub extends AppSource {
[
GeneratedFormSwitch('dontSortReleasesList',
label: tr('dontSortReleasesList'))
],
[
GeneratedFormSwitch('useLatestAssetDateAsReleaseDate',
label: tr('useLatestAssetDateAsReleaseDate'), defaultValue: false)
]
];
@ -108,7 +113,8 @@ class GitHub extends AppSource {
for (var path in possibleBuildGradleLocations) {
try {
var res = await sourceRequest(
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path');
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path',
additionalSettings);
if (res.statusCode == 200) {
try {
var body = jsonDecode(res.body);
@ -149,18 +155,20 @@ class GitHub extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) async {
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
var token = await getTokenIfAny(additionalSettings);
var headers = <String, String>{};
if (token != null) {
@ -203,11 +211,11 @@ class GitHub extends AppSource {
}
Future<String> getAPIHost(Map<String, dynamic> additionalSettings) async =>
'https://api.$host';
'https://api.${hosts[0]}';
Future<String> convertStandardUrlToAPIUrl(
String standardUrl, Map<String, dynamic> additionalSettings) async =>
'${await getAPIHost(additionalSettings)}/repos${standardUrl.substring('https://$host'.length)}';
'${await getAPIHost(additionalSettings)}/repos${standardUrl.substring('https://${hosts[0]}'.length)}';
@override
String? changeLogPageFromStandardUrl(String standardUrl) =>
@ -234,11 +242,14 @@ class GitHub extends AppSource {
bool verifyLatestTag = additionalSettings['verifyLatestTag'] == true;
bool dontSortReleasesList =
additionalSettings['dontSortReleasesList'] == true;
bool useLatestAssetDateAsReleaseDate =
additionalSettings['useLatestAssetDateAsReleaseDate'] == true;
dynamic latestRelease;
if (verifyLatestTag) {
var temp = requestUrl.split('?');
Response res = await sourceRequest(
'${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}');
'${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}',
additionalSettings);
if (res.statusCode != 200) {
if (onHttpErrorCode != null) {
onHttpErrorCode(res);
@ -247,7 +258,7 @@ class GitHub extends AppSource {
}
latestRelease = jsonDecode(res.body);
}
Response res = await sourceRequest(requestUrl);
Response res = await sourceRequest(requestUrl, additionalSettings);
if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>;
if (latestRelease != null) {
@ -273,10 +284,31 @@ class GitHub extends AppSource {
.toList() ??
[];
DateTime? getReleaseDateFromRelease(dynamic rel) =>
DateTime? getPublishDateFromRelease(dynamic rel) =>
rel?['published_at'] != null
? DateTime.parse(rel['published_at'])
: null;
DateTime? getNewestAssetDateFromRelease(dynamic rel) {
var t = (rel['assets'] as List<dynamic>?)
?.map((e) {
return e?['updated_at'] != null
? DateTime.parse(e['updated_at'])
: null;
})
.where((e) => e != null)
.toList();
t?.sort((a, b) => b!.compareTo(a!));
if (t?.isNotEmpty == true) {
return t!.first;
}
return null;
}
DateTime? getReleaseDateFromRelease(dynamic rel, bool useAssetDate) =>
!useAssetDate
? getPublishDateFromRelease(rel)
: getNewestAssetDateFromRelease(rel);
if (dontSortReleasesList) {
releases = releases.reversed.toList();
} else {
@ -301,8 +333,12 @@ class GitHub extends AppSource {
(nameA as String).substring(matchA!.start, matchA.end),
(nameB as String).substring(matchB!.start, matchB.end));
} else {
return (getReleaseDateFromRelease(a) ?? DateTime(1))
.compareTo(getReleaseDateFromRelease(b) ?? DateTime(0));
return (getReleaseDateFromRelease(
a, useLatestAssetDateAsReleaseDate) ??
DateTime(1))
.compareTo(getReleaseDateFromRelease(
b, useLatestAssetDateAsReleaseDate) ??
DateTime(0));
}
}
});
@ -346,6 +382,8 @@ class GitHub extends AppSource {
continue;
}
var apkUrls = getReleaseAPKUrls(releases[i]);
apkUrls = filterApks(apkUrls, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']);
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
continue;
}
@ -357,7 +395,8 @@ class GitHub extends AppSource {
throw NoReleasesError();
}
String? version = targetRelease['tag_name'] ?? targetRelease['name'];
DateTime? releaseDate = getReleaseDateFromRelease(targetRelease);
DateTime? releaseDate = getReleaseDateFromRelease(
targetRelease, useLatestAssetDateAsReleaseDate);
if (version == null) {
throw NoVersionError();
}
@ -419,7 +458,7 @@ class GitHub extends AppSource {
String query, String requestUrl, String rootProp,
{Function(Response)? onHttpErrorCode,
Map<String, dynamic> querySettings = const {}}) async {
Response res = await sourceRequest(requestUrl);
Response res = await sourceRequest(requestUrl, {});
if (res.statusCode == 200) {
int minStarCount = querySettings['minStarCount'] != null
? int.parse(querySettings['minStarCount'])

View File

@ -13,8 +13,9 @@ import 'package:url_launcher/url_launcher_string.dart';
class GitLab extends AppSource {
GitLab() {
host = 'gitlab.com';
hosts = ['gitlab.com'];
canSearch = true;
showReleaseDateAsVersionToggle = true;
sourceConfigSettingFormItems = [
GeneratedFormTextField('gitlab-creds',
@ -52,12 +53,14 @@ class GitLab extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
Future<String?> getPATIfAny(Map<String, dynamic> additionalSettings) async {
@ -81,15 +84,15 @@ class GitLab extends AppSource {
Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) async {
var url =
'https://$host/api/v4/projects?search=${Uri.encodeQueryComponent(query)}';
var res = await sourceRequest(url);
'https://${hosts[0]}/api/v4/projects?search=${Uri.encodeQueryComponent(query)}';
var res = await sourceRequest(url, {});
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var json = jsonDecode(res.body) as List<dynamic>;
Map<String, List<String>> results = {};
for (var element in json) {
results['https://$host/${element['path_with_namespace']}'] = [
results['https://${hosts[0]}/${element['path_with_namespace']}'] = [
element['name_with_namespace'],
element['description'] ?? tr('noDescription')
];
@ -113,7 +116,8 @@ class GitLab extends AppSource {
if (PAT != null) {
var names = GitHub().getAppNames(standardUrl);
Response res = await sourceRequest(
'https://$host/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT');
'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT',
additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -148,7 +152,8 @@ class GitLab extends AppSource {
releaseDate: releaseDate);
});
} else {
Response res = await sourceRequest('$standardUrl/-/tags?format=atom');
Response res = await sourceRequest(
'$standardUrl/-/tags?format=atom', additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}

View File

@ -1,5 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
@ -18,13 +17,19 @@ String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
.split('/')
.where((element) => element.trim().isNotEmpty)
.toList();
String absoluteUrl;
if (ambiguousUrl.startsWith('/') || currPathSegments.isEmpty) {
return '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
absoluteUrl = '${referenceAbsoluteUrl.origin}$ambiguousUrl';
} else if (currPathSegments.isEmpty) {
absoluteUrl = '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
} else if (ambiguousUrl.split('/').where((e) => e.isNotEmpty).length == 1) {
return '${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
absoluteUrl =
'${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
} else {
return '${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - (currPathSegments.last.contains('.') ? 1 : 0)).join('/')}/$ambiguousUrl';
absoluteUrl =
'${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - (currPathSegments.last.contains('.') ? 1 : 0)).join('/')}/$ambiguousUrl';
}
return Uri.parse(absoluteUrl).toString();
}
int compareAlphaNumeric(String a, String b) {
@ -88,18 +93,7 @@ bool _isNumeric(String s) {
}
class HTML extends AppSource {
HTML() {
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormSwitch('sortByFileNamesNotLinks',
label: tr('sortByFileNamesNotLinks'))
],
[GeneratedFormSwitch('skipSort', label: tr('skipSort'))],
[GeneratedFormSwitch('reverseSort', label: tr('takeFirstLink'))],
[
GeneratedFormSwitch('supportFixedAPKURL',
defaultValue: true, label: tr('supportFixedAPKURL')),
],
var finalStepFormitems = [
[
GeneratedFormTextField('customLinkFilterRegex',
label: tr('customLinkFilterRegex'),
@ -111,52 +105,104 @@ class HTML extends AppSource {
}
])
],
[
GeneratedFormTextField('intermediateLinkRegex',
label: tr('intermediateLinkRegex'),
hint: '([0-9]+.)*[0-9]+/\$',
required: false,
additionalValidators: [(value) => regExValidator(value)])
],
[
GeneratedFormTextField('versionExtractionRegEx',
label: tr('versionExtractionRegEx'),
required: false,
additionalValidators: [(value) => regExValidator(value)]),
],
[
GeneratedFormTextField('matchGroupToUse',
label: tr('matchGroupToUse'),
required: false,
hint: '0',
textInputType: const TextInputType.numberWithOptions(),
additionalValidators: [
(value) {
if (value?.isEmpty == true) {
value = null;
}
value ??= '0';
return intValidator(value);
}
])
],
[
GeneratedFormSwitch('versionExtractWholePage',
label: tr('versionExtractWholePage'))
]
];
overrideVersionDetectionFormDefault('noVersionDetection',
disableStandard: false, disableRelDate: true);
var commonFormItems = [
[GeneratedFormSwitch('filterByLinkText', label: tr('filterByLinkText'))],
[GeneratedFormSwitch('skipSort', label: tr('skipSort'))],
[GeneratedFormSwitch('reverseSort', label: tr('takeFirstLink'))],
[
GeneratedFormSwitch('sortByLastLinkSegment',
label: tr('sortByLastLinkSegment'))
],
];
var intermediateFormItems = [
[
GeneratedFormTextField('customLinkFilterRegex',
label: tr('intermediateLinkRegex'),
hint: '([0-9]+.)*[0-9]+/\$',
required: true,
additionalValidators: [(value) => regExValidator(value)])
],
];
HTML() {
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormSubForm(
'intermediateLink', [...intermediateFormItems, ...commonFormItems],
label: tr('intermediateLink'))
],
finalStepFormitems[0],
...commonFormItems,
...finalStepFormitems.sublist(1),
[
GeneratedFormSubForm(
'requestHeader',
[
[
GeneratedFormTextField('requestHeader',
label: tr('requestHeader'),
required: false,
additionalValidators: [
(value) {
if ((value ?? 'empty:valid')
.split(':')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.length <
2) {
return tr('invalidInput');
}
return null;
}
])
]
],
label: tr('requestHeader'),
defaultValue: [
{
'requestHeader':
'User-Agent: Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
}
])
],
[
GeneratedFormDropdown(
'defaultPseudoVersioningMethod',
[
MapEntry('partialAPKHash', tr('partialAPKHash')),
MapEntry('APKLinkHash', tr('APKLinkHash'))
],
label: tr('defaultPseudoVersioningMethod'),
defaultValue: 'partialAPKHash')
]
];
}
@override
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) async {
return {
"User-Agent":
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
};
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
if (additionalSettings.isNotEmpty) {
if (additionalSettings['requestHeader']?.isNotEmpty != true) {
additionalSettings['requestHeader'] = [];
}
additionalSettings['requestHeader'] = additionalSettings['requestHeader']
.where((l) => l['requestHeader'].isNotEmpty == true)
.toList();
Map<String, String> requestHeaders = {};
for (int i = 0; i < (additionalSettings['requestHeader'].length); i++) {
var temp =
(additionalSettings['requestHeader'][i]['requestHeader'] as String)
.split(':');
requestHeaders[temp[0].trim()] = temp.sublist(1).join(':').trim();
}
return requestHeaders;
}
return null;
}
@override
@ -164,107 +210,117 @@ class HTML extends AppSource {
return url;
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var uri = Uri.parse(standardUrl);
Response res = await sourceRequest(standardUrl);
if (res.statusCode == 200) {
// Given an HTTP response, grab some links according to the common additional settings
// (those that apply to intermediate and final steps)
Future<List<MapEntry<String, String>>> grabLinksCommon(
Response res, Map<String, dynamic> additionalSettings) async {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
List<String> allLinks = html
List<MapEntry<String, String>> allLinks = html
.querySelectorAll('a')
.map((element) => element.attributes['href'] ?? '')
.where((element) => element.isNotEmpty)
.map((element) => MapEntry(
element.attributes['href'] ?? '',
element.text.isNotEmpty
? element.text
: (element.attributes['href'] ?? '').split('/').last))
.where((element) => element.key.isNotEmpty)
.map((e) =>
MapEntry(ensureAbsoluteUrl(e.key, res.request!.url), e.value))
.toList();
if (allLinks.isEmpty) {
allLinks = RegExp(
r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?')
.allMatches(res.body)
.map((match) => match.group(0)!)
.map((match) =>
MapEntry(match.group(0)!, match.group(0)?.split('/').last ?? ''))
.toList();
}
List<String> links = [];
List<MapEntry<String, String>> links = [];
bool skipSort = additionalSettings['skipSort'] == true;
if ((additionalSettings['intermediateLinkRegex'] as String?)
?.isNotEmpty ==
true) {
var reg = RegExp(additionalSettings['intermediateLinkRegex']);
links = allLinks.where((element) => reg.hasMatch(element)).toList();
if (!skipSort) {
links.sort((a, b) => compareAlphaNumeric(a, b));
}
if (links.isEmpty) {
throw ObtainiumError(tr('intermediateLinkNotFound'));
}
Map<String, dynamic> additionalSettingsTemp =
Map.from(additionalSettings);
additionalSettingsTemp['intermediateLinkRegex'] = null;
return getLatestAPKDetails(
ensureAbsoluteUrl(links.last, uri), additionalSettingsTemp);
}
if ((additionalSettings['customLinkFilterRegex'] as String?)
?.isNotEmpty ==
bool filterLinkByText = additionalSettings['filterByLinkText'] == true;
if ((additionalSettings['customLinkFilterRegex'] as String?)?.isNotEmpty ==
true) {
var reg = RegExp(additionalSettings['customLinkFilterRegex']);
links = allLinks.where((element) => reg.hasMatch(element)).toList();
links = allLinks
.where((element) =>
reg.hasMatch(filterLinkByText ? element.value : element.key))
.toList();
} else {
links = allLinks
.where((element) =>
Uri.parse(element).path.toLowerCase().endsWith('.apk'))
Uri.parse(filterLinkByText ? element.value : element.key)
.path
.toLowerCase()
.endsWith('.apk'))
.toList();
}
if (!skipSort) {
links.sort((a, b) =>
additionalSettings['sortByFileNamesNotLinks'] == true
links.sort((a, b) => additionalSettings['sortByLastLinkSegment'] == true
? compareAlphaNumeric(
a.split('/').where((e) => e.isNotEmpty).last,
b.split('/').where((e) => e.isNotEmpty).last)
: compareAlphaNumeric(a, b));
a.key.split('/').where((e) => e.isNotEmpty).last,
b.key.split('/').where((e) => e.isNotEmpty).last)
: compareAlphaNumeric(a.key, b.key));
}
if (additionalSettings['reverseSort'] == true) {
links = links.reversed.toList();
}
if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty ==
true) {
var reg = RegExp(additionalSettings['apkFilterRegEx']);
links = links.where((element) => reg.hasMatch(element)).toList();
return links;
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var currentUrl = standardUrl;
if (additionalSettings['intermediateLink']?.isNotEmpty != true) {
additionalSettings['intermediateLink'] = [];
}
additionalSettings['intermediateLink'] =
additionalSettings['intermediateLink']
.where((l) => l['customLinkFilterRegex'].isNotEmpty == true)
.toList();
for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) {
var intLinks = await grabLinksCommon(
await sourceRequest(currentUrl, additionalSettings),
additionalSettings['intermediateLink'][i]);
if (intLinks.isEmpty) {
throw NoReleasesError();
} else {
currentUrl = intLinks.last.key;
}
}
var uri = Uri.parse(currentUrl);
List<MapEntry<String, String>> links = [];
String versionExtractionWholePageString = currentUrl;
if (additionalSettings['directAPKLink'] != true) {
Response res = await sourceRequest(currentUrl, additionalSettings);
versionExtractionWholePageString =
res.body.split('\r\n').join('\n').split('\n').join('\\n');
links = await grabLinksCommon(res, additionalSettings);
links = filterApks(links, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']);
if (links.isEmpty) {
throw NoReleasesError();
}
var rel = links.last;
} else {
links = [MapEntry(currentUrl, currentUrl)];
}
var rel = links.last.key;
String? version;
if (additionalSettings['supportFixedAPKURL'] != true) {
version = rel.hashCode.toString();
}
var versionExtractionRegEx =
additionalSettings['versionExtractionRegEx'] as String?;
if (versionExtractionRegEx?.isNotEmpty == true) {
var match = RegExp(versionExtractionRegEx!).allMatches(
version = extractVersion(
additionalSettings['versionExtractionRegEx'] as String?,
additionalSettings['matchGroupToUse'] as String?,
additionalSettings['versionExtractWholePage'] == true
? res.body.split('\r\n').join('\n').split('\n').join('\\n')
? versionExtractionWholePageString
: rel);
if (match.isEmpty) {
throw NoVersionError();
}
String matchGroupString =
(additionalSettings['matchGroupToUse'] as String).trim();
if (matchGroupString.isEmpty) {
matchGroupString = "0";
}
version = match.last.group(int.parse(matchGroupString));
if (version?.isEmpty == true) {
throw NoVersionError();
}
}
rel = ensureAbsoluteUrl(rel, uri);
version ??= (await checkDownloadHash(rel)).toString();
version ??=
additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash'
? rel.hashCode.toString()
: (await checkPartialDownloadHashDynamic(rel)).toString();
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
AppNames(uri.host, tr('app')));
} else {
throw getObtainiumHttpError(res);
}
}
}

View File

@ -6,26 +6,30 @@ import 'package:obtainium/providers/source_provider.dart';
class HuaweiAppGallery extends AppSource {
HuaweiAppGallery() {
name = 'Huawei AppGallery';
host = 'appgallery.huawei.com';
overrideVersionDetectionFormDefault('releaseDateAsVersion',
disableStandard: true);
hosts = ['appgallery.huawei.com'];
versionDetectionDisallowed = true;
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/app/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/app/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
getDlUrl(String standardUrl) =>
'https://${host!.replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}';
'https://${hosts[0].replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}';
requestAppdlRedirect(String dlUrl) async {
Response res = await sourceRequest(dlUrl, followRedirects: false);
requestAppdlRedirect(
String dlUrl, Map<String, dynamic> additionalSettings) async {
Response res =
await sourceRequest(dlUrl, additionalSettings, followRedirects: false);
if (res.statusCode == 200 ||
res.statusCode == 302 ||
res.statusCode == 304) {
@ -52,7 +56,7 @@ class HuaweiAppGallery extends AppSource {
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
String dlUrl = getDlUrl(standardUrl);
Response res = await requestAppdlRedirect(dlUrl);
Response res = await requestAppdlRedirect(dlUrl, additionalSettings);
return res.headers['location'] != null
? appIdFromRedirectDlUrl(res.headers['location']!)
: null;
@ -64,7 +68,7 @@ class HuaweiAppGallery extends AppSource {
Map<String, dynamic> additionalSettings,
) async {
String dlUrl = getDlUrl(standardUrl);
Response res = await requestAppdlRedirect(dlUrl);
Response res = await requestAppdlRedirect(dlUrl, additionalSettings);
if (res.headers['location'] == null) {
throw NoReleasesError();
}

View File

@ -6,7 +6,7 @@ class IzzyOnDroid extends AppSource {
late FDroid fd;
IzzyOnDroid() {
host = 'izzysoft.de';
hosts = ['izzysoft.de'];
fd = FDroid();
additionalSourceAppSpecificSettingFormItems =
fd.additionalSourceAppSpecificSettingFormItems;
@ -15,17 +15,20 @@ class IzzyOnDroid extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExA = RegExp('^https?://android.$host/repo/apk/[^/]+');
RegExpMatch? match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://android.${getSourceRegex(hosts)}/repo/apk/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExA.firstMatch(url);
if (match == null) {
RegExp standardUrlRegExB =
RegExp('^https?://apt.$host/fdroid/index/apk/[^/]+');
match = standardUrlRegExB.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://apt.${getSourceRegex(hosts)}/fdroid/index/apk/[^/]+',
caseSensitive: false);
match = standardUrlRegExB.firstMatch(url);
}
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -42,7 +45,8 @@ class IzzyOnDroid extends AppSource {
String? appId = await tryInferringAppId(standardUrl);
return fd.getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest(
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId',
additionalSettings),
'https://android.izzysoft.de/frepo/$appId',
standardUrl,
name,

View File

@ -6,8 +6,9 @@ import 'package:obtainium/providers/source_provider.dart';
class Jenkins extends AppSource {
Jenkins() {
overrideVersionDetectionFormDefault('releaseDateAsVersion',
disableStandard: true);
versionDetectionDisallowed = true;
neverAutoSelect = true;
showReleaseDateAsVersionToggle = true;
}
String trimJobUrl(String url) {
@ -16,7 +17,7 @@ class Jenkins extends AppSource {
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -29,8 +30,8 @@ class Jenkins extends AppSource {
Map<String, dynamic> additionalSettings,
) async {
standardUrl = trimJobUrl(standardUrl);
Response res =
await sourceRequest('$standardUrl/lastSuccessfulBuild/api/json');
Response res = await sourceRequest(
'$standardUrl/lastSuccessfulBuild/api/json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
var releaseDate = json['timestamp'] == null

View File

@ -6,17 +6,19 @@ import 'package:obtainium/providers/source_provider.dart';
class Mullvad extends AppSource {
Mullvad() {
host = 'mullvad.net';
hosts = ['mullvad.net'];
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -28,7 +30,8 @@ class Mullvad extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('$standardUrl/en/download/android');
Response res = await sourceRequest(
'$standardUrl/en/download/android', additionalSettings);
if (res.statusCode == 200) {
var versions = parse(res.body)
.querySelectorAll('p')

View File

@ -5,17 +5,20 @@ import 'package:obtainium/providers/source_provider.dart';
class NeutronCode extends AppSource {
NeutronCode() {
host = 'neutroncode.com';
hosts = ['neutroncode.com'];
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/downloads/file/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/downloads/file/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -78,7 +81,7 @@ class NeutronCode extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest(standardUrl);
Response res = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var name = http.querySelector('.pd-title')?.innerHtml;
@ -91,7 +94,7 @@ class NeutronCode extends AppSource {
if (version == null) {
throw NoVersionError();
}
String? apkUrl = 'https://$host/download/$filename';
String? apkUrl = 'https://${hosts[0]}/download/$filename';
var dateStringOriginal =
http.querySelector('.pd-date-txt')?.nextElementSibling?.innerHtml;
var dateString = dateStringOriginal != null

View File

@ -5,12 +5,12 @@ import 'package:obtainium/providers/source_provider.dart';
class Signal extends AppSource {
Signal() {
host = 'signal.org';
hosts = ['signal.org'];
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
@ -18,8 +18,8 @@ class Signal extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res =
await sourceRequest('https://updates.$host/android/latest.json');
Response res = await sourceRequest(
'https://updates.${hosts[0]}/android/latest.json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
String? apkUrl = json['url'];

View File

@ -5,23 +5,34 @@ import 'package:obtainium/providers/source_provider.dart';
class SourceForge extends AppSource {
SourceForge() {
host = 'sourceforge.net';
hosts = ['sourceforge.net'];
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB = RegExp('^https?://$host/p/[^/]+');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
var sourceRegex = getSourceRegex(hosts);
RegExp standardUrlRegExC =
RegExp('^https?://(www\\.)?$sourceRegex/p/.+', caseSensitive: false);
RegExpMatch? match = standardUrlRegExC.firstMatch(url);
if (match != null) {
url =
'https://${Uri.parse(url.substring(0, match.end)).host}/projects/${url.substring(Uri.parse(url.substring(0, match.end)).host.length + '/projects/'.length + 1)}';
'https://${Uri.parse(match.group(0)!).host}/projects/${url.substring(Uri.parse(match.group(0)!).host.length + '/projects/'.length + 1)}';
}
RegExp standardUrlRegExA = RegExp('^https?://$host/projects/[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://(www\\.)?$sourceRegex/projects/[^/]+',
caseSensitive: false);
match = standardUrlRegExB.firstMatch(url);
if (match != null && match.group(0) == url) {
url = '$url/files';
}
RegExp standardUrlRegExA = RegExp(
'^https?://(www\\.)?$sourceRegex/projects/[^/]+/files(/.+)?',
caseSensitive: false);
match = standardUrlRegExA.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -29,37 +40,79 @@ class SourceForge extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('$standardUrl/rss?path=/');
var standardUri = Uri.parse(standardUrl);
if (standardUri.pathSegments.length == 2) {
standardUrl = '$standardUrl/files';
standardUri = Uri.parse(standardUrl);
}
Response res = await sourceRequest(
'${standardUri.origin}/${standardUri.pathSegments.sublist(0, 2).join('/')}/rss?path=/',
additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
var allDownloadLinks =
parsedHtml.querySelectorAll('guid').map((e) => e.innerHtml).toList();
var allDownloadLinks = parsedHtml
.querySelectorAll('guid')
.map((e) => e.innerHtml)
.where((element) => element.startsWith(standardUrl))
.toList();
getVersion(String url) {
try {
var tokens = url.split('/');
var fi = tokens.indexOf('files');
return tokens[tokens[fi + 2] == 'download' ? fi - 1 : fi + 1];
var segments = url
.substring(standardUrl.length)
.split('/')
.where((element) => element.isNotEmpty)
.toList()
.reversed
.toList()
.sublist(1)
.reversed
.toList();
segments = segments.length > 1
? segments.reversed.toList().sublist(1).reversed.toList()
: segments;
var version = segments.isNotEmpty ? segments.join('/') : null;
if (version != null) {
try {
var extractedVersion = extractVersion(
additionalSettings['versionExtractionRegEx'] as String?,
additionalSettings['matchGroupToUse'] as String?,
version);
if (extractedVersion != null) {
version = extractedVersion;
}
} catch (e) {
if (e is NoVersionError) {
version = null;
} else {
rethrow;
}
}
}
return version;
} catch (e) {
return null;
}
}
String? version = getVersion(allDownloadLinks[0]);
var apkUrlListAllReleases = allDownloadLinks
.where((element) => element.toLowerCase().endsWith('.apk/download'))
.where((element) => getVersion(element) != null)
.toList();
if (apkUrlListAllReleases.isEmpty) {
throw NoReleasesError();
}
String? version = getVersion(apkUrlListAllReleases[0]);
if (version == null) {
throw NoVersionError();
}
var apkUrlListAllReleases = allDownloadLinks
.where((element) => element.toLowerCase().endsWith('.apk/download'))
.toList();
var apkUrlList =
apkUrlListAllReleases // This can be used skipped for fallback support later
.where((element) => getVersion(element) == version)
.toList();
return APKDetails(
version,
getApkUrlsFromUrls(apkUrlList),
AppNames(
name, standardUrl.substring(standardUrl.lastIndexOf('/') + 1)));
var segments = standardUrl.split('/');
return APKDetails(version, getApkUrlsFromUrls(apkUrlList),
AppNames(name, segments[segments.indexOf('files') - 1]));
} else {
throw getObtainiumHttpError(res);
}

View File

@ -8,7 +8,8 @@ import 'package:easy_localization/easy_localization.dart';
class SourceHut extends AppSource {
SourceHut() {
host = 'git.sr.ht';
hosts = ['git.sr.ht'];
showReleaseDateAsVersionToggle = true;
additionalSourceAppSpecificSettingFormItems = [
[
@ -20,12 +21,14 @@ class SourceHut extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -36,11 +39,21 @@ class SourceHut extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
if (standardUrl.endsWith('/refs')) {
standardUrl = standardUrl
.split('/')
.reversed
.toList()
.sublist(1)
.reversed
.join('/');
}
Uri standardUri = Uri.parse(standardUrl);
String appName = standardUri.pathSegments.last;
bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'] == true;
Response res = await sourceRequest('$standardUrl/refs/rss.xml');
Response res =
await sourceRequest('$standardUrl/refs/rss.xml', additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
List<APKDetails> apkDetailsList = [];
@ -69,7 +82,7 @@ class SourceHut extends AppSource {
} catch (e) {
// ignore
}
var res2 = await sourceRequest(releasePage);
var res2 = await sourceRequest(releasePage, additionalSettings);
List<MapEntry<String, String>> apkUrls = [];
if (res2.statusCode == 200) {
apkUrls = getApkUrlsFromUrls(parse(res2.body)

View File

@ -7,8 +7,8 @@ import 'package:obtainium/providers/source_provider.dart';
class SteamMobile extends AppSource {
SteamMobile() {
host = 'store.steampowered.com';
name = tr('steam');
hosts = ['store.steampowered.com'];
name = 'Steam';
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormDropdown('app', apks.entries.toList(),
@ -21,7 +21,7 @@ class SteamMobile extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
@ -29,7 +29,8 @@ class SteamMobile extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('https://$host/mobile');
Response res =
await sourceRequest('https://${hosts[0]}/mobile', additionalSettings);
if (res.statusCode == 200) {
var apkNamePrefix = additionalSettings['app'] as String?;
if (apkNamePrefix == null) {

View File

@ -6,13 +6,13 @@ import 'package:obtainium/providers/source_provider.dart';
class TelegramApp extends AppSource {
TelegramApp() {
host = 'telegram.org';
hosts = ['telegram.org'];
name = 'Telegram ${tr('app')}';
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
@ -20,7 +20,8 @@ class TelegramApp extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('https://t.me/s/TAndroidAPK');
Response res =
await sourceRequest('https://t.me/s/TAndroidAPK', additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var messages =

View File

@ -6,29 +6,34 @@ import 'package:obtainium/providers/source_provider.dart';
class Uptodown extends AppSource {
Uptodown() {
host = 'uptodown.com';
hosts = ['uptodown.com'];
allowSubDomains = true;
naiveStandardVersionDetection = true;
showReleaseDateAsVersionToggle = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://([^\\.]+\\.){2,}$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return '${url.substring(0, match.end)}/android/download';
return '${match.group(0)!}/android/download';
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
return (await getAppDetailsFromPage(standardUrl))['appId'];
return (await getAppDetailsFromPage(
standardUrl, additionalSettings))['appId'];
}
Future<Map<String, String?>> getAppDetailsFromPage(String standardUrl) async {
var res = await sourceRequest(standardUrl);
Future<Map<String, String?>> getAppDetailsFromPage(
String standardUrl, Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -56,7 +61,8 @@ class Uptodown extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var appDetails = await getAppDetailsFromPage(standardUrl);
var appDetails =
await getAppDetailsFromPage(standardUrl, additionalSettings);
var version = appDetails['version'];
var apkUrl = appDetails['apkUrl'];
var appId = appDetails['appId'];
@ -82,9 +88,9 @@ class Uptodown extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
var res = await sourceRequest(apkUrl);
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest(apkUrl, additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -94,6 +100,6 @@ class Uptodown extends AppSource {
if (finalUrlKey == null) {
throw NoAPKError();
}
return 'https://dw.$host/dwn/$finalUrlKey';
return 'https://dw.${hosts[0]}/dwn/$finalUrlKey';
}
}

View File

@ -1,31 +1,33 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class VLC extends AppSource {
VLC() {
host = 'videolan.org';
hosts = ['videolan.org'];
}
get dwUrlBase => 'https://get.$host/vlc-android/';
get dwUrlBase => 'https://get.${hosts[0]}/vlc-android/';
@override
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) =>
HTML().getRequestHeaders(
additionalSettings: additionalSettings,
forAPKDownload: forAPKDownload);
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
return {
"User-Agent":
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
};
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
Future<String?> getLatestVersion(String standardUrl) async {
Response res = await sourceRequest(dwUrlBase);
Future<String?> getLatestVersion(
String standardUrl, Map<String, dynamic> additionalSettings) async {
Response res = await sourceRequest(dwUrlBase, additionalSettings);
if (res.statusCode == 200) {
var dwLinks = parse(res.body)
.querySelectorAll('a')
@ -77,9 +79,9 @@ class VLC extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
Response res = await sourceRequest(apkUrl);
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
Response res = await sourceRequest(apkUrl, additionalSettings);
if (res.statusCode == 200) {
String? apkUrl =
parse(res.body).querySelector('#alt_link')?.attributes['href'];

View File

@ -5,18 +5,20 @@ import 'package:obtainium/providers/source_provider.dart';
class WhatsApp extends AppSource {
WhatsApp() {
host = 'whatsapp.com';
hosts = ['whatsapp.com'];
versionDetectionDisallowed = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
Response res = await sourceRequest('$standardUrl/android');
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
Response res =
await sourceRequest('$standardUrl/android', additionalSettings);
if (res.statusCode == 200) {
var targetLinks = parse(res.body)
.querySelectorAll('a')
@ -40,8 +42,8 @@ class WhatsApp extends AppSource {
) async {
// This is a CDN link that is consistent per version
// But it has query params that change constantly
Uri apkUri =
Uri.parse(await apkUrlPrefetchModifier(standardUrl, standardUrl));
Uri apkUri = Uri.parse(await apkUrlPrefetchModifier(
standardUrl, standardUrl, additionalSettings));
var unusableApkUrl = '${apkUri.origin}/${apkUri.path}';
// So we use the param-less URL is a pseudo-version to add the app and check for updates
// See #357 for why we can't scrape the version number directly

View File

@ -4,6 +4,7 @@ import 'package:hsluv/hsluv.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/providers/source_provider.dart';
abstract class GeneratedFormItem {
late String key;
@ -12,6 +13,7 @@ abstract class GeneratedFormItem {
late dynamic defaultValue;
List<dynamic> additionalValidators;
dynamic ensureType(dynamic val);
GeneratedFormItem clone();
GeneratedFormItem(this.key,
{this.label = 'Input',
@ -31,7 +33,8 @@ class GeneratedFormTextField extends GeneratedFormItem {
{super.label,
super.belowWidgets,
String super.defaultValue = '',
List<String? Function(String? value)> super.additionalValidators = const [],
List<String? Function(String? value)> super.additionalValidators =
const [],
this.required = true,
this.max = 1,
this.hint,
@ -42,6 +45,20 @@ class GeneratedFormTextField extends GeneratedFormItem {
String ensureType(val) {
return val.toString();
}
@override
GeneratedFormTextField clone() {
return GeneratedFormTextField(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
additionalValidators: List.from(additionalValidators),
required: required,
max: max,
hint: hint,
password: password,
textInputType: textInputType);
}
}
class GeneratedFormDropdown extends GeneratedFormItem {
@ -62,14 +79,31 @@ class GeneratedFormDropdown extends GeneratedFormItem {
String ensureType(val) {
return val.toString();
}
@override
GeneratedFormDropdown clone() {
return GeneratedFormDropdown(
key,
opts?.map((e) => MapEntry(e.key, e.value)).toList(),
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
disabledOptKeys:
disabledOptKeys != null ? List.from(disabledOptKeys!) : null,
additionalValidators: List.from(additionalValidators),
);
}
}
class GeneratedFormSwitch extends GeneratedFormItem {
bool disabled = false;
GeneratedFormSwitch(
super.key, {
super.label,
super.belowWidgets,
bool super.defaultValue = false,
bool disabled = false,
List<String? Function(bool value)> super.additionalValidators = const [],
});
@ -77,6 +111,16 @@ class GeneratedFormSwitch extends GeneratedFormItem {
bool ensureType(val) {
return val == true || val == 'true';
}
@override
GeneratedFormSwitch clone() {
return GeneratedFormSwitch(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
disabled: false,
additionalValidators: List.from(additionalValidators));
}
}
class GeneratedFormTagInput extends GeneratedFormItem {
@ -101,6 +145,20 @@ class GeneratedFormTagInput extends GeneratedFormItem {
Map<String, MapEntry<int, bool>> ensureType(val) {
return val is Map<String, MapEntry<int, bool>> ? val : {};
}
@override
GeneratedFormTagInput clone() {
return GeneratedFormTagInput(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
additionalValidators: List.from(additionalValidators),
deleteConfirmationMessage: deleteConfirmationMessage,
singleSelect: singleSelect,
alignment: alignment,
emptyMessage: emptyMessage,
showLabelWhenNotEmpty: showLabelWhenNotEmpty);
}
}
typedef OnValueChanges = void Function(
@ -117,6 +175,37 @@ class GeneratedForm extends StatefulWidget {
State<GeneratedForm> createState() => _GeneratedFormState();
}
List<List<GeneratedFormItem>> cloneFormItems(
List<List<GeneratedFormItem>> items) {
List<List<GeneratedFormItem>> clonedItems = [];
for (var row in items) {
List<GeneratedFormItem> clonedRow = [];
for (var it in row) {
clonedRow.add(it.clone());
}
clonedItems.add(clonedRow);
}
return clonedItems;
}
class GeneratedFormSubForm extends GeneratedFormItem {
final List<List<GeneratedFormItem>> items;
GeneratedFormSubForm(super.key, this.items,
{super.label, super.belowWidgets, super.defaultValue = const []});
@override
ensureType(val) {
return val; // Not easy to validate List<Map<String, dynamic>>
}
@override
GeneratedFormSubForm clone() {
return GeneratedFormSubForm(key, cloneFormItems(items),
label: label, belowWidgets: belowWidgets, defaultValue: defaultValue);
}
}
// Generates a color in the HSLuv (Pastel) color space
// https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
Color generateRandomLightColor() {
@ -133,27 +222,38 @@ Color generateRandomLightColor() {
return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]);
}
int generateRandomNumber(int seed1,
{int seed2 = 0, int seed3 = 0, max = 10000}) {
int combinedSeed = seed1.hashCode ^ seed2.hashCode ^ seed3.hashCode;
Random random = Random(combinedSeed);
int randomNumber = random.nextInt(max);
return randomNumber;
}
bool validateTextField(TextFormField tf) =>
(tf.key as GlobalKey<FormFieldState>).currentState?.isValid == true;
class _GeneratedFormState extends State<GeneratedForm> {
final _formKey = GlobalKey<FormState>();
Map<String, dynamic> values = {};
late List<List<Widget>> formInputs;
List<List<Widget>> rows = [];
String? initKey;
int forceUpdateKeyCount = 0;
// If any value changes, call this to update the parent with value and validity
void someValueChanged({bool isBuilding = false}) {
void someValueChanged({bool isBuilding = false, bool forceInvalid = false}) {
Map<String, dynamic> returnValues = values;
var valid = true;
for (int r = 0; r < widget.items.length; r++) {
for (int i = 0; i < widget.items[r].length; i++) {
if (formInputs[r][i] is TextFormField) {
var fieldState =
(formInputs[r][i].key as GlobalKey<FormFieldState>).currentState;
if (fieldState != null) {
valid = valid && fieldState.isValid;
valid = valid && validateTextField(formInputs[r][i] as TextFormField);
}
}
}
if (forceInvalid) {
valid = false;
}
widget.onValueChanges(returnValues, valid, isBuilding);
}
@ -229,6 +329,17 @@ class _GeneratedFormState extends State<GeneratedForm> {
someValueChanged();
});
});
} else if (formItem is GeneratedFormSubForm) {
values[formItem.key] = [];
for (Map<String, dynamic> v
in ((formItem.defaultValue ?? []) as List<dynamic>)) {
var fullDefaults = getDefaultValuesFromFormItems(formItem.items);
for (var element in v.entries) {
fullDefaults[element.key] = element.value;
}
values[formItem.key].add(fullDefaults);
}
return Container();
} else {
return Container(); // Some input types added in build
}
@ -250,6 +361,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
}
for (var r = 0; r < formInputs.length; r++) {
for (var e = 0; e < formInputs[r].length; e++) {
String fieldKey = widget.items[r][e].key;
if (widget.items[r][e] is GeneratedFormSwitch) {
formInputs[r][e] = Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -259,20 +371,55 @@ class _GeneratedFormState extends State<GeneratedForm> {
width: 8,
),
Switch(
value: values[widget.items[r][e].key],
onChanged: (value) {
value: values[fieldKey],
onChanged:
(widget.items[r][e] as GeneratedFormSwitch).disabled
? null
: (value) {
setState(() {
values[widget.items[r][e].key] = value;
values[fieldKey] = value;
someValueChanged();
});
})
],
);
} else if (widget.items[r][e] is GeneratedFormTagInput) {
onAddPressed() {
showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: widget.items[r][e].label,
items: [
[GeneratedFormTextField('label', label: tr('label'))]
]);
}).then((value) {
String? label = value?['label'];
if (label != null) {
setState(() {
var temp =
values[fieldKey] as Map<String, MapEntry<int, bool>>?;
temp ??= {};
if (temp[label] == null) {
var singleSelect =
(widget.items[r][e] as GeneratedFormTagInput)
.singleSelect;
var someSelected = temp.entries
.where((element) => element.value.value)
.isNotEmpty;
temp[label] = MapEntry(generateRandomLightColor().value,
!(someSelected && singleSelect));
values[fieldKey] = temp;
someValueChanged();
}
});
}
});
}
formInputs[r][e] =
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
if ((values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
if ((values[fieldKey] as Map<String, MapEntry<int, bool>>?)
?.isNotEmpty ==
true &&
(widget.items[r][e] as GeneratedFormTagInput)
@ -295,17 +442,15 @@ class _GeneratedFormState extends State<GeneratedForm> {
(widget.items[r][e] as GeneratedFormTagInput).alignment,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
(values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
?.isEmpty ==
true
? Text(
(widget.items[r][e] as GeneratedFormTagInput)
.emptyMessage,
)
: const SizedBox.shrink(),
...(values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
// (values[fieldKey] as Map<String, MapEntry<int, bool>>?)
// ?.isEmpty ==
// true
// ? Text(
// (widget.items[r][e] as GeneratedFormTagInput)
// .emptyMessage,
// )
// : const SizedBox.shrink(),
...(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
?.entries
.map((e2) {
return Padding(
@ -318,11 +463,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
selected: e2.value.value,
onSelected: (value) {
setState(() {
(values[widget.items[r][e].key] as Map<String,
(values[fieldKey] as Map<String,
MapEntry<int, bool>>)[e2.key] =
MapEntry(
(values[widget.items[r][e].key] as Map<
String,
(values[fieldKey] as Map<String,
MapEntry<int, bool>>)[e2.key]!
.key,
value);
@ -330,20 +474,16 @@ class _GeneratedFormState extends State<GeneratedForm> {
as GeneratedFormTagInput)
.singleSelect &&
value == true) {
for (var key in (values[
widget.items[r][e].key]
for (var key in (values[fieldKey]
as Map<String, MapEntry<int, bool>>)
.keys) {
if (key != e2.key) {
(values[widget.items[r][e].key] as Map<
String,
MapEntry<int, bool>>)[key] =
MapEntry(
(values[widget.items[r][e].key]
as Map<
(values[fieldKey] as Map<
String,
MapEntry<int,
bool>>)[key]!
bool>>)[key] = MapEntry(
(values[fieldKey] as Map<String,
MapEntry<int, bool>>)[key]!
.key,
false);
}
@ -355,8 +495,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
));
}) ??
[const SizedBox.shrink()],
(values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
?.values
.where((e) => e.value)
.length ==
@ -366,7 +505,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
child: IconButton(
onPressed: () {
setState(() {
var temp = values[widget.items[r][e].key]
var temp = values[fieldKey]
as Map<String, MapEntry<int, bool>>;
// get selected category str where bool is true
final oldEntry = temp.entries
@ -379,7 +518,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
// Update entry with new color, remain selected
temp.update(oldEntry.key,
(old) => MapEntry(newColor, old.value));
values[widget.items[r][e].key] = temp;
values[fieldKey] = temp;
someValueChanged();
});
},
@ -388,8 +527,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
tooltip: tr('colour'),
))
: const SizedBox.shrink(),
(values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
?.values
.where((e) => e.value)
.isNotEmpty ==
@ -400,10 +538,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
onPressed: () {
fn() {
setState(() {
var temp = values[widget.items[r][e].key]
var temp = values[fieldKey]
as Map<String, MapEntry<int, bool>>;
temp.removeWhere((key, value) => value.value);
values[widget.items[r][e].key] = temp;
values[fieldKey] = temp;
someValueChanged();
});
}
@ -435,45 +573,22 @@ class _GeneratedFormState extends State<GeneratedForm> {
tooltip: tr('remove'),
))
: const SizedBox.shrink(),
Padding(
(values[fieldKey] as Map<String, MapEntry<int, bool>>?)
?.isEmpty ==
true
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: TextButton.icon(
onPressed: onAddPressed,
icon: const Icon(Icons.add),
label: Text(
(widget.items[r][e] as GeneratedFormTagInput)
.label),
))
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: IconButton(
onPressed: () {
showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: widget.items[r][e].label,
items: [
[
GeneratedFormTextField('label',
label: tr('label'))
]
]);
}).then((value) {
String? label = value?['label'];
if (label != null) {
setState(() {
var temp = values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?;
temp ??= {};
if (temp[label] == null) {
var singleSelect = (widget.items[r][e]
as GeneratedFormTagInput)
.singleSelect;
var someSelected = temp.entries
.where((element) => element.value.value)
.isNotEmpty;
temp[label] = MapEntry(
generateRandomLightColor().value,
!(someSelected && singleSelect));
values[widget.items[r][e].key] = temp;
someValueChanged();
}
});
}
});
},
onPressed: onAddPressed,
icon: const Icon(Icons.add),
visualDensity: VisualDensity.compact,
tooltip: tr('add'),
@ -481,6 +596,97 @@ class _GeneratedFormState extends State<GeneratedForm> {
],
)
]);
} else if (widget.items[r][e] is GeneratedFormSubForm) {
List<Widget> subformColumn = [];
var compact = (widget.items[r][e] as GeneratedFormSubForm)
.items
.length ==
1 &&
(widget.items[r][e] as GeneratedFormSubForm).items[0].length == 1;
for (int i = 0; i < values[fieldKey].length; i++) {
var internalFormKey = ValueKey(generateRandomNumber(
values[fieldKey].length,
seed2: i,
seed3: forceUpdateKeyCount));
subformColumn.add(Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!compact)
const SizedBox(
height: 16,
),
if (!compact)
Text(
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
style: const TextStyle(fontWeight: FontWeight.bold),
),
GeneratedForm(
key: internalFormKey,
items: cloneFormItems(
(widget.items[r][e] as GeneratedFormSubForm).items)
.map((x) => x.map((y) {
y.defaultValue = values[fieldKey]?[i]?[y.key];
y.key = '${y.key.toString()},$internalFormKey';
return y;
}).toList())
.toList(),
onValueChanges: (values, valid, isBuilding) {
values = values.map(
(key, value) => MapEntry(key.split(',')[0], value));
if (valid) {
this.values[fieldKey]?[i] = values;
}
someValueChanged(
isBuilding: isBuilding, forceInvalid: !valid);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton.icon(
style: TextButton.styleFrom(
foregroundColor:
Theme.of(context).colorScheme.error),
onPressed: (values[fieldKey].length > 0)
? () {
var temp = List.from(values[fieldKey]);
temp.removeAt(i);
values[fieldKey] = List.from(temp);
forceUpdateKeyCount++;
someValueChanged();
}
: null,
label: Text(
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
),
icon: const Icon(
Icons.delete_outline_rounded,
))
],
)
],
));
}
subformColumn.add(Padding(
padding: const EdgeInsets.only(bottom: 0, top: 8),
child: Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
values[fieldKey].add(getDefaultValuesFromFormItems(
(widget.items[r][e] as GeneratedFormSubForm)
.items));
forceUpdateKeyCount++;
someValueChanged();
},
icon: const Icon(Icons.add),
label: Text((widget.items[r][e] as GeneratedFormSubForm)
.label))),
],
),
));
formInputs[r][e] = Column(children: subformColumn);
}
}
}

View File

@ -12,19 +12,13 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
import 'package:background_fetch/background_fetch.dart';
import 'package:easy_localization/easy_localization.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.14.41';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
const int bgUpdateCheckAlarmId = 666;
List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('en'), 'English'),
MapEntry(Locale('zh'), '简体中文'),
@ -36,7 +30,7 @@ List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('fr'), 'Français'),
MapEntry(Locale('es'), 'Español'),
MapEntry(Locale('pl'), 'Polski'),
MapEntry(Locale('ru'), 'Русский язык'),
MapEntry(Locale('ru'), 'Русский'),
MapEntry(Locale('bs'), 'Bosanski'),
MapEntry(Locale('pt'), 'Brasileiro'),
MapEntry(Locale('cs'), 'Česky'),
@ -76,6 +70,19 @@ Future<void> loadTranslations() async {
fallbackTranslations: controller.fallbackTranslations);
}
@pragma('vm:entry-point')
void backgroundFetchHeadlessTask(HeadlessTask task) async {
String taskId = task.taskId;
bool isTimeout = task.timeout;
if (isTimeout) {
print('BG update task timed out.');
BackgroundFetch.finish(taskId);
return;
}
await bgUpdateCheck(taskId, null);
BackgroundFetch.finish(taskId);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
try {
@ -93,7 +100,6 @@ void main() async {
);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
}
await AndroidAlarmManager.initialize();
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppsProvider()),
@ -108,6 +114,7 @@ void main() async {
useOnlyLangCode: true,
child: const Obtainium()),
));
BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}
var defaultThemeColour = Colors.deepPurple;
@ -122,6 +129,33 @@ class Obtainium extends StatefulWidget {
class _ObtainiumState extends State<Obtainium> {
var existingUpdateInterval = -1;
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
await BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 15,
stopOnTerminate: false,
startOnBoot: true,
enableHeadless: true,
requiresBatteryNotLow: false,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
requiredNetworkType: NetworkType.ANY), (String taskId) async {
await bgUpdateCheck(taskId, null);
BackgroundFetch.finish(taskId);
}, (String taskId) async {
context.read<LogsProvider>().add('BG update task timed out.');
BackgroundFetch.finish(taskId);
});
if (!mounted) return;
}
@override
Widget build(BuildContext context) {
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
@ -137,21 +171,31 @@ class _ObtainiumState extends State<Obtainium> {
// If this is the first run, ask for notification permissions and add Obtainium to the Apps list
Permission.notification.request();
if (!fdroid) {
getInstalledInfo(obtainiumId).then((value) {
if (value?.versionName != null) {
appsProvider.saveApps([
App(
obtainiumId,
'https://github.com/ImranR98/Obtainium',
obtainiumUrl,
'ImranR98',
'Obtainium',
currentReleaseTag,
currentReleaseTag,
value!.versionName,
value.versionName!,
[],
0,
{'includePrereleases': true},
{
'versionDetection': true,
'apkFilterRegEx': 'fdroid',
'invertAPKFilter': true
},
null,
false)
], onlyIfExists: false);
}
}).catchError((err) {
print(err);
});
}
}
if (!supportedLocales
.map((e) => e.key.languageCode)
@ -161,30 +205,6 @@ class _ObtainiumState extends State<Obtainium> {
context.locale.languageCode)) {
settingsProvider.resetLocaleSafe(context);
}
// Register the background update task according to the user's setting
var actualUpdateInterval = settingsProvider.updateInterval;
if (existingUpdateInterval != actualUpdateInterval) {
if (actualUpdateInterval == 0) {
AndroidAlarmManager.cancel(bgUpdateCheckAlarmId);
} else {
var settingChanged = existingUpdateInterval != -1;
var lastCheckWasTooLongAgo = actualUpdateInterval != 0 &&
settingsProvider.lastBGCheckTime
.add(Duration(minutes: actualUpdateInterval + 60))
.isBefore(DateTime.now());
if (settingChanged || lastCheckWasTooLongAgo) {
logs.add(
'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).');
AndroidAlarmManager.periodic(
Duration(minutes: actualUpdateInterval),
bgUpdateCheckAlarmId,
bgUpdateCheck,
rescheduleOnReboot: true,
wakeup: true);
}
}
existingUpdateInterval = actualUpdateInterval;
}
}
return DynamicColorBuilder(
@ -221,13 +241,15 @@ class _ObtainiumState extends State<Obtainium> {
colorScheme: settingsProvider.theme == ThemeSettings.dark
? darkColorScheme
: lightColorScheme,
fontFamily: 'Metropolis'),
fontFamily:
settingsProvider.useSystemFont ? 'SystemFont' : 'Metropolis'),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: settingsProvider.theme == ThemeSettings.light
? lightColorScheme
: darkColorScheme,
fontFamily: 'Metropolis'),
fontFamily:
settingsProvider.useSystemFont ? 'SystemFont' : 'Metropolis'),
home: Shortcuts(shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
}, child: const HomePage()));

View File

@ -18,7 +18,7 @@ class GitHubStars implements MassAppUrlSource {
Response res = await get(
Uri.parse(
'https://api.github.com/users/$username/starred?per_page=100&page=$page'),
headers: await GitHub().getRequestHeaders());
headers: await GitHub().getRequestHeaders({}));
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body) as List<dynamic>)) {

View File

@ -59,7 +59,9 @@ class AddAppPageState extends State<AddAppPage> {
if (updateUrlInput) {
urlInputKey++;
}
var prevHost = pickedSource?.host;
var prevHost = pickedSource?.hosts.isNotEmpty == true
? pickedSource?.hosts[0]
: null;
try {
var naturalSource =
valid ? sourceProvider.getSource(userInput) : null;
@ -77,7 +79,7 @@ class AddAppPageState extends State<AddAppPage> {
overrideSource: pickedSourceOverride)
: null;
if (pickedSource.runtimeType != source.runtimeType ||
(prevHost != null && prevHost != source?.host)) {
(prevHost != null && prevHost != source?.hosts[0])) {
pickedSource = source;
additionalSettings = source != null
? getDefaultValuesFromFormItems(
@ -133,8 +135,7 @@ class AddAppPageState extends State<AddAppPage> {
getReleaseDateAsVersionConfirmationIfNeeded(
bool userPickedTrackOnly) async {
return (!(additionalSettings['versionDetection'] ==
'releaseDateAsVersion' &&
return (!(additionalSettings['releaseDateAsVersion'] == true &&
// ignore: use_build_context_synchronously
await showDialog(
context: context,
@ -190,8 +191,7 @@ class AddAppPageState extends State<AddAppPage> {
throw ObtainiumError(tr('appAlreadyAdded'));
}
if (app.additionalSettings['trackOnly'] == true ||
app.additionalSettings['versionDetection'] !=
'standardVersionDetection') {
app.additionalSettings['versionDetection'] != true) {
app.installedVersion = app.latestVersion;
}
app.categories = pickedCategories;
@ -286,10 +286,14 @@ class AddAppPageState extends State<AddAppPage> {
selectedByDefault: true,
onlyOneSelectionAllowed: false,
titlesAreLinks: false,
deselectThese: settingsProvider.searchDeselected,
);
}) ??
[];
if (searchSources.isNotEmpty) {
settingsProvider.searchDeselected = sourceStrings.keys
.where((s) => !searchSources.contains(s))
.toList();
var results = await Future.wait(sourceProvider.sources
.where((e) => searchSources.contains(e.name))
.map((e) async {
@ -306,7 +310,6 @@ class AddAppPageState extends State<AddAppPage> {
}
}));
// .then((results) async {
// Interleave results instead of simple reduce
Map<String, List<String>> res = {};
var si = 0;
@ -493,36 +496,74 @@ class AddAppPageState extends State<AddAppPage> {
],
);
Widget getSourcesListWidget() => Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
Widget getSourcesListWidget() => Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Text(
tr('supportedSources'),
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(
height: 16,
),
...sourceProvider.sources.map((e) => GestureDetector(
onTap: e.host != null
GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
return GeneratedFormModal(
singleNullReturnButton: tr('ok'),
title: tr('supportedSources'),
items: const [],
additionalWidgets: [
...sourceProvider.sources.map(
(e) => Padding(
padding:
const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: e.hosts.isNotEmpty
? () {
launchUrlString('https://${e.host}',
mode: LaunchMode.externalApplication);
launchUrlString(
'https://${e.hosts[0]}',
mode: LaunchMode
.externalApplication);
}
: null,
child: Text(
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
style: TextStyle(
decoration: e.host != null
decoration: e.hosts.isNotEmpty
? TextDecoration.underline
: TextDecoration.none,
: TextDecoration.none),
))),
),
const SizedBox(
height: 16,
),
Text(
'${tr('note')}:',
style:
const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(
height: 4,
),
Text(tr('selfHostedNote',
args: [tr('overrideSource')])),
],
);
},
);
},
child: Text(
tr('supportedSources'),
style: const TextStyle(
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
fontStyle: FontStyle.italic),
)))
]);
))
],
),
);
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
bottomNavigationBar:
pickedSource == null ? getSourcesListWidget() : null,
body: CustomScrollView(shrinkWrap: true, slivers: <Widget>[
CustomAppBar(title: tr('addApp')),
SliverToBoxAdapter(
@ -554,18 +595,7 @@ class AddAppPageState extends State<AddAppPage> {
: const SizedBox();
},
future: pickedSource?.getSourceNote()),
SizedBox(
height: pickedSource != null ? 16 : 96,
),
if (pickedSource != null) getAdditionalOptsCol(),
if (pickedSource == null)
const Divider(
height: 48,
),
if (pickedSource == null) getSourcesListWidget(),
SizedBox(
height: pickedSource != null ? 8 : 2,
),
])),
)
]));

View File

@ -1,7 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
@ -24,16 +23,35 @@ class AppPage extends StatefulWidget {
class _AppPageState extends State<AppPage> {
AppInMemory? prevApp;
bool updating = false;
@override
Widget build(BuildContext context) {
var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.watch<SettingsProvider>();
getUpdate(String id) {
appsProvider.checkUpdate(id).catchError((e) {
showError(e, context);
return null;
getUpdate(String id, {bool resetVersion = false}) async {
try {
setState(() {
updating = true;
});
await appsProvider.checkUpdate(id);
if (resetVersion) {
appsProvider.apps[id]?.app.additionalSettings['versionDetection'] =
true;
if (appsProvider.apps[id]?.app.installedVersion != null) {
appsProvider.apps[id]?.app.installedVersion =
appsProvider.apps[id]?.app.latestVersion;
}
appsProvider.saveApps([appsProvider.apps[id]!.app]);
}
} catch (err) {
// ignore: use_build_context_synchronously
showError(err, context);
} finally {
setState(() {
updating = false;
});
}
}
bool areDownloadsRunning = appsProvider.areDownloadsRunning();
@ -54,80 +72,70 @@ class _AppPageState extends State<AppPage> {
var trackOnly = app?.app.additionalSettings['trackOnly'] == true;
bool isVersionDetectionStandard =
app?.app.additionalSettings['versionDetection'] ==
'standardVersionDetection';
app?.app.additionalSettings['versionDetection'] == true;
bool installedVersionIsEstimate = trackOnly ||
(app?.app.installedVersion != null &&
app?.app.additionalSettings['versionDetection'] ==
'noVersionDetection');
app?.app.additionalSettings['versionDetection'] != true);
getInfoColumn() => Column(
getInfoColumn() {
String versionLines = '';
bool installed = app?.app.installedVersion != null;
bool upToDate = app?.app.installedVersion == app?.app.latestVersion;
if (installed) {
versionLines = '${app?.app.installedVersion} ${tr('installed')}';
if (upToDate) {
versionLines += '/${tr('latest')}';
}
} else {
versionLines = tr('notInstalled');
}
if (!upToDate) {
versionLines += '\n${app?.app.latestVersion} ${tr('latest')}';
}
String infoLines = tr('lastUpdateCheckX', args: [
app?.app.lastUpdateCheck == null
? tr('never')
: '${app?.app.lastUpdateCheck?.toLocal()}'
]);
if (trackOnly) {
infoLines = '${tr('xIsTrackOnly', args: [tr('app')])}\n$infoLines';
}
if (installedVersionIsEstimate) {
infoLines = '${tr('pseudoVersionInUse')}\n$infoLines';
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
GestureDetector(
onTap: () {
if (app?.app.url != null) {
launchUrlString(app?.app.url ?? '',
mode: LaunchMode.externalApplication);
}
},
onLongPress: () {
Clipboard.setData(ClipboardData(text: app?.app.url ?? ''));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(tr('copiedToClipboard')),
));
},
child: Text(
app?.app.url ?? '',
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 24),
child: Column(
children: [
const SizedBox(
height: 8,
),
Text(versionLines,
textAlign: TextAlign.start,
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(fontWeight: FontWeight.bold)),
app?.app.releaseDate == null
? const SizedBox.shrink()
: Text(
app!.app.releaseDate.toString(),
textAlign: TextAlign.center,
style: const TextStyle(
decoration: TextDecoration.underline,
fontStyle: FontStyle.italic,
fontSize: 12),
)),
const SizedBox(
height: 32,
),
Column(
children: [
Text(
'${tr('latestVersionX', args: [
app?.app.latestVersion ?? tr('unknown')
])}\n${tr('installedVersionX', args: [
app?.app.installedVersion ?? tr('none')
])}${installedVersionIsEstimate ? '\n${tr('estimateInBrackets')}' : ''}',
textAlign: TextAlign.end,
style: Theme.of(context).textTheme.bodyLarge!,
),
],
),
if (app?.app.installedVersion != null &&
!isVersionDetectionStandard)
Column(
children: [
const SizedBox(
height: 16,
),
Text(
'${trackOnly ? '${tr('xIsTrackOnly', args: [
tr('app')
])}\n' : ''}${tr('noVersionDetection')}',
style: Theme.of(context).textTheme.labelSmall,
textAlign: TextAlign.center,
)
],
),
const SizedBox(
height: 32,
height: 8,
),
],
),
),
Text(
tr('lastUpdateCheckX', args: [
app?.app.lastUpdateCheck == null
? tr('never')
: '\n${app?.app.lastUpdateCheck?.toLocal()}'
]),
infoLines,
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
),
@ -170,6 +178,7 @@ class _AppPageState extends State<AppPage> {
),
],
);
}
getFullInfoColumn() => Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -196,29 +205,37 @@ class _AppPageState extends State<AppPage> {
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.displayLarge,
),
Text(
tr('byX', args: [app?.app.author ?? tr('unknown')]),
Text(tr('byX', args: [app?.app.author ?? tr('unknown')]),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium,
),
style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(
height: 8,
height: 24,
),
GestureDetector(
onTap: () {
if (app?.app.url != null) {
launchUrlString(app?.app.url ?? '',
mode: LaunchMode.externalApplication);
}
},
onLongPress: () {
Clipboard.setData(ClipboardData(text: app?.app.url ?? ''));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(tr('copiedToClipboard')),
));
},
child: Text(
app?.app.url ?? '',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelSmall!.copyWith(
decoration: TextDecoration.underline,
fontStyle: FontStyle.italic),
)),
Text(
app?.app.id ?? '',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelSmall,
),
app?.app.releaseDate == null
? const SizedBox.shrink()
: Text(
app!.app.releaseDate.toString(),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelSmall,
),
const SizedBox(
height: 32,
),
getInfoColumn(),
const SizedBox(height: 150)
],
@ -287,25 +304,6 @@ class _AppPageState extends State<AppPage> {
return row;
}).toList();
items = items.map((row) {
row = row.map((e) {
if (e.key == 'versionDetection' && e is GeneratedFormDropdown) {
e.disabledOptKeys ??= [];
if (app?.app.installedVersion != null &&
app?.app.additionalSettings['versionDetection'] !=
'releaseDateAsVersion' &&
!appsProvider.isVersionDetectionPossible(app)) {
e.disabledOptKeys!.add('standardVersionDetection');
}
if (app?.app.releaseDate == null) {
e.disabledOptKeys!.add('releaseDateAsVersion');
}
}
return e;
}).toList();
return row;
}).toList();
return GeneratedFormModal(
title: tr('additionalOptions'), items: items);
});
@ -320,44 +318,41 @@ class _AppPageState extends State<AppPage> {
// ignore: use_build_context_synchronously
showMessage(tr('appsFromSourceAreTrackOnly'), context);
}
if (app.app.additionalSettings['versionDetection'] ==
'releaseDateAsVersion') {
if (originalSettings['versionDetection'] != 'releaseDateAsVersion') {
var versionDetectionEnabled =
app.app.additionalSettings['versionDetection'] == true &&
originalSettings['versionDetection'] != true;
var releaseDateVersionEnabled =
app.app.additionalSettings['releaseDateAsVersion'] == true &&
originalSettings['releaseDateAsVersion'] != true;
var releaseDateVersionDisabled =
app.app.additionalSettings['releaseDateAsVersion'] != true &&
originalSettings['releaseDateAsVersion'] == true;
if (releaseDateVersionEnabled) {
if (app.app.releaseDate != null) {
bool isUpdated =
app.app.installedVersion == app.app.latestVersion;
bool isUpdated = app.app.installedVersion == app.app.latestVersion;
app.app.latestVersion =
app.app.releaseDate!.microsecondsSinceEpoch.toString();
if (isUpdated) {
app.app.installedVersion = app.app.latestVersion;
}
}
}
} else if (originalSettings['versionDetection'] ==
'releaseDateAsVersion') {
} else if (releaseDateVersionDisabled) {
app.app.installedVersion =
app.installedInfo?.versionName ?? app.app.installedVersion;
}
if (versionDetectionEnabled) {
app.app.additionalSettings['versionDetection'] = true;
app.app.additionalSettings['releaseDateAsVersion'] = false;
}
appsProvider.saveApps([app.app]).then((value) {
getUpdate(app.app.id);
getUpdate(app.app.id, resetVersion: versionDetectionEnabled);
});
}
}
getResetInstallStatusButton() => TextButton(
onPressed: app?.app == null
? null
: () {
app!.app.installedVersion = null;
appsProvider.saveApps([app.app]);
},
child: Text(
tr('resetInstallStatus'),
textAlign: TextAlign.center,
));
getInstallOrUpdateButton() => TextButton(
onPressed: (app?.app.installedVersion == null ||
onPressed: !updating &&
(app?.app.installedVersion == null ||
app?.app.installedVersion != app?.app.latestVersion) &&
!areDownloadsRunning
? () async {
@ -367,7 +362,7 @@ class _AppPageState extends State<AppPage> {
app?.app.id != null ? [app!.app.id] : [],
globalNavigatorKey.currentContext,
);
if (app?.app.installedVersion != null && !trackOnly) {
if (res.isNotEmpty && !trackOnly) {
// ignore: use_build_context_synchronously
showMessage(tr('appsUpdated'), context);
}
@ -399,20 +394,10 @@ class _AppPageState extends State<AppPage> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (app?.app.installedVersion != null &&
app?.app.installedVersion != app?.app.latestVersion &&
!isVersionDetectionStandard &&
!trackOnly)
IconButton(
onPressed: app?.downloadProgress != null
? null
: showMarkUpdatedDialog,
tooltip: tr('markUpdated'),
icon: const Icon(Icons.done)),
if (source != null &&
source.combinedAppSpecificSettingFormItems.isNotEmpty)
IconButton(
onPressed: app?.downloadProgress != null
onPressed: app?.downloadProgress != null || updating
? null
: () async {
var values =
@ -454,17 +439,33 @@ class _AppPageState extends State<AppPage> {
},
icon: const Icon(Icons.more_horiz),
tooltip: tr('more')),
const SizedBox(width: 16.0),
Expanded(
child: (!isVersionDetectionStandard || trackOnly) &&
if (app?.app.installedVersion != null &&
app?.app.installedVersion != app?.app.latestVersion &&
!isVersionDetectionStandard &&
!trackOnly)
IconButton(
onPressed: app?.downloadProgress != null || updating
? null
: showMarkUpdatedDialog,
tooltip: tr('markUpdated'),
icon: const Icon(Icons.done)),
if ((!isVersionDetectionStandard || trackOnly) &&
app?.app.installedVersion != null &&
app?.app.installedVersion ==
app?.app.latestVersion
? getResetInstallStatusButton()
: getInstallOrUpdateButton()),
app?.app.installedVersion == app?.app.latestVersion)
IconButton(
onPressed: app?.app == null || updating
? null
: () {
app!.app.installedVersion = null;
appsProvider.saveApps([app.app]);
},
icon: const Icon(Icons.restore_rounded),
tooltip: tr('resetInstallStatus')),
const SizedBox(width: 16.0),
Expanded(child: getInstallOrUpdateButton()),
const SizedBox(width: 16.0),
IconButton(
onPressed: app?.downloadProgress != null
onPressed: app?.downloadProgress != null || updating
? null
: () {
appsProvider

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -356,6 +358,16 @@ class AppsPageState extends State<AppsPage> {
String? changesUrl =
appSource.changeLogPageFromStandardUrl(listedApps[appIndex].app.url);
String? changeLog = listedApps[appIndex].app.changeLog;
if (changeLog?.split('\n').length == 1) {
if (RegExp(
'(http|ftp|https)://([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?')
.hasMatch(changeLog!)) {
if (changesUrl == null) {
changesUrl = changeLog;
changeLog = null;
}
}
}
return (changeLog == null && changesUrl == null)
? null
: () {
@ -419,7 +431,7 @@ class AppsPageState extends State<AppsPage> {
}
getVersionText(int appIndex) {
return '${listedApps[appIndex].app.installedVersion ?? tr('notInstalled')}${listedApps[appIndex].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}';
return '${listedApps[appIndex].app.installedVersion ?? tr('notInstalled')}${listedApps[appIndex].app.additionalSettings['trackOnly'] == true ? ' ${tr('pseudoVersion')}' : ''}';
}
getChangesButtonString(int appIndex, bool hasChangeLogFn) {
@ -694,7 +706,7 @@ class AppsPageState extends State<AppsPage> {
showError(e, context);
return <String>[];
}).then((value) {
if (shouldInstallUpdates) {
if (value.isNotEmpty && shouldInstallUpdates) {
showMessage(tr('appsUpdated'), context);
}
});
@ -871,20 +883,42 @@ class AppsPageState extends State<AppsPage> {
onPressed: () {
String urls = '';
for (var a in selectedApps) {
urls += 'obtainium://add/${a.url}\n';
urls += '${a.url}\n';
}
urls = urls.substring(0, urls.length - 1);
Share.share(urls,
subject: tr('selectedAppURLsFromObtainium'));
subject: 'Obtainium - ${tr('appsString')}');
Navigator.of(context).pop();
},
tooltip: tr('shareSelectedAppURLs'),
icon: const Icon(Icons.share),
icon: const Icon(Icons.share_rounded),
),
IconButton(
onPressed: resetSelectedAppsInstallStatuses,
tooltip: tr('resetInstallStatus'),
icon: const Icon(Icons.restore_page_outlined),
onPressed: selectedAppIds.isEmpty
? null
: () {
String urls =
'<p>${tr('customLinkMessage')}:</p>\n\n<ul>\n';
for (var a in selectedApps) {
urls +=
' <li><a href="obtainium://app/${Uri.encodeComponent(jsonEncode({
'id': a.id,
'url': a.url,
'author': a.author,
'name': a.name,
'preferredApkIndex':
a.preferredApkIndex,
'additionalSettings':
jsonEncode(a.additionalSettings)
}))}">${a.name}</a></li>\n';
}
urls +=
'</ul>\n\n<p><a href="$obtainiumUrl">${tr('about')}</a></p>';
Share.share(urls,
subject: 'Obtainium - ${tr('appsString')}');
},
tooltip: tr('shareAppConfigLinks'),
icon: const Icon(Icons.ios_share),
),
]),
),
@ -1004,7 +1038,7 @@ class AppsPageState extends State<AppsPage> {
IconButton(
color: Theme.of(context).colorScheme.primary,
style: const ButtonStyle(visualDensity: VisualDensity.compact),
tooltip: isFilterOff ? tr('filter') : tr('filterActive'),
tooltip: '${tr('filter')}${isFilterOff ? '' : ' *'}',
onPressed: isFilterOff
? showFilterDialog
: () {

View File

@ -5,6 +5,7 @@ import 'package:app_links/app_links.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/pages/add_app.dart';
import 'package:obtainium/pages/apps.dart';
@ -76,14 +77,39 @@ class _HomePageState extends State<HomePage> {
try {
if (action == 'add') {
await goToAddApp(data);
} else if (action == 'app') {
await context
.read<AppsProvider>()
.import('{ "apps": [${Uri.decodeComponent(data)}] }');
} else if (action == 'apps') {
await context
.read<AppsProvider>()
.import('{ "apps": ${Uri.decodeComponent(data)} }');
} else if (action == 'app' || action == 'apps') {
var dataStr = Uri.decodeComponent(data);
if (await showDialog(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: tr('importX', args: [
action == 'app' ? tr('app') : tr('appsString')
]),
items: const [],
additionalWidgets: [
ExpansionTile(
title: const Text('Raw JSON'),
children: [
Text(
dataStr,
style: const TextStyle(fontFamily: 'monospace'),
)
],
)
],
);
}) !=
null) {
// ignore: use_build_context_synchronously
var result = await context.read<AppsProvider>().import(
action == 'app'
? '{ "apps": [$dataStr] }'
: '{ "apps": $dataStr }');
// ignore: use_build_context_synchronously
showMessage(
tr('importedX', args: [plural('apps', result.key)]), context);
}
} else {
throw ObtainiumError(tr('unknown'));
}

View File

@ -199,10 +199,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
...source.searchQuerySettingFormItems.map((e) => [e]),
[
GeneratedFormTextField('url',
label: source.host != null
label: source.hosts.isNotEmpty
? tr('overrideSource')
: plural('url', 1).substring(2),
defaultValue: source.host ?? '',
defaultValue:
source.hosts.isNotEmpty ? source.hosts[0] : '',
required: true)
],
],
@ -212,7 +213,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
setState(() {
importInProgress = true;
});
if (values['url'] != source.host) {
if (values['url'] != source.hosts[0]) {
source = sourceProvider.getSource(values['url'],
overrideSource: source.runtimeType.toString());
}
@ -590,7 +591,7 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
onPressed: () {
Navigator.of(context).pop(null);
},
child: Text(tr('okay')))
child: Text(tr('ok')))
],
);
}
@ -604,11 +605,13 @@ class SelectionModal extends StatefulWidget {
this.selectedByDefault = true,
this.onlyOneSelectionAllowed = false,
this.titlesAreLinks = true,
this.title});
this.title,
this.deselectThese = const []});
String? title;
Map<String, List<String>> entries;
bool selectedByDefault;
List<String> deselectThese;
bool onlyOneSelectionAllowed;
bool titlesAreLinks;
@ -622,9 +625,13 @@ class _SelectionModalState extends State<SelectionModal> {
@override
void initState() {
super.initState();
for (var url in widget.entries.entries) {
entrySelections.putIfAbsent(url,
() => widget.selectedByDefault && !widget.onlyOneSelectionAllowed);
for (var entry in widget.entries.entries) {
entrySelections.putIfAbsent(
entry,
() =>
widget.selectedByDefault &&
!widget.onlyOneSelectionAllowed &&
!widget.deselectThese.contains(entry.key));
}
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
selectOnlyOne(widget.entries.entries.first.key);

View File

@ -1,4 +1,3 @@
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -8,6 +7,7 @@ import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/logs_provider.dart';
import 'package:obtainium/providers/native_provider.dart';
import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart';
import 'package:provider/provider.dart';
@ -38,9 +38,9 @@ class _SettingsPageState extends State<SettingsPage> {
value: InstallMethodSettings.normal,
child: Text(tr('normal')),
),
DropdownMenuItem(
const DropdownMenuItem(
value: InstallMethodSettings.shizuku,
child: Text(tr('shizuku')),
child: Text('Shizuku'),
),
DropdownMenuItem(
value: InstallMethodSettings.root,
@ -79,14 +79,14 @@ class _SettingsPageState extends State<SettingsPage> {
var colourDropdown = DropdownButtonFormField(
decoration: InputDecoration(labelText: tr('colour')),
value: settingsProvider.colour,
items: [
items: const [
DropdownMenuItem(
value: ColourSettings.basic,
child: Text(tr('obtainium')),
child: Text('Obtainium'),
),
DropdownMenuItem(
value: ColourSettings.materialYou,
child: Text(tr('materialYou')),
child: Text('Material You'),
)
],
onChanged: (value) {
@ -351,8 +351,6 @@ class _SettingsPageState extends State<SettingsPage> {
],
),
height16,
installMethodDropdown,
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -365,6 +363,7 @@ class _SettingsPageState extends State<SettingsPage> {
})
],
),
installMethodDropdown,
height32,
Text(
tr('sourceSpecific'),
@ -409,6 +408,34 @@ class _SettingsPageState extends State<SettingsPage> {
height16,
localeDropdown,
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(tr('useSystemFont'))),
Switch(
value: settingsProvider.useSystemFont,
onChanged: (useSystemFont) {
if (useSystemFont) {
NativeFeatures.loadSystemFont()
.then((fontLoadRes) {
if (fontLoadRes == 'ok') {
settingsProvider.useSystemFont =
true;
} else {
showError(
ObtainiumError(tr(
'systemFontError',
args: [fontLoadRes])),
context);
}
});
} else {
settingsProvider.useSystemFont = false;
}
})
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -605,41 +632,9 @@ class _SettingsPageState extends State<SettingsPage> {
label: Text(tr('appLogs'))),
],
),
const Divider(
height: 32,
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(tr('debugMenu'))),
Switch(
value: settingsProvider.showDebugOpts,
onChanged: (value) {
settingsProvider.showDebugOpts = value;
})
],
),
if (settingsProvider.showDebugOpts)
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
height16,
TextButton(
onPressed: () {
AndroidAlarmManager.oneShot(
const Duration(seconds: 0),
bgUpdateCheckAlarmId + 200,
bgUpdateCheck);
showMessage(tr('bgTaskStarted'), context);
},
child: Text(tr('runBgCheckNow')))
],
),
]),
),
const SizedBox(
height: 16,
)
],
),
)

View File

@ -8,7 +8,6 @@ import 'dart:math';
import 'package:http/http.dart' as http;
import 'package:crypto/crypto.dart';
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
import 'package:android_intent_plus/flag.dart';
import 'package:android_package_installer/android_package_installer.dart';
import 'package:android_package_manager/android_package_manager.dart';
@ -33,7 +32,7 @@ import 'package:http/http.dart';
import 'package:android_intent_plus/android_intent.dart';
import 'package:flutter_archive/flutter_archive.dart';
import 'package:shared_storage/shared_storage.dart' as saf;
import 'installers_provider.dart';
import 'native_provider.dart';
final pm = AndroidPackageManager();
@ -168,8 +167,24 @@ String hashListOfLists(List<List<int>> data) {
return hash.hashCode.toString();
}
Future<String> checkDownloadHash(String url,
{int bytesToGrab = 1024, Map<String, String>? headers}) async {
Future<String> checkPartialDownloadHashDynamic(String url,
{int startingSize = 1024,
int lowerLimit = 128,
Map<String, String>? headers}) async {
for (int i = startingSize; i >= lowerLimit; i -= 256) {
List<String> ab = await Future.wait([
checkPartialDownloadHash(url, i, headers: headers),
checkPartialDownloadHash(url, i, headers: headers)
]);
if (ab[0] == ab[1]) {
return ab[0];
}
}
throw NoVersionError();
}
Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
{Map<String, String>? headers}) async {
var req = Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
@ -228,13 +243,29 @@ Future<File> downloadFile(
tempDownloadedFile.deleteSync(recursive: true);
throw response.reasonPhrase ?? tr('unexpectedError');
}
if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.renameSync(downloadedFile.path);
}
} else {
client.close();
}
return downloadedFile;
}
Future<PackageInfo?> getInstalledInfo(String? packageName,
{bool printErr = true}) async {
if (packageName != null) {
try {
return await pm.getPackageInfo(packageName: packageName);
} catch (e) {
if (printErr) {
print(e); // OK
}
}
}
return null;
}
class AppsProvider with ChangeNotifier {
// In memory App state (should always be kept in sync with local storage versions)
Map<String, AppInMemory> apps = {};
@ -327,13 +358,15 @@ class AppsProvider with ChangeNotifier {
AppSource source = SourceProvider()
.getSource(app.url, overrideSource: app.overrideSource);
String downloadUrl = await source.apkUrlPrefetchModifier(
app.apkUrls[app.preferredApkIndex].value, app.url);
app.apkUrls[app.preferredApkIndex].value,
app.url,
app.additionalSettings);
var notif = DownloadNotification(app.finalName, 100);
notificationsProvider?.cancel(notif.id);
int? prevProg;
var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}';
var headers = await source.getRequestHeaders(
additionalSettings: app.additionalSettings, forAPKDownload: true);
var headers = await source.getRequestHeaders(app.additionalSettings,
forAPKDownload: true);
var downloadedFile = await downloadFileWithRetry(
downloadUrl, fileNameNoExt,
headers: headers, (double? progress) {
@ -499,9 +532,18 @@ class AppsProvider with ChangeNotifier {
{bool needsBGWorkaround = false}) async {
var newInfo =
await pm.getPackageArchiveInfo(archiveFilePath: file.file.path);
if (newInfo == null) {
try {
file.file.deleteSync(recursive: true);
} catch (e) {
//
} finally {
throw ObtainiumError(tr('badDownload'));
}
}
PackageInfo? appInfo = await getInstalledInfo(apps[file.appId]!.app.id);
if (appInfo != null &&
newInfo!.versionCode! < appInfo.versionCode! &&
newInfo.versionCode! < appInfo.versionCode! &&
!(await canDowngradeApps())) {
throw DowngradeError();
}
@ -523,12 +565,13 @@ class AppsProvider with ChangeNotifier {
code = await AndroidPackageInstaller.installApk(
apkFilePath: file.file.path);
case InstallMethodSettings.shizuku:
code = (await Installers.installWithShizuku(
code = (await NativeFeatures.installWithShizuku(
apkFileUri: file.file.uri.toString()))
? 0
: 1;
case InstallMethodSettings.root:
code = (await Installers.installWithRoot(apkFilePath: file.file.path))
code =
(await NativeFeatures.installWithRoot(apkFilePath: file.file.path))
? 0
: 1;
}
@ -621,7 +664,8 @@ class AppsProvider with ChangeNotifier {
// Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
Future<List<String>> downloadAndInstallLatestApps(
List<String> appIds, BuildContext? context,
{NotificationsProvider? notificationsProvider}) async {
{NotificationsProvider? notificationsProvider,
bool forceParallelDownloads = false}) async {
notificationsProvider =
notificationsProvider ?? context?.read<NotificationsProvider>();
List<String> appsToInstall = [];
@ -636,6 +680,7 @@ class AppsProvider with ChangeNotifier {
MapEntry<String, String>? apkUrl;
var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true;
if (!trackOnly) {
// ignore: use_build_context_synchronously
apkUrl = await confirmApkUrl(apps[id]!.app, context);
}
if (apkUrl != null) {
@ -694,14 +739,14 @@ class AppsProvider with ChangeNotifier {
throw ObtainiumError(tr('cancelled'));
}
case InstallMethodSettings.shizuku:
int code = await Installers.checkPermissionShizuku();
int code = await NativeFeatures.checkPermissionShizuku();
if (code == -1) {
throw ObtainiumError(tr('shizukuBinderNotFound'));
} else if (code == 0) {
throw ObtainiumError(tr('cancelled'));
}
case InstallMethodSettings.root:
if (!(await Installers.checkPermissionRoot())) {
if (!(await NativeFeatures.checkPermissionRoot())) {
throw ObtainiumError(tr('cancelled'));
}
}
@ -742,7 +787,7 @@ class AppsProvider with ChangeNotifier {
}
}
if (!settingsProvider.parallelDownloads) {
if (forceParallelDownloads || !settingsProvider.parallelDownloads) {
for (var id in appsToInstall) {
await updateFn(id);
}
@ -772,20 +817,6 @@ class AppsProvider with ChangeNotifier {
return appsDir;
}
Future<PackageInfo?> getInstalledInfo(String? packageName,
{bool printErr = true}) async {
if (packageName != null) {
try {
return await pm.getPackageInfo(packageName: packageName);
} catch (e) {
if (printErr) {
print(e); // OK
}
}
}
return null;
}
bool isVersionDetectionPossible(AppInMemory? app) {
if (app?.app == null) {
return false;
@ -795,13 +826,16 @@ class AppsProvider with ChangeNotifier {
SourceProvider()
.getSource(app.app.url, overrideSource: app.app.overrideSource)
.naiveStandardVersionDetection;
String? realInstalledVersion =
app.app.additionalSettings['useVersionCodeAsOSVersion'] == true
? app.installedInfo?.versionCode.toString()
: app.installedInfo?.versionName;
return app.app.additionalSettings['trackOnly'] != true &&
app.app.additionalSettings['versionDetection'] !=
'releaseDateAsVersion' &&
app.installedInfo?.versionName != null &&
app.app.additionalSettings['releaseDateAsVersion'] != true &&
realInstalledVersion != null &&
app.app.installedVersion != null &&
(reconcileVersionDifferences(app.installedInfo!.versionName!,
app.app.installedVersion!) !=
(reconcileVersionDifferences(
realInstalledVersion, app.app.installedVersion!) !=
null ||
naiveStandardVersionDetection);
}
@ -813,37 +847,39 @@ class AppsProvider with ChangeNotifier {
var modded = false;
var trackOnly = app.additionalSettings['trackOnly'] == true;
var versionDetectionIsStandard =
app.additionalSettings['versionDetection'] ==
'standardVersionDetection';
app.additionalSettings['versionDetection'] == true;
var naiveStandardVersionDetection =
app.additionalSettings['naiveStandardVersionDetection'] == true ||
SourceProvider()
.getSource(app.url, overrideSource: app.overrideSource)
.naiveStandardVersionDetection;
String? realInstalledVersion =
app.additionalSettings['useVersionCodeAsOSVersion'] == true
? installedInfo?.versionCode.toString()
: installedInfo?.versionName;
// FIRST, COMPARE THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE ONE IS NULL
if (installedInfo == null && app.installedVersion != null && !trackOnly) {
// App says it's installed but isn't really (and isn't track only) - set to not installed
app.installedVersion = null;
modded = true;
} else if (installedInfo?.versionName != null &&
app.installedVersion == null) {
// App says it's not installed but really is - set to installed and use real package versionName
app.installedVersion = installedInfo!.versionName;
} else if (realInstalledVersion != null && app.installedVersion == null) {
// App says it's not installed but really is - set to installed and use real package versionName (or versionCode if chosen)
app.installedVersion = realInstalledVersion;
modded = true;
}
// SECOND, RECONCILE DIFFERENCES BETWEEN THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE NEITHER IS NULL
if (installedInfo?.versionName != null &&
installedInfo!.versionName != app.installedVersion &&
if (realInstalledVersion != null &&
realInstalledVersion != app.installedVersion &&
versionDetectionIsStandard) {
// App's reported version and real version don't match (and it uses standard version detection)
// If they share a standard format (and are still different under it), update the reported version accordingly
var correctedInstalledVersion = reconcileVersionDifferences(
installedInfo.versionName!, app.installedVersion!);
realInstalledVersion, app.installedVersion!);
if (correctedInstalledVersion?.key == false) {
app.installedVersion = correctedInstalledVersion!.value;
modded = true;
} else if (naiveStandardVersionDetection) {
app.installedVersion = installedInfo.versionName;
app.installedVersion = realInstalledVersion;
modded = true;
}
}
@ -865,7 +901,7 @@ class AppsProvider with ChangeNotifier {
versionDetectionIsStandard &&
!isVersionDetectionPossible(
AppInMemory(app, null, installedInfo, null))) {
app.additionalSettings['versionDetection'] = 'noVersionDetection';
app.additionalSettings['versionDetection'] = false;
logs.add('Could not reconcile version formats for: ${app.id}');
modded = true;
}
@ -907,6 +943,17 @@ class AppsProvider with ChangeNotifier {
: false;
}
Future<void> updateInstallStatusInMemory(AppInMemory app) async {
apps[app.app.id]?.installedInfo = await getInstalledInfo(app.app.id);
apps[app.app.id]?.icon =
await apps[app.app.id]?.installedInfo?.applicationInfo?.getAppIcon();
apps[app.app.id]?.app.name = await (apps[app.app.id]
?.installedInfo
?.applicationInfo
?.getAppLabel()) ??
app.name;
}
Future<void> loadApps({String? singleId}) async {
while (loadingApps) {
await Future.delayed(const Duration(microseconds: 1));
@ -955,19 +1002,11 @@ class AppsProvider with ChangeNotifier {
NotificationsProvider().notify(
AppsRemovedNotification(errors.map((e) => [e[1], e[2]]).toList()));
}
for (var app in apps.values) {
// Get install status and other OS info for each App (slow)
apps[app.app.id]?.installedInfo = await getInstalledInfo(app.app.id);
apps[app.app.id]?.icon =
await apps[app.app.id]?.installedInfo?.applicationInfo?.getAppIcon();
apps[app.app.id]?.app.name = await (apps[app.app.id]
?.installedInfo
?.applicationInfo
?.getAppLabel()) ??
app.name;
await Future.wait(apps.values.map((app) {
return updateInstallStatusInMemory(app);
}));
notifyListeners();
}
// Reconcile version differences
List<App> modifiedApps = [];
for (var app in apps.values) {
@ -990,7 +1029,6 @@ class AppsProvider with ChangeNotifier {
}
}
}
loadingApps = false;
notifyListeners();
}
@ -1288,8 +1326,11 @@ class AppsProvider with ChangeNotifier {
await Future.delayed(const Duration(microseconds: 1));
}
for (App a in importedApps) {
var installedInfo = await getInstalledInfo(a.id, printErr: false);
a.installedVersion =
(await getInstalledInfo(a.id, printErr: false))?.versionName;
a.additionalSettings['useVersionCodeAsOSVersion'] == true
? installedInfo?.versionCode.toString()
: installedInfo?.versionName;
}
await saveApps(importedApps, onlyIfExists: false);
notifyListeners();
@ -1300,6 +1341,9 @@ class AppsProvider with ChangeNotifier {
settingsProvider.prefs?.setInt(key, value);
} else if (value is bool) {
settingsProvider.prefs?.setBool(key, value);
} else if (value is List) {
settingsProvider.prefs
?.setStringList(key, value.map((e) => e as String).toList());
} else {
settingsProvider.prefs?.setString(key, value as String);
}
@ -1448,19 +1492,17 @@ class _APKOriginWarningDialogState extends State<APKOriginWarningDialog> {
/// When toCheck is empty, the function is in "install mode" (else it is in "update mode").
/// In update mode, all apps in toCheck are checked for updates (in parallel).
/// If an update is available and it cannot be installed silently, the user is notified of the available update.
/// If there are any errors, the task is run again for the remaining apps after a few minutes (based on the error with the longest retry interval).
/// Any app that has reached it's retry limit, the user is notified that it could not be checked.
/// If there are any errors, we recursively call the same function with retry count for the relevant apps decremented (if zero, the user is notified).
///
/// Once all update checks are complete, the task is run again in install mode.
/// In this mode, all pending silent updates are downloaded and installed in the background (serially - one at a time).
/// If there is an error, the offending app is moved to the back of the line of remaining apps, and the task is retried.
/// If an app repeatedly fails to install up to its retry limit, the user is notified.
/// In this mode, all pending silent updates are downloaded (in parallel) and installed in the background.
/// If there is an error, the user is notified.
///
@pragma('vm:entry-point')
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
// ignore: avoid_print
print('Started $taskId: ${params.toString()}');
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
await AndroidAlarmManager.initialize();
await loadTranslations();
LogsProvider logs = LogsProvider();
@ -1469,11 +1511,20 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
await appsProvider.loadApps();
int maxAttempts = 4;
int maxRetryWaitSeconds = 5;
var netResult = await (Connectivity().checkConnectivity());
if (netResult == ConnectivityResult.none) {
logs.add('BG update task: No network.');
return;
}
params ??= {};
if (params['toCheck'] == null) {
appsProvider.settingsProvider.lastBGCheckTime = DateTime.now();
}
bool firstEverUpdateTask = DateTime.fromMillisecondsSinceEpoch(0)
.compareTo(appsProvider.settingsProvider.lastCompletedBGCheckTime) ==
0;
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
...(params['toCheck']
?.map((entry) => MapEntry<String, int>(
@ -1481,6 +1532,11 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
.toList() ??
appsProvider
.getAppsSortedByUpdateCheckTime(
ignoreAppsCheckedAfter: params['toCheck'] == null
? firstEverUpdateTask
? null
: appsProvider.settingsProvider.lastCompletedBGCheckTime
: null,
onlyCheckInstalledOrTrackOnlyApps: appsProvider
.settingsProvider.onlyCheckInstalledOrTrackOnlyApps)
.map((e) => MapEntry(e, 0)))
@ -1493,51 +1549,34 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
(<List<MapEntry<String, int>>>[]))
];
var netResult = await (Connectivity().checkConnectivity());
if (netResult == ConnectivityResult.none) {
var networkBasedRetryInterval = 15;
var nextRegularCheck = appsProvider.settingsProvider.lastBGCheckTime
.add(Duration(minutes: appsProvider.settingsProvider.updateInterval));
var potentialNetworkRetryCheck =
DateTime.now().add(Duration(minutes: networkBasedRetryInterval));
var shouldRetry = potentialNetworkRetryCheck.isBefore(nextRegularCheck);
logs.add(
'BG update task $taskId: No network. Will ${shouldRetry ? 'retry in $networkBasedRetryInterval minutes' : 'not retry'}.');
AndroidAlarmManager.oneShot(
const Duration(minutes: 15), taskId + 1, bgUpdateCheck,
params: {
'toCheck': toCheck
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
'toInstall': toInstall
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
});
return;
}
var networkRestricted = false;
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
networkRestricted = (netResult != ConnectivityResult.wifi) &&
(netResult != ConnectivityResult.ethernet);
}
bool installMode =
toCheck.isEmpty; // Task is either in update mode or install mode
logs.add(
'BG ${installMode ? 'install' : 'update'} task $taskId: Started (${installMode ? toInstall.length : toCheck.length}).');
if (!installMode) {
if (toCheck.isNotEmpty) {
// Task is either in update mode or install mode
// If in update mode, we check for updates.
// We divide the results into 4 groups:
// - toNotify - Apps with updates that the user will be notified about (can't be silently installed)
// - toRetry - Apps with update check errors that will be retried in a while
// - toThrow - Apps with update check errors that the user will be notified about (no retry)
// After grouping the updates, we take care of toNotify and toThrow first
// Then if toRetry is not empty, we schedule another update task to run in a while
// If toRetry is empty, we take care of schedule another task that will run in install mode (toCheck is empty)
// Then we run the function again in install mode (toCheck is empty)
var enoughTimePassed = appsProvider.settingsProvider.updateInterval != 0 &&
appsProvider.settingsProvider.lastCompletedBGCheckTime
.add(
Duration(minutes: appsProvider.settingsProvider.updateInterval))
.isBefore(DateTime.now());
if (!enoughTimePassed) {
// ignore: avoid_print
print(
'BG update task: Too early for another check (last check was ${appsProvider.settingsProvider.lastCompletedBGCheckTime.toIso8601String()}, interval is ${appsProvider.settingsProvider.updateInterval}).');
return;
}
logs.add('BG update task: Started (${toCheck.length}).');
// Init. vars.
List<App> updates = []; // All updates found (silent and non-silent)
@ -1545,8 +1584,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
[]; // All non-silent updates that the user will be notified about
List<MapEntry<String, int>> toRetry =
[]; // All apps that got errors while checking
var retryAfterXSeconds =
0; // How long to wait until the next attempt (if there are errors)
var retryAfterXSeconds = 0;
MultiAppMultiError?
errors; // All errors including those that will lead to a retry
MultiAppMultiError toThrow =
@ -1569,28 +1607,33 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
specificIds: toCheck.map((e) => e.key).toList(),
sp: appsProvider.settingsProvider);
} catch (e) {
// If there were errors, group them into toRetry and toThrow based on max retry count per app
if (e is Map) {
updates = e['updates'];
errors = e['errors'];
errors!.rawErrors.forEach((key, err) {
logs.add(
'BG update task $taskId: Got error on checking for $key \'${err.toString()}\'.');
'BG update task: Got error on checking for $key \'${err.toString()}\'.');
var toCheckApp = toCheck.where((element) => element.key == key).first;
if (toCheckApp.value < maxAttempts) {
toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1));
// Next task interval is based on the error with the longest retry time
var minRetryIntervalForThisApp = err is RateLimitError
int minRetryIntervalForThisApp = err is RateLimitError
? (err.remainingMinutes * 60)
: e is ClientException
? (15 * 60)
: pow(toCheckApp.value + 1, 2).toInt();
: (toCheckApp.value + 1);
if (minRetryIntervalForThisApp > maxRetryWaitSeconds) {
minRetryIntervalForThisApp = maxRetryWaitSeconds;
}
if (minRetryIntervalForThisApp > retryAfterXSeconds) {
retryAfterXSeconds = minRetryIntervalForThisApp;
}
} else {
if (err is! RateLimitError) {
toThrow.add(key, err, appName: errors?.appIdNames[key]);
}
}
});
} else {
// We don't expect to ever get here in any situation so no need to catch (but log it in case)
@ -1624,14 +1667,12 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
id: Random().nextInt(10000)));
}
}
// if there are update checks to retry, schedule a retry task
logs.add('BG update task: Done checking for updates.');
if (toRetry.isNotEmpty) {
logs.add(
'BG update task $taskId: Will retry in $retryAfterXSeconds seconds.');
AndroidAlarmManager.oneShot(
Duration(seconds: retryAfterXSeconds), taskId + 1, bgUpdateCheck,
params: {
return await bgUpdateCheck(taskId, {
'toCheck': toRetry
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
@ -1640,12 +1681,9 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
.toList(),
});
} else {
// If there are no more update checks, schedule an install task
logs.add(
'BG update task $taskId: Done. Scheduling install task to run immediately.');
AndroidAlarmManager.oneShot(
const Duration(minutes: 0), taskId + 1, bgUpdateCheck,
params: {
// If there are no more update checks, call the function in install mode
logs.add('BG update task: Done checking for updates.');
return await bgUpdateCheck(taskId, {
'toCheck': [],
'toInstall': toInstall
.map((entry) => {'key': entry.key, 'value': entry.value})
@ -1654,7 +1692,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
}
} else {
// In install mode...
// If you haven't explicitly been given updates to install (which is the case for new tasks), grab all available silent updates
// If you haven't explicitly been given updates to install, grab all available silent updates
if (toInstall.isEmpty && !networkRestricted) {
var temp = appsProvider.findExistingUpdates(installedOnly: true);
for (var i = 0; i < temp.length; i++) {
@ -1664,7 +1702,8 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
}
}
}
var didCompleteInstalling = false;
if (toInstall.isNotEmpty) {
logs.add('BG install task: Started (${toInstall.length}).');
var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
if (tempObtArr.isNotEmpty) {
// Move obtainium to the end of the list as it must always install last
@ -1672,52 +1711,25 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
toInstall = moveStrToEndMapEntryWithCount(toInstall, obt);
}
// Loop through all updates and install each
for (var i = 0; i < toInstall.length; i++) {
var appId = toInstall[i].key;
var retryCount = toInstall[i].value;
try {
logs.add(
'BG install task $taskId: Attempting to update $appId in the background.');
await appsProvider.downloadAndInstallLatestApps([appId], null,
notificationsProvider: notificationsProvider);
await Future.delayed(const Duration(
seconds:
5)); // Just in case task ending causes install fail (not clear)
if (i == (toCheck.length - 1)) {
didCompleteInstalling = true;
}
await appsProvider.downloadAndInstallLatestApps(
toInstall.map((e) => e.key).toList(), null,
notificationsProvider: notificationsProvider,
forceParallelDownloads: true);
} catch (e) {
// If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue installing shortly
logs.add(
'BG install task $taskId: Got error on updating $appId \'${e.toString()}\'.');
if (retryCount < maxAttempts) {
var remainingSeconds = retryCount;
logs.add(
'BG install task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).');
var remainingToInstall = moveStrToEndMapEntryWithCount(
toInstall.sublist(i), MapEntry(appId, retryCount + 1));
AndroidAlarmManager.oneShot(
Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck,
params: {
'toCheck': toCheck
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
'toInstall': remainingToInstall
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
if (e is MultiAppMultiError) {
e.idsByErrorString.forEach((key, value) {
notificationsProvider.notify(ErrorCheckingUpdatesNotification(
e.errorsAppsString(key, value)));
});
break;
} else {
// If the offender has reached its fail limit, notify the user and remove it from the list (task can continue)
toInstall.removeAt(i);
i--;
notificationsProvider
.notify(ErrorCheckingUpdatesNotification(e.toString()));
// We don't expect to ever get here in any situation so no need to catch (but log it in case)
logs.add('Fatal error in BG install task: ${e.toString()}');
rethrow;
}
}
}
if (didCompleteInstalling || toInstall.isEmpty) {
logs.add('BG install task $taskId: Done.');
logs.add('BG install task: Done installing updates.');
}
}
appsProvider.settingsProvider.lastCompletedBGCheckTime = DateTime.now();
}

View File

@ -1,12 +1,26 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
class Installers {
static const MethodChannel _channel = MethodChannel('installers');
class NativeFeatures {
static const MethodChannel _channel = MethodChannel('native');
static bool _systemFontLoaded = false;
static bool _callbacksApplied = false;
static int _resPermShizuku = -2; // not set
static Future waitWhile(bool Function() test,
static Future<ByteData> _readFileBytes(String path) async {
var file = File(path);
var bytes = await file.readAsBytes();
return ByteData.view(bytes.buffer);
}
static Future _handleCalls(MethodCall call) async {
if (call.method == 'resPermShizuku') {
_resPermShizuku = call.arguments['res'];
}
}
static Future _waitWhile(bool Function() test,
[Duration pollInterval = const Duration(milliseconds: 250)]) {
var completer = Completer();
check() {
@ -20,20 +34,25 @@ class Installers {
return completer.future;
}
static Future handleCalls(MethodCall call) async {
if (call.method == 'resPermShizuku') {
_resPermShizuku = call.arguments['res'];
}
static Future<String> loadSystemFont() async {
if (_systemFontLoaded) { return "ok"; }
var getFontRes = await _channel.invokeMethod('getSystemFont');
if (getFontRes[0] != '/') { return getFontRes; } // Error
var fontLoader = FontLoader('SystemFont');
fontLoader.addFont(_readFileBytes(getFontRes));
await fontLoader.load();
_systemFontLoaded = true;
return "ok";
}
static Future<int> checkPermissionShizuku() async {
if (!_callbacksApplied) {
_channel.setMethodCallHandler(handleCalls);
_channel.setMethodCallHandler(_handleCalls);
_callbacksApplied = true;
}
int res = await _channel.invokeMethod('checkPermissionShizuku');
if (res == -2) {
await waitWhile(() => _resPermShizuku == -2);
await _waitWhile(() => _resPermShizuku == -2);
res = _resPermShizuku;
_resPermShizuku = -2;
}

View File

@ -14,8 +14,9 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shared_storage/shared_storage.dart' as saf;
String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}';
String obtainiumTempId = 'imranr98_obtainium_${GitHub().hosts[0]}';
String obtainiumId = 'dev.imranr.obtainium';
String obtainiumUrl = 'https://github.com/ImranR98/Obtainium';
enum InstallMethodSettings { normal, shizuku, root }
@ -51,9 +52,18 @@ class SettingsProvider with ChangeNotifier {
notifyListeners();
}
bool get useSystemFont {
return prefs?.getBool('useSystemFont') ?? false;
}
set useSystemFont(bool useSystemFont) {
prefs?.setBool('useSystemFont', useSystemFont);
notifyListeners();
}
InstallMethodSettings get installMethod {
return InstallMethodSettings
.values[prefs?.getInt('installMethod') ?? InstallMethodSettings.normal.index];
return InstallMethodSettings.values[
prefs?.getInt('installMethod') ?? InstallMethodSettings.normal.index];
}
set installMethod(InstallMethodSettings t) {
@ -345,15 +355,15 @@ class SettingsProvider with ChangeNotifier {
notifyListeners();
}
DateTime get lastBGCheckTime {
int? temp = prefs?.getInt('lastBGCheckTime');
DateTime get lastCompletedBGCheckTime {
int? temp = prefs?.getInt('lastCompletedBGCheckTime');
return temp != null
? DateTime.fromMillisecondsSinceEpoch(temp)
: DateTime.fromMillisecondsSinceEpoch(0);
}
set lastBGCheckTime(DateTime val) {
prefs?.setInt('lastBGCheckTime', val.millisecondsSinceEpoch);
set lastCompletedBGCheckTime(DateTime val) {
prefs?.setInt('lastCompletedBGCheckTime', val.millisecondsSinceEpoch);
notifyListeners();
}
@ -446,4 +456,13 @@ class SettingsProvider with ChangeNotifier {
prefs?.setBool('parallelDownloads', val);
notifyListeners();
}
List<String> get searchDeselected {
return prefs?.getStringList('searchDeselected') ?? [];
}
set searchDeselected(List<String> list) {
prefs?.setStringList('searchDeselected', list);
notifyListeners();
}
}

View File

@ -11,6 +11,7 @@ import 'package:obtainium/app_sources/apkmirror.dart';
import 'package:obtainium/app_sources/apkpure.dart';
import 'package:obtainium/app_sources/aptoide.dart';
import 'package:obtainium/app_sources/codeberg.dart';
import 'package:obtainium/app_sources/directAPKLink.dart';
import 'package:obtainium/app_sources/fdroid.dart';
import 'package:obtainium/app_sources/fdroidrepo.dart';
import 'package:obtainium/app_sources/github.dart';
@ -19,7 +20,6 @@ import 'package:obtainium/app_sources/huaweiappgallery.dart';
import 'package:obtainium/app_sources/izzyondroid.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/app_sources/jenkins.dart';
import 'package:obtainium/app_sources/mullvad.dart';
import 'package:obtainium/app_sources/neutroncode.dart';
import 'package:obtainium/app_sources/signal.dart';
import 'package:obtainium/app_sources/sourceforge.dart';
@ -104,6 +104,21 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
additionalSettings.remove('releaseDateAsVersion');
}
}
// Convert dropdown style version detection options back into bool style
if (additionalSettings['versionDetection'] == 'standardVersionDetection') {
additionalSettings['versionDetection'] = true;
} else if (additionalSettings['versionDetection'] == 'noVersionDetection') {
additionalSettings['versionDetection'] = false;
} else if (additionalSettings['versionDetection'] == 'releaseDateAsVersion') {
additionalSettings['versionDetection'] = false;
additionalSettings['releaseDateAsVersion'] = true;
}
// Convert bool style pseudo version method to dropdown style
if (originalAdditionalSettings['supportFixedAPKURL'] == true) {
additionalSettings['defaultPseudoVersioningMethod'] = 'partialAPKHash';
} else if (originalAdditionalSettings['supportFixedAPKURL'] == false) {
additionalSettings['defaultPseudoVersioningMethod'] = 'APKLinkHash';
}
// Ensure additionalSettings are correctly typed
for (var item in formItems) {
if (additionalSettings[item.key] != null) {
@ -135,11 +150,53 @@ appJSONCompatibilityModifiers(Map<String, dynamic> json) {
if (additionalSettings['autoApkFilterByArch'] == null) {
additionalSettings['autoApkFilterByArch'] = false;
}
if (source.runtimeType == HTML().runtimeType) {
// HTML 'fixed URL' support should be disabled if it previously did not exist
if (source.runtimeType == HTML().runtimeType &&
originalAdditionalSettings['supportFixedAPKURL'] == null) {
if (originalAdditionalSettings['supportFixedAPKURL'] == null) {
additionalSettings['supportFixedAPKURL'] = false;
}
// HTML key rename
if (originalAdditionalSettings['sortByFileNamesNotLinks'] != null) {
additionalSettings['sortByLastLinkSegment'] =
originalAdditionalSettings['sortByFileNamesNotLinks'];
}
// HTML single 'intermediate link' should be converted to multi-support version
if (originalAdditionalSettings['intermediateLinkRegex'] != null &&
additionalSettings['intermediateLinkRegex']?.isNotEmpty != true) {
additionalSettings['intermediateLink'] = [
{
'customLinkFilterRegex':
originalAdditionalSettings['intermediateLinkRegex'],
'filterByLinkText':
originalAdditionalSettings['intermediateLinkByText']
}
];
}
if ((additionalSettings['intermediateLink']?.length ?? 0) > 0) {
additionalSettings['intermediateLink'] =
additionalSettings['intermediateLink'].where((e) {
return e['customLinkFilterRegex']?.isNotEmpty == true;
}).toList();
}
// Steam source apps should be converted to HTML (#1244)
var legacySteamSourceApps = SteamMobile().apks.keys;
if (legacySteamSourceApps.contains(additionalSettings['app'] ?? '')) {
json['url'] = '${json['url']}/mobile';
var replacementAdditionalSettings = getDefaultValuesFromFormItems(
HTML().combinedAppSpecificSettingFormItems);
for (var s in replacementAdditionalSettings.keys) {
if (additionalSettings.containsKey(s)) {
replacementAdditionalSettings[s] = additionalSettings[s];
}
}
replacementAdditionalSettings['customLinkFilterRegex'] =
'/${additionalSettings['app']}-(([0-9]+\\.?){1,})\\.apk';
replacementAdditionalSettings['versionExtractionRegEx'] =
replacementAdditionalSettings['customLinkFilterRegex'];
replacementAdditionalSettings['matchGroupToUse'] = '\$1';
additionalSettings = replacementAdditionalSettings;
}
}
json['additionalSettings'] = jsonEncode(additionalSettings);
// F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately
// This allows us to reverse the changes made for issue #418 (support cloudflare.f-droid)
@ -235,9 +292,10 @@ class App {
json['installedVersion'] == null
? null
: json['installedVersion'] as String,
json['latestVersion'] as String,
assumed2DlistToStringMapList(jsonDecode(json['apkUrls'])),
json['preferredApkIndex'] as int,
(json['latestVersion'] ?? tr('unknown')) as String,
assumed2DlistToStringMapList(jsonDecode(
(json['apkUrls'] ?? '[["placeholder", "placeholder"]]'))),
(json['preferredApkIndex'] ?? -1) as int,
jsonDecode(json['additionalSettings']) as Map<String, dynamic>,
json['lastUpdateCheck'] == null
? null
@ -324,8 +382,12 @@ List<MapEntry<String, String>> getApkUrlsFromUrls(List<String> urls) =>
return MapEntry(apkSegs.isNotEmpty ? apkSegs.last : segments.last, e);
}).toList();
getSourceRegex(List<String> hosts) {
return '(${hosts.join('|').replaceAll('.', '\\.')})';
}
abstract class AppSource {
String? host;
List<String> hosts = [];
bool hostChanged = false;
late String name;
bool enforceTrackOnly = false;
@ -334,28 +396,24 @@ abstract class AppSource {
bool allowSubDomains = false;
bool naiveStandardVersionDetection = false;
bool neverAutoSelect = false;
bool showReleaseDateAsVersionToggle = false;
bool versionDetectionDisallowed = false;
List<String> excludeCommonSettingKeys = [];
AppSource() {
name = runtimeType.toString();
}
overrideVersionDetectionFormDefault(String vd,
{bool disableStandard = false, bool disableRelDate = false}) {
additionalAppSpecificSourceAgnosticSettingFormItems =
additionalAppSpecificSourceAgnosticSettingFormItems.map((e) {
overrideAdditionalAppSpecificSourceAgnosticSettingSwitch(String key,
{bool disabled = true, bool defaultValue = true}) {
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly =
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
.map((e) {
return e.map((e2) {
if (e2.key == 'versionDetection') {
var item = e2 as GeneratedFormDropdown;
item.defaultValue = vd;
item.disabledOptKeys = [];
if (disableStandard) {
item.disabledOptKeys?.add('standardVersionDetection');
}
if (disableRelDate) {
item.disabledOptKeys?.add('releaseDateAsVersion');
}
item.disabledOptKeys =
item.disabledOptKeys?.where((element) => element != vd).toList();
if (e2.key == key) {
var item = e2 as GeneratedFormSwitch;
item.disabled = disabled;
item.defaultValue = defaultValue;
}
return e2;
}).toList();
@ -371,8 +429,8 @@ abstract class AppSource {
}
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) async {
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
return null;
}
@ -380,12 +438,10 @@ abstract class AppSource {
return app;
}
Future<Response> sourceRequest(String url,
{bool followRedirects = true,
Map<String, dynamic> additionalSettings =
const <String, dynamic>{}}) async {
var requestHeaders =
await getRequestHeaders(additionalSettings: additionalSettings);
Future<Response> sourceRequest(
String url, Map<String, dynamic> additionalSettings,
{bool followRedirects = true}) async {
var requestHeaders = await getRequestHeaders(additionalSettings);
if (requestHeaders != null || followRedirects == false) {
var req = Request('GET', Uri.parse(url));
req.followRedirects = followRedirects;
@ -413,7 +469,7 @@ abstract class AppSource {
// Some additional data may be needed for Apps regardless of Source
List<List<GeneratedFormItem>>
additionalAppSpecificSourceAgnosticSettingFormItems = [
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly = [
[
GeneratedFormSwitch(
'trackOnly',
@ -421,16 +477,22 @@ abstract class AppSource {
)
],
[
GeneratedFormDropdown(
'versionDetection',
[
MapEntry(
'standardVersionDetection', tr('standardVersionDetection')),
MapEntry('releaseDateAsVersion', tr('releaseDateAsVersion')),
MapEntry('noVersionDetection', tr('noVersionDetection'))
GeneratedFormTextField('versionExtractionRegEx',
label: tr('versionExtractionRegEx'),
required: false,
additionalValidators: [(value) => regExValidator(value)]),
],
label: tr('versionDetection'),
defaultValue: 'standardVersionDetection')
[
GeneratedFormTextField('matchGroupToUse',
label: tr('matchGroupToUse'), required: false, hint: '\$0')
],
[
GeneratedFormSwitch('versionDetection',
label: tr('versionDetectionExplanation'), defaultValue: true)
],
[
GeneratedFormSwitch('useVersionCodeAsOSVersion',
label: tr('useVersionCodeAsOSVersion'), defaultValue: false)
],
[
GeneratedFormTextField('apkFilterRegEx',
@ -442,6 +504,11 @@ abstract class AppSource {
}
])
],
[
GeneratedFormSwitch('invertAPKFilter',
label: '${tr('invertRegEx')} (${tr('filterAPKsByRegEx')})',
defaultValue: false)
],
[
GeneratedFormSwitch('autoApkFilterByArch',
label: tr('autoApkFilterByArch'), defaultValue: true)
@ -460,9 +527,48 @@ abstract class AppSource {
// Previous 2 variables combined into one at runtime for convenient usage
List<List<GeneratedFormItem>> get combinedAppSpecificSettingFormItems {
if (showReleaseDateAsVersionToggle == true) {
if (additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
.indexWhere((List<GeneratedFormItem> e) =>
e.indexWhere((GeneratedFormItem i) =>
i.key == 'releaseDateAsVersion') >=
0) <
0) {
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly.insert(
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
.indexWhere((List<GeneratedFormItem> e) =>
e.indexWhere((GeneratedFormItem i) =>
i.key == 'versionDetection') >=
0) +
1,
[
GeneratedFormSwitch('releaseDateAsVersion',
label:
'${tr('releaseDateAsVersion')} (${tr('pseudoVersion')})',
defaultValue: false)
]);
}
}
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly =
additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
.map((e) => e
.where((ee) => !excludeCommonSettingKeys.contains(ee.key))
.toList())
.where((e) => e.isNotEmpty)
.toList();
if (versionDetectionDisallowed) {
overrideAdditionalAppSpecificSourceAgnosticSettingSwitch(
'versionDetection',
disabled: true,
defaultValue: false);
overrideAdditionalAppSpecificSourceAgnosticSettingSwitch(
'useVersionCodeAsOSVersion',
disabled: true,
defaultValue: false);
}
return [
...additionalSourceAppSpecificSettingFormItems,
...additionalAppSpecificSourceAgnosticSettingFormItems
...additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
];
}
@ -492,8 +598,8 @@ abstract class AppSource {
return null;
}
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
return apkUrl;
}
@ -556,6 +662,71 @@ bool isTempId(App app) {
return RegExp('^[0-9]+\$').hasMatch(app.id);
}
replaceMatchGroupsInString(RegExpMatch match, String matchGroupString) {
if (RegExp('^\\d+\$').hasMatch(matchGroupString)) {
matchGroupString = '\$$matchGroupString';
}
// Regular expression to match numbers in the input string
final numberRegex = RegExp(r'\$\d+');
// Extract all numbers from the input string
final numbers = numberRegex.allMatches(matchGroupString);
if (numbers.isEmpty) {
// If no numbers found, return the original string
return null;
}
// Replace numbers with corresponding match groups
var outputString = matchGroupString;
for (final numberMatch in numbers) {
final number = numberMatch.group(0)!;
final matchGroup = match.group(int.parse(number.substring(1))) ?? '';
// Check if the number is preceded by a single backslash
final isEscaped = outputString.contains('\\$number');
// Replace the number with the corresponding match group
if (!isEscaped) {
outputString = outputString.replaceAll(number, matchGroup);
} else {
outputString = outputString.replaceAll('\\$number', number);
}
}
return outputString;
}
String? extractVersion(String? versionExtractionRegEx, String? matchGroupString,
String stringToCheck) {
if (versionExtractionRegEx?.isNotEmpty == true) {
String? version = stringToCheck;
var match = RegExp(versionExtractionRegEx!).allMatches(version);
if (match.isEmpty) {
throw NoVersionError();
}
matchGroupString = matchGroupString?.trim() ?? '';
if (matchGroupString.isEmpty) {
matchGroupString = "0";
}
version = replaceMatchGroupsInString(match.last, matchGroupString);
if (version?.isNotEmpty != true) {
throw NoVersionError();
}
return version!;
} else {
return null;
}
}
List<MapEntry<String, String>> filterApks(
List<MapEntry<String, String>> apkUrls,
String? apkFilterRegEx,
bool? invert) {
if (apkFilterRegEx?.isNotEmpty == true) {
var reg = RegExp(apkFilterRegEx!);
apkUrls = apkUrls.where((element) {
var hasMatch = reg.hasMatch(element.key);
return invert == true ? !hasMatch : hasMatch;
}).toList();
}
return apkUrls;
}
class SourceProvider {
// Add more source classes here so they are available via the service
List<AppSource> get sources => [
@ -573,14 +744,12 @@ class SourceProvider {
APKMirror(),
HuaweiAppGallery(),
Jenkins(),
// APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden)
Mullvad(),
Signal(),
VLC(),
WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
WhatsApp(),
TelegramApp(),
SteamMobile(),
NeutronCode(),
DirectAPKLink(),
HTML() // This should ALWAYS be the last option as they are tried in order
];
@ -596,22 +765,26 @@ class SourceProvider {
throw UnsupportedURLError();
}
var res = srcs.first;
res.host = Uri.parse(url).host;
res.hosts = [Uri.parse(url).host];
res.hostChanged = true;
return srcs.first;
}
AppSource? source;
for (var s in sources.where((element) => element.host != null)) {
for (var s in sources.where((element) => element.hosts.isNotEmpty)) {
try {
if (RegExp(
'://${s.allowSubDomains ? '([^\\.]+\\.)*' : '(www\\.)?'}${s.host}(/|\\z)?')
.hasMatch(url)) {
'^${s.allowSubDomains ? '([^\\.]+\\.)*' : '(www\\.)?'}(${getSourceRegex(s.hosts)})\$')
.hasMatch(Uri.parse(url).host)) {
source = s;
break;
}
} catch (e) {
// Ignore
}
}
if (source == null) {
for (var s in sources.where(
(element) => element.host == null && !element.neverAutoSelect)) {
(element) => element.hosts.isEmpty && !element.neverAutoSelect)) {
try {
s.sourceSpecificStandardizeURL(url);
source = s;
@ -655,15 +828,25 @@ class SourceProvider {
String standardUrl = source.standardizeUrl(url);
APKDetails apk =
await source.getLatestAPKDetails(standardUrl, additionalSettings);
if (additionalSettings['versionDetection'] == 'releaseDateAsVersion' &&
if (source.runtimeType !=
HTML().runtimeType && // Some sources do it separately
source.runtimeType != SourceForge().runtimeType) {
String? extractedVersion = extractVersion(
additionalSettings['versionExtractionRegEx'] as String?,
additionalSettings['matchGroupToUse'] as String?,
apk.version);
if (extractedVersion != null) {
apk.version = extractedVersion;
}
}
if (additionalSettings['releaseDateAsVersion'] == true &&
apk.releaseDate != null) {
apk.version = apk.releaseDate!.microsecondsSinceEpoch.toString();
}
if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']);
apk.apkUrls =
apk.apkUrls.where((element) => reg.hasMatch(element.key)).toList();
}
apk.apkUrls = filterApks(apk.apkUrls, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']);
if (apk.apkUrls.isEmpty && !trackOnly) {
throw NoAPKError();
}

View File

@ -1,14 +1,6 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
android_alarm_manager_plus:
dependency: "direct main"
description:
name: android_alarm_manager_plus
sha256: "84720c8ad2758aabfbeafd24a8c355d8c8dd3aa52b01eaf3bb827c7210f61a91"
url: "https://pub.dev"
source: hosted
version: "3.0.4"
android_intent_plus:
dependency: "direct main"
description:
@ -30,18 +22,18 @@ packages:
dependency: "direct main"
description:
name: android_package_manager
sha256: b873fe5856f7c442aca9751dac05d117285be9e4de08eb15d1ffb811fd1b688d
sha256: "2de859fae7226a7de1c1ff9a2308f1967599408800330501a1ce97927c051153"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
version: "0.7.1"
animations:
dependency: "direct main"
description:
name: animations
sha256: "708e4b68c23228c264b038fe7003a2f5d01ce85fc64d8cae090e86b27fcea6c5"
sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb
url: "https://pub.dev"
source: hosted
version: "2.0.10"
version: "2.0.11"
app_links:
dependency: "direct main"
description:
@ -54,10 +46,10 @@ packages:
dependency: transitive
description:
name: archive
sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b"
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
url: "https://pub.dev"
source: hosted
version: "3.4.9"
version: "3.4.10"
args:
dependency: transitive
description:
@ -74,6 +66,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
background_fetch:
dependency: "direct main"
description:
name: background_fetch
sha256: "34550cf9b383e5a1844e7d22119aa500508c7df9421fa967c9fb4430d6cb2878"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
boolean_selector:
dependency: transitive
description:
@ -190,10 +190,10 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6"
sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110"
url: "https://pub.dev"
source: hosted
version: "9.1.1"
version: "9.1.2"
device_info_plus_platform_interface:
dependency: transitive
description:
@ -258,6 +258,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flutter:
dependency: "direct main"
description: flutter
@ -299,10 +307,10 @@ packages:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: bb5cd63ff7c91d6efe452e41d0d0ae6348925c82eafd10ce170ef585ea04776e
sha256: c18f1de98fe0bb9dd5ba91e1330d4febc8b6a7de6aae3ffe475ef423723e72f3
url: "https://pub.dev"
source: hosted
version: "16.2.0"
version: "16.3.2"
flutter_local_notifications_linux:
dependency: transitive
description:
@ -328,10 +336,10 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: "35108526a233cc0755664d445f8a6b4b61e6f8fe993b3658b80b4a26827fc196"
sha256: "30088ce826b5b9cfbf9e8bece34c716c8a59fa54461dcae1e4ac01a94639e762"
url: "https://pub.dev"
source: hosted
version: "0.6.18+2"
version: "0.6.18+3"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -386,10 +394,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.2.0"
http_parser:
dependency: transitive
description:
@ -402,10 +410,10 @@ packages:
dependency: transitive
description:
name: image
sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271"
sha256: "49a0d4b0c12402853d3f227fe7c315601b238d126aa4caa5dbb2dcf99421aa4a"
url: "https://pub.dev"
source: hosted
version: "4.1.3"
version: "4.1.6"
intl:
dependency: transitive
description:
@ -442,10 +450,10 @@ packages:
dependency: transitive
description:
name: markdown
sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd
sha256: "1b134d9f8ff2da15cb298efe6cd8b7d2a78958c1b00384ebcbdf13fe340a6c90"
url: "https://pub.dev"
source: hosted
version: "7.1.1"
version: "7.2.1"
matcher:
dependency: transitive
description:
@ -474,10 +482,10 @@ packages:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.5"
nested:
dependency: transitive
description:
@ -506,26 +514,26 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.2.2"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
path_provider_linux:
dependency: transitive
description:
@ -538,10 +546,10 @@ packages:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
@ -554,50 +562,50 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: "860c6b871c94c78e202dc69546d4d8fd84bd59faeb36f8fb9888668a53ff4f78"
sha256: "45ff3fbcb99040fde55c528d5e3e6ca29171298a85436274d49c6201002087d6"
url: "https://pub.dev"
source: hosted
version: "11.1.0"
version: "11.2.0"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "2f1bec180ee2f5665c22faada971a8f024761f632e93ddc23310487df52dcfa6"
sha256: "758284a0976772f9c744d6384fc5dc4834aa61e3f7aa40492927f244767374eb"
url: "https://pub.dev"
source: hosted
version: "12.0.1"
version: "12.0.3"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: "1a816084338ada8d574b1cb48390e6e8b19305d5120fe3a37c98825bacc78306"
sha256: c6bf440f80acd2a873d3d91a699e4cc770f86e7e6b576dda98759e8b92b39830
url: "https://pub.dev"
source: hosted
version: "9.2.0"
version: "9.3.0"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "11b762a8c123dced6461933a88ea1edbbe036078c3f9f41b08886e678e7864df"
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
url: "https://pub.dev"
source: hosted
version: "0.1.0+2"
version: "0.1.1"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: d87349312f7eaf6ce0adaf668daf700ac5b06af84338bd8b8574dfbd93ffe1a1
sha256: "5c43148f2bfb6d14c5a8162c0a712afe891f2d847f35fcff29c406b37da43c3c"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
version: "4.1.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1e8640c1e39121128da6b816d236e714d2cf17fac5a105dd6acdd3403a628004"
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.2.1"
petitparser:
dependency: transitive
description:
@ -610,26 +618,26 @@ packages:
dependency: transitive
description:
name: platform
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
version: "3.1.4"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.7"
version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev"
source: hosted
version: "3.7.3"
version: "3.7.4"
provider:
dependency: "direct main"
description:
@ -642,10 +650,10 @@ packages:
dependency: "direct main"
description:
name: share_plus
sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900"
url: "https://pub.dev"
source: hosted
version: "7.2.1"
version: "7.2.2"
share_plus_platform_interface:
dependency: transitive
description:
@ -674,10 +682,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7"
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev"
source: hosted
version: "2.3.4"
version: "2.3.5"
shared_preferences_linux:
dependency: transitive
description:
@ -690,10 +698,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
shared_preferences_web:
dependency: transitive
description:
@ -743,18 +751,18 @@ packages:
dependency: "direct main"
description:
name: sqflite
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.2"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
url: "https://pub.dev"
source: hosted
version: "2.5.0+2"
version: "2.5.3"
stack_trace:
dependency: transitive
description:
@ -823,26 +831,26 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
url: "https://pub.dev"
source: hosted
version: "6.2.2"
version: "6.2.4"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def"
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
url: "https://pub.dev"
source: hosted
version: "6.2.0"
version: "6.2.2"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
url: "https://pub.dev"
source: hosted
version: "6.2.1"
version: "6.2.4"
url_launcher_linux:
dependency: transitive
description:
@ -863,18 +871,18 @@ packages:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50"
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.3.1"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9"
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.2.3"
url_launcher_windows:
dependency: transitive
description:
@ -887,10 +895,10 @@ packages:
dependency: transitive
description:
name: uuid
sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f"
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
url: "https://pub.dev"
source: hosted
version: "4.2.2"
version: "4.3.3"
vector_math:
dependency: transitive
description:
@ -911,42 +919,42 @@ packages:
dependency: "direct main"
description:
name: webview_flutter
sha256: "42393b4492e629aa3a88618530a4a00de8bb46e50e7b3993fedbfdc5352f0dbf"
sha256: d81b68e88cc353e546afb93fb38958e3717282c5ac6e5d3be4a4aef9fc3c1413
url: "https://pub.dev"
source: hosted
version: "4.4.2"
version: "4.5.0"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: b54c89fe14a6d26a2a46e24880da0441cdd2bf1f6d01a5b3e1d39558feb1de0b
sha256: "4ea3c4e1b8ed590162b15b8a61b41b1ef3ff179a314627c16ce40c086d94b8af"
url: "https://pub.dev"
source: hosted
version: "3.13.1"
version: "3.14.0"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: dbe745ee459a16b6fec296f7565a8ef430d0d681001d8ae521898b9361854943
sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
url: "https://pub.dev"
source: hosted
version: "2.9.0"
version: "2.10.0"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: eebfabfa8a115b535b52031b8b26f7a4b58ceceab378bc9db8762b0fb46f7b5d
sha256: b99ca8d8bae9c6b43d568218691aa537fb0aeae1d7d34eadf112a6aa36d26506
url: "https://pub.dev"
source: hosted
version: "3.10.0"
version: "3.11.0"
win32:
dependency: transitive
description:
name: win32
sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
version: "5.2.0"
win32_registry:
dependency: transitive
description:
@ -959,10 +967,10 @@ packages:
dependency: transitive
description:
name: xdg_directories
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.3"
version: "1.0.4"
xml:
dependency: transitive
description:
@ -980,5 +988,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.2.0 <4.0.0"
flutter: ">=3.16.0"
dart: ">=3.2.3 <4.0.0"
flutter: ">=3.16.6"

View File

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 0.14.41+235 # When changing this, update the tag in main() accordingly
version: 1.0.2+252 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=3.0.0 <4.0.0'
@ -55,9 +55,8 @@ dependencies:
git:
url: https://github.com/ImranR98/android_package_installer
ref: main
android_package_manager: ^0.6.0
android_package_manager: ^0.7.0
share_plus: ^7.0.0
android_alarm_manager_plus: ^3.0.0
sqflite: ^2.2.0+3
easy_localization: ^3.0.1
android_intent_plus: ^4.0.0
@ -68,6 +67,7 @@ dependencies:
shared_storage: ^0.8.0
crypto: ^3.0.3
app_links: ^3.5.0
background_fetch: ^1.2.1
dev_dependencies:
flutter_test: