diff --git a/.flutter b/.flutter index 68415ad..c519ee9 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 +Subproject commit c519ee916eaeb88923e67befb89c0f1dabfa83e6 diff --git a/assets/screenshots/1.apps.png b/assets/screenshots/1.apps.png index ecd7aa8..ba9ec46 100644 Binary files a/assets/screenshots/1.apps.png and b/assets/screenshots/1.apps.png differ diff --git a/assets/screenshots/2.dark_theme.png b/assets/screenshots/2.dark_theme.png index 699bbf7..77b3d96 100644 Binary files a/assets/screenshots/2.dark_theme.png and b/assets/screenshots/2.dark_theme.png differ diff --git a/assets/screenshots/3.material_you.png b/assets/screenshots/3.material_you.png index 04b0ae1..63abd7d 100644 Binary files a/assets/screenshots/3.material_you.png and b/assets/screenshots/3.material_you.png differ diff --git a/assets/screenshots/4.app.png b/assets/screenshots/4.app.png index 55ae937..f26ab8a 100644 Binary files a/assets/screenshots/4.app.png and b/assets/screenshots/4.app.png differ diff --git a/assets/screenshots/5.app_opts.png b/assets/screenshots/5.app_opts.png index 5e5e7fe..8a3bd88 100644 Binary files a/assets/screenshots/5.app_opts.png and b/assets/screenshots/5.app_opts.png differ diff --git a/assets/screenshots/6.app_webview.png b/assets/screenshots/6.app_webview.png index 636eaf2..10c8f5e 100644 Binary files a/assets/screenshots/6.app_webview.png and b/assets/screenshots/6.app_webview.png differ diff --git a/assets/translations/de.json b/assets/translations/de.json index 567fe90..f478db1 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -31,7 +31,7 @@ "xIsTrackOnly": "{} ist nur zur Nachverfolgung", "source": "Quelle", "app": "App", - "appsFromSourceAreTrackOnly": "Apps aus dieser Quelle sind nur zur Versionsüberwachung.", + "appsFromSourceAreTrackOnly": "Apps aus dieser Quelle sind nur zur Nachverfolgung.", "youPickedTrackOnly": "Du hast die Option „Nur nachverfolgen“ gewählt.", "trackOnlyAppDescription": "Die App wird auf neue verfügbare Versionen überwacht, aber Obtainium wird sie nicht herunterladen oder installieren.", "cancelled": "Abgebrochen", @@ -85,7 +85,7 @@ "filter": "Filter", "filterApps": "Apps filtern", "appName": "App-Name", - "author": "Autor:in", + "author": "Herausgebende", "upToDateApps": "Apps mit aktuellster Version", "nonInstalledApps": "Nicht installierte Apps", "importExport": "Import/Export", @@ -109,15 +109,15 @@ "selectURL": "URL auswählen", "selectURLs": "URLs auswählen", "pick": "Auswählen", - "theme": "Theme", + "theme": "Design", "dark": "Dunkel", "light": "Hell", "followSystem": "Systemstandard", "followSystemThemeExplanation": "Das Abrufen des Systemdesigns ist unter Android < 10 nur mit Hilfe von Drittanbieterapps möglich", "useBlackTheme": "Rein schwarzen Hintergrund verwenden", "appSortBy": "App sortieren nach", - "authorName": "Autor:in/Name", - "nameAuthor": "Name/Autor:in", + "authorName": "Hrsg./Name", + "nameAuthor": "Name/Hrsg.", "asAdded": "Wie hinzugefügt", "appSortOrder": "App sortieren nach", "ascending": "Aufsteigend", @@ -152,9 +152,9 @@ "xWasUpdatedToY": "{} wurde auf {} aktualisiert.", "xWasNotUpdatedToY": "Die Aktualisierung von {} auf {} ist fehlgeschlagen.", "errorCheckingUpdates": "Fehler beim Prüfen auf Aktualisierungen", - "errorCheckingUpdatesNotifDescription": "Weist darauf hin, dass die Prüfung der Hintergrundaktualisierung fehlgeschlagen ist", + "errorCheckingUpdatesNotifDescription": "Benachrichtigt, wenn die Prüfung der Hintergrundaktualisierung fehlgeschlagen ist", "appsRemoved": "Apps entfernt", - "appsRemovedNotifDescription": "Weist darauf hin, dass eine oder mehrere Apps aufgrund von Fehlern beim Laden entfernt wurden", + "appsRemovedNotifDescription": "Benachrichtigt, wenn eine oder mehrere Apps aufgrund von Fehlern beim Laden entfernt wurden", "xWasRemovedDueToErrorY": "{} wurde aufgrund des folgenden Fehlers entfernt: {}", "completeAppInstallation": "App-Installation abschließen", "obtainiumMustBeOpenToInstallApps": "Obtainium muss geöffnet sein, um Apps zu installieren", @@ -244,10 +244,10 @@ "filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck\nfiltern", "customLinkFilterRegex": "Benutzerdefinierter APK-Linkfilter durch regulären Ausdruck (Standard '.apk$')", "appsPossiblyUpdated": "App-Aktualisierungen wurden versucht", - "appsPossiblyUpdatedNotifDescription": "Benachrichtigt, dass Aktualisierungen für eine oder mehrere Apps möglicherweise im Hintergrund durchgeführt wurden", - "xWasPossiblyUpdatedToY": "{} wurde möglicherweise aktualisiert auf {}.", + "appsPossiblyUpdatedNotifDescription": "Benachrichtigt, dass möglicherweise eine oder mehrere Apps im Hintergrund aktualisiert wurden", + "xWasPossiblyUpdatedToY": "{} wurde eventuell auf Version {} aktualisiert.", "enableBackgroundUpdates": "Hintergrundaktualisierungen aktivieren", - "backgroundUpdateReqsExplanation": "Die Hintergrundaktualisierung ist möglicherweise nicht für alle Apps möglich.", + "backgroundUpdateReqsExplanation": "Die Hintergrundaktualisierung ist unter Umständen nicht für alle Apps möglich.", "backgroundUpdateLimitsExplanation": "Der Erfolg einer Hintergrundinstallation kann nur festgestellt werden, wenn Obtainium geöffnet wird.", "verifyLatestTag": "„Latest“-Tag überprüfen", "intermediateLinkRegex": "Filter für einen „Zwischen“-Link, der zuerst besucht werden soll", @@ -292,8 +292,8 @@ "parallelDownloads": "Parallele Downloads erlauben", "useShizuku": "Shizuku oder Sui zur Installation verwenden", "shizukuBinderNotFound": "Kompatibler Shizuku-Dienst wurde nicht gefunden", - "shizukuOld": "Alte Shizuku-Version (< 11) - aktualisieren Sie sie", - "shizukuOldAndroidWithADB": "Shizuku läuft auf Android < 8.1 mit ADB - aktualisieren Sie Android oder verwenden Sie stattdessen Sui", + "shizukuOld": "Veraltete Shizuku-Version (< 11) - bitte aktualisiere sie", + "shizukuOldAndroidWithADB": "Shizuku läuft auf Android < 8.1 mit ADB - aktualisiere die Android-Version oder verwende stattdessen Sui", "shizukuPretendToBeGooglePlay": "(Mittels Shizuku) Google Play als Installationsquelle registrieren", "useSystemFont": "Systemschriftart verwenden", "useVersionCodeAsOSVersion": "Versionscode (versionCode) als erkannte Version vom Betriebssystem verwenden", diff --git a/assets/translations/ko.json b/assets/translations/ko.json new file mode 100644 index 0000000..de433fb --- /dev/null +++ b/assets/translations/ko.json @@ -0,0 +1,382 @@ +{ + "invalidURLForSource": "유효한 {} 앱 URL이 아닙니다", + "noReleaseFound": "적절한 릴리스를 찾을 수 없습니다", + "noVersionFound": "릴리스 버전을 결정할 수 없습니다", + "urlMatchesNoSource": "URL이 알려진 소스와 일치하지 않습니다", + "cantInstallOlderVersion": "앱의 이전 버전을 설치할 수 없습니다", + "appIdMismatch": "다운로드된 패키지 ID가 기존 앱 ID와 일치하지 않습니다", + "functionNotImplemented": "이 클래스는 이 기능을 구현하지 않았습니다", + "placeholder": "플레이스홀더", + "someErrors": "일부 오류가 발생했습니다", + "unexpectedError": "예기치 않은 오류", + "ok": "확인", + "and": "그리고", + "githubPATLabel": "GitHub 개인 액세스 토큰 (속도 제한 증가)", + "includePrereleases": "사전 릴리스 포함", + "fallbackToOlderReleases": "이전 릴리스로 대체", + "filterReleaseTitlesByRegEx": "정규 표현식으로 릴리스 제목 필터링", + "invalidRegEx": "잘못된 정규 표현식", + "noDescription": "설명 없음", + "cancel": "취소", + "continue": "계속", + "requiredInBrackets": "(필수)", + "dropdownNoOptsError": "오류: 드롭다운에는 최소 하나의 옵션이 있어야 합니다", + "colour": "색상", + "standard": "표준", + "custom": "사용자 정의", + "useMaterialYou": "Material You 사용", + "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\nURL 및 타사 가져오기 방법에만 영향을 미칩니다.", + "importErrors": "가져오기 오류", + "importedXOfYApps": "{}개의 앱 중 {}개 가져오기 완료.", + "followingURLsHadErrors": "다음 URL에 오류가 있었습니다:", + "selectURL": "URL 선택", + "selectURLs": "URL 선택", + "pick": "선택", + "theme": "테마", + "dark": "다크", + "light": "라이트", + "followSystem": "시스템 따르기", + "followSystemThemeExplanation": "시스템 테마를 따르려면 타사 애플리케이션을 사용해야 합니다", + "useBlackTheme": "순수한 검은색 다크 테마 사용", + "appSortBy": "앱 정렬 기준", + "authorName": "저자/이름", + "nameAuthor": "이름/저자", + "asAdded": "추가된 순서대로", + "appSortOrder": "앱 정렬 순서", + "ascending": "오름차순", + "descending": "내림차순", + "bgUpdateCheckInterval": "백그라운드 업데이트 확인 간격", + "neverManualOnly": "절대 - 수동만", + "appearance": "외관", + "showWebInAppView": "앱 보기에서 소스 웹페이지 표시", + "pinUpdates": "앱 보기 상단에 업데이트 고정", + "updates": "업데이트", + "sourceSpecific": "소스별", + "appSource": "앱 소스", + "noLogs": "로그 없음", + "appLogs": "앱 로그", + "close": "닫기", + "share": "공유", + "appNotFound": "앱을 찾을 수 없습니다", + "obtainiumExportHyphenatedLowercase": "obtainium-export", + "pickAnAPK": "APK 선택", + "appHasMoreThanOnePackage": "{}에는 둘 이상의 패키지가 있습니다:", + "deviceSupportsXArch": "장치는 {} CPU 아키텍처를 지원합니다.", + "deviceSupportsFollowingArchs": "장치는 다음 CPU 아키텍처를 지원합니다:", + "warning": "경고", + "sourceIsXButPackageFromYPrompt": "앱 소스는 '{}'이지만 릴리스 패키지는 '{}'에서 제공됩니다. 계속하시겠습니까?", + "updatesAvailable": "업데이트 가능", + "updatesAvailableNotifDescription": "Obtainium이 추적하는 하나 이상의 앱에 대한 업데이트가 있음을 사용자에게 알립니다", + "noNewUpdates": "새로운 업데이트가 없습니다.", + "xHasAnUpdate": "{}에 업데이트가 있습니다.", + "appsUpdated": "앱 업데이트됨", + "appsNotUpdated": "앱 업데이트 실패", + "appsUpdatedNotifDescription": "백그라운드에서 하나 이상의 앱에 대한 업데이트가 적용되었음을 사용자에게 알립니다", + "xWasUpdatedToY": "{}가 {}로 업데이트되었습니다.", + "xWasNotUpdatedToY": "{}를 {}로 업데이트하지 못했습니다.", + "errorCheckingUpdates": "업데이트 확인 오류", + "errorCheckingUpdatesNotifDescription": "백그라운드 업데이트 확인이 실패할 때 표시되는 알림", + "appsRemoved": "앱 제거됨", + "appsRemovedNotifDescription": "로드 중 오류로 인해 하나 이상의 앱이 제거되었음을 사용자에게 알립니다", + "xWasRemovedDueToErrorY": "{}가 다음 오류로 인해 제거되었습니다: {}", + "completeAppInstallation": "앱 설치 완료", + "obtainiumMustBeOpenToInstallApps": "앱을 설치하려면 Obtainium이 열려 있어야 합니다", + "completeAppInstallationNotifDescription": "앱 설치를 완료하려면 Obtainium으로 돌아가도록 사용자에게 요청합니다", + "checkingForUpdates": "업데이트 확인 중", + "checkingForUpdatesNotifDescription": "업데이트 확인 시 나타나는 일시적인 알림", + "pleaseAllowInstallPerm": "Obtainium이 앱을 설치할 수 있도록 허용해 주세요", + "trackOnly": "추적 전용", + "errorWithHttpStatusCode": "오류 {}", + "versionCorrectionDisabled": "버전 수정 비활성화됨 (플러그인이 작동하지 않는 것 같습니다)", + "unknown": "알 수 없음", + "none": "없음", + "never": "절대", + "latestVersionX": "최신: {}", + "installedVersionX": "설치됨: {}", + "lastUpdateCheckX": "마지막 업데이트 확인: {}", + "remove": "제거", + "yesMarkUpdated": "예, 업데이트됨으로 표시", + "fdroid": "F-Droid 공식", + "appIdOrName": "앱 ID 또는 이름", + "appId": "앱 ID", + "appWithIdOrNameNotFound": "해당 ID 또는 이름의 앱을 찾을 수 없습니다", + "reposHaveMultipleApps": "저장소에는 여러 앱이 포함될 수 있습니다", + "fdroidThirdPartyRepo": "F-Droid 타사 저장소", + "install": "설치", + "markInstalled": "설치됨으로 표시", + "update": "업데이트", + "markUpdated": "업데이트됨으로 표시", + "additionalOptions": "추가 옵션", + "disableVersionDetection": "버전 감지 비활성화", + "noVersionDetectionExplanation": "이 옵션은 버전 감지가 올바르게 작동하지 않는 앱에만 사용해야 합니다.", + "downloadingX": "{} 다운로드 중", + "downloadX": "{} 다운로드", + "downloadedX": "{} 다운로드 완료", + "releaseAsset": "릴리스 자산", + "downloadNotifDescription": "앱 다운로드 진행 상황을 사용자에게 알립니다", + "noAPKFound": "APK를 찾을 수 없습니다", + "noVersionDetection": "버전 감지 없음", + "categorize": "분류", + "categories": "카테고리", + "category": "카테고리", + "noCategory": "카테고리 없음", + "noCategories": "카테고리 없음", + "deleteCategoriesQuestion": "카테고리를 삭제하시겠습니까?", + "categoryDeleteWarning": "삭제된 카테고리의 모든 앱은 미분류로 설정됩니다.", + "addCategory": "카테고리 추가", + "label": "레이블", + "language": "언어", + "copiedToClipboard": "클립보드에 복사됨", + "storagePermissionDenied": "저장소 권한 거부됨", + "selectedCategorizeWarning": "이 작업은 선택한 앱의 기존 카테고리 설정을 대체합니다.", + "filterAPKsByRegEx": "정규 표현식으로 APK 필터링", + "removeFromObtainium": "Obtainium에서 제거", + "uninstallFromDevice": "장치에서 제거", + "onlyWorksWithNonVersionDetectApps": "버전 감지가 비활성화된 앱에만 작동합니다.", + "releaseDateAsVersion": "릴리스 날짜를 버전 문자열로 사용", + "releaseTitleAsVersion": "릴리스 제목을 버전 문자열로 사용", + "releaseDateAsVersionExplanation": "이 옵션은 버전 감지가 올바르게 작동하지 않지만 릴리스 날짜가 있는 앱에만 사용해야 합니다.", + "changes": "변경 사항", + "releaseDate": "릴리스 날짜", + "importFromURLsInFile": "파일의 URL에서 가져오기 (OPML과 같은)", + "versionDetectionExplanation": "OS에서 감지된 버전과 버전 문자열 조정", + "versionDetection": "버전 감지", + "standardVersionDetection": "표준 버전 감지", + "groupByCategory": "카테고리별 그룹화", + "autoApkFilterByArch": "가능한 경우 CPU 아키텍처별로 APK 필터링 시도", + "overrideSource": "소스 재정의", + "dontShowAgain": "다시 표시하지 않기", + "dontShowTrackOnlyWarnings": "'추적 전용' 경고 표시 안 함", + "dontShowAPKOriginWarnings": "APK 출처 경고 표시 안 함", + "moveNonInstalledAppsToBottom": "설치되지 않은 앱을 앱 보기 하단으로 이동", + "gitlabPATLabel": "GitLab 개인 액세스 토큰", + "about": "정보", + "requiresCredentialsInSettings": "{}는 추가 자격 증명이 필요합니다 (설정에서)", + "checkOnStart": "시작 시 업데이트 확인", + "tryInferAppIdFromCode": "소스 코드에서 앱 ID 추론 시도", + "removeOnExternalUninstall": "외부에서 제거된 앱 자동 제거", + "pickHighestVersionCode": "가장 높은 버전 코드 APK 자동 선택", + "checkUpdateOnDetailPage": "앱 세부 정보 페이지 열 때 업데이트 확인", + "disablePageTransitions": "페이지 전환 애니메이션 비활성화", + "reversePageTransitions": "페이지 전환 애니메이션 반전", + "minStarCount": "최소 별 개수", + "addInfoBelow": "아래에 이 정보를 추가하십시오.", + "addInfoInSettings": "설정에 이 정보를 추가하십시오.", + "githubSourceNote": "GitHub 속도 제한은 API 키를 사용하여 피할 수 있습니다.", + "sortByLastLinkSegment": "링크의 마지막 세그먼트로만 정렬", + "filterReleaseNotesByRegEx": "정규 표현식으로 릴리스 노트 필터링", + "customLinkFilterRegex": "정규 표현식으로 사용자 정의 APK 링크 필터링 (기본값 '.apk$')", + "appsPossiblyUpdated": "앱 업데이트 시도됨", + "appsPossiblyUpdatedNotifDescription": "백그라운드에서 하나 이상의 앱에 대한 업데이트가 잠재적으로 적용되었음을 사용자에게 알립니다", + "xWasPossiblyUpdatedToY": "{}가 {}로 업데이트되었을 수 있습니다.", + "enableBackgroundUpdates": "백그라운드 업데이트 활성화", + "backgroundUpdateReqsExplanation": "모든 앱에 대해 백그라운드 업데이트가 가능하지 않을 수 있습니다.", + "backgroundUpdateLimitsExplanation": "백그라운드 설치의 성공 여부는 Obtainium이 열릴 때만 확인할 수 있습니다.", + "verifyLatestTag": "'최신' 태그 확인", + "intermediateLinkRegex": "'중간' 링크 방문 필터", + "filterByLinkText": "링크 텍스트로 링크 필터링", + "intermediateLinkNotFound": "중간 링크를 찾을 수 없습니다", + "intermediateLink": "중간 링크", + "exemptFromBackgroundUpdates": "백그라운드 업데이트에서 제외 (활성화된 경우)", + "bgUpdatesOnWiFiOnly": "WiFi가 아닐 때 백그라운드 업데이트 비활성화", + "bgUpdatesWhileChargingOnly": "충전 중이 아닐 때 백그라운드 업데이트 비활성화", + "autoSelectHighestVersionCode": "가장 높은 versionCode APK 자동 선택", + "versionExtractionRegEx": "버전 문자열 추출 정규 표현식", + "trimVersionString": "정규 표현식으로 버전 문자열 자르기", + "matchGroupToUseForX": "\"{}\"에 사용할 일치 그룹", + "matchGroupToUse": "버전 문자열 추출 정규 표현식에 사용할 일치 그룹", + "highlightTouchTargets": "덜 명확한 터치 대상 강조", + "pickExportDir": "내보내기 디렉토리 선택", + "autoExportOnChanges": "변경 시 자동 내보내기", + "includeSettings": "설정 포함", + "filterVersionsByRegEx": "정규 표현식으로 버전 필터링", + "trySelectingSuggestedVersionCode": "제안된 versionCode APK 선택 시도", + "dontSortReleasesList": "API에서 릴리스 순서 유지", + "reverseSort": "정렬 반전", + "takeFirstLink": "첫 번째 링크 선택", + "skipSort": "정렬 건너뛰기", + "debugMenu": "디버그 메뉴", + "bgTaskStarted": "백그라운드 작업 시작됨 - 로그를 확인하세요.", + "runBgCheckNow": "지금 백그라운드 업데이트 확인 실행", + "versionExtractWholePage": "전체 페이지에 버전 문자열 추출 정규 표현식 적용", + "installing": "설치 중", + "skipUpdateNotifications": "업데이트 알림 건너뛰기", + "updatesAvailableNotifChannel": "업데이트 가능", + "appsUpdatedNotifChannel": "앱 업데이트됨", + "appsPossiblyUpdatedNotifChannel": "앱 업데이트 시도됨", + "errorCheckingUpdatesNotifChannel": "업데이트 확인 오류", + "appsRemovedNotifChannel": "앱 제거됨", + "downloadingXNotifChannel": "{} 다운로드 중", + "completeAppInstallationNotifChannel": "앱 설치 완료", + "checkingForUpdatesNotifChannel": "업데이트 확인 중", + "onlyCheckInstalledOrTrackOnlyApps": "설치된 앱과 추적 전용 앱만 업데이트 확인", + "supportFixedAPKURL": "고정 APK URL 지원", + "selectX": "{} 선택", + "parallelDownloads": "병렬 다운로드 허용", + "useShizuku": "Shizuku 또는 Sui를 사용하여 설치", + "shizukuBinderNotFound": "Shizuku 서비스가 실행 중이 아닙니다", + "shizukuOld": "오래된 Shizuku 버전 (<11) - 업데이트 필요", + "shizukuOldAndroidWithADB": "ADB로 Android < 8.1에서 실행 중인 Shizuku - Android를 업데이트하거나 대신 Sui를 사용하세요", + "shizukuPretendToBeGooglePlay": "설치 소스로 Google Play 설정 (Shizuku 사용 시)", + "useSystemFont": "시스템 글꼴 사용", + "useVersionCodeAsOSVersion": "앱 versionCode를 OS에서 감지된 버전으로 사용", + "requestHeader": "요청 헤더", + "useLatestAssetDateAsReleaseDate": "최신 자산 업로드를 릴리스 날짜로 사용", + "defaultPseudoVersioningMethod": "기본 의사 버전 관리 방법", + "partialAPKHash": "부분 APK 해시", + "APKLinkHash": "APK 링크 해시", + "directAPKLink": "직접 APK 링크", + "pseudoVersionInUse": "의사 버전 사용 중", + "installed": "설치됨", + "latest": "최신", + "invertRegEx": "정규 표현식 반전", + "note": "노트", + "selfHostedNote": "\"{}\" 드롭다운을 사용하여 소스의 자체 호스팅/사용자 정의 인스턴스에 도달할 수 있습니다.", + "badDownload": "APK를 구문 분석할 수 없습니다 (호환되지 않거나 부분 다운로드)", + "beforeNewInstallsShareToAppVerifier": "새 앱을 AppVerifier와 공유 (가능한 경우)", + "appVerifierInstructionToast": "AppVerifier에 공유한 후 준비가 되면 여기로 돌아오세요.", + "wiki": "도움말/위키", + "crowdsourcedConfigsLabel": "크라우드소싱 앱 구성 (자신의 책임 하에 사용)", + "crowdsourcedConfigsShort": "크라우드소싱 앱 구성", + "allowInsecure": "안전하지 않은 HTTP 요청 허용", + "stayOneVersionBehind": "최신 버전보다 한 버전 뒤에 머무르기", + "refreshBeforeDownload": "다운로드 전에 앱 세부 정보 새로 고침", + "tencentAppStore": "텐센트 앱 스토어", + "removeAppQuestion": { + "one": "앱을 제거하시겠습니까?", + "other": "앱을 제거하시겠습니까?" + }, + "tooManyRequestsTryAgainInMinutes": { + "one": "요청이 너무 많습니다 (속도 제한) - {}분 후에 다시 시도하세요", + "other": "요청이 너무 많습니다 (속도 제한) - {}분 후에 다시 시도하세요" + }, + "bgUpdateGotErrorRetryInMinutes": { + "one": "BG 업데이트 확인 중 {} 오류가 발생했습니다. {}분 후에 다시 확인을 예약합니다", + "other": "BG 업데이트 확인 중 {} 오류가 발생했습니다. {}분 후에 다시 확인을 예약합니다" + }, + "bgCheckFoundUpdatesWillNotifyIfNeeded": { + "one": "BG 업데이트 확인에서 {}개의 업데이트를 발견했습니다 - 필요 시 사용자에게 알립니다", + "other": "BG 업데이트 확인에서 {}개의 업데이트를 발견했습니다 - 필요 시 사용자에게 알립니다" + }, + "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": "{} 및 {}개의 앱이 업데이트되었습니다." + }, + "xAndNMoreUpdatesFailed": { + "one": "{} 및 1개의 앱 업데이트에 실패했습니다.", + "other": "{} 및 {}개의 앱 업데이트에 실패했습니다." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "{} 및 1개의 앱이 업데이트되었을 수 있습니다.", + "other": "{} 및 {}개의 앱이 업데이트되었을 수 있습니다." + }, + "apk": { + "one": "{} APK", + "other": "{} APK" + } +} diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index 810ef91..ab246e6 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -501,7 +501,7 @@ class GitHub extends AppSource { AppNames getAppNames(String standardUrl) { String temp = standardUrl.substring(standardUrl.indexOf('://') + 3); List names = temp.substring(temp.indexOf('/') + 1).split('/'); - return AppNames(names[0], names[1]); + return AppNames(names[0], names.sublist(1).join('/')); } Future>> searchCommon( diff --git a/lib/app_sources/gitlab.dart b/lib/app_sources/gitlab.dart index e8404a4..b47e891 100644 --- a/lib/app_sources/gitlab.dart +++ b/lib/app_sources/gitlab.dart @@ -54,7 +54,7 @@ class GitLab extends AppSource { @override String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) { RegExp standardUrlRegEx = RegExp( - '^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+', + '^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+(/[^/]+){1,20}', caseSensitive: false); RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { @@ -126,6 +126,8 @@ class GitLab extends AppSource { ) async { // Prepare request params var names = GitHub().getAppNames(standardUrl); + String projectUriComponent = + '${Uri.encodeComponent(names.author)}%2F${Uri.encodeComponent(names.name)}'; String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {}); String optionalAuth = (PAT != null) ? 'private_token=$PAT' : ''; @@ -133,7 +135,7 @@ class GitLab extends AppSource { // Get project ID Response res0 = await sourceRequest( - 'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}?$optionalAuth', + 'https://${hosts[0]}/api/v4/projects/$projectUriComponent?$optionalAuth', additionalSettings); if (res0.statusCode != 200) { throw getObtainiumHttpError(res0); @@ -145,7 +147,7 @@ class GitLab extends AppSource { // Request data from REST API Response res = await sourceRequest( - 'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/${trackOnly ? 'repository/tags' : 'releases'}?$optionalAuth', + 'https://${hosts[0]}/api/v4/projects/$projectUriComponent/${trackOnly ? 'repository/tags' : 'releases'}?$optionalAuth', additionalSettings); if (res.statusCode != 200) { throw getObtainiumHttpError(res); @@ -180,7 +182,7 @@ class GitLab extends AppSource { return APKDetails( e['tag_name'] ?? e['name'], getApkUrlsFromUrls(apkUrlsSet.toList()), - GitHub().getAppNames(standardUrl), + AppNames(names.author, names.name.split('/').last), releaseDate: releaseDate); }); if (apkDetailsList.isEmpty) { diff --git a/lib/main.dart b/lib/main.dart index 50ef0f2..db1c0be 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -44,7 +44,8 @@ List> supportedLocales = const [ MapEntry(Locale('da'), 'Dansk'), MapEntry(Locale('en', 'EO'), 'Esperanto'), // https://github.com/aissat/easy_localization/issues/220#issuecomment-846035493 - MapEntry(Locale('in'), 'Bahasa Indonesia') + MapEntry(Locale('in'), 'Bahasa Indonesia'), + MapEntry(Locale('ko'), '한국어'), ]; const fallbackLocale = Locale('en'); const localeDir = 'assets/translations'; @@ -244,6 +245,7 @@ class _ObtainiumState extends State { supportedLocales: context.supportedLocales, locale: context.locale, navigatorKey: globalNavigatorKey, + debugShowCheckedModeBanner: false, theme: ThemeData( useMaterial3: true, colorScheme: settingsProvider.theme == ThemeSettings.dark diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index a28a8a9..b787ab4 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -151,13 +151,15 @@ Future downloadFileWithRetry(String url, String fileName, {bool useExisting = true, Map? headers, int retries = 3, - bool allowInsecure = false}) async { + bool allowInsecure = false, + LogsProvider? logs}) async { try { return await downloadFile( url, fileName, fileNameHasExt, onProgress, destDir, useExisting: useExisting, headers: headers, - allowInsecure: allowInsecure); + allowInsecure: allowInsecure, + logs: logs); } catch (e) { if (retries > 0 && e is ClientException) { await Future.delayed(const Duration(seconds: 5)); @@ -166,7 +168,8 @@ Future downloadFileWithRetry(String url, String fileName, useExisting: useExisting, headers: headers, retries: (retries - 1), - allowInsecure: allowInsecure); + allowInsecure: allowInsecure, + logs: logs); } else { rethrow; } @@ -219,7 +222,8 @@ Future downloadFile(String url, String fileName, bool fileNameHasExt, Function? onProgress, String destDir, {bool useExisting = true, Map? headers, - bool allowInsecure = false}) async { + bool allowInsecure = false, + LogsProvider? logs}) async { // Send the initial request but cancel it as soon as you have the headers var reqHeaders = headers ?? {}; var req = Request('GET', Uri.parse(url)); @@ -280,6 +284,42 @@ Future downloadFile(String url, String fileName, bool fileNameHasExt, // Download to a '.temp' file (to distinguish btn. complete/incomplete files) File tempDownloadedFile = File('${downloadedFile.path}.part'); + // If there is already a temp file, a download may already be in progress - account for this (see #2073) + bool tempFileExists = tempDownloadedFile.existsSync(); + if (tempFileExists && useExisting) { + logs?.add( + 'Partial download exists - will wait: ${tempDownloadedFile.uri.pathSegments.last}'); + bool isDownloading = true; + int currentTempFileSize = await tempDownloadedFile.length(); + bool shouldReturn = false; + while (isDownloading) { + await Future.delayed(Duration(seconds: 7)); + if (tempDownloadedFile.existsSync()) { + int newTempFileSize = await tempDownloadedFile.length(); + if (newTempFileSize > currentTempFileSize) { + currentTempFileSize = newTempFileSize; + logs?.add( + 'Existing partial download still in progress: ${tempDownloadedFile.uri.pathSegments.last}'); + } else { + logs?.add( + 'Ignoring existing partial download: ${tempDownloadedFile.uri.pathSegments.last}'); + break; + } + } else { + shouldReturn = downloadedFile.existsSync(); + } + } + if (shouldReturn) { + logs?.add( + 'Existing partial download completed - not repeating: ${tempDownloadedFile.uri.pathSegments.last}'); + client.close(); + return downloadedFile; + } else { + logs?.add( + 'Existing partial download not in progress: ${tempDownloadedFile.uri.pathSegments.last}'); + } + } + // If the range feature is not available (or you need to start a ranged req from 0), // complete the already-started request, else cancel it and start a ranged request, // and open the file for writing in the appropriate mode @@ -419,9 +459,7 @@ class AppsProvider with ChangeNotifier { // Delete any partial APKs (if safe to do so) var cutoff = DateTime.now().subtract(const Duration(days: 7)); APKDir.listSync() - .where((element) => - element.path.endsWith('.part') || - element.statSync().modified.isBefore(cutoff)) + .where((element) => element.statSync().modified.isBefore(cutoff)) .forEach((partialApk) { if (!areDownloadsRunning()) { partialApk.delete(recursive: true); @@ -495,7 +533,8 @@ class AppsProvider with ChangeNotifier { prevProg = prog; }, APKDir.path, useExisting: useExisting, - allowInsecure: app.additionalSettings['allowInsecure'] == true); + allowInsecure: app.additionalSettings['allowInsecure'] == true, + logs: logs); // Set to 90 for remaining steps, will make null in 'finally' if (apps[app.id] != null) { apps[app.id]!.downloadProgress = -1; @@ -1124,7 +1163,8 @@ class AppsProvider with ChangeNotifier { forAPKDownload: fileUrl.key.endsWith('.apk') ? true : false), useExisting: false, - allowInsecure: app.additionalSettings['allowInsecure'] == true); + allowInsecure: app.additionalSettings['allowInsecure'] == true, + logs: logs); notificationsProvider .notify(DownloadedNotification(fileUrl.key, fileUrl.value)); } catch (e) { @@ -1414,8 +1454,10 @@ class AppsProvider with ChangeNotifier { app = getCorrectedInstallStatusAppIfPossible(app, info) ?? app; } if (!onlyIfExists || this.apps.containsKey(app.id)) { - File('${(await getAppsDir()).path}/${app.id}.json') - .writeAsStringSync(jsonEncode(app.toJson())); + String filePath = '${(await getAppsDir()).path}/${app.id}.json'; + File('$filePath.tmp') + .writeAsStringSync(jsonEncode(app.toJson())); // #2089 + File('$filePath.tmp').renameSync(filePath); } try { this.apps.update(app.id, diff --git a/pubspec.lock b/pubspec.lock index 1e4b647..b6a9e57 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -224,10 +224,10 @@ packages: dependency: transitive description: name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" device_info_plus: dependency: "direct main" description: @@ -256,10 +256,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 + sha256: "0f5239c7b8ab06c66440cfb0e9aa4b4640429c6668d5a42fe389c5de42220b12" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.0.7+1" easy_logger: dependency: transitive description: @@ -405,10 +405,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "31cd0885738e87c72d6f055564d37fabcdacee743b396b78c7636c169cac64f5" + sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c url: "https://pub.dev" source: hosted - version: "0.14.2" + version: "0.14.3" flutter_lints: dependency: "direct dev" description: @@ -524,10 +524,10 @@ packages: dependency: "direct main" description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_parser: dependency: transitive description: @@ -844,18 +844,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a + sha256: c59819dacc6669a1165d54d2735a9543f136f9b3cec94ca65cea6ab8dffc422e url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: bf808be89fe9dc467475e982c1db6c2faf3d2acf54d526cd5ec37d86c99dbd84 + sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" shared_preferences_foundation: dependency: transitive description: @@ -1155,10 +1155,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: d1ee28f44894cbabb1d94cc42f9980297f689ff844d067ec50ff88d86e27d63f + sha256: "5568f17a9c25c0fdd0737900fa1c2d1fee2d780bc212d9aec10c2d1f48ef0f59" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.3.1" webview_flutter_platform_interface: dependency: transitive description: @@ -1171,18 +1171,18 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "4adc14ea9a770cc9e2c8f1ac734536bd40e82615bd0fa6b94be10982de656cc7" + sha256: "8e0593559bfecd35eb1757d6907ed6b995a41ef82607d6113df897c2805ce6be" url: "https://pub.dev" source: hosted - version: "3.17.0" + version: "3.18.0" win32: dependency: transitive description: name: win32 - sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.10.1" win32_registry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 97de453..8c57967 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,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: 1.1.39+2296 +version: 1.1.40+2297 environment: sdk: ^3.6.0