Compare commits

...

39 Commits

Author SHA1 Message Date
8ece0bbef9 Increment version 2022-12-18 13:13:44 -05:00
6a41283e74 Merge pull request #167 from bluefly000/main
Fix Japanese translations
2022-12-18 13:06:20 -05:00
e6d5c7db3e Add Japanese translation of untranslated sections 2022-12-18 19:30:11 +09:00
d4c016d8ee Fix Japanese translation (corrections to translations of notifications) 2022-12-18 18:58:06 +09:00
67b986de93 Merge pull request #164 from gidano/Editing
HU text length adjustment
2022-12-18 01:31:54 -05:00
aafe4bc515 Increment version 2022-12-18 01:31:40 -05:00
e524335900 Add App bugfix 2022-12-18 01:21:14 -05:00
77751fa03f HU text length adjustment 2022-12-17 20:22:22 +01:00
b4e06ffb8e Increment version 2022-12-17 13:35:20 -05:00
af511deeca Merge pull request #162 from gidano/main
Hungarian translate
2022-12-17 13:33:31 -05:00
71c6db9510 Hungarian translate 2022-12-17 09:56:52 +01:00
8fac67c9e9 Fix Japanese translation 2022-12-17 17:56:42 +09:00
c317f23741 Increment version 2022-12-17 00:22:17 -05:00
12c0dd8489 Merge pull request #161 from HRTK92/main
fix translation japanese
2022-12-17 00:21:41 -05:00
1c7385ab56 fix translation japanese 2022-12-17 13:38:57 +09:00
b46347a6e3 Increment version 2022-12-16 22:47:21 -05:00
a7104c89dc Merge pull request #160 from HRTK92/main
add Japanese translation
2022-12-16 22:46:42 -05:00
347d2c2738 unified indentation 2022-12-17 12:04:19 +09:00
cc17260e54 add japanese translation 2022-12-17 12:01:54 +09:00
1985dcec3a Fixed bug for FDroid repos with uppercase in AppID 2022-12-16 19:48:48 -05:00
d435481f0b Increment version 2022-12-16 19:37:22 -05:00
a68d49c71c Added Steam as a Source (#159) + Bugfixes 2022-12-16 19:26:07 -05:00
2b6a16637e Merge branch 'main' of github.com:ImranR98/Obtainium 2022-12-16 18:56:06 -05:00
e46e4e5dbc Merge pull request #157 from atilluF/Italian-TL
Update it.json
2022-12-16 18:54:18 -05:00
848c8eaf5e Merge pull request #156 from RanTranslations/main
assets: Update Simplified Chinese translations
2022-12-16 18:54:07 -05:00
ebc48169a1 Bugfix #158 2022-12-16 18:25:51 -05:00
54c37641d5 Update it.json 2022-12-16 08:33:08 +01:00
05ad01bf85 assets: Update Simplified Chinese translations 2022-12-16 13:02:40 +08:00
049b023e01 Adding from custom fdroid repos is easier (name based) 2022-12-15 21:39:05 -05:00
f6ca5d42e8 Initial third party F-Droid repo support
Plus various bugfixes
And version increment
2022-12-15 21:22:03 -05:00
6d0cac5894 Bugfix for switching pages while downloading #150 2022-12-15 18:57:06 -05:00
bfa661c8e0 Enabled italian translations, increment version 2022-12-15 12:15:35 -05:00
e5825fe1d3 Merge pull request #153 from atilluF/Italian-TL
Italian translation
2022-12-15 12:12:00 -05:00
9e09aba444 Merge pull request #152 from atilluF/README
Added SourceForge to README.md
2022-12-15 12:11:55 -05:00
8f5e07a5ca Added Italian translation 2022-12-15 18:01:58 +01:00
e7f3cdafe5 Added SourceForge to README.md 2022-12-15 17:55:02 +01:00
14ae43de92 Internationalized more strings
Added ZH to supported language codes
Increment version
2022-12-15 11:09:03 -05:00
a8f0d784a2 Merge pull request #151 from RanTranslations/main
assets: Add Simplified Chinese translations
2022-12-15 10:32:29 -05:00
b1fb06e90b assets: Add Simplified Chinese translations 2022-12-15 18:53:33 +08:00
25 changed files with 1279 additions and 127 deletions

View File

@ -13,7 +13,10 @@ Currently supported App sources:
- [IzzyOnDroid](https://android.izzysoft.de/)
- [Mullvad](https://mullvad.net/en/)
- [Signal](https://signal.org/)
- [SourceForge](https://sourceforge.net/)
- [APKMirror](https://apkmirror.com/) (Track-Only)
- Third Party F-Droid Repos (URLs ending with `/fdroid/repo`)
- [Steam](https://store.steampowered.com/mobile)
## Limitations
- App installs happen asynchronously and the success/failure of an install cannot be determined directly. This results in install statuses and versions sometimes being out of sync with the OS until the next launch or until the problem is manually corrected.

View File

@ -20,7 +20,7 @@
"githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)",
"githubPATHint": "PAT must be in this format: username:token",
"githubPATFormat": "username:token",
"githubPATLinkText": "'About GitHub PATs",
"githubPATLinkText": "About GitHub PATs",
"includePrereleases": "Include prereleases",
"fallbackToOlderReleases": "Fallback to older releases",
"filterReleaseTitlesByRegEx": "Filter Release Titles by Regular Expression",
@ -171,6 +171,23 @@
"trackOnly": "Track-Only",
"errorWithHttpStatusCode": "Error {}",
"versionCorrectionDisabled": "Version correction disabled (plugin doesn't seem to work)",
"unknown": "Unknown",
"none": "None",
"never": "Never",
"latestVersionX": "Latest Version: {}",
"installedVersionX": "Installed Version: {}",
"lastUpdateCheckX": "Last Update Check: {}",
"remove": "Remove",
"removeAppQuestion": "Remove App?",
"yesMarkUpdated": "Yes, Mark as Updated",
"fdroid": "F-Droid",
"appIdOrName": "App ID or Name",
"appWithIdOrNameNotFound": "No App was found with that ID or Name",
"reposHaveMultipleApps": "Repos may contain multiple Apps",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"tooManyRequestsTryAgainInMinutes": {
"one": "Too many requests (rate limited) - try again in {} minute",
"other": "Too many requests (rate limited) - try again in {} minutes"

235
assets/translations/hu.json Normal file
View File

@ -0,0 +1,235 @@
{
"invalidURLForSource": "Érvénytelen a(z) {} app URL-je",
"noReleaseFound": "Nem található megfelelő kiadás",
"noVersionFound": "Nem sikerült meghatározni a kiadás verzióját",
"urlMatchesNoSource": "Az URL nem egyezik ismert forrással",
"cantInstallOlderVersion": "Nem telepíthető egy app régebbi verziója",
"appIdMismatch": "A letöltött csomagazonosító nem egyezik a meglévő app azonosítóval",
"functionNotImplemented": "Ez az osztály nem valósította meg ezt a függvényt",
"placeholder": "Helykitöltő",
"someErrors": "Néhány hiba történt",
"unexpectedError": "Váratlan hiba",
"ok": "Oké",
"and": "és",
"startedBgUpdateTask": "Háttérfrissítés ellenőrzési feladat elindítva",
"bgUpdateIgnoreAfterIs": "Háttérfrissítés ignoreAfter a következő: {}",
"startedActualBGUpdateCheck": "Elkezdődött a tényleges BG frissítés ellenőrzése",
"bgUpdateTaskFinished": "A háttérfrissítés ellenőrzési feladat befejeződött",
"firstRun": "Ez az Obtainium első futása",
"settingUpdateCheckIntervalTo": "A frissítési intervallum beállítása a erre: {}",
"githubPATLabel": "GitHub személyes hozzáférési token (megnöveli a díjkorlátot)",
"githubPATHint": "A PAT-nak a következő formátumban kell lennie: felhasználónév:token",
"githubPATFormat": "felhasználónév:token",
"githubPATLinkText": "A GitHub PAT-okról",
"includePrereleases": "Tartalmazza az előzetes kiadásokat",
"fallbackToOlderReleases": "Visszatérés a régebbi kiadásokhoz",
"filterReleaseTitlesByRegEx": "A kiadás címeinek szűrése reguláris kifejezéssel",
"invalidRegEx": "Érvénytelen reguláris kifejezés",
"noDescription": "Nincs leírás",
"cancel": "Mégse",
"continue": "Tovább",
"requiredInBrackets": "(Kötlező)",
"dropdownNoOptsError": "HIBA: A LEDOBÁST LEGALÁBB EGY OPCIÓBAN KELL RENDELNI",
"colour": "Szín",
"githubStarredRepos": "GitHub Csillagozott Repo-k",
"uname": "Felh.név",
"wrongArgNum": "Rossz számú argumentumot adott meg",
"xIsTrackOnly": "{} csak nyomon követhető",
"source": "Forrás",
"app": "App",
"appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.' ",
"youPickedTrackOnly": "A 'Csak követés' opciót választotta.",
"trackOnlyAppDescription": "Az alkalmazás frissítéseit nyomon követi, de az Obtainium nem tudja letölteni vagy telepíteni.",
"cancelled": "Törölve",
"appAlreadyAdded": "Az app már hozzáadva",
"alreadyUpToDateQuestion": "Az app már naprakész?",
"addApp": "App hozzáadás",
"appSourceURL": "App forrás URL",
"error": "Hiba",
"add": "Hozzáadás",
"searchSomeSourcesLabel": "Keresés (csak egyes források)",
"search": "Keresés",
"additionalOptsFor": "További lehetőségek a következőhöz: {}",
"supportedSourcesBelow": "Támogatott források:",
"trackOnlyInBrackets": "(Csak nyomonkövetés)",
"searchableInBrackets": "(Kereshető)",
"appsString": "Appok",
"noApps": "Nincs App",
"noAppsForFilter": "Nincsenek appok a szűrőhöz",
"byX": "By {}",
"percentProgress": "Folyamat: {}%",
"pleaseWait": "Kis türelmet",
"updateAvailable": "Frissítés elérhető",
"estimateInBracketsShort": "(Becsült)",
"notInstalled": "Nem telepített",
"estimateInBrackets": "(Becslés)",
"selectAll": "Mindet kiválaszt",
"deselectN": "Törölje {} kijelölését",
"xWillBeRemovedButRemainInstalled": "{} el lesz távolítva az Obtainiumból, de továbbra is telepítve marad az eszközön.",
"removeSelectedAppsQuestion": "Eltávolítja a kiválasztott appokat?",
"removeSelectedApps": "Távolítsa el a kiválasztott appokat",
"updateX": "Frissítés: {}",
"installX": "Telepítés {}",
"markXTrackOnlyAsUpdated": "Jelölje meg: {}\n(Csak nyomon követhető)\nas Frissítve",
"changeX": "Változás {}",
"installUpdateApps": "Appok telepítése/frissítése",
"installUpdateSelectedApps": "Telepítse/frissítse a kiválasztott appokat",
"onlyWorksWithNonEVDApps": "Csak azoknál az alkalmazásoknál működik, amelyek telepítési állapota nem észlelhető automatikusan (nem gyakori).",
"markXSelectedAppsAsUpdated": "Megjelöl {} kiválasztott alkalmazást frissítettként?",
"no": "Nem",
"yes": "Igen",
"markSelectedAppsUpdated": "Jelölje meg a kiválasztott appokat frissítettként",
"pinToTop": "Rögzítés a felülre",
"unpinFromTop": "Eltávolít felülről",
"resetInstallStatusForSelectedAppsQuestion": "Visszaállítja a kiválasztott appok telepítési állapotát?",
"installStatusOfXWillBeResetExplanation": "A kiválasztott appok telepítési állapota visszaáll.\n\nEz akkor segíthet, ha az Obtainiumban megjelenített app verzió hibás, frissítések vagy egyéb problémák miatt.",
"shareSelectedAppURLs": "Ossza meg a kiválasztott app URL címeit",
"resetInstallStatus": "Telepítési állapot visszaállítása",
"more": "További",
"removeOutdatedFilter": "Távolítsa el az elavult alkalmazásszűrőt",
"showOutdatedOnly": "Csak az elavult alkalmazások megjelenítése",
"filter": "Szűrő",
"filterActive": "Szűrő *",
"filterApps": "Appok szűrése",
"appName": "App név",
"author": "Szerző",
"upToDateApps": "Naprakész appok",
"nonInstalledApps": "Nem telepített appok",
"importExport": "Import/Export",
"settings": "Beállítások",
"exportedTo": "Exportálva ide {}",
"obtainiumExport": "Obtainium Export",
"invalidInput": "Hibás bemenet",
"importedX": "Importálva innen {}",
"obtainiumImport": "Obtainium Import",
"importFromURLList": "Importálás URL listából",
"searchQuery": "Keresési lekérdezés",
"appURLList": "App URL lista",
"line": "Sor",
"searchX": "Keresés {}",
"noResults": "Nincs találat",
"importX": "Import {}",
"importedAppsIdDisclaimer": "Előfordulhat, hogy az importált appok helytelenül \"Nincs telepítve\" jelzéssel jelennek meg.\nA probléma megoldásához telepítse újra őket az Obtainiumon keresztül.\nEz nem érinti az alkalmazásadatokat.\n\nCsak az URL-ekre és a harmadik féltől származó importálási módszerekre vonatkozik..",
"importErrors": "Importálási hibák",
"importedXOfYApps": "{}/{} app importálva.",
"followingURLsHadErrors": "A következő URL-ek hibákat tartalmaztak:",
"okay": "Oké",
"selectURL": "Válassza ki az URL-t",
"selectURLs": "Kiválasztott URL-ek",
"pick": "Válasszon",
"theme": "Téma",
"dark": "Söét",
"light": "Világos",
"followSystem": "Rendszer szerint",
"obtainium": "Obtainium",
"materialYou": "Material You",
"appSortBy": "App rendezés...",
"authorName": "Szerző/Név",
"nameAuthor": "Név/Szerző",
"asAdded": "Mint hozzáadott",
"appSortOrder": "Appok rendezése",
"ascending": "Emelkedő",
"descending": "Csökkenő",
"bgUpdateCheckInterval": "Háttérfrissítés ellenőrzési időköz",
"neverManualOnly": "Soha csak manuális",
"appearance": "Megjelenés",
"showWebInAppView": "Forrás webhely megjelenítése az Appok nézetben",
"pinUpdates": "Frissítések kitűzése az App nézet tetejére",
"updates": "Frissítve",
"sourceSpecific": "Forrás-specifikus",
"appSource": "App forrás",
"noLogs": "Nincsenek naplók",
"appLogs": "App naplók",
"close": "Bezár",
"share": "Megoszt",
"appNotFound": "App nem található",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Válasszon egy APK-t",
"appHasMoreThanOnePackage": "{} egynél több csomaggal rendelkezik:",
"deviceSupportsXArch": "Eszköze támogatja a {} CPU architektúrát.",
"deviceSupportsFollowingArchs": "Az eszköze a következő CPU architektúrákat támogatja:",
"warning": "Figyelem",
"sourceIsXButPackageFromYPrompt": "Az alkalmazás forrása „{}”, de a kiadási csomag innen származik: „{}”. Folytatja?",
"updatesAvailable": "Frissítések érhetők el",
"updatesAvailableNotifDescription": "Értesíti a felhasználót, hogy frissítések állnak rendelkezésre egy vagy több, az Obtainium által nyomon követett alkalmazáshoz",
"noNewUpdates": "Nincsenek új frissítések.",
"xHasAnUpdate": "{} frissítést kapott.",
"appsUpdated": "Alkalmazások frissítve",
"appsUpdatedNotifDescription": "Értesíti a felhasználót, hogy egy vagy több app frissítése történt a háttérben",
"xWasUpdatedToY": "{} frissítve a következőre: {}.",
"errorCheckingUpdates": "Hiba a frissítések keresésekor",
"errorCheckingUpdatesNotifDescription": "Értesítés, amely akkor jelenik meg, ha a háttérbeli frissítések ellenőrzése sikertelen",
"appsRemoved": "Alkalmazások eltávolítva",
"appsRemovedNotifDescription": "Értesíti a felhasználót egy vagy több alkalmazás eltávolításáról a betöltésük során fellépő hibák miatt",
"xWasRemovedDueToErrorY": "A(z) {} a következő hiba miatt lett eltávolítva: {}",
"completeAppInstallation": "Teljes alkalmazástelepítés",
"obtainiumMustBeOpenToInstallApps": "Az Obtainiumnak megnyitva kell lennie az alkalmazások telepítéséhez",
"completeAppInstallationNotifDescription": "Megkéri a felhasználót, hogy térjen vissza az Obtainiumhoz, hogy befejezze az alkalmazás telepítését",
"checkingForUpdates": "Frissítések keresése",
"checkingForUpdatesNotifDescription": "Átmeneti értesítés, amely a frissítések keresésekor jelenik meg",
"pleaseAllowInstallPerm": "Kérjük, engedélyezze az Obtainiumnak az alkalmazások telepítését",
"trackOnly": "Csak követés",
"errorWithHttpStatusCode": "Hiba {}",
"versionCorrectionDisabled": "Verzió korrekció letiltva (úgy tűnik, a beépülő modul nem működik)",
"unknown": "Ismeretlen",
"none": "Egyik sem",
"never": "Soha",
"latestVersionX": "Legújabb verzió: {}",
"installedVersionX": "Telepített verzió: {}",
"lastUpdateCheckX": "Frissítés ellenőrizve: {}",
"remove": "Eltávolítás",
"removeAppQuestion": "Eltávolítja az alkalmazást?",
"yesMarkUpdated": "Igen, megjelölés frissítettként",
"fdroid": "F-Droid",
"appIdOrName": "App ID vagy név",
"appWithIdOrNameNotFound": "Nem található app ezzel az azonosítóval vagy névvel",
"reposHaveMultipleApps": "A repók több alkalmazást is tartalmazhatnak",
"fdroidThirdPartyRepo": "F-Droid Harmadik fél Repo",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"tooManyRequestsTryAgainInMinutes": {
"one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva",
"other": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "A háttérfrissítések ellenőrzése {}-t észlelt, {} perc múlva ütemezi az újrapróbálkozást",
"other": "A háttérfrissítések ellenőrzése {}-t észlelt, {} perc múlva ütemezi az újrapróbálkozást"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "A háttérfrissítés ellenőrzése {} frissítést talált szükség esetén értesíti a felhasználót",
"other": "A háttérfrissítés ellenőrzése {} frissítést talált szükség esetén értesíti a felhasználót"
},
"apps": {
"one": "{} app",
"other": "{} app"
},
"url": {
"one": "{} URL",
"other": "{} URL"
},
"minute": {
"one": "{} perc",
"other": "{} perc"
},
"hour": {
"one": "{} óra",
"other": "{} óra"
},
"day": {
"one": "{} nap",
"other": "{} nap"
},
"clearedNLogsBeforeXAfterY": {
"one": "{n} napló törölve (előtte = {előtte}, utána = {utána})",
"other": "{n} napló törölve (előtte = {előtte}, utána = {utána})"
},
"xAndNMoreUpdatesAvailable": {
"one": "A(z) {} és 1 további alkalmazás frissítéseket kapott.",
"other": "{} és további {} alkalmazás frissítéseket kapott."
},
"xAndNMoreUpdatesInstalled": {
"one": "A(z) {} és 1 további alkalmazás frissítve.",
"other": "{} és további {} alkalmazás frissítve."
}
}

235
assets/translations/it.json Normal file
View File

@ -0,0 +1,235 @@
{
"invalidURLForSource": "URL dell'App da {} non valido",
"noReleaseFound": "Impossibile trovare una release adatta",
"noVersionFound": "Impossibile determinare la versione della release",
"urlMatchesNoSource": "L'URL non corrisponde ad alcuna fonte conosciuta",
"cantInstallOlderVersion": "Impossibile installare una versione precedente di un'App",
"appIdMismatch": "L'ID del pacchetto scaricato non corrisponde all'ID dell'App esistente",
"functionNotImplemented": "Questa classe non ha implementato questa funzione",
"placeholder": "Segnaposto",
"someErrors": "Si sono verificati degli errori",
"unexpectedError": "Errore imprevisto",
"ok": "Va bene",
"and": "e",
"startedBgUpdateTask": "Avviata l'attività di controllo degli aggiornamenti in background",
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
"startedActualBGUpdateCheck": "Avviato il controllo effettivo degli aggiornamenti in background",
"bgUpdateTaskFinished": "Terminata l'attività di controllo degli aggiornamenti in background",
"firstRun": "Questo è il primo avvio di sempre di Obtainium",
"settingUpdateCheckIntervalTo": "Imposta l'intervallo di aggiornamento a {}",
"githubPATLabel": "GitHub Personal Access Token (aumenta il limite di traffico)",
"githubPATHint": "PAT deve seguire questo formato: username:token",
"githubPATFormat": "username:token",
"githubPATLinkText": "Informazioni su GitHub PAT",
"includePrereleases": "Includi prerelease",
"fallbackToOlderReleases": "Ripiega su release datate",
"filterReleaseTitlesByRegEx": "Filtra le release con le espressioni regolari",
"invalidRegEx": "Espressione regolare invalida",
"noDescription": "Descrizione assente",
"cancel": "Annulla",
"continue": "Continua",
"requiredInBrackets": "(Richiesto)",
"dropdownNoOptsError": "ERRORE: LA TENDINA DEVE AVERE ALMENO UN'OPZIONE",
"colour": "Colore",
"githubStarredRepos": "i repository stellati da GitHub",
"uname": "Username",
"wrongArgNum": "Numero di argomenti forniti errato",
"xIsTrackOnly": "{} è Solo-Monitoraggio",
"source": "Fonte",
"app": "App",
"appsFromSourceAreTrackOnly": "Le App da questa fonte sono in modalità 'Solo-Monitoraggio'.",
"youPickedTrackOnly": "Hai selezionato l'opzione 'Solo-Monitoraggio'.",
"trackOnlyAppDescription": "L'App sarà monitorata per gli aggiornamenti, ma Obtainium non sarà in grado di scaricarli o di installarli.",
"cancelled": "Annullato",
"appAlreadyAdded": "App già aggiunta",
"alreadyUpToDateQuestion": "App già aggiornata?",
"addApp": "Aggiungi App",
"appSourceURL": "URL della fonte dell'App",
"error": "Errore",
"add": "Aggiungi",
"searchSomeSourcesLabel": "Cerca (disponibile solo per alcune fonti)",
"search": "Cerca",
"additionalOptsFor": "Opzioni aggiuntive per {}",
"supportedSourcesBelow": "Fonti supportate:",
"trackOnlyInBrackets": "(Solo-Monitoraggio)",
"searchableInBrackets": "(ricercabile)",
"appsString": "App",
"noApps": "Nessuna App",
"noAppsForFilter": "Nessuna App per i filtri selezionati",
"byX": "Da {}",
"percentProgress": "Progresso: {}%",
"pleaseWait": "Attendere prego",
"updateAvailable": "Aggiornamento disponibile",
"estimateInBracketsShort": "(Prev.)",
"notInstalled": "Non installato",
"estimateInBrackets": "(Previsto)",
"selectAll": "Seleziona tutto",
"deselectN": "Deseleziona {}",
"xWillBeRemovedButRemainInstalled": "{} sarà rimosso da Obtainium ma resterà installato sul dispositivo.",
"removeSelectedAppsQuestion": "Rimuovere le App selezionate?",
"removeSelectedApps": "Rimuovi le App selezionate",
"updateX": "Aggiorna {}",
"installX": "Installa {}",
"markXTrackOnlyAsUpdated": "Contrassegna {}\n(Solo-Monitoraggio)\ncome aggiornato",
"changeX": "modifica {}",
"installUpdateApps": "Installa/Aggiorna le App",
"installUpdateSelectedApps": "Installa/Aggiornale le App selezionate",
"onlyWorksWithNonEVDApps": "Funziona solo per le App il cui stato d'installazione non può essere rilevato automaticamente (inconsueto).",
"markXSelectedAppsAsUpdated": "Contrassegnare le {} App selezionate come aggiornate?",
"no": "No",
"yes": "Sì",
"markSelectedAppsUpdated": "Contrassegna le App selezionate come aggiornate",
"pinToTop": "Fissa in alto",
"unpinFromTop": "Rimuovi dall'alto",
"resetInstallStatusForSelectedAppsQuestion": "Ripristinare lo stato d'installazione delle App selezionate?",
"installStatusOfXWillBeResetExplanation": "Lo stato d'installazione di ogni App selezionata sarà ripristinato.\n\nCiò può essere d'aiuto nel caso in cui la versione mostrata dell'App in Obtainium non è corretta a causa di un aggiornamento fallito o di altri problemi.",
"shareSelectedAppURLs": "Condividi gli URL delle App selezionate",
"resetInstallStatus": "Ripristina lo stato d'installazione",
"more": "Di più",
"removeOutdatedFilter": "Rimuovi il filtro per le App datate",
"showOutdatedOnly": "Mostra solo le App datate",
"filter": "Filtri",
"filterActive": "Filtri *",
"filterApps": "Filtra App",
"appName": "Nome dell'App",
"author": "Autore",
"upToDateApps": "App aggiornate",
"nonInstalledApps": "App non installate",
"importExport": "Importa/Esporta",
"settings": "Impostazioni",
"exportedTo": "Esportato in {}",
"obtainiumExport": "Esporta da Obtainium",
"invalidInput": "Inserimento non valido",
"importedX": "Importato {}",
"obtainiumImport": "Importa in Obtainium",
"importFromURLList": "Importa da lista di URL",
"searchQuery": "Stringa di ricerca",
"appURLList": "Lista di URL delle App",
"line": "Linea",
"searchX": "Cerca {}",
"noResults": "Nessun risultato trovato",
"importX": "Importa {}",
"importedAppsIdDisclaimer": "Le App importate potrebbero essere visualizzate erroneamente come \"Non installate\".\nPer risolvere il problema, reinstallale con Obtainium.\nQuesto non dovrebbe influire sui dati delle App.\n\nRiguarda solo l'URL e i metodi di importazione di terze parti.",
"importErrors": "Errori dell'importazione",
"importedXOfYApps": "{} App di {} importate.",
"followingURLsHadErrors": "I seguenti URL contengono errori:",
"okay": "Va bene",
"selectURL": "Seleziona l'URL",
"selectURLs": "Seleziona gli URL",
"pick": "Seleziona",
"theme": "Tema",
"dark": "Scuro",
"light": "Chiaro",
"followSystem": "Segui il sistema",
"obtainium": "Obtainium",
"materialYou": "Material You",
"appSortBy": "App ordinate per",
"authorName": "Autore/Nome",
"nameAuthor": "Nome/Autore",
"asAdded": "Data di aggiunta",
"appSortOrder": "Ordinamento",
"ascending": "Ascendente",
"descending": "Discendente",
"bgUpdateCheckInterval": "Intervallo di controllo degli aggiornamenti in background",
"neverManualOnly": "Mai - Solo manuale",
"appearance": "Aspetto",
"showWebInAppView": "Mostra la pagina web dell'App se selezionata",
"pinUpdates": "Fissa in alto gli aggiornamenti disponibili nella pagina delle App",
"updates": "Aggiornato",
"sourceSpecific": "Specifico per la fonte",
"appSource": "Sorgente dell'App",
"noLogs": "Nessun log",
"appLogs": "Log dell'App",
"close": "Chiudi",
"share": "Condividi",
"appNotFound": "App non trovata",
"obtainiumExportHyphenatedLowercase": "esportazione-obtainium",
"pickAnAPK": "Seleziona un APK",
"appHasMoreThanOnePackage": "{} offre più di un pacchetto:",
"deviceSupportsXArch": "Il tuo dispositivo supporta l'architettura {} della CPU.",
"deviceSupportsFollowingArchs": "Il tuo dispositivo supporta le seguenti architetture della CPU:",
"warning": "Attenzione",
"sourceIsXButPackageFromYPrompt": "L'origine dell'App è '{}' ma il pacchetto della release proviene da '{}'. Continuare?",
"updatesAvailable": "Aggiornamenti disponibili",
"updatesAvailableNotifDescription": "Avvisa l'utente che sono disponibili gli aggiornamenti di una o più App monitorate da Obtainium",
"noNewUpdates": "Nessun nuovo aggiornamento.",
"xHasAnUpdate": "{} è stato aggiornato.",
"appsUpdated": "App aggiornate",
"appsUpdatedNotifDescription": "Avvisa l'utente che una o più App sono state aggiornate in background",
"xWasUpdatedToY": "{} è stato aggiornato a {}.",
"errorCheckingUpdates": "Controllo degli errori per gli aggiornamenti",
"errorCheckingUpdatesNotifDescription": "Una notifica che mostra quando il controllo degli aggiornamenti in background fallisce",
"appsRemoved": "App rimosse",
"appsRemovedNotifDescription": "Avvisa l'utente che una o più App sono state rimosse a causa di errori durante il caricamento",
"xWasRemovedDueToErrorY": "{} è stata rimosso a causa di questo errore: {}",
"completeAppInstallation": "Completa l'installazione dell'App",
"obtainiumMustBeOpenToInstallApps": "Obtainium deve essere aperto per poter installare le App",
"completeAppInstallationNotifDescription": "Chiede all'utente di riaprire Obtainium per terminare l'installazione di un App",
"checkingForUpdates": "Controllo degli aggiornamenti in corso",
"checkingForUpdatesNotifDescription": "Notifica transitoria che appare durante la verifica degli aggiornamenti",
"pleaseAllowInstallPerm": "Per favore permetti a Obtainium di installare le App",
"trackOnly": "Solo-Monitoraggio",
"errorWithHttpStatusCode": "Errore {}",
"versionCorrectionDisabled": "Correzione della versione disabilitata (il plugin non pare funzionare)",
"unknown": "Sconosciuto",
"none": "Nessuno",
"never": "Mai",
"latestVersionX": "Ultima versione: {}",
"installedVersionX": "Versione installata: {}",
"lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}",
"remove": "Rimuovi",
"removeAppQuestion": "Rimuovere App?",
"yesMarkUpdated": "Sì, contrassegna come aggiornato",
"fdroid": "F-Droid",
"appIdOrName": "ID o nome dell'App",
"appWithIdOrNameNotFound": "Non è stata trovata alcuna App con quell'ID o nome",
"reposHaveMultipleApps": "I repository possono contenere più App",
"fdroidThirdPartyRepo": "Repository di terze parti di F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"tooManyRequestsTryAgainInMinutes": {
"one": "Troppe richieste (traffico limitato) - riprova tra {} minuto",
"other": "Troppe richieste (traffico limitato) - riprova tra {} minuti"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "Il controllo degli aggiornamenti in background ha incontrato un {}, ricontrollo tra {} minuto",
"other": "Il controllo degli aggiornamenti in background ha incontrato un {}, ricontrollo tra {} minuti"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "Il controllo degli aggiornamenti in background ha trovato {} aggiornamento - avviserà l'utento se necessario",
"other": "Il controllo degli aggiornamenti in background ha trovato {} aggiornamenti - avviserà l'utento se necessario"
},
"apps": {
"one": "{} App",
"other": "{} App"
},
"url": {
"one": "{} URL",
"other": "{} URL"
},
"minute": {
"one": "{} minuto",
"other": "{} minuti"
},
"hour": {
"one": "{} ora",
"other": "{} ore"
},
"day": {
"one": "{} giorno",
"other": "{} giorni"
},
"clearedNLogsBeforeXAfterY": {
"one": "Pulito {n} log (prima = {before}, dopo = {after})",
"other": "Puliti {n} log (prima = {before}, dopo = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} e un'altra App hanno aggiornamenti disponibili.",
"other": "{} e altre {} App hanno aggiornamenti disponibili."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} e un'altra App sono state aggiornate.",
"other": "{} e altre {} App sono state aggiornate."
}
}

235
assets/translations/ja.json Normal file
View File

@ -0,0 +1,235 @@
{
"invalidURLForSource": "{}は有効なソースURLではありません",
"noReleaseFound": "適切なリリースが見つかりませんでした",
"noVersionFound": "リリースバージョンを特定できませんでした",
"urlMatchesNoSource": "URLが既知のソースと一致しません",
"cantInstallOlderVersion": "旧バージョンのアプリをインストールできません",
"appIdMismatch": "ダウンロードしたパッケージのIDが既存のApp IDと一致しません",
"functionNotImplemented": "このクラスはこの機能を実装していません",
"placeholder": "プレースホルダー",
"someErrors": "いくつかのエラーが発生しました",
"unexpectedError": "予期せぬエラーが発生しました",
"ok": "OK",
"and": "と",
"startedBgUpdateTask": "バックグラウンドのアップデート確認タスクを開始",
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
"startedActualBGUpdateCheck": "実際のバックグラウンドのアップデート確認を開始",
"bgUpdateTaskFinished": "バックグラウンドのアップデート確認タスクを終了",
"firstRun": "これがObtainiumの最初の実行です",
"settingUpdateCheckIntervalTo": "更新間隔を{}に設定する",
"githubPATLabel": "GitHub パーソナルアクセストークン (レート制限の引き上げ)",
"githubPATHint": "PATは次の形式でなければなりません: ユーザー名:トークン",
"githubPATFormat": "ユーザー名:トークン",
"githubPATLinkText": "GitHub PATsについて",
"includePrereleases": "プレリリースを含む",
"fallbackToOlderReleases": "旧リリースへのフォールバック",
"filterReleaseTitlesByRegEx": "正規表現でリリースタイトルを絞り込む",
"invalidRegEx": "無効な正規表現",
"noDescription": "説明はありません",
"cancel": "キャンセル",
"continue": "続ける",
"requiredInBrackets": "(必須)",
"dropdownNoOptsError": "エラー: ドロップダウンには、少なくとも1つのoptが必要です。",
"colour": "カラー",
"githubStarredRepos": "Githubでスターしたリポジトリ",
"uname": "ユーザー名",
"wrongArgNum": "提供する引数の数が間違っています",
"xIsTrackOnly": "{} は'追跡のみ'です",
"source": "ソース",
"app": "アプリ",
"appsFromSourceAreTrackOnly": "このソースからのアプリは'追跡のみ'です'。",
"youPickedTrackOnly": "'追跡のみ'を選択しています",
"trackOnlyAppDescription": "アプリのアップデートは追跡されますが、Obtainiumはアプリのダウンロードやインストールをすることはできません。",
"cancelled": "キャンセルしました",
"appAlreadyAdded": "アプリはすでに追加されています",
"alreadyUpToDateQuestion": "アプリはすでに最新ですか?",
"addApp": "アプリの追加",
"appSourceURL": "アプリのソースURL",
"error": "エラー",
"add": "追加",
"searchSomeSourcesLabel": "検索 (一部ソースのみ)",
"search": "検索",
"additionalOptsFor": "{}の追加オプション",
"supportedSourcesBelow": "対応するソース:",
"trackOnlyInBrackets": "(追跡のみ)",
"searchableInBrackets": "(検索可能)",
"appsString": "アプリ",
"noApps": "アプリはありません",
"noAppsForFilter": "フィルターに一致するアプリはありません",
"byX": "by {}",
"percentProgress": "ダウンロード中: {}%",
"pleaseWait": "しばらくお待ちください",
"updateAvailable": "アップデートが利用可能",
"estimateInBracketsShort": "(推定)",
"notInstalled": "未インストール",
"estimateInBrackets": "(推定)",
"selectAll": "すべて選択",
"deselectN": "{}件を選択解除",
"xWillBeRemovedButRemainInstalled": "{}はObtainiumから削除されますが、デバイスにはインストールされたままです。",
"removeSelectedAppsQuestion": "選択したアプリを削除しますか?",
"removeSelectedApps": "選択したアプリを削除する",
"updateX": "{}をアップデートする",
"installX": "{}をインストールする",
"markXTrackOnlyAsUpdated": "{}\n(追跡のみ)\nをアップデート済みとしてマークする",
"changeX": "{}を変更する",
"installUpdateApps": "アプリのインストール/アップデート",
"installUpdateSelectedApps": "選択したアプリのインストール/アップデート",
"onlyWorksWithNonEVDApps": "インストール状況を自動検出できないアプリ(一般的でないもの)のみ動作します。",
"markXSelectedAppsAsUpdated": "{}個の選択したアプリをアップデート済みとしてマークしますか?",
"no": "いいえ",
"yes": "はい",
"markSelectedAppsUpdated": "選択したアプリをアップデート済みとしてマークする",
"pinToTop": "トップに固定",
"unpinFromTop": "トップから固定解除",
"resetInstallStatusForSelectedAppsQuestion": "選択したアプリのインストール状態をリセットしますか?",
"installStatusOfXWillBeResetExplanation": "選択したアプリのインストール状態がリセットされます。\n\nアップデートに失敗するなどして、Obtainiumに表示されるアプリのバージョンが正しくない場合に役立ちます。",
"shareSelectedAppURLs": "選択したアプリのURLを共有する",
"resetInstallStatus": "インストール状態をリセットする",
"more": "もっと見る",
"removeOutdatedFilter": "アップデートが存在するアプリのフィルターを解除",
"showOutdatedOnly": "アップデートが存在するアプリのみ表示する",
"filter": "フィルター",
"filterActive": "フィルター *",
"filterApps": "アプリを絞り込む",
"appName": "アプリ名",
"author": "作者",
"upToDateApps": "最新のアプリ",
"nonInstalledApps": "未インストールのアプリ",
"importExport": "インポート/エクスポート",
"settings": "設定",
"exportedTo": "{}にエクスポートしました",
"obtainiumExport": "Obtainium エクスポート",
"invalidInput": "無効な入力",
"importedX": "{}をインポートしました",
"obtainiumImport": "Obtainium インポート",
"importFromURLList": "URLリストからのインポート",
"searchQuery": "検索キーワード",
"appURLList": "アプリのURLリスト",
"line": "行",
"searchX": "{}で検索",
"noResults": "結果は見つかりませんでした",
"importX": "{}をインポートする",
"importedAppsIdDisclaimer": "インポートしたアプリが「未インストール」と表示されることがあります。\nこれを解決するには、Obtainiumから再インストールしてください。\nアプリのデータには影響しません。\n\nURLとサードパーティーのインポートメソッドにのみ影響します。",
"importErrors": "インポートエラー",
"importedXOfYApps": "{} / {} アプリをインポートしました",
"followingURLsHadErrors": "以下のURLでエラーが発生しました:",
"okay": "OK",
"selectURL": "URLを選択",
"selectURLs": "URLを選択",
"pick": "選択",
"theme": "テーマ",
"dark": "ダーク",
"light": "ライト",
"followSystem": "システムに従う",
"obtainium": "Obtainium",
"materialYou": "Material You",
"appSortBy": "アプリの並び方",
"authorName": "作者/アプリ名",
"nameAuthor": "アプリ名/作者",
"asAdded": "追加順",
"appSortOrder": "並び順",
"ascending": "昇順",
"descending": "降順",
"bgUpdateCheckInterval": "バックグラウンド更新の確認間隔",
"neverManualOnly": "手動",
"appearance": "外観",
"showWebInAppView": "アプリビューにソースウェブページを表示する",
"pinUpdates": "アップデートがあるアプリをトップに固定する",
"updates": "更新",
"sourceSpecific": "Github アクセストークン",
"appSource": "アプリのソース",
"noLogs": "ログはありません",
"appLogs": "アプリのログ",
"close": "閉じる",
"share": "共有",
"appNotFound": "アプリが見つかりません",
"obtainiumExportHyphenatedLowercase": "obtainium-エクスポート",
"pickAnAPK": "APKを選ぶ",
"appHasMoreThanOnePackage": "{}は複数のパッケージが存在します: ",
"deviceSupportsXArch": "お使いのデバイスは{} CPUアーキテクチャに対応しています。",
"deviceSupportsFollowingArchs": "お使いのデバイスは、以下のCPUアーキテクチャをサポートしています:",
"warning": "警告",
"sourceIsXButPackageFromYPrompt": "アプリのソースは'{}'ですが、リリースパッケージは'{}'から来ています。続行しますか?",
"updatesAvailable": "アップデートが利用可能",
"updatesAvailableNotifDescription": "Obtainiumが追跡している1つまたは複数のアプリのアップデートが利用可能であることをユーザーに通知する",
"noNewUpdates": "新しいアップデートはありません。",
"xHasAnUpdate": "{}のアップデートが利用可能です",
"appsUpdated": "アプリをアップデートしました",
"appsUpdatedNotifDescription": "1つまたは複数のAppのアップデートがバックグラウンドで適用されたことをユーザーに通知する",
"xWasUpdatedToY": "{}が{}にアップデートされました。",
"errorCheckingUpdates": "アップデート確認中のエラー",
"errorCheckingUpdatesNotifDescription": "バックグラウンドでのアップデート確認に失敗した際に表示される通知",
"appsRemoved": "削除されたアプリ",
"appsRemovedNotifDescription": "アプリの読み込み中にエラーが発生したため、1つまたは複数のアプリが削除されたことをユーザーに通知する",
"xWasRemovedDueToErrorY": "このエラーのため、{}は削除されました: {}",
"completeAppInstallation": "アプリのインストールを完了する",
"obtainiumMustBeOpenToInstallApps": "アプリをインストールするにはObtainiumを開いている必要があります。",
"completeAppInstallationNotifDescription": "アプリのインストールを完了するために、Obtainiumに戻る必要があります。",
"checkingForUpdates": "アップデートを確認中",
"checkingForUpdatesNotifDescription": "アップデートを確認する際に表示される一時的な通知",
"pleaseAllowInstallPerm": "Obtainiumによるアプリのインストールを許可してください。",
"trackOnly": "追跡のみ",
"errorWithHttpStatusCode": "エラー {}",
"versionCorrectionDisabled": "バージョン補正無効 (プラグインが動作していません)",
"unknown": "不明",
"none": "なし",
"never": "Never",
"latestVersionX": "最新のバージョン: {}",
"installedVersionX": "インストールされたバージョン: {}",
"lastUpdateCheckX": "最終アップデート確認: {}",
"remove": "削除",
"removeAppQuestion": "アプリを削除しますか?",
"yesMarkUpdated": "はい、アップデート済みとしてマークします",
"fdroid": "F-Droid",
"appIdOrName": "アプリIDまたは名前",
"appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした",
"reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"tooManyRequestsTryAgainInMinutes": {
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "バックグラウンドのアップデート確認で {} の問題が発生, {} 分後に再試行します",
"other": "バックグラウンドのアップデート確認で {} の問題が発生, {} 分後に再試行します"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "バックグラウンドのアップデート確認で {} 個のアップデートを発見 - 必要に応じてユーザーに通知します",
"other": "バックグラウンドのアップデート確認で {} 個のアップデートを発見 - 必要に応じてユーザーに通知します"
},
"apps": {
"one": "{}個のアプリ",
"other": "{}個のアプリ"
},
"url": {
"one": "{}個のURL",
"other": "{}個のURL"
},
"minute": {
"one": "{}分",
"other": "{}分"
},
"hour": {
"one": "{}時間",
"other": "{}時間"
},
"day": {
"one": "{}日",
"other": "{}日"
},
"clearedNLogsBeforeXAfterY": {
"one": "{n}個のログをクリアしました (前 = {before}, 後 = {after})",
"other": "{n}個のログをクリアしました (前 = {before}, 後 = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{}とさらに{}個のアプリのアップデートが利用可能です。",
"other": "{}とさらに{}個のアプリのアップデートが利用可能です。"
},
"xAndNMoreUpdatesInstalled": {
"one": "{}とさらに{}個のアプリがアップデートされました。",
"other": "{}とさらに{}個のアプリがアップデートされました。"
}
}

235
assets/translations/zh.json Normal file
View File

@ -0,0 +1,235 @@
{
"invalidURLForSource": "不是一个有效的 {} URL",
"noReleaseFound": "找不到合适的更新",
"noVersionFound": "无法确定更新版本",
"urlMatchesNoSource": "URL 与已知来源不符",
"cantInstallOlderVersion": "无法安装旧版应用程序",
"appIdMismatch": "下载的软件包名与现有的应用程序包名不一致",
"functionNotImplemented": "该类没有实现此功能",
"placeholder": "占位符",
"someErrors": "出现了一些错误",
"unexpectedError": "意外错误",
"ok": "好的",
"and": "和",
"startedBgUpdateTask": "开始后台检查更新任务",
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
"startedActualBGUpdateCheck": "后台检查更新已开始",
"bgUpdateTaskFinished": "后台检查更新已完成",
"firstRun": "这是你第一次运行 Obtainium",
"settingUpdateCheckIntervalTo": "设置检查更新间隔为 {}",
"githubPATLabel": "GitHub 个人访问令牌 (提高 API 限制)",
"githubPATHint": "个人访问令牌必须为: username:token 形式",
"githubPATFormat": "username:token",
"githubPATLinkText": "关于 GitHub 个人访问令牌",
"includePrereleases": "包含预发布版",
"fallbackToOlderReleases": "回落到旧版",
"filterReleaseTitlesByRegEx": "通过正则表达式过滤发布标题",
"invalidRegEx": "无效的正则表达式",
"noDescription": "无描述",
"cancel": "取消",
"continue": "继续",
"requiredInBrackets": "(必须)",
"dropdownNoOptsError": "错误:下拉菜单必须至少有一个选项",
"colour": "颜色",
"githubStarredRepos": "GitHub 已星标仓库",
"uname": "用户名",
"wrongArgNum": "提供了错误的参数数量",
"xIsTrackOnly": "{} 仅追踪",
"source": "源码",
"app": "应用程序",
"appsFromSourceAreTrackOnly": "来自此来源的应用为仅追踪",
"youPickedTrackOnly": "你已选择仅追踪选项",
"trackOnlyAppDescription": "该应用程序将被跟踪更新,但 Obtainium 无法下载或安装它",
"cancelled": "已取消",
"appAlreadyAdded": "此应用程序已被添加",
"alreadyUpToDateQuestion": "应用已是最新?",
"addApp": "添加应用",
"appSourceURL": "应用来源 URL",
"error": "错误",
"add": "添加",
"searchSomeSourcesLabel": "搜索 (仅部分来源)",
"search": "搜索",
"additionalOptsFor": "{} 的更多选项",
"supportedSourcesBelow": "受支持的来源:",
"trackOnlyInBrackets": "(仅追踪)",
"searchableInBrackets": "(可被搜索)",
"appsString": "应用程序",
"noApps": "无应用程序",
"noAppsForFilter": "没有应用可被过滤",
"byX": "来自 {}",
"percentProgress": "进度: {}%",
"pleaseWait": "请等待...",
"updateAvailable": "更新可用",
"estimateInBracketsShort": "(预计.)",
"notInstalled": "未安装",
"estimateInBrackets": "(预计)",
"selectAll": "全选",
"deselectN": "取消选择 {}",
"xWillBeRemovedButRemainInstalled": "{} 将被从 Obtainium 中删除,但仍安装在设备上。",
"removeSelectedAppsQuestion": "删除已选择的应用程序吗?",
"removeSelectedApps": "删除已选择的应用程序",
"updateX": "更新 {}",
"installX": "安装 {}",
"markXTrackOnlyAsUpdated": "将仅追踪编辑为已更新",
"changeX": "更改 {}",
"installUpdateApps": "安装/更新应用程序",
"installUpdateSelectedApps": "安装/更新已选择的应用程序",
"onlyAppliesToInstalledAndOutdatedApps": "'只适用于已安装但已过时的应用程序",
"markXSelectedAppsAsUpdated": "将已选择的 {} 个应用程序标记为已更新?",
"no": "不要",
"yes": "好的",
"markSelectedAppsUpdated": "标记已选择的应用程序为已更新",
"pinToTop": "置顶",
"unpinFromTop": "取消置顶",
"resetInstallStatusForSelectedAppsQuestion": "为已选择的应用程序重置安装状态吗?",
"installStatusOfXWillBeResetExplanation": "当 Obtainium 中显示的应用程序版本由于更新失败或其他问题而不正确时,这将有助于重置任何选定应用程序的安装状态。",
"shareSelectedAppURLs": "分享已选择的应用程序 URL",
"resetInstallStatus": "重置安装状态",
"more": "更多",
"removeOutdatedFilter": "删除过时的应用程序过滤器",
"showOutdatedOnly": "只显示过时的应用程序",
"filter": "过滤器",
"filterActive": "过滤器 *",
"filterApps": "过滤应用",
"appName": "应用名称",
"author": "作者",
"upToDateApps": "已更新的应用程序",
"nonInstalledApps": "未安装的应用程序",
"importExport": "导入/导出",
"settings": "设置",
"exportedTo": "导出到 {}",
"obtainiumExport": "Obtainium 导出",
"invalidInput": "无效输入",
"importedX": "已导出到 {}",
"obtainiumImport": "Obtainium 导入",
"importFromURLList": "从 URL 列表导入",
"searchQuery": "搜索查询",
"appURLList": "应用 URL 列表",
"line": "行",
"searchX": "搜索 {}",
"noResults": "无结果",
"importX": "导入 {}",
"importedAppsIdDisclaimer": "导入的应用程序可能不正确地显示为未安装。要解决这个问题,请通过 Obtainium 重新安装它们。",
"importErrors": "导入错误",
"importedXOfYApps": "{} 中的 {} 个应用已导入",
"followingURLsHadErrors": "以下 URL 有错误:",
"okay": "好的",
"selectURL": "已选择的 URL",
"selectURLs": "已选择的 URL",
"pick": "选择",
"theme": "主题",
"dark": "深色",
"light": "浅色",
"followSystem": "跟随系统",
"obtainium": "Obtainium",
"materialYou": "Material You",
"appSortBy": "排列方式",
"authorName": "作者 / 名字",
"nameAuthor": "名字 / 作者",
"asAdded": "添加顺序",
"appSortOrder": "排列顺序",
"ascending": "升序",
"descending": "降序",
"bgUpdateCheckInterval": "后台更新检查间隔",
"neverManualOnly": "手动",
"appearance": "外观",
"showWebInAppView": "在应用来源页显示网页",
"pinUpdates": "将需要更新的应用固定到顶部",
"updates": "检查间隔",
"sourceSpecific": "Github 访问令牌",
"appSource": "源代码",
"noLogs": "无日志",
"appLogs": "应用日志",
"close": "关闭",
"share": "分享",
"appNotFound": "未找到应用",
"obtainiumExportHyphenatedLowercase": "obtainium-导出",
"pickAnAPK": "选择一个安装包",
"appHasMoreThanOnePackage": "{} 有多于一个安装包:",
"deviceSupportsXArch": "你的设备支持 {} CPU 架构",
"deviceSupportsFollowingArchs": "你的设备支持以下 CPU 架构:",
"warning": "警告",
"sourceIsXButPackageFromYPrompt": "此应用来源是 '{}' 但更新包来自 '{}'。 继续吗?",
"updatesAvailable": "更新可用",
"updatesAvailableNotifDescription": "通知 Obtainium 所跟踪应用程序的更新",
"noNewUpdates": "你的应用已是最新。",
"xHasAnUpdate": "{} 有更新啦",
"appsUpdated": "应用已更新",
"appsUpdatedNotifDescription": "通知在后台安装应用程序的更新",
"xWasUpdatedToY": "{} 已更新到 {}.",
"errorCheckingUpdates": "检查更新出错",
"errorCheckingUpdatesNotifDescription": "当后台更新检查失败时显示的通知",
"appsRemoved": "应用已删除",
"appsRemovedNotifDescription": "通知由于加载应用程序时出错而被删除",
"xWasRemovedDueToErrorY": "{} 已因以下错误被删除: {}",
"completeAppInstallation": "完成应用安装",
"obtainiumMustBeOpenToInstallApps": "Obtainium 需要被启动以安装更新",
"completeAppInstallationNotifDescription": "需要返回 Obtainium以完成应用程序的安装。",
"checkingForUpdates": "检查更新中",
"checkingForUpdatesNotifDescription": "检查更新时出现的瞬时通知",
"pleaseAllowInstallPerm": "请允许 Obtainium 安装应用程序",
"trackOnly": "仅追踪",
"errorWithHttpStatusCode": "错误 {}",
"versionCorrectionDisabled": "禁用版本更正(插件似乎未起作用)",
"unknown": "未知",
"none": "无",
"never": "从不",
"latestVersionX": "最新: {}",
"installedVersionX": "已安装: {}",
"lastUpdateCheckX": "最后检查: {}",
"remove": "删除",
"removeAppQuestion": "删除应用?",
"yesMarkUpdated": "'是的,标为已更新",
"fdroid": "F-Droid",
"appIdOrName": "应用 ID 或名称",
"appWithIdOrNameNotFound": "没有发现具有此 ID 或名称的应用",
"reposHaveMultipleApps": "来源可能包含多个应用",
"fdroidThirdPartyRepo": "F-Droid 第三方源",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"tooManyRequestsTryAgainInMinutes": {
"one": "请求过多 (API 限制) - 在 {} 分钟后重试",
"other": "请求过多 (API 限制) - 在 {} 分钟后重试"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "后台更新检查遇到了 {} 问题, 将在 {} 分钟后重试",
"other": "后台更新检查遇到了 {} 问题, 将在 {} 分钟后重试"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "后台更新检查找到了 {} 个更新 - 将通知用户",
"other": "后台更新检查找到了 {} 个更新 - 将通知用户"
},
"apps": {
"one": "{} 个应用",
"other": "{} 个应用"
},
"url": {
"one": "{} 个 URL",
"other": "{} 个 URL"
},
"minute": {
"one": "{} 分钟",
"other": "{} 分钟"
},
"hour": {
"one": "{} 小时",
"other": "{} 小时"
},
"day": {
"one": "{} 天",
"other": "{} 天"
},
"clearedNLogsBeforeXAfterY": {
"one": "清除了 {n} 个日志 (清除前 = {before}, 清除后 = {after})",
"other": "清除了 {n} 个日志 (清除前 = {before}, 清除后 = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} 和 {} 更多应用已被更新",
"other": "{} 和 {} 更多应用已被更新"
},
"xAndNMoreUpdatesInstalled": {
"one": "{} 和 {} 更多应用已被安装",
"other": "{} 和 {} 更多应用已被安装"
}
}

View File

@ -14,7 +14,7 @@ class APKMirror extends AppSource {
RegExp standardUrlRegEx = RegExp('^https?://$host/apk/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(runtimeType.toString());
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@ -43,13 +43,12 @@ class APKMirror extends AppSource {
if (version == null || version.isEmpty) {
throw NoVersionError();
}
return APKDetails(version, []);
return APKDetails(version, [], getAppNames(standardUrl));
} else {
throw NoReleasesError();
}
}
@override
AppNames getAppNames(String standardUrl) {
String temp = standardUrl.substring(standardUrl.indexOf('://') + 3);
List<String> names = temp.substring(temp.indexOf('/') + 1).split('/');

View File

@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -7,6 +8,7 @@ import 'package:obtainium/providers/source_provider.dart';
class FDroid extends AppSource {
FDroid() {
host = 'f-droid.org';
name = tr('fdroid');
}
@override
@ -20,7 +22,7 @@ class FDroid extends AppSource {
RegExp standardUrlRegExA = RegExp('^https?://$host/+packages/+[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(runtimeType.toString());
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@ -29,12 +31,13 @@ class FDroid extends AppSource {
String? changeLogPageFromStandardUrl(String standardUrl) => null;
@override
String? tryInferringAppId(String standardUrl) {
String? tryInferringAppId(String standardUrl,
{List<String> additionalData = const []}) {
return Uri.parse(standardUrl).pathSegments.last;
}
APKDetails getAPKUrlsFromFDroidPackagesAPIResponse(
Response res, String apkUrlPrefix) {
Response res, String apkUrlPrefix, String standardUrl) {
if (res.statusCode == 200) {
List<dynamic> releases = jsonDecode(res.body)['packages'] ?? [];
if (releases.isEmpty) {
@ -48,7 +51,8 @@ class FDroid extends AppSource {
.where((element) => element['versionName'] == latestVersion)
.map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk')
.toList();
return APKDetails(latestVersion, apkUrls);
return APKDetails(latestVersion, apkUrls,
AppNames(name, Uri.parse(standardUrl).pathSegments.last));
} else {
throw NoReleasesError();
}
@ -61,11 +65,7 @@ class FDroid extends AppSource {
String? appId = tryInferringAppId(standardUrl);
return getAPKUrlsFromFDroidPackagesAPIResponse(
await get(Uri.parse('https://f-droid.org/api/v1/packages/$appId')),
'https://f-droid.org/repo/$appId');
}
@override
AppNames getAppNames(String standardUrl) {
return AppNames('F-Droid', Uri.parse(standardUrl).pathSegments.last);
'https://f-droid.org/repo/$appId',
standardUrl);
}
}

View File

@ -0,0 +1,90 @@
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';
class FDroidRepo extends AppSource {
FDroidRepo() {
name = tr('fdroidThirdPartyRepo');
additionalSourceAppSpecificFormItems = [
[
GeneratedFormItem(
label: tr('appIdOrName'),
hint: tr('reposHaveMultipleApps'),
required: true,
key: 'appIdOrName')
]
];
}
@override
String standardizeURL(String url) {
RegExp standardUrlRegExp =
RegExp('^https?://.+/fdroid/(repo(/|\\?)|repo\$)');
RegExpMatch? match = standardUrlRegExp.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData,
{bool trackOnly = false}) async {
String? appIdOrName = findGeneratedFormValueByKey(
additionalSourceAppSpecificFormItems
.reduce((value, element) => [...value, ...element]),
additionalData,
'appIdOrName');
if (appIdOrName == null) {
throw NoReleasesError();
}
var res = await get(Uri.parse('$standardUrl/index.xml'));
if (res.statusCode == 200) {
var body = parse(res.body);
var foundApps = body.querySelectorAll('application').where((element) {
return element.attributes['id'] == appIdOrName;
}).toList();
if (foundApps.isEmpty) {
foundApps = body.querySelectorAll('application').where((element) {
return element.querySelector('name')?.innerHtml.toLowerCase() ==
appIdOrName.toLowerCase();
}).toList();
}
if (foundApps.isEmpty) {
foundApps = body.querySelectorAll('application').where((element) {
return element
.querySelector('name')
?.innerHtml
.toLowerCase()
.contains(appIdOrName.toLowerCase()) ??
false;
}).toList();
}
if (foundApps.isEmpty) {
throw ObtainiumError(tr('appWithIdOrNameNotFound'));
}
var authorName = body.querySelector('repo')?.attributes['name'] ?? name;
var appName =
foundApps[0].querySelector('name')?.innerHtml ?? appIdOrName;
var releases = foundApps[0].querySelectorAll('package');
String? latestVersion = releases[0].querySelector('version')?.innerHtml;
if (latestVersion == null) {
throw NoVersionError();
}
List<String> apkUrls = releases
.where((element) =>
element.querySelector('version')?.innerHtml == latestVersion &&
element.querySelector('apkname') != null)
.map((e) => '$standardUrl/${e.querySelector('apkname')!.innerHtml}')
.toList();
return APKDetails(latestVersion, apkUrls, AppNames(authorName, appName));
} else {
throw NoReleasesError();
}
}
}

View File

@ -90,7 +90,7 @@ class GitHub extends AppSource {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(runtimeType.toString());
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@ -162,14 +162,14 @@ class GitHub extends AppSource {
if (version == null) {
throw NoVersionError();
}
return APKDetails(version, targetRelease['apkUrls'] as List<String>);
return APKDetails(version, targetRelease['apkUrls'] as List<String>,
getAppNames(standardUrl));
} else {
rateLimitErrorCheck(res);
throw getObtainiumHttpError(res);
}
}
@override
AppNames getAppNames(String standardUrl) {
String temp = standardUrl.substring(standardUrl.indexOf('://') + 3);
List<String> names = temp.substring(temp.indexOf('/') + 1).split('/');

View File

@ -14,7 +14,7 @@ class GitLab extends AppSource {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(runtimeType.toString());
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@ -56,15 +56,9 @@ class GitLab extends AppSource {
if (version == null) {
throw NoVersionError();
}
return APKDetails(version, apkUrls);
return APKDetails(version, apkUrls, GitHub().getAppNames(standardUrl));
} else {
throw NoReleasesError();
}
}
@override
AppNames getAppNames(String standardUrl) {
// Same as GitHub
return GitHub().getAppNames(standardUrl);
}
}

View File

@ -13,7 +13,7 @@ class IzzyOnDroid extends AppSource {
RegExp standardUrlRegEx = RegExp('^https?://$host/repo/apk/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(runtimeType.toString());
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@ -22,7 +22,8 @@ class IzzyOnDroid extends AppSource {
String? changeLogPageFromStandardUrl(String standardUrl) => null;
@override
String? tryInferringAppId(String standardUrl) {
String? tryInferringAppId(String standardUrl,
{List<String> additionalData = const []}) {
return FDroid().tryInferringAppId(standardUrl);
}
@ -34,11 +35,7 @@ class IzzyOnDroid extends AppSource {
return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse(
await get(
Uri.parse('https://apt.izzysoft.de/fdroid/api/v1/packages/$appId')),
'https://android.izzysoft.de/frepo/$appId');
}
@override
AppNames getAppNames(String standardUrl) {
return AppNames('IzzyOnDroid', Uri.parse(standardUrl).pathSegments.last);
'https://android.izzysoft.de/frepo/$appId',
standardUrl);
}
}

View File

@ -13,7 +13,7 @@ class Mullvad extends AppSource {
RegExp standardUrlRegEx = RegExp('^https?://$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(runtimeType.toString());
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@ -38,14 +38,11 @@ class Mullvad extends AppSource {
throw NoVersionError();
}
return APKDetails(
version, ['https://mullvad.net/download/app/apk/latest']);
version,
['https://mullvad.net/download/app/apk/latest'],
AppNames(name, 'Mullvad-VPN'));
} else {
throw NoReleasesError();
}
}
@override
AppNames getAppNames(String standardUrl) {
return AppNames('Mullvad-VPN', 'Mullvad-VPN');
}
}

View File

@ -30,12 +30,9 @@ class Signal extends AppSource {
if (version == null) {
throw NoVersionError();
}
return APKDetails(version, apkUrls);
return APKDetails(version, apkUrls, AppNames(name, 'Signal'));
} else {
throw NoReleasesError();
}
}
@override
AppNames getAppNames(String standardUrl) => AppNames('Signal', 'Signal');
}

View File

@ -13,7 +13,7 @@ class SourceForge extends AppSource {
RegExp standardUrlRegEx = RegExp('^https?://$host/projects/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(runtimeType.toString());
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@ -50,15 +50,13 @@ class SourceForge extends AppSource {
apkUrlListAllReleases // This can be used skipped for fallback support later
.where((element) => getVersion(element) == version)
.toList();
return APKDetails(version, apkUrlList);
return APKDetails(
version,
apkUrlList,
AppNames(
name, standardUrl.substring(standardUrl.lastIndexOf('/') + 1)));
} else {
throw NoReleasesError();
}
}
@override
AppNames getAppNames(String standardUrl) {
return AppNames(runtimeType.toString(),
standardUrl.substring(standardUrl.lastIndexOf('/') + 1));
}
}

View File

@ -0,0 +1,69 @@
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';
class SteamMobile extends AppSource {
SteamMobile() {
host = 'store.steampowered.com';
name = tr('steam');
additionalSourceAppSpecificFormItems = [
[
GeneratedFormItem(
label: tr('app'),
key: 'app',
required: true,
opts: apks.entries.toList())
]
];
}
final apks = {'steam': tr('steamMobile'), 'steam-chat-app': tr('steamChat')};
@override
String standardizeURL(String url) {
return 'https://$host';
}
@override
String? changeLogPageFromStandardUrl(String standardUrl) => null;
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData,
{bool trackOnly = false}) async {
Response res = await get(Uri.parse('https://$host/mobile'));
if (res.statusCode == 200) {
var apkNamePrefix = findGeneratedFormValueByKey(
additionalSourceAppSpecificFormItems
.reduce((value, element) => [...value, ...element]),
additionalData,
'app');
if (apkNamePrefix == null) {
throw NoReleasesError();
}
String apkInURLRegexPattern = '/$apkNamePrefix-[^/]+\\.apk\$';
var links = parse(res.body)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => RegExp('https://.*$apkInURLRegexPattern').hasMatch(e))
.toList();
if (links.isEmpty) {
throw NoReleasesError();
}
var versionMatch = RegExp(apkInURLRegexPattern).firstMatch(links[0]);
if (versionMatch == null) {
throw NoVersionError();
}
var version = links[0].substring(
versionMatch.start + apkNamePrefix.length + 2, versionMatch.end - 4);
var apkUrls = [links[0]];
return APKDetails(version, apkUrls, AppNames(name, apks[apkNamePrefix]!));
} else {
throw NoReleasesError();
}
}
}

View File

@ -16,7 +16,7 @@ class GeneratedFormItem {
late String id;
late List<Widget> belowWidgets;
late String? hint;
late List<String>? opts;
late List<MapEntry<String, String>>? opts;
GeneratedFormItem(
{this.label = 'Input',
@ -28,7 +28,11 @@ class GeneratedFormItem {
this.belowWidgets = const [],
this.hint,
this.opts,
this.key = 'default'});
this.key = 'default'}) {
if (type != FormItemType.string) {
required = false;
}
}
}
class GeneratedForm extends StatefulWidget {
@ -82,7 +86,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
return j < widget.defaultValues.length
? widget.defaultValues[j++]
: e.opts != null
? e.opts!.first
? e.opts!.first.key
: '';
}).toList())
.toList();
@ -126,14 +130,15 @@ class _GeneratedFormState extends State<GeneratedForm> {
return Text(tr('dropdownNoOptsError'));
}
return DropdownButtonFormField(
decoration: InputDecoration(labelText: tr('colour')),
decoration: InputDecoration(labelText: e.value.label),
value: values[row.key][e.key],
items: e.value.opts!
.map((e) => DropdownMenuItem(value: e, child: Text(e)))
.map((e) =>
DropdownMenuItem(value: e.key, child: Text(e.value)))
.toList(),
onChanged: (value) {
setState(() {
values[row.key][e.key] = value ?? e.value.opts!.first;
values[row.key][e.key] = value ?? e.value.opts!.first.key;
someValueChanged();
});
});

View File

@ -21,16 +21,24 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.8.10';
const String currentVersion = '0.8.20';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
const int bgUpdateCheckAlarmId = 666;
const supportedLocales = [Locale('en')];
const supportedLocales = [
Locale('en'),
Locale('zh'),
Locale('it'),
Locale('ja'),
Locale('hu')
];
const fallbackLocale = Locale('en');
const localeDir = 'assets/translations';
final globalNavigatorKey = GlobalKey<NavigatorState>();
Future<void> loadTranslations() async {
// See easy_localization/issues/210
await EasyLocalizationController.initEasyLocation();
@ -85,7 +93,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
if (e is RateLimitError || e is SocketException) {
var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15;
logs.add(plural('bgUpdateGotErrorRetryInMinutes', remainingMinutes,
args: [e.runtimeType.toString(), remainingMinutes.toString()]));
args: [e.toString(), remainingMinutes.toString()]));
AndroidAlarmManager.oneShot(Duration(minutes: remainingMinutes),
Random().nextInt(pow(2, 31) as int), bgUpdateCheck, params: {
'ignoreAfterMicroseconds': nextIgnoreAfter.microsecondsSinceEpoch
@ -237,6 +245,7 @@ class _ObtainiumState extends State<Obtainium> {
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
navigatorKey: globalNavigatorKey,
theme: ThemeData(
useMaterial3: true,
colorScheme: settingsProvider.theme == ThemeSettings.dark

View File

@ -5,6 +5,7 @@ import 'package:obtainium/components/custom_app_bar.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/pages/app.dart';
import 'package:obtainium/pages/import_export.dart';
import 'package:obtainium/providers/apps_provider.dart';
@ -40,12 +41,12 @@ class _AddAppPageState extends State<AddAppPage> {
userInput = input;
fn() {
var source = valid ? sourceProvider.getSource(userInput) : null;
if (pickedSource != source) {
if (pickedSource.runtimeType != source.runtimeType) {
pickedSource = source;
sourceSpecificAdditionalData =
source != null ? source.additionalSourceAppSpecificDefaults : [];
sourceSpecificDataIsValid = source != null
? sourceProvider.ifSourceAppsRequireAdditionalData(source)
? !sourceProvider.ifSourceAppsRequireAdditionalData(source)
: true;
}
}
@ -108,7 +109,8 @@ class _AddAppPageState extends State<AddAppPage> {
}
app.preferredApkIndex = app.apkUrls.indexOf(apkUrl);
// ignore: use_build_context_synchronously
var downloadedApk = await appsProvider.downloadApp(app, context);
var downloadedApk = await appsProvider.downloadApp(
app, globalNavigatorKey.currentContext);
app.id = downloadedApk.appId;
}
if (appsProvider.apps.containsKey(app.id)) {
@ -306,10 +308,8 @@ class _AddAppPageState extends State<AddAppPage> {
height: 64,
),
Text(
tr('additionalOptsFor', args: [
pickedSource?.runtimeType.toString() ??
tr('source')
]),
tr('additionalOptsFor',
args: [pickedSource?.name ?? tr('source')]),
style: TextStyle(
color:
Theme.of(context).colorScheme.primary)),
@ -323,10 +323,7 @@ class _AddAppPageState extends State<AddAppPage> {
items: pickedSource!
.additionalSourceAppSpecificFormItems,
onValueChanges: (values, valid, isBuilding) {
if (isBuilding) {
sourceSpecificAdditionalData = values;
sourceSpecificDataIsValid = valid;
} else {
if (!isBuilding) {
setState(() {
sourceSpecificAdditionalData = values;
sourceSpecificDataIsValid = valid;
@ -350,10 +347,7 @@ class _AddAppPageState extends State<AddAppPage> {
.map((e) => [e])
.toList(),
onValueChanges: (values, valid, isBuilding) {
if (isBuilding) {
otherAdditionalData = values;
otherAdditionalDataIsValid = valid;
} else {
if (!isBuilding) {
setState(() {
otherAdditionalData = values;
otherAdditionalDataIsValid = valid;
@ -381,16 +375,20 @@ class _AddAppPageState extends State<AddAppPage> {
),
...sourceProvider.sources
.map((e) => GestureDetector(
onTap: () {
launchUrlString('https://${e.host}',
mode:
LaunchMode.externalApplication);
},
onTap: e.host != null
? () {
launchUrlString(
'https://${e.host}',
mode: LaunchMode
.externalApplication);
}
: null,
child: Text(
'${e.runtimeType.toString()}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
style: const TextStyle(
decoration:
TextDecoration.underline,
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
style: TextStyle(
decoration: e.host != null
? TextDecoration.underline
: TextDecoration.none,
fontStyle: FontStyle.italic),
)))
.toList()

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -76,7 +77,7 @@ class _AppPageState extends State<AppPage> {
style: Theme.of(context).textTheme.displayLarge,
),
Text(
'By ${app?.app.author ?? 'Unknown'}',
tr('byX', args: [app?.app.author ?? tr('unknown')]),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium,
),
@ -102,12 +103,17 @@ class _AppPageState extends State<AppPage> {
height: 32,
),
Text(
'Latest Version: ${app?.app.latestVersion ?? 'Unknown'}',
tr('latestVersionX',
args: [app?.app.latestVersion ?? tr('unknown')]),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
),
Text(
'Installed Version: ${app?.app.installedVersion ?? 'None'}${app?.app.trackOnly == true ? ' (Estimate)\n\nApp is Track-Only' : ''}',
'${tr('installedVersionX', args: [
app?.app.installedVersion ?? tr('none')
])}${app?.app.trackOnly == true ? ' ${tr('estimateInBrackets')}\n\n${tr('xIsTrackOnly', args: [
tr('app')
])}' : ''}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
),
@ -115,7 +121,11 @@ class _AppPageState extends State<AppPage> {
height: 32,
),
Text(
'Last Update Check: ${app?.app.lastUpdateCheck == null ? 'Never' : '\n${app?.app.lastUpdateCheck?.toLocal()}'}',
tr('lastUpdateCheckX', args: [
app?.app.lastUpdateCheck == null
? tr('never')
: '\n${app?.app.lastUpdateCheck?.toLocal()}'
]),
textAlign: TextAlign.center,
style: const TextStyle(
fontStyle: FontStyle.italic, fontSize: 12),
@ -166,7 +176,7 @@ class _AppPageState extends State<AppPage> {
Navigator.of(context)
.pop();
},
child: const Text('No')),
child: Text(tr('no'))),
TextButton(
onPressed: () {
HapticFeedback
@ -183,8 +193,8 @@ class _AppPageState extends State<AppPage> {
Navigator.of(context)
.pop();
},
child: const Text(
'Yes, Mark as Updated'))
child: Text(
tr('yesMarkUpdated')))
],
);
});
@ -241,7 +251,9 @@ class _AppPageState extends State<AppPage> {
appsProvider
.downloadAndInstallLatestApps(
[app!.app.id],
context).then((res) {
globalNavigatorKey
.currentContext).then(
(res) {
if (res.isNotEmpty && mounted) {
Navigator.of(context).pop();
}
@ -267,9 +279,14 @@ class _AppPageState extends State<AppPage> {
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
title: const Text('Remove App?'),
content: Text(
'This will remove \'${app?.installedInfo?.name ?? app?.app.name}\' from Obtainium.${app?.app.installedVersion != null ? '\n\nNote that while Obtainium will no longer track its updates, the App will remain installed.' : ''}'),
title: Text(tr('removeAppQuestion')),
content: Text(tr(
'xWillBeRemovedButRemainInstalled',
args: [
app?.installedInfo?.name ??
app?.app.name ??
tr('app')
])),
actions: [
TextButton(
onPressed: () {
@ -283,12 +300,12 @@ class _AppPageState extends State<AppPage> {
count++ >= 2);
});
},
child: const Text('Remove')),
child: Text(tr('remove'))),
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Cancel'))
child: Text(tr('cancel')))
],
);
});
@ -298,7 +315,7 @@ class _AppPageState extends State<AppPage> {
Theme.of(context).colorScheme.error,
surfaceTintColor:
Theme.of(context).colorScheme.error),
child: const Text('Remove'),
child: Text(tr('remove')),
),
])),
if (app?.downloadProgress != null)

View File

@ -5,6 +5,7 @@ import 'package:obtainium/components/custom_app_bar.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/pages/app.dart';
import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/settings_provider.dart';
@ -462,8 +463,8 @@ class AppsPageState extends State<AppsPage> {
trackOnlyUpdateIdsAllOrSelected);
}
appsProvider
.downloadAndInstallLatestApps(
toInstall, context)
.downloadAndInstallLatestApps(toInstall,
globalNavigatorKey.currentContext)
.catchError((e) {
showError(e, context);
});

View File

@ -232,9 +232,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
return GeneratedFormModal(
title: tr('searchX',
args: [
source
.runtimeType
.toString()
source.name
]),
items: [
[
@ -319,9 +317,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
});
});
},
child: Text(tr('searchX', args: [
source.runtimeType.toString()
])))
child: Text(
tr('searchX', args: [source.name])))
]))
.toList(),
...sourceProvider.massUrlSources

View File

@ -274,9 +274,14 @@ class AppsProvider with ChangeNotifier {
);
});
}
getHost(String url) {
var temp = Uri.parse(url).host.split('.');
return temp.sublist(temp.length - 2).join('.');
}
// If the picked APK comes from an origin different from the source, get user confirmation (if context provided)
if (apkUrl != null &&
Uri.parse(apkUrl).origin != Uri.parse(app.url).origin &&
getHost(apkUrl) != getHost(app.url) &&
context != null) {
if (await showDialog(
context: context,

View File

@ -8,12 +8,14 @@ import 'package:html/dom.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/apkmirror.dart';
import 'package:obtainium/app_sources/fdroid.dart';
import 'package:obtainium/app_sources/fdroidrepo.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/app_sources/gitlab.dart';
import 'package:obtainium/app_sources/izzyondroid.dart';
import 'package:obtainium/app_sources/mullvad.dart';
import 'package:obtainium/app_sources/signal.dart';
import 'package:obtainium/app_sources/sourceforge.dart';
import 'package:obtainium/app_sources/steammobile.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/mass_app_sources/githubstars.dart';
@ -28,8 +30,9 @@ class AppNames {
class APKDetails {
late String version;
late List<String> apkUrls;
late AppNames names;
APKDetails(this.version, this.apkUrls);
APKDetails(this.version, this.apkUrls, this.names);
}
class App {
@ -135,8 +138,14 @@ List<String> getLinksFromParsedHTML(
.toList();
class AppSource {
late String host;
String? host;
late String name;
bool enforceTrackOnly = false;
AppSource() {
name = runtimeType.toString();
}
String standardizeURL(String url) {
throw NotImplementedError();
}
@ -147,10 +156,6 @@ class AppSource {
throw NotImplementedError();
}
AppNames getAppNames(String standardUrl) {
throw NotImplementedError();
}
// Different Sources may need different kinds of additional data for Apps
List<List<GeneratedFormItem>> additionalSourceAppSpecificFormItems = [];
List<String> additionalSourceAppSpecificDefaults = [];
@ -168,7 +173,7 @@ class AppSource {
List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = [];
String? changeLogPageFromStandardUrl(String standardUrl) {
throw NotImplementedError();
return null;
}
Future<String> apkUrlPrefetchModifier(String apkUrl) async {
@ -180,7 +185,8 @@ class AppSource {
throw NotImplementedError();
}
String? tryInferringAppId(String standardUrl) {
String? tryInferringAppId(String standardUrl,
{List<String> additionalData = const []}) {
return null;
}
}
@ -206,7 +212,9 @@ class SourceProvider {
Mullvad(),
Signal(),
SourceForge(),
APKMirror()
APKMirror(),
FDroidRepo(),
SteamMobile()
];
// Add more mass url source classes here so they are available via the service
@ -215,12 +223,23 @@ class SourceProvider {
AppSource getSource(String url) {
url = preStandardizeUrl(url);
AppSource? source;
for (var s in sources) {
if (url.toLowerCase().contains('://${s.host}')) {
for (var s in sources.where((element) => element.host != null)) {
if (url.contains('://${s.host}')) {
source = s;
break;
}
}
if (source == null) {
for (var s in sources.where((element) => element.host == null)) {
try {
s.standardizeURL(url);
source = s;
break;
} catch (e) {
//
}
}
}
if (source == null) {
throw UnsupportedURLError();
}
@ -230,7 +249,7 @@ class SourceProvider {
bool ifSourceAppsRequireAdditionalData(AppSource source) {
for (var row in source.additionalSourceAppSpecificFormItems) {
for (var element in row) {
if (element.required) {
if (element.required && element.opts == null) {
return true;
}
}
@ -252,7 +271,7 @@ class SourceProvider {
return false;
}
}
return sources.map((e) => e.host).contains(parts.last);
return true;
}
Future<App> getApp(AppSource source, String url, List<String> additionalData,
@ -262,7 +281,6 @@ class SourceProvider {
bool trackOnly = false,
String? installedVersion}) async {
String standardUrl = source.standardizeURL(preStandardizeUrl(url));
AppNames names = source.getAppNames(standardUrl);
APKDetails apk = await source
.getLatestAPKDetails(standardUrl, additionalData, trackOnly: trackOnly);
if (apk.apkUrls.isEmpty && !trackOnly) {
@ -271,13 +289,14 @@ class SourceProvider {
String apkVersion = apk.version.replaceAll('/', '-');
return App(
id ??
source.tryInferringAppId(standardUrl) ??
generateTempID(names, source),
source.tryInferringAppId(standardUrl,
additionalData: additionalData) ??
generateTempID(apk.names, source),
standardUrl,
names.author[0].toUpperCase() + names.author.substring(1),
apk.names.author[0].toUpperCase() + apk.names.author.substring(1),
name.trim().isNotEmpty
? name
: names.name[0].toUpperCase() + names.name.substring(1),
: apk.names.name[0].toUpperCase() + apk.names.name.substring(1),
installedVersion,
apkVersion,
apk.apkUrls,

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.8.10+74 # When changing this, update the tag in main() accordingly
version: 0.8.20+84 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=2.18.2 <3.0.0'