diff --git a/assets/translations/fr.json b/assets/translations/fr.json index c09de3b..f61527e 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -167,7 +167,7 @@ "lastUpdateCheckX": "Vérification de la dernière mise à jour : {}", "remove": "Retirer", "yesMarkUpdated": "Oui, marquer comme mis à jour", - "fdroid": "F-Droïde Officiel", + "fdroid": "F-Droid Officiel", "appIdOrName": "ID ou nom de l'application", "appId": "ID de l'application", "appWithIdOrNameNotFound": "Aucune application n'a été trouvée avec cet identifiant ou ce nom", diff --git a/assets/translations/hu.json b/assets/translations/hu.json index a7ead85..277f6a6 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -281,14 +281,14 @@ "parallelDownloads": "Párhuzamos letöltéseket enged", "installMethod": "Telepítési mód", "normal": "Normál", - "root": "Gyökér", + "root": "Root", "shizukuBinderNotFound": "A Shizuku nem fut", "useSystemFont": "Használja a rendszer betűtípusát", "systemFontError": "Hiba a rendszer betűtípusának betöltésekor: {}", "useVersionCodeAsOSVersion": "Az app verziókód használata a rendszer által észlelt verzióként", "requestHeader": "Kérelem fejléc", "useLatestAssetDateAsReleaseDate": "Használja a legújabb tartalomfeltöltést megjelenési dátumként", - "defaultPseudoVersioningMethod": "Alapértelmezett álversziós módszer", + "defaultPseudoVersioningMethod": "Alapértelmezett álverziós módszer", "partialAPKHash": "Részleges APK Hash", "APKLinkHash": "APK Link Hash", "directAPKLink": "Közvetlen APK Link", diff --git a/assets/translations/uk.json b/assets/translations/uk.json new file mode 100644 index 0000000..fd3365b --- /dev/null +++ b/assets/translations/uk.json @@ -0,0 +1,359 @@ +{ + "invalidURLForSource": "Неправильна URL-адреса для джерела застосунку {}", + "noReleaseFound": "Не вдалося знайти відповідне видання", + "noVersionFound": "Не вдалося визначити версію видання", + "urlMatchesNoSource": "URL не відповідає відомому джерелу", + "cantInstallOlderVersion": "Не можна встановити старішу версію застосунку", + "appIdMismatch": "Ідентифікатор пакета, завантажений, не відповідає ідентифікатору існуючого застосунку", + "functionNotImplemented": "Цей клас не реалізував цю функцію", + "placeholder": "Заповнювач", + "someErrors": "Виникла деяка помилка", + "unexpectedError": "Неочікувана помилка", + "ok": "Добре", + "and": "та", + "githubPATLabel": "Персональний ключ доступу 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": "Додаткові опції для {}", + "supportedSources": "Підтримувані джерела", + "trackOnlyInBrackets": "(Тільки для відстеження)", + "searchableInBrackets": "(Можливий пошук)", + "appsString": "Додатки", + "noApps": "Додатків немає", + "noAppsForFilter": "Додатків для фільтрації немає", + "byX": "За {}", + "percentProgress": "Прогрес: {}%", + "pleaseWait": "Будь ласка, зачекайте", + "updateAvailable": "Доступно оновлення", + "notInstalled": "Не встановлено", + "pseudoVersion": "псевдо-версія", + "selectAll": "Вибрати все", + "deselectX": "Скасувати вибір {}", + "xWillBeRemovedButRemainInstalled": "{} буде видалено з Obtainium, але залишиться встановленим на пристрої.", + "removeSelectedAppsQuestion": "Видалити вибрані додатки?", + "removeSelectedApps": "Видалити вибрані додатки", + "updateX": "Оновити {}", + "installX": "Встановити {}", + "markXTrackOnlyAsUpdated": "Позначити {}\n(Тільки відстежування)\nяк оновлено", + "changeX": "Змінити {}", + "installUpdateApps": "Встановити/Оновити додатки", + "installUpdateSelectedApps": "Встановити/Оновити вибрані додатки", + "markXSelectedAppsAsUpdated": "Позначити {} вибрані додатки як оновлені?", + "no": "Ні", + "yes": "Так", + "markSelectedAppsUpdated": "Позначити вибрані додатки як оновлені", + "pinToTop": "Закріпити угорі", + "unpinFromTop": "Відкріпити зверху", + "resetInstallStatusForSelectedAppsQuestion": "Скинути статус встановлення для вибраних додатків?", + "installStatusOfXWillBeResetExplanation": "Статус встановлення будь-яких вибраних додатків буде скинутий.\n\nЦе може допомогти, коли версія застосунку, відображена в Obtainium, є неправильною через невдалі оновлення або інші проблеми.", + "customLinkMessage": "Ці посилання працюють на пристроях з встановленим Obtainium", + "shareAppConfigLinks": "Поділитися посиланнями на конфігурацію Застосунку як HTML", + "shareSelectedAppURLs": "Поділитися вибраними URL-адресами додатків", + "resetInstallStatus": "Скинути статус встановлення", + "more": "Більше", + "removeOutdatedFilter": "Видалити фільтр застарілих додатків", + "showOutdatedOnly": "Показати лише застарілі додатки", + "filter": "Фільтр", + "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\nПов'язано лише з URL-адресами та імпортом від третіх сторін.", + "importErrors": "Помилки імпорту", + "importedXOfYApps": "Імпортовано {} з {} додатків.", + "followingURLsHadErrors": "Помилки в наступних URL-адресах:", + "selectURL": "Вибрати URL", + "selectURLs": "Вибрати URL-адреси", + "pick": "Вибрати", + "theme": "Тема", + "dark": "Темна", + "light": "Світла", + "followSystem": "Дотримуватися системи", + "useBlackTheme": "Використовувати чисто чорну темну тему", + "appSortBy": "Сортувати додатки за", + "authorName": "Автор/Назва", + "nameAuthor": "Назва/Автор", + "asAdded": "За додаванням", + "appSortOrder": "Порядок сортування додатків", + "ascending": "За зростанням", + "descending": "За спаданням", + "bgUpdateCheckInterval": "Інтервал перевірки оновлень у фоновому режимі", + "neverManualOnly": "Ніколи - Тільки вручну", + "appearance": "Вигляд", + "showWebInAppView": "Показати джерело застосунку у вигляді веб-сторінки", + "pinUpdates": "Закріпити оновлення у верхній частині вигляду додатків", + "updates": "Оновлення", + "sourceSpecific": "Певне джерело", + "appSource": "Джерело застосунку", + "noLogs": "Немає логів", + "appLogs": "Лог застосунку", + "close": "Закрити", + "share": "Поділитися", + "appNotFound": "Застосунок не знайдено", + "obtainiumExportHyphenatedLowercase": "експорт з Obtainium", + "pickAnAPK": "Вибрати APK", + "appHasMoreThanOnePackage": "{} має більше одного пакету:", + "deviceSupportsXArch": "Ваш пристрій підтримує архітектуру процесора {}.", + "deviceSupportsFollowingArchs": "Ваш пристрій підтримує наступні архітектури процесора:", + "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": "Помилка {} HTTP-коду", + "versionCorrectionDisabled": "Виправлення версії вимкнено (здається, плагін не працює)", + "unknown": "Невідомо", + "none": "Нічого", + "never": "Ніколи", + "latestVersionX": "Остання версія: {}", + "installedVersionX": "Встановлено: {}", + "lastUpdateCheckX": "Остання перевірка оновлень: {}", + "remove": "Видалити", + "yesMarkUpdated": "Так, позначити як оновлене", + "fdroid": "F-Droid Офіційний", + "appIdOrName": "Ідентифікатор або назва застосунку", + "appId": "Ідентифікатор застосунку", + "appWithIdOrNameNotFound": "Застосунок з таким ідентифікатором або назвою не знайдено", + "reposHaveMultipleApps": "Сховища можуть містити кілька додатків", + "fdroidThirdPartyRepo": "F-Droid Стороннє сховище", + "steamMobile": "Мобільний Steam", + "steamChat": "Чат Steam", + "install": "Встановити", + "markInstalled": "Позначити як встановлене", + "update": "Оновити", + "markUpdated": "Позначити як оновлене", + "additionalOptions": "Додаткові опції", + "disableVersionDetection": "Вимкнути визначення версії", + "noVersionDetectionExplanation": "Цю опцію слід використовувати лише для додатків, де визначення версії працює неправильно.", + "downloadingX": "Завантаження {}", + "downloadNotifDescription": "Повідомляє користувача про прогрес завантаження застосунку", + "noAPKFound": "APK не знайдено", + "noVersionDetection": "Визначення версії відключено", + "categorize": "Категоризувати", + "categories": "Категорії", + "category": "Категорія", + "noCategory": "Без категорії", + "noCategories": "Немає категорій", + "deleteCategoriesQuestion": "Видалити категорії?", + "categoryDeleteWarning": "Усі додатки у видалених категоріях будуть переведені у некатегоризовані.", + "addCategory": "Додати категорію", + "label": "Мітка", + "language": "Мова", + "copiedToClipboard": "Скопійовано в буфер обміну", + "storagePermissionDenied": "Відмовлено у дозволі на доступ до сховища", + "selectedCategorizeWarning": "Це замінить будь-які існуючі налаштування категорій для вибраних додатків.", + "filterAPKsByRegEx": "Фільтрувати APK за регулярним виразом", + "removeFromObtainium": "Видалити з Obtainium", + "uninstallFromDevice": "Видалити з пристрою", + "onlyWorksWithNonVersionDetectApps": "Працює лише з застосунками з вимкненим визначенням версії.", + "releaseDateAsVersion": "Використовувати дату випуску як рядок версії", + "releaseDateAsVersionExplanation": "Цю опцію слід використовувати лише для додатків, де визначення версії працює неправильно, але є дата випуску.", + "changes": "Зміни", + "releaseDate": "Дата випуску", + "importFromURLsInFile": "Імпорт з URL-адрес у файлі (наприклад, OPML)", + "versionDetectionExplanation": "Порівняти рядок версії з версією, визначеною операційною системою", + "versionDetection": "Визначення версії", + "standardVersionDetection": "Стандартне визначення версії", + "groupByCategory": "Групувати за категоріями", + "autoApkFilterByArch": "Спробувати фільтрувати APK за архітектурою ЦП, якщо можливо", + "overrideSource": "Перевизначити джерело", + "dontShowAgain": "Не показувати це знову", + "dontShowTrackOnlyWarnings": "Не показувати попередження про 'Тільки відстеження'", + "dontShowAPKOriginWarnings": "Не показувати попередження про походження APK", + "moveNonInstalledAppsToBottom": "Перемістити невстановлені додатки вниз у перегляді додатків", + "gitlabPATLabel": "Особистий токен GitLab (Увімкнення пошуку та краще виявлення APK)", + "about": "Про програму", + "requiresCredentialsInSettings": "{} потребує додаткових облікових даних (у налаштуваннях)", + "checkOnStart": "Перевірити наявність оновлень при запуску", + "tryInferAppIdFromCode": "Спробувати вивести ідентифікатор застосунку з вихідного коду", + "removeOnExternalUninstall": "Автоматично видаляти додатки, які було видалено зовнішнім чином", + "pickHighestVersionCode": "Автоматично вибрати APK з найвищим кодом версії", + "checkUpdateOnDetailPage": "Перевіряти наявність оновлень при відкритті сторінки деталей застосунку", + "disablePageTransitions": "Вимкнути анімації переходів між сторінками", + "reversePageTransitions": "Зворотні анімації переходів між сторінками", + "minStarCount": "Мінімальна кількість зірок", + "addInfoBelow": "Додати цю інформацію нижче.", + "addInfoInSettings": "Додати цю інформацію у налаштуваннях.", + "githubSourceNote": "Лімітування швидкості GitHub можна уникнути, використовуючи ключ API.", + "gitlabSourceNote": "Вилучення APK з GitLab може не працювати без ключа API.", + "sortByLastLinkSegment": "Сортувати лише за останнім сегментом посилання", + "filterReleaseNotesByRegEx": "Фільтрувати примітки до релізу за регулярним виразом", + "customLinkFilterRegex": "Фільтр кастомного посилання на APK за регулярним виразом (за замовчуванням '.apk$')", + "appsPossiblyUpdated": "Оновлення додатків спробовано", + "appsPossiblyUpdatedNotifDescription": "Повідомляє користувача, що оновлення одного або декількох додатків можливо були застосовані в фоновому режимі", + "xWasPossiblyUpdatedToY": "{} можливо було оновлено до {}.", + "enableBackgroundUpdates": "Увімкнути оновлення в фоновому режимі", + "backgroundUpdateReqsExplanation": "Оновлення в фоновому режимі може бути неможливим для всіх додатків.", + "backgroundUpdateLimitsExplanation": "Успіх фонової установки може бути визначений лише після відкриття Obtainium.", + "verifyLatestTag": "Перевірити тег 'latest'", + "intermediateLinkRegex": "Фільтр для 'Проміжного' Посилання для Відвідування", + "filterByLinkText": "Фільтрувати посилання за текстом посилання", + "intermediateLinkNotFound": "Проміжне посилання не знайдено", + "intermediateLink": "Проміжне посилання", + "exemptFromBackgroundUpdates": "Виключено з фонових оновлень (якщо ввімкнено)", + "bgUpdatesOnWiFiOnly": "Вимкнути фонові оновлення поза Wi-Fi", + "autoSelectHighestVersionCode": "Автоматичний вибір APK з найвищим кодом версії", + "versionExtractionRegEx": "Регулярний вираз для вилучення рядка версії", + "matchGroupToUse": "Група співпадінь для використання в регулярному виразі вилучення версії", + "highlightTouchTargets": "Підсвічувати менш очевидні області дотику", + "pickExportDir": "Вибрати каталог експорту", + "autoExportOnChanges": "Автоматичний експорт при змінах", + "includeSettings": "Включити налаштування", + "filterVersionsByRegEx": "Фільтрувати версії за регулярним виразом", + "trySelectingSuggestedVersionCode": "Спробуйте вибрати запропонований код версії APK", + "dontSortReleasesList": "Зберігати порядок випуску з API", + "reverseSort": "Зворотне сортування", + "takeFirstLink": "Вибрати перше посилання", + "skipSort": "Пропустити сортування", + "debugMenu": "Меню налагодження", + "bgTaskStarted": "Запущено фонове завдання - перевірте журнали.", + "runBgCheckNow": "Запустити перевірку оновлень в фоновому режимі зараз", + "versionExtractWholePage": "Застосувати регулярний вираз вилучення версії до всієї сторінки", + "installing": "Встановлення", + "skipUpdateNotifications": "Пропустити сповіщення про оновлення", + "updatesAvailableNotifChannel": "Доступні оновлення", + "appsUpdatedNotifChannel": "Додатки оновлені", + "appsPossiblyUpdatedNotifChannel": "Спроба оновлення додатків", + "errorCheckingUpdatesNotifChannel": "Помилка перевірки оновлень", + "appsRemovedNotifChannel": "Додатки видалені", + "downloadingXNotifChannel": "Завантаження {}", + "completeAppInstallationNotifChannel": "Завершення встановлення застосунку", + "checkingForUpdatesNotifChannel": "Перевірка оновлень", + "onlyCheckInstalledOrTrackOnlyApps": "Перевіряти лише встановлені та додатки, які відстежуються для оновлень", + "supportFixedAPKURL": "Підтримка фіксованих посилань на APK", + "selectX": "Вибрати {}", + "parallelDownloads": "Дозволити паралельні завантаження", + "installMethod": "Метод встановлення", + "normal": "Звичайний", + "root": "Root", + "shizukuBinderNotFound": "Сумісний сервіс Shizuku не було знайдено", + "useSystemFont": "Використовувати системний шрифт", + "systemFontError": "Помилка завантаження системного шрифту: {}", + "useVersionCodeAsOSVersion": "Використовувати код версії застосунку як версію, визначену операційною системою", + "requestHeader": "Заголовок запиту", + "useLatestAssetDateAsReleaseDate": "Використовувати останню дату завантаження ресурсу як дату випуску", + "defaultPseudoVersioningMethod": "Метод за замовчуванням псевдо-версіонування", + "partialAPKHash": "Хеш часткового APK", + "APKLinkHash": "Хеш посилання на APK", + "directAPKLink": "Пряме посилання на APK", + "pseudoVersionInUse": "Використовується псевдо-версія", + "installed": "Встановлено", + "latest": "Остання", + "invertRegEx": "Інвертувати регулярний вираз", + "note": "Примітка", + "selfHostedNote": "Випадаючий список \"{}\" може використовуватися для доступу до власних/призначених для самостійного використання екземплярів будь-якого джерела.", + "badDownload": "APK не вдалося розпарсити (несумісний або часткове завантаження)", + "removeAppQuestion": { + "one": "Видалити Застосунок?", + "other": "Видалити додатки?" + }, + "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": "{} і 1 інше Застосунок мають оновлення.", + "other": "{} і {} інших додатки мають оновлення." + }, + "xAndNMoreUpdatesInstalled": { + "one": "{} і 1 інше Застосунок було оновлено.", + "other": "{} і {} інших додатків було оновлено." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "{} і 1 інше Застосунок можливо було оновлено.", + "other": "{} і {} інших додатків можливо було оновлено." + }, + "apk": { + "one": "{} APK", + "other": "{} APK-файли" + } +} diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 74c306a..8e7e8ad 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -52,7 +52,7 @@ "pleaseWait": "请稍候", "updateAvailable": "更新可用", "notInstalled": "未安装", - "pseudoVersion": "伪版本", + "pseudoVersion": "虚拟版本号", "selectAll": "全选", "deselectX": "取消选择 {}", "xWillBeRemovedButRemainInstalled": "“{}”将从 Obtainium 中删除,但仍安装在您的设备中。", @@ -208,7 +208,7 @@ "changes": "更新日志", "releaseDate": "发行日期", "importFromURLsInFile": "从文件中的 URL 导入(如 OPML)", - "versionDetectionExplanation": "将版本字符串与操作系统检测到的版本进行协调", + "versionDetectionExplanation": "使发行版本号与应用定义的版本号一致", "versionDetection": "版本检测", "standardVersionDetection": "常规版本检测", "groupByCategory": "按类别分组显示", @@ -224,7 +224,7 @@ "checkOnStart": "启动时进行一次检查", "tryInferAppIdFromCode": "尝试从源代码推断应用 ID", "removeOnExternalUninstall": "自动删除列表中已卸载的应用", - "pickHighestVersionCode": "自动选择版本号最高的 APK 文件", + "pickHighestVersionCode": "自动选取内部版本号最高的 APK 文件", "checkUpdateOnDetailPage": "打开应用详情页时进行检查", "disablePageTransitions": "禁用页面过渡动画效果", "reversePageTransitions": "反转页面过渡动画效果", @@ -248,7 +248,7 @@ "intermediateLink": "中转链接", "exemptFromBackgroundUpdates": "禁用后台更新(如果已经全局启用)", "bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新", - "autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件", + "autoSelectHighestVersionCode": "自动选择内部版本号最高的 APK 文件", "versionExtractionRegEx": "版本号提取规则(正则表达式)", "matchGroupToUse": "引用的捕获组", "highlightTouchTargets": "突出展示不明显的触摸区域", @@ -285,20 +285,20 @@ "shizukuBinderNotFound": "未发现兼容的 Shizuku 服务", "useSystemFont": "使用系统字体", "systemFontError": "加载系统字体出错:{}", - "useVersionCodeAsOSVersion": "使用应用程序版本代码作为操作系统检测到的版本", + "useVersionCodeAsOSVersion": "使用内部版本号代替应用定义的版本号", "requestHeader": "请求标头", - "useLatestAssetDateAsReleaseDate": "使用最新资产上传作为发布日期", - "defaultPseudoVersioningMethod": "默认伪版本控制方法", - "partialAPKHash": "部分 APK 哈希值", - "APKLinkHash": "APK 链接哈希", - "directAPKLink": "直接 APK 链接", - "pseudoVersionInUse": "伪版本正在使用", - "installed": "已安装", - "latest": "最新的", - "invertRegEx": "反转正则表达式", + "useLatestAssetDateAsReleaseDate": "使用最近文件上传时间作为发行日期", + "defaultPseudoVersioningMethod": "默认虚拟版本方案", + "partialAPKHash": "APK 文件散列值片段", + "APKLinkHash": "APK 文件链接散列值", + "directAPKLink": "APK 文件直链", + "pseudoVersionInUse": "正在使用虚拟版本号", + "installed": "当前版本", + "latest": "最新版本", + "invertRegEx": "反转匹配", "note": "备注", - "selfHostedNote": "{}\"下拉菜单可用于访问任何来源的自托管/自定义实例。", - "badDownload": "无法解析 APK(不兼容或部分下载)", + "selfHostedNote": "可以通过“{}”下拉菜单来指向任意来源的自托管/自定义实例。", + "badDownload": "无法解析 APK 文件(不兼容或文件不完整)", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/app_sources/gitlab.dart b/lib/app_sources/gitlab.dart index 601edb4..42a00a0 100644 --- a/lib/app_sources/gitlab.dart +++ b/lib/app_sources/gitlab.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:html/parser.dart'; import 'package:http/http.dart'; import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/custom_errors.dart'; @@ -117,91 +116,58 @@ class GitLab extends AppSource { String standardUrl, Map additionalSettings, ) async { - bool fallbackToOlderReleases = - additionalSettings['fallbackToOlderReleases'] == true; + // Prepare request params + var names = GitHub().getAppNames(standardUrl); String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {}); - Iterable apkDetailsList = []; - if (PAT != null) { - var names = GitHub().getAppNames(standardUrl); - Response res = await sourceRequest( - 'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT', - additionalSettings); - if (res.statusCode != 200) { - throw getObtainiumHttpError(res); - } - var json = jsonDecode(res.body) as List; - apkDetailsList = json.map((e) { - var apkUrlsFromAssets = (e['assets']?['links'] as List? ?? []) - .map((e) { - return (e['direct_asset_url'] ?? e['url'] ?? '') as String; - }) - .where((s) => s.isNotEmpty) - .toList(); - List uploadedAPKsFromDescription = - ((e['description'] ?? '') as String) - .split('](') - .join('\n') - .split('.apk)') - .join('.apk\n') - .split('\n') - .where((s) => s.startsWith('/uploads/') && s.endsWith('apk')) - .map((s) => '$standardUrl$s') - .toList(); - var apkUrlsSet = apkUrlsFromAssets.toSet(); - apkUrlsSet.addAll(uploadedAPKsFromDescription); - var releaseDateString = e['released_at'] ?? e['created_at']; - DateTime? releaseDate = releaseDateString != null - ? DateTime.parse(releaseDateString) - : null; - return APKDetails( - e['tag_name'] ?? e['name'], - getApkUrlsFromUrls(apkUrlsSet.toList()), - GitHub().getAppNames(standardUrl), - releaseDate: releaseDate); - }); - } else { - Response res = await sourceRequest( - '$standardUrl/-/tags?format=atom', additionalSettings); - if (res.statusCode != 200) { - throw getObtainiumHttpError(res); - } - var standardUri = Uri.parse(standardUrl); - var parsedHtml = parse(res.body); - apkDetailsList = parsedHtml.querySelectorAll('entry').map((entry) { - var entryContent = parse( - parseFragment(entry.querySelector('content')!.innerHtml).text); - var apkUrls = [ - ...getLinksFromParsedHTML( - entryContent, - RegExp( - '^${standardUri.path.replaceAllMapped(RegExp(r'[.*+?^${}()|[\]\\]'), (x) { - return '\\${x[0]}'; - })}/uploads/[^/]+/[^/]+\\.apk\$', - caseSensitive: false), - standardUri.origin), - // GitLab releases may contain links to externally hosted APKs - ...getLinksFromParsedHTML(entryContent, - RegExp('/[^/]+\\.apk\$', caseSensitive: false), '') - .where((element) => Uri.parse(element).host != '') - ]; - var entryId = entry.querySelector('id')?.innerHtml; - var version = - entryId == null ? null : Uri.parse(entryId).pathSegments.last; - var releaseDateString = entry.querySelector('updated')?.innerHtml; - DateTime? releaseDate = releaseDateString != null - ? DateTime.parse(releaseDateString) - : null; - if (version == null) { - throw NoVersionError(); - } - return APKDetails(version, getApkUrlsFromUrls(apkUrls), - GitHub().getAppNames(standardUrl), - releaseDate: releaseDate); - }); + String optionalAuth = (PAT != null) ? 'private_token=$PAT' : ''; + + // Request data from REST API + Response res = await sourceRequest( + 'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?$optionalAuth', + additionalSettings); + if (res.statusCode != 200) { + throw getObtainiumHttpError(res); } + + // Extract .apk details from received data + Iterable apkDetailsList = []; + var json = jsonDecode(res.body) as List; + apkDetailsList = json.map((e) { + var apkUrlsFromAssets = (e['assets']?['links'] as List? ?? []) + .map((e) { + return (e['direct_asset_url'] ?? e['url'] ?? '') as String; + }) + .where((s) => s.isNotEmpty) + .toList(); + List uploadedAPKsFromDescription = + ((e['description'] ?? '') as String) + .split('](') + .join('\n') + .split('.apk)') + .join('.apk\n') + .split('\n') + .where((s) => s.startsWith('/uploads/') && s.endsWith('apk')) + .map((s) => '$standardUrl$s') + .toList(); + var apkUrlsSet = apkUrlsFromAssets.toSet(); + apkUrlsSet.addAll(uploadedAPKsFromDescription); + var releaseDateString = e['released_at'] ?? e['created_at']; + DateTime? releaseDate = releaseDateString != null + ? DateTime.parse(releaseDateString) + : null; + return APKDetails( + e['tag_name'] ?? e['name'], + getApkUrlsFromUrls(apkUrlsSet.toList()), + GitHub().getAppNames(standardUrl), + releaseDate: releaseDate); + }); if (apkDetailsList.isEmpty) { throw NoReleasesError(); } + + // Fallback procedure + bool fallbackToOlderReleases = + additionalSettings['fallbackToOlderReleases'] == true; if (fallbackToOlderReleases) { if (additionalSettings['trackOnly'] != true) { apkDetailsList = @@ -211,6 +177,7 @@ class GitLab extends AppSource { throw NoReleasesError(); } } + return apkDetailsList.first; } } diff --git a/lib/main.dart b/lib/main.dart index 3b7abda..32f2c0b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -38,6 +38,7 @@ List> supportedLocales = const [ MapEntry(Locale('nl'), 'Nederlands'), MapEntry(Locale('vi'), 'Tiếng Việt'), MapEntry(Locale('tr'), 'Türkçe'), + MapEntry(Locale('uk'), 'Українська'), ]; const fallbackLocale = Locale('en'); const localeDir = 'assets/translations';