261개의 문서, 6개 언어, 한 명의 관리자: Frontmatter가 진실의 원천이다
요약
대규모 다국어 문서 관리의 어려움을 해결하기 위해 YAML frontmatter를 단일 진실의 원천(SSOT)으로 활용하는 방법을 소개합니다. 수동 인덱스 편집 대신 데이터를 기반으로 인덱스를 자동 재생성하는 메커니즘을 다룹니다.
핵심 포인트
- 문서를 산문이 아닌 구조화된 데이터로 취급해야 함
- YAML frontmatter를 통해 제목, 카테고리, 태그 등을 관리
- 모든 인덱스는 수동 편집 없이 파일로부터 자동 재생성
- CI를 활용해 인덱스 불일치 문제를 자동으로 감지
만약 당신이 관리하는 docs 폴더가 인덱스 항목 누락, 오래된 번역, 더 이상 일치하지 않는 카테고리 등으로 인해 점점 어긋나고 있다면, 이 글이 도움이 될 것입니다.
**NENE2**는 AI가 읽을 수 있는 비즈니스 API를 구축하기 위한 저의 작은 PHP 프레임워크입니다.
Repository:
https://github.com/hideyukiMORI/NENE2
시간이 흐르면서 이 프레임워크는 방대한 양의 How-to 가이드를 갖게 되었습니다. 현재 영어 디렉토리에는 261개의 가이드가 있으며, 그 각각은 5개의 추가 언어인 일본어, 프랑스어, 중국어, 독일어, 브라질 포르투갈어로 미러링되어 있습니다.
즉, 6개의 로케일(locales)에 걸친 261개의 가이드이며, 총 1,566개의 파일입니다.
이 모든 것은 한 명에 의해 관리됩니다.
이 상태를 정상적으로 유지할 수 있는 유일한 방법은 문서를 산문(prose)으로 취급하는 것을 멈추고 데이터(data)로 취급하기 시작하는 것입니다.
이 글은 그것을 가능하게 만드는 메커니즘에 관한 것입니다.
학습 내용:
- YAML frontmatter를 단일 진실의 원천(single source of truth)으로 만들고 디스크의 모든 인덱스를 재생성하는 방법
- 분류 체계(taxonomy)를 중복시키지 않고 6개 로케일의 번역을 동기화하는 방법
- 인덱스 어긋남(drift)을 수동 작업이 아닌 CI의 Red check(실패 항목)로 전환하는 방법
수동으로 관리되는 인덱스의 문제점
문서 폴더를 관리해 본 적이 있다면, 인덱스 페이지가 어떻게 부식되는지 알고 있을 것입니다.
가이드를 하나 추가합니다. 하지만 인덱스에 추가하는 것을 잊어버립니다. 다른 누군가가 파일 이름을 변경합니다. 카테고리 목록이 어긋납니다. 번역이 누락되지만 몇 달 동안 아무도 알아차리지 못합니다.
이 중 어느 것도 극적인 일은 아닙니다. 그저 서서히 붕괴되는 것뿐입니다.
가이드가 6개라면 상관없습니다. 하지만 261 × 6개라면 치명적입니다.
그래서 제가 정한 규칙은 간단합니다:
어떠한 인덱스도 수동으로 편집하지 않는다. 모든 인덱스는 디스크 상의 파일로부터 재생성된다.
Frontmatter가 진실의 원천이다
각 영어 가이드는 YAML frontmatter 블록으로 시작합니다:
---
title: "How-to: A/B Testing Framework"
category: product
...
해당 블록은 인덱스가 가이드에 대해 알고 있는 모든 것에 대한 진실의 원천(source of truth)입니다:
title— 표시 이름 (display name)category— 7개의 고정된 버킷 중 하나tags— 소문자 케밥 케이스 (kebab-case) 레이블difficulty— 초급 (beginner), 중급 (intermediate), 또는 고급 (advanced)related— 형제 가이드들의 슬러그 (slugs)ft— 선택 사항인 필드 테스트 (field-trial) 참조
7개의 카테고리는 코드에 고정되어 있습니다:
getting-started
auth
security
...
가이드 본문은 프론트매터 (frontmatter) 뒤에 이어집니다. 프론트매터는 단순한 장식이 아닙니다. 이는 제너레이터 (generator)가 읽어들이는 구조화된 데이터 (structured data)입니다.
제너레이터 (The generator)
단일 스크립트인 tools/build-howto-index.php가 모든 인덱스를 다시 빌드합니다. 이 스크립트는 composer howto:index에 연결되어 있습니다.
영어의 경우, 모든 가이드를 순회하며 프론트매터를 파싱 (parse)하고, 가이드들을 7개의 카테고리로 그룹화합니다. 그리고 다음 두 가지를 작성합니다:
docs/howto/README.md에 삽입되는 "카테고리별 찾아보기" (Browse by category) 테이블- 각 태그 아래에 모든 가이드를 그룹화하는 별도의
by-tag.md페이지
카테고리 루프 (loop)는 몇 줄의 코드로 전체 아이디어를 보여줍니다:
foreach (guideFiles($dir) as $file) {
$slug = basename($file, '.md');
$fm = readFrontmatter($file);
...
프론트매터가 없거나 category가 없는 가이드들은 조용히 누락되지 않습니다. 이들은 $missing에 수집되어 경고 (warning)로 출력됩니다. 무언가를 건너뛰는 것은 조용한 공백이 아니라 눈에 보이는 이벤트입니다.
번역본에는 프론트매터가 포함되지 않습니다
사람들을 놀라게 하는 결정이 하나 있습니다. 번역된 가이드에는 프론트매터가 전혀 없습니다.
일본어 가이드는 바로 H1 태그로 시작합니다:
# ハウツー: A/B テストフレームワーク
번역본에는 category: 라인이 없습니다. 분류 체계 (taxonomy)를 6개 언어로 복제하는 것은 동기화가 어긋날 기회를 6번 만드는 것과 같습니다.
따라서 제너레이터는 로케일 (locale)을 다르게 취급합니다. 영어가 아닌 모든 로케일에 대해서는, 각 파일에서 처음 발견되는 H1을 사용하여 **평면적인 알파벳순 인덱스 (flat, alphabetical index)**를 생성합니다:
function howtoTitle(string $path): string
{
foreach (preg_split('/
?
/', file_get_contents($path)) as $line) {
...
분류 체계(Taxonomy)는 정확히 단 한 곳, 즉 영어 프론트매터(frontmatter)에 존재합니다. 번역본은 단지 존재하기만 하면 되며 헤더(heading)만 가지고 있으면 됩니다. 이를 통해 진실의 원천(source of truth)을 단일하게 유지합니다.
덮어쓰지 않고 주입하기 (Injecting without clobbering)
각 README에는 제가 유지하고 싶은 수동 작성 부분이 있습니다. 바로 큐레이션된 "I want to..." 찾기 테이블입니다.
따라서 생성기(generator)는 파일 전체를 덮어쓰지 않습니다. 오직 두 마커(marker) 사이의 콘텐츠만 교체합니다.
<!-- AUTO-INDEX:START (generated by `composer howto:index` — do not edit by hand) -->
...재생성된 콘텐츠...
<!-- AUTO-INDEX:END -->
시작 마커 이전과 종료 마커 이후의 모든 내용은 있는 그대로 보존됩니다. 이 주입 방식은 멱등성(idempotent)을 가집니다. 즉, 두 번 실행해도 바이트 단위로 동일한 출력을 얻습니다. 이러한 특성 덕분에 전체 과정을 CI(지속적 통합)에서 강제할 수 있습니다.
드리프트(Drift)는 CI 실패 요인이다
생성기는 오래된 인덱스가 병합될 수 없을 때만 유용합니다. 그 보장은 CI 파이프라인에 있습니다.
- name: Verify howto index is up to date
run: |
composer howto:index
...
첫 번째 단계는 모든 인덱스를 재생성한 다음 git diff --exit-code를 실행합니다. 만약 재생성 과정에서 어떠한 변경 사항이라도 발생하면, 워킹 트리(working tree)가 더러워진(dirty) 상태가 되고, diff가 비어 있지 않게 되어 빌드가 실패합니다.
다시 말해, 가이드를 추가했지만 인덱스를 재생성하지 않았다면 CI가 이를 감지합니다. CI가 사용자를 대신해 인덱스를 재생성하고, 커밋된 버전이 일치하지 않는다는 것을 확인하기 때문입니다.
두 번째 단계는 별도의 검증기인 tools/validate-howto-frontmatter.php입니다. 이 도구는 모든 영어 가이드를 스키마(schema)에 따라 검사합니다. 필수 필드 존재 여부, 허용된 집합 내의 category 및 difficulty, 소문자 케밥 케이스(kebab-case)인 태그(tags), 그리고 실제로 존재하는 가이드를 가리키는 related 링크 등을 확인합니다. --require-all 플래그는 프론트매터가 없는 영어 가이드 또한 빌드 실패로 처리함을 의미합니다.
이 두 단계 사이에서 드리프트(drift)가 조용히 누적되는 것은 불가능합니다. 누락된 인덱스 항목, 카테고리의 오타, 깨진 related 링크, 또는 메타데이터가 없는 가이드는 모두 빨간색 체크(실패)로 변합니다.
단계별 구축 과정
이것은 한 번에 구축된 것이 아닙니다. Git 히스토리를 보면 의도적인 단계적 배포 과정을 확인할 수 있습니다:
- Phase A — H1 헤딩(heading)부터 시작하여 자동 생성된 인덱스(index)를 모두 추가합니다.
- Phase B1 — 프론트매터 (frontmatter) 스키마 (schema)를 확정하고 5개의 가이드에서 이를 검증합니다.
- Phase B2 — 모든 영어 가이드에 프론트매터 (frontmatter)를 주석으로 추가합니다 (당시 기준 256개).
- Phase B3 — 프론트매터 (frontmatter)로부터 인덱스 (index)를 다시 생성하고, 이를 CI에서 영구적으로 필수 사항으로 만듭니다.
마지막 단계는 프론트매터 (frontmatter)를 "있으면 좋은 것"에서 "없으면 빌드가 실패하는 것"으로 뒤바꾼 결정적인 단계였습니다. 그 이후 가이드 수는 256개에서 261개로 늘어났으며, 시스템은 수동 인덱스 수정 없이도 이러한 성장을 흡수했습니다.
번역 작업량
가이드를 하나 추가하는 것은 파일 하나를 추가하는 것이 아닙니다. 하나의 영어 가이드와 5개의 번역본이 추가되는 것이며, 인덱스 (index)는 6개 로케일 (locale) 모두에서 완전한 상태를 유지해야 합니다.
실제로 번역은 배치 (batch) 단위로 이루어집니다. 커밋 히스토리에는 "미번역된 N개의 가이드를 5개 로케일 전체로 번역"과 같은 항목들이 존재합니다. 이것은 AI 보조 1인 프로젝트이므로, 가이드의 6개 언어 버전을 초안 작성하는 것은 모델에게 위임한 후 검토를 거치는 전형적인 작업 방식입니다.
하지만 솔직한 부분은 이것입니다. 품질 (quality) 관문은 "AI가 작성했는가"가 아닙니다. 관문은 모두에게 동일한 기계적 검사입니다. 번역이 존재하여 인덱스 (index) 행을 생성하거나, 아니면 로케일 인덱스 (locale index)가 CI가 재생성한 것과 달라 빌드가 빨간색(실패)으로 변합니다. 생성기는 누가 또는 무엇이 파일을 만들었는지 상관하지 않습니다.
과거의 나에게 해주고 싶은 말
규모가 커졌을 때 버텨준 세 가지 요소는 다음과 같습니다:
- 단일 진실 공급원 (One source of truth). 분류 체계 (taxonomy)는 영어 프론트매터 (frontmatter)에만 존재하며 다른 곳에는 없습니다. 번역은 콘텐츠를 반영할 뿐, 메타데이터 (metadata)를 반영하지는 않습니다.
- 수동 편집하지 말고 생성할 것. 모든 인덱스 (index)는 빌드 아티팩트 (build artifact)입니다. 수동으로 작성되는 부분은 자동 인덱스 마커 (auto-index marker) 외부에만 존재합니다.
- 드리프트 (drift)를 번거로운 일이 아닌 실패로 간주할 것. CI에서 재생성하고 커밋과 차이(diff)를 비교하세요. 오래된 인덱스 (index)는 누군가 기억해주길 바라는 대상이 아니라, 빌드 실패(red build)를 의미해야 합니다.
이 중 영리한 것은 하나도 없습니다. 의도적으로 지루하게 만든 것입니다.
지루함이란, 문서들이 밑바닥에서 조용히 부패하지 않도록 한 명의 관리자가 6개 언어로 된 261개의 가이드를 계속 유지할 수 있게 해주는 힘입니다.
링크
── Hideyuki Mori (Ayane International) 작성 — 저는 소규모 비즈니스를 위해 정해진 가격으로 백오피스 시스템 (back-office systems)을 구축합니다.
🔗 hideyuki-mori.com
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기