환경 설정 > 저장 공간 정리: 작업 임시 파일 · 받다 만 다운로드 (.part 등) · 받다 만 AI 모델 · 업데이트 남은 파일 · 지난 기록 (진행 중인 것은 건너뜀). AI 자막 임시 폴더가 남던 것 수정
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>main
parent
61da57aea1
commit
d4ab2f87b7
|
|
@ -49,8 +49,10 @@
|
||||||
"AI 자막 중단: {0}": "AI subtitles stopped: {0}",
|
"AI 자막 중단: {0}": "AI subtitles stopped: {0}",
|
||||||
"AI 자막 취소: {0}": "AI subtitles cancelled: {0}",
|
"AI 자막 취소: {0}": "AI subtitles cancelled: {0}",
|
||||||
"AI 자막: {0}": "AI subtitles: {0}",
|
"AI 자막: {0}": "AI subtitles: {0}",
|
||||||
|
"AI 자막용 음성 (wav) · 자막 변환 사본 · 미리보기 그림 등 작업하다 남은 파일": "Leftovers from jobs: audio for AI subtitles (wav) · converted subtitle copies · preview images",
|
||||||
"AI 자막을 만들고 이어서 MKV 로 만듭니다": "makes AI subtitles and then makes the MKV",
|
"AI 자막을 만들고 이어서 MKV 로 만듭니다": "makes AI subtitles and then makes the MKV",
|
||||||
"AI 자막을 만듭니다": "makes AI subtitles",
|
"AI 자막을 만듭니다": "makes AI subtitles",
|
||||||
|
"AI 작업 중이라 이번에는 건너뜁니다": "Skipped this time because an AI job is running",
|
||||||
"API 키": "API key",
|
"API 키": "API key",
|
||||||
"API 키 (필수, 무료)": "API key (required, free)",
|
"API 키 (필수, 무료)": "API key (required, free)",
|
||||||
"AV1 영상은 이 기기에서 다시 인코딩할 수 없습니다. 코덱을 \"원본 유지\" 로 바꿔 MKV 를 만드세요. (PC 판은 됩니다)": "AV1 video cannot be re-encoded on this device. Change the codec to \"Keep original\" to make the MKV. (The PC version can.)",
|
"AV1 영상은 이 기기에서 다시 인코딩할 수 없습니다. 코덱을 \"원본 유지\" 로 바꿔 MKV 를 만드세요. (PC 판은 됩니다)": "AV1 video cannot be re-encoded on this device. Change the codec to \"Keep original\" to make the MKV. (The PC version can.)",
|
||||||
|
|
@ -82,6 +84,7 @@
|
||||||
"MKV 만들기": "Make MKV",
|
"MKV 만들기": "Make MKV",
|
||||||
"MKV 만들기 (대기열에 추가)": "Make MKV (add to queue)",
|
"MKV 만들기 (대기열에 추가)": "Make MKV (add to queue)",
|
||||||
"MKV 만들기 {0}개": "Make {0} MKVs",
|
"MKV 만들기 {0}개": "Make {0} MKVs",
|
||||||
|
"MKV 만들기 · AI 자막 작업 중이라 이번에는 건너뜁니다": "Skipped this time because a Make MKV · AI subtitle job is running",
|
||||||
"MKV 만들기 · AI 자막 작업이 끝난 뒤 업데이트하세요.": "Update after the Make MKV · AI subtitle jobs finish.",
|
"MKV 만들기 · AI 자막 작업이 끝난 뒤 업데이트하세요.": "Update after the Make MKV · AI subtitle jobs finish.",
|
||||||
"MKV 만들기 · 인코딩 · 음성 추출": "Make MKV · encoding · audio extraction",
|
"MKV 만들기 · 인코딩 · 음성 추출": "Make MKV · encoding · audio extraction",
|
||||||
"MKV 만들기 목록에 동영상 {0}개를 추가했습니다.": "Added {0} videos to the Make MKV list.",
|
"MKV 만들기 목록에 동영상 {0}개를 추가했습니다.": "Added {0} videos to the Make MKV list.",
|
||||||
|
|
@ -159,6 +162,7 @@
|
||||||
"{0}: 이미 이동 폴더에 있습니다": "{0}: already in the move folder",
|
"{0}: 이미 이동 폴더에 있습니다": "{0}: already in the move folder",
|
||||||
"{0}: 작업 중이라 옮기지 않았습니다": "{0}: not moved because it is being processed",
|
"{0}: 작업 중이라 옮기지 않았습니다": "{0}: not moved because it is being processed",
|
||||||
"{0}개": "{0}",
|
"{0}개": "{0}",
|
||||||
|
"{0}개 · {1}": "{0} · {1}",
|
||||||
"{0}개 · 주소 {1}개": "{0} · {1} links",
|
"{0}개 · 주소 {1}개": "{0} · {1} links",
|
||||||
"{0}개 선택": "{0} selected",
|
"{0}개 선택": "{0} selected",
|
||||||
"{0}개를 \"기타 즐겨찾기\" 에 가져왔습니다.": "Imported {0} into \"Other bookmarks\".",
|
"{0}개를 \"기타 즐겨찾기\" 에 가져왔습니다.": "Imported {0} into \"Other bookmarks\".",
|
||||||
|
|
@ -233,6 +237,7 @@
|
||||||
"내려받기 실패 ({0})": "Download failed ({0})",
|
"내려받기 실패 ({0})": "Download failed ({0})",
|
||||||
"내려받기 실패 ({0}): {1}": "Download failed ({0}): {1}",
|
"내려받기 실패 ({0}): {1}": "Download failed ({0}): {1}",
|
||||||
"내려받는 중": "Downloading",
|
"내려받는 중": "Downloading",
|
||||||
|
"내려받다 끊긴 모델 (.part) · 변환하다 남은 파일 (.tmp)": "Interrupted model downloads (.part) · leftovers from conversion (.tmp)",
|
||||||
"내려받은 Chrome 엔진 지우기": "Delete the downloaded Chrome engine",
|
"내려받은 Chrome 엔진 지우기": "Delete the downloaded Chrome engine",
|
||||||
"내보냈습니다: {0}": "Exported: {0}",
|
"내보냈습니다: {0}": "Exported: {0}",
|
||||||
"내용": "Content",
|
"내용": "Content",
|
||||||
|
|
@ -360,10 +365,14 @@
|
||||||
"바로 재생 (기본)": "Play right away (default)",
|
"바로 재생 (기본)": "Play right away (default)",
|
||||||
"바로 전 작업: {0}": "Previous job: {0}",
|
"바로 전 작업: {0}": "Previous job: {0}",
|
||||||
"받는 중": "Downloading",
|
"받는 중": "Downloading",
|
||||||
|
"받는 중이거나 일시정지한 다운로드가 있어 건너뜁니다 (이어받기가 깨지지 않게)": "Skipped because downloads are running or paused (so they can resume)",
|
||||||
"받는 중인 다운로드 {0}개": "{0} downloads in progress",
|
"받는 중인 다운로드 {0}개": "{0} downloads in progress",
|
||||||
"받는 중인 다운로드가 없습니다.": "Nothing is downloading.",
|
"받는 중인 다운로드가 없습니다.": "Nothing is downloading.",
|
||||||
"받는 크기: 약 {0}MB · 받은 파일 · AI 모델 · 설정은 그대로 둡니다": "Download size: about {0}MB · downloaded files · AI models · settings are kept",
|
"받는 크기: 약 {0}MB · 받은 파일 · AI 모델 · 설정은 그대로 둡니다": "Download size: about {0}MB · downloaded files · AI models · settings are kept",
|
||||||
|
"받다 만 AI 모델": "Unfinished AI models",
|
||||||
|
"받다 만 다운로드": "Unfinished downloads",
|
||||||
"받은 동영상을 편집 목록에 추가: {0}": "Added the received video to the edit list: {0}",
|
"받은 동영상을 편집 목록에 추가: {0}": "Added the received video to the edit list: {0}",
|
||||||
|
"받은 업데이트 · 필수 프로그램 · Chrome 엔진을 설치하고 남은 임시 파일": "Temporary files left from installing updates · required programs · the Chrome engine",
|
||||||
"받은 자막을 한국어로 번역해 함께 추가 (한국어 자막이 없을 때)": "Translate received subtitles to Korean and add them too (when there are no Korean subtitles)",
|
"받은 자막을 한국어로 번역해 함께 추가 (한국어 자막이 없을 때)": "Translate received subtitles to Korean and add them too (when there are no Korean subtitles)",
|
||||||
"받은 파일에 libcef.dll 이 없습니다.": "The downloaded file has no libcef.dll.",
|
"받은 파일에 libcef.dll 이 없습니다.": "The downloaded file has no libcef.dll.",
|
||||||
"받은 파일이 손상되었거나 다른 파일입니다 (SHA256 불일치). 설치를 중단했습니다.": "The downloaded file is corrupt or a different file (SHA256 mismatch). Installation was stopped.",
|
"받은 파일이 손상되었거나 다른 파일입니다 (SHA256 불일치). 설치를 중단했습니다.": "The downloaded file is corrupt or a different file (SHA256 mismatch). Installation was stopped.",
|
||||||
|
|
@ -480,6 +489,7 @@
|
||||||
"언어: 미지정": "Language: unspecified",
|
"언어: 미지정": "Language: unspecified",
|
||||||
"언어를 더하지 못했습니다: {0}": "Could not add the language: {0}",
|
"언어를 더하지 못했습니다: {0}": "Could not add the language: {0}",
|
||||||
"업데이트": "Update",
|
"업데이트": "Update",
|
||||||
|
"업데이트 · 설치하고 남은 파일": "Update · install leftovers",
|
||||||
"업데이트 실패: {0}": "Update failed: {0}",
|
"업데이트 실패: {0}": "Update failed: {0}",
|
||||||
"없음": "None",
|
"없음": "None",
|
||||||
"여기에 놓으면 MKV 만들기의 동영상 목록에 추가합니다": "Drop here to add to the Make MKV video list",
|
"여기에 놓으면 MKV 만들기의 동영상 목록에 추가합니다": "Drop here to add to the Make MKV video list",
|
||||||
|
|
@ -497,6 +507,7 @@
|
||||||
"예: Ctrl+Shift+X (Ctrl·Shift·Alt·Win + 영문자·숫자·F1~F12)": "e.g. Ctrl+Shift+X (Ctrl·Shift·Alt·Win + letter·digit·F1~F12)",
|
"예: Ctrl+Shift+X (Ctrl·Shift·Alt·Win + 영문자·숫자·F1~F12)": "e.g. Ctrl+Shift+X (Ctrl·Shift·Alt·Win + letter·digit·F1~F12)",
|
||||||
"예전 구조의 프로그램 파일 정리 ({0}개): {1}": "Cleaned up program files of the old layout ({0}): {1}",
|
"예전 구조의 프로그램 파일 정리 ({0}개): {1}": "Cleaned up program files of the old layout ({0}): {1}",
|
||||||
"오늘 받을 수 있는 자막 수를 모두 사용했습니다. ({0})": "You have used all of today's subtitle downloads. ({0})",
|
"오늘 받을 수 있는 자막 수를 모두 사용했습니다. ({0})": "You have used all of today's subtitle downloads. ({0})",
|
||||||
|
"오래된 작업 기록 파일 (app.log.1 등). 지금 쓰는 기록은 남깁니다": "Old job log files (app.log.1 etc.). The current log is kept",
|
||||||
"오른쪽 90°": "Right 90°",
|
"오른쪽 90°": "Right 90°",
|
||||||
"오픈 소스 라이선스": "Open source licenses",
|
"오픈 소스 라이선스": "Open source licenses",
|
||||||
"올린 이 {0}": "Uploaded by {0}",
|
"올린 이 {0}": "Uploaded by {0}",
|
||||||
|
|
@ -585,6 +596,7 @@
|
||||||
"일반": "General",
|
"일반": "General",
|
||||||
"일본어": "Japanese",
|
"일본어": "Japanese",
|
||||||
"일시정지": "Pause",
|
"일시정지": "Pause",
|
||||||
|
"임시 파일 · 남은 조각 정리": "Clean temporary files · leftovers",
|
||||||
"자동": "Auto",
|
"자동": "Auto",
|
||||||
"자동 감지": "Auto-detect",
|
"자동 감지": "Auto-detect",
|
||||||
"자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "Automatic installation is not possible here. Please download it from the page yourself.",
|
"자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "Automatic installation is not possible here. Please download it from the page yourself.",
|
||||||
|
|
@ -623,6 +635,7 @@
|
||||||
"작업": "Jobs",
|
"작업": "Jobs",
|
||||||
"작업 기록 보기": "Show job log",
|
"작업 기록 보기": "Show job log",
|
||||||
"작업 기록 숨기기": "Hide job log",
|
"작업 기록 숨기기": "Hide job log",
|
||||||
|
"작업 임시 파일": "Job temporary files",
|
||||||
"작업 중": "Working",
|
"작업 중": "Working",
|
||||||
"작업 중지: {0}{1}": "Job stopped: {0}{1}",
|
"작업 중지: {0}{1}": "Job stopped: {0}{1}",
|
||||||
"작업 취소": "Cancel job",
|
"작업 취소": "Cancel job",
|
||||||
|
|
@ -630,6 +643,7 @@
|
||||||
"작업 현황 닫기": "Close job status",
|
"작업 현황 닫기": "Close job status",
|
||||||
"작업 현황 보기 · 화면 분할 (Ctrl+Shift+J)": "Show job status · split screen (Ctrl+Shift+J)",
|
"작업 현황 보기 · 화면 분할 (Ctrl+Shift+J)": "Show job status · split screen (Ctrl+Shift+J)",
|
||||||
"작업 확인 (진행 중인 작업 없음)": "Job check (no jobs in progress)",
|
"작업 확인 (진행 중인 작업 없음)": "Job check (no jobs in progress)",
|
||||||
|
"작업하다 남은 임시 파일 · 받다 만 다운로드 (.part 등) · 받다 만 AI 모델 · 업데이트하고 남은 파일 · 지난 작업 기록을 찾아 지웁니다. 받은 동영상 · 만든 MKV · 자막은 지우지 않고, 진행 중인 작업 · 다운로드가 쓰는 것은 건너뜁니다": "Finds and deletes temporary files left from jobs · unfinished downloads (.part etc.) · unfinished AI models · update leftovers · old job logs. Downloaded videos · made MKVs · subtitles are not deleted, and anything used by running jobs · downloads is skipped",
|
||||||
"작은 용량": "Small size",
|
"작은 용량": "Small size",
|
||||||
"잘못된 varint (위치 {0})": "Invalid varint (position {0})",
|
"잘못된 varint (위치 {0})": "Invalid varint (position {0})",
|
||||||
"재개": "Resume",
|
"재개": "Resume",
|
||||||
|
|
@ -647,6 +661,8 @@
|
||||||
"저장": "Save",
|
"저장": "Save",
|
||||||
"저장 (MKV 반영)": "Save (apply to MKV)",
|
"저장 (MKV 반영)": "Save (apply to MKV)",
|
||||||
"저장 (MKV 반영) *": "Save (apply to MKV) *",
|
"저장 (MKV 반영) *": "Save (apply to MKV) *",
|
||||||
|
"저장 공간 정리": "Storage cleanup",
|
||||||
|
"저장 공간 정리: 파일 {0}개 · {1}": "Storage cleanup: {0} files · {1}",
|
||||||
"저장 실패: {0}": "Save failed: {0}",
|
"저장 실패: {0}": "Save failed: {0}",
|
||||||
"저장 위치": "Save locations",
|
"저장 위치": "Save locations",
|
||||||
"저장: {0}": "Save: {0}",
|
"저장: {0}": "Save: {0}",
|
||||||
|
|
@ -662,6 +678,12 @@
|
||||||
"전체 화면": "Full screen",
|
"전체 화면": "Full screen",
|
||||||
"전체 화면 (F)": "Full screen (F)",
|
"전체 화면 (F)": "Full screen (F)",
|
||||||
"점검": "Check",
|
"점검": "Check",
|
||||||
|
"정리": "Clean up",
|
||||||
|
"정리 ({0})": "Clean up ({0})",
|
||||||
|
"정리…": "Clean up…",
|
||||||
|
"정리할 파일을 찾는 중…": "Looking for files to clean up…",
|
||||||
|
"정리할 파일이 없었습니다.": "There was nothing to clean up.",
|
||||||
|
"정리했습니다: 파일 {0}개 · {1}": "Cleaned up: {0} files · {1}",
|
||||||
"정사각 1:1": "Square 1:1",
|
"정사각 1:1": "Square 1:1",
|
||||||
"정지": "Stop",
|
"정지": "Stop",
|
||||||
"정하지 않음: MKV 목록에서 [이동] 을 처음 누를 때 고릅니다": "Not set: you choose it the first time you press [Move] in the MKV list",
|
"정하지 않음: MKV 목록에서 [이동] 을 처음 누를 때 고릅니다": "Not set: you choose it the first time you press [Move] in the MKV list",
|
||||||
|
|
@ -702,6 +724,7 @@
|
||||||
"지금 쓰는 버전: v{0} → 새 버전: v{1}": "Current version: v{0} → new version: v{1}",
|
"지금 쓰는 버전: v{0} → 새 버전: v{1}": "Current version: v{0} → new version: v{1}",
|
||||||
"지금: {0}": "Now: {0}",
|
"지금: {0}": "Now: {0}",
|
||||||
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "The last run did not end normally (last check {0}:{1}). It has been noted in the job log.",
|
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "The last run did not end normally (last check {0}:{1}). It has been noted in the job log.",
|
||||||
|
"지난 작업 기록": "Old job logs",
|
||||||
"지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "Unsupported protobuf format (wire type {0}, position {1})",
|
"지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "Unsupported protobuf format (wire type {0}, position {1})",
|
||||||
"지원하지 않는 문자셋: {0}": "Unsupported charset: {0}",
|
"지원하지 않는 문자셋: {0}": "Unsupported charset: {0}",
|
||||||
"직접 입력": "Custom",
|
"직접 입력": "Custom",
|
||||||
|
|
@ -731,6 +754,7 @@
|
||||||
"추가: {0} (내장 자막 {1}개, 같은 폴더 자막 {2}개)": "Added: {0} ({1} embedded subtitles, {2} subtitles in the same folder)",
|
"추가: {0} (내장 자막 {1}개, 같은 폴더 자막 {2}개)": "Added: {0} ({1} embedded subtitles, {2} subtitles in the same folder)",
|
||||||
"출력: {0}": "Output: {0}",
|
"출력: {0}": "Output: {0}",
|
||||||
"취소": "Cancel",
|
"취소": "Cancel",
|
||||||
|
"취소 · 실패한 다운로드가 남긴 .part · .ytdl · .aria2 · 합치기 전 조각 파일 (jj_yt-dlp · jj_aria2)": ".part · .ytdl · .aria2 · unmerged fragments left by cancelled · failed downloads (jj_yt-dlp · jj_aria2)",
|
||||||
"취소됨": "Cancelled",
|
"취소됨": "Cancelled",
|
||||||
"취소했습니다.": "Cancelled.",
|
"취소했습니다.": "Cancelled.",
|
||||||
"켜져 있는 창에서 (기본)": "In the open window (default)",
|
"켜져 있는 창에서 (기본)": "In the open window (default)",
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,10 @@
|
||||||
"AI 자막 중단: {0}": "AI 字幕中断: {0}",
|
"AI 자막 중단: {0}": "AI 字幕中断: {0}",
|
||||||
"AI 자막 취소: {0}": "AI 字幕キャンセル: {0}",
|
"AI 자막 취소: {0}": "AI 字幕キャンセル: {0}",
|
||||||
"AI 자막: {0}": "AI 字幕: {0}",
|
"AI 자막: {0}": "AI 字幕: {0}",
|
||||||
|
"AI 자막용 음성 (wav) · 자막 변환 사본 · 미리보기 그림 등 작업하다 남은 파일": "作業の残り: AI 字幕用の音声(wav)・字幕変換のコピー・プレビュー画像など",
|
||||||
"AI 자막을 만들고 이어서 MKV 로 만듭니다": "AI 字幕を作成し、続けて MKV を作成します",
|
"AI 자막을 만들고 이어서 MKV 로 만듭니다": "AI 字幕を作成し、続けて MKV を作成します",
|
||||||
"AI 자막을 만듭니다": "AI 字幕を作成します",
|
"AI 자막을 만듭니다": "AI 字幕を作成します",
|
||||||
|
"AI 작업 중이라 이번에는 건너뜁니다": "AI 作業中のため今回はスキップします",
|
||||||
"API 키": "API キー",
|
"API 키": "API キー",
|
||||||
"API 키 (필수, 무료)": "API キー(必須・無料)",
|
"API 키 (필수, 무료)": "API キー(必須・無料)",
|
||||||
"AV1 영상은 이 기기에서 다시 인코딩할 수 없습니다. 코덱을 \"원본 유지\" 로 바꿔 MKV 를 만드세요. (PC 판은 됩니다)": "AV1 動画はこの端末では再エンコードできません。コーデックを「元のまま」に変えて MKV を作成してください。(PC 版はできます)",
|
"AV1 영상은 이 기기에서 다시 인코딩할 수 없습니다. 코덱을 \"원본 유지\" 로 바꿔 MKV 를 만드세요. (PC 판은 됩니다)": "AV1 動画はこの端末では再エンコードできません。コーデックを「元のまま」に変えて MKV を作成してください。(PC 版はできます)",
|
||||||
|
|
@ -82,6 +84,7 @@
|
||||||
"MKV 만들기": "MKV 作成",
|
"MKV 만들기": "MKV 作成",
|
||||||
"MKV 만들기 (대기열에 추가)": "MKV 作成(キューに追加)",
|
"MKV 만들기 (대기열에 추가)": "MKV 作成(キューに追加)",
|
||||||
"MKV 만들기 {0}개": "MKV 作成 {0}個",
|
"MKV 만들기 {0}개": "MKV 作成 {0}個",
|
||||||
|
"MKV 만들기 · AI 자막 작업 중이라 이번에는 건너뜁니다": "MKV 作成・AI 字幕の作業中のため今回はスキップします",
|
||||||
"MKV 만들기 · AI 자막 작업이 끝난 뒤 업데이트하세요.": "MKV 作成・AI 字幕の作業が終わってから更新してください。",
|
"MKV 만들기 · AI 자막 작업이 끝난 뒤 업데이트하세요.": "MKV 作成・AI 字幕の作業が終わってから更新してください。",
|
||||||
"MKV 만들기 · 인코딩 · 음성 추출": "MKV 作成・エンコード・音声抽出",
|
"MKV 만들기 · 인코딩 · 음성 추출": "MKV 作成・エンコード・音声抽出",
|
||||||
"MKV 만들기 목록에 동영상 {0}개를 추가했습니다.": "MKV 作成リストに動画を {0}個追加しました。",
|
"MKV 만들기 목록에 동영상 {0}개를 추가했습니다.": "MKV 作成リストに動画を {0}個追加しました。",
|
||||||
|
|
@ -159,6 +162,7 @@
|
||||||
"{0}: 이미 이동 폴더에 있습니다": "{0}: 既に移動フォルダーにあります",
|
"{0}: 이미 이동 폴더에 있습니다": "{0}: 既に移動フォルダーにあります",
|
||||||
"{0}: 작업 중이라 옮기지 않았습니다": "{0}: 作業中のため移動しませんでした",
|
"{0}: 작업 중이라 옮기지 않았습니다": "{0}: 作業中のため移動しませんでした",
|
||||||
"{0}개": "{0}個",
|
"{0}개": "{0}個",
|
||||||
|
"{0}개 · {1}": "{0}個・{1}",
|
||||||
"{0}개 · 주소 {1}개": "{0}個・URL {1}個",
|
"{0}개 · 주소 {1}개": "{0}個・URL {1}個",
|
||||||
"{0}개 선택": "{0}個選択",
|
"{0}개 선택": "{0}個選択",
|
||||||
"{0}개를 \"기타 즐겨찾기\" 에 가져왔습니다.": "{0}個を「その他のブックマーク」に取り込みました。",
|
"{0}개를 \"기타 즐겨찾기\" 에 가져왔습니다.": "{0}個を「その他のブックマーク」に取り込みました。",
|
||||||
|
|
@ -233,6 +237,7 @@
|
||||||
"내려받기 실패 ({0})": "ダウンロード失敗({0})",
|
"내려받기 실패 ({0})": "ダウンロード失敗({0})",
|
||||||
"내려받기 실패 ({0}): {1}": "ダウンロード失敗({0}): {1}",
|
"내려받기 실패 ({0}): {1}": "ダウンロード失敗({0}): {1}",
|
||||||
"내려받는 중": "ダウンロード中",
|
"내려받는 중": "ダウンロード中",
|
||||||
|
"내려받다 끊긴 모델 (.part) · 변환하다 남은 파일 (.tmp)": "ダウンロードが途切れたモデル(.part)・変換の残り(.tmp)",
|
||||||
"내려받은 Chrome 엔진 지우기": "ダウンロードした Chrome エンジンを削除",
|
"내려받은 Chrome 엔진 지우기": "ダウンロードした Chrome エンジンを削除",
|
||||||
"내보냈습니다: {0}": "書き出しました: {0}",
|
"내보냈습니다: {0}": "書き出しました: {0}",
|
||||||
"내용": "内容",
|
"내용": "内容",
|
||||||
|
|
@ -360,10 +365,14 @@
|
||||||
"바로 재생 (기본)": "すぐ再生(既定)",
|
"바로 재생 (기본)": "すぐ再生(既定)",
|
||||||
"바로 전 작업: {0}": "直前の作業: {0}",
|
"바로 전 작업: {0}": "直前の作業: {0}",
|
||||||
"받는 중": "ダウンロード中",
|
"받는 중": "ダウンロード中",
|
||||||
|
"받는 중이거나 일시정지한 다운로드가 있어 건너뜁니다 (이어받기가 깨지지 않게)": "ダウンロード中または一時停止中のものがあるためスキップします(再開できるように)",
|
||||||
"받는 중인 다운로드 {0}개": "ダウンロード中 {0}個",
|
"받는 중인 다운로드 {0}개": "ダウンロード中 {0}個",
|
||||||
"받는 중인 다운로드가 없습니다.": "ダウンロード中のものはありません。",
|
"받는 중인 다운로드가 없습니다.": "ダウンロード中のものはありません。",
|
||||||
"받는 크기: 약 {0}MB · 받은 파일 · AI 모델 · 설정은 그대로 둡니다": "ダウンロードサイズ: 約 {0}MB・ダウンロードしたファイル・AI モデル・設定はそのまま",
|
"받는 크기: 약 {0}MB · 받은 파일 · AI 모델 · 설정은 그대로 둡니다": "ダウンロードサイズ: 約 {0}MB・ダウンロードしたファイル・AI モデル・設定はそのまま",
|
||||||
|
"받다 만 AI 모델": "ダウンロード途中の AI モデル",
|
||||||
|
"받다 만 다운로드": "ダウンロード途中のファイル",
|
||||||
"받은 동영상을 편집 목록에 추가: {0}": "受け取った動画を編集リストに追加: {0}",
|
"받은 동영상을 편집 목록에 추가: {0}": "受け取った動画を編集リストに追加: {0}",
|
||||||
|
"받은 업데이트 · 필수 프로그램 · Chrome 엔진을 설치하고 남은 임시 파일": "更新・必須プログラム・Chrome エンジンのインストールで残った一時ファイル",
|
||||||
"받은 자막을 한국어로 번역해 함께 추가 (한국어 자막이 없을 때)": "受け取った字幕を韓国語に翻訳して一緒に追加(韓国語字幕がないとき)",
|
"받은 자막을 한국어로 번역해 함께 추가 (한국어 자막이 없을 때)": "受け取った字幕を韓国語に翻訳して一緒に追加(韓国語字幕がないとき)",
|
||||||
"받은 파일에 libcef.dll 이 없습니다.": "ダウンロードしたファイルに libcef.dll がありません。",
|
"받은 파일에 libcef.dll 이 없습니다.": "ダウンロードしたファイルに libcef.dll がありません。",
|
||||||
"받은 파일이 손상되었거나 다른 파일입니다 (SHA256 불일치). 설치를 중단했습니다.": "ダウンロードしたファイルが壊れているか別のファイルです(SHA256 不一致)。インストールを中止しました。",
|
"받은 파일이 손상되었거나 다른 파일입니다 (SHA256 불일치). 설치를 중단했습니다.": "ダウンロードしたファイルが壊れているか別のファイルです(SHA256 不一致)。インストールを中止しました。",
|
||||||
|
|
@ -480,6 +489,7 @@
|
||||||
"언어: 미지정": "言語: 未指定",
|
"언어: 미지정": "言語: 未指定",
|
||||||
"언어를 더하지 못했습니다: {0}": "言語を追加できませんでした: {0}",
|
"언어를 더하지 못했습니다: {0}": "言語を追加できませんでした: {0}",
|
||||||
"업데이트": "更新",
|
"업데이트": "更新",
|
||||||
|
"업데이트 · 설치하고 남은 파일": "更新・インストールの残り",
|
||||||
"업데이트 실패: {0}": "更新失敗: {0}",
|
"업데이트 실패: {0}": "更新失敗: {0}",
|
||||||
"없음": "なし",
|
"없음": "なし",
|
||||||
"여기에 놓으면 MKV 만들기의 동영상 목록에 추가합니다": "ここにドロップすると MKV 作成の動画リストに追加します",
|
"여기에 놓으면 MKV 만들기의 동영상 목록에 추가합니다": "ここにドロップすると MKV 作成の動画リストに追加します",
|
||||||
|
|
@ -497,6 +507,7 @@
|
||||||
"예: Ctrl+Shift+X (Ctrl·Shift·Alt·Win + 영문자·숫자·F1~F12)": "例: Ctrl+Shift+X(Ctrl・Shift・Alt・Win + 英字・数字・F1〜F12)",
|
"예: Ctrl+Shift+X (Ctrl·Shift·Alt·Win + 영문자·숫자·F1~F12)": "例: Ctrl+Shift+X(Ctrl・Shift・Alt・Win + 英字・数字・F1〜F12)",
|
||||||
"예전 구조의 프로그램 파일 정리 ({0}개): {1}": "旧構成のプログラムファイルを整理({0}個): {1}",
|
"예전 구조의 프로그램 파일 정리 ({0}개): {1}": "旧構成のプログラムファイルを整理({0}個): {1}",
|
||||||
"오늘 받을 수 있는 자막 수를 모두 사용했습니다. ({0})": "今日ダウンロードできる字幕数をすべて使いました。({0})",
|
"오늘 받을 수 있는 자막 수를 모두 사용했습니다. ({0})": "今日ダウンロードできる字幕数をすべて使いました。({0})",
|
||||||
|
"오래된 작업 기록 파일 (app.log.1 등). 지금 쓰는 기록은 남깁니다": "古い作業記録ファイル(app.log.1 など)。現在の記録は残します",
|
||||||
"오른쪽 90°": "右 90°",
|
"오른쪽 90°": "右 90°",
|
||||||
"오픈 소스 라이선스": "オープンソースライセンス",
|
"오픈 소스 라이선스": "オープンソースライセンス",
|
||||||
"올린 이 {0}": "投稿者 {0}",
|
"올린 이 {0}": "投稿者 {0}",
|
||||||
|
|
@ -585,6 +596,7 @@
|
||||||
"일반": "一般",
|
"일반": "一般",
|
||||||
"일본어": "日本語",
|
"일본어": "日本語",
|
||||||
"일시정지": "一時停止",
|
"일시정지": "一時停止",
|
||||||
|
"임시 파일 · 남은 조각 정리": "一時ファイル・残った断片を整理",
|
||||||
"자동": "自動",
|
"자동": "自動",
|
||||||
"자동 감지": "自動検出",
|
"자동 감지": "自動検出",
|
||||||
"자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "自動インストールできない場所です。ページから直接ダウンロードしてください。",
|
"자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "自動インストールできない場所です。ページから直接ダウンロードしてください。",
|
||||||
|
|
@ -623,6 +635,7 @@
|
||||||
"작업": "作業",
|
"작업": "作業",
|
||||||
"작업 기록 보기": "作業記録を表示",
|
"작업 기록 보기": "作業記録を表示",
|
||||||
"작업 기록 숨기기": "作業記録を隠す",
|
"작업 기록 숨기기": "作業記録を隠す",
|
||||||
|
"작업 임시 파일": "作業の一時ファイル",
|
||||||
"작업 중": "作業中",
|
"작업 중": "作業中",
|
||||||
"작업 중지: {0}{1}": "作業中止: {0}{1}",
|
"작업 중지: {0}{1}": "作業中止: {0}{1}",
|
||||||
"작업 취소": "作業をキャンセル",
|
"작업 취소": "作業をキャンセル",
|
||||||
|
|
@ -630,6 +643,7 @@
|
||||||
"작업 현황 닫기": "作業状況を閉じる",
|
"작업 현황 닫기": "作業状況を閉じる",
|
||||||
"작업 현황 보기 · 화면 분할 (Ctrl+Shift+J)": "作業状況を表示・画面分割(Ctrl+Shift+J)",
|
"작업 현황 보기 · 화면 분할 (Ctrl+Shift+J)": "作業状況を表示・画面分割(Ctrl+Shift+J)",
|
||||||
"작업 확인 (진행 중인 작업 없음)": "作業確認(進行中の作業なし)",
|
"작업 확인 (진행 중인 작업 없음)": "作業確認(進行中の作業なし)",
|
||||||
|
"작업하다 남은 임시 파일 · 받다 만 다운로드 (.part 등) · 받다 만 AI 모델 · 업데이트하고 남은 파일 · 지난 작업 기록을 찾아 지웁니다. 받은 동영상 · 만든 MKV · 자막은 지우지 않고, 진행 중인 작업 · 다운로드가 쓰는 것은 건너뜁니다": "作業で残った一時ファイル・ダウンロード途中のファイル(.part など)・途中の AI モデル・更新の残り・古い作業記録を探して削除します。ダウンロードした動画・作成した MKV・字幕は削除せず、進行中の作業・ダウンロードが使っているものはスキップします",
|
||||||
"작은 용량": "小さい容量",
|
"작은 용량": "小さい容量",
|
||||||
"잘못된 varint (위치 {0})": "不正な varint(位置 {0})",
|
"잘못된 varint (위치 {0})": "不正な varint(位置 {0})",
|
||||||
"재개": "再開",
|
"재개": "再開",
|
||||||
|
|
@ -647,6 +661,8 @@
|
||||||
"저장": "保存",
|
"저장": "保存",
|
||||||
"저장 (MKV 반영)": "保存(MKV に反映)",
|
"저장 (MKV 반영)": "保存(MKV に反映)",
|
||||||
"저장 (MKV 반영) *": "保存(MKV に反映)*",
|
"저장 (MKV 반영) *": "保存(MKV に反映)*",
|
||||||
|
"저장 공간 정리": "ストレージの整理",
|
||||||
|
"저장 공간 정리: 파일 {0}개 · {1}": "ストレージの整理: ファイル {0}個・{1}",
|
||||||
"저장 실패: {0}": "保存失敗: {0}",
|
"저장 실패: {0}": "保存失敗: {0}",
|
||||||
"저장 위치": "保存先",
|
"저장 위치": "保存先",
|
||||||
"저장: {0}": "保存: {0}",
|
"저장: {0}": "保存: {0}",
|
||||||
|
|
@ -662,6 +678,12 @@
|
||||||
"전체 화면": "全画面",
|
"전체 화면": "全画面",
|
||||||
"전체 화면 (F)": "全画面(F)",
|
"전체 화면 (F)": "全画面(F)",
|
||||||
"점검": "点検",
|
"점검": "点検",
|
||||||
|
"정리": "整理",
|
||||||
|
"정리 ({0})": "整理({0})",
|
||||||
|
"정리…": "整理…",
|
||||||
|
"정리할 파일을 찾는 중…": "整理するファイルを探しています…",
|
||||||
|
"정리할 파일이 없었습니다.": "整理するファイルはありませんでした。",
|
||||||
|
"정리했습니다: 파일 {0}개 · {1}": "整理しました: ファイル {0}個・{1}",
|
||||||
"정사각 1:1": "正方形 1:1",
|
"정사각 1:1": "正方形 1:1",
|
||||||
"정지": "停止",
|
"정지": "停止",
|
||||||
"정하지 않음: MKV 목록에서 [이동] 을 처음 누를 때 고릅니다": "未設定: MKV リストで[移動]を初めて押したときに選びます",
|
"정하지 않음: MKV 목록에서 [이동] 을 처음 누를 때 고릅니다": "未設定: MKV リストで[移動]を初めて押したときに選びます",
|
||||||
|
|
@ -702,6 +724,7 @@
|
||||||
"지금 쓰는 버전: v{0} → 새 버전: v{1}": "現在のバージョン: v{0} → 新しいバージョン: v{1}",
|
"지금 쓰는 버전: v{0} → 새 버전: v{1}": "現在のバージョン: v{0} → 新しいバージョン: v{1}",
|
||||||
"지금: {0}": "現在: {0}",
|
"지금: {0}": "現在: {0}",
|
||||||
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "前回の実行が正常に終了しませんでした(最終確認 {0}:{1})。作業記録に残しました。",
|
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "前回の実行が正常に終了しませんでした(最終確認 {0}:{1})。作業記録に残しました。",
|
||||||
|
"지난 작업 기록": "過去の作業記録",
|
||||||
"지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "対応していない protobuf 形式(wire type {0}、位置 {1})",
|
"지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "対応していない protobuf 形式(wire type {0}、位置 {1})",
|
||||||
"지원하지 않는 문자셋: {0}": "対応していない文字コード: {0}",
|
"지원하지 않는 문자셋: {0}": "対応していない文字コード: {0}",
|
||||||
"직접 입력": "直接入力",
|
"직접 입력": "直接入力",
|
||||||
|
|
@ -731,6 +754,7 @@
|
||||||
"추가: {0} (내장 자막 {1}개, 같은 폴더 자막 {2}개)": "追加: {0}(内蔵字幕 {1}個、同じフォルダーの字幕 {2}個)",
|
"추가: {0} (내장 자막 {1}개, 같은 폴더 자막 {2}개)": "追加: {0}(内蔵字幕 {1}個、同じフォルダーの字幕 {2}個)",
|
||||||
"출력: {0}": "出力: {0}",
|
"출력: {0}": "出力: {0}",
|
||||||
"취소": "キャンセル",
|
"취소": "キャンセル",
|
||||||
|
"취소 · 실패한 다운로드가 남긴 .part · .ytdl · .aria2 · 합치기 전 조각 파일 (jj_yt-dlp · jj_aria2)": "キャンセル・失敗したダウンロードが残した .part・.ytdl・.aria2・結合前の断片(jj_yt-dlp・jj_aria2)",
|
||||||
"취소됨": "キャンセル済み",
|
"취소됨": "キャンセル済み",
|
||||||
"취소했습니다.": "キャンセルしました。",
|
"취소했습니다.": "キャンセルしました。",
|
||||||
"켜져 있는 창에서 (기본)": "開いているウィンドウで(既定)",
|
"켜져 있는 창에서 (기본)": "開いているウィンドウで(既定)",
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,10 @@
|
||||||
"AI 자막 중단: {0}": "AI 字幕中断: {0}",
|
"AI 자막 중단: {0}": "AI 字幕中断: {0}",
|
||||||
"AI 자막 취소: {0}": "AI 字幕已取消: {0}",
|
"AI 자막 취소: {0}": "AI 字幕已取消: {0}",
|
||||||
"AI 자막: {0}": "AI 字幕: {0}",
|
"AI 자막: {0}": "AI 字幕: {0}",
|
||||||
|
"AI 자막용 음성 (wav) · 자막 변환 사본 · 미리보기 그림 등 작업하다 남은 파일": "任务残留: AI 字幕用音频(wav)· 字幕转换副本 · 预览图片等",
|
||||||
"AI 자막을 만들고 이어서 MKV 로 만듭니다": "生成 AI 字幕后接着制作 MKV",
|
"AI 자막을 만들고 이어서 MKV 로 만듭니다": "生成 AI 字幕后接着制作 MKV",
|
||||||
"AI 자막을 만듭니다": "生成 AI 字幕",
|
"AI 자막을 만듭니다": "生成 AI 字幕",
|
||||||
|
"AI 작업 중이라 이번에는 건너뜁니다": "正在进行 AI 任务,本次跳过",
|
||||||
"API 키": "API 密钥",
|
"API 키": "API 密钥",
|
||||||
"API 키 (필수, 무료)": "API 密钥(必填,免费)",
|
"API 키 (필수, 무료)": "API 密钥(必填,免费)",
|
||||||
"AV1 영상은 이 기기에서 다시 인코딩할 수 없습니다. 코덱을 \"원본 유지\" 로 바꿔 MKV 를 만드세요. (PC 판은 됩니다)": "此设备无法重新编码 AV1 视频。请将编解码器改为“保持原样”后制作 MKV。(PC 版可以)",
|
"AV1 영상은 이 기기에서 다시 인코딩할 수 없습니다. 코덱을 \"원본 유지\" 로 바꿔 MKV 를 만드세요. (PC 판은 됩니다)": "此设备无法重新编码 AV1 视频。请将编解码器改为“保持原样”后制作 MKV。(PC 版可以)",
|
||||||
|
|
@ -82,6 +84,7 @@
|
||||||
"MKV 만들기": "制作 MKV",
|
"MKV 만들기": "制作 MKV",
|
||||||
"MKV 만들기 (대기열에 추가)": "制作 MKV(加入队列)",
|
"MKV 만들기 (대기열에 추가)": "制作 MKV(加入队列)",
|
||||||
"MKV 만들기 {0}개": "制作 MKV {0} 个",
|
"MKV 만들기 {0}개": "制作 MKV {0} 个",
|
||||||
|
"MKV 만들기 · AI 자막 작업 중이라 이번에는 건너뜁니다": "正在制作 MKV · AI 字幕,本次跳过",
|
||||||
"MKV 만들기 · AI 자막 작업이 끝난 뒤 업데이트하세요.": "请在制作 MKV · AI 字幕任务完成后再更新。",
|
"MKV 만들기 · AI 자막 작업이 끝난 뒤 업데이트하세요.": "请在制作 MKV · AI 字幕任务完成后再更新。",
|
||||||
"MKV 만들기 · 인코딩 · 음성 추출": "制作 MKV · 编码 · 音频提取",
|
"MKV 만들기 · 인코딩 · 음성 추출": "制作 MKV · 编码 · 音频提取",
|
||||||
"MKV 만들기 목록에 동영상 {0}개를 추가했습니다.": "已向制作 MKV 列表添加 {0} 个视频。",
|
"MKV 만들기 목록에 동영상 {0}개를 추가했습니다.": "已向制作 MKV 列表添加 {0} 个视频。",
|
||||||
|
|
@ -159,6 +162,7 @@
|
||||||
"{0}: 이미 이동 폴더에 있습니다": "{0}: 已在移动文件夹中",
|
"{0}: 이미 이동 폴더에 있습니다": "{0}: 已在移动文件夹中",
|
||||||
"{0}: 작업 중이라 옮기지 않았습니다": "{0}: 正在处理,未移动",
|
"{0}: 작업 중이라 옮기지 않았습니다": "{0}: 正在处理,未移动",
|
||||||
"{0}개": "{0} 个",
|
"{0}개": "{0} 个",
|
||||||
|
"{0}개 · {1}": "{0} 个 · {1}",
|
||||||
"{0}개 · 주소 {1}개": "{0} 个 · 地址 {1} 个",
|
"{0}개 · 주소 {1}개": "{0} 个 · 地址 {1} 个",
|
||||||
"{0}개 선택": "已选 {0} 个",
|
"{0}개 선택": "已选 {0} 个",
|
||||||
"{0}개를 \"기타 즐겨찾기\" 에 가져왔습니다.": "已将 {0} 个导入“其他书签”。",
|
"{0}개를 \"기타 즐겨찾기\" 에 가져왔습니다.": "已将 {0} 个导入“其他书签”。",
|
||||||
|
|
@ -233,6 +237,7 @@
|
||||||
"내려받기 실패 ({0})": "下载失败({0})",
|
"내려받기 실패 ({0})": "下载失败({0})",
|
||||||
"내려받기 실패 ({0}): {1}": "下载失败({0}): {1}",
|
"내려받기 실패 ({0}): {1}": "下载失败({0}): {1}",
|
||||||
"내려받는 중": "正在下载",
|
"내려받는 중": "正在下载",
|
||||||
|
"내려받다 끊긴 모델 (.part) · 변환하다 남은 파일 (.tmp)": "下载中断的模型(.part)· 转换残留(.tmp)",
|
||||||
"내려받은 Chrome 엔진 지우기": "删除已下载的 Chrome 引擎",
|
"내려받은 Chrome 엔진 지우기": "删除已下载的 Chrome 引擎",
|
||||||
"내보냈습니다: {0}": "已导出: {0}",
|
"내보냈습니다: {0}": "已导出: {0}",
|
||||||
"내용": "内容",
|
"내용": "内容",
|
||||||
|
|
@ -360,10 +365,14 @@
|
||||||
"바로 재생 (기본)": "立即播放(默认)",
|
"바로 재생 (기본)": "立即播放(默认)",
|
||||||
"바로 전 작업: {0}": "上一个任务: {0}",
|
"바로 전 작업: {0}": "上一个任务: {0}",
|
||||||
"받는 중": "正在下载",
|
"받는 중": "正在下载",
|
||||||
|
"받는 중이거나 일시정지한 다운로드가 있어 건너뜁니다 (이어받기가 깨지지 않게)": "有正在下载或已暂停的下载,已跳过(以便继续下载)",
|
||||||
"받는 중인 다운로드 {0}개": "正在下载 {0} 个",
|
"받는 중인 다운로드 {0}개": "正在下载 {0} 个",
|
||||||
"받는 중인 다운로드가 없습니다.": "没有正在进行的下载。",
|
"받는 중인 다운로드가 없습니다.": "没有正在进行的下载。",
|
||||||
"받는 크기: 약 {0}MB · 받은 파일 · AI 모델 · 설정은 그대로 둡니다": "下载大小: 约 {0}MB · 已下载的文件 · AI 模型 · 设置均保留",
|
"받는 크기: 약 {0}MB · 받은 파일 · AI 모델 · 설정은 그대로 둡니다": "下载大小: 约 {0}MB · 已下载的文件 · AI 模型 · 设置均保留",
|
||||||
|
"받다 만 AI 모델": "未下载完的 AI 模型",
|
||||||
|
"받다 만 다운로드": "未完成的下载",
|
||||||
"받은 동영상을 편집 목록에 추가: {0}": "已将收到的视频加入编辑列表: {0}",
|
"받은 동영상을 편집 목록에 추가: {0}": "已将收到的视频加入编辑列表: {0}",
|
||||||
|
"받은 업데이트 · 필수 프로그램 · Chrome 엔진을 설치하고 남은 임시 파일": "安装更新 · 必需程序 · Chrome 引擎后残留的临时文件",
|
||||||
"받은 자막을 한국어로 번역해 함께 추가 (한국어 자막이 없을 때)": "将收到的字幕翻译成韩语并一并添加(没有韩语字幕时)",
|
"받은 자막을 한국어로 번역해 함께 추가 (한국어 자막이 없을 때)": "将收到的字幕翻译成韩语并一并添加(没有韩语字幕时)",
|
||||||
"받은 파일에 libcef.dll 이 없습니다.": "下载的文件中没有 libcef.dll。",
|
"받은 파일에 libcef.dll 이 없습니다.": "下载的文件中没有 libcef.dll。",
|
||||||
"받은 파일이 손상되었거나 다른 파일입니다 (SHA256 불일치). 설치를 중단했습니다.": "下载的文件已损坏或不是正确的文件(SHA256 不匹配)。已停止安装。",
|
"받은 파일이 손상되었거나 다른 파일입니다 (SHA256 불일치). 설치를 중단했습니다.": "下载的文件已损坏或不是正确的文件(SHA256 不匹配)。已停止安装。",
|
||||||
|
|
@ -480,6 +489,7 @@
|
||||||
"언어: 미지정": "语言: 未指定",
|
"언어: 미지정": "语言: 未指定",
|
||||||
"언어를 더하지 못했습니다: {0}": "未能添加语言: {0}",
|
"언어를 더하지 못했습니다: {0}": "未能添加语言: {0}",
|
||||||
"업데이트": "更新",
|
"업데이트": "更新",
|
||||||
|
"업데이트 · 설치하고 남은 파일": "更新 · 安装残留",
|
||||||
"업데이트 실패: {0}": "更新失败: {0}",
|
"업데이트 실패: {0}": "更新失败: {0}",
|
||||||
"없음": "无",
|
"없음": "无",
|
||||||
"여기에 놓으면 MKV 만들기의 동영상 목록에 추가합니다": "拖放到此处即可加入制作 MKV 的视频列表",
|
"여기에 놓으면 MKV 만들기의 동영상 목록에 추가합니다": "拖放到此处即可加入制作 MKV 的视频列表",
|
||||||
|
|
@ -497,6 +507,7 @@
|
||||||
"예: Ctrl+Shift+X (Ctrl·Shift·Alt·Win + 영문자·숫자·F1~F12)": "例: Ctrl+Shift+X(Ctrl·Shift·Alt·Win + 字母·数字·F1~F12)",
|
"예: Ctrl+Shift+X (Ctrl·Shift·Alt·Win + 영문자·숫자·F1~F12)": "例: Ctrl+Shift+X(Ctrl·Shift·Alt·Win + 字母·数字·F1~F12)",
|
||||||
"예전 구조의 프로그램 파일 정리 ({0}개): {1}": "已清理旧结构的程序文件({0} 个): {1}",
|
"예전 구조의 프로그램 파일 정리 ({0}개): {1}": "已清理旧结构的程序文件({0} 个): {1}",
|
||||||
"오늘 받을 수 있는 자막 수를 모두 사용했습니다. ({0})": "今天可下载的字幕数已用完。({0})",
|
"오늘 받을 수 있는 자막 수를 모두 사용했습니다. ({0})": "今天可下载的字幕数已用完。({0})",
|
||||||
|
"오래된 작업 기록 파일 (app.log.1 등). 지금 쓰는 기록은 남깁니다": "旧的任务记录文件(app.log.1 等)。当前记录会保留",
|
||||||
"오른쪽 90°": "向右 90°",
|
"오른쪽 90°": "向右 90°",
|
||||||
"오픈 소스 라이선스": "开源许可",
|
"오픈 소스 라이선스": "开源许可",
|
||||||
"올린 이 {0}": "上传者 {0}",
|
"올린 이 {0}": "上传者 {0}",
|
||||||
|
|
@ -585,6 +596,7 @@
|
||||||
"일반": "常规",
|
"일반": "常规",
|
||||||
"일본어": "日语",
|
"일본어": "日语",
|
||||||
"일시정지": "暂停",
|
"일시정지": "暂停",
|
||||||
|
"임시 파일 · 남은 조각 정리": "清理临时文件 · 残留碎片",
|
||||||
"자동": "自动",
|
"자동": "自动",
|
||||||
"자동 감지": "自动检测",
|
"자동 감지": "自动检测",
|
||||||
"자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "此位置无法自动安装。请从页面手动下载。",
|
"자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "此位置无法自动安装。请从页面手动下载。",
|
||||||
|
|
@ -623,6 +635,7 @@
|
||||||
"작업": "任务",
|
"작업": "任务",
|
||||||
"작업 기록 보기": "显示任务记录",
|
"작업 기록 보기": "显示任务记录",
|
||||||
"작업 기록 숨기기": "隐藏任务记录",
|
"작업 기록 숨기기": "隐藏任务记录",
|
||||||
|
"작업 임시 파일": "任务临时文件",
|
||||||
"작업 중": "处理中",
|
"작업 중": "处理中",
|
||||||
"작업 중지: {0}{1}": "任务已停止: {0}{1}",
|
"작업 중지: {0}{1}": "任务已停止: {0}{1}",
|
||||||
"작업 취소": "取消任务",
|
"작업 취소": "取消任务",
|
||||||
|
|
@ -630,6 +643,7 @@
|
||||||
"작업 현황 닫기": "关闭任务状态",
|
"작업 현황 닫기": "关闭任务状态",
|
||||||
"작업 현황 보기 · 화면 분할 (Ctrl+Shift+J)": "显示任务状态 · 分屏(Ctrl+Shift+J)",
|
"작업 현황 보기 · 화면 분할 (Ctrl+Shift+J)": "显示任务状态 · 分屏(Ctrl+Shift+J)",
|
||||||
"작업 확인 (진행 중인 작업 없음)": "任务检查(没有进行中的任务)",
|
"작업 확인 (진행 중인 작업 없음)": "任务检查(没有进行中的任务)",
|
||||||
|
"작업하다 남은 임시 파일 · 받다 만 다운로드 (.part 등) · 받다 만 AI 모델 · 업데이트하고 남은 파일 · 지난 작업 기록을 찾아 지웁니다. 받은 동영상 · 만든 MKV · 자막은 지우지 않고, 진행 중인 작업 · 다운로드가 쓰는 것은 건너뜁니다": "查找并删除任务残留的临时文件 · 未完成的下载(.part 等)· 未下载完的 AI 模型 · 更新残留 · 旧任务记录。不会删除已下载的视频 · 制作的 MKV · 字幕,正在进行的任务 · 下载所用的文件会跳过",
|
||||||
"작은 용량": "小体积",
|
"작은 용량": "小体积",
|
||||||
"잘못된 varint (위치 {0})": "无效的 varint(位置 {0})",
|
"잘못된 varint (위치 {0})": "无效的 varint(位置 {0})",
|
||||||
"재개": "继续",
|
"재개": "继续",
|
||||||
|
|
@ -647,6 +661,8 @@
|
||||||
"저장": "保存",
|
"저장": "保存",
|
||||||
"저장 (MKV 반영)": "保存(应用到 MKV)",
|
"저장 (MKV 반영)": "保存(应用到 MKV)",
|
||||||
"저장 (MKV 반영) *": "保存(应用到 MKV)*",
|
"저장 (MKV 반영) *": "保存(应用到 MKV)*",
|
||||||
|
"저장 공간 정리": "存储空间清理",
|
||||||
|
"저장 공간 정리: 파일 {0}개 · {1}": "存储空间清理: 文件 {0} 个 · {1}",
|
||||||
"저장 실패: {0}": "保存失败: {0}",
|
"저장 실패: {0}": "保存失败: {0}",
|
||||||
"저장 위치": "保存位置",
|
"저장 위치": "保存位置",
|
||||||
"저장: {0}": "保存: {0}",
|
"저장: {0}": "保存: {0}",
|
||||||
|
|
@ -662,6 +678,12 @@
|
||||||
"전체 화면": "全屏",
|
"전체 화면": "全屏",
|
||||||
"전체 화면 (F)": "全屏(F)",
|
"전체 화면 (F)": "全屏(F)",
|
||||||
"점검": "检查",
|
"점검": "检查",
|
||||||
|
"정리": "清理",
|
||||||
|
"정리 ({0})": "清理({0})",
|
||||||
|
"정리…": "清理…",
|
||||||
|
"정리할 파일을 찾는 중…": "正在查找可清理的文件…",
|
||||||
|
"정리할 파일이 없었습니다.": "没有可清理的文件。",
|
||||||
|
"정리했습니다: 파일 {0}개 · {1}": "已清理: 文件 {0} 个 · {1}",
|
||||||
"정사각 1:1": "正方形 1:1",
|
"정사각 1:1": "正方形 1:1",
|
||||||
"정지": "停止",
|
"정지": "停止",
|
||||||
"정하지 않음: MKV 목록에서 [이동] 을 처음 누를 때 고릅니다": "未设置: 首次在 MKV 列表中点击[移动]时选择",
|
"정하지 않음: MKV 목록에서 [이동] 을 처음 누를 때 고릅니다": "未设置: 首次在 MKV 列表中点击[移动]时选择",
|
||||||
|
|
@ -702,6 +724,7 @@
|
||||||
"지금 쓰는 버전: v{0} → 새 버전: v{1}": "当前版本: v{0} → 新版本: v{1}",
|
"지금 쓰는 버전: v{0} → 새 버전: v{1}": "当前版本: v{0} → 新版本: v{1}",
|
||||||
"지금: {0}": "当前: {0}",
|
"지금: {0}": "当前: {0}",
|
||||||
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "上次运行未正常结束(最后检查 {0}:{1})。已记录在任务记录中。",
|
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "上次运行未正常结束(最后检查 {0}:{1})。已记录在任务记录中。",
|
||||||
|
"지난 작업 기록": "旧任务记录",
|
||||||
"지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "不支持的 protobuf 格式(wire type {0},位置 {1})",
|
"지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "不支持的 protobuf 格式(wire type {0},位置 {1})",
|
||||||
"지원하지 않는 문자셋: {0}": "不支持的字符集: {0}",
|
"지원하지 않는 문자셋: {0}": "不支持的字符集: {0}",
|
||||||
"직접 입력": "手动输入",
|
"직접 입력": "手动输入",
|
||||||
|
|
@ -731,6 +754,7 @@
|
||||||
"추가: {0} (내장 자막 {1}개, 같은 폴더 자막 {2}개)": "已添加: {0}(内嵌字幕 {1} 个,同文件夹字幕 {2} 个)",
|
"추가: {0} (내장 자막 {1}개, 같은 폴더 자막 {2}개)": "已添加: {0}(内嵌字幕 {1} 个,同文件夹字幕 {2} 个)",
|
||||||
"출력: {0}": "输出: {0}",
|
"출력: {0}": "输出: {0}",
|
||||||
"취소": "取消",
|
"취소": "取消",
|
||||||
|
"취소 · 실패한 다운로드가 남긴 .part · .ytdl · .aria2 · 합치기 전 조각 파일 (jj_yt-dlp · jj_aria2)": "取消 · 失败的下载留下的 .part · .ytdl · .aria2 · 合并前的碎片文件(jj_yt-dlp · jj_aria2)",
|
||||||
"취소됨": "已取消",
|
"취소됨": "已取消",
|
||||||
"취소했습니다.": "已取消。",
|
"취소했습니다.": "已取消。",
|
||||||
"켜져 있는 창에서 (기본)": "在已打开的窗口中(默认)",
|
"켜져 있는 창에서 (기본)": "在已打开的窗口中(默认)",
|
||||||
|
|
|
||||||
|
|
@ -885,7 +885,12 @@ class AppController extends ChangeNotifier {
|
||||||
..message = e is MediaToolException ? e.message : '$e';
|
..message = e is MediaToolException ? e.message : '$e';
|
||||||
_log(trf('AI 자막 실패: {0}\n{1}', [v.fileName, v.message]));
|
_log(trf('AI 자막 실패: {0}\n{1}', [v.fileName, v.message]));
|
||||||
} finally {
|
} finally {
|
||||||
await storage.delete(wav);
|
// 작업 폴더째 지운다 (음성인식이 만드는 변환 사본 audio.wav.wav 도 함께)
|
||||||
|
try {
|
||||||
|
await Directory(tmp).delete(recursive: true);
|
||||||
|
} catch (_) {
|
||||||
|
await storage.delete(wav);
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import '../l10n/tr.dart';
|
||||||
|
import 'app_controller.dart';
|
||||||
|
import 'download_manager.dart';
|
||||||
|
|
||||||
|
/// 정리할 파일 · 폴더 하나
|
||||||
|
class CleanupItem {
|
||||||
|
final String path;
|
||||||
|
final int bytes;
|
||||||
|
final bool isDir;
|
||||||
|
const CleanupItem(this.path, this.bytes, {this.isDir = false});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 정리 묶음 (작업 임시 파일 · 받다 만 다운로드 …)
|
||||||
|
class CleanupGroup {
|
||||||
|
final String id;
|
||||||
|
final String title;
|
||||||
|
final String hint;
|
||||||
|
final List<CleanupItem> items;
|
||||||
|
|
||||||
|
/// 작업 중이라 이번에는 건너뛰는 이유 (있으면 지우지 않음)
|
||||||
|
final String? skipped;
|
||||||
|
CleanupGroup(this.id, this.title, this.hint, this.items, {this.skipped});
|
||||||
|
|
||||||
|
int get bytes => items.fold(0, (a, i) => a + i.bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 저장 공간 정리 (환경 설정 > 저장 공간 정리): 작업하다 남은 임시 파일 · 받다 만 다운로드 조각 (.part 등) ·
|
||||||
|
/// 받다 만 AI 모델 · 업데이트 · 설치하고 남은 파일 · 지난 작업 기록을 찾아 지운다.
|
||||||
|
/// 진행 중인 작업 · 다운로드가 쓰는 곳은 건너뛴다 (이어받기 · 작업이 깨지지 않게).
|
||||||
|
class Cleaner {
|
||||||
|
final AppController c;
|
||||||
|
final DownloadManager? downloads;
|
||||||
|
|
||||||
|
/// 앱 설정 폴더 (테스트에서 바꿔 끼움)
|
||||||
|
final String? dataDir;
|
||||||
|
|
||||||
|
/// 시스템 임시 폴더 (Windows 의 업데이트 · 도구 설치 남은 파일. 테스트에서 바꿔 끼움)
|
||||||
|
final String? systemTemp;
|
||||||
|
|
||||||
|
Cleaner(this.c, {this.downloads, this.dataDir, this.systemTemp});
|
||||||
|
|
||||||
|
/// 받다 만 다운로드 조각: yt-dlp (.part · .ytdl · .part-Frag · .temp. · 합치기 전 영상.f137.mp4) · aria2 (.aria2)
|
||||||
|
static final downloadLeftover =
|
||||||
|
RegExp(r'\.part$|\.part-frag\d+|\.ytdl$|\.temp\.|\.f\d+\.[a-z0-9]+$|\.aria2$', caseSensitive: false);
|
||||||
|
|
||||||
|
static int _size(FileSystemEntity e) {
|
||||||
|
try {
|
||||||
|
if (e is File) return e.lengthSync();
|
||||||
|
if (e is Directory) {
|
||||||
|
var n = 0;
|
||||||
|
for (final f in e.listSync(recursive: true, followLinks: false)) {
|
||||||
|
if (f is File) n += f.lengthSync();
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FileSystemEntity> _list(String dir, {bool recursive = false}) {
|
||||||
|
try {
|
||||||
|
final d = Directory(dir);
|
||||||
|
if (!d.existsSync()) return const [];
|
||||||
|
return d.listSync(recursive: recursive, followLinks: false);
|
||||||
|
} catch (_) {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CleanupItem _item(FileSystemEntity e) => CleanupItem(e.path, _size(e), isDir: e is Directory);
|
||||||
|
|
||||||
|
/// 정리할 것 찾기
|
||||||
|
Future<List<CleanupGroup>> scan() async {
|
||||||
|
final working = c.busy;
|
||||||
|
final downloading = (downloads?.activeCount ?? 0) > 0;
|
||||||
|
final data = dataDir ?? (await getApplicationSupportDirectory()).path;
|
||||||
|
final groups = <CleanupGroup>[];
|
||||||
|
|
||||||
|
// 1. 작업 임시 파일: 앱 임시 폴더 전체 + 설정 파일을 저장하다 남은 이름.12345.tmp
|
||||||
|
final tmpDir = await c.services.storage.tempDirectory();
|
||||||
|
groups.add(CleanupGroup(
|
||||||
|
'work',
|
||||||
|
tr('작업 임시 파일'),
|
||||||
|
tr('AI 자막용 음성 (wav) · 자막 변환 사본 · 미리보기 그림 등 작업하다 남은 파일'),
|
||||||
|
[
|
||||||
|
for (final e in _list(tmpDir)) _item(e),
|
||||||
|
for (final e in _list(data))
|
||||||
|
if (e is File && RegExp(r'\.\d+\.tmp$').hasMatch(e.path)) _item(e),
|
||||||
|
],
|
||||||
|
skipped: working ? tr('MKV 만들기 · AI 자막 작업 중이라 이번에는 건너뜁니다') : null,
|
||||||
|
));
|
||||||
|
|
||||||
|
// 2. 받다 만 다운로드 조각
|
||||||
|
final s = c.settings;
|
||||||
|
groups.add(CleanupGroup(
|
||||||
|
'download',
|
||||||
|
tr('받다 만 다운로드'),
|
||||||
|
tr('취소 · 실패한 다운로드가 남긴 .part · .ytdl · .aria2 · 합치기 전 조각 파일 (jj_yt-dlp · jj_aria2)'),
|
||||||
|
[
|
||||||
|
for (final dir in {s.ytDlpDir, s.aria2Dir})
|
||||||
|
for (final e in _list(dir, recursive: true))
|
||||||
|
if (e is File && downloadLeftover.hasMatch(p.basename(e.path))) _item(e),
|
||||||
|
],
|
||||||
|
skipped: downloading ? tr('받는 중이거나 일시정지한 다운로드가 있어 건너뜁니다 (이어받기가 깨지지 않게)') : null,
|
||||||
|
));
|
||||||
|
|
||||||
|
// 3. 받다 만 AI 모델 (.part) · 모델 변환하다 남은 .tmp
|
||||||
|
final models = await c.services.models.directory();
|
||||||
|
groups.add(CleanupGroup(
|
||||||
|
'models',
|
||||||
|
tr('받다 만 AI 모델'),
|
||||||
|
tr('내려받다 끊긴 모델 (.part) · 변환하다 남은 파일 (.tmp)'),
|
||||||
|
[
|
||||||
|
for (final e in _list(models, recursive: true))
|
||||||
|
if (e is File && (e.path.endsWith('.part') || e.path.endsWith('.tmp'))) _item(e),
|
||||||
|
],
|
||||||
|
skipped: working ? tr('AI 작업 중이라 이번에는 건너뜁니다') : null,
|
||||||
|
));
|
||||||
|
|
||||||
|
// 4. 업데이트 · 도구 설치하고 남은 파일 (Windows 임시 폴더)
|
||||||
|
final sysTmp = systemTemp ?? (Platform.isWindows ? Directory.systemTemp.path : null);
|
||||||
|
if (sysTmp != null) {
|
||||||
|
final mine = RegExp(r'^(jj_mkvmaker_update|jj_cef_|jj_tool_|jj_[\w.-]+\.download$|jj_mkvmaker_update\.(ps1|log)$)');
|
||||||
|
groups.add(CleanupGroup(
|
||||||
|
'update',
|
||||||
|
tr('업데이트 · 설치하고 남은 파일'),
|
||||||
|
tr('받은 업데이트 · 필수 프로그램 · Chrome 엔진을 설치하고 남은 임시 파일'),
|
||||||
|
[
|
||||||
|
for (final e in _list(sysTmp))
|
||||||
|
if (mine.hasMatch(p.basename(e.path))) _item(e),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 지난 작업 기록 (지금 쓰는 app.log 는 남김)
|
||||||
|
final log = c.logFile;
|
||||||
|
if (log != null) {
|
||||||
|
groups.add(CleanupGroup(
|
||||||
|
'logs',
|
||||||
|
tr('지난 작업 기록'),
|
||||||
|
tr('오래된 작업 기록 파일 (app.log.1 등). 지금 쓰는 기록은 남깁니다'),
|
||||||
|
[
|
||||||
|
for (final e in _list(p.dirname(log)))
|
||||||
|
if (e is File && !p.equals(e.path, log)) _item(e),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 고른 묶음 지우기 (건너뛴 묶음은 지우지 않음). (지운 개수, 비운 용량)
|
||||||
|
Future<(int, int)> clean(Iterable<CleanupGroup> groups) async {
|
||||||
|
var n = 0, bytes = 0;
|
||||||
|
for (final g in groups) {
|
||||||
|
if (g.skipped != null) continue;
|
||||||
|
for (final i in g.items) {
|
||||||
|
try {
|
||||||
|
if (i.isDir) {
|
||||||
|
await Directory(i.path).delete(recursive: true);
|
||||||
|
} else {
|
||||||
|
await File(i.path).delete();
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
bytes += i.bytes;
|
||||||
|
} catch (_) {
|
||||||
|
// 쓰는 중이거나 지울 수 없는 파일은 그대로
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (n, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "12.3 MB" · "850 KB"
|
||||||
|
static String sizeText(int bytes) {
|
||||||
|
if (bytes >= 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
|
||||||
|
if (bytes >= 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||||||
|
if (bytes >= 1024) return '${(bytes / 1024).round()} KB';
|
||||||
|
return '$bytes B';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../app/app_controller.dart';
|
||||||
|
import '../app/cleanup.dart';
|
||||||
|
import '../app/download_manager.dart';
|
||||||
|
import '../l10n/tr.dart';
|
||||||
|
import 'theme.dart';
|
||||||
|
|
||||||
|
/// 저장 공간 정리: 찾은 묶음별 크기를 보여 주고, 고른 것만 지운다
|
||||||
|
Future<void> showCleanup(BuildContext context, AppController c, DownloadManager? downloads) async {
|
||||||
|
final cleaner = Cleaner(c, downloads: downloads);
|
||||||
|
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||||
|
final result = await showDialog<(int, int)>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => _CleanupDialog(cleaner: cleaner),
|
||||||
|
);
|
||||||
|
if (result == null) return;
|
||||||
|
final (n, bytes) = result;
|
||||||
|
c.note(trf('저장 공간 정리: 파일 {0}개 · {1}', [n, Cleaner.sizeText(bytes)]));
|
||||||
|
messenger?.showSnackBar(SnackBar(
|
||||||
|
content: Text(n == 0 ? tr('정리할 파일이 없었습니다.') : trf('정리했습니다: 파일 {0}개 · {1}', [n, Cleaner.sizeText(bytes)]))));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CleanupDialog extends StatefulWidget {
|
||||||
|
final Cleaner cleaner;
|
||||||
|
const _CleanupDialog({required this.cleaner});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_CleanupDialog> createState() => _CleanupDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CleanupDialogState extends State<_CleanupDialog> {
|
||||||
|
List<CleanupGroup>? _groups;
|
||||||
|
final _picked = <String>{};
|
||||||
|
bool _cleaning = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
widget.cleaner.scan().then((g) {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_groups = g;
|
||||||
|
// 처음에는 지울 수 있고 무언가 있는 묶음을 모두 고른 상태로
|
||||||
|
_picked.addAll([for (final x in g) if (x.skipped == null && x.items.isNotEmpty) x.id]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int get _total => [
|
||||||
|
for (final g in _groups ?? const <CleanupGroup>[])
|
||||||
|
if (_picked.contains(g.id)) g.bytes,
|
||||||
|
].fold(0, (a, b) => a + b);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final groups = _groups;
|
||||||
|
return AlertDialog(
|
||||||
|
title: Row(children: [
|
||||||
|
const Icon(Icons.cleaning_services_outlined, color: JjColors.accent),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(tr('저장 공간 정리')),
|
||||||
|
]),
|
||||||
|
content: SizedBox(
|
||||||
|
width: 560,
|
||||||
|
child: groups == null
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Row(children: [
|
||||||
|
const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(tr('정리할 파일을 찾는 중…')),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
: ListView(shrinkWrap: true, children: [
|
||||||
|
for (final g in groups)
|
||||||
|
CheckboxListTile(
|
||||||
|
value: g.skipped == null && _picked.contains(g.id),
|
||||||
|
onChanged: g.skipped != null || g.items.isEmpty || _cleaning
|
||||||
|
? null
|
||||||
|
: (v) => setState(() => v! ? _picked.add(g.id) : _picked.remove(g.id)),
|
||||||
|
title: Row(children: [
|
||||||
|
Expanded(child: Text(g.title)),
|
||||||
|
Text(
|
||||||
|
g.items.isEmpty ? tr('없음') : trf('{0}개 · {1}', [g.items.length, Cleaner.sizeText(g.bytes)]),
|
||||||
|
style: const TextStyle(fontSize: 13, color: JjColors.textDim),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
subtitle: Text(g.skipped ?? g.hint,
|
||||||
|
style: TextStyle(fontSize: 12, color: g.skipped != null ? Colors.amber : JjColors.textDim)),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: _cleaning ? null : () => Navigator.pop(context), child: Text(tr('닫기'))),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: groups == null || _picked.isEmpty || _total == 0 && !_hasItems || _cleaning
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
setState(() => _cleaning = true);
|
||||||
|
final r = await widget.cleaner.clean(groups.where((g) => _picked.contains(g.id)));
|
||||||
|
if (context.mounted) Navigator.pop(context, r);
|
||||||
|
},
|
||||||
|
icon: _cleaning
|
||||||
|
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
|
||||||
|
: const Icon(Icons.delete_sweep_outlined, size: 18),
|
||||||
|
label: Text(groups == null ? tr('정리') : trf('정리 ({0})', [Cleaner.sizeText(_total)])),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _hasItems => (_groups ?? const []).any((g) => _picked.contains(g.id) && g.items.isNotEmpty);
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ import 'folder_picker.dart';
|
||||||
import 'language_settings.dart';
|
import 'language_settings.dart';
|
||||||
import 'app_actions.dart';
|
import 'app_actions.dart';
|
||||||
import 'cef_setup.dart';
|
import 'cef_setup.dart';
|
||||||
|
import 'cleanup_dialog.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
import '../l10n/tr.dart';
|
import '../l10n/tr.dart';
|
||||||
|
|
||||||
|
|
@ -652,6 +653,20 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
_group('cleanup', Icons.cleaning_services_outlined, tr('저장 공간 정리'), [
|
||||||
|
ListTile(
|
||||||
|
title: Text(tr('임시 파일 · 남은 조각 정리')),
|
||||||
|
subtitle: Text(tr('작업하다 남은 임시 파일 · 받다 만 다운로드 (.part 등) · 받다 만 AI 모델 · '
|
||||||
|
'업데이트하고 남은 파일 · 지난 작업 기록을 찾아 지웁니다. '
|
||||||
|
'받은 동영상 · 만든 MKV · 자막은 지우지 않고, 진행 중인 작업 · 다운로드가 쓰는 것은 건너뜁니다')),
|
||||||
|
isThreeLine: true,
|
||||||
|
trailing: FilledButton.icon(
|
||||||
|
onPressed: () => showCleanup(context, c, AppScope.maybeOf(context)?.downloads),
|
||||||
|
icon: const Icon(Icons.cleaning_services_outlined, size: 18),
|
||||||
|
label: Text(tr('정리…')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
_group('about', Icons.info_outline, tr('프로그램 정보'), [
|
_group('about', Icons.info_outline, tr('프로그램 정보'), [
|
||||||
if (desk) ...[
|
if (desk) ...[
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -693,7 +708,9 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||||
/// 묶음 이름 → 위치 (바로가기 버튼으로 그 묶음까지 스크롤)
|
/// 묶음 이름 → 위치 (바로가기 버튼으로 그 묶음까지 스크롤)
|
||||||
final _groupKeys = <String, GlobalKey>{};
|
final _groupKeys = <String, GlobalKey>{};
|
||||||
|
|
||||||
static const _groupIds = ['general', 'display', 'mkv', 'subtitle', 'play', 'browser', 'download', 'run', 'about'];
|
static const _groupIds = [
|
||||||
|
'general', 'display', 'mkv', 'subtitle', 'play', 'browser', 'download', 'run', 'cleanup', 'about', //
|
||||||
|
];
|
||||||
|
|
||||||
String _groupTitle(String id) => switch (id) {
|
String _groupTitle(String id) => switch (id) {
|
||||||
'general' => tr('일반'),
|
'general' => tr('일반'),
|
||||||
|
|
@ -704,6 +721,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||||
'browser' => tr('웹 브라우저'),
|
'browser' => tr('웹 브라우저'),
|
||||||
'download' => tr('다운로드'),
|
'download' => tr('다운로드'),
|
||||||
'run' => tr('실행 · 종료'),
|
'run' => tr('실행 · 종료'),
|
||||||
|
'cleanup' => tr('저장 공간 정리'),
|
||||||
_ => tr('프로그램 정보'),
|
_ => tr('프로그램 정보'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||||||
|
import 'package:jj_mkvmaker/app/cleanup.dart';
|
||||||
|
import 'package:jj_mkvmaker/app/download_manager.dart';
|
||||||
|
import 'package:jj_mkvmaker/core/download_detect.dart';
|
||||||
|
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
|
||||||
|
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
|
||||||
|
import 'package:jj_mkvmaker/services/downloader.dart';
|
||||||
|
import 'package:jj_mkvmaker/services/model_store.dart';
|
||||||
|
import 'package:jj_mkvmaker/services/platform_services.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
|
/// 앱 임시 폴더를 시험 폴더로
|
||||||
|
class _Storage extends DesktopStorageService {
|
||||||
|
final String tmp;
|
||||||
|
_Storage(this.tmp);
|
||||||
|
@override
|
||||||
|
Future<String> tempDirectory() async => tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Backend implements DownloadBackend {
|
||||||
|
@override
|
||||||
|
DownloadKind get kind => DownloadKind.video;
|
||||||
|
@override
|
||||||
|
Future<(String, List<PlaylistEntry>)?> expandPlaylist(String url) async => null;
|
||||||
|
@override
|
||||||
|
Future<void> start(DownloadTask t, void Function() changed) async => t.state = DownloadState.downloading;
|
||||||
|
@override
|
||||||
|
Future<void> pause(DownloadTask t) async {}
|
||||||
|
@override
|
||||||
|
Future<void> cancel(DownloadTask t) async {}
|
||||||
|
@override
|
||||||
|
Future<void> shutdown() async {}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Directory root;
|
||||||
|
late AppController c;
|
||||||
|
late String tmp, data, models, sysTmp, logs;
|
||||||
|
|
||||||
|
File make(String path, [int size = 100]) => File(path)
|
||||||
|
..parent.createSync(recursive: true)
|
||||||
|
..writeAsBytesSync(List.filled(size, 1));
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
root = Directory.systemTemp.createTempSync('jj_clean_');
|
||||||
|
tmp = p.join(root.path, 'apptmp');
|
||||||
|
data = p.join(root.path, 'data');
|
||||||
|
models = p.join(root.path, 'models');
|
||||||
|
sysTmp = p.join(root.path, 'systemp');
|
||||||
|
logs = p.join(data, 'Logs');
|
||||||
|
c = AppController(PlatformServices(
|
||||||
|
mediaTool: ProcessMediaTool('x', 'y'),
|
||||||
|
storage: _Storage(tmp),
|
||||||
|
models: ModelStore(models),
|
||||||
|
));
|
||||||
|
c.settings.downloadRoot = p.join(root.path, 'dl');
|
||||||
|
c.logFile = p.join(logs, 'app.log');
|
||||||
|
|
||||||
|
// 지울 것
|
||||||
|
make(p.join(tmp, 'ai_123', 'audio.wav'), 5000);
|
||||||
|
make(p.join(tmp, 'jj_adjust_after_1.png'));
|
||||||
|
make(p.join(data, 'settings.json.4567.tmp'));
|
||||||
|
make(p.join(c.settings.ytDlpDir, '영상 [abc].mp4.part'), 3000);
|
||||||
|
make(p.join(c.settings.ytDlpDir, '영상 [abc].f137.mp4'), 2000);
|
||||||
|
make(p.join(c.settings.ytDlpDir, '영상 [abc].mp4.ytdl'));
|
||||||
|
make(p.join(c.settings.ytDlpDir, '재생목록', '두번째 [def].webm.part-Frag12'));
|
||||||
|
make(p.join(c.settings.aria2Dir, 'movie.mkv.aria2'));
|
||||||
|
make(p.join(models, 'whisper', 'ggml-small.bin.part'), 4000);
|
||||||
|
make(p.join(sysTmp, 'jj_mkvmaker_update_2026.10.04_001', 'JJ_MKVMaker.zip'), 6000);
|
||||||
|
make(p.join(sysTmp, 'jj_ffmpeg.download'));
|
||||||
|
make(p.join(logs, 'app.log.1'), 700);
|
||||||
|
|
||||||
|
// 남길 것
|
||||||
|
make(p.join(c.settings.ytDlpDir, '영상 [abc].mp4'), 9000);
|
||||||
|
make(p.join(c.settings.ytDlpDir, 'jj_mkv', '영상 [abc].mkv'));
|
||||||
|
make(p.join(c.settings.aria2Dir, 'movie.mkv'));
|
||||||
|
make(p.join(models, 'whisper', 'ggml-base.bin'));
|
||||||
|
make(p.join(data, 'settings.json'));
|
||||||
|
make(p.join(sysTmp, 'other_app.tmp'));
|
||||||
|
make(p.join(logs, 'app.log'));
|
||||||
|
});
|
||||||
|
tearDown(() => root.deleteSync(recursive: true));
|
||||||
|
|
||||||
|
test('찾기: 묶음별로 남은 조각만 찾고, 받은 동영상 · MKV · 모델 · 설정 · 지금 기록은 건드리지 않는다', () async {
|
||||||
|
final groups = await Cleaner(c, dataDir: data, systemTemp: sysTmp).scan();
|
||||||
|
Map<String, List<String>> names = {
|
||||||
|
for (final g in groups) g.id: [for (final i in g.items) p.basename(i.path)]..sort(),
|
||||||
|
};
|
||||||
|
expect(names['work'], ['ai_123', 'jj_adjust_after_1.png', 'settings.json.4567.tmp']);
|
||||||
|
expect(names['download'], [
|
||||||
|
'movie.mkv.aria2',
|
||||||
|
'두번째 [def].webm.part-Frag12',
|
||||||
|
'영상 [abc].f137.mp4',
|
||||||
|
'영상 [abc].mp4.part',
|
||||||
|
'영상 [abc].mp4.ytdl',
|
||||||
|
]);
|
||||||
|
expect(names['models'], ['ggml-small.bin.part']);
|
||||||
|
expect(names['update'], ['jj_ffmpeg.download', 'jj_mkvmaker_update_2026.10.04_001']);
|
||||||
|
expect(names['logs'], ['app.log.1']);
|
||||||
|
expect(groups.firstWhere((g) => g.id == 'work').bytes, 5000 + 100 + 100);
|
||||||
|
expect(groups.every((g) => g.skipped == null), isTrue);
|
||||||
|
|
||||||
|
final (n, bytes) = await Cleaner(c, dataDir: data, systemTemp: sysTmp).clean(groups);
|
||||||
|
expect(n, 3 + 5 + 1 + 2 + 1);
|
||||||
|
expect(bytes, greaterThan(20000));
|
||||||
|
// 남길 것은 그대로
|
||||||
|
for (final keep in [
|
||||||
|
p.join(c.settings.ytDlpDir, '영상 [abc].mp4'),
|
||||||
|
p.join(c.settings.ytDlpDir, 'jj_mkv', '영상 [abc].mkv'),
|
||||||
|
p.join(c.settings.aria2Dir, 'movie.mkv'),
|
||||||
|
p.join(models, 'whisper', 'ggml-base.bin'),
|
||||||
|
p.join(data, 'settings.json'),
|
||||||
|
p.join(sysTmp, 'other_app.tmp'),
|
||||||
|
p.join(logs, 'app.log'),
|
||||||
|
]) {
|
||||||
|
expect(File(keep).existsSync(), isTrue, reason: keep);
|
||||||
|
}
|
||||||
|
// 다시 찾으면 없음
|
||||||
|
final again = await Cleaner(c, dataDir: data, systemTemp: sysTmp).scan();
|
||||||
|
expect(again.every((g) => g.items.isEmpty), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('다운로드 중 (일시정지 포함) 이면 받다 만 다운로드는 건너뛴다', () async {
|
||||||
|
final d = DownloadManager(backends: [_Backend()], settings: () => c.settings, readClipboard: () async => null);
|
||||||
|
addTearDown(d.dispose);
|
||||||
|
d.addPage('https://www.youtube.com/watch?v=abc');
|
||||||
|
await Future<void>.delayed(Duration.zero);
|
||||||
|
expect(d.activeCount, 1);
|
||||||
|
final groups = await Cleaner(c, downloads: d, dataDir: data, systemTemp: sysTmp).scan();
|
||||||
|
final dl = groups.firstWhere((g) => g.id == 'download');
|
||||||
|
expect(dl.skipped, isNotNull);
|
||||||
|
await Cleaner(c, downloads: d, dataDir: data, systemTemp: sysTmp).clean(groups);
|
||||||
|
expect(File(p.join(c.settings.ytDlpDir, '영상 [abc].mp4.part')).existsSync(), isTrue, reason: '이어받기용 조각은 그대로');
|
||||||
|
expect(Directory(p.join(tmp, 'ai_123')).existsSync(), isFalse, reason: '다른 묶음은 정리');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('크기 글', () {
|
||||||
|
expect(Cleaner.sizeText(512), '512 B');
|
||||||
|
expect(Cleaner.sizeText(2048), '2 KB');
|
||||||
|
expect(Cleaner.sizeText(5 * 1024 * 1024), '5.0 MB');
|
||||||
|
expect(Cleaner.sizeText(3 * 1024 * 1024 * 1024), '3.0 GB');
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue