WIP 86 보충 (관리자 · 15): 덮어쓰기에도 받는 쪽이 더 새것이면 그대로 · Windows 에서 쓸 수 없는 이름은 미리 건너뜀 · 릴리스에 7-Zip 꼭 (검토 대기)
- [덮어쓰기] 를 골라도 설정 "받는 쪽이 더 새것이면 건너뛰기" (229, 처음 켜짐) 를 따른다: 이미 있는 파일이 압축 안의 것보다 2초 넘게 새것이면 덮지 않는다 (ZIP 은 DOS 시각, 7z 는 목록의 Modified 로 견줘 목록 파일에서 뺌). 끝 알림 "n개는 더 새 파일이라 그대로 둠". 확인 창에 [덮어쓰기] 를 고르면 "덮어쓴 파일은 되돌릴 수 없습니다" (휴지통에 남지 않음). - 압축 안 이름이 이 기기에서 쓸 수 없으면 (Windows: CON · NUL · ? * " < > | · 끝 점 · 빈칸 - 78 의 nameProblem) 풀기 전에 걸러 확인 창에 "풀 수 없는 이름 n개 (건너뜀)" - 나머지는 그대로 푼다 (풀다 실패하거나 7-Zip 이 다른 이름으로 바꾸지 않게). Android 는 지금처럼. - 릴리스: make_zip.ps1 이 Lib\7zip\7z.exe · 7z.dll · License.txt 가 없으면 실패. CMake 는 없으면 경고 (개발 빌드는 그대로). - (번역 파일에는 다음 커밋 87 의 글도 함께 들어 있다 - 쓰이지 않는 열쇠) - 시험: archive_ops_test (쓸 수 없는 이름 · ZIP 과 실제 7z 의 받는 쪽이 더 새 파일). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>묶음6
parent
219510fec0
commit
c440b688ea
|
|
@ -321,6 +321,7 @@
|
||||||
"WebDAV {0} 실패: {1} {2}{3}": "WebDAV {0} failed: {1} {2}{3}",
|
"WebDAV {0} 실패: {1} {2}{3}": "WebDAV {0} failed: {1} {2}{3}",
|
||||||
"WebDAV · OpenSubtitles 등. 서버 목록과 아이디는 남깁니다": "WebDAV, OpenSubtitles, etc. Server list and user names are kept",
|
"WebDAV · OpenSubtitles 등. 서버 목록과 아이디는 남깁니다": "WebDAV, OpenSubtitles, etc. Server list and user names are kept",
|
||||||
"WebDAV · OpenSubtitles 비밀번호를 이 기기의 안전 저장소에 둡니다. 끄면 저장하지 않고 (있던 것도 지움) 앱을 켤 때마다 다시 넣습니다": "Keeps WebDAV and OpenSubtitles passwords in this device's secure storage. When off, nothing is saved (existing ones are removed) and you enter them each time the app starts",
|
"WebDAV · OpenSubtitles 비밀번호를 이 기기의 안전 저장소에 둡니다. 끄면 저장하지 않고 (있던 것도 지움) 앱을 켤 때마다 다시 넣습니다": "Keeps WebDAV and OpenSubtitles passwords in this device's secure storage. When off, nothing is saved (existing ones are removed) and you enter them each time the app starts",
|
||||||
|
"WebDAV 는 서버에서 안의 것을 모두 읽어 오래 걸릴 수 있습니다. 분석할까요?": "WebDAV reads everything inside from the server, which can take a long time. Analyze?",
|
||||||
"WebDAV 서버 \"{0}\" 을(를) 지울까요?": "Delete WebDAV server \"{0}\"?",
|
"WebDAV 서버 \"{0}\" 을(를) 지울까요?": "Delete WebDAV server \"{0}\"?",
|
||||||
"WebDAV 서버 고치기": "Edit WebDAV server",
|
"WebDAV 서버 고치기": "Edit WebDAV server",
|
||||||
"WebDAV 서버 목록과 그 저장된 비밀번호도 지워집니다. 동영상 목록 · 즐겨찾기 · 파일은 그대로입니다": "The WebDAV server list and its saved passwords are deleted too. The video list, bookmarks and files are kept",
|
"WebDAV 서버 목록과 그 저장된 비밀번호도 지워집니다. 동영상 목록 · 즐겨찾기 · 파일은 그대로입니다": "The WebDAV server list and its saved passwords are deleted too. The video list, bookmarks and files are kept",
|
||||||
|
|
@ -435,6 +436,7 @@
|
||||||
"{0} · {1}{2} (메인 화면 상단에서 변경)": "{0} · {1}{2} (change at the top of the main screen)",
|
"{0} · {1}{2} (메인 화면 상단에서 변경)": "{0} · {1}{2} (change at the top of the main screen)",
|
||||||
"{0} · {1}배 · {2}": "{0} · {1}× · {2}",
|
"{0} · {1}배 · {2}": "{0} · {1}× · {2}",
|
||||||
"{0} · 링크 {1}개 제외": "{0} · {1} links excluded",
|
"{0} · 링크 {1}개 제외": "{0} · {1} links excluded",
|
||||||
|
"{0} · 파일 {1}개": "{0} · {1} files",
|
||||||
"{0} → {1} 번역 {2}%": "{0} → {1} translation {2}%",
|
"{0} → {1} 번역 {2}%": "{0} → {1} translation {2}%",
|
||||||
"{0} 는 이미 끝났습니다": "{0} has already finished",
|
"{0} 는 이미 끝났습니다": "{0} has already finished",
|
||||||
"{0} 라이선스": "{0} license",
|
"{0} 라이선스": "{0} license",
|
||||||
|
|
@ -510,6 +512,7 @@
|
||||||
"{0}개 항목을 지울까요? 되돌릴 수 없습니다.": "Delete {0} items? This cannot be undone.",
|
"{0}개 항목을 지울까요? 되돌릴 수 없습니다.": "Delete {0} items? This cannot be undone.",
|
||||||
"{0}개 항목을 휴지통으로 보낼까요?": "Move {0} items to the Recycle Bin?",
|
"{0}개 항목을 휴지통으로 보낼까요?": "Move {0} items to the Recycle Bin?",
|
||||||
"{0}개 항목을 휴지통으로 보냈습니다.": "Moved {0} items to the Recycle Bin.",
|
"{0}개 항목을 휴지통으로 보냈습니다.": "Moved {0} items to the Recycle Bin.",
|
||||||
|
"{0}개는 더 새 파일이라 그대로 둠": "{0} kept (existing file is newer)",
|
||||||
"{0}개는 원본에 남음 (건너뜀)": "{0} left in the source (skipped)",
|
"{0}개는 원본에 남음 (건너뜀)": "{0} left in the source (skipped)",
|
||||||
"{0}개는 지우지 않았습니다 (취소).": "{0} were not deleted (cancelled).",
|
"{0}개는 지우지 않았습니다 (취소).": "{0} were not deleted (cancelled).",
|
||||||
"{0}개는 휴지통보다 커서 영구 삭제했습니다 (Windows 가 물은 대로).": "{0} were too large for the Recycle Bin and were deleted permanently (as Windows asked).",
|
"{0}개는 휴지통보다 커서 영구 삭제했습니다 (Windows 가 물은 대로).": "{0} were too large for the Recycle Bin and were deleted permanently (as Windows asked).",
|
||||||
|
|
@ -924,6 +927,8 @@
|
||||||
"덮어쓰기 (원래 파일은 없어집니다)": "Overwrite (the existing file is replaced)",
|
"덮어쓰기 (원래 파일은 없어집니다)": "Overwrite (the existing file is replaced)",
|
||||||
"덮어쓰기여도 다 만든 뒤에만 바꿉니다 (도중에 취소 · 실패하면 예전 MKV 그대로)": "Even when overwriting, the old file is replaced only after the new one is finished (if cancelled or failed, the old MKV stays)",
|
"덮어쓰기여도 다 만든 뒤에만 바꿉니다 (도중에 취소 · 실패하면 예전 MKV 그대로)": "Even when overwriting, the old file is replaced only after the new one is finished (if cancelled or failed, the old MKV stays)",
|
||||||
"덮어쓰기여도 다 만든 뒤에만 바꿉니다. 도중에 취소 · 실패하면 예전 MKV 는 그대로 남습니다. 늘 같게 하려면 환경 설정 > MKV 만들기 > 만들 MKV 가 이미 있으면": "Even when overwriting, the old file is replaced only after the new one is finished. If cancelled or failed, the old MKV stays. To always do the same: Settings > Make MKV > If the MKV already exists",
|
"덮어쓰기여도 다 만든 뒤에만 바꿉니다. 도중에 취소 · 실패하면 예전 MKV 는 그대로 남습니다. 늘 같게 하려면 환경 설정 > MKV 만들기 > 만들 MKV 가 이미 있으면": "Even when overwriting, the old file is replaced only after the new one is finished. If cancelled or failed, the old MKV stays. To always do the same: Settings > Make MKV > If the MKV already exists",
|
||||||
|
"덮어쓴 파일은 되돌릴 수 없습니다 (받는 쪽이 더 새 파일은 그대로 둡니다).": "Overwritten files can't be restored (files that are newer at the destination are kept).",
|
||||||
|
"덮어쓴 파일은 되돌릴 수 없습니다.": "Overwritten files can't be restored.",
|
||||||
"덮어씁니다": "overwrites it",
|
"덮어씁니다": "overwrites it",
|
||||||
"덴마크어": "Danish",
|
"덴마크어": "Danish",
|
||||||
"독일어": "German",
|
"독일어": "German",
|
||||||
|
|
@ -1004,6 +1009,7 @@
|
||||||
"링크 {0} (건너뜀)": "Links {0} (skipped)",
|
"링크 {0} (건너뜀)": "Links {0} (skipped)",
|
||||||
"링크 {0}개는 넣지 않음": "{0} links not included",
|
"링크 {0}개는 넣지 않음": "{0} links not included",
|
||||||
"링크 {0}개는 만들지 않고 건너뜁니다: {1}": "{0} links will be skipped (not created): {1}",
|
"링크 {0}개는 만들지 않고 건너뜁니다: {1}": "{0} links will be skipped (not created): {1}",
|
||||||
|
"링크 {0}개는 세지 않음": "{0} links not counted",
|
||||||
"링크 · junction 은 넣지 않습니다": "Links and junctions are not included",
|
"링크 · junction 은 넣지 않습니다": "Links and junctions are not included",
|
||||||
"마스터 · 최상 비밀번호": "Master and super passwords",
|
"마스터 · 최상 비밀번호": "Master and super passwords",
|
||||||
"마스터 비밀번호": "Master password",
|
"마스터 비밀번호": "Master password",
|
||||||
|
|
@ -1270,8 +1276,10 @@
|
||||||
"복사했습니다.": "copied.",
|
"복사했습니다.": "copied.",
|
||||||
"볼 그림이 없습니다.": "No images to show.",
|
"볼 그림이 없습니다.": "No images to show.",
|
||||||
"볼 수 없는 영상입니다 (삭제 · 지역 제한).": "This video is unavailable (deleted · region restricted).",
|
"볼 수 없는 영상입니다 (삭제 · 지역 제한).": "This video is unavailable (deleted · region restricted).",
|
||||||
|
"분석": "Analyze",
|
||||||
"분석 실패: {0} - {1}": "Analysis failed: {0} - {1}",
|
"분석 실패: {0} - {1}": "Analysis failed: {0} - {1}",
|
||||||
"분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "Re-analyzed {0} videos that had failed",
|
"분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "Re-analyzed {0} videos that had failed",
|
||||||
|
"분석하지 못했습니다: {0}": "Couldn't analyze: {0}",
|
||||||
"붙여넣기": "Paste",
|
"붙여넣기": "Paste",
|
||||||
"붙여넣을 항목이 없습니다. 먼저 [복사] · [이동] 으로 담으세요.": "Nothing to paste. First hold items with [Copy] · [Move].",
|
"붙여넣을 항목이 없습니다. 먼저 [복사] · [이동] 으로 담으세요.": "Nothing to paste. First hold items with [Copy] · [Move].",
|
||||||
"브라우저": "Browser",
|
"브라우저": "Browser",
|
||||||
|
|
@ -1505,6 +1513,7 @@
|
||||||
"아직 지원하지 않는 플랫폼입니다: {0}": "This platform is not supported yet: {0}",
|
"아직 지원하지 않는 플랫폼입니다: {0}": "This platform is not supported yet: {0}",
|
||||||
"아직 허용되지 않았습니다": "Not allowed yet",
|
"아직 허용되지 않았습니다": "Not allowed yet",
|
||||||
"안내 닫기": "Close tip",
|
"안내 닫기": "Close tip",
|
||||||
|
"안에 폴더가 없습니다": "No folders inside",
|
||||||
"안쪽 파일을 확인하는 중 …": "Checking files inside …",
|
"안쪽 파일을 확인하는 중 …": "Checking files inside …",
|
||||||
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합쳐도 건너뜁니다 (원본에 남음)": "{0} files inside are newer on the receiving side - they are skipped when merging (they stay in the source)",
|
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합쳐도 건너뜁니다 (원본에 남음)": "{0} files inside are newer on the receiving side - they are skipped when merging (they stay in the source)",
|
||||||
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합치면 예전 것으로 덮어씁니다": "{0} files inside are newer on the receiving side - merging overwrites them with older ones",
|
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합치면 예전 것으로 덮어씁니다": "{0} files inside are newer on the receiving side - merging overwrites them with older ones",
|
||||||
|
|
@ -1893,12 +1902,14 @@
|
||||||
"일본어": "Japanese",
|
"일본어": "Japanese",
|
||||||
"일시정지": "Pause",
|
"일시정지": "Pause",
|
||||||
"읽기 전용 매체입니다": "Read-only media",
|
"읽기 전용 매체입니다": "Read-only media",
|
||||||
|
"읽는 중 · 폴더 {0}개 · {1}": "Reading · {0} folders · {1}",
|
||||||
"읽을 수 없습니다": "Cannot read",
|
"읽을 수 없습니다": "Cannot read",
|
||||||
"읽을 수 없습니다: {0}": "Cannot read: {0}",
|
"읽을 수 없습니다: {0}": "Cannot read: {0}",
|
||||||
"읽을 수 없어 건너뛴 폴더: {0}": "Skipped folders that could not be read: {0}",
|
"읽을 수 없어 건너뛴 폴더: {0}": "Skipped folders that could not be read: {0}",
|
||||||
"읽을 수 없음": "Cannot read",
|
"읽을 수 없음": "Cannot read",
|
||||||
"읽지 못한 원래 파일은 지우지 않고 아래에 보관했습니다:": "The original file that could not be read was not deleted; it is kept here:",
|
"읽지 못한 원래 파일은 지우지 않고 아래에 보관했습니다:": "The original file that could not be read was not deleted; it is kept here:",
|
||||||
"읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다": "{0} unreadable folders (permission or connection problem) - not searched inside",
|
"읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다": "{0} unreadable folders (permission or connection problem) - not searched inside",
|
||||||
|
"읽지 못한 폴더 {0}개 (크기가 실제보다 작을 수 있음)": "{0} folders couldn't be read (sizes may be smaller than actual)",
|
||||||
"임시 파일 · 남은 조각 정리": "Clean temporary files · leftovers",
|
"임시 파일 · 남은 조각 정리": "Clean temporary files · leftovers",
|
||||||
"자동": "Auto",
|
"자동": "Auto",
|
||||||
"자동 (가로로 긴 그림만)": "Auto (wide images only)",
|
"자동 (가로로 긴 그림만)": "Auto (wide images only)",
|
||||||
|
|
@ -1986,6 +1997,7 @@
|
||||||
"저장": "Save",
|
"저장": "Save",
|
||||||
"저장 (MKV 반영)": "Save (apply to MKV)",
|
"저장 (MKV 반영)": "Save (apply to MKV)",
|
||||||
"저장 (MKV 반영) *": "Save (apply to MKV) *",
|
"저장 (MKV 반영) *": "Save (apply to MKV) *",
|
||||||
|
"저장 공간 분석": "Storage analysis",
|
||||||
"저장 공간 정리": "Storage cleanup",
|
"저장 공간 정리": "Storage cleanup",
|
||||||
"저장 공간 정리: 파일 {0}개 · {1}": "Storage cleanup: {0} files · {1}",
|
"저장 공간 정리: 파일 {0}개 · {1}": "Storage cleanup: {0} files · {1}",
|
||||||
"저장 공간이 모자랍니다": "Not enough storage space",
|
"저장 공간이 모자랍니다": "Not enough storage space",
|
||||||
|
|
@ -2269,6 +2281,9 @@
|
||||||
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "Changing the size requires re-encoding, so the codec was changed from \"Keep original\" to H.264.",
|
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "Changing the size requires re-encoding, so the codec was changed from \"Keep original\" to H.264.",
|
||||||
"크기순 정렬일 때만 (기본)": "Only when sorting by size (default)",
|
"크기순 정렬일 때만 (기본)": "Only when sorting by size (default)",
|
||||||
"크메르어": "Khmer",
|
"크메르어": "Khmer",
|
||||||
|
"큰 파일": "Large files",
|
||||||
|
"큰 파일이 없습니다": "No large files",
|
||||||
|
"큰 폴더": "Large folders",
|
||||||
"키 있음": "Key saved",
|
"키 있음": "Key saved",
|
||||||
"타갈로그어": "Tagalog",
|
"타갈로그어": "Tagalog",
|
||||||
"타밀어": "Tamil",
|
"타밀어": "Tamil",
|
||||||
|
|
@ -2290,6 +2305,7 @@
|
||||||
"트레이의 JJ_MKVMaker 아이콘을 누르면 열립니다.": "Click the JJ_MKVMaker icon in the tray to open it.",
|
"트레이의 JJ_MKVMaker 아이콘을 누르면 열립니다.": "Click the JJ_MKVMaker icon in the tray to open it.",
|
||||||
"파일": "File",
|
"파일": "File",
|
||||||
"파일 {0}/{1}": "Files {0}/{1}",
|
"파일 {0}/{1}": "Files {0}/{1}",
|
||||||
|
"파일 {0}개": "{0} files",
|
||||||
"파일 {0}개 · {1}": "{0} files · {1}",
|
"파일 {0}개 · {1}": "{0} files · {1}",
|
||||||
"파일 관리자에서 보기": "Show in file manager",
|
"파일 관리자에서 보기": "Show in file manager",
|
||||||
"파일 복사 · 이동 방법": "File copy · move method",
|
"파일 복사 · 이동 방법": "File copy · move method",
|
||||||
|
|
@ -2382,6 +2398,8 @@
|
||||||
"표준": "Standard",
|
"표준": "Standard",
|
||||||
"푸는 중": "Extracting",
|
"푸는 중": "Extracting",
|
||||||
"푸는 중: {0}": "Extracting: {0}",
|
"푸는 중: {0}": "Extracting: {0}",
|
||||||
|
"풀 수 없는 이름 {0}개": "{0} invalid names",
|
||||||
|
"풀 수 없는 이름 {0}개 (건너뜀): {1}": "{0} names can't be used on this device (skipped): {1}",
|
||||||
"풀기": "Extract",
|
"풀기": "Extract",
|
||||||
"풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?": "The extracted size ({0}) is larger than the free space ({1}). Extract anyway?",
|
"풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?": "The extracted size ({0}) is larger than the free space ({1}). Extract anyway?",
|
||||||
"풀지 못했습니다: {0}": "Could not extract: {0}",
|
"풀지 못했습니다: {0}": "Could not extract: {0}",
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,7 @@
|
||||||
"WebDAV {0} 실패: {1} {2}{3}": "WebDAV {0} 失敗: {1} {2}{3}",
|
"WebDAV {0} 실패: {1} {2}{3}": "WebDAV {0} 失敗: {1} {2}{3}",
|
||||||
"WebDAV · OpenSubtitles 등. 서버 목록과 아이디는 남깁니다": "WebDAV・OpenSubtitles など。サーバー一覧と ID は残します",
|
"WebDAV · OpenSubtitles 등. 서버 목록과 아이디는 남깁니다": "WebDAV・OpenSubtitles など。サーバー一覧と ID は残します",
|
||||||
"WebDAV · OpenSubtitles 비밀번호를 이 기기의 안전 저장소에 둡니다. 끄면 저장하지 않고 (있던 것도 지움) 앱을 켤 때마다 다시 넣습니다": "WebDAV・OpenSubtitles のパスワードをこの端末の安全な保存領域に保存します。オフにすると保存せず (既存のものも削除)、アプリを起動するたびに入力します",
|
"WebDAV · OpenSubtitles 비밀번호를 이 기기의 안전 저장소에 둡니다. 끄면 저장하지 않고 (있던 것도 지움) 앱을 켤 때마다 다시 넣습니다": "WebDAV・OpenSubtitles のパスワードをこの端末の安全な保存領域に保存します。オフにすると保存せず (既存のものも削除)、アプリを起動するたびに入力します",
|
||||||
|
"WebDAV 는 서버에서 안의 것을 모두 읽어 오래 걸릴 수 있습니다. 분석할까요?": "WebDAV はサーバーから中身をすべて読むため、時間がかかることがあります。分析しますか?",
|
||||||
"WebDAV 서버 \"{0}\" 을(를) 지울까요?": "WebDAV サーバー「{0}」を削除しますか?",
|
"WebDAV 서버 \"{0}\" 을(를) 지울까요?": "WebDAV サーバー「{0}」を削除しますか?",
|
||||||
"WebDAV 서버 고치기": "WebDAV サーバーを編集",
|
"WebDAV 서버 고치기": "WebDAV サーバーを編集",
|
||||||
"WebDAV 서버 목록과 그 저장된 비밀번호도 지워집니다. 동영상 목록 · 즐겨찾기 · 파일은 그대로입니다": "WebDAV サーバー一覧とその保存したパスワードも消えます。動画リスト・お気に入り・ファイルはそのままです",
|
"WebDAV 서버 목록과 그 저장된 비밀번호도 지워집니다. 동영상 목록 · 즐겨찾기 · 파일은 그대로입니다": "WebDAV サーバー一覧とその保存したパスワードも消えます。動画リスト・お気に入り・ファイルはそのままです",
|
||||||
|
|
@ -435,6 +436,7 @@
|
||||||
"{0} · {1}{2} (메인 화면 상단에서 변경)": "{0} · {1}{2}(メイン画面の上部で変更)",
|
"{0} · {1}{2} (메인 화면 상단에서 변경)": "{0} · {1}{2}(メイン画面の上部で変更)",
|
||||||
"{0} · {1}배 · {2}": "{0}・{1} 倍・{2}",
|
"{0} · {1}배 · {2}": "{0}・{1} 倍・{2}",
|
||||||
"{0} · 링크 {1}개 제외": "{0} · リンク {1} 件を除く",
|
"{0} · 링크 {1}개 제외": "{0} · リンク {1} 件を除く",
|
||||||
|
"{0} · 파일 {1}개": "{0} · ファイル {1} 件",
|
||||||
"{0} → {1} 번역 {2}%": "{0} → {1} 翻訳 {2}%",
|
"{0} → {1} 번역 {2}%": "{0} → {1} 翻訳 {2}%",
|
||||||
"{0} 는 이미 끝났습니다": "{0} はすでに終わりました",
|
"{0} 는 이미 끝났습니다": "{0} はすでに終わりました",
|
||||||
"{0} 라이선스": "{0} ライセンス",
|
"{0} 라이선스": "{0} ライセンス",
|
||||||
|
|
@ -510,6 +512,7 @@
|
||||||
"{0}개 항목을 지울까요? 되돌릴 수 없습니다.": "{0} 件の項目を削除しますか? 元に戻せません。",
|
"{0}개 항목을 지울까요? 되돌릴 수 없습니다.": "{0} 件の項目を削除しますか? 元に戻せません。",
|
||||||
"{0}개 항목을 휴지통으로 보낼까요?": "{0} 個の項目をごみ箱に移動しますか?",
|
"{0}개 항목을 휴지통으로 보낼까요?": "{0} 個の項目をごみ箱に移動しますか?",
|
||||||
"{0}개 항목을 휴지통으로 보냈습니다.": "{0} 個の項目をごみ箱に移動しました。",
|
"{0}개 항목을 휴지통으로 보냈습니다.": "{0} 個の項目をごみ箱に移動しました。",
|
||||||
|
"{0}개는 더 새 파일이라 그대로 둠": "{0} 件は新しいファイルなのでそのまま",
|
||||||
"{0}개는 원본에 남음 (건너뜀)": "{0} 件は元の場所に残っています (スキップ)",
|
"{0}개는 원본에 남음 (건너뜀)": "{0} 件は元の場所に残っています (スキップ)",
|
||||||
"{0}개는 지우지 않았습니다 (취소).": "{0} 個は削除しませんでした (キャンセル)。",
|
"{0}개는 지우지 않았습니다 (취소).": "{0} 個は削除しませんでした (キャンセル)。",
|
||||||
"{0}개는 휴지통보다 커서 영구 삭제했습니다 (Windows 가 물은 대로).": "{0} 個はごみ箱より大きいため完全に削除しました (Windows の確認どおり)。",
|
"{0}개는 휴지통보다 커서 영구 삭제했습니다 (Windows 가 물은 대로).": "{0} 個はごみ箱より大きいため完全に削除しました (Windows の確認どおり)。",
|
||||||
|
|
@ -924,6 +927,8 @@
|
||||||
"덮어쓰기 (원래 파일은 없어집니다)": "上書き (元のファイルは消えます)",
|
"덮어쓰기 (원래 파일은 없어집니다)": "上書き (元のファイルは消えます)",
|
||||||
"덮어쓰기여도 다 만든 뒤에만 바꿉니다 (도중에 취소 · 실패하면 예전 MKV 그대로)": "上書きでも作り終えてから置き換えます (途中でキャンセル · 失敗すると以前の MKV はそのまま)",
|
"덮어쓰기여도 다 만든 뒤에만 바꿉니다 (도중에 취소 · 실패하면 예전 MKV 그대로)": "上書きでも作り終えてから置き換えます (途中でキャンセル · 失敗すると以前の MKV はそのまま)",
|
||||||
"덮어쓰기여도 다 만든 뒤에만 바꿉니다. 도중에 취소 · 실패하면 예전 MKV 는 그대로 남습니다. 늘 같게 하려면 환경 설정 > MKV 만들기 > 만들 MKV 가 이미 있으면": "上書きでも作り終えてから置き換えます。途中でキャンセル · 失敗すると以前の MKV はそのまま残ります。いつも同じにするには: 環境設定 > MKV 作成 > 作る MKV がすでにあるとき",
|
"덮어쓰기여도 다 만든 뒤에만 바꿉니다. 도중에 취소 · 실패하면 예전 MKV 는 그대로 남습니다. 늘 같게 하려면 환경 설정 > MKV 만들기 > 만들 MKV 가 이미 있으면": "上書きでも作り終えてから置き換えます。途中でキャンセル · 失敗すると以前の MKV はそのまま残ります。いつも同じにするには: 環境設定 > MKV 作成 > 作る MKV がすでにあるとき",
|
||||||
|
"덮어쓴 파일은 되돌릴 수 없습니다 (받는 쪽이 더 새 파일은 그대로 둡니다).": "上書きしたファイルは元に戻せません (受け取る側が新しいファイルはそのまま残します)。",
|
||||||
|
"덮어쓴 파일은 되돌릴 수 없습니다.": "上書きしたファイルは元に戻せません。",
|
||||||
"덮어씁니다": "上書きします",
|
"덮어씁니다": "上書きします",
|
||||||
"덴마크어": "デンマーク語",
|
"덴마크어": "デンマーク語",
|
||||||
"독일어": "ドイツ語",
|
"독일어": "ドイツ語",
|
||||||
|
|
@ -1004,6 +1009,7 @@
|
||||||
"링크 {0} (건너뜀)": "リンク {0} (スキップ)",
|
"링크 {0} (건너뜀)": "リンク {0} (スキップ)",
|
||||||
"링크 {0}개는 넣지 않음": "リンク {0} 件は含めず",
|
"링크 {0}개는 넣지 않음": "リンク {0} 件は含めず",
|
||||||
"링크 {0}개는 만들지 않고 건너뜁니다: {1}": "リンク {0} 件は作成せずスキップします: {1}",
|
"링크 {0}개는 만들지 않고 건너뜁니다: {1}": "リンク {0} 件は作成せずスキップします: {1}",
|
||||||
|
"링크 {0}개는 세지 않음": "リンク {0} 件は数えず",
|
||||||
"링크 · junction 은 넣지 않습니다": "リンク・ジャンクションは含めません",
|
"링크 · junction 은 넣지 않습니다": "リンク・ジャンクションは含めません",
|
||||||
"마스터 · 최상 비밀번호": "マスター / 最上位パスワード",
|
"마스터 · 최상 비밀번호": "マスター / 最上位パスワード",
|
||||||
"마스터 비밀번호": "マスターパスワード",
|
"마스터 비밀번호": "マスターパスワード",
|
||||||
|
|
@ -1270,8 +1276,10 @@
|
||||||
"복사했습니다.": "コピーしました。",
|
"복사했습니다.": "コピーしました。",
|
||||||
"볼 그림이 없습니다.": "表示する画像がありません。",
|
"볼 그림이 없습니다.": "表示する画像がありません。",
|
||||||
"볼 수 없는 영상입니다 (삭제 · 지역 제한).": "視聴できない動画です(削除・地域制限)。",
|
"볼 수 없는 영상입니다 (삭제 · 지역 제한).": "視聴できない動画です(削除・地域制限)。",
|
||||||
|
"분석": "分析",
|
||||||
"분석 실패: {0} - {1}": "分析失敗: {0} - {1}",
|
"분석 실패: {0} - {1}": "分析失敗: {0} - {1}",
|
||||||
"분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "分析に失敗していた動画 {0} 件を再分析しました",
|
"분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "分析に失敗していた動画 {0} 件を再分析しました",
|
||||||
|
"분석하지 못했습니다: {0}": "分析できませんでした: {0}",
|
||||||
"붙여넣기": "貼り付け",
|
"붙여넣기": "貼り付け",
|
||||||
"붙여넣을 항목이 없습니다. 먼저 [복사] · [이동] 으로 담으세요.": "貼り付ける項目がありません。先に [コピー] · [移動] で保持してください。",
|
"붙여넣을 항목이 없습니다. 먼저 [복사] · [이동] 으로 담으세요.": "貼り付ける項目がありません。先に [コピー] · [移動] で保持してください。",
|
||||||
"브라우저": "ブラウザー",
|
"브라우저": "ブラウザー",
|
||||||
|
|
@ -1505,6 +1513,7 @@
|
||||||
"아직 지원하지 않는 플랫폼입니다: {0}": "まだ対応していないプラットフォームです: {0}",
|
"아직 지원하지 않는 플랫폼입니다: {0}": "まだ対応していないプラットフォームです: {0}",
|
||||||
"아직 허용되지 않았습니다": "まだ許可されていません",
|
"아직 허용되지 않았습니다": "まだ許可されていません",
|
||||||
"안내 닫기": "案内を閉じる",
|
"안내 닫기": "案内を閉じる",
|
||||||
|
"안에 폴더가 없습니다": "中にフォルダーがありません",
|
||||||
"안쪽 파일을 확인하는 중 …": "中のファイルを確認中 …",
|
"안쪽 파일을 확인하는 중 …": "中のファイルを確認中 …",
|
||||||
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합쳐도 건너뜁니다 (원본에 남음)": "中に受け取る側の方が新しいファイルが {0} 件 - 合わせてもスキップします (元に残ります)",
|
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합쳐도 건너뜁니다 (원본에 남음)": "中に受け取る側の方が新しいファイルが {0} 件 - 合わせてもスキップします (元に残ります)",
|
||||||
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합치면 예전 것으로 덮어씁니다": "中に受け取る側の方が新しいファイルが {0} 件 - 合わせると古いもので上書きします",
|
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합치면 예전 것으로 덮어씁니다": "中に受け取る側の方が新しいファイルが {0} 件 - 合わせると古いもので上書きします",
|
||||||
|
|
@ -1893,12 +1902,14 @@
|
||||||
"일본어": "日本語",
|
"일본어": "日本語",
|
||||||
"일시정지": "一時停止",
|
"일시정지": "一時停止",
|
||||||
"읽기 전용 매체입니다": "読み取り専用メディアです",
|
"읽기 전용 매체입니다": "読み取り専用メディアです",
|
||||||
|
"읽는 중 · 폴더 {0}개 · {1}": "読み込み中 · フォルダー {0} 件 · {1}",
|
||||||
"읽을 수 없습니다": "読み込めません",
|
"읽을 수 없습니다": "読み込めません",
|
||||||
"읽을 수 없습니다: {0}": "読み込めません: {0}",
|
"읽을 수 없습니다: {0}": "読み込めません: {0}",
|
||||||
"읽을 수 없어 건너뛴 폴더: {0}": "読み込めずスキップしたフォルダー: {0}",
|
"읽을 수 없어 건너뛴 폴더: {0}": "読み込めずスキップしたフォルダー: {0}",
|
||||||
"읽을 수 없음": "読み取れません",
|
"읽을 수 없음": "読み取れません",
|
||||||
"읽지 못한 원래 파일은 지우지 않고 아래에 보관했습니다:": "読めなかった元のファイルは削除せず、ここに保管しました:",
|
"읽지 못한 원래 파일은 지우지 않고 아래에 보관했습니다:": "読めなかった元のファイルは削除せず、ここに保管しました:",
|
||||||
"읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다": "読めなかったフォルダー {0} 個 (権限・接続の問題) - その中は探せませんでした",
|
"읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다": "読めなかったフォルダー {0} 個 (権限・接続の問題) - その中は探せませんでした",
|
||||||
|
"읽지 못한 폴더 {0}개 (크기가 실제보다 작을 수 있음)": "読めなかったフォルダー {0} 件 (サイズが実際より小さいことがあります)",
|
||||||
"임시 파일 · 남은 조각 정리": "一時ファイル・残った断片を整理",
|
"임시 파일 · 남은 조각 정리": "一時ファイル・残った断片を整理",
|
||||||
"자동": "自動",
|
"자동": "自動",
|
||||||
"자동 (가로로 긴 그림만)": "自動 (横長の画像のみ)",
|
"자동 (가로로 긴 그림만)": "自動 (横長の画像のみ)",
|
||||||
|
|
@ -1986,6 +1997,7 @@
|
||||||
"저장": "保存",
|
"저장": "保存",
|
||||||
"저장 (MKV 반영)": "保存(MKV に反映)",
|
"저장 (MKV 반영)": "保存(MKV に反映)",
|
||||||
"저장 (MKV 반영) *": "保存(MKV に反映)*",
|
"저장 (MKV 반영) *": "保存(MKV に反映)*",
|
||||||
|
"저장 공간 분석": "ストレージ分析",
|
||||||
"저장 공간 정리": "ストレージの整理",
|
"저장 공간 정리": "ストレージの整理",
|
||||||
"저장 공간 정리: 파일 {0}개 · {1}": "ストレージの整理: ファイル {0}個・{1}",
|
"저장 공간 정리: 파일 {0}개 · {1}": "ストレージの整理: ファイル {0}個・{1}",
|
||||||
"저장 공간이 모자랍니다": "保存容量が足りません",
|
"저장 공간이 모자랍니다": "保存容量が足りません",
|
||||||
|
|
@ -2269,6 +2281,9 @@
|
||||||
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "サイズを変えると再エンコードが必要なため、コーデックを「元のまま」から H.264 に変更しました。",
|
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "サイズを変えると再エンコードが必要なため、コーデックを「元のまま」から H.264 に変更しました。",
|
||||||
"크기순 정렬일 때만 (기본)": "サイズ順のときだけ (既定)",
|
"크기순 정렬일 때만 (기본)": "サイズ順のときだけ (既定)",
|
||||||
"크메르어": "クメール語",
|
"크메르어": "クメール語",
|
||||||
|
"큰 파일": "大きいファイル",
|
||||||
|
"큰 파일이 없습니다": "大きいファイルはありません",
|
||||||
|
"큰 폴더": "大きいフォルダー",
|
||||||
"키 있음": "キーあり",
|
"키 있음": "キーあり",
|
||||||
"타갈로그어": "タガログ語",
|
"타갈로그어": "タガログ語",
|
||||||
"타밀어": "タミル語",
|
"타밀어": "タミル語",
|
||||||
|
|
@ -2290,6 +2305,7 @@
|
||||||
"트레이의 JJ_MKVMaker 아이콘을 누르면 열립니다.": "トレイの JJ_MKVMaker アイコンをクリックすると開きます。",
|
"트레이의 JJ_MKVMaker 아이콘을 누르면 열립니다.": "トレイの JJ_MKVMaker アイコンをクリックすると開きます。",
|
||||||
"파일": "ファイル",
|
"파일": "ファイル",
|
||||||
"파일 {0}/{1}": "ファイル {0}/{1}",
|
"파일 {0}/{1}": "ファイル {0}/{1}",
|
||||||
|
"파일 {0}개": "ファイル {0} 件",
|
||||||
"파일 {0}개 · {1}": "ファイル {0} 件 · {1}",
|
"파일 {0}개 · {1}": "ファイル {0} 件 · {1}",
|
||||||
"파일 관리자에서 보기": "ファイルマネージャーで表示",
|
"파일 관리자에서 보기": "ファイルマネージャーで表示",
|
||||||
"파일 복사 · 이동 방법": "ファイルのコピー · 移動方法",
|
"파일 복사 · 이동 방법": "ファイルのコピー · 移動方法",
|
||||||
|
|
@ -2382,6 +2398,8 @@
|
||||||
"표준": "標準",
|
"표준": "標準",
|
||||||
"푸는 중": "展開中",
|
"푸는 중": "展開中",
|
||||||
"푸는 중: {0}": "展開中: {0}",
|
"푸는 중: {0}": "展開中: {0}",
|
||||||
|
"풀 수 없는 이름 {0}개": "展開できない名前 {0} 件",
|
||||||
|
"풀 수 없는 이름 {0}개 (건너뜀): {1}": "展開できない名前 {0} 件 (スキップ): {1}",
|
||||||
"풀기": "展開",
|
"풀기": "展開",
|
||||||
"풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?": "展開後のサイズ ({0}) が空き容量 ({1}) より大きいです。それでも展開しますか?",
|
"풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?": "展開後のサイズ ({0}) が空き容量 ({1}) より大きいです。それでも展開しますか?",
|
||||||
"풀지 못했습니다: {0}": "展開できませんでした: {0}",
|
"풀지 못했습니다: {0}": "展開できませんでした: {0}",
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,7 @@
|
||||||
"WebDAV {0} 실패: {1} {2}{3}": "WebDAV {0} 失败: {1} {2}{3}",
|
"WebDAV {0} 실패: {1} {2}{3}": "WebDAV {0} 失败: {1} {2}{3}",
|
||||||
"WebDAV · OpenSubtitles 등. 서버 목록과 아이디는 남깁니다": "WebDAV · OpenSubtitles 等。保留服务器列表和用户名",
|
"WebDAV · OpenSubtitles 등. 서버 목록과 아이디는 남깁니다": "WebDAV · OpenSubtitles 等。保留服务器列表和用户名",
|
||||||
"WebDAV · OpenSubtitles 비밀번호를 이 기기의 안전 저장소에 둡니다. 끄면 저장하지 않고 (있던 것도 지움) 앱을 켤 때마다 다시 넣습니다": "将 WebDAV · OpenSubtitles 密码保存在本设备的安全存储中。关闭后不保存 (已有的也会删除),每次启动应用时重新输入",
|
"WebDAV · OpenSubtitles 비밀번호를 이 기기의 안전 저장소에 둡니다. 끄면 저장하지 않고 (있던 것도 지움) 앱을 켤 때마다 다시 넣습니다": "将 WebDAV · OpenSubtitles 密码保存在本设备的安全存储中。关闭后不保存 (已有的也会删除),每次启动应用时重新输入",
|
||||||
|
"WebDAV 는 서버에서 안의 것을 모두 읽어 오래 걸릴 수 있습니다. 분석할까요?": "WebDAV 需要从服务器读取全部内容,可能需要较长时间。要分析吗?",
|
||||||
"WebDAV 서버 \"{0}\" 을(를) 지울까요?": "删除 WebDAV 服务器“{0}”吗?",
|
"WebDAV 서버 \"{0}\" 을(를) 지울까요?": "删除 WebDAV 服务器“{0}”吗?",
|
||||||
"WebDAV 서버 고치기": "编辑 WebDAV 服务器",
|
"WebDAV 서버 고치기": "编辑 WebDAV 服务器",
|
||||||
"WebDAV 서버 목록과 그 저장된 비밀번호도 지워집니다. 동영상 목록 · 즐겨찾기 · 파일은 그대로입니다": "WebDAV 服务器列表及其保存的密码也会被删除。视频列表 · 收藏 · 文件保持不变",
|
"WebDAV 서버 목록과 그 저장된 비밀번호도 지워집니다. 동영상 목록 · 즐겨찾기 · 파일은 그대로입니다": "WebDAV 服务器列表及其保存的密码也会被删除。视频列表 · 收藏 · 文件保持不变",
|
||||||
|
|
@ -435,6 +436,7 @@
|
||||||
"{0} · {1}{2} (메인 화면 상단에서 변경)": "{0} · {1}{2}(在主界面顶部更改)",
|
"{0} · {1}{2} (메인 화면 상단에서 변경)": "{0} · {1}{2}(在主界面顶部更改)",
|
||||||
"{0} · {1}배 · {2}": "{0} · {1} 倍 · {2}",
|
"{0} · {1}배 · {2}": "{0} · {1} 倍 · {2}",
|
||||||
"{0} · 링크 {1}개 제외": "{0} · 已排除 {1} 个链接",
|
"{0} · 링크 {1}개 제외": "{0} · 已排除 {1} 个链接",
|
||||||
|
"{0} · 파일 {1}개": "{0} · {1} 个文件",
|
||||||
"{0} → {1} 번역 {2}%": "{0} → {1} 翻译 {2}%",
|
"{0} → {1} 번역 {2}%": "{0} → {1} 翻译 {2}%",
|
||||||
"{0} 는 이미 끝났습니다": "{0} 已经完成",
|
"{0} 는 이미 끝났습니다": "{0} 已经完成",
|
||||||
"{0} 라이선스": "{0} 许可证",
|
"{0} 라이선스": "{0} 许可证",
|
||||||
|
|
@ -510,6 +512,7 @@
|
||||||
"{0}개 항목을 지울까요? 되돌릴 수 없습니다.": "删除 {0} 个项目吗? 此操作无法撤销。",
|
"{0}개 항목을 지울까요? 되돌릴 수 없습니다.": "删除 {0} 个项目吗? 此操作无法撤销。",
|
||||||
"{0}개 항목을 휴지통으로 보낼까요?": "将 {0} 个项目移到回收站吗?",
|
"{0}개 항목을 휴지통으로 보낼까요?": "将 {0} 个项目移到回收站吗?",
|
||||||
"{0}개 항목을 휴지통으로 보냈습니다.": "已将 {0} 个项目移到回收站。",
|
"{0}개 항목을 휴지통으로 보냈습니다.": "已将 {0} 个项目移到回收站。",
|
||||||
|
"{0}개는 더 새 파일이라 그대로 둠": "{0} 个因现有文件更新而保留",
|
||||||
"{0}개는 원본에 남음 (건너뜀)": "{0} 个留在源中(已跳过)",
|
"{0}개는 원본에 남음 (건너뜀)": "{0} 个留在源中(已跳过)",
|
||||||
"{0}개는 지우지 않았습니다 (취소).": "{0} 个未删除 (已取消)。",
|
"{0}개는 지우지 않았습니다 (취소).": "{0} 个未删除 (已取消)。",
|
||||||
"{0}개는 휴지통보다 커서 영구 삭제했습니다 (Windows 가 물은 대로).": "{0} 个超出回收站容量,已永久删除 (按 Windows 的提示)。",
|
"{0}개는 휴지통보다 커서 영구 삭제했습니다 (Windows 가 물은 대로).": "{0} 个超出回收站容量,已永久删除 (按 Windows 的提示)。",
|
||||||
|
|
@ -924,6 +927,8 @@
|
||||||
"덮어쓰기 (원래 파일은 없어집니다)": "覆盖 (原文件将被替换)",
|
"덮어쓰기 (원래 파일은 없어집니다)": "覆盖 (原文件将被替换)",
|
||||||
"덮어쓰기여도 다 만든 뒤에만 바꿉니다 (도중에 취소 · 실패하면 예전 MKV 그대로)": "即使覆盖,也只在制作完成后才替换(中途取消 · 失败时保留原来的 MKV)",
|
"덮어쓰기여도 다 만든 뒤에만 바꿉니다 (도중에 취소 · 실패하면 예전 MKV 그대로)": "即使覆盖,也只在制作完成后才替换(中途取消 · 失败时保留原来的 MKV)",
|
||||||
"덮어쓰기여도 다 만든 뒤에만 바꿉니다. 도중에 취소 · 실패하면 예전 MKV 는 그대로 남습니다. 늘 같게 하려면 환경 설정 > MKV 만들기 > 만들 MKV 가 이미 있으면": "即使覆盖,也只在制作完成后才替换。中途取消 · 失败时原来的 MKV 会保留。若要始终相同:设置 > 制作 MKV > 要制作的 MKV 已存在时",
|
"덮어쓰기여도 다 만든 뒤에만 바꿉니다. 도중에 취소 · 실패하면 예전 MKV 는 그대로 남습니다. 늘 같게 하려면 환경 설정 > MKV 만들기 > 만들 MKV 가 이미 있으면": "即使覆盖,也只在制作完成后才替换。中途取消 · 失败时原来的 MKV 会保留。若要始终相同:设置 > 制作 MKV > 要制作的 MKV 已存在时",
|
||||||
|
"덮어쓴 파일은 되돌릴 수 없습니다 (받는 쪽이 더 새 파일은 그대로 둡니다).": "被覆盖的文件无法恢复 (目标处较新的文件会保留)。",
|
||||||
|
"덮어쓴 파일은 되돌릴 수 없습니다.": "被覆盖的文件无法恢复。",
|
||||||
"덮어씁니다": "覆盖",
|
"덮어씁니다": "覆盖",
|
||||||
"덴마크어": "丹麦语",
|
"덴마크어": "丹麦语",
|
||||||
"독일어": "德语",
|
"독일어": "德语",
|
||||||
|
|
@ -1004,6 +1009,7 @@
|
||||||
"링크 {0} (건너뜀)": "链接 {0}(跳过)",
|
"링크 {0} (건너뜀)": "链接 {0}(跳过)",
|
||||||
"링크 {0}개는 넣지 않음": "{0} 个链接未包含",
|
"링크 {0}개는 넣지 않음": "{0} 个链接未包含",
|
||||||
"링크 {0}개는 만들지 않고 건너뜁니다: {1}": "{0} 个链接将跳过 (不创建): {1}",
|
"링크 {0}개는 만들지 않고 건너뜁니다: {1}": "{0} 个链接将跳过 (不创建): {1}",
|
||||||
|
"링크 {0}개는 세지 않음": "{0} 个链接未计入",
|
||||||
"링크 · junction 은 넣지 않습니다": "不包含链接和 junction",
|
"링크 · junction 은 넣지 않습니다": "不包含链接和 junction",
|
||||||
"마스터 · 최상 비밀번호": "主密码和最高密码",
|
"마스터 · 최상 비밀번호": "主密码和最高密码",
|
||||||
"마스터 비밀번호": "主密码",
|
"마스터 비밀번호": "主密码",
|
||||||
|
|
@ -1270,8 +1276,10 @@
|
||||||
"복사했습니다.": "已复制。",
|
"복사했습니다.": "已复制。",
|
||||||
"볼 그림이 없습니다.": "没有可显示的图片。",
|
"볼 그림이 없습니다.": "没有可显示的图片。",
|
||||||
"볼 수 없는 영상입니다 (삭제 · 지역 제한).": "该视频无法观看(已删除 · 地区限制)。",
|
"볼 수 없는 영상입니다 (삭제 · 지역 제한).": "该视频无法观看(已删除 · 地区限制)。",
|
||||||
|
"분석": "分析",
|
||||||
"분석 실패: {0} - {1}": "分析失败: {0} - {1}",
|
"분석 실패: {0} - {1}": "分析失败: {0} - {1}",
|
||||||
"분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "已重新分析之前分析失败的 {0} 个视频",
|
"분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "已重新分析之前分析失败的 {0} 个视频",
|
||||||
|
"분석하지 못했습니다: {0}": "无法分析: {0}",
|
||||||
"붙여넣기": "粘贴",
|
"붙여넣기": "粘贴",
|
||||||
"붙여넣을 항목이 없습니다. 먼저 [복사] · [이동] 으로 담으세요.": "没有可粘贴的项目。请先用 [复制] · [移动] 暂存。",
|
"붙여넣을 항목이 없습니다. 먼저 [복사] · [이동] 으로 담으세요.": "没有可粘贴的项目。请先用 [复制] · [移动] 暂存。",
|
||||||
"브라우저": "浏览器",
|
"브라우저": "浏览器",
|
||||||
|
|
@ -1505,6 +1513,7 @@
|
||||||
"아직 지원하지 않는 플랫폼입니다: {0}": "尚不支持此平台: {0}",
|
"아직 지원하지 않는 플랫폼입니다: {0}": "尚不支持此平台: {0}",
|
||||||
"아직 허용되지 않았습니다": "尚未允许",
|
"아직 허용되지 않았습니다": "尚未允许",
|
||||||
"안내 닫기": "关闭提示",
|
"안내 닫기": "关闭提示",
|
||||||
|
"안에 폴더가 없습니다": "里面没有文件夹",
|
||||||
"안쪽 파일을 확인하는 중 …": "正在检查里面的文件 …",
|
"안쪽 파일을 확인하는 중 …": "正在检查里面的文件 …",
|
||||||
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합쳐도 건너뜁니다 (원본에 남음)": "里面有 {0} 个文件在接收端较新 - 合并时会跳过(保留在源中)",
|
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합쳐도 건너뜁니다 (원본에 남음)": "里面有 {0} 个文件在接收端较新 - 合并时会跳过(保留在源中)",
|
||||||
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합치면 예전 것으로 덮어씁니다": "里面有 {0} 个文件在接收端较新 - 合并会用旧文件覆盖",
|
"안쪽에 받는 쪽이 더 새 파일 {0}개 - 합치면 예전 것으로 덮어씁니다": "里面有 {0} 个文件在接收端较新 - 合并会用旧文件覆盖",
|
||||||
|
|
@ -1893,12 +1902,14 @@
|
||||||
"일본어": "日语",
|
"일본어": "日语",
|
||||||
"일시정지": "暂停",
|
"일시정지": "暂停",
|
||||||
"읽기 전용 매체입니다": "只读介质",
|
"읽기 전용 매체입니다": "只读介质",
|
||||||
|
"읽는 중 · 폴더 {0}개 · {1}": "正在读取 · {0} 个文件夹 · {1}",
|
||||||
"읽을 수 없습니다": "无法读取",
|
"읽을 수 없습니다": "无法读取",
|
||||||
"읽을 수 없습니다: {0}": "无法读取: {0}",
|
"읽을 수 없습니다: {0}": "无法读取: {0}",
|
||||||
"읽을 수 없어 건너뛴 폴더: {0}": "无法读取而跳过的文件夹: {0}",
|
"읽을 수 없어 건너뛴 폴더: {0}": "无法读取而跳过的文件夹: {0}",
|
||||||
"읽을 수 없음": "无法读取",
|
"읽을 수 없음": "无法读取",
|
||||||
"읽지 못한 원래 파일은 지우지 않고 아래에 보관했습니다:": "无法读取的原文件没有删除,保存在这里:",
|
"읽지 못한 원래 파일은 지우지 않고 아래에 보관했습니다:": "无法读取的原文件没有删除,保存在这里:",
|
||||||
"읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다": "{0} 个无法读取的文件夹 (权限 · 连接问题) - 未在其中查找",
|
"읽지 못한 폴더 {0}개 (권한 · 연결 문제) - 그 안은 찾지 못했습니다": "{0} 个无法读取的文件夹 (权限 · 连接问题) - 未在其中查找",
|
||||||
|
"읽지 못한 폴더 {0}개 (크기가 실제보다 작을 수 있음)": "{0} 个文件夹无法读取 (大小可能小于实际)",
|
||||||
"임시 파일 · 남은 조각 정리": "清理临时文件 · 残留碎片",
|
"임시 파일 · 남은 조각 정리": "清理临时文件 · 残留碎片",
|
||||||
"자동": "自动",
|
"자동": "自动",
|
||||||
"자동 (가로로 긴 그림만)": "自动 (仅横向图片)",
|
"자동 (가로로 긴 그림만)": "自动 (仅横向图片)",
|
||||||
|
|
@ -1986,6 +1997,7 @@
|
||||||
"저장": "保存",
|
"저장": "保存",
|
||||||
"저장 (MKV 반영)": "保存(应用到 MKV)",
|
"저장 (MKV 반영)": "保存(应用到 MKV)",
|
||||||
"저장 (MKV 반영) *": "保存(应用到 MKV)*",
|
"저장 (MKV 반영) *": "保存(应用到 MKV)*",
|
||||||
|
"저장 공간 분석": "存储空间分析",
|
||||||
"저장 공간 정리": "存储空间清理",
|
"저장 공간 정리": "存储空间清理",
|
||||||
"저장 공간 정리: 파일 {0}개 · {1}": "存储空间清理: 文件 {0} 个 · {1}",
|
"저장 공간 정리: 파일 {0}개 · {1}": "存储空间清理: 文件 {0} 个 · {1}",
|
||||||
"저장 공간이 모자랍니다": "存储空间不足",
|
"저장 공간이 모자랍니다": "存储空间不足",
|
||||||
|
|
@ -2269,6 +2281,9 @@
|
||||||
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "更改大小需要重新编码,因此编解码器已从“保持原样”改为 H.264。",
|
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "更改大小需要重新编码,因此编解码器已从“保持原样”改为 H.264。",
|
||||||
"크기순 정렬일 때만 (기본)": "仅在按大小排序时 (默认)",
|
"크기순 정렬일 때만 (기본)": "仅在按大小排序时 (默认)",
|
||||||
"크메르어": "高棉语",
|
"크메르어": "高棉语",
|
||||||
|
"큰 파일": "大文件",
|
||||||
|
"큰 파일이 없습니다": "没有大文件",
|
||||||
|
"큰 폴더": "大文件夹",
|
||||||
"키 있음": "已有密钥",
|
"키 있음": "已有密钥",
|
||||||
"타갈로그어": "他加禄语",
|
"타갈로그어": "他加禄语",
|
||||||
"타밀어": "泰米尔语",
|
"타밀어": "泰米尔语",
|
||||||
|
|
@ -2290,6 +2305,7 @@
|
||||||
"트레이의 JJ_MKVMaker 아이콘을 누르면 열립니다.": "点击托盘中的 JJ_MKVMaker 图标即可打开。",
|
"트레이의 JJ_MKVMaker 아이콘을 누르면 열립니다.": "点击托盘中的 JJ_MKVMaker 图标即可打开。",
|
||||||
"파일": "文件",
|
"파일": "文件",
|
||||||
"파일 {0}/{1}": "文件 {0}/{1}",
|
"파일 {0}/{1}": "文件 {0}/{1}",
|
||||||
|
"파일 {0}개": "{0} 个文件",
|
||||||
"파일 {0}개 · {1}": "{0} 个文件 · {1}",
|
"파일 {0}개 · {1}": "{0} 个文件 · {1}",
|
||||||
"파일 관리자에서 보기": "在文件管理器中显示",
|
"파일 관리자에서 보기": "在文件管理器中显示",
|
||||||
"파일 복사 · 이동 방법": "文件复制 · 移动方式",
|
"파일 복사 · 이동 방법": "文件复制 · 移动方式",
|
||||||
|
|
@ -2382,6 +2398,8 @@
|
||||||
"표준": "标准",
|
"표준": "标准",
|
||||||
"푸는 중": "正在解压",
|
"푸는 중": "正在解压",
|
||||||
"푸는 중: {0}": "正在解压: {0}",
|
"푸는 중: {0}": "正在解压: {0}",
|
||||||
|
"풀 수 없는 이름 {0}개": "{0} 个无法解压的名称",
|
||||||
|
"풀 수 없는 이름 {0}개 (건너뜀): {1}": "{0} 个名称无法在本设备使用 (跳过): {1}",
|
||||||
"풀기": "解压",
|
"풀기": "解压",
|
||||||
"풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?": "解压后大小 ({0}) 大于剩余空间 ({1})。仍要解压吗?",
|
"풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?": "解压后大小 ({0}) 大于剩余空间 ({1})。仍要解压吗?",
|
||||||
"풀지 못했습니다: {0}": "无法解压: {0}",
|
"풀지 못했습니다: {0}": "无法解压: {0}",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import 'package:archive/archive_io.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
import '../l10n/tr.dart';
|
import '../l10n/tr.dart';
|
||||||
import 'file_ops.dart' show NameConflict;
|
import 'file_ops.dart' show NameConflict, nameProblem;
|
||||||
|
|
||||||
/// 86: 압축 종류 (확장자로)
|
/// 86: 압축 종류 (확장자로)
|
||||||
enum ArchiveKind { zip, sevenZip, rar }
|
enum ArchiveKind { zip, sevenZip, rar }
|
||||||
|
|
@ -28,7 +28,11 @@ class ArchiveEntry {
|
||||||
|
|
||||||
/// 7-Zip 에 넘길 원래 이름 (Windows 의 7z 는 '\' 로 보여 준다)
|
/// 7-Zip 에 넘길 원래 이름 (Windows 의 7z 는 '\' 로 보여 준다)
|
||||||
final String raw;
|
final String raw;
|
||||||
const ArchiveEntry(this.name, {this.size = 0, this.isDir = false, this.isLink = false, this.encrypted = false, String? raw}) : raw = raw ?? name;
|
|
||||||
|
/// 압축 안 파일의 바뀐 날 (모르면 null) - 덮어쓰기에서 받는 쪽이 더 새것인지 볼 때
|
||||||
|
final DateTime? modified;
|
||||||
|
const ArchiveEntry(this.name, {this.size = 0, this.isDir = false, this.isLink = false, this.encrypted = false, String? raw, this.modified})
|
||||||
|
: raw = raw ?? name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 비밀번호가 필요하거나 틀림
|
/// 비밀번호가 필요하거나 틀림
|
||||||
|
|
@ -64,20 +68,26 @@ class ExtractPlan {
|
||||||
/// 만들지 않고 건너뛰는 링크
|
/// 만들지 않고 건너뛰는 링크
|
||||||
final List<String> links;
|
final List<String> links;
|
||||||
|
|
||||||
|
/// 이 기기에서 쓸 수 없는 이름 (Windows: CON · NUL · ? * " < > | · 끝 점 · 빈칸 …) - (이름, 까닭), 건너뜀
|
||||||
|
final List<(String, String)> badNames;
|
||||||
|
|
||||||
/// 풀린 크기 합 (바이트)
|
/// 풀린 크기 합 (바이트)
|
||||||
final int totalSize;
|
final int totalSize;
|
||||||
|
|
||||||
/// 비밀번호가 걸린 항목이 있음
|
/// 비밀번호가 걸린 항목이 있음
|
||||||
final bool encrypted;
|
final bool encrypted;
|
||||||
const ExtractPlan(this.files, this.unsafe, this.links, this.totalSize, this.encrypted);
|
const ExtractPlan(this.files, this.unsafe, this.links, this.totalSize, this.encrypted, [this.badNames = const []]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 목록 [entries] 에서 풀 것을 고른다. [names] 가 있으면 그 이름 (폴더면 그 안 모두) 만
|
/// 목록 [entries] 에서 풀 것을 고른다. [names] 가 있으면 그 이름 (폴더면 그 안 모두) 만
|
||||||
ExtractPlan planExtract(List<ArchiveEntry> entries, {List<String> names = const []}) {
|
/// 15 지적 · 관리자 판단: 리눅스 · 맥에서 만든 압축의 이름이 Windows 에서 쓸 수 없으면 (78 의 nameProblem) 미리 걸러 건너뛴다
|
||||||
|
ExtractPlan planExtract(List<ArchiveEntry> entries, {List<String> names = const [], bool? windowsRules}) {
|
||||||
|
final win = windowsRules ?? Platform.isWindows;
|
||||||
bool wanted(String n) => names.isEmpty || names.any((x) => n == x || n.startsWith(x.endsWith('/') ? x : '$x/'));
|
bool wanted(String n) => names.isEmpty || names.any((x) => n == x || n.startsWith(x.endsWith('/') ? x : '$x/'));
|
||||||
final files = <ArchiveEntry>[];
|
final files = <ArchiveEntry>[];
|
||||||
final unsafe = <(String, String)>[];
|
final unsafe = <(String, String)>[];
|
||||||
final links = <String>[];
|
final links = <String>[];
|
||||||
|
final badNames = <(String, String)>[];
|
||||||
var size = 0;
|
var size = 0;
|
||||||
var enc = false;
|
var enc = false;
|
||||||
for (final e in entries) {
|
for (final e in entries) {
|
||||||
|
|
@ -92,11 +102,17 @@ ExtractPlan planExtract(List<ArchiveEntry> entries, {List<String> names = const
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (e.isDir) continue;
|
if (e.isDir) continue;
|
||||||
|
// 이름의 각 부분이 이 기기에서 쓸 수 있는지 (풀다가 실패하거나 7-Zip 이 다른 이름으로 바꾸지 않게)
|
||||||
|
final why = [for (final s in e.name.split('/')) if (s.isNotEmpty && s != '.') nameProblem(s, windowsRules: win)].nonNulls.firstOrNull;
|
||||||
|
if (why != null) {
|
||||||
|
badNames.add((e.name, why));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
files.add(e);
|
files.add(e);
|
||||||
size += e.size;
|
size += e.size;
|
||||||
enc |= e.encrypted;
|
enc |= e.encrypted;
|
||||||
}
|
}
|
||||||
return ExtractPlan(files, unsafe, links, size, enc);
|
return ExtractPlan(files, unsafe, links, size, enc, badNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 풀 때 [dest] 안의 자리 ([name] 은 [unsafeArchivePath] 를 지난 것)
|
/// 풀 때 [dest] 안의 자리 ([name] 은 [unsafeArchivePath] 를 지난 것)
|
||||||
|
|
@ -118,6 +134,25 @@ class ExtractResult {
|
||||||
int done = 0;
|
int done = 0;
|
||||||
final skippedExisting = <String>[];
|
final skippedExisting = <String>[];
|
||||||
final renamed = <String>[];
|
final renamed = <String>[];
|
||||||
|
|
||||||
|
/// 덮어쓰기인데 받는 쪽이 더 새것이라 그대로 둔 것 (229 와 같은 규칙)
|
||||||
|
final keptNewer = <String>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 덮어쓰기에서 건너뛸지: 받는 쪽 파일이 압축 안의 것보다 2초 넘게 새것
|
||||||
|
bool _targetNewer(String target, DateTime? modified) {
|
||||||
|
if (modified == null) return false;
|
||||||
|
try {
|
||||||
|
return File(target).lastModifiedSync().isAfter(modified.add(const Duration(seconds: 2)));
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ZIP 의 MS-DOS 날짜 · 시각 (지역 시각) → DateTime (0 이면 null)
|
||||||
|
DateTime? dosDateTime(int date, int time) {
|
||||||
|
if (date == 0) return null;
|
||||||
|
return DateTime(1980 + (date >> 9), (date >> 5) & 0xF, date & 0x1F, time >> 11, (time >> 5) & 0x3F, (time & 0x1F) * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 취소 (풀기 · 묶기 도중)
|
/// 취소 (풀기 · 묶기 도중)
|
||||||
|
|
@ -142,7 +177,8 @@ Future<List<ArchiveEntry>> listZip(String path) async {
|
||||||
isDir: h.filename.endsWith('/') || h.filename.endsWith('\\'),
|
isDir: h.filename.endsWith('/') || h.filename.endsWith('\\'),
|
||||||
// 유닉스 파일 종류 (상위 16비트) 가 심볼릭 링크 (0xA000)
|
// 유닉스 파일 종류 (상위 16비트) 가 심볼릭 링크 (0xA000)
|
||||||
isLink: (h.externalFileAttributes >> 16) & 0xF000 == 0xA000,
|
isLink: (h.externalFileAttributes >> 16) & 0xF000 == 0xA000,
|
||||||
encrypted: h.generalPurposeBitFlag & 0x1 != 0),
|
encrypted: h.generalPurposeBitFlag & 0x1 != 0,
|
||||||
|
modified: dosDateTime(h.lastModifiedFileDate, h.lastModifiedFileTime)),
|
||||||
];
|
];
|
||||||
} finally {
|
} finally {
|
||||||
await input.close();
|
await input.close();
|
||||||
|
|
@ -151,7 +187,11 @@ Future<List<ArchiveEntry>> listZip(String path) async {
|
||||||
|
|
||||||
/// ZIP 풀기 - [plan] 의 파일만, 같은 이름은 [conflict] 대로
|
/// ZIP 풀기 - [plan] 의 파일만, 같은 이름은 [conflict] 대로
|
||||||
Future<ExtractResult> extractZipPlan(String path, String dest, ExtractPlan plan,
|
Future<ExtractResult> extractZipPlan(String path, String dest, ExtractPlan plan,
|
||||||
{NameConflict conflict = NameConflict.rename, String? password, void Function(int done, int total)? onProgress, bool Function()? cancelled}) async {
|
{NameConflict conflict = NameConflict.rename,
|
||||||
|
bool skipNewer = false,
|
||||||
|
String? password,
|
||||||
|
void Function(int done, int total)? onProgress,
|
||||||
|
bool Function()? cancelled}) async {
|
||||||
// 비밀번호가 걸린 항목인데 비밀번호가 없으면 (암호 글자를 그대로 쓰지 않게) 먼저 묻는다
|
// 비밀번호가 걸린 항목인데 비밀번호가 없으면 (암호 글자를 그대로 쓰지 않게) 먼저 묻는다
|
||||||
if (plan.encrypted && (password == null || password.isEmpty)) throw const ArchivePasswordException();
|
if (plan.encrypted && (password == null || password.isEmpty)) throw const ArchivePasswordException();
|
||||||
final input = InputFileStream(path);
|
final input = InputFileStream(path);
|
||||||
|
|
@ -160,6 +200,7 @@ Future<ExtractResult> extractZipPlan(String path, String dest, ExtractPlan plan,
|
||||||
// verify: CRC 를 맞춰 보아 비밀번호가 틀리면 (풀린 내용이 다르면) 실패로
|
// verify: CRC 를 맞춰 보아 비밀번호가 틀리면 (풀린 내용이 다르면) 실패로
|
||||||
final a = ZipDecoder().decodeStream(input, password: password, verify: plan.encrypted);
|
final a = ZipDecoder().decodeStream(input, password: password, verify: plan.encrypted);
|
||||||
final want = {for (final e in plan.files) e.name};
|
final want = {for (final e in plan.files) e.name};
|
||||||
|
final modified = {for (final e in plan.files) e.name: e.modified};
|
||||||
final todo = [for (final f in a.files) if (f.isFile && want.contains(f.name.replaceAll('\\', '/'))) f];
|
final todo = [for (final f in a.files) if (f.isFile && want.contains(f.name.replaceAll('\\', '/'))) f];
|
||||||
for (final f in todo) {
|
for (final f in todo) {
|
||||||
if (cancelled?.call() ?? false) throw const ArchiveCancelled();
|
if (cancelled?.call() ?? false) throw const ArchiveCancelled();
|
||||||
|
|
@ -173,6 +214,11 @@ Future<ExtractResult> extractZipPlan(String path, String dest, ExtractPlan plan,
|
||||||
target = _freeName(target);
|
target = _freeName(target);
|
||||||
r.renamed.add(f.name);
|
r.renamed.add(f.name);
|
||||||
}
|
}
|
||||||
|
// 관리자 판단: 덮어쓰기에도 "받는 쪽이 더 새것이면 건너뛰기" (229)
|
||||||
|
if (conflict == NameConflict.overwrite && skipNewer && _targetNewer(target, modified[f.name.replaceAll('\\', '/')])) {
|
||||||
|
r.keptNewer.add(f.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await Directory(p.dirname(target)).create(recursive: true);
|
await Directory(p.dirname(target)).create(recursive: true);
|
||||||
final bytes = f.readBytes();
|
final bytes = f.readBytes();
|
||||||
|
|
@ -270,6 +316,7 @@ List<ArchiveEntry> parse7zList(String out) {
|
||||||
entries.add(ArchiveEntry(raw.replaceAll('\\', '/'),
|
entries.add(ArchiveEntry(raw.replaceAll('\\', '/'),
|
||||||
raw: raw,
|
raw: raw,
|
||||||
size: int.tryParse(m['Size'] ?? '') ?? 0,
|
size: int.tryParse(m['Size'] ?? '') ?? 0,
|
||||||
|
modified: DateTime.tryParse((m['Modified'] ?? '').replaceFirst(' ', 'T')),
|
||||||
isDir: (m['Folder'] ?? '') == '+' || RegExp(r'^[A-Z_]*D').hasMatch(attr),
|
isDir: (m['Folder'] ?? '') == '+' || RegExp(r'^[A-Z_]*D').hasMatch(attr),
|
||||||
isLink: link,
|
isLink: link,
|
||||||
encrypted: (m['Encrypted'] ?? '') == '+'));
|
encrypted: (m['Encrypted'] ?? '') == '+'));
|
||||||
|
|
@ -326,8 +373,18 @@ Future<List<ArchiveEntry>> list7z(String exe, String path, {String? password}) a
|
||||||
|
|
||||||
/// 7z · RAR 풀기 - [plan] 의 파일만 (목록 파일로 넘김, 링크 · 막은 것은 빠짐). 같은 이름은 [conflict] 대로
|
/// 7z · RAR 풀기 - [plan] 의 파일만 (목록 파일로 넘김, 링크 · 막은 것은 빠짐). 같은 이름은 [conflict] 대로
|
||||||
Future<ExtractResult> extract7zPlan(String exe, String path, String dest, ExtractPlan plan,
|
Future<ExtractResult> extract7zPlan(String exe, String path, String dest, ExtractPlan plan,
|
||||||
{NameConflict conflict = NameConflict.rename, String? password, void Function(double v)? onProgress, bool Function()? cancelled}) async {
|
{NameConflict conflict = NameConflict.rename,
|
||||||
|
bool skipNewer = false,
|
||||||
|
String? password,
|
||||||
|
void Function(double v)? onProgress,
|
||||||
|
bool Function()? cancelled}) async {
|
||||||
final r = ExtractResult();
|
final r = ExtractResult();
|
||||||
|
// 관리자 판단: 덮어쓰기 (-aoa) 에도 받는 쪽이 더 새 파일은 목록에서 빼서 그대로 둔다 (229)
|
||||||
|
if (conflict == NameConflict.overwrite && skipNewer) {
|
||||||
|
final keep = [for (final e in plan.files) if (_targetNewer(extractTarget(dest, e.name), e.modified)) e];
|
||||||
|
r.keptNewer.addAll(keep.map((e) => e.name));
|
||||||
|
plan = ExtractPlan([for (final e in plan.files) if (!keep.contains(e)) e], plan.unsafe, plan.links, plan.totalSize, plan.encrypted, plan.badNames);
|
||||||
|
}
|
||||||
if (plan.files.isEmpty) return r;
|
if (plan.files.isEmpty) return r;
|
||||||
final tmp = await Directory.systemTemp.createTemp('jj_7z_');
|
final tmp = await Directory.systemTemp.createTemp('jj_7z_');
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,12 @@ Future<String?> extractArchiveFlow(BuildContext context, AppController c, String
|
||||||
[plan.unsafe.length, plan.unsafe.take(5).map((x) => '${x.$1} (${x.$2})').join(', ')]),
|
[plan.unsafe.length, plan.unsafe.take(5).map((x) => '${x.$1} (${x.$2})').join(', ')]),
|
||||||
style: const TextStyle(color: JjColors.danger, fontSize: 12)),
|
style: const TextStyle(color: JjColors.danger, fontSize: 12)),
|
||||||
],
|
],
|
||||||
|
if (plan.badNames.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(trf('풀 수 없는 이름 {0}개 (건너뜀): {1}',
|
||||||
|
[plan.badNames.length, plan.badNames.take(5).map((x) => '${x.$1} (${x.$2})').join(', ')]),
|
||||||
|
style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)),
|
||||||
|
],
|
||||||
if (plan.links.isNotEmpty) ...[
|
if (plan.links.isNotEmpty) ...[
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(trf('링크 {0}개는 만들지 않고 건너뜁니다: {1}', [plan.links.length, plan.links.take(5).join(', ')]),
|
Text(trf('링크 {0}개는 만들지 않고 건너뜁니다: {1}', [plan.links.length, plan.links.take(5).join(', ')]),
|
||||||
|
|
@ -201,6 +207,13 @@ Future<String?> extractArchiveFlow(BuildContext context, AppController c, String
|
||||||
onChanged: (v) => set(() => conflict = v!),
|
onChanged: (v) => set(() => conflict = v!),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
// 관리자 판단: 덮어쓴 파일은 휴지통에 남지 않는다 - 되돌릴 수 없다고 알린다 (받는 쪽이 더 새것이면 설정대로 그대로 둠)
|
||||||
|
if (conflict == NameConflict.overwrite)
|
||||||
|
Text(
|
||||||
|
c.settings.copySkipNewer
|
||||||
|
? tr('덮어쓴 파일은 되돌릴 수 없습니다 (받는 쪽이 더 새 파일은 그대로 둡니다).')
|
||||||
|
: tr('덮어쓴 파일은 되돌릴 수 없습니다.'),
|
||||||
|
style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)),
|
||||||
// 관리자 판단: 풀린 크기가 빈 공간보다 크면 풀기 전에 알리고 묻는다 (압축 폭탄 · 공간 부족)
|
// 관리자 판단: 풀린 크기가 빈 공간보다 크면 풀기 전에 알리고 묻는다 (압축 폭탄 · 공간 부족)
|
||||||
if (short)
|
if (short)
|
||||||
Text(trf('풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?', [formatSize(plan.totalSize), formatSize(f)]),
|
Text(trf('풀린 크기 ({0}) 가 남은 공간 ({1}) 보다 큽니다. 그래도 풀까요?', [formatSize(plan.totalSize), formatSize(f)]),
|
||||||
|
|
@ -229,16 +242,23 @@ Future<String?> extractArchiveFlow(BuildContext context, AppController c, String
|
||||||
final r = await _withProgress(context, trf('압축 푸는 중 · {0}', [vBasename(path)]), (set, cancelled) async {
|
final r = await _withProgress(context, trf('압축 푸는 중 · {0}', [vBasename(path)]), (set, cancelled) async {
|
||||||
if (kind == ArchiveKind.zip) {
|
if (kind == ArchiveKind.zip) {
|
||||||
return extractZipPlan(local, dest, plan,
|
return extractZipPlan(local, dest, plan,
|
||||||
conflict: conflict, password: pw, cancelled: cancelled, onProgress: (d, t) => set(t == 0 ? null : d / t));
|
conflict: conflict,
|
||||||
|
skipNewer: c.settings.copySkipNewer,
|
||||||
|
password: pw,
|
||||||
|
cancelled: cancelled,
|
||||||
|
onProgress: (d, t) => set(t == 0 ? null : d / t));
|
||||||
}
|
}
|
||||||
return extract7zPlan(exe!, local, dest, plan, conflict: conflict, password: pw, cancelled: cancelled, onProgress: set);
|
return extract7zPlan(exe!, local, dest, plan,
|
||||||
|
conflict: conflict, skipNewer: c.settings.copySkipNewer, password: pw, cancelled: cancelled, onProgress: set);
|
||||||
});
|
});
|
||||||
say([
|
say([
|
||||||
trf('{0}개를 풀었습니다: {1}', [r.done, vDisplay(dest)]),
|
trf('{0}개를 풀었습니다: {1}', [r.done, vDisplay(dest)]),
|
||||||
if (r.skippedExisting.isNotEmpty) trf('같은 이름이라 건너뜀 {0}개', [r.skippedExisting.length]),
|
if (r.skippedExisting.isNotEmpty) trf('같은 이름이라 건너뜀 {0}개', [r.skippedExisting.length]),
|
||||||
if (r.renamed.isNotEmpty) trf('이름을 바꿔 풂 {0}개', [r.renamed.length]),
|
if (r.renamed.isNotEmpty) trf('이름을 바꿔 풂 {0}개', [r.renamed.length]),
|
||||||
|
if (r.keptNewer.isNotEmpty) trf('{0}개는 더 새 파일이라 그대로 둠', [r.keptNewer.length]),
|
||||||
if (plan.unsafe.isNotEmpty) trf('막은 경로 {0}개', [plan.unsafe.length]),
|
if (plan.unsafe.isNotEmpty) trf('막은 경로 {0}개', [plan.unsafe.length]),
|
||||||
if (plan.links.isNotEmpty) trf('건너뛴 링크 {0}개', [plan.links.length]),
|
if (plan.links.isNotEmpty) trf('건너뛴 링크 {0}개', [plan.links.length]),
|
||||||
|
if (plan.badNames.isNotEmpty) trf('풀 수 없는 이름 {0}개', [plan.badNames.length]),
|
||||||
].join(' · '));
|
].join(' · '));
|
||||||
return dest;
|
return dest;
|
||||||
} on ArchivePasswordException catch (e) {
|
} on ArchivePasswordException catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,18 @@ void main() {
|
||||||
expect(plan.encrypted, isTrue);
|
expect(plan.encrypted, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('86 보충: Windows 에서 쓸 수 없는 이름 (CON · ? · 끝 점) 은 미리 걸러 건너뛴다 (다른 것은 그대로)', () {
|
||||||
|
final plan = planExtract([
|
||||||
|
const ArchiveEntry('ok/a.txt'),
|
||||||
|
const ArchiveEntry('con.txt'),
|
||||||
|
const ArchiveEntry('dir/what?.txt'),
|
||||||
|
const ArchiveEntry('end./x.txt'),
|
||||||
|
], windowsRules: true);
|
||||||
|
expect(plan.files.map((e) => e.name), ['ok/a.txt']);
|
||||||
|
expect(plan.badNames.map((x) => x.$1), ['con.txt', 'dir/what?.txt', 'end./x.txt']);
|
||||||
|
expect(planExtract([const ArchiveEntry('con.txt')], windowsRules: false).files, hasLength(1), reason: 'Android 는 지금처럼');
|
||||||
|
});
|
||||||
|
|
||||||
group('ZIP', () {
|
group('ZIP', () {
|
||||||
test('만들기 (폴더 · 파일, 링크는 넣지 않음) → 목록 → 풀기 (같은 이름은 고른 대로)', () async {
|
test('만들기 (폴더 · 파일, 링크는 넣지 않음) → 목록 → 풀기 (같은 이름은 고른 대로)', () async {
|
||||||
final src = Directory(p.join(tmp.path, 'src'))..createSync();
|
final src = Directory(p.join(tmp.path, 'src'))..createSync();
|
||||||
|
|
@ -73,6 +85,23 @@ void main() {
|
||||||
expect(File(p.join(dest.path, 'one.txt')).readAsStringSync(), '1');
|
expect(File(p.join(dest.path, 'one.txt')).readAsStringSync(), '1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('86 보충: 덮어쓰기에도 받는 쪽이 더 새 파일은 그대로 (229 와 같게)', () async {
|
||||||
|
final a = Archive()
|
||||||
|
..addFile(ArchiveFile.string('old.txt', 'from zip')..lastModTime = DateTime(2020, 1, 1).millisecondsSinceEpoch ~/ 1000)
|
||||||
|
..addFile(ArchiveFile.string('new.txt', 'from zip')..lastModTime = DateTime(2020, 1, 1).millisecondsSinceEpoch ~/ 1000);
|
||||||
|
final zip = p.join(tmp.path, 'n.zip');
|
||||||
|
File(zip).writeAsBytesSync(ZipEncoder().encode(a));
|
||||||
|
final dest = Directory(p.join(tmp.path, 'd'))..createSync();
|
||||||
|
final older = File(p.join(dest.path, 'old.txt'))..writeAsStringSync('mine');
|
||||||
|
older.setLastModifiedSync(DateTime(2019, 1, 1));
|
||||||
|
File(p.join(dest.path, 'new.txt')).writeAsStringSync('mine'); // 지금 = 더 새것
|
||||||
|
final plan = planExtract(await listZip(zip));
|
||||||
|
final r = await extractZipPlan(zip, dest.path, plan, conflict: NameConflict.overwrite, skipNewer: true);
|
||||||
|
expect(r.keptNewer, ['new.txt']);
|
||||||
|
expect(File(p.join(dest.path, 'new.txt')).readAsStringSync(), 'mine');
|
||||||
|
expect(File(p.join(dest.path, 'old.txt')).readAsStringSync(), 'from zip');
|
||||||
|
});
|
||||||
|
|
||||||
test('zip slip: ../ · 드라이브 경로 · 링크 항목은 대상 밖에 쓰지 않고 알린다', () async {
|
test('zip slip: ../ · 드라이브 경로 · 링크 항목은 대상 밖에 쓰지 않고 알린다', () async {
|
||||||
final a = Archive()
|
final a = Archive()
|
||||||
..addFile(ArchiveFile.string('ok.txt', 'ok'))
|
..addFile(ArchiveFile.string('ok.txt', 'ok'))
|
||||||
|
|
@ -149,6 +178,13 @@ void main() {
|
||||||
expect(File(p.join(dest, 'src', 'a_1.txt')).existsSync() || File(p.join(dest, 'src', 'a (2).txt')).existsSync() ||
|
expect(File(p.join(dest, 'src', 'a_1.txt')).existsSync() || File(p.join(dest, 'src', 'a (2).txt')).existsSync() ||
|
||||||
Directory(p.join(dest, 'src')).listSync().length > 3, isTrue);
|
Directory(p.join(dest, 'src')).listSync().length > 3, isTrue);
|
||||||
|
|
||||||
|
// 86 보충: 덮어쓰기에도 받는 쪽이 더 새 파일은 그대로 (7z)
|
||||||
|
final mine = File(p.join(dest, 'src', 'a.txt'))..writeAsStringSync('mine');
|
||||||
|
mine.setLastModifiedSync(DateTime.now().add(const Duration(days: 1)));
|
||||||
|
final keep = await extract7zPlan(exe, plain, dest, planExtract(list), conflict: NameConflict.overwrite, skipNewer: true);
|
||||||
|
expect(keep.keptNewer, ['src/a.txt']);
|
||||||
|
expect(mine.readAsStringSync(), 'mine');
|
||||||
|
|
||||||
await expectLater(list7z(exe, enc), throwsA(isA<ArchivePasswordException>()));
|
await expectLater(list7z(exe, enc), throwsA(isA<ArchivePasswordException>()));
|
||||||
await expectLater(list7z(exe, enc, password: 'nope'), throwsA(isA<ArchivePasswordException>().having((e) => e.wrong, 'wrong', isTrue)));
|
await expectLater(list7z(exe, enc, password: 'nope'), throwsA(isA<ArchivePasswordException>().having((e) => e.wrong, 'wrong', isTrue)));
|
||||||
final encList = await list7z(exe, enc, password: 'pw2');
|
final encList = await list7z(exe, enc, password: 'pw2');
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,13 @@ if (-not ($list -contains "$name/jj_mkvmaker.exe")) {
|
||||||
Remove-Item $Zip -Force
|
Remove-Item $Zip -Force
|
||||||
throw "zip 맨 위에 $name/jj_mkvmaker.exe 가 없습니다"
|
throw "zip 맨 위에 $name/jj_mkvmaker.exe 가 없습니다"
|
||||||
}
|
}
|
||||||
|
# 86 (관리자 판단): 7-Zip 이 빠진 채 정식 판이 나가지 않게 (CMake 는 없으면 경고만 하고 빼고 빌드한다)
|
||||||
|
$need = @("$name/Lib/7zip/7z.exe", "$name/Lib/7zip/7z.dll", "$name/Lib/7zip/License.txt")
|
||||||
|
$missing = $need | Where-Object { -not ($list -contains $_) }
|
||||||
|
if ($missing) {
|
||||||
|
Remove-Item $Zip -Force
|
||||||
|
throw ("7-Zip 이 빠졌습니다 (tool\fetch_7zip.ps1 로 받은 뒤 다시 빌드):`n" + ($missing -join "`n"))
|
||||||
|
}
|
||||||
$item = Get-Item $Zip
|
$item = Get-Item $Zip
|
||||||
$hash = (Get-FileHash $Zip -Algorithm SHA256).Hash.ToLower()
|
$hash = (Get-FileHash $Zip -Algorithm SHA256).Hash.ToLower()
|
||||||
"{0} {1} bytes {2} files sha256 {3}" -f $item.Name, $item.Length, $list.Count, $hash
|
"{0} {1} bytes {2} files sha256 {3}" -f $item.Name, $item.Length, $list.Count, $hash
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,9 @@ file(GLOB JJ_7ZIP_FILES "${JJ_7ZIP_DIR}/7z.exe" "${JJ_7ZIP_DIR}/7z.dll" "${JJ_7Z
|
||||||
if(JJ_7ZIP_FILES)
|
if(JJ_7ZIP_FILES)
|
||||||
install(FILES ${JJ_7ZIP_FILES}
|
install(FILES ${JJ_7ZIP_FILES}
|
||||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/7zip" COMPONENT Runtime)
|
DESTINATION "${CMAKE_INSTALL_PREFIX}/7zip" COMPONENT Runtime)
|
||||||
|
else()
|
||||||
|
# 개발 빌드는 그대로 (7z · RAR 풀기만 빠짐). 정식 판은 make_zip.ps1 검사가 막는다
|
||||||
|
message(WARNING "7-Zip 이 없습니다 (third_party/7zip/windows) - 7z · RAR 풀기가 빠집니다. tool\\fetch_7zip.ps1 로 받으세요.")
|
||||||
endif()
|
endif()
|
||||||
install(FILES "${JJ_LICENSES}/LGPL-2.1.txt" DESTINATION "${CMAKE_INSTALL_PREFIX}/7zip" COMPONENT Runtime OPTIONAL)
|
install(FILES "${JJ_LICENSES}/LGPL-2.1.txt" DESTINATION "${CMAKE_INSTALL_PREFIX}/7zip" COMPONENT Runtime OPTIONAL)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue