HOME > python > examples

Google Translate으로 MarkDown 문서 번역하기

By JS | 08 Dec 2019

python에서 Google Translate API를 이용하여 MarkDown 문서를 번역하는 코드를 구현하였습니다. 예를 들어, 한글로 작성된 MarkDown 파일이 ko.md라면 이것을 일본어로 번역하고 ja.md라는 파일로 만들어주는 것입니다.

물론, MarkDown의 모든 문법에 대한 예외처리를 하지 않아서 일부는 수작업으로 수정해줘야 합니다.

저는 GatsbyJS로 Blog를 작성하고 있으며, GatsbyJS는 다음과 같은 MarkDown을 읽어들입니다.

---
title: Google Translate으로 MarkDown 문서 번역하기
date: 2019-12-08
tags: [python, python-example]
description: python에서 Google Translate API를 이용하여 MarkDown 문서를 번역하는 코드를 구현하였습니다. GatsbyJS에서 사용하는 MarkDown 형식이며, 원하는 언어로 번역을 하고 파일로 출력합니다. 제가 구현한 코드를 공유합니다.
---
본문...

위의 MarkDown에서 meta-data는 title과 description만 번역하면 됩니다. 그리고 본문은 모두 번역이 되어야 합니다.

MarkDown parser는 번역이 필요한 부분을 찾고 Google Translate API를 통해 번역합니다. 번역이 필요하지 않은 부분은 그냥 파일에 출력하도록 구현합니다.

제가 만든 툴은 다음과 같은 명령어로 실행할 수 있습니다. 인자는 3개를 전달하며, ko.md는 번역할 문서이며, en.md는 번역 후 생성할 파일 이름입니다. 마지막으로 en은 번역하려는 언어의 locale입니다.

$ python3 translate.py ko.md en.md en

혹시 필요하시거나 참고하실 분이 있을지 몰라 여기에 코드를 공유합니다.

translate.py

#!/usr/bin/env python3
from google.cloud import translate_v2 as translate
import sys
import os

SEPARATOR_META_DATA = "---"
SEPARATOR_CODE = "```"
SEPARATOR_IMG = "!["
SEPARATOR_IMG_CODE = "<img"
STR_TITLE = "title:"
STR_STAR = "*"
STR_SHARP = "#"
STR_DESCRIPTION = "description:"

translate_client = translate.Client()
USE_GOOGLE_TRANS = True

def is_english(text):
    for c in text:
        if not 0 <= ord(c) <= 128:
            return False
    return True

def correct_code_text(old, new):
    CHAR_CODE = '`'
    old_code_cnt = old.count(CHAR_CODE)
    new_code_cnt = new.count(CHAR_CODE)
    result = new
    if (old_code_cnt == new_code_cnt) and (old_code_cnt%2 == 0) and (new_code_cnt%2 == 0):
        old_idx = 0
        new_idx = 0
        for i in range(0, int(old_code_cnt/2)):
            first_old = old.index(CHAR_CODE, old_idx)
            old_idx = first_old + 1
            second_old = old.index(CHAR_CODE, old_idx)
            old_idx = second_old + 1
            first_new = new.index(CHAR_CODE, new_idx)
            new_idx = first_new + 1
            second_new = new.index(CHAR_CODE, new_idx)
            new_idx = second_new + 1
            old_str = old[first_old:(second_old + 1)]
            new_str = new[first_new:(second_new + 1)]
            result = result.replace(new_str, old_str)

    return result

def correct_link_text(new_text):
    new = new_text
    start = 0
    end = 0
    while True:
        try:
            start = new.index("](", end)
            if start >= 0:
                end = new.index(")", start)
                if end >= 0:
                    link = new[start:(end+1)]
                    removed_white = link.replace(" ", "")
                    new = new.replace(link, removed_white)
                    diff_idx = len(link) - len(removed_white)
                    end = end - diff_idx
            else:
                break
        except:
            break
    return new

def correct_bold_text(new_text):
    new = new_text
    start = 0
    end = 0
    while True:
        try:
            start = new.index("**", end)
            if start >= 0:
                end = new.index("**", start + 1) +2
                if end >= 0:
                    link = new[start:(end)]
                    removed_white = link.replace(" ", "")
                    new = new.replace(link, removed_white)
                    diff_idx = len(link) - len(removed_white)
                    end = end - diff_idx
            else:
                break
        except:
            break
    return new

def translate(origin, dest_lang):
    print(origin)
    result = translate_client.translate(origin, target_language=dest_lang)
    translated = result['translatedText']
    translated = translated.replace('(', '(')
    translated = translated.replace(')', ')')
    translated = translated.replace('#', '#')
    translated = translated.replace('【', '[')
    translated = translated.replace('】', ']')
    translated = translated.replace('&quot;', '"')
    translated = translated.replace('&gt;', '>')
    translated = translated.replace('「', '"')
    translated = translated.replace('」', '"')
    translated = translated.replace('&#39;', '`')
    translated = correct_code_text(origin, translated)
    translated = correct_link_text(translated)
    translated = correct_bold_text(translated)
    print(translated)
    return translated


def parse_metadata_and_translate(src_lines, des_lines, idx, dest_lang):
    text = src_lines[idx]
    if not text.startswith(SEPARATOR_META_DATA):
        return -1

    des_lines.append(text)
    idx = idx + 1
    start = idx
    for i in range(start, len(src_lines)):
        idx = i
        text = src_lines[idx]
        if text.startswith(SEPARATOR_META_DATA):
            idx = idx + 1
            des_lines.append(text)
            break
        elif text.startswith(STR_TITLE):
            title = text[len(STR_TITLE):-1].strip()
            translated = translate(title, dest_lang) + "\n"
            des_lines.append("%s %s"%(STR_TITLE, translated))
        elif text.startswith(STR_DESCRIPTION):
            description = text[len(STR_DESCRIPTION):-1].strip()
            translated = translate(description, dest_lang) + "\n"
            des_lines.append("%s %s"%(STR_DESCRIPTION, translated))
        else:
            des_lines.append(text)

    if (idx + 1) == len(src_lines):
        return -1

    return idx

def parse_body_and_translate(src_lines, des_lines, idx, dest_lang):
    start = idx
    is_in_code = False
    for i in range(start, len(src_lines)):
        idx = i
        text = src_lines[i].strip()
        if text == "\n" or text == "":
            des_lines.append(src_lines[i])
        elif text.startswith(SEPARATOR_CODE):
            is_in_code = not is_in_code
            des_lines.append(src_lines[i])
        elif text.startswith(SEPARATOR_IMG):
            des_lines.append(src_lines[i])
        elif text.startswith(SEPARATOR_IMG_CODE):
            des_lines.append(src_lines[i])
        elif len(text) > 4 and text[0] == STR_STAR and text[1] == STR_STAR and text[-1] == STR_STAR and text[-2] == STR_STAR:
            des_lines.append(src_lines[i])          
        elif is_english(text):
            des_lines.append(src_lines[i])
        else:
            if is_in_code:
                des_lines.append(src_lines[i])
            else:
                translated = translate(text, dest_lang) + "\n"
                des_lines.append(translated)

def parse_and_translate(src, des, lang):
    src_lines = []
    des_lines = []

    with open(src, "r") as f:
        src_lines = f.readlines()

    idx = 0
    idx = parse_metadata_and_translate(src_lines, des_lines, idx, lang)
    idx = parse_body_and_translate(src_lines, des_lines, idx, lang)

    if src != des:
        with open(des, "w") as f:
            for line in des_lines:
                f.write(line)

if __name__ == "__main__":
    if len(sys.argv) != 4:
        print("please input src, des files and des lang")
        exit(1)

    src_file_name = sys.argv[1]
    des_file_name = sys.argv[2]
    des_lang = sys.argv[3]
    cur_dir = os.getcwd()

    for (root, dirs, files) in os.walk(cur_dir):
        src = root + "/" + src_file_name
        des = root + "/" + des_file_name
        if not os.path.exists(des) and os.path.exists(src):
            print(src)
            print(des)
            parse_and_translate(src, des, des_lang)

Google Translate API를 사용하는 방법은 이 글을 참고해주세요.