이삭 줍기

Python
공개

2025-05-10

초록

파이썬 번역 팀이 원본 수정에 대응하는 법.

경고

미완성 글입니다. 최종본에서는 이 경고가 제거됩니다.

장기적인 관점에서 번역팀의 주 업무는 변경 관리다

파이썬 번역 작업은 크게 세 종류로 나뉜다.

  • 번역된 적이 없는 문서를 새로 번역하기.
  • 이미 번역된 문서의 잘못된 점을 찾고 편집하기.
  • 이미 번역된 문서의 원본이 수정될 때 변경 사항을 반영하기.

첫번째 시즌의 주제는 첫번째 과제였고, 이번 시즌 2의 주제는 남은 두 과제다. 첫 번째 과제가 완전히 해결된 것은 아니다. 시즌 1을 끝마칠 때도 번역되지 않은 문서가 24개 남아있었고. 글자 수로는 13% 정도에 해당한다. 시즌 2를 시작하는 시점에 새로 추가된 문서는 43개다. 시즌 2에서 이미 처리한 11개의 문서를 제외하면 56개가 남아있다. 돌아보면 새로 추가되는 문서는 평균 한달에 하나도 안된다. 처음 밀려있는 번역을 처리하는 것이 어렵지, 새로 추가되는 문서를 번역하는 것은 그리 큰 작업이 아니다.

시즌 2에서 시도할 교정과 교열 작업도 양적인 측면에서는 신규 번역과 비슷한 성격을 갖는다. 교정과 교열은 번역 작업에 포함시킬 수 있고, 번역보다 시간과 인력이 더 필요하다고 보기는 힘들다. 이 역시 가장 어려운 부분은 이미 번역된 451개의 문서를 교정/교열하는 것이다. 이 미션을 어떻게 공략할지는 아직 정리되지 않았다. 나중에 다루자.

오늘 집중적으로 다룰 내용은 마지막 과제인 변경 대응이다. 먼저 변경에 대해 살펴보자. 아주 사소한 경우부터 보자.

어느 날 관사 an 이 쓰여야 할 곳에 a 가 들어가 있는 것을 발견하고 CPython 으로 누군가 PR 을 보냈다. 쉽게 확인할 수 있는 내용이라 쉽게 병합될 것이다. 병합되고 나면 다음 설명서 빌드 때 바로 적용되어 docs.python.org 에 올라간다. 지금은 하루 한 번 KST 로 오후 3시 경에 빌드되는 것으로 보인다. 늦어도 병합 다음날 오후 3시 쯤에는 프로덕션에 반영된다는 뜻이다. 영문 사이트는 an 으로 바뀐 올바른 내용이 나오게 될 것이지만, 한국어 사이트에는 해당 변경이 들어간 문단 전체가 번역되지 않은 상태로 바뀐다. 파이썬 설명서의 번역 단위가 문단이기 때문에 일어나는 일이다. 이를 바로잡는 절차는 이렇다.

  1. 개발 컨테이너 설정 파일에 지정한 커밋 해시를 변경이 포함된 커밋 해시로 바꾸고 컨테이너를 리빌드한다.
  2. pdk extractpdk update 로 변경을 .po 파일에 반영한다. 이로 인해 해당 문단에 fuzzy 플래그가 추가된다.
  3. fuzzy 플래그가 붙은 문단의 변경 내용을 파악하고, 번역을 적절히 수정한 후 fuzzy 플래그를 지운다. 이 경우 부정관사의 변화로 번역이 달라질 일은 없으니 fuzzy 플래그를 지우기만 하면 된다.

이 변경이 병합되면 역시 늦어도 다음날 오후 3시 쯤에는 한국어 사이트의 번역이 복구된다.

처음 두 단계는 관리자가 할 일이고, 마지막 단계만 번역 기여자들의 몫이다. 하지만 이 작업을 한 번역 기여자는 없다. 시즌 1에서는 세단계 모두 필자가 전담했다. 시즌 2에서도 지금 까지는 여전하다.1 시즌 2를 시작할 때 변경된 파일이 430개 나왔는데, 거의 모두 fuzzy 플래그가 포함되어 있다. 매일 조금씩 처리해서 이제 250개 정도만 남아있는 상태다.2 그래도 한꺼번에 작업하다보니 현타가 온다. 더군다나 남은 250개를 처리한다고 끝나는 것이 아니다. CPython 3.13 브랜치의 Doc 디렉터리에는, 지난 4월 한달 동안 50개가 넘는 커밋이 병합되었다. 매일 뭔가 바뀐다는 뜻이다. 장기적인 관점에서 변경 관리가 번역팀의 주 업무라고 보아야 한다.

이삭 줍는 기계를 만들어보자.

이삭 줍기

pdk update 명령은 pdk extract 에서 추출된 .pot 파일들을 대응하는 .po 파일과 대조한다. msgid 를 일종의 key 로 볼 수 있는데, .pot 파일에 있지만 .po 파일에 없는 것이 있다면 추가한다. 물론 이 때 msgstr 은 빈 문자열이된다. .po 파일에 이미 있다면 드대로 둔다. .po 파일에는 있지만 .pot 파일에는 없는 것들은 두 가지 중 하나가 된다.

  • 새로 추가되는 msgid 중에서 비슷한 것이 있다면, 빈 문자열대신 비슷한 것의 msgstr 을 넣는다. 새로 생긴 메시지가 아니라, 기존 메시지가 수정되었을 가능성이 있다고 보는 것이다. 하지만 어디까지나 추측일 뿐이기 때문에, fuzzy 라는 플래그를 붙여서 번역자에게 확인할 것을 요구한다. fuzzy 을 제거하기 전에는 빌드할 때는 msgstr 이 빈 문자열인 것으로 취급된다.
  • 비슷한 것을 찾지 못하면 .po 파일의 끝에 주석 처리해서 남겨둔다.

지금 우리의 관심사는 fuzzy 메시지이니, 첫번째 경우뿐이다.

Diff 뷰

번역자가 fuzzy 메시지를 보면, 새로 번역하기 보다는 원문의 어디가 어떻게 변했는지를 보고 필요한 부분만 수정하는 것이 대부분의 경우 빠르다. 그래서 pdk update 하기 전의 파일과 diff 로 비교하는 것이 필요하다. 개발 컨테이너에 설치해둔 Git History 확장을 사용해서 비교하면 찾을 수 있는 경우가 많다. 하지만 이 때 diff 는 .po 파일 전체를 비교하는 것이라 찾기 힘들 때가 종종 있다.

podiff 라는 .po 파일에 전문화된 도구도 있다. git 의 플러그인 처럼 설치해서 사용할 수 있는데, 이 역시 여전히 파일 단위로 비교하기 때문에 상기한 어려움을 근본적으로 해결해주지 못한다. 더군다나 이 도구는 원래 파일을 그대로 비교하지 않고 msgid 로 정렬한 후에 비교하기 때문에 문제를 더 악화시키는 경향이 있다. 또한 필자가 테스트해본 결과 컬러 출력이 망가진다.

그래서 좀 더 간단한 Diff 뷰를 직접 만들기로 한다. 몇가지 중요한 특징이다.

  • 공백 문자를 특별하게 취급하지 않는다. 공백 문자의 변경도 msgid 의 변경으로 인식되고 있기 때문이다.
  • msgid 의 줄넘김은 중요하지 않다. 오히려 방해만 된다. 줄넘김을 제거한 상태로 비교한다.
  • 파일 단위의 비교대신 메시지 단위로 비교한다. 대응하는 메시지는 msgstr 이 같은 메시지를 찾으면 된다. 이는 fuzzy 메시지 이기 때문에 가능하다.
  • msgid 를 하나만 출력하고 추가된 부분은 녹색으로, 제거된 부분은 붉은 색으로 표시한다.

이런식이다.

The *source_address* argument was added.

source_address* 로 감싸고 앞에 The 를 붙였다는 뜻이다. 삭제한 부분은 없다.

다른 예도 살펴보자.

An :class:`SMTP` instance encapsulates an SMTP connection. It has methods that support a full repert
oire of SMTP and ESMTP operations. If the optional *host* and *port* parameters are given, the SMTP :
meth:`connect` method is called with those parameters during initialization. If specified, *local_ho
stname* is used as the FQDN of the local host in the HELO/EHLO command. Otherwise, the local hostnam
e is found using :func:`socket.getfqdn`. If the :meth:`connect` call returns anything other than a s
uccess code, an :exc:`SMTPConnectError` is raised. The optional *timeout* parameter specifies a timeo
ut in seconds for blocking operations like the connection attempt (if not specified, the global defau
lt timeout setting will be used). If the timeout expires, :exc:`socket.tTimeoutError` is raised. Th
e optional *source_address* parameter allows binding to some specific source address in a machine wit
h multiple network interfaces, and/or to some specific source TCP port. It takes a 2-tuple ``(host, p
ort)``, for the socket to bind to as its source address before connecting. If omitted (or if *host* o
r *port* are ``''`` and/or ``0`` respectively) the OS default behavior will be used.

대부분 마크업 변경이고, 내용의 변경은 단 하나

:exc:`socket.timeout` --> :exc:`TimeoutError`

뿐이다.

이런 식으로 작은 변경들이 듬성등성 박혀있을 때 효과적인 뷰다.

pdk glean 명령에 구현했다.3

이제 이 변경들을 msgstr 에 반영한 다음 fuzzy 를 제거하면 된다.

그런데 이 작업을 자동화할 수는 없을까? 즉 msgid 의 변경 내용을 msgstr 에 적용하는 알고리즘을 구성할 수는 없을까? 해보자.

자동 패치

Todo

각주

  1. 좀 더 정확한 스토리는 이렇다. 시즌 1 때는 fuzzy 플래그를 모두 처리한 이후에 변경된 .po 를 푸시했다. 그래서 번역 기여자들은 fuzzy 플래그가 붙은 .po 도, 그런 작업을 위한 이슈도 볼 일이 없었다. 시즌 2를 리부트하려고 하니, 이 작업량이 너무 많아서 fuzzy 플래그를 남겨둔 채로 푸시했다. 덕분에 이슈만 잔뜩 늘어났다. 이제 번역 기여자들도 원한다면 작업할 수 있다.↩︎

  2. 그리 많이 처리한 것이 아니다. pdk coverage 명령은 잔여 작업량 순으로 정렬해서 보여준다. 이 순서로 처리했기 때문에 파일 수만 많을 뿐 처리된 양은 얼마되지 않는다.↩︎

  3. 소스 코드는 https://github.com/python/python-docs-ko/blob/3.13/.pdk/pdk/gleaner.py 에서 찾아볼 수 있다.↩︎