앱이 직접 할 때의 파일 오류도 화면 언어로 (245)

- 버그 (기기 확인): 현재 방식 실패 글이 'Cannot delete file …' 영어 - 원본을 못 지웠으면 rsync 때 (215) 와 같은 글, 그 밖도 짧은 한글 + 경로 + OS 오류

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
묶음5
RyuJongJin 2026-10-12 00:44:17 +09:00
parent 58bc8fa35b
commit 01ef79f86f
6 changed files with 49 additions and 7 deletions

View File

@ -813,6 +813,7 @@
"마지막으로: {0}": "To the last: {0}",
"만든 그림 · 저장: {0}": "Made images · saved in: {0}",
"만들 자막 언어 (파일명_언어코드.srt)": "Subtitle languages to make (filename_languagecode.srt)",
"만들거나 쓰지 못했습니다": "Could not create or write",
"만들기": "Make",
"만들다 만 파일 {0}개": "{0} incomplete files",
"만들다 만 파일 {0}개를 지울까요?": "Delete {0} incomplete files?",
@ -1332,6 +1333,7 @@
"올린 그림은 지워집니다. 다시 보려면 해상도 올리기를 다시 해야 합니다.": "The upscaled image will be deleted. To see it again you will need to upscale again.",
"올린 이 {0}": "Uploaded by {0}",
"옮겼습니다.": "moved.",
"옮기거나 이름을 바꾸지 못했습니다": "Could not move or rename",
"옮기기": "Move",
"옮기는 중": "Moving",
"옮길": "move",
@ -1794,6 +1796,7 @@
"지우면 휴지통으로": "Delete to the Recycle Bin",
"지우지 못한 파일: {0}": "Couldn't delete: {0}",
"지우지 못함: {0}": "Could not delete: {0}",
"지우지 못했습니다 (권한이 없거나 사용 중)": "Could not delete (no permission or in use)",
"지우지 못했습니다: {0}": "Could not delete: {0}",
"지우지 않고 맞춤 · 대상에만 있는 {0}개를 지울지 확인 필요": "Synced without deleting · confirm whether to delete {0} items only in the target",
"지우지 않기 (지우기 끄기)": "Don't delete (turn off delete)",
@ -1934,7 +1937,9 @@
"파일 폴더": "File folder",
"파일 확인 중…": "Checking files…",
"파일만 골랐을 때": "When only files are selected",
"파일을 복사하지 못했습니다": "Could not copy the file",
"파일을 분석할 수 없습니다: {0}": "Cannot analyze the file: {0}",
"파일을 읽지 못했습니다": "Could not read the file",
"파일이 깨졌습니다": "The file is damaged",
"파일이 깨졌습니다 · [다시 받기] 를 눌러 주세요": "The file is damaged · tap [Download again]",
"파일이 보이지 않습니다: \"모든 파일에 대한 접근\" 권한이 없습니다": "Files are not shown: \"All files access\" is not allowed",

View File

@ -813,6 +813,7 @@
"마지막으로: {0}": "最後へ: {0}",
"만든 그림 · 저장: {0}": "作成した画像・保存先: {0}",
"만들 자막 언어 (파일명_언어코드.srt)": "作成する字幕の言語(ファイル名_言語コード.srt)",
"만들거나 쓰지 못했습니다": "作成または書き込みできませんでした",
"만들기": "作成",
"만들다 만 파일 {0}개": "未完成のファイル {0} 個",
"만들다 만 파일 {0}개를 지울까요?": "未完成のファイル {0} 個を削除しますか?",
@ -1332,6 +1333,7 @@
"올린 그림은 지워집니다. 다시 보려면 해상도 올리기를 다시 해야 합니다.": "高解像度化した画像は削除されます。もう一度見るには高解像度化をやり直す必要があります。",
"올린 이 {0}": "投稿者 {0}",
"옮겼습니다.": "移動しました。",
"옮기거나 이름을 바꾸지 못했습니다": "移動または名前の変更ができませんでした",
"옮기기": "移動",
"옮기는 중": "移動中",
"옮길": "移す",
@ -1794,6 +1796,7 @@
"지우면 휴지통으로": "削除するとごみ箱へ",
"지우지 못한 파일: {0}": "削除できなかったファイル: {0}",
"지우지 못함: {0}": "削除できません: {0}",
"지우지 못했습니다 (권한이 없거나 사용 중)": "削除できませんでした (権限がないか使用中)",
"지우지 못했습니다: {0}": "削除できませんでした: {0}",
"지우지 않고 맞춤 · 대상에만 있는 {0}개를 지울지 확인 필요": "削除せずに同期・対象にだけある {0} 個を削除するか確認が必要",
"지우지 않기 (지우기 끄기)": "削除しない (削除をオフ)",
@ -1934,7 +1937,9 @@
"파일 폴더": "ファイル フォルダー",
"파일 확인 중…": "ファイルを確認中…",
"파일만 골랐을 때": "ファイルだけを選んだとき",
"파일을 복사하지 못했습니다": "ファイルをコピーできませんでした",
"파일을 분석할 수 없습니다: {0}": "ファイルを分析できません: {0}",
"파일을 읽지 못했습니다": "ファイルを読み取れませんでした",
"파일이 깨졌습니다": "ファイルが壊れています",
"파일이 깨졌습니다 · [다시 받기] 를 눌러 주세요": "ファイルが壊れています・[再ダウンロード] を押してください",
"파일이 보이지 않습니다: \"모든 파일에 대한 접근\" 권한이 없습니다": "ファイルが表示されません: 「すべてのファイルへのアクセス」権限がありません",

View File

@ -813,6 +813,7 @@
"마지막으로: {0}": "到最后: {0}",
"만든 그림 · 저장: {0}": "生成的图片 · 保存位置: {0}",
"만들 자막 언어 (파일명_언어코드.srt)": "要生成的字幕语言(文件名_语言代码.srt)",
"만들거나 쓰지 못했습니다": "无法创建或写入",
"만들기": "生成",
"만들다 만 파일 {0}개": "{0} 个未完成的文件",
"만들다 만 파일 {0}개를 지울까요?": "删除 {0} 个未完成的文件吗?",
@ -1332,6 +1333,7 @@
"올린 그림은 지워집니다. 다시 보려면 해상도 올리기를 다시 해야 합니다.": "提升后的图片将被删除。要再次查看需要重新提升分辨率。",
"올린 이 {0}": "上传者 {0}",
"옮겼습니다.": "已移动。",
"옮기거나 이름을 바꾸지 못했습니다": "无法移动或重命名",
"옮기기": "移动",
"옮기는 중": "正在移动",
"옮길": "移动",
@ -1794,6 +1796,7 @@
"지우면 휴지통으로": "删除时移到回收站",
"지우지 못한 파일: {0}": "无法删除的文件: {0}",
"지우지 못함: {0}": "无法删除: {0}",
"지우지 못했습니다 (권한이 없거나 사용 중)": "无法删除(没有权限或正在使用)",
"지우지 못했습니다: {0}": "无法删除: {0}",
"지우지 않고 맞춤 · 대상에만 있는 {0}개를 지울지 확인 필요": "已同步但未删除 · 需确认是否删除仅在目标中的 {0} 个项目",
"지우지 않기 (지우기 끄기)": "不删除 (关闭删除)",
@ -1934,7 +1937,9 @@
"파일 폴더": "文件夹",
"파일 확인 중…": "正在检查文件…",
"파일만 골랐을 때": "只选择文件时",
"파일을 복사하지 못했습니다": "无法复制文件",
"파일을 분석할 수 없습니다: {0}": "无法分析文件: {0}",
"파일을 읽지 못했습니다": "无法读取文件",
"파일이 깨졌습니다": "文件已损坏",
"파일이 깨졌습니다 · [다시 받기] 를 눌러 주세요": "文件已损坏 · 请点按 [重新下载]",
"파일이 보이지 않습니다: \"모든 파일에 대한 접근\" 권한이 없습니다": "文件不可见: 没有“所有文件访问”权限",

View File

@ -142,7 +142,7 @@ class CopyCenter extends ChangeNotifier {
if (job.leftInSource > 0) trf('{0}개는 원본에 남음 (건너뜀)', [job.leftInSource]),
if (job.pruned > 0) trf('빈 폴더 {0}개 지움', [job.pruned]),
].join(' · ')
: userErrorText(e), // 237: 예외 이름 없이 (원래 글은 작업 기록에)
: userErrorText(e, sources: t.sources), // 237 · 245: 예외 이름 없이 · 화면 언어로 (원래 글은 작업 기록에)
lastFiles: job.doneFiles,
lastLeft: e == null ? job.leftInSource : 0,
lastDetail: e == null || e is FileOpCancelled ? '' : '$e',

View File

@ -14,17 +14,39 @@ import '../l10n/tr.dart';
/// 복사 · 이동 한 번 (파일 탐색기). 방법 (현재 방식 · rsync · robocopy) 과 상관없이 같은 진행 상태를 알린다:
/// - 위쪽: 고른 항목 (폴더) 중 몇 번째인지 · 전체 진행률
/// - 아래쪽: 지금 항목 (폴더) 의 파일 중 몇 개를 했는지
/// 215: rsync 가 실패했을 때 보일 글 - 원본을 못 지웠으면 쉬운 말 한 줄, 그리고 오류 · 권한 줄을 끝쪽부터 (전송한 파일 이름이 아니라).
/// 예전에는 출력의 앞쪽 8줄이라 파일 이름만 보이고 "Permission denied" · "rsync error … (code 23)" 가 빠지기 쉬웠다
/// 237: 사용자에게 보이는 실패 글 - "ProcessException:" 같은 예외 이름 · "Command:" 줄은 빼고 내용만.
/// 원래 글 ('$e') 은 작업 기록에 그대로 남긴다
String userErrorText(Object e) => switch (e) {
/// 원래 글 ('$e') 은 작업 기록에 그대로 남긴다.
/// 245: 앱이 직접 할 때의 파일 오류 (Dart 의 "Cannot delete file" 등 영어) 는 화면 언어로. [sources] 안의 파일을 못 지웠으면
/// rsync 때 (215) 와 같은 글 - 이동에서 원본만 남은 것
String userErrorText(Object e, {List<String> sources = const []}) => switch (e) {
ProcessException(:final message) when message.isNotEmpty => message,
FileSystemException(:final message, :final path, :final osError) =>
[message, if (path != null && path.isNotEmpty) path, if (osError != null && osError.message.isNotEmpty) osError.message].join(' · '),
FileSystemException(:final message, :final path, :final osError) => [
_fileErrorText(message, path, sources),
if (path != null && path.isNotEmpty) path,
if (osError != null && osError.message.isNotEmpty) osError.message,
].join(' · '),
_ => '$e'.replaceFirst(RegExp(r'^\w*(Exception|Error)(\([^)]*\))?: '), ''),
};
String _fileErrorText(String message, String? path, List<String> sources) {
final m = message.toLowerCase();
if (m.startsWith('cannot delete') || m.startsWith('deletion failed')) {
final inSource = path != null && sources.any((s) => isSameOrInside(path, s));
return inSource
? tr('원본 파일을 지우지 못했습니다 (권한이 없거나 사용 중). 대상에는 복사됐고 원본은 남아 있습니다.')
: tr('지우지 못했습니다 (권한이 없거나 사용 중)');
}
if (m.startsWith('cannot copy')) return tr('파일을 복사하지 못했습니다');
if (m.startsWith('cannot rename')) return tr('옮기거나 이름을 바꾸지 못했습니다');
if (m.startsWith('cannot open') || m.startsWith('cannot read')) return tr('파일을 읽지 못했습니다');
if (m.startsWith('cannot create') || m.startsWith('creation failed') || m.startsWith('cannot write')) {
return tr('만들거나 쓰지 못했습니다');
}
return message;
}
/// 215: rsync 가 실패했을 때 보일 글 - 원본을 못 지웠으면 쉬운 말 한 줄, 그리고 오류 · 권한 줄을 끝쪽부터 (전송한 파일 이름이 아니라).
/// 예전에는 출력의 앞쪽 8줄이라 파일 이름만 보이고 "Permission denied" · "rsync error … (code 23)" 가 빠지기 쉬웠다
String rsyncFailureText(int code, List<String> errLines, List<String> allLines) {
final all = [...errLines, ...allLines];
final removeFailed = all.any((l) {

View File

@ -274,6 +274,11 @@ void main() {
expect(userErrorText(const ProcessException('rsync', ['-a'], '원본 파일을 지우지 못했습니다', 23)), '원본 파일을 지우지 못했습니다');
expect(userErrorText(const FileSystemException('지울 수 없음', r'C:\x', OSError('액세스가 거부되었습니다', 5))), r'지울 수 없음 · C:\x · 액세스가 거부되었습니다');
expect(userErrorText(Exception('끊김')), '끊김');
// 245: 앱이 직접 할 때의 Dart 파일 오류 (영어) 도 rsync 때와 같은 글 - 원본을 못 지웠으면
expect(userErrorText(const FileSystemException('Cannot delete file', r'C:\s\a.txt', OSError('사용 중', 32)), sources: [r'C:\s']),
r'원본 파일을 지우지 못했습니다 (권한이 없거나 사용 중). 대상에는 복사됐고 원본은 남아 있습니다. · C:\s\a.txt · 사용 중');
expect(userErrorText(const FileSystemException('Cannot delete file', r'D:\t\x.txt'), sources: [r'C:\s']).startsWith('지우지 못했습니다'), isTrue);
expect(userErrorText(const FileSystemException('Cannot copy file to D:\\t', r'C:\s\a.txt')).startsWith('파일을 복사하지 못했습니다'), isTrue);
expect(userErrorText('그냥 글'), '그냥 글');
});