본문으로 건너뛰기
AZ Tools

gettext .mo 카탈로그 분석기

.mo 파일은 msgfmt가 .po를 컴파일하고 남긴 결과물이며, 실제로 locale/<lang>/LC_MESSAGES/에 배포되는 파일입니다. .po를 잃어버리면 아무도 읽을 수 없는 바이너리가 되지만 구조 자체는 단순합니다. 매직 넘버, 리비전, 두 개의 문자열 테이블에 대한 개수와 오프셋, 그리고 리더가 무시해도 되는 선택적 해시 테이블이 전부입니다. 매직 넘버는 0x950412de이면서 동시에 바이트를 뒤집은 0xde120495이기도 한데, 이것이 나머지 워드의 바이트 순서를 선언하는 유일한 수단입니다. .mo 안에 숨어 있는 두 가지는 모두 내장된 NUL 바이트라 텍스트 편집기에서는 보이지 않습니다. 컨텍스트가 붙은 항목은 msgctxt, 0x04 바이트, msgid를 한 문자열에 이어 붙인 것이고, 복수형 항목은 원문 쪽에 msgid와 msgid_plural을 NUL로 구분해 담고 번역 쪽에는 각 형태를 NUL로 이어 붙입니다. 이 도구는 둘 다 분해해서 컨텍스트를 별도 열로 보여주고 복수형은 번호를 매겨 나열합니다. msgid가 빈 메타데이터 항목은 Project-Id-Version, Language, Content-Type, Plural-Forms, POT-Creation-Date로 분해하고, 나머지 문자열은 추측이 아니라 그 헤더가 선언한 인코딩으로 해독합니다. 헤더가 없거나 거짓말을 하면 조용히 깨진 글자를 내놓는 대신 그 사실을 알려 줍니다. 복수형 수식은 직접 파싱해서 n = 0부터 20까지 계산하므로 각 개수가 어떤 형태를 고르는지 볼 수 있고, 항목의 형태 개수를 nplurals와 대조합니다. 이 불일치는 특정 개수에서만 잘못된 문자열이 나오는 실제 버그입니다. 그 밖에 미번역 항목, msgid와 완전히 같은 번역, 번역에서 빠진 %s나 {}, 정렬되지 않은 원문 테이블, 항목 수보다 슬롯이 적은 해시 테이블도 짚어 줍니다. 다만 .po로 되돌리지는 않으며, 번역 품질을 판단하지도 않습니다.

사용법

  1. locale/<lang>/LC_MESSAGES/에 있는 .mo 파일을 상자에 놓으세요. msgfmt가 만든 파일이면 무엇이든 됩니다.
  2. 파일 헤더 줄을 먼저 보세요. 바이트 순서, 리비전, 항목 수, 두 문자열 테이블과 해시 테이블이 파일 안 어디에 있는지 알 수 있습니다.
  3. 메타데이터 항목에서 인코딩을 확인하세요. 아래 모든 문자열이 그 인코딩으로 해독되므로 Content-Type이 틀리면 가장 먼저 고쳐야 합니다.
  4. n = 0부터 20까지의 복수형 표에서 각 개수가 고르는 형태를 확인하고, 복수형 항목의 형태 개수를 nplurals와 비교하세요.
  5. 발견된 문제 목록을 훑은 뒤 필터로 원하는 msgid, 번역, 컨텍스트를 찾아 표에서 확인하세요.

자주 묻는 질문

매직 넘버가 왜 두 가지 값으로 나오나요?
매직 넘버는 0x950412de 하나뿐입니다. 다만 .mo 파일은 모든 32비트 워드를 컴파일한 기계의 바이트 순서로 저장하고 매직 넘버도 똑같이 저장합니다. 리더는 앞 4바이트를 두 방향으로 읽어서, 리틀 엔디언으로 읽었을 때 0x950412de가 나오면 파일 전체가 리틀 엔디언이고 빅 엔디언으로 읽었을 때 나오면 전체가 빅 엔디언이라고 판단합니다. 다른 곳에 플래그는 없습니다. 그래서 빅 엔디언 장비에서 만든 카탈로그도 리틀 엔디언 장비에서 그대로 동작합니다. 이 페이지는 판별 결과 옆에 원본 4바이트를 함께 보여 주므로 판별을 믿는 대신 직접 확인할 수 있습니다.
일부 msgid 안에 있는 0x04 바이트는 무엇인가요?
msgctxt를 저장하는 방식입니다. gettext에는 컨텍스트를 담는 별도 필드가 없어서 msgfmt가 컨텍스트, 0x04 바이트 하나(ASCII EOT), msgid를 한 문자열로 이어 붙이고 pgettext는 그 합쳐진 문자열을 찾습니다. 그래서 "Open"이라는 같은 msgid를 가진 두 항목이 서로 다른 항목일 수 있습니다. 하나는 "menu\x04Open"이고 다른 하나는 "state\x04Open"입니다. 편집기나 단순한 덤프에서는 구분자가 보이지 않아 중복처럼 보입니다. 이 도구는 그 바이트로 나눠 컨텍스트를 별도 열에 넣기 때문에 같은 msgid를 공유하는 항목이 나란히 보입니다.
복수형은 어떻게 저장되고 nplurals와 무엇이 맞아야 하나요?
원문 쪽은 msgid, NUL 바이트, msgid_plural 순서이고 번역 쪽은 각 형태를 NUL로 이어 붙인 것입니다. 형태가 몇 개여야 하는지는 헤더가 정합니다. Plural-Forms에 nplurals와, 사용할 형태의 인덱스를 돌려주는 n에 대한 수식이 들어 있습니다. 항목의 형태가 nplurals보다 적으면 없는 인덱스를 고르는 개수에서는 아무것도 돌아오지 않고 gettext가 번역되지 않은 msgid나 msgid_plural로 되돌아갑니다. 일부 숫자에서만 그러기 때문에 테스트를 통과해 버립니다. 이 페이지는 모든 복수형 항목의 형태 수를 세어 헤더와 어긋나는 항목을 표시합니다.
Content-Type 헤더가 없거나 틀리면 어떻게 되나요?
파일 안에서 자신의 인코딩을 밝히는 것은 그 헤더뿐이라 리더는 그것을 믿을 수밖에 없습니다. 파이썬의 gettext는 아예 거부합니다. 채워지지 않은 charset=CHARSET은 알 수 없는 인코딩 오류를, 선언된 인코딩에 맞지 않는 바이트는 디코딩 오류를 냅니다. 이 페이지는 일부러 더 너그럽게 동작해서 UTF-8로 되돌아가 읽을 수 있는 만큼 읽고 둘 중 무엇이 일어났는지 알려 줍니다. 애초에 이 파일을 여는 이유가 애플리케이션에서 글자가 깨지는 원인을 찾기 위해서인 경우가 많기 때문입니다. 메타데이터 항목이 아예 없으면 인코딩도 언어도 복수형 규칙도 없으며, gettext는 문자열을 ASCII로 보고 n != 1 규칙을 적용합니다.
항목의 순서가 중요한가요?
중요합니다. 손으로 .mo를 만드는 도구가 가장 자주 놓치는 부분입니다. gettext 명세는 원문 문자열이 바이트 순서로 정렬되어 있어야 한다고 규정합니다. 리더가 원문 테이블에 대해 이진 탐색으로 항목을 찾아도 되기 때문입니다. C 구현은 쓸 만한 해시 테이블이 없을 때 실제로 그렇게 하므로, 정렬되지 않은 카탈로그는 일부 항목을 조용히 찾지 못합니다. 반면 파일 전체로 사전을 만드는 파이썬은 아무 문제 없이 읽습니다. 테스트 스크립트에서는 되고 실제 애플리케이션에서는 안 되는 고약한 버그입니다. 이 페이지는 정렬을 검사해 어긋난 쌍의 개수를 알려 줍니다.
해시 테이블은 무엇이고 무시해도 되나요?
선택적인 조회 가속 장치입니다. msgfmt는 항목 수보다 3분의 1쯤 큰 hashpjw 테이블을 개방 주소법으로 만들어, 리더가 이진 탐색 없이 msgid를 찾을 수 있게 합니다. 리더는 이를 완전히 무시해도 되고 파이썬이 실제로 그렇게 합니다. 크기가 0인 카탈로그도 정상입니다. 정상이 아닌 것은 슬롯 수가 항목 수보다 크지 않은 테이블입니다. 개방 주소법은 최소 한 칸이 비어 있어야 하기 때문입니다. 그런 조합은 포맷을 이해하지 못한 도구가 파일을 썼다는 뜻이라 여기서 보고합니다. 표시되는 크기와 오프셋은 헤더 워드에서 그대로 가져온 값입니다.

관련 도구