Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions BackgroundAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,25 +113,25 @@ private static BackgroundAgentSettings ReadBackgroundAgentSettingsUnlocked()
if (!File.Exists(path)) return new BackgroundAgentSettings();
FileInfo info = new FileInfo(path);
if (info.Length <= 0 || info.Length > BackgroundAgentSettingsMaximumBytes)
throw new InvalidDataException("백그라운드 에이전트 설정 파일 크기가 올바르지 않습니다.");
throw Localized(new InvalidDataException("백그라운드 에이전트 설정 파일 크기가 올바르지 않습니다."), "The background agent settings file size is invalid.");
BackgroundAgentSettings settings;
try
{
settings = new JavaScriptSerializer().Deserialize<BackgroundAgentSettings>(File.ReadAllText(path));
}
catch (Exception exception)
{
throw new InvalidDataException("백그라운드 에이전트 설정 파일이 손상되었습니다. 원본 파일은 변경하지 않았습니다.", exception);
throw Localized(new InvalidDataException("백그라운드 에이전트 설정 파일이 손상되었습니다. 원본 파일은 변경하지 않았습니다.", exception), "The background agent settings file is damaged. The original file was left unchanged.");
}
if (settings == null || settings.SchemaVersion != BackgroundAgentSchemaVersion)
throw new InvalidDataException("지원하지 않는 백그라운드 에이전트 설정 버전입니다.");
throw Localized(new InvalidDataException("지원하지 않는 백그라운드 에이전트 설정 버전입니다."), "Unsupported background agent settings version.");
return settings;
}

private static void WriteBackgroundAgentSettings(BackgroundAgentSettings settings)
{
if (settings == null || settings.SchemaVersion != BackgroundAgentSchemaVersion)
throw new InvalidDataException("지원하지 않는 백그라운드 에이전트 설정 버전입니다.");
throw Localized(new InvalidDataException("지원하지 않는 백그라운드 에이전트 설정 버전입니다."), "Unsupported background agent settings version.");
lock (BackgroundAgentSettingsLock)
{
WithBackgroundAgentSettingsLock(delegate
Expand All @@ -157,7 +157,7 @@ private static T WithBackgroundAgentSettingsLock<T>(Func<T> action)
{
try { entered = mutex.WaitOne(TimeSpan.FromSeconds(5)); }
catch (AbandonedMutexException) { entered = true; }
if (!entered) throw new IOException("다른 MineHarbor 프로세스가 백그라운드 설정을 갱신하고 있습니다.");
if (!entered) throw Localized(new IOException("다른 MineHarbor 프로세스가 백그라운드 설정을 갱신하고 있습니다."), "Another MineHarbor process is updating the background settings.");
return action();
}
finally { if (entered) mutex.ReleaseMutex(); }
Expand Down Expand Up @@ -280,7 +280,7 @@ private static void StopBackgroundAgentForLauncherUpdate()
if (!IsBackgroundAgentRunning()) return;
Thread.Sleep(100);
}
throw new IOException("백그라운드 에이전트를 안전하게 종료하지 못해 런처 업데이트를 중단했습니다.");
throw Localized(new IOException("백그라운드 에이전트를 안전하게 종료하지 못해 런처 업데이트를 중단했습니다."), "The launcher update was cancelled because the background agent could not be stopped safely.");
}

private static BackgroundAgentResponse SendBackgroundAgentRequest(string command, string profile, string value, int timeoutMilliseconds)
Expand All @@ -296,7 +296,7 @@ private static BackgroundAgentResponse SendBackgroundAgentRequest(string command
using (StreamWriter writer = new StreamWriter(client, new UTF8Encoding(false), 4096, true) { AutoFlush = true })
{
string request = new JavaScriptSerializer().Serialize(new BackgroundAgentRequest { Command = command, Profile = profile, Value = value });
if (request.Length > 16384) throw new InvalidDataException("백그라운드 요청이 너무 큽니다.");
if (request.Length > 16384) throw Localized(new InvalidDataException("백그라운드 요청이 너무 큽니다."), "The background request is too large.");
int timeout = Math.Max(100, timeoutMilliseconds);
Task writeTask = writer.WriteLineAsync(request);
if (!writeTask.Wait(timeout)) return null;
Expand All @@ -323,7 +323,7 @@ private static void SetBackgroundAgentStartupRegistration(bool enabled, string e
}
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run"))
{
if (key == null) throw new InvalidOperationException("Windows 자동 시작 설정을 열지 못했습니다.");
if (key == null) throw Localized(new InvalidOperationException("Windows 자동 시작 설정을 열지 못했습니다."), "Could not open the Windows startup settings.");
if (enabled) key.SetValue(BackgroundAgentRunValueName, QuoteCommandLineArgument(Path.GetFullPath(executablePath)) + " --background-agent", RegistryValueKind.String);
else key.DeleteValue(BackgroundAgentRunValueName, false);
}
Expand Down Expand Up @@ -417,7 +417,7 @@ private static NamedPipeServerStream CreateSecuredAgentPipe()
PipeSecurity security = new PipeSecurity();
SecurityIdentifier currentUser;
using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) currentUser = identity.User;
if (currentUser == null) throw new InvalidOperationException("현재 Windows 사용자 SID를 확인하지 못했습니다.");
if (currentUser == null) throw Localized(new InvalidOperationException("현재 Windows 사용자 SID를 확인하지 못했습니다."), "Could not determine the current Windows user SID.");
security.SetAccessRuleProtection(true, false);
security.AddAccessRule(new PipeAccessRule(currentUser, PipeAccessRights.FullControl, AccessControlType.Allow));
return new NamedPipeServerStream(GetBackgroundAgentPipeName(), PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 65536, 65536, security);
Expand All @@ -430,7 +430,7 @@ private async Task HandlePipeClientAsync(NamedPipeServerStream pipe)
{
Task<string> readTask = reader.ReadLineAsync();
Task completed = await Task.WhenAny(readTask, Task.Delay(3000, cancellation.Token)).ConfigureAwait(false);
if (completed != readTask) throw new IOException("IPC 요청 수신 시간이 초과되었습니다.");
if (completed != readTask) throw Localized(new IOException("IPC 요청 수신 시간이 초과되었습니다."), "Timed out while receiving the IPC request.");
string line = await readTask.ConfigureAwait(false);
BackgroundAgentResponse response;
if (string.IsNullOrEmpty(line) || line.Length > 16384)
Expand Down Expand Up @@ -571,7 +571,7 @@ private async Task ExecuteAutomationClaimAsync(ManagedProfileRecord profile, Aut
}
else if (IsLocalTcpPortListening(profile.Port))
{
throw new InvalidOperationException("에이전트가 소유하지 않은 실행 중 서버는 안전하게 백업할 수 없습니다.");
throw Localized(new InvalidOperationException("에이전트가 소유하지 않은 실행 중 서버는 안전하게 백업할 수 없습니다."), "A running server not owned by the agent cannot be backed up safely.");
}
string path = await CreateAgentBackupAsync(profile.Directory, configuration, "scheduled", cancellation.Token).ConfigureAwait(false);
result = ManagedText("백업 완료: ", "Backup completed: ") + Path.GetFileName(path);
Expand Down Expand Up @@ -678,7 +678,7 @@ private Task<string> StartImmediateBackupAsync(string profileName)
SendSessionCommand(session, "save-all flush");
await Task.Delay(1000, cancellation.Token).ConfigureAwait(false);
}
else if (IsLocalTcpPortListening(profile.Port)) throw new InvalidOperationException("에이전트가 소유하지 않은 실행 중 서버는 안전하게 백업할 수 없습니다.");
else if (IsLocalTcpPortListening(profile.Port)) throw Localized(new InvalidOperationException("에이전트가 소유하지 않은 실행 중 서버는 안전하게 백업할 수 없습니다."), "A running server not owned by the agent cannot be backed up safely.");
string path = await CreateAgentBackupAsync(profile.Directory, configuration, "manual-agent", cancellation.Token).ConfigureAwait(false);
TryRecordOperationEvent(profile.Directory, "backup", "info", "백그라운드 에이전트 백업을 완료했습니다.", "Background agent backup completed.", "background-agent", false);
return path;
Expand Down Expand Up @@ -739,7 +739,7 @@ private BackgroundAgentResponse AdoptProfile(string profileName, string serializ
{
process = Process.GetProcessById(descriptor.ChildProcessId);
if (process.StartTime.Ticks != descriptor.ChildProcessStartTicks)
throw new InvalidOperationException("관리 서버 프로세스 시작 시간이 바뀌었습니다.");
throw Localized(new InvalidOperationException("관리 서버 프로세스 시작 시간이 바뀌었습니다."), "The managed server process start time changed.");
int agentProcessId;
long agentProcessStartTicks;
using (Process current = Process.GetCurrentProcess())
Expand Down Expand Up @@ -769,7 +769,7 @@ private BackgroundAgentResponse AdoptProfile(string profileName, string serializ
{
BackgroundAgentSession concurrent;
if (sessions.TryGetValue(profile.Name, out concurrent) && IsSessionRunning(concurrent))
throw new InvalidOperationException("같은 프로필의 서버가 동시에 등록되었습니다.");
throw Localized(new InvalidOperationException("같은 프로필의 서버가 동시에 등록되었습니다."), "A server for the same profile was registered at the same time.");
transferResponse = SendManagedChildControlRequest(descriptor.PipeName, transferRequest, 3000);
bool transferred = IsManagedChildOwnedBy(
transferResponse,
Expand Down Expand Up @@ -884,7 +884,7 @@ private BackgroundAgentResponse StartProfile(string profileName, bool automaticR
process.OutputDataReceived += delegate(object sender, DataReceivedEventArgs eventArgs) { if (eventArgs.Data != null) session.AddLine(eventArgs.Data); };
process.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs eventArgs) { if (eventArgs.Data != null) session.AddLine(eventArgs.Data); };
process.Exited += delegate { HandleSessionExit(session); };
if (!process.Start()) throw new InvalidOperationException("관리 서버 프로세스를 시작하지 못했습니다.");
if (!process.Start()) throw Localized(new InvalidOperationException("관리 서버 프로세스를 시작하지 못했습니다."), "Could not start the managed server process.");
session.Process = process;
session.Status = ManagedText("실행 중", "Running");
process.BeginOutputReadLine();
Expand Down Expand Up @@ -934,7 +934,7 @@ private BackgroundAgentResponse SendProfileCommand(string profileName, string co

private static void SendSessionCommand(BackgroundAgentSession session, string command)
{
if (!IsSessionRunning(session)) throw new InvalidOperationException("서버가 실행 중이 아닙니다.");
if (!IsSessionRunning(session)) throw Localized(new InvalidOperationException("서버가 실행 중이 아닙니다."), "The server is not running.");
if (!string.IsNullOrWhiteSpace(session.ControlPipeName) && !string.IsNullOrWhiteSpace(session.ControlToken))
{
ManagedChildControlRequest request = NewManagedChildControlRequest(session.ControlToken, "command", command);
Expand Down Expand Up @@ -1349,7 +1349,7 @@ private void SaveSettings()
SetBackgroundAgentStartupRegistration(previous.Enabled && previous.StartWithWindows, AssemblyLocation());
}
catch { }
ShowMineHarborDialog(this, (korean ? "설정을 저장하지 못했습니다: " : "Could not save settings: ") + exception.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
ShowMineHarborDialog(this, (korean ? "설정을 저장하지 못했습니다: " : "Could not save settings: ") + DescribeException(exception), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion BackupAndProfileTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ private void RunBackupWork(string status, Func<string> work, bool reload)
TryPostToUi(this, (MethodInvoker)delegate
{
SetBackupBusy(false, (IsBackupKorean() ? "작업 실패: " : "Operation failed: ") + exception.Message);
ShowMineHarborDialog(this, exception.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
ShowMineHarborDialog(this, DescribeException(exception), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
});
}
});
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@

Product versions follow [Semantic Versioning](https://semver.org/), while `26.2.45.xx` is a separate internal build number.

## [1.18.0] - 2026-07-27

### Korean

- **영어 UI의 한국어 오류 메시지 수정**: 영어로 사용해도 오류 대화상자에 한국어 문구가 그대로 나오던 문제를 수정했습니다. 예외 타입은 바꾸지 않고 영어 문구만 함께 담아, 기존 예외 처리 동작에는 영향이 없습니다.
- **적용 범위**: 운영 기록, 백그라운드 에이전트, 예약 자동화, Discord 원격 제어, Windows 알림, 서버 휴지통, 저장 위치, 관리 서버 인계의 오류 문구 121개를 한국어·영어로 제공합니다.
- **표시 경로 정리**: 오류를 사용자에게 보여 주는 30곳이 현재 언어에 맞는 문구를 고르도록 했습니다. 아직 영어 문구가 없는 오류는 지금까지와 동일하게 원래 문구를 표시합니다.

### English

- **Korean error text in the English UI fixed**: error dialogs showed Korean text even when the launcher was used in English. Exception types are unchanged — the English wording is carried alongside — so existing exception handling behaves exactly as before.
- **Coverage**: 121 error messages across operations history, the background agent, scheduled automation, Discord remote control, Windows notifications, the server trash, storage location, and managed-server handoff are now available in both Korean and English.
- **Display paths updated**: the 30 places that surface errors to users now pick the wording for the current language. Errors that do not yet carry English wording continue to show their original text.
## [1.17.0] - 2026-07-26

### Korean
Expand Down
16 changes: 8 additions & 8 deletions ContentManagementUi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ private async Task ReloadInstalledAsync()
EndContentOperation(IsContentUiKorean() ? values.Count + "개 콘텐츠" : values.Count + " content item(s)");
}
catch (OperationCanceledException) { EndContentOperation(IsContentUiKorean() ? "작업을 취소했습니다." : "Operation cancelled."); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "콘텐츠 목록 오류: " : "Content list error: ") + exception.Message); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "콘텐츠 목록 오류: " : "Content list error: ") + DescribeException(exception)); }
}

private async Task SearchAsync()
Expand All @@ -288,7 +288,7 @@ private async Task SearchAsync()
EndContentOperation(IsContentUiKorean() ? values.Count + "개를 찾았습니다." : values.Count + " result(s) found.");
}
catch (OperationCanceledException) { EndContentOperation(IsContentUiKorean() ? "검색을 취소했습니다." : "Search cancelled."); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "검색 오류: " : "Search error: ") + exception.Message); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "검색 오류: " : "Search error: ") + DescribeException(exception)); }
}

private async Task InstallSelectedAsync()
Expand All @@ -309,7 +309,7 @@ private async Task InstallSelectedAsync()
await ReloadInstalledAsync();
}
catch (OperationCanceledException) { EndContentOperation(korean ? "설치를 취소했습니다." : "Installation cancelled."); }
catch (Exception exception) { EndContentOperation((korean ? "설치 오류: " : "Installation error: ") + exception.Message); }
catch (Exception exception) { EndContentOperation((korean ? "설치 오류: " : "Installation error: ") + DescribeException(exception)); }
}

private async Task InstallLocalAsync()
Expand All @@ -328,7 +328,7 @@ private async Task InstallLocalAsync()
await ReloadInstalledAsync();
}
catch (OperationCanceledException) { EndContentOperation(IsContentUiKorean() ? "설치를 취소했습니다." : "Installation cancelled."); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "파일 설치 오류: " : "File installation error: ") + exception.Message); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "파일 설치 오류: " : "File installation error: ") + DescribeException(exception)); }
}
}

Expand All @@ -341,7 +341,7 @@ private async Task CheckUpdatesAsync()
if (closing) return; RenderInstalledItems(); EndContentOperation(IsContentUiKorean() ? "업데이트 가능 " + available + "개" : available + " update(s) available");
}
catch (OperationCanceledException) { EndContentOperation(IsContentUiKorean() ? "확인을 취소했습니다." : "Check cancelled."); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "업데이트 확인 오류: " : "Update check error: ") + exception.Message); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "업데이트 확인 오류: " : "Update check error: ") + DescribeException(exception)); }
}

private async Task UpdateSelectedAsync(bool all)
Expand All @@ -358,15 +358,15 @@ private async Task UpdateSelectedAsync(bool all)
if (closing) return; EndContentOperation(IsContentUiKorean() ? "업데이트를 완료했습니다." : "Updates completed."); await ReloadInstalledAsync();
}
catch (OperationCanceledException) { EndContentOperation(IsContentUiKorean() ? "업데이트를 취소했습니다." : "Update cancelled."); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "업데이트 오류: " : "Update error: ") + exception.Message); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "업데이트 오류: " : "Update error: ") + DescribeException(exception)); }
}

private async Task ToggleSelectedAsync()
{
InstalledContentItem item = SelectedInstalledItem(); if (item == null) return;
CancellationToken token; if (!BeginContentOperation(IsContentUiKorean() ? "상태를 변경하는 중..." : "Changing state...", out token)) return;
try { await Task.Run(delegate { token.ThrowIfCancellationRequested(); SetContentEnabled(options.ServerDirectory, item.Entry, !item.Active); }, token); if (closing) return; EndContentOperation(IsContentUiKorean() ? "상태를 변경했습니다." : "State changed."); await ReloadInstalledAsync(); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "상태 변경 오류: " : "State change error: ") + exception.Message); }
catch (Exception exception) { EndContentOperation((IsContentUiKorean() ? "상태 변경 오류: " : "State change error: ") + DescribeException(exception)); }
}

private async Task RemoveSelectedAsync()
Expand All @@ -376,7 +376,7 @@ private async Task RemoveSelectedAsync()
if (ShowMineHarborDialog(this, korean ? "선택한 콘텐츠를 서버에서 제거할까요? 안전을 위해 .mineharbor/content-trash로 이동합니다." : "Remove the selected content from the server? It will be moved to .mineharbor/content-trash for safety.", Text, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return;
CancellationToken token; if (!BeginContentOperation(korean ? "콘텐츠를 제거하는 중..." : "Removing content...", out token)) return;
try { await Task.Run(delegate { token.ThrowIfCancellationRequested(); RemoveContentItem(options.ServerDirectory, item.Entry); }, token); if (closing) return; EndContentOperation(korean ? "콘텐츠를 제거했습니다." : "Content removed."); await ReloadInstalledAsync(); }
catch (Exception exception) { EndContentOperation((korean ? "제거 오류: " : "Removal error: ") + exception.Message); }
catch (Exception exception) { EndContentOperation((korean ? "제거 오류: " : "Removal error: ") + DescribeException(exception)); }
}

private void RenderInstalledItems()
Expand Down
Loading