728x90
반응형
  • 본 내용은 과거 번역 파일 정리 자동화 프로그램 작업에 대한 업무 일지이다.

Properties 파일 구조

  • properties 파일: <단어> = <번역된 단어> 형태로 관리됨
  • 기능에 따라 폴더가 구분되어있음. 이 폴더명을 back/<폴더명> 명으로 사용
    • 파일 위치 경로: /assets/backend/
  • ko.properties, en.properties로 파일이 나뉘어져 있다.
  • 두 파일은 같은 key를 공유하고 있기 때문에, 하나의 시트에 합쳐서 보여질 수 있게 한다.
    • key, ko, en 으로 칼럼을 잡았다.

코드

  • 번역 파일이 저장되어 있는 디렉토리 내의 파일들 조회
package read

// Import 내용들


// 디렉토리 내의 파일/폴더들 리스트 조회
func ReadDir(dir string) []os.DirEntry {
    entries, err := os.ReadDir(dir)

    if err != nil {
        log.Fatalf("Failed to Read Dir Error: %v", err)

        return nil
    }

    return entries
}

// 파일 읽기
func ReadFile(fileDir string) ([]fs.DirEntry, string) {

    fileList, readErr := os.ReadDir(fileDir)

    if readErr != nil {
        log.Printf("Can't Read json file: %v", readErr)
        return nil, fileDir
    }

    return fileList, fileDir
}
  • Property 파일 내의 데이터 처리하여 저장할 수 있게 가공
package read

// Import 내용들

type PropertyStruct struct {
    Code string
    Ko   string
    En   string
}

func ReadProperties(fileList []fs.DirEntry, pwd string) []PropertyStruct {
    var propertyDataDataList []PropertyStruct

    for _, file := range fileList {
        fileName := file.Name()

        fileNameWithoutFormat := strings.Split(fileName, ".properties")[0]

        if fileNameWithoutFormat == "example" {
            continue
        }

        fileNameLength := len(fileNameWithoutFormat)
        langCode := fileNameWithoutFormat[fileNameLength-2 : fileNameLength]

        if langCode == "en" || langCode == "ko" {
            // 파일 열기
            file, err := os.Open(pwd + "/" + fileName)
            if err != nil {
                log.Printf("[Properties] data Open Error: %v", err)
            }

            defer file.Close()
            // 스캐너로 파일 한 줄씩 읽기
            scanner := bufio.NewScanner(file)

            for scanner.Scan() {
                line := scanner.Text()

                // 빈 줄 또는 주석(#, ;) 무시
                if strings.TrimSpace(line) == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
                    continue
                }
                // key=value 포맷으로 나누기
                parts := strings.SplitN(line, "=", 2)
                if len(parts) == 2 {
                    key := strings.TrimSpace(parts[0])
                    value := strings.TrimSpace(parts[1])

                    if langCode == "ko" {
                        convertedString, convTerr := utils.DecodeUnicodeEscapes(value)

                        if convTerr != nil {
                            log.Printf("Convert To Utf8 Err: %v", convTerr)
                        }
                        value = convertedString
                    }

                    isFound := false

                    for i := range propertyDataDataList {
                        data := &propertyDataDataList[i]

                        if data.Code == key {
                            isFound = true

                            switch langCode {
                            case "en":
                                data.En = value
                            case "ko":
                                data.Ko = value
                            }
                            break // 값을 찾았으므로 루프를 종
                        }
                    }

                    if !isFound {
                        propertyData := PropertyStruct{Code: key} // 새 구조체 생성
                        switch langCode {
                        case "en":
                            propertyData.En = value
                        case "ko":
                            propertyData.Ko = value
                        }
                        propertyDataDataList = append(propertyDataDataList, propertyData)
                    }

                }
            }

            if err := scanner.Err(); err != nil {
                log.Printf("Scan Error: %v", err)
            }
        }

    }

    return propertyDataDataList
}
  • 처리한 데이터들을 받아 액셀파일 생성
package write

// Import 내용들


type FileWork struct {
    file        *excelize.File
    FileName    string
    SheetLength int
}

// 칼럼 세팅
func (excelFile *FileWork)HandlePropertiesColumns () {
    excelFile.file.SetCellValue(sheetName, "A1", "Code")
    excelFile.file.SetCellValue(sheetName, "B1", "Ko")
    excelFile.file.SetCellValue(sheetName, "C1", "En")
}

// 데이터 레코드 입력
func (excelFile *FileWork)HandlePropertiesRows(dataList []read.PropertyStruct) {
    for i := 0; i <= len(dataList)-1; i += 1 {
        excelFile.file.SetCellValue(sheetName, fmt.Sprintf("A%d", i+2), dataList[i].Code)
        excelFile.file.SetCellValue(sheetName, fmt.Sprintf("B%d", i+2), dataList[i].Ko)
        excelFile.file.SetCellValue(sheetName, fmt.Sprintf("C%d", i+2), dataList[i].En)
    }
}

// 액셀 파일 저장
func (file *FileWork) SaveExcelFile() {
    savingErr := file.file.SaveAs(file.FileName)
    if savingErr != nil {
        log.Printf("Saving File Error: %v", savingErr)
    } else {
        log.Printf("Saving Success: %s", file.FileName)
    }
}

func OpenSaveExcelWithProperties(sheetName string, index int, excelFileName string, pwd string, dataList []read.PropertyStruct) {
    file := HandleFiles(sheetName, index, excelFileName, pwd, dataList)

    sheetIndex := file.HandleSheet(sheetName)

    log.Printf("Handled Sheet Index: %d", sheetIndex)

    file.file.HandlePropertiesColumns()

    file.HandlePropertiesRows(dataList)

    file.SaveExcelFile()
}
  • property 파일들이 저장되어 있는 디렉토리 내의 폴더와 파일 리스트 내에서 property 파일들만 읽어 처리하기
package libraries

// Import 내용들

// 위의 함수들 호출하여 properties 파일 처리 및 액셀 파일 생성
func CreatePropertiesTranslationData(fileName string, pwd string) {
    entries := read.ReadDir(pwd + "/assets/backend")

    log.Printf("[PROPERTIES] Entry Length: %d", len(entries))

    for i, folderData := range entries {
        folderName := folderData.Name()

        var propertyFileList []fs.DirEntry
        var proPwd string

        // 리드미 파일 혹은 예시 파일들은 제외
        if folderName == "README.md" || folderName == "example.properties" {
            continue
        }

        // 디렉토리인지 파일인지 여부 체크하여 분기처리
        if folderData.IsDir() {
            log.Printf("[PROPERTIES] Folder Name: %s", folderName)

            propertyFileList, proPwd = read.ReadFile(pwd + "/assets/common/" + folderName)

            if propertyFileList == nil {
                log.Printf("[PROPERTIES] The File is not readable: %s", proPwd)
                continue
            }
        } else {
            log.Printf("[PROPERTIES] Common File Name: %s", folderName)

            propertyFileList, proPwd = read.ReadFile(pwd + "/assets/common")

            if propertyFileList == nil {
                log.Printf("[PROPERTIES] The File is not readable: %s", proPwd)
                continue
            }

            folderName = "default"
        }

        fileDataList := read.ReadProperties(propertyFileList, proPwd)
        log.Printf("File Data List: %d", len(fileDataList))

        write.OpenSaveExcelWithProperties(folderName, i, fileName, proPwd, fileDataList)
    }
}

정리

  1. 디렉토리 내의 번역 파일 / 하위 디렉토리를 조회
  2. 번역 파일 / 하위 디렉토리 내부의 번역 파일 읽기
  3. 데이터를 파싱하여 원하는 형태로 나눠 구조체 안에서 관리
  4. 액셀 파일 생성

문제해결

  • 디렉토리 내부의 하위 디렉토리를 따로 감지하여 파일 읽는 방법
    • golang에 디렉토리 여부를 체크할 수 있는 기능이 내장되어 있음
  • 함수의 분리
    • 로직을 최소 기능 단위로 분리하여 함수 먼저 작성
    • 함수 Receiver를 이용해 클래스 내부 메서드처럼 활용할 수 있게 작성
  • 에러 발생 시 처리
    • 요구사항 상, 프로그램이 종료 되기보다 에러 발생 부분은 건너뛰고 처리
    • 로그를 확인하여 해당 부분은 매뉴얼하게 체크하는 방식을 선택함
728x90
반응형

+ Recent posts