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
86 changes: 86 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# MORT 프로젝트 작업 규약

## 프로젝트 성격
MORT는 2013년부터 이어진 장기 유지보수 프로젝트로, **옛날 방식과 현재 방식이 한 코드베이스 안에 혼재**되어 있다. 사용자 설정 파일 호환성, 기존 사용자층 보호, 점진적 리팩터링 중인 상태이기 때문에 한 번에 정리할 수 없다.

## 작업 원칙

### 1. 통일하려 들지 말 것
- 작업 요청 범위를 벗어나서 "낡았으니 정리하자"는 식의 리팩터링 금지
- 사용자가 명시적으로 요청할 때만 패턴을 통일한다
- 버그 픽스/기능 추가는 **주변 코드 스타일을 그대로 따른다**
- Newtonsoft.Json과 System.Text.Json이 같이 쓰여도, RestSharp와 HttpClient가 같이 쓰여도 통일하지 말 것

### 2. 싱글톤과 DI 공존
- `OcrManager.Instace`, `FormManager.Instace`, `TransManager._instance` 같은 싱글톤과 `Program.ServiceContainer`, `ConfigureServices`의 DI는 **공존하는 게 정상**
- 새 매니저/서비스를 만들 땐 DI 우선
- 기존 매니저를 호출할 땐 싱글톤 접근(`Instace`)을 자연스럽게 써도 된다
- `TransManager`처럼 DI로 등록되어 있지만 `_instance`도 같이 유지하는 하이브리드 패턴도 OK

### 3. 호환성 묶인 구조 보존 (절대 변경 금지)
- `SettingManager.TransType` enum 순서 (`google_url`, `db`, `papago_web`, `naver`, `google`, `deepl`, `deeplApi`, `gemini`, `ezTrans`, `customApi`)
- `SettingManager.OcrType` enum 순서 (`Tesseract=0`, `Window=1`, `OneOcr=2`, `Google=3`, `EasyOcr=4`)
- `SettingManager.Skin` enum 순서 (`dark`, `layer`, `over`)
- `@KEY ` 프리픽스 기반 텍스트 키-값 설정 포맷
- 직렬화 키 이름
- 코드 주석에도 "앞 소문자 바꾸면 안 됨 -> 기존 버전과 호환성"이라고 명시되어 있음

### 4. 거대 파일은 그대로 둠
점진적 분리 중인 상태로, 손대지 않는다:
- `Form1.cs` (3441줄)
- `SettingManager.cs` (1930줄)
- `UIAdvencedOption.cs` (1190줄)
- `TransManager.cs` (1246줄)
- `FormManager.cs` (1152줄)
- `AdvencedOptionManager.cs` (740줄)

요청받은 작업 범위 외엔 분리/리팩터링하지 않는다.

### 5. 새 기능 추가 위치
최근 커밋 흐름을 따른다:
- 비즈니스 로직 → `Service/` (e.g. `Service/Gemini/`, `Service/CustomApi/`)
- 데이터 모델 → `Model/` (record 타입 선호)
- DI 등록 → `Program.ConfigureServices`
- 번역 API → `TransAPI/`
- OCR API → `OcrApi/`

### 6. 로컬라이즈 CSV 직접 수정 금지
- `Resources/localize.csv`는 구글 스프레드 시트에서 관리한다
- 코드 작업 중 `Resources/localize.csv`를 직접 수정하지 않는다

## 프로젝트 구조 요약

### 솔루션 (5개 프로젝트)
1. **MORT** — 메인 WinForms 앱 (.NET 9, x64)
2. **CloudVision** — Google Cloud Vision OCR 래퍼
3. **GSTrans** — Google Sheets 번역기
4. **PipeClient** — EzTrans 연동용 IPC
5. **Updater** — 업데이트 모듈 (AnyCPU)

### 핵심 디렉토리
- **진입점**: `Program.cs` → DI 구성 → `Form1`
- **Manager**: `OcrManager`(싱글톤), `TransManager`(DI+싱글톤 하이브리드), `FormManager`(싱글톤), `OCRDataManager`
- **Service**: `Service/Gemini/`, `Service/CustomApi/`, `Service/TranslateTyp/`, `Service/PythonService/`, `Service/ProcessTranslateService/`
- **번역 API**: `TransAPI/` (Google, Naver, Papago Web, DeepL, DeepL API, Gemini, EzTrans, CustomAPI)
- **OCR API**: `OcrApi/OneOcr/`, `OcrApi/WindowOcr/`, `OcrApi/EasyOcr/` (+ Tesseract는 `MORT_CORE.DLL`)
- **로컬라이즈**: `LocalizeManager/`, `Resources/localize.csv` (ko/en/ja/zh-CN/id/ru/pt/uk/tr)

### 외부 의존성
- `MORT_CORE.DLL`, `nhocr.DLL` — 별도 C++ 프로젝트, 빌드 후 릴리즈 폴더에 압축해제 필요
- `Google.GenAI` 패키지는 있지만 Gemini는 직접 REST 호출 사용

### 빌드
- **x64 전용** (Updater만 AnyCPU)
- `.NET 9` (`net9.0-windows10.0.22621.0`)
- WinForms + WPF 동시 사용

## 구현 위키 유지

- 구현 전에 `docs/wiki/index.html`을 참조한다.
- 위키는 단순 클래스 설명보다 구현 의도, 작동 방식, 실제 예시, 아직 답이 없는 질문을 우선한다.
- 코드 변경으로 공통 의도·흐름·예시·질문의 답이 달라지면 `docs/wiki/wiki-content.json`을 같은 작업에서 수정한다.
- 기능의 구현·작동 방식이 달라지면 `docs/wiki/feature-content.json`을 수정한다.
- 자동 분류보다 구체적인 파일 설명이 필요하면 `docs/wiki/file-overrides.json`에 구현 의도와 작동 방식을 기록한다.
- 모든 코드 작업이 끝나면 `powershell -NoProfile -ExecutionPolicy Bypass -File tools/update-wiki.ps1`을 실행한다.
- 생성물인 `docs/wiki/index.html`은 직접 수정하지 않는다.
- 빌드와 저장소 pre-commit hook도 위키를 자동 갱신한다.
81 changes: 18 additions & 63 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,71 +1,26 @@
# MORT 프로젝트 작업 규약
# MORT 작업 규약 — 원본은 AGENTS.md

## 프로젝트 성격
MORT는 2013년부터 이어진 장기 유지보수 프로젝트로, **옛날 방식과 현재 방식이 한 코드베이스 안에 혼재**되어 있다. 사용자 설정 파일 호환성, 기존 사용자층 보호, 점진적 리팩터링 중인 상태이기 때문에 한 번에 정리할 수 없다.
이 저장소의 작업 규약 원본은 **`AGENTS.md`** 하나뿐이다. Claude Code는 보조 도구이며,
Codex가 쓰는 `AGENTS.md`의 정의를 그대로 따른다.

## 작업 원칙
## 작업 시작 전

### 1. 통일하려 들지 말 것
- 작업 요청 범위를 벗어나서 "낡았으니 정리하자"는 식의 리팩터링 금지
- 사용자가 명시적으로 요청할 때만 패턴을 통일한다
- 버그 픽스/기능 추가는 **주변 코드 스타일을 그대로 따른다**
- Newtonsoft.Json과 System.Text.Json이 같이 쓰여도, RestSharp와 HttpClient가 같이 쓰여도 통일하지 말 것
**어떤 작업이든 시작하기 전에 `AGENTS.md`를 읽고 그 내용을 그대로 적용한다.**
이 파일에 규약 본문이 없다고 해서 제약이 없는 것이 아니다.

### 2. 싱글톤과 DI 공존
- `OcrManager.Instace`, `FormManager.Instace`, `TransManager._instance` 같은 싱글톤과 `Program.ServiceContainer`, `ConfigureServices`의 DI는 **공존하는 게 정상**
- 새 매니저/서비스를 만들 땐 DI 우선
- 기존 매니저를 호출할 땐 싱글톤 접근(`Instace`)을 자연스럽게 써도 된다
- `TransManager`처럼 DI로 등록되어 있지만 `_instance`도 같이 유지하는 하이브리드 패턴도 OK
`AGENTS.md`가 정하는 것:
- 작업 원칙 6개 (통일 금지 / 싱글톤·DI 공존 / 호환성 구조 동결 / 거대 파일 불가침 /
새 기능 추가 위치 / `Resources/localize.csv` 직접 수정 금지)
- 프로젝트 구조와 빌드 조건
- 구현 위키(`docs/wiki/`) 유지 절차

### 3. 호환성 묶인 구조 보존 (절대 변경 금지)
- `SettingManager.TransType` enum 순서 (`google_url`, `db`, `papago_web`, `naver`, `google`, `deepl`, `deeplApi`, `gemini`, `ezTrans`, `customApi`)
- `SettingManager.OcrType` enum 순서 (`Tesseract=0`, `Window=1`, `OneOcr=2`, `Google=3`, `EasyOcr=4`)
- `SettingManager.Skin` enum 순서 (`dark`, `layer`, `over`)
- `@KEY ` 프리픽스 기반 텍스트 키-값 설정 포맷
- 직렬화 키 이름
- 코드 주석에도 "앞 소문자 바꾸면 안 됨 -> 기존 버전과 호환성"이라고 명시되어 있음
## 규약을 바꿔야 할 때

### 4. 거대 파일은 그대로 둠
점진적 분리 중인 상태로, 손대지 않는다:
- `Form1.cs` (3441줄)
- `SettingManager.cs` (1930줄)
- `UIAdvencedOption.cs` (1190줄)
- `TransManager.cs` (1246줄)
- `FormManager.cs` (1152줄)
- `AdvencedOptionManager.cs` (740줄)
`AGENTS.md`만 수정한다. 이 파일에는 규약 내용을 옮겨 적지 않는다.
과거에 두 파일에 같은 내용을 중복해 두었다가 `AGENTS.md`에만 추가된 항목
(로컬라이즈 CSV 규칙, 구현 위키 유지 절차)이 이쪽에 빠진 채 어긋난 적이 있다.

요청받은 작업 범위 외엔 분리/리팩터링하지 않는다.
## 이 파일이 담당하는 범위

### 5. 새 기능 추가 위치
최근 커밋 흐름을 따른다:
- 비즈니스 로직 → `Service/` (e.g. `Service/Gemini/`, `Service/CustomApi/`)
- 데이터 모델 → `Model/` (record 타입 선호)
- DI 등록 → `Program.ConfigureServices`
- 번역 API → `TransAPI/`
- OCR API → `OcrApi/`

## 프로젝트 구조 요약

### 솔루션 (5개 프로젝트)
1. **MORT** — 메인 WinForms 앱 (.NET 9, x64)
2. **CloudVision** — Google Cloud Vision OCR 래퍼
3. **GSTrans** — Google Sheets 번역기
4. **PipeClient** — EzTrans 연동용 IPC
5. **Updater** — 업데이트 모듈 (AnyCPU)

### 핵심 디렉토리
- **진입점**: `Program.cs` → DI 구성 → `Form1`
- **Manager**: `OcrManager`(싱글톤), `TransManager`(DI+싱글톤 하이브리드), `FormManager`(싱글톤), `OCRDataManager`
- **Service**: `Service/Gemini/`, `Service/CustomApi/`, `Service/TranslateTyp/`, `Service/PythonService/`, `Service/ProcessTranslateService/`
- **번역 API**: `TransAPI/` (Google, Naver, Papago Web, DeepL, DeepL API, Gemini, EzTrans, CustomAPI)
- **OCR API**: `OcrApi/OneOcr/`, `OcrApi/WindowOcr/`, `OcrApi/EasyOcr/` (+ Tesseract는 `MORT_CORE.DLL`)
- **로컬라이즈**: `LocalizeManager/`, `Resources/localize.csv` (ko/en/ja/zh-CN/id/ru/pt/uk/tr)

### 외부 의존성
- `MORT_CORE.DLL`, `nhocr.DLL` — 별도 C++ 프로젝트, 빌드 후 릴리즈 폴더에 압축해제 필요
- `Google.GenAI` 패키지는 있지만 Gemini는 직접 REST 호출 사용

### 빌드
- **x64 전용** (Updater만 AnyCPU)
- `.NET 9` (`net9.0-windows10.0.22621.0`)
- WinForms + WPF 동시 사용
Claude Code 세션에만 해당하는 사항이 생기면 여기에 적는다.
프로젝트 규약은 여기에 적지 않는다.
20 changes: 18 additions & 2 deletions MORT/Form1.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 14 additions & 5 deletions MORT/Form1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ public List<string> WinLanguageCodeList
public static bool IsDebugShowFormerResultLog = false;
public static bool IsDebugTransOneLine = false;
public static bool IsDebugShowWordArea = false;
public static bool IsDebugSaveAnalysisResult = false;

private List<KeyInputLabel> inputKeyUIList = new List<KeyInputLabel>();

Expand Down Expand Up @@ -820,7 +821,7 @@ public Form1(GeminiConfigMaker geminiConfigMaker, TranslateTypListService transl
//GDI+ 동작 여부 검사.
CheckGDI();

_processTranslateService = new ProcessTranslateService(this, _translateResultMemoryService, MySettingManager, loader, isAvailableWinOCR, StopTrans);
_processTranslateService = new ProcessTranslateService(this, _translateResultMemoryService, MySettingManager, loader, isAvailableWinOCR, isOnceTrans => StopTrans(isOnceTrans));

MakeLogo();

Expand Down Expand Up @@ -1181,7 +1182,8 @@ public void gHook_KeyDown(object sender, KeyEventArgs e)
}
else if (_processTranslateService.ProcessingState)
{
StopTrans();
//저수준 키보드 훅에서 부르는 자리다. 오래 붙잡히면 훅이 제거된다
StopTrans(false, true);
}
}
//한 번만 번역하기
Expand All @@ -1194,7 +1196,8 @@ public void gHook_KeyDown(object sender, KeyEventArgs e)
}
else if (_processTranslateService.ProcessingState)
{
_processTranslateService.PauseAndRestartTranslate(SetCaptureArea, OcrMethodType.Once);
_processTranslateService.PauseAndRestartTranslate(SetCaptureArea, OcrMethodType.Once,
ProcessTranslateService.KeyHookJoinTimeoutMs);
}
}

Expand Down Expand Up @@ -1975,12 +1978,18 @@ public void StartTrnas(OcrMethodType ocrMethodType)
MakeTransForm();
}

public void StopTrans(bool isOnceTrans = false)
/// <param name="fromKeyHook">
/// 저수준 키보드 훅에서 불렸는지. 훅 프로시저가 300ms 넘게 붙잡히면
/// 윈도우가 훅을 제거해 이후 모든 단축키가 죽으므로 대기 시간을 짧게 잡는다.
/// </param>
public void StopTrans(bool isOnceTrans = false, bool fromKeyHook = false)
{
_processTrans = false;

FormManager.Instace.MyRemoteController.ToggleStartButton(false);
_processTranslateService.StopTranslate();
_processTranslateService.StopTranslate(fromKeyHook
? ProcessTranslateService.KeyHookJoinTimeoutMs
: ProcessTranslateService.DefaultJoinTimeoutMs);

var transform = FormManager.Instace.GetITransform();

Expand Down
8 changes: 8 additions & 0 deletions MORT/Form1Button.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ private void cbShowOverlayWordArea_CheckedChanged(object sender, EventArgs e)
}
}

private void cbSaveAnalysisResult_CheckedChanged(object sender, EventArgs e)
{
if (MySettingManager.isDebugMode)
{
IsDebugSaveAnalysisResult = cbSaveAnalysisResult.Checked;
}
}

#endregion


Expand Down
7 changes: 6 additions & 1 deletion MORT/Manager/OCRDataManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -951,14 +951,19 @@ public static OCRDataManager Instace
public bool MergeLine { get; set; } = false;


/// <summary>
/// 목록만 복사해서 넘긴다. 원소는 그대로 공유한다.
/// 번역 스레드가 다음 회차에 ClearData 로 내부 목록을 비우기 때문에,
/// 내부 목록을 그대로 넘기면 번역창이 순회하는 도중에 비어버린다.
/// </summary>
public List<ResultData> GetData()
{
List<ResultData> list = new List<ResultData>();
for(int i = 0; i < dataList.Count; i++)
{
list.Add(dataList[i]);
}
return dataList;
return list;
}

public ResultData GetData(int index)
Expand Down
Loading
Loading