Compare commits

...

100 Commits

Author SHA1 Message Date
59cc08f28a Merge pull request #1439 from ImranR98/dev
- Allow any source to be overridden (#1420)
- Bugfix for installing apps with ID change step (#1424)
- Bugfix: 'null' changelog for some track-only apps (#1425)
- Show latest APK name/count on app page (#1426)
- More BG update interval options (#1431)
2024-03-01 20:37:02 -05:00
afc0c3a2fa Upgrade packages, increment version 2024-03-01 20:34:15 -05:00
a827046acc More BG update interval options (#1431) 2024-03-01 20:21:46 -05:00
fd8f967036 Show latest APK name/count on app page (#1426) 2024-03-01 20:18:07 -05:00
26be524c6d Bugfix: 'null' changelog for some track-only apps (#1425) 2024-03-01 19:50:04 -05:00
8ec3360575 Merge remote-tracking branch 'origin/main' into dev 2024-03-01 19:46:38 -05:00
f66753498b Bugfix for installing apps with ID change step (#1424) 2024-03-01 19:46:30 -05:00
90e6e5a3a3 Update README.md 2024-02-29 19:17:53 -05:00
2c4713ff25 Merge pull request #1411 from akramer-zibra/fix-gitlab-request-headers
Solve issues with gitlab .apk files and cloudflare protection
2024-02-26 10:56:11 -05:00
ef3b01ac56 Use gitlab host defined in class member variable 2024-02-26 13:42:56 +01:00
1cfb258dcc Allow any source to be overridden (#1420) 2024-02-25 15:41:12 -05:00
d13464a392 Merge pull request #1415 from ngocanhtve/main
Update vi.json
2024-02-25 14:20:38 -05:00
f1dd50faee Merge pull request #1413 from DwainZwerg/patch-13
Update README.md
2024-02-25 14:20:27 -05:00
6f5315db27 Update vi.json 2024-02-20 21:00:39 +07:00
02922c3c8e Merge remote-tracking branch 'upstream/main' 2024-02-20 20:35:07 +07:00
03ef649c0f Update README.md
Add IzzyOnDroid-Badge, since it isn't out of day
2024-02-20 09:38:57 +00:00
9083e28637 Merge branch 'main' into fix-gitlab-request-headers 2024-02-19 16:16:42 +01:00
cfa4c680cf Set referer header for gitlab app source context
There occur issues if a gitlab project refers in its release data to external hosted .apk file. In some cases (e.g. Aurora Store) download is not possible because cloudflare protection gives "forbidden" error. The referer header seems to pacify this cloudflare protection. Tested with Android 14 in an AVD emulator.

Related to: #1397, #1389, #1384, #1382, #1381, #1380, #1359, #854, #785, #697
2024-02-19 16:02:49 +01:00
a5b3b9d1d0 Merge pull request #1408 from ImranR98/dev
- Improved URL handling for F-Droid third party repos (#1399)
- Fixed F-Droid repo search (#1400) + general search bugfixes
- Improved APK filter for F-Droid (#1386)
- Don't remove storage permission (#1391)
- Jump in versionCode to fix updates in x86 versions (#1370)
2024-02-18 18:20:54 -05:00
13cd7e76c1 Update Flutter + packages, increment version 2024-02-18 18:19:29 -05:00
6e7ccfba37 Merge remote-tracking branch 'origin/main' into dev 2024-02-18 18:18:40 -05:00
34fd673e25 Jump in versionCode to fix updates in x86 versions (#1370) 2024-02-18 18:16:33 -05:00
db484f7b28 Don't remove storage permission (#1391) 2024-02-18 18:13:17 -05:00
b4cf0f5d29 Improved APK filter for F-Droid (#1386) 2024-02-18 18:00:03 -05:00
4db205dd9a Fixed F-Droid repo search (#1400) + general search bugfixes 2024-02-18 17:43:24 -05:00
64f8e2a57b Improved URL handling for F-Droid third party repos (#1399) 2024-02-18 17:00:56 -05:00
eddc245ff4 Update issue templates 2024-02-15 17:13:02 -05:00
ed2f0f2c0c Merge pull request #1368 from gidano/main
Update hu.json
2024-02-03 15:47:45 -05:00
7d5a422855 Merge pull request #1371 from inson1/patch-1
Add F-droid badge to README.md
2024-02-03 15:47:28 -05:00
e420862546 Add F-droid badge to README.md 2024-02-03 17:45:32 +01:00
2cc59948a7 Update hu.json 2024-02-03 10:59:10 +01:00
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
8dd8f471a2 Update vi.json 2023-11-20 10:09:45 +07:00
657d1cd042 Update vi.json 2023-11-20 10:00:08 +07:00
sim
7987083555 Add flutter as a submodule 2023-02-02 21:56:32 +01:00
57 changed files with 903 additions and 420 deletions

1
.flutter Submodule

Submodule .flutter added at 7482962148

View File

@ -10,6 +10,7 @@ assignees: ''
**Prerequisites**
<!-- Please ensure your request is not part of an existing issue. -->
<!-- Please ensure you have checked the Obtainium Wiki. -->
<!-- Please ensure your request is an actual bug and not intended behaviour (this is frequently the case for issues involving version strings and the HTML source. -->
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->

View File

@ -14,11 +14,14 @@ assignees: ''
**Describe the feature**
<!-- A clear and concise description of what you want to happen.
For new Sources, it's preferable (not required) if you suggest how the following details can be extracted from the Source in a reliable way (like an API or through web scraping):
For new Sources, please ensure:
1. It is not already possible to use the HTML Source for your purposes.
2. It must be possible to extract the following details from the Source in a reliable way:
- The App version (or any release-specific identifier - a "pseudo-version") for the latest release
- One or more APK URL(s) for the latest release
- Above details for previous releases (optional)
If you're not sure about 1 or 2, open a discussion item instead.
Note that the Web scraper cannot deal with JavaScript-enabled content. -->
**Describe alternatives you've considered (if applicable)**

View File

@ -23,16 +23,27 @@ 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
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: |

3
.gitmodules vendored Normal file
View File

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

View File

@ -42,7 +42,13 @@ Currently supported App sources:
[<img src="https://github.com/machiav3lli/oandbackupx/blob/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png"
alt="Get it on GitHub"
height="80">](https://github.com/ImranR98/Obtainium/releases)
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
alt="Get it on IzzyOnDroid"
height="80">](https://apt.izzysoft.de/fdroid/index/apk/dev.imranr.obtainium)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/dev.imranr.obtainium.fdroid/)
[PGP Public Key](https://keyserver.ubuntu.com/pks/lookup?search=contact%40imranr.dev&fingerprint=on&op=index)
## Limitations

View File

@ -108,3 +108,16 @@ dependencies {
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

@ -70,6 +70,6 @@
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
android:maxSdkVersion="29" />\
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
</manifest>

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "Uklonite zastarjeli filter aplikacija",
"showOutdatedOnly": "Prikaži samo zastarjele aplikacije",
"filter": "Filtriranje",
"filterActive": "Filtriranje",
"filterApps": "Filtriraj aplikacije",
"appName": "Naziv aplikacije",
"author": "Autor",
@ -298,6 +297,9 @@
"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?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} i još jedna aplikacija je vjerovatno ažurirana.",
"other": "{} i još {} aplikacija su vjerovatno ažurirane."
},
"apk": {
"one": "{} APK",
"other": "{} APKs"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "Odstranit filtr Neaktuální",
"showOutdatedOnly": "Zobrazovat pouze zastaralé aplikace",
"filter": "Filtr",
"filterActive": "Filtr *",
"filterApps": "Filtrovat aplikace",
"appName": "Název aplikace",
"author": "Autor",
@ -292,12 +291,15 @@
"useLatestAssetDateAsReleaseDate": "Použít poslední nahrané dílo jako datum vydání",
"defaultPseudoVersioningMethod": "Výchozí metoda pseudoverze",
"partialAPKHash": "Částečný hash APK",
"APKLinkHash": "APK Link Hash",
"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?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} a 1 další aplikace možno aktualizovat",
"other": "{} a {} další aplikace mohou být aktualizovány."
},
"apk": {
"one": "{} APK",
"other": "{} APK"
}
}

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?",
@ -80,13 +80,12 @@
"removeOutdatedFilter": "App-Filter Nicht aktuell entfernen",
"showOutdatedOnly": "Nur nicht aktuelle Apps anzeigen",
"filter": "Filter",
"filterActive": "Filter *",
"filterApps": "Apps filtern",
"appName": "App Name",
"author": "Autor",
"upToDateApps": "Apps mit aktueller Version",
"nonInstalledApps": "Nicht installierte Apps",
"importExport": "Import Export",
"importExport": "Import/Export",
"settings": "Einstellungen",
"exportedTo": "Exportiert zu {}",
"obtainiumExport": "Obtainium-Export",
@ -107,7 +106,7 @@
"selectURL": "URL auswählen",
"selectURLs": "URLs auswählen",
"pick": "Auswählen",
"theme": "Thema",
"theme": "Theme",
"dark": "Dunkel",
"light": "Hell",
"followSystem": "System folgen",
@ -170,7 +169,7 @@
"yesMarkUpdated": "Ja, als aktualisiert markieren",
"fdroid": "offizielles F-Droid-Repo",
"appIdOrName": "App ID oder Name",
"appId": "App-ID",
"appId": "App ID",
"appWithIdOrNameNotFound": "Es wurde keine App mit dieser ID oder diesem Namen gefunden",
"reposHaveMultipleApps": "Repos können mehrere Apps enthalten",
"fdroidThirdPartyRepo": "F-Droid Drittparteienrepo",
@ -209,7 +208,7 @@
"changes": "Änderungen",
"releaseDate": "Veröffentlichungsdatum",
"importFromURLsInFile": "Importieren von URLs aus Datei (z. B. OPML)",
"versionDetectionExplanation": "Versionszeichenfolge mit der vom Betriebssystem erkannten Version abgleichen",
"versionDetectionExplanation": "Abgleich der Versionsnummer mit der vom Betriebssystem erkannten Version",
"versionDetection": "Versionserkennung",
"standardVersionDetection": "Standardversionserkennung",
"groupByCategory": "Nach Kategorie gruppieren",
@ -226,7 +225,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",
@ -238,7 +237,7 @@
"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.",
@ -268,7 +267,7 @@
"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",
@ -283,28 +282,31 @@
"parallelDownloads": "Erlaube parallele Downloads",
"installMethod": "Installationsmethode",
"normal": "Normal",
"root": "Wurzel",
"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": "Standard-Pseudoversionierungsmethode",
"partialAPKHash": "Teilweiser APK-Hash",
"defaultPseudoVersioningMethod": "Standardmäßiges Verfahren zur Pseudo-Versionierung",
"partialAPKHash": "partieller APK-Hash",
"APKLinkHash": "APK-Link-Hash",
"directAPKLink": "Direkter APK-Link",
"pseudoVersionInUse": "Eine Pseudoversion ist im Einsatz",
"installed": "Eingerichtet",
"latest": "Neueste",
"invertRegEx": "Regulären Ausdruck umkehren",
"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",
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} und 1 weitere Anwendung wurden möglicherweise aktualisiert.",
"other": "{} und {} weitere Anwendungen wurden möglicherweise aktualisiert."
},
"apk": {
"one": "{} APK",
"other": "{} APKs"
}
}

View File

@ -80,7 +80,6 @@
"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",
@ -298,6 +297,9 @@
"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?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
},
"apk": {
"one": "{} APK",
"other": "{} APKs"
}
}

View File

@ -1,7 +1,7 @@
{
"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",
@ -11,7 +11,7 @@
"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 por título de versión",
@ -22,20 +22,20 @@
"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 'Sólo seguimiento'",
"xIsTrackOnly": "{} es de 'sólo seguimiento'",
"source": "Origen",
"app": "Aplicación",
"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 descargarla o actalizarla.",
"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)",
@ -71,16 +71,15 @@
"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.",
"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",
"resetInstallStatus": "Restaurar estado de instalación",
"more": "Más",
"removeOutdatedFilter": "Elimiar filtro de aplicaciones desactualizado",
"showOutdatedOnly": "Mostrar solo aplicaciones desactualizadas",
"filter": "Filtrar",
"filterActive": "Filtrar *",
"filterApps": "Filtrar Actualizaciones",
"appName": "Nombre de la aplicación",
"author": "Autor",
@ -123,7 +122,7 @@
"neverManualOnly": "Nunca, solo manual",
"appearance": "Apariencia",
"showWebInAppView": "Mostrar vista de la web de origen",
"pinUpdates": "Fijar Actualizaciones al principio",
"pinUpdates": "Fijar actualizaciones al principio",
"updates": "Actualizaciones",
"sourceSpecific": "Fuente Específica",
"appSource": "Obtainium en GitHub",
@ -140,77 +139,77 @@
"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, permita que Obtainium instale aplicaciones",
"trackOnly": "Solo Seguimiento",
"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",
"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": "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)",
"versionDetectionExplanation": "Conciliar la cadena de versión con la versión detectada desde el sistema operativo",
"versionDetection": "Detección de Versiones",
"versionDetection": "Detección de versiones",
"standardVersionDetection": "Por versión",
"groupByCategory": "Agrupar por categoría",
"autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador (si es posible)",
@ -219,25 +218,25 @@
"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 GitLab de acceso personal\n(habilita la búsqueda y mejor detección de APKs)",
"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 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 versión superior del código APK",
"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": "Puede 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.",
"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",
"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",
@ -250,16 +249,16 @@
"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",
"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": "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": "Pruebe 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",
@ -284,10 +283,10 @@
"installMethod": "Método de instalación",
"normal": "Normal",
"root": "Raíz",
"shizukuBinderNotFound": "Shizuku no está operativo",
"useSystemFont": "Usar la fuente del sistema",
"systemFontError": "Error al cargar la fuente del sistema: {}",
"useVersionCodeAsOSVersion": "Usar el código de versión de la aplicación como versión detectada por el sistema operativo",
"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",
@ -296,8 +295,11 @@
"directAPKLink": "Enlace APK directo",
"pseudoVersionInUse": "Se está utilizando una pseudoversión",
"installed": "Instalado",
"latest": "El último",
"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?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} y 1 aplicación más podría haber sido actualizada.",
"other": "{} y {} aplicaciones más podrían haber sido actualizadas."
},
"apk": {
"one": "{} APK",
"other": "{} APKs"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "فیلتر برنامه قدیمی را حذف کنید",
"showOutdatedOnly": "فقط برنامه های قدیمی را نشان دهید",
"filter": "فیلتر",
"filterActive": "فیلتر *",
"filterApps": "فیلتر کردن برنامه ها",
"appName": "نام برنامه",
"author": "سازنده",
@ -298,6 +297,9 @@
"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": "برنامه ها حذف شوند؟"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} و 1 برنامه دیگر ممکن است به روز شده باشند.",
"other": "ممکن است {} و {} برنامه های دیگر به روز شده باشند."
},
"apk": {
"one": "{} APK",
"other": "{} APKs"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "Supprimer le filtre d'application obsolète",
"showOutdatedOnly": "Afficher uniquement les applications obsolètes",
"filter": "Filtre",
"filterActive": "Filtre *",
"filterApps": "Filtrer les applications",
"appName": "Nom de l'application",
"author": "Auteur",
@ -298,6 +297,9 @@
"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 ?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"une": "{} et 1 application supplémentaire ont peut-être été mises à jour.",
"other": "{} et {} autres applications peuvent avoir été mises à jour."
},
"apk": {
"one": "{} APK",
"other": "{} APKs"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "Távolítsa el az elavult app szűrőt",
"showOutdatedOnly": "Csak az elavult appok megjelenítése",
"filter": "Szűrő",
"filterActive": "Szűrő *",
"filterApps": "Appok szűrése",
"appName": "App név",
"author": "Szerző",
@ -287,7 +286,7 @@
"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 versionCode használata a rendszer által észlelt verzióként",
"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",
@ -298,9 +297,12 @@
"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?"
"other": "Eltávolítja az alkalmazásokat?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva",
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} és 1 további alkalmazás is frissült.",
"other": "{} és {} további alkalmazás is frissült."
},
"apk": {
"one": "{} APK",
"other": "{} APK-k"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "Rimuovi il filtro per le app non aggiornate",
"showOutdatedOnly": "Mostra solo le app non aggiornate",
"filter": "Filtri",
"filterActive": "Filtri *",
"filterApps": "Filtra app",
"appName": "Nome dell'app",
"author": "Autore",
@ -111,7 +110,7 @@
"dark": "Scuro",
"light": "Chiaro",
"followSystem": "Segui il sistema",
"useBlackTheme": "Usa il tema Nero puro",
"useBlackTheme": "Usa il tema nero puro",
"appSortBy": "App ordinate per",
"authorName": "Autore/Nome",
"nameAuthor": "Nome/Autore",
@ -283,13 +282,13 @@
"parallelDownloads": "Permetti download paralleli",
"installMethod": "Metodo d'installazione",
"normal": "Normale",
"root": "Radice",
"root": "Root",
"shizukuBinderNotFound": "Shizuku non è in esecuzione",
"useSystemFont": "Utilizza il carattere di sistema",
"systemFontError": "Errore durante il caricamento del carattere di sistema: {}",
"useVersionCodeAsOSVersion": "Utilizza il codice versione dell'app come versione rilevata dal sistema operativo",
"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": "Utilizza l'ultimo caricamento della risorsa come data di rilascio",
"useLatestAssetDateAsReleaseDate": "Usa l'ultimo caricamento della risorsa come data di rilascio",
"defaultPseudoVersioningMethod": "Metodo di pseudoversione predefinito",
"partialAPKHash": "Hash APK parziale",
"APKLinkHash": "Hash collegamento APK",
@ -298,6 +297,9 @@
"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?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} e un'altra app potrebbero essere state aggiornate.",
"other": "{} e altre {} app potrebbero essere state aggiornate."
},
"apk": {
"one": "{} APK",
"other": "{} APK"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "アップデートが存在するアプリのフィルターを解除",
"showOutdatedOnly": "アップデートが存在するアプリのみ表示する",
"filter": "フィルター",
"filterActive": "フィルター *",
"filterApps": "アプリをフィルタリングする",
"appName": "アプリ名",
"author": "作者",
@ -168,14 +167,14 @@
"lastUpdateCheckX": "最終アップデート確認: {}",
"remove": "削除",
"yesMarkUpdated": "はい、アップデート済みとしてマークします",
"fdroid": "F-ドロイドオフィシャル",
"fdroid": "F-Droid公式",
"appIdOrName": "アプリのIDまたは名前",
"appId": "アプリID",
"appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした",
"reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります",
"fdroidThirdPartyRepo": "F-Droid サードパーティリポジトリ",
"steamMobile": "スチームモバイル",
"steamChat": "スチームチャット",
"steamMobile": "Steamモバイル",
"steamChat": "Steamチャット",
"install": "インストール",
"markInstalled": "インストール済みとしてマークする",
"update": "アップデート",
@ -209,7 +208,7 @@
"changes": "変更点",
"releaseDate": "リリース日",
"importFromURLsInFile": "ファイルOPMLなど内のURLからインポート",
"versionDetectionExplanation": "バージョン文字列とOSから検出されたバージョンを一致させます",
"versionDetectionExplanation": "バージョン文字列とOSから検出されたバージョンを照合する",
"versionDetection": "バージョン検出",
"standardVersionDetection": "標準のバージョン検出",
"groupByCategory": "カテゴリ別にグループ化する",
@ -283,21 +282,24 @@
"parallelDownloads": "並行ダウンロードを許可する",
"installMethod": "インストール方法",
"normal": "通常",
"root": "",
"root": "Root",
"shizukuBinderNotFound": "Shizukuが起動していません",
"useSystemFont": "システムフォントを使用する",
"systemFontError": "システムフォントの読み込みエラー: {}",
"useVersionCodeAsOSVersion": "アプリの versionCode を OS で検出されたバージョンとして使用する",
"useVersionCodeAsOSVersion": "アプリのバージョンコードをOSで検出されたバージョンとして使用する",
"requestHeader": "リクエストヘッダー",
"useLatestAssetDateAsReleaseDate": "最新のアセットアップロードをリリース日として使用",
"useLatestAssetDateAsReleaseDate": "最新のアセットアップロードをリリース日として使用する",
"defaultPseudoVersioningMethod": "デフォルトの疑似バージョン管理方法",
"partialAPKHash": "部分的なAPKハッシュ",
"APKLinkHash": "APKリンクハッシュ",
"directAPKLink": "APK ダイレクトリンク",
"partialAPKHash": "部分的なAPKハッシュ",
"APKLinkHash": "APKリンクハッシュ",
"directAPKLink": "APKダイレクトリンク",
"pseudoVersionInUse": "疑似バージョンが使用されています",
"installed": "インストールされました",
"installed": "インストール済み",
"latest": "最新",
"invertRegEx": "正規表現を反転",
"note": "注",
"selfHostedNote": "ドロップダウン\"{}\"を使用すると、あらゆるソースのセルフホスト/カスタムインスタンスにアクセスできます。",
"badDownload": "APK を解析できませんでした(互換性がないか、部分的にダウンロードされています)。",
"removeAppQuestion": {
"one": "アプリを削除しますか?",
"other": "アプリを削除しますか?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} とさらに 1 個のアプリがアップデートされた可能性があります。",
"other": "{} とさらに {} 個のアプリがアップデートされた可能性があります。"
},
"apk": {
"one": "{}APK",
"other": "{}APK"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "Verwijder out-of-date app filter",
"showOutdatedOnly": "Toon alleen out-of-date apps",
"filter": "Filter",
"filterActive": "Filteren *",
"filterApps": "Filter apps",
"appName": "App naam",
"author": "Auteur",
@ -298,6 +297,9 @@
"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?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} en nog 1 app zijn mogelijk bijgewerkt.",
"other": "{} en {} meer apps zijn mogelijk bijgwerkt."
},
"apk": {
"one": "{} APK",
"other": "{} APK's"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "Usuń filtr nieaktualnych aplikacji",
"showOutdatedOnly": "Pokaż tylko nieaktualne aplikacje",
"filter": "FIltr",
"filterActive": "Filtruj *",
"filterApps": "Filtruj aplikacje",
"appName": "Nazwa aplikacji",
"author": "Autor",
@ -298,6 +297,9 @@
"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?",
@ -375,5 +377,9 @@
"few": "{} i {} inne apki mogły zostać zaktualizowane.",
"many": "{} i {} innych apek mogło zostać zaktualizowanych.",
"other": "{} i {} inne apki mogły zostać zaktualizowane."
},
"apk": {
"one": "{} APK",
"other": "{} APK"
}
}

View File

@ -2,7 +2,7 @@
"invalidURLForSource": "URL {} inválida",
"noReleaseFound": "Não foi possível encontrar uma versão adequada",
"noVersionFound": "Não foi possível encontrar uma versão",
"urlMatchesNoSource": "URL não corresponde a uma fonte conhecida",
"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",
@ -11,10 +11,10 @@
"unexpectedError": "Erro inesperado",
"ok": "OK",
"and": "e",
"githubPATLabel": "Token de acesso 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 títulos de versões por expressão regular",
"filterReleaseTitlesByRegEx": "Filtrar títulos de versões usando expressão regular",
"invalidRegEx": "Expressão regular inválida",
"noDescription": "Sem descrição",
"cancel": "Cancelar",
@ -22,7 +22,7 @@
"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 monitorar'",
@ -38,12 +38,12 @@
"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 monitorar)",
"searchableInBrackets": "(Pesquisável)",
"trackOnlyInBrackets": "(apenas monitorar)",
"searchableInBrackets": "(pesquisável)",
"appsString": "Aplicativos",
"noApps": "Não há aplicativos",
"noAppsForFilter": "Sem aplicativos para filtrar",
@ -70,34 +70,33 @@
"markSelectedAppsUpdated": "Marcar aplicativos selecionados como Atualizados",
"pinToTop": "Fixar no topo",
"unpinFromTop": "Desafixar do topo",
"resetInstallStatusForSelectedAppsQuestion": "Reiniciar status de instalação para aplicativos selecionados?",
"installStatusOfXWillBeResetExplanation": "O status de instalação de qualquer aplicativo selecionado 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.",
"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 Iistalação",
"resetInstallStatus": "Reiniciar status de instalação",
"more": "Mais",
"removeOutdatedFilter": "Remover filtro de aplicativos desatualizados",
"showOutdatedOnly": "Mostrar apenas aplicativos desatualizados",
"filter": "Filtro",
"filterActive": "Filtro *",
"filterApps": "Filtrar aplicativos",
"appName": "Nome do aplicativo",
"author": "Autor",
"upToDateApps": "Aplicativos tualizados",
"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",
"obtainiumImport": "Importar dados do Obtainium",
"importFromURLList": "Importar de lista de URLs",
"searchQuery": "Pesquisa",
"appURLList": "Lista de URLs de aplicativos",
"line": "Linha",
"searchX": "Pesquisa {}",
"searchX": "Pesquisar na/o {}",
"noResults": "Nenhum resultado encontrado",
"importX": "Importar {}",
"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.",
@ -110,8 +109,8 @@
"theme": "Tema",
"dark": "Escuro",
"light": "Claro",
"followSystem": "Seguir o sistema",
"useBlackTheme": "Usar tema preto completamente escuro",
"followSystem": "Padrão do sistema",
"useBlackTheme": "Usar tema preto AMOLED",
"appSortBy": "Classificar aplicativo por",
"authorName": "Autor/Nome",
"nameAuthor": "Nome/Autor",
@ -122,10 +121,10 @@
"bgUpdateCheckInterval": "Intervalo de verificação de atualizações em segundo-plano",
"neverManualOnly": "Nunca - apenas manual",
"appearance": "Aparência",
"showWebInAppView": "Mostrar página da internet em informações do aplicativo",
"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",
"sourceSpecific": "Token de acesso",
"appSource": "Fonte do aplicativo",
"noLogs": "Sem logs",
"appLogs": "Logs do aplicativo",
@ -151,7 +150,7 @@
"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 do aplicativo completa",
"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",
@ -167,15 +166,15 @@
"installedVersionX": "Versão instalada: {}",
"lastUpdateCheckX": "Última verificação de atualizações: {}",
"remove": "Remover",
"yesMarkUpdated": "Sim, marcar como Atualizado",
"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 multiplos aplicativos",
"reposHaveMultipleApps": "Repositórios podem conter múltiplos aplicativos",
"fdroidThirdPartyRepo": "Repositórios de terceiros F-Droid",
"steamMobile": "Vapor Móvel",
"steamChat": "Bate-papo Steam",
"steamMobile": "Steam para celular",
"steamChat": "Chat do Steam",
"install": "Instalar",
"markInstalled": "Marcar instalado",
"update": "Atualizar",
@ -191,7 +190,7 @@
"categories": "Categorias",
"category": "Categoria",
"noCategory": "Sem categoria",
"noCategories": "Sem categoria",
"noCategories": "Sem categorias",
"deleteCategoriesQuestion": "Deletar categorias?",
"categoryDeleteWarning": "Todos os aplicativos em categorias removidas serão descategorizados.",
"addCategory": "Adicionar categoria",
@ -200,15 +199,15 @@
"copiedToClipboard": "Copiado para a área de transferência",
"storagePermissionDenied": "Permissão de armazenamento negada",
"selectedCategorizeWarning": "Isso vai substituir qualquer configuração de categoria para os aplicativos selecionados.",
"filterAPKsByRegEx": "Filtrar APKs por expressão regular",
"filterAPKsByRegEx": "Filtrar APKs usando expressão regular",
"removeFromObtainium": "Remover do Obtainium",
"uninstallFromDevice": "Desinstalar do dispositivo",
"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",
"changes": "Alterações",
"releaseDate": "Data de lançamento",
"importFromURLsInFile": "Importar de URLs em arquivo (como OPML)",
"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",
@ -216,10 +215,10 @@
"autoApkFilterByArch": "Tente filtrar APKs por arquitetura de CPU, se possível",
"overrideSource": "Substituir fonte",
"dontShowAgain": "Não mostrar isso novamente",
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas Monitorar'",
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas monitorar'",
"dontShowAPKOriginWarnings": "Não mostrar avisos de origem da APK",
"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)",
"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": "Verificar se há atualizações ao iniciar",
@ -228,7 +227,7 @@
"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": "Reverter animações de transição de página",
"reversePageTransitions": "Animações de transição de página invertidas",
"minStarCount": "Contagem mínima de estrelas",
"addInfoBelow": "Adicionar essa informação abaixo.",
"addInfoInSettings": "Adicionar essa informação nas configurações.",
@ -254,7 +253,7 @@
"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 a exportação",
"pickExportDir": "Escolher diretório para exportação",
"autoExportOnChanges": "Auto-exportar em mudanças",
"includeSettings": "Incluir configurações",
"filterVersionsByRegEx": "Filtrar versões por expressão regular",
@ -272,18 +271,18 @@
"updatesAvailableNotifChannel": "Atualizações disponíveis",
"appsUpdatedNotifChannel": "Aplicativos atualizados",
"appsPossiblyUpdatedNotifChannel": "Tentativas de atualização de aplicativos",
"errorCheckingUpdatesNotifChannel": "Erro ao Procurar por Atualizações",
"errorCheckingUpdatesNotifChannel": "Erro ao procurar por atualizações",
"appsRemovedNotifChannel": "Aplicativos removidos",
"downloadingXNotifChannel": "Baixando {}",
"completeAppInstallationNotifChannel": "Instalação completa do aplicativo",
"checkingForUpdatesNotifChannel": "Checando por Atualizações",
"onlyCheckInstalledOrTrackOnlyApps": "Apenas checar aplicativos instalados e 'Apenas Seguir' por updates",
"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": "Raiz",
"root": "Root",
"shizukuBinderNotFound": "O Shizuku não está rodando",
"useSystemFont": "Usar fonte padrão do sistema",
"systemFontError": "Erro ao carregar a fonte do sistema: {}",
@ -298,6 +297,9 @@
"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 aplicativo?",
"other": "Remover aplicativos?"
@ -339,15 +341,19 @@
"other": "Foram limpos {n} logs (antes = {antes}, depois = {depois})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} e 1 outro aplicativo possui atualizações.",
"one": "{} e um outro aplicativo possui atualizações.",
"other": "{} e {} outros aplicativo possuem atualizações."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} e um outro aplicativo foi atualizado.",
"one": "{} e um outro aplicativo foram atualizado.",
"other": "{} e {} outros aplicativos foram atualizados."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} e 1 outro aplicativo pode ter sido atualizado.",
"one": "{} e um outro aplicativo podem ter sido atualizados.",
"other": "{} e {} outros aplicativos podem ter sido atualizados."
},
"apk": {
"one": "{} APK",
"other": "{} APKs"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "Удалить фильтр для устаревших приложений",
"showOutdatedOnly": "Показывать только устаревшие приложения",
"filter": "Фильтр",
"filterActive": "Фильтр *",
"filterApps": "Фильтровать приложения",
"appName": "Название приложения",
"author": "Автор",
@ -298,6 +297,9 @@
"installed": "Установлен",
"latest": "Последний",
"invertRegEx": "Инвертировать регулярное выражение",
"note": "Примечание",
"selfHostedNote": "Выпадающий список \"{}\" можно использовать для доступа к самостоятельно размещенным/настроенным экземплярам любого источника.",
"badDownload": "APK не удалось разобрать (несовместимая или неполная загрузка)",
"removeAppQuestion": {
"one": "Удалить приложение?",
"other": "Удалить приложения?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} и ещё 1 приложение могли быть обновлены",
"other": "{} и ещё {} приложений могли быть обновлены"
},
"apk": {
"one": "{} APK",
"other": "{} APKs"
}
}

View File

@ -1,30 +1,118 @@
// 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
// 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 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 deeplAPIKey = process.argv[2]
const templateTranslation = require(templateFile)
const neverAutoTranslate = {
steamMobile: ['*'],
steamChat: ['*'],
root: ['*'],
obtainiumExportHyphenatedLowercase: ['*'],
theme: ['de'],
appId: ['de']
}
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`)
});
otherFiles.forEach(file => {
const thisTranslation = require(file)
Object.keys(templateTranslation).forEach(k => {
if (JSON.stringify(thisTranslation[k]) == JSON.stringify(templateTranslation[k])) {
console.log(`${file} :::: ${k} :::: ${JSON.stringify(thisTranslation[k])}`)
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 = JSON.parse(fs.readFileSync(templateFile).toString())
otherFiles.forEach(file => {
const thisTranslationOriginal = JSON.parse(fs.readFileSync((file).toString()))
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 = JSON.parse(fs.readFileSync((file).toString()))
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

@ -80,7 +80,6 @@
"removeOutdatedFilter": "Ta bort Utgånga App-filtret",
"showOutdatedOnly": "Visa Endast Utgånga Appar",
"filter": "Filtrera",
"filterActive": "Filter *",
"filterApps": "Filtrera Appar",
"appName": "Appnamn",
"author": "Utvecklare",
@ -288,16 +287,19 @@
"useSystemFont": "Använd systemteckensnittet",
"systemFontError": "Fel vid laddning av systemteckensnittet: {}",
"useVersionCodeAsOSVersion": "Använd appversionskoden som OS-upptäckt version",
"requestHeader": "Request header",
"requestHeader": "Rubrik för begäran",
"useLatestAssetDateAsReleaseDate": "Använd senaste tillgångsuppladdning som releasedatum",
"defaultPseudoVersioningMethod": "Standard pseudoversionsmetod",
"partialAPKHash": "Delvis APK-hash",
"APKLinkHash": "APK Link 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?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} och 1 till app kan ha uppdaterats.",
"other": "{} och {} appar till kan ha uppdaterats."
},
"apk": {
"one": "{} APK",
"other": "{} APK:er"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "Güncel Olmayan Uygulama Filtresini Kaldır",
"showOutdatedOnly": "Yalnızca Güncel Olmayan Uygulamaları Göster",
"filter": "Filtre",
"filterActive": "Filtre *",
"filterApps": "Uygulamaları Filtrele",
"appName": "Uygulama Adı",
"author": "Yazar",
@ -298,6 +297,9 @@
"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?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} ve 1 diğer uygulama muhtemelen güncellendi.",
"other": "{} ve {} daha fazla uygulama muhtemelen güncellendi."
},
"apk": {
"one": "{} APK",
"other": "{} APK'lar"
}
}

View File

@ -25,11 +25,11 @@
"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",
@ -42,7 +42,7 @@
"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",
@ -60,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",
@ -72,7 +72,7 @@
"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",
"customLinkMessage": "Các liên kết này hoạt động trên các thiết bị có cài đặt Obtainium",
"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",
@ -80,14 +80,13 @@
"removeOutdatedFilter": "Xóa bộ lọc ứng dụng lỗi thời",
"showOutdatedOnly": "Chỉ hiển thị các ứng dụng lỗi thời",
"filter": "Lọc",
"filterActive": "Lọc *",
"filterApps": "Lọc ứng dụng",
"appName": "Tên ứng dụng",
"author": "Tác giả",
"upToDateApps": "Ứng dụng cập nhật",
"nonInstalledApps": "Ứng dụng chưa được cài đặt",
"importExport": "Nhập/Xuất",
"settings": "Cài đặt",
"settings": "Thiết đặt",
"exportedTo": "Đã xuất sang {}",
"obtainiumExport": "Xuất",
"invalidInput": "Đầu vào không hợp lệ",
@ -132,7 +131,7 @@
"close": "Đóng",
"share": "Chia sẻ",
"appNotFound": "Không tìm thấy ứng dụng",
"obtainiumExportHyphenatedLowercase": "xuất khẩu-obtainium",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Chọn một APK",
"appHasMoreThanOnePackage": "{} có nhiều gói:",
"deviceSupportsXArch": "Thiết bị của bạn hỗ trợ kiến trúc CPU {}.",
@ -157,7 +156,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",
@ -168,7 +167,7 @@
"lastUpdateCheckX": "Kiểm tra cập nhật lần cuối: {}",
"remove": "Loại bỏ",
"yesMarkUpdated": "Có, Đánh dấu là đã cập nhật",
"fdroid": "Chính thức của F-Droid",
"fdroid": "F-Droid Chính thức",
"appIdOrName": "ID hoặc tên ứng dụng",
"appId": "ID ứng dụng",
"appWithIdOrNameNotFound": "Không tìm thấy ứng dụng nào có ID hoặc tên đó",
@ -188,18 +187,18 @@
"noAPKFound": "Không tìm thấy APK",
"noVersionDetection": "Không phát hiện phiên bản",
"categorize": "Phân loại",
"categories": "Thể loại",
"category": "Thể loại",
"noCategory": "Không thể loại",
"noCategories": "Không thể loại",
"deleteCategoriesQuestion": "Xóa thể loại?",
"categoryDeleteWarning": "Tất cả ứng dụng trong thể loại đã xóa sẽ được đặt thành chưa được phân loại.",
"categories": "Danh mục",
"category": "Danh mục",
"noCategory": "Không danh mục",
"noCategories": "Không danh mục",
"deleteCategoriesQuestion": "Xóa danh mục?",
"categoryDeleteWarning": "Tất cả ứng dụng trong danh mục đã xóa sẽ được đặt thành chưa được phân loại.",
"addCategory": "Thêm thể loại",
"label": "Nhãn",
"language": "Ngôn ngữ",
"copiedToClipboard": "Sao chép vào clipboard",
"storagePermissionDenied": "Quyền lưu trữ bị từ chối",
"selectedCategorizeWarning": "Điều này sẽ thay thế mọi cài đặt thể loại hiện có cho Ứng dụng đã chọn.",
"selectedCategorizeWarning": "Điều này sẽ thay thế mọi thiết đặt danh mục hiện có cho Ứng dụng đã chọn.",
"filterAPKsByRegEx": "Lọc APK theo biểu thức chính quy",
"removeFromObtainium": "Loại khỏi Obtainium",
"uninstallFromDevice": "Gỡ cài đặt khỏi thiết bị",
@ -212,16 +211,16 @@
"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",
"groupByCategory": "Nhóm theo danh mục",
"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": "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)",
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Thiết đặt)",
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
"tryInferAppIdFromCode": "Thử suy ra ID ứng dụng từ mã nguồn",
"removeOnExternalUninstall": "Tự động xóa ứng dụng đã gỡ cài đặt bên ngoài",
@ -231,7 +230,7 @@
"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.",
"addInfoInSettings": "Thêm thông tin này vào Thiết đặ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.",
"sortByLastLinkSegment": "Chỉ sắp xếp theo đoạn cuối của liên kết",
@ -256,7 +255,7 @@
"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",
"includeSettings": "Bao gồm cài đặt ứng dụng",
"includeSettings": "Bao gồm thiết đặt",
"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",
@ -277,13 +276,13 @@
"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ập nhật các ứng dụng đã cài đặt và Chỉ-Theo dõi",
"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": "Nguồn gốc",
"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: {}",
@ -295,9 +294,12 @@
"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": "Muộn nhất",
"installed": "Đã cài đặt",
"latest": "Mới nhất",
"invertRegEx": "Đảo ngược biểu thức chính quy",
"note": "Ghi chú",
"selfHostedNote": "Trình đơn thả xuống \"{}\" có thể được dùng để tiếp cận các phiên bản tự lưu trữ/tùy chỉnh của bất kỳ nguồn nào.",
"badDownload": "Không thể phân tích cú pháp APK (tải xuống một phần hoặc không tương thích)",
"removeAppQuestion": {
"one": "Gỡ ứng dụng?",
"other": "Gỡ ứng dụng?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} và 1 ứng dụng khác có thể đã được cập nhật.",
"other": "{} và {} ứng dụng khác có thể đã được cập nhật."
},
"apk": {
"one": "{} APK",
"other": "{} APKs"
}
}

View File

@ -80,7 +80,6 @@
"removeOutdatedFilter": "删除失效的应用筛选",
"showOutdatedOnly": "只显示待更新应用",
"filter": "筛选",
"filterActive": "筛选 *",
"filterApps": "筛选应用",
"appName": "应用名称",
"author": "作者",
@ -283,7 +282,7 @@
"parallelDownloads": "启用并行下载",
"installMethod": "安装方式",
"normal": "常规",
"root": "",
"root": "root",
"shizukuBinderNotFound": "未发现兼容的 Shizuku 服务",
"useSystemFont": "使用系统字体",
"systemFontError": "加载系统字体出错:{}",
@ -298,6 +297,9 @@
"installed": "已安装",
"latest": "最新的",
"invertRegEx": "反转正则表达式",
"note": "备注",
"selfHostedNote": "{}\"下拉菜单可用于访问任何来源的自托管/自定义实例。",
"badDownload": "无法解析 APK不兼容或部分下载",
"removeAppQuestion": {
"one": "是否删除应用?",
"other": "是否删除应用?"
@ -349,5 +351,9 @@
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} 和另外 1 个应用已尝试更新。",
"other": "“{}”和另外 {} 个应用已尝试更新。"
},
"apk": {
"one": "{}APK",
"other": "{}APK"
}
}

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

@ -3,6 +3,8 @@ 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';
@ -74,16 +76,7 @@ class FDroid extends AppSource {
'https://$host/repo/$appId',
standardUrl,
name,
autoSelectHighestVersionCode:
additionalSettings['autoSelectHighestVersionCode'] == true,
trySelectingSuggestedVersionCode:
additionalSettings['trySelectingSuggestedVersionCode'] == true,
filterVersionsByRegEx:
(additionalSettings['filterVersionsByRegEx'] as String?)
?.isNotEmpty ==
true
? additionalSettings['filterVersionsByRegEx']
: null);
additionalSettings: additionalSettings);
if (!hostChanged) {
try {
var res = await sourceRequest(
@ -95,17 +88,32 @@ class FDroid extends AppSource {
details.names.author =
authorLines.first.split(': ').sublist(1).join(': ');
}
var changelogUrls = lines.where((l) => l.startsWith('Changelog: '));
var changelogUrls = lines
.where((l) => l.startsWith('Changelog: '))
.map((e) => e.split(' ').sublist(1).join(' '));
if (changelogUrls.isNotEmpty) {
details.changeLog = changelogUrls.first;
details.changeLog = (await sourceRequest(
details.changeLog!
.split(': ')
.sublist(1)
.join(': ')
.replaceFirst('/blob/', '/raw/'),
additionalSettings))
.body;
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
@ -149,12 +157,30 @@ class FDroid extends AppSource {
APKDetails getAPKUrlsFromFDroidPackagesAPIResponse(
Response res, String apkUrlPrefix, String standardUrl, String sourceName,
{bool autoSelectHighestVersionCode = false,
bool trySelectingSuggestedVersionCode = false,
String? filterVersionsByRegEx}) {
{Map<String, dynamic> additionalSettings = const {}}) {
var autoSelectHighestVersionCode =
additionalSettings['autoSelectHighestVersionCode'] == true;
var trySelectingSuggestedVersionCode =
additionalSettings['trySelectingSuggestedVersionCode'] == true;
var filterVersionsByRegEx =
(additionalSettings['filterVersionsByRegEx'] as String?)?.isNotEmpty ==
true
? additionalSettings['filterVersionsByRegEx']
: null;
var apkFilterRegEx =
(additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == true
? additionalSettings['apkFilterRegEx']
: null;
if (res.statusCode == 200) {
var response = jsonDecode(res.body);
List<dynamic> releases = response['packages'] ?? [];
if (apkFilterRegEx != null) {
releases = releases.where((rel) {
String apk = '${apkUrlPrefix}_${rel['versionCode']}.apk';
return filterApks([MapEntry(apk, apk)], apkFilterRegEx, false)
.isNotEmpty;
}).toList();
}
if (releases.isEmpty) {
throw NoReleasesError();
}

View File

@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -45,7 +46,7 @@ class FDroidRepo extends AppSource {
String sourceSpecificStandardizeURL(String url) {
var standardUri = Uri.parse(url);
var pathSegments = standardUri.pathSegments;
if (pathSegments.last == 'index.xml') {
if (pathSegments.isNotEmpty && pathSegments.last == 'index.xml') {
pathSegments.removeLast();
standardUri = standardUri.replace(path: pathSegments.join('/'));
}
@ -60,7 +61,7 @@ class FDroidRepo extends AppSource {
throw NoReleasesError();
}
url = removeQueryParamsFromUrl(standardizeUrl(url));
var res = await sourceRequest('$url/index.xml', {});
var res = await sourceRequestWithURLVariants(url, {});
if (res.statusCode == 200) {
var body = parse(res.body);
Map<String, List<String>> results = {};
@ -72,7 +73,11 @@ class FDroidRepo extends AppSource {
appId.contains(query) ||
appName.contains(query) ||
appDesc.contains(query)) {
results['$url?appId=$appId'] = [appName, appDesc];
results[
'${res.request!.url.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}?appId=$appId'] = [
appName,
appDesc
];
}
});
return results;
@ -102,6 +107,26 @@ class FDroidRepo extends AppSource {
return app;
}
Future<Response> sourceRequestWithURLVariants(
String url,
Map<String, dynamic> additionalSettings,
) async {
var res = await sourceRequest(
'$url${url.endsWith('/index.xml') ? '' : '/index.xml'}',
additionalSettings);
if (res.statusCode != 200) {
var base = url.endsWith('/index.xml')
? url.split('/').reversed.toList().sublist(1).reversed.join('/')
: url;
res = await sourceRequest('$base/repo/index.xml', additionalSettings);
if (res.statusCode != 200) {
res = await sourceRequest(
'$base/fdroid/repo/index.xml', additionalSettings);
}
}
return res;
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
@ -117,9 +142,8 @@ class FDroidRepo extends AppSource {
if (appIdOrName == null) {
throw NoReleasesError();
}
var res = await sourceRequest(
'$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}',
additionalSettings);
var res =
await sourceRequestWithURLVariants(standardUrl, additionalSettings);
if (res.statusCode == 200) {
var body = parse(res.body);
var foundApps = body.querySelectorAll('application').where((element) {
@ -168,7 +192,8 @@ class FDroidRepo extends AppSource {
latestVersionReleases = [latestVersionReleases[0]];
}
List<String> apkUrls = latestVersionReleases
.map((e) => '$standardUrl/${e.querySelector('apkname')!.innerHtml}')
.map((e) =>
'${res.request!.url.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}/${e.querySelector('apkname')!.innerHtml}')
.toList();
return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),
AppNames(authorName, appName),

View File

@ -400,7 +400,7 @@ class GitHub extends AppSource {
if (version == null) {
throw NoVersionError();
}
var changeLog = targetRelease['body'].toString();
var changeLog = (targetRelease['body'] ?? '').toString();
return APKDetails(
version,
targetRelease['apkUrls'] as List<MapEntry<String, String>>,

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:html/parser.dart';
@ -104,6 +105,21 @@ class GitLab extends AppSource {
String? changeLogPageFromStandardUrl(String standardUrl) =>
'$standardUrl/-/releases';
@override
Future<Map<String, String>?> getRequestHeaders(
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
// Change headers to pacify, e.g. cloudflare protection
// Related to: (#1397, #1389, #1384, #1382, #1381, #1380, #1359, #854, #785, #697)
var headers = <String, String>{};
headers[HttpHeaders.refererHeader] = 'https://${hosts[0]}';
if (headers.isNotEmpty) {
return headers;
} else {
return null;
}
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,

View File

@ -319,7 +319,7 @@ class HTML extends AppSource {
version ??=
additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash'
? rel.hashCode.toString()
: (await checkPartialDownloadHashDynamc(rel)).toString();
: (await checkPartialDownloadHashDynamic(rel)).toString();
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
AppNames(uri.host, tr('app')));
}

View File

@ -50,10 +50,6 @@ class IzzyOnDroid extends AppSource {
'https://android.izzysoft.de/frepo/$appId',
standardUrl,
name,
autoSelectHighestVersionCode:
additionalSettings['autoSelectHighestVersionCode'] == true,
trySelectingSuggestedVersionCode:
additionalSettings['trySelectingSuggestedVersionCode'] == true,
filterVersionsByRegEx: additionalSettings['filterVersionsByRegEx']);
additionalSettings: additionalSettings);
}
}

View File

@ -10,16 +10,23 @@ class SourceForge extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/p/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
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(match.group(0)!).host}/projects/${url.substring(Uri.parse(match.group(0)!).host.length + '/projects/'.length + 1)}';
}
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\\.)?${getSourceRegex(hosts)}/projects/[^/]+',
'^https?://(www\\.)?$sourceRegex/projects/[^/]+/files(/.+)?',
caseSensitive: false);
match = standardUrlRegExA.firstMatch(url);
if (match == null) {
@ -33,38 +40,79 @@ class SourceForge extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res =
await sourceRequest('$standardUrl/rss?path=/', additionalSettings);
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

@ -39,6 +39,15 @@ 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 =

View File

@ -184,7 +184,6 @@ class _ObtainiumState extends State<Obtainium> {
[],
0,
{
'includePrereleases': true,
'versionDetection': true,
'apkFilterRegEx': 'fdroid',
'invertAPKFilter': true

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/app_sources/html.dart';
import 'package:obtainium/components/custom_app_bar.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
@ -62,18 +61,6 @@ class AddAppPageState extends State<AddAppPage> {
var prevHost = pickedSource?.hosts.isNotEmpty == true
? pickedSource?.hosts[0]
: null;
try {
var naturalSource =
valid ? sourceProvider.getSource(userInput) : null;
if (naturalSource != null &&
naturalSource.runtimeType.toString() !=
HTML().runtimeType.toString()) {
// If input has changed to match a regular source, reset the override
pickedSourceOverride = null;
}
} catch (e) {
// ignore
}
var source = valid
? sourceProvider.getSource(userInput,
overrideSource: pickedSourceOverride)
@ -163,7 +150,7 @@ class AddAppPageState extends State<AddAppPage> {
app = await sourceProvider.getApp(
pickedSource!, userInput.trim(), additionalSettings,
trackOnlyOverride: trackOnly,
overrideSource: pickedSourceOverride,
sourceIsOverriden: pickedSourceOverride != null,
inferAppIdIfOptional: inferAppIdIfOptional);
// Only download the APK here if you need to for the package ID
if (isTempId(app) && app.additionalSettings['trackOnly'] != true) {
@ -361,8 +348,9 @@ class AddAppPageState extends State<AddAppPage> {
[
GeneratedFormDropdown(
'overrideSource',
defaultValue: HTML().runtimeType.toString(),
defaultValue: '',
[
MapEntry('', tr('none')),
...sourceProvider.sources.map(
(s) => MapEntry(s.runtimeType.toString(), s.name))
],
@ -530,7 +518,20 @@ class AddAppPageState extends State<AddAppPage> {
? TextDecoration.underline
: 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')])),
],
);
},
@ -564,11 +565,7 @@ class AddAppPageState extends State<AddAppPage> {
const SizedBox(
height: 16,
),
if (pickedSourceOverride != null ||
(pickedSource != null &&
pickedSource.runtimeType.toString() ==
HTML().runtimeType.toString()))
getHTMLSourceOverrideDropdown(),
if (pickedSource != null) getHTMLSourceOverrideDropdown(),
if (shouldShowSearchBar()) getSearchBarRow(),
if (pickedSource != null)
FutureBuilder(

View File

@ -104,6 +104,10 @@ class _AppPageState extends State<AppPage> {
if (installedVersionIsEstimate) {
infoLines = '${tr('pseudoVersionInUse')}\n$infoLines';
}
if ((app?.app.apkUrls.length ?? 0) > 0) {
infoLines =
'$infoLines\n${app?.app.apkUrls.length == 1 ? app?.app.apkUrls[0].key : plural('apk', app?.app.apkUrls.length ?? 0)}';
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
@ -205,6 +209,12 @@ class _AppPageState extends State<AppPage> {
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.displayLarge,
),
Text(tr('byX', args: [app?.app.author ?? tr('unknown')]),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(
height: 24,
),
GestureDetector(
onTap: () {
if (app?.app.url != null) {
@ -219,15 +229,12 @@ class _AppPageState extends State<AppPage> {
));
},
child: Text(
tr('byX', args: [app?.app.author ?? tr('unknown')]),
app?.app.url ?? '',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
style: Theme.of(context).textTheme.labelSmall!.copyWith(
decoration: TextDecoration.underline,
fontStyle: FontStyle.italic),
)),
const SizedBox(
height: 8,
),
Text(
app?.app.id ?? '',
textAlign: TextAlign.center,
@ -347,18 +354,6 @@ class _AppPageState extends State<AppPage> {
}
}
getResetInstallStatusButton() => TextButton(
onPressed: app?.app == null || updating
? null
: () {
app!.app.installedVersion = null;
appsProvider.saveApps([app.app]);
},
child: Text(
tr('resetInstallStatus'),
textAlign: TextAlign.center,
));
getInstallOrUpdateButton() => TextButton(
onPressed: !updating &&
(app?.app.installedVersion == null ||
@ -403,16 +398,6 @@ 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 || updating
? null
: showMarkUpdatedDialog,
tooltip: tr('markUpdated'),
icon: const Icon(Icons.done)),
if (source != null &&
source.combinedAppSpecificSettingFormItems.isNotEmpty)
IconButton(
@ -458,14 +443,30 @@ class _AppPageState extends State<AppPage> {
},
icon: const Icon(Icons.more_horiz),
tooltip: tr('more')),
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)
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: (!isVersionDetectionStandard || trackOnly) &&
app?.app.installedVersion != null &&
app?.app.installedVersion ==
app?.app.latestVersion
? getResetInstallStatusButton()
: getInstallOrUpdateButton()),
Expanded(child: getInstallOrUpdateButton()),
const SizedBox(width: 16.0),
IconButton(
onPressed: app?.downloadProgress != null || updating

View File

@ -358,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
: () {
@ -1028,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

@ -213,7 +213,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
setState(() {
importInProgress = true;
});
if (values['url'] != source.hosts[0]) {
if (source.hosts.isEmpty || values['url'] != source.hosts[0]) {
source = sourceProvider.getSource(values['url'],
overrideSource: source.runtimeType.toString());
}

View File

@ -167,7 +167,7 @@ String hashListOfLists(List<List<int>> data) {
return hash.hashCode.toString();
}
Future<String> checkPartialDownloadHashDynamc(String url,
Future<String> checkPartialDownloadHashDynamic(String url,
{int startingSize = 1024,
int lowerLimit = 128,
Map<String, String>? headers}) async {
@ -243,7 +243,9 @@ Future<File> downloadFile(
tempDownloadedFile.deleteSync(recursive: true);
throw response.reasonPhrase ?? tr('unexpectedError');
}
tempDownloadedFile.renameSync(downloadedFile.path);
if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.renameSync(downloadedFile.path);
}
} else {
client.close();
}
@ -530,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();
}
@ -706,7 +717,7 @@ class AppsProvider with ChangeNotifier {
appsToInstall =
moveStrToEnd(appsToInstall, obtainiumId, strB: obtainiumTempId);
Future<void> updateFn(String id, {bool skipInstalls = false}) async {
Future<String> updateFn(String id, {bool skipInstalls = false}) async {
try {
var downloadedArtifact =
// ignore: use_build_context_synchronously
@ -719,8 +730,8 @@ class AppsProvider with ChangeNotifier {
} else {
downloadedDir = downloadedArtifact as DownloadedXApkDir;
}
var appId = downloadedFile?.appId ?? downloadedDir!.appId;
bool willBeSilent = await canInstallSilently(apps[appId]!.app);
id = downloadedFile?.appId ?? downloadedDir!.appId;
bool willBeSilent = await canInstallSilently(apps[id]!.app);
switch (settingsProvider.installMethod) {
case InstallMethodSettings.normal:
if (!(await settingsProvider.getInstallPermission(
@ -762,18 +773,19 @@ class AppsProvider with ChangeNotifier {
}
if (willBeSilent && context == null) {
notificationsProvider?.notify(SilentUpdateAttemptNotification(
[apps[appId]!.app],
id: appId.hashCode));
[apps[id]!.app],
id: id.hashCode));
}
installedIds.add(id);
}
} finally {
apps[id]?.downloadProgress = null;
notifyListeners();
}
installedIds.add(id);
} catch (e) {
errors.add(id, e, appName: apps[id]?.name);
}
return id;
}
if (forceParallelDownloads || !settingsProvider.parallelDownloads) {
@ -781,9 +793,9 @@ class AppsProvider with ChangeNotifier {
await updateFn(id);
}
} else {
await Future.wait(
List<String> ids = await Future.wait(
appsToInstall.map((id) => updateFn(id, skipInstalls: true)));
for (var id in appsToInstall) {
for (var id in ids) {
if (!errors.appIdNames.containsKey(id)) {
await updateFn(id);
}

View File

@ -30,8 +30,22 @@ enum SortOrderSettings { ascending, descending }
const maxAPIRateLimitMinutes = 30;
const minUpdateIntervalMinutes = maxAPIRateLimitMinutes + 30;
const maxUpdateIntervalMinutes = 4320;
List<int> updateIntervals = [15, 30, 60, 120, 180, 360, 720, 1440, 4320, 0]
const maxUpdateIntervalMinutes = 43200;
List<int> updateIntervals = [
15,
30,
60,
120,
180,
360,
720,
1440,
4320,
10080,
20160,
43200,
0
]
.where((element) =>
(element >= minUpdateIntervalMinutes &&
element <= maxUpdateIntervalMinutes) ||

View File

@ -819,7 +819,7 @@ class SourceProvider {
AppSource source, String url, Map<String, dynamic> additionalSettings,
{App? currentApp,
bool trackOnlyOverride = false,
String? overrideSource,
bool sourceIsOverriden = false,
bool inferAppIdIfOptional = false}) async {
if (trackOnlyOverride || source.enforceTrackOnly) {
additionalSettings['trackOnly'] = true;
@ -829,8 +829,9 @@ class SourceProvider {
APKDetails apk =
await source.getLatestAPKDetails(standardUrl, additionalSettings);
if (source.runtimeType != HTML().runtimeType) {
// HTML does it separately
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?,
@ -886,7 +887,9 @@ class SourceProvider {
categories: currentApp?.categories ?? const [],
releaseDate: apk.releaseDate,
changeLog: apk.changeLog,
overrideSource: overrideSource ?? currentApp?.overrideSource,
overrideSource: sourceIsOverriden
? source.runtimeType.toString()
: currentApp?.overrideSource,
allowIdChange: currentApp?.allowIdChange ??
trackOnly ||
(source.appIdInferIsOptional &&
@ -910,6 +913,7 @@ class SourceProvider {
apps.add(await getApp(
source,
url,
sourceIsOverriden: sourceOverride != null,
getDefaultValuesFromFormItems(
source.combinedAppSpecificSettingFormItems)));
} catch (e) {

View File

@ -22,10 +22,10 @@ packages:
dependency: "direct main"
description:
name: android_package_manager
sha256: e52ca607b9f19f95d5dae4211ed8fa93e67093f22ac570db47489c5bca512940
sha256: "2de859fae7226a7de1c1ff9a2308f1967599408800330501a1ce97927c051153"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
version: "0.7.1"
animations:
dependency: "direct main"
description:
@ -70,10 +70,10 @@ packages:
dependency: "direct main"
description:
name: background_fetch
sha256: "34550cf9b383e5a1844e7d22119aa500508c7df9421fa967c9fb4430d6cb2878"
sha256: "1a7868d9bd165eb177f039ff8244cfa7952340b18f7caabf322b26e712b438a3"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
version: "1.2.3"
boolean_selector:
dependency: transitive
description:
@ -150,10 +150,10 @@ packages:
dependency: transitive
description:
name: cross_file
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32"
url: "https://pub.dev"
source: hosted
version: "0.3.3+8"
version: "0.3.4+1"
crypto:
dependency: "direct main"
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:
@ -206,18 +206,18 @@ packages:
dependency: "direct main"
description:
name: dynamic_color
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b
sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
url: "https://pub.dev"
source: hosted
version: "1.6.9"
version: "1.7.0"
easy_localization:
dependency: "direct main"
description:
name: easy_localization
sha256: de63e3b422adfc97f256cbb3f8cf12739b6a4993d390f3cadb3f51837afaefe5
sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.5"
easy_logger:
dependency: transitive
description:
@ -238,10 +238,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.2"
file:
dependency: transitive
description:
@ -307,10 +307,10 @@ packages:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "66cc2fe16bf4bca71d795939763ad3f1830ad85772dc3b1561613c501859826d"
sha256: c18f1de98fe0bb9dd5ba91e1330d4febc8b6a7de6aae3ffe475ef423723e72f3
url: "https://pub.dev"
source: hosted
version: "16.3.1+1"
version: "16.3.2"
flutter_local_notifications_linux:
dependency: transitive
description:
@ -336,10 +336,10 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: "30088ce826b5b9cfbf9e8bece34c716c8a59fa54461dcae1e4ac01a94639e762"
sha256: a64c5323ac83ed2b7940d2b6288d160aa1753ff271ba9d9b2a86770414aa3eab
url: "https://pub.dev"
source: hosted
version: "0.6.18+3"
version: "0.6.20+1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -394,10 +394,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.2.1"
http_parser:
dependency: transitive
description:
@ -410,10 +410,10 @@ packages:
dependency: transitive
description:
name: image
sha256: "004a2e90ce080f8627b5a04aecb4cdfac87d2c3f3b520aa291260be5a32c033d"
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
url: "https://pub.dev"
source: hosted
version: "4.1.4"
version: "4.1.7"
intl:
dependency: transitive
description:
@ -438,6 +438,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.8.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints:
dependency: transitive
description:
@ -458,34 +482,34 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.10.0"
version: "1.11.0"
mime:
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,10 +530,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_provider:
dependency: "direct main"
description:
@ -562,26 +586,26 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: "45ff3fbcb99040fde55c528d5e3e6ca29171298a85436274d49c6201002087d6"
sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44"
url: "https://pub.dev"
source: hosted
version: "11.2.0"
version: "11.3.0"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "758284a0976772f9c744d6384fc5dc4834aa61e3f7aa40492927f244767374eb"
sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474"
url: "https://pub.dev"
source: hosted
version: "12.0.3"
version: "12.0.5"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: c6bf440f80acd2a873d3d91a699e4cc770f86e7e6b576dda98759e8b92b39830
sha256: bdafc6db74253abb63907f4e357302e6bb786ab41465e8635f362ee71fd8707b
url: "https://pub.dev"
source: hosted
version: "9.3.0"
version: "9.4.0"
permission_handler_html:
dependency: transitive
description:
@ -594,10 +618,10 @@ packages:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "5c43148f2bfb6d14c5a8162c0a712afe891f2d847f35fcff29c406b37da43c3c"
sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
version: "4.2.0"
permission_handler_windows:
dependency: transitive
description:
@ -642,18 +666,18 @@ packages:
dependency: "direct main"
description:
name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "6.1.1"
version: "6.1.2"
share_plus:
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:
@ -706,10 +730,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_web
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.3.0"
shared_preferences_windows:
dependency: transitive
description:
@ -722,10 +746,10 @@ packages:
dependency: "direct main"
description:
name: shared_storage
sha256: "7c65a9d64f0f5521256be974cfd74010af12196657cec9f9fb7b03b2f11bcaf6"
sha256: cf20428d06af065311b71e09cbfbbfe431e979a3bf9180001c1952129b7c708f
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.8.1"
sky_engine:
dependency: transitive
description: flutter
@ -751,18 +775,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:
@ -831,26 +855,26 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: d25bb0ca00432a5e1ee40e69c36c85863addf7cc45e433769d61bed3fe81fd96
sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e"
url: "https://pub.dev"
source: hosted
version: "6.2.3"
version: "6.2.5"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
url: "https://pub.dev"
source: hosted
version: "6.2.2"
version: "6.3.0"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://pub.dev"
source: hosted
version: "6.2.4"
version: "6.2.5"
url_launcher_linux:
dependency: transitive
description:
@ -871,18 +895,18 @@ packages:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
version: "2.3.0"
url_launcher_windows:
dependency: transitive
description:
@ -907,46 +931,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
web:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
version: "0.5.0"
webview_flutter:
dependency: "direct main"
description:
name: webview_flutter
sha256: "71e1bfaef41016c8d5954291df5e9f8c6172f1f6ff3af01b5656456ddb11f94c"
sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932"
url: "https://pub.dev"
source: hosted
version: "4.4.4"
version: "4.7.0"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: "161af93c2abaf94ef2192bffb53a3658b2d721a3bf99b69aa1e47814ee18cc96"
sha256: "3e5f4e9d818086b0d01a66fb1ff9cc72ab0cc58c71980e3d3661c5685ea0efb0"
url: "https://pub.dev"
source: hosted
version: "3.13.2"
version: "3.15.0"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: "80b40ae4fb959957eef9fa8970b6c9accda9f49fc45c2b75154696a8e8996cfe"
sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
url: "https://pub.dev"
source: hosted
version: "2.9.1"
version: "2.10.0"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: b99ca8d8bae9c6b43d568218691aa537fb0aeae1d7d34eadf112a6aa36d26506
sha256: "9bf168bccdf179ce90450b5f37e36fe263f591c9338828d6bf09b6f8d0f57f86"
url: "https://pub.dev"
source: hosted
version: "3.11.0"
version: "3.12.0"
win32:
dependency: transitive
description:
@ -988,5 +1020,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.2.3 <4.0.0"
flutter: ">=3.16.6"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.0"

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.16.1+249 # When changing this, update the tag in main() accordingly
version: 1.0.4+2254 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=3.0.0 <4.0.0'