md 한영 병기 표기 렌더링
mkdocs-material에서 md 파일 글에 대해 한/영 병기를 하고 싶어. 안녕하세요. Hello. Everyone. 이런 식으로 말이야. 이 때 영어는 좀 연하고 작게 표기하고 싶은데, 이걸 md에 어떤 마크를 추가해서 자동으로 그렇게 렌더링 해줬으면 좋겠어. 안녕하세요.
좋아요! “한국어 원문 + 연하고 작은 영어 번역”은 마크다운 속성 리스트(attr_list) 를 쓰는 방법이 가장 간단하고, 원하시면 <en> 같은 커스텀 태그로도 가능합니다. 두 가지를 모두 보여드릴게요.
방법 A: 가장 간단 — 문단에 클래스 붙이기 (attr_list)
1) mkdocs.yml 설정
theme:
name: material
markdown_extensions:
- attr_list # ← 이 줄 추가
extra_css:
- stylesheets/translation.css # ← 커스텀 CSS 등록
2) docs/stylesheets/translation.css 작성
/* 영어 번역 공통 스타일 */
.md-typeset .en {
font-size: 0.85em; /* 조금 작게 */
opacity: 0.65; /* 연하게 */
}
/* 한국어 문단 바로 다음에 오는 영어 번역은 간격을 조금만 */
.md-typeset p + p.en {
margin-top: 0.25em;
}
3) 마크다운에서 사용
안녕하세요.
Hello. Everyone. {.en}
-
Hello… {.en}처럼 문장 끝에{.en}을 붙이면, 렌더링 시<p class="en">…</p>가 되어 위 CSS가 적용됩니다. -
문장 중간 인라인 으로 쓰고 싶으면:
안녕하세요. <span class="en">Hello. Everyone.</span>
방법 B: 커스텀 태그 <en> 그대로 쓰기
원하신 표기대로 써도 됩니다. Python-Markdown은 기본적으로 인라인 HTML 통과 를 허용하므로 아래처럼 작성하고 CSS에서 <en>을 스타일링하세요.
1) 마크다운
안녕하세요.
<en>Hello. Everyone.</en>
2) translation.css
/* 알 수 없는 태그도 CSS에서 그대로 선택 가능 */
.md-typeset en {
font-size: 0.85em;
opacity: 0.65;
display: block; /* 줄바꿈 형태라면 block이 보기 좋음 */
margin-top: 0.25em;
}
참고: HTML 표준상 “커스텀 요소”는 보통 이름에 하이픈이 들어갑니다(
<en-text>등). 굳이 표준을 따르려면<en-text>로 쓰고 CSS도en-text로 지정해도 됩니다. 다만 실제 동작은<en>도 문제없습니다.
(선택) 접근성까지 고려한 버전
번역 텍스트에 lang="en"을 더해주면 스크린리더가 언어 전환을 인지합니다.
Hello. Everyone. { .en lang=en }
CSS는 그대로 .en에 걸려서 시각 스타일은 유지되고, 보조공학 친화성만 올라갑니다.
어떤 방법을 쓰면 좋을까?
-
가장 권장: 방법 A (attr_list)
- 마크다운 문법을 유지하면서도 깔끔하게 클래스만 부여 → 관리 쉬움
-
원하신 표기를 그대로 쓰고 싶다면: 방법 B (
<en>…</en>)- 취향/가독성 면에서 선호하면 사용, 다만 HTML 태그를 직접 쓰는 점만 유의
둘 중 하나만 채택하셔도 되고, 문단은{.en}, 인라인은 <span class="en">…</span>처럼 혼용해도 깔끔하게 관리됩니다.
필요하시면 목록/표/인용문 등에서의 미세 간격 조정용 CSS도 이어서 만들어 드릴게요.
안 되는데 띄어쓰기나 줄바꿈 같은걸 잘 해야하나?
가능해! 주로 띄어쓰기/줄바꿈 과 설정 로딩 에서 자주 미끄러져요. 아래 순서대로 한 번만 점검해보죠. (복붙용 최소 예제도 맨 아래에 넣었어요.)
1) 설정이 진짜 켜졌는지 확인
mkdocs.yml에 정확히 다음이 있어야 해요.theme: name: material markdown_extensions: - attr_list # ← 이게 있어야 {.en}이 먹습니다. extra_css: - stylesheets/translation.css-
docs/stylesheets/translation.css파일이 실제로 존재하는지 확인. mkdocs serve를 돌리는 중에extra_css를 추가했다면, 서버를 한 번 재시작 해 주세요. (가끔 새 CSS를 못 읽고 있습니다.)
2) 줄바꿈 규칙 이해 (여기서 많이 틀어집니다)
마크다운에서:
-
빈 줄(공백 줄) = 새 문단(
<p>…</p>) -
그냥 줄바꿈 하나 = 같은 문단 안에서 공백 하나(줄바꿈이 사라짐)
-
줄 끝에 공백 두 칸 + 엔터 = 강제 줄바꿈(
<br>)
원하는 형태가 “한국어 한 줄 + (조금 연하고 작은) 영어 한 줄”이라면,
-
문단을 분리 하거나(빈 줄 1개),
-
강제 줄바꿈 을 쓰거나(한국어 줄 끝 두 칸 + 엔터)
둘 중 하나를 반드시 해 주세요.
3) 실제 작성 예제 3가지
A. 가장 간단: 문단 분리 + {.en}
안녕하세요.
Hello. Everyone. {.en}
포인트: 한국어와 영어 사이에 빈 줄 이 있어야 영어가 별도 문단 이 되고,
{.en}이 그 문단에 적용됩니다.
B. 같은 문단 안에서 줄만 바꾸고 싶을 때 (간격 더 촘촘)
안녕하세요.··
<span class="en">Hello. Everyone.</span>
한국어 줄 끝의
··(공백 두 칸)가 강제 줄바꿈 입니다.
<span>은 HTML이므로 줄바꿈을 위해<br>를 써도 됩니다:md 안녕하세요.<br> <span class="en">Hello. Everyone.</span>
C. 커스텀 태그 <en> 그대로
안녕하세요.
<en>Hello. Everyone.</en>
인라인 HTML은 기본 허용이라 별도 확장 필요 없습니다. (블록 HTML로 감싸 복잡하게 쓰는 경우에만
md_in_html이 필요)
4) CSS (Material 테마 기준, 복붙)
docs/stylesheets/translation.css
/* 영어 번역 공통 스타일 */
.md-typeset .en,
.md-typeset en,
.md-typeset span.en {
font-size: 0.85em; /* 조금 작게 */
opacity: 0.65; /* 연하게 */
}
/* 문단으로 분리한 경우: 한국어 문단 바로 뒤 영어 문단의 위 여백 줄이기 */
.md-typeset p + p.en {
margin-top: 0.25em;
}
/* 줄바꿈(브레이크) 뒤에 오는 인라인 번역을 '한 줄'처럼 보이게 */
.md-typeset span.en {
display: inline-block; /* <br> 다음에 내려오면 한 줄로 떨어져 보임 */
margin-top: 0.25em;
}
/* 커스텀 태그 <en>을 블록처럼 쓰고 싶다면 */
.md-typeset en {
display: block;
margin-top: 0.25em;
}
5) 체크리스트(문제 계속되면 여기서 잡힙니다)
-
mkdocs.yml에attr_list가 정확히 들어가 있다. (오탈자, 들여쓰기 주의) -
extra_css경로가 실제 파일 위치와 일치 한다. (docs/stylesheets/translation.css) -
서버 재시작 후 새 CSS가 로드된다.
-
마크다운에서 영어 줄을 별도 문단 으로 만들려면 빈 줄 1개 ,
같은 문단 안 줄바꿈 이면 한국어 줄 끝에 공백 2칸 또는<br>. -
개발자도구에서
.en규칙이 실제로 적용 되는지 확인한다. (선택자 앞에.md-typeset를 붙이면 우선순위 문제를 대부분 이깁니다.)
6) 복붙용 “완전 최소 예제”
mkdocs.yml
site_name: demo
theme:
name: material
markdown_extensions:
- attr_list
extra_css:
- stylesheets/translation.css
docs/stylesheets/translation.css → 위 CSS 그대로
docs/index.md
# 데모
안녕하세요.
Hello. Everyone. {.en}
---
안녕하세요.··
<span class="en">Hello. Everyone.</span>
---
안녕하세요.
<en>Hello. Everyone.</en>
이대로 하면 3가지 방식 모두 “연하고 작게” 나옵니다.
그래도 안 되면, 사용하신 mkdocs.yml과 한두 줄의 md 예제를 붙여주시면 바로 원인 pinpoint 해드릴게요!
다음 줄에다가 {: .en}을 쓰니까 되네 안녕하세요. (공백 두개) Hello. Everyone. {: .en} 이런 식으로 말이야. 근데 문제는 한글까지 같이 연하게 변해버려.
맞아요—그 증상은 {: .en}이 “이전 블록 전체”에 붙어서 그래요.
지금 쓰신 형태는 안녕하세요. 뒤의 공백 두 칸 때문에 한국어+영어가 같은 문단(<p>…</p>) 안에 들어갑니다. 그러면 {: .en}이 그 문단 전체 에 적용되어 한글까지 연해져요.
아래 셋 중 하나로 고치면 딱 해결됩니다.
✅ 방법 1: 문단을 분리 하고 영어 줄에만 클래스 붙이기 (가장 깔끔)
안녕하세요.
Hello. Everyone. {.en}
- 포인트: 한국어와 영어 사이에 빈 줄 1개 → 서로 다른 문단이 되면서
.en은 영어 문단에만 적용.
✅ 방법 2: 앞줄에 속성 배치 해서 “다음 블록”에만 적용
안녕하세요.
{:.en lang=en}
Hello. Everyone.
-
{: .en}을 영어 문단 앞줄 에 두면, 바로 아래 블록(영어 문단) 에만 적용됩니다. -
접근성까지 챙기려면
lang=en도 같이 두세요.
✅ 방법 3: 같은 문단을 유지 하려면 <br> + 인라인 span
안녕하세요.<br>
<span class="en" lang="en">Hello. Everyone.</span>
- 한국어 줄 끝 공백 두 칸 대신
<br>로 줄바꿈하고, 영어만<span class="en">…</span>로 감싸면 됩니다.
왜 이런가요? (핵심 정리)
-
공백 두 칸 + 엔터 = 같은 문단 안의
<br> -
{: .en}(단독 줄) = 바로 앞 블록 에 붙음 → 같은 문단이면 한글+영어 모두 에 적용됨 -
해결책은 블록을 나누거나(방법 1/2), 인라인만 감싸기(방법 3)입니다.
필요하면, 지금 쓰시는 실제 한두 줄을 붙여 주시면 그 형태에 맞춰 가장 간단한 문법으로 딱 맞춰 드릴게요!
아니 왜 자꾸 {.en}이라고 하는건데, 이거 동작하는거야?
맞아요, 헷갈릴 만해요. 정리하면:
{.en}={:.en}
-
둘 다 attr_list 확장 문법입니다. (콜론은 선택)
-
동작 조건:
mkdocs.yml에markdown_extensions: - attr_list가 있어야 합니다.
- 붙는 위치 규칙(핵심)
-
{: .en}같은 속성 줄은 “바로 앞 블록”에 붙습니다. -
한글 줄 끝에 공백 두 개를 넣으면 한글+영어가 한 문단(같은 블록) 이라서,
{: .en}이 둘 다에 적용돼요 → 그래서 한글까지 연해진 것.
- 원하는 효과(영어만 연하게) 만드는 법 – 3가지 중 하나 선택
- ✅ 문단 분리 + 영어에만 클래스 (가장 간단)
안녕하세요. Hello. Everyone. {.en}
또는 ```md 안녕하세요.
Hello. Everyone.
{:.en}
```
빈 줄 하나로 영어를 별도 문단 으로 만들면
.en이 영어 문단에만 적용됩니다.
- ✅ 같은 문단을 유지해야 한다면 → 줄바꿈은
<br>로, 영어만 감싸기안녕하세요.<br> <span class="en" lang="en">Hello. Everyone.</span> - ✅ 커스텀 태그 선호 시
안녕하세요. <en lang="en">Hello. Everyone.</en>
- CSS 예시(Material 테마 기준)
/* 영어 번역을 작고 연하게 */
.md-typeset .en,
.md-typeset en {
font-size: 0.85em;
opacity: 0.65;
}
/* 한국어 문단 바로 다음 영어 문단 간격 다듬기 */
.md-typeset p + p.en { margin-top: 0.25em; }
/* <en>을 줄바꿈 형태로 쓰려면 */
.md-typeset en { display: block; margin-top: 0.25em; }
- 빠른 체크리스트
-
mkdocs.yml에- attr_list가 있음? -
CSS가
extra_css로 로드됨? -
한글 줄 끝 공백 두 개 로 줄바꿈을 만들고 있지는 않음? (그럼 같은 문단이 됩니다)
-
영어를 별도 문단 으로 만들었는지, 아니면
<span class="en">…</span>로 인라인만 감쌌는지
요약: {.en}/{:.en}은 제대로 동작합니다. 다만 “속성은 바로 앞 블록 전체 에 붙는다”는 규칙 때문에, 영어만 스타일링하려면 영어를 별도 블록으로 분리 하거나 영어만 인라인으로 감싸야 합니다.
아니 그냥 Hello. Everyone. {.en} 이렇게 최종 렌더링 되는데
아하, 그럼 상황이 확실해요.
Hello. Everyone. {.en}가 그대로 글자로 보이는 건 MkDocs(=Python-Markdown)에서는 문단(paragraph) 끝에{.en}을 붙이는 문법이 동작하지 않기 때문입니다. 그건 주로 Kramdown(Jekyll) 쪽 문법이고, MkDocs에서는 단독 줄의 속성 블록 을 써야 해요.
아래처럼 고치면 됩니다—차근차근:
1) 결론부터: 이렇게 쓰세요
안녕하세요.
{:.en lang=en}
Hello. Everyone.
-
포인트 1: 한국어와 영어 사이에 빈 줄 1개 → 서로 다른 문단 이 됩니다.
-
포인트 2:
{: .en}은 “바로 아래 블록(영어 문단)” 에만 붙게 앞줄에 둡니다. -
포인트 3: 접근성용으로
lang=en도 같이 달아주면 좋아요.
2) 왜 Hello. Everyone. {.en}가 안 먹나?
-
MkDocs의 attr_list 는 “단독 줄 의 속성 블록”이나 “특정 요소 바로 뒤(예: 이미지, 링크, 헤더 등)”에서 주로 작동합니다.
-
평범한 문단 텍스트 뒤에 같은 줄로
{.en}을 붙이는 방식은 지원하지 않아서 그대로 렌더링돼요.
3) 공백 두 칸(+엔터) 쓰면 왜 한글까지 흐려지나?
-
줄 끝 공백 두 칸 은 같은 문단 안
<br>만 만들어요. -
그래서 “안녕하세요 +
<br>+ Hello …”가 한 문단 이 되고,
뒤줄의{: .en}을 쓰면 그 한 문단 전체 에 클래스가 붙어 한글까지 연해집니다. -
해결: 공백 두 칸을 지우고 빈 줄 로 문단 분리 하거나, 같은 문단을 유지하고 싶다면 영어만 인라인으로 감싸기 👇
안녕하세요.<br>
<span class="en" lang="en">Hello. Everyone.</span>
4) CSS(그대로 쓰셔도 됩니다)
/* 영어 번역: 조금 작고 연하게 */
.md-typeset .en,
.md-typeset en {
font-size: 0.85em;
opacity: 0.65;
}
/* <en> 커스텀 태그를 블록처럼 쓸 때 */
.md-typeset en {
display: block;
margin-top: 0.25em;
}
/* 문단 분리 케이스: 한글 문단 바로 뒤 영어 문단의 간격 */
.md-typeset p + p.en { margin-top: 0.25em; }
5) 빠른 체크
-
mkdocs.yml→markdown_extensions: - attr_list들어가 있나요? -
한국어 줄 끝 공백 두 칸 제거하고 빈 줄 로 문단 분리했나요?
-
영어 문단 앞줄 에
{: .en}을 뒀나요? (뒤줄×, 같은 줄×)
원하시면 지금 문제 나는 실제 3~4줄 붙여 주시면, 그 형태에 딱 맞춰 “한 줄 수정” 버전으로 바로 고쳐드릴게요!
markdown 패키지만 가지고 한 번 테스트 해보고 싶어. 쥬피터 노트북 예시 만들어줘
좋아! Python-Markdown만 으로 테스트해볼 수 있는 쥬피터 노트북을 만들어 놨어.
여기서 내려받아 로컬에서 실행해 보세요:
Download the Jupyter Notebook
노트북에 들어있는 것 (step-by-step)
-
!pip install markdown— Python-Markdown 설치 -
attr_list확장 켠 렌더 함수 + CSS -
케이스별 미리보기:
-
A :
Hello. Everyone. {.en}→ 동작 안 하는 예시를 눈으로 확인 -
B :
{: .en}줄을 앞 에 두면 다음 블록 에 적용 -
C :
{: .en}줄을 뒤 에 두면 이전 블록 에 적용 -
D : 빈 줄로 문단 분리 후 영어 문단에만
{:.en}적용 -
E : 같은 문단 유지 →
<br>+<span class="en">…</span> -
F :
<en>…</en>커스텀 태그
-
사용 팁 (핵심만)
-
문단 텍스트 같은 줄 끝의
{.en}는 보통 문단에는 적용되지 않음 → A 케이스로 확인 가능 -
문단에 클래스 주려면 속성만 있는 줄(
{:.en})을 앞 또는 뒤 에 둬서 그 블록에 붙이기(B/C) -
한국어/영어를 다른 문단 으로 분리하면(D) 제어가 가장 깔끔
-
한 문단 내 줄바꿈이면(E) 영어만
<span class="en">로 감싸기
노트북 돌려보시고, 혹시 특정 케이스가 기대와 다르게 나오면 그 셀 출력 캡처나 원문을 붙여줘—원인 딱 집어서 고쳐드릴게!
보기 힘들어 그냥 본문에 써줘
오케이—노트북 파일 말고, 그대로 복붙해서 돌릴 수 있는 셀 을 본문에 씁니다.
아래 두 셀만 Jupyter에 순서대로 붙여넣고 실행하세요. (첫 셀은 설치, 두 번째 셀은 케이스 A~F를 한 번에 렌더/미리보기)
셀 1 — 설치
!pip -q install markdown
셀 2 — 렌더 도우미 + 케이스 A~F 미리보기
from IPython.display import HTML, display
import markdown
CSS = '''
.md-typeset .en,
.md-typeset en {
font-size: 0.85em;
opacity: 0.65;
}
.md-typeset en {
display: block;
margin-top: 0.25em;
}
.md-typeset p + p.en { /* 한국어 문단 바로 다음 영어 문단 간격 */
margin-top: 0.25em;
}
/* 보기 편하도록 테두리 */
.demo {
border: 1px solid #ddd;
padding: 12px;
margin: 8px 0 24px 0;
border-radius: 6px;
background: #fafafa;
}
'''
def render_md(md_text, title=""):
html = markdown.markdown(
md_text,
extensions=[
"attr_list", # 핵심 확장
# "extra", # 원하면 추가(여기에도 attr_list 포함됨)
# "md_in_html" # 복잡한 HTML 블록 내 마크다운이 필요할 때
]
)
wrapped = f"""
<style>{CSS}</style>
<div class="demo">
<div><strong>Markdown 원문</strong></div>
<pre style="white-space:pre-wrap">{md_text}</pre>
<div style="margin-top:8px"><strong>렌더 결과</strong></div>
<div class="md-typeset">{html}</div>
</div>
"""
display(HTML(f"<h3>{title}</h3>" + wrapped))
# ───────── 테스트 케이스 ─────────
# A) 같은 줄 끝에 {.en} → 일반 문단에서는 보통 "동작 안 함" (그대로 텍스트로 보임)
case_A = """Hello. Everyone. {.en}"""
# B) 속성 줄을 '앞'에 두면 → "다음 블록"에 적용
case_B = """{:.en lang=en}
Hello. Everyone."""
# C) 속성 줄을 '뒤'에 두면 → "이전 블록"에 적용
case_C = """Hello. Everyone.
{:.en lang=en}"""
# D) 문단 분리(빈 줄) 후 영어 문단에만 적용
case_D = """안녕하세요.
Hello. Everyone.
{:.en}"""
# E) 같은 문단 유지: 줄바꿈은 <br>, 영어만 인라인으로 감싸기
case_E = """안녕하세요.<br>
<span class="en" lang="en">Hello. Everyone.</span>"""
# F) 커스텀 태그 <en>
case_F = """안녕하세요.
<en lang="en">Hello. Everyone.</en>"""
# 렌더
render_md(case_A, "A) same-line `{.en}` (문단에는 보통 적용 안 됨)")
render_md(case_B, "B) attr 줄을 앞에 → 다음 블록에 적용")
render_md(case_C, "C) attr 줄을 뒤에 → 이전 블록에 적용")
render_md(case_D, "D) 문단 분리 + 영어 문단만 스타일")
render_md(case_E, "E) 같은 문단 유지: <br> + <span class='en'>")
render_md(case_F, "F) 커스텀 태그 <en>")
무엇이 보이면 정상인가 (체크 포인트)
-
A :
Hello. Everyone. {.en}가 그대로 글자 로 보임 → 일반 문단에서는 같은 줄 끝{.en}이 안 먹는다 는 증거. -
B : 영어 줄이 작고 연하게 보임 →
{:.en}(속성만 있는 줄)이 아래 블록(다음 문단) 에 적용. -
C : 영어 줄이 작고 연하게 보임 →
{:.en}이 위 블록(이전 문단) 에 적용. -
D : 한국어/영어가 서로 다른 문단 이며, 영어만 작고 연함.
-
E : 한 문단으로 보이되,
<br>다음 줄의 영어만 작고 연함. -
F :
<en>…</en>도 작고 연함. (커스텀 태그를 CSS에서 선택자로 잡음)
결론: 문단 텍스트 같은 줄 끝의
{.en}은 기대대로 동작하지 않습니다.
문단에 클래스를 주려면 속성만 있는 줄({:.en}) 을 앞/뒤 에 두거나,
같은 문단을 유지하려면<br>+<span class="en">…</span>방식이 가장 확실합니다.
원하시면 이 케이스 중 딱 맞는 패턴을 골라 MkDocs에 그대로 이식하는 예시도 바로 적어드릴게요.
아무리 해도 그냥 앞 뒤 줄 띄워야함
맞아요. 결론 = 블록 속성 줄({: .en})은 “독립 블록”이어야 해서 앞뒤로 빈 줄을 둬서 ‘완전히 분리’하면 가장 안전합니다. 특히 Python-Markdown/MkDocs 조합에서는 이 규칙이 까다로워서, 실전에서는 “앞·뒤 줄 띄우기”가 사실상 정석이에요.
아래 3가지 항상 잘 먹는 패턴 만 써주세요.
1) “이전 문단(영어)”에 적용 — 가장 추천
안녕하세요.
Hello. Everyone.
{: .en lang=en}
- 포인트:
{: .en}위·아래 모두 빈 줄 → 속성 줄이 완전히 독립된 단락이 되어 바로 위 문단(영어) 에만 붙습니다.
2) “다음 문단(영어)”에 적용
안녕하세요.
{: .en lang=en}
Hello. Everyone.
- 포인트: 마찬가지로 속성 줄 앞·뒤 빈 줄 → 바로 아래 문단(영어) 에만 붙습니다.
3) 같은 문단을 유지해야 할 때(줄바꿈만) — 인라인 방식
안녕하세요.<br>
<span class="en" lang="en">Hello. Everyone.</span>
- 블록 속성 규칙을 건드리지 않아서 환경 차이에 덜 민감합니다.
CSS (Material 기준, 공통)
.md-typeset .en, .md-typeset en {
font-size: 0.85em; /* 작게 */
opacity: 0.65; /* 연하게 */
}
.md-typeset en { /* <en>을 블록처럼 쓰고 싶을 때 */
display: block;
margin-top: 0.25em;
}
.md-typeset p + p.en { /* 한글 문단 바로 뒤 영어 문단 간격 다듬기 */
margin-top: 0.25em;
}
왜 이렇게 해야 하나? (짧게)
-
속성 줄은 그 자체가 하나의 “단락(block)”이어야 인식됩니다.
-
빈 줄이 없으면 파서가 “같은 문단의 일반 텍스트”로 오해해서
{:.en}이 그대로 글자 로 보이거나, 한 문단 전체(한글+영어)에 적용되는 일이 생깁니다. -
그래서 속성 줄을 위/아래 빈 줄로 고립 시키는 습관을 들이면 어디서든 안정적으로 동작합니다.
원하시면 지금 작성하신 실제 몇 줄을 보내 주시면, 위 패턴으로 최소 수정 버전을 바로 잡아드릴게요.
pymdownx의 caret 처럼 간단히 가만의 을 추가할 수 없을까
좋아, caret(예:^^text^^)처럼 간단한 기호로
<span class="en">…</span>을 자동으로 넣고 싶다면 작은 커스텀 Markdown 확장 을 하나 추가하면 딱 해결돼요.
아래대로 하면 %%영문%%처럼 쓰기만 해도 <span class="en" lang="en">영문</span>으로 렌더됩니다.
1) 파이썬 확장 파일 만들기 (5~10줄짜리 핵심)
프로젝트 루트(보통 mkdocs.yml 옆)에 en_span.py 파일을 만들고 아래를 복붙하세요.
# en_span.py
import re
from markdown.extensions import Extension
from markdown.inlinepatterns import InlineProcessor
from xml.etree import ElementTree as etree
class EnSpanInline(InlineProcessor):
def __init__(self, pattern, md, klass="en", lang="en"):
super().__init__(pattern, md)
self.klass = klass
self.lang = lang
def handleMatch(self, m, data):
text = m.group(1).replace(r'\%%', '%%') # \%% → %
el = etree.Element("span")
el.set("class", self.klass)
if self.lang:
el.set("lang", self.lang)
el.text = text
return el, m.start(0), m.end(0)
class EnSpanExtension(Extension):
def __init__(self, **kwargs):
self.config = {
"delim": ["%%", "Inline delimiter, e.g. %%, ^^, !!"],
"class": ["en", "CSS class name"],
"lang": ["en", "lang attribute ('' to disable)"],
}
super().__init__(**kwargs)
def extendMarkdown(self, md):
delim = self.getConfig("delim")
pattern = rf"(?<!\\){re.escape(delim)}(.+?)(?<!\\){re.escape(delim)}"
proc = EnSpanInline(pattern, md,
klass=self.getConfig("class"),
lang=self.getConfig("lang"))
# 175: em/strong(EMPHASIS)보다 살짝 앞서도록 등록
md.inlinePatterns.register(proc, "en-span", 175)
def makeExtension(**kwargs):
return EnSpanExtension(**kwargs)
-
기본 구분자는
%%…%%입니다. -
텍스트 안에서
%%를 문자 그대로 쓰고 싶으면\%%로 이스케이프하세요. -
이미
pymdownx.caret(^^)을 쓰고 있다면, 충돌 없이%%만 쓰면 됩니다. (원하면delim: "^^"로 바꿀 수 있어요)
2) mkdocs.yml에 확장 등록
theme:
name: material
markdown_extensions:
- attr_list
- 'en_span:EnSpanExtension' # 또는 그냥 - en_span (makeExtension가 있으므로)
# 옵션을 바꾸고 싶다면:
# - en_span:
# delim: "%%" # 바꾸려면 "^^" 등
# class: "en"
# lang: "en"
extra_css:
- stylesheets/translation.css
주의: MkDocs가 이 모듈을 import할 수 있어야 해요.
가장 간단히는 MkDocs 실행할 때 프로젝트 루트를 파이썬 경로에 추가하세요.
macOS/Linux:
PYTHONPATH=. mkdocs serveWindows(PowerShell):
$env:PYTHONPATH="."; mkdocs serve
(혹은 en_span.py를 패키지로 설치하거나, venv의 site-packages에 넣어도 됩니다.)
3) CSS (Material 기준)
docs/stylesheets/translation.css:
.md-typeset .en {
font-size: 0.85em; /* 조금 작게 */
opacity: 0.65; /* 연하게 */
}
원하면 줄 간격 조정, 색상 등도 여기서 커스텀하면 됩니다.
4) 사용법
그냥 본문에 이렇게 쓰면 끝:
안녕하세요. %%Hello. Everyone.%%
렌더링 결과(핵심만):
<p>안녕하세요. <span class="en" lang="en">Hello. Everyone.</span></p>
-
블록으로 쓰고 싶으면 기존대로
{:.en}을 쓰거나,<en>…</en>를 쓰세요(이미 해두신 CSS로 스타일 적용 가능). -
같은 문단에서 줄을 바꾸고 싶다면 여전히
<br>을 쓰는 게 가장 확실합니다:안녕하세요.<br>%%Hello. Everyone.%%
5) 대안(플러그인 없이 가능한 꼼수)
플러그인을 전혀 쓰기 싫다면, 아래 꼼수도 가능은 합니다.
*Hello. Everyone.*{.en}
CSS로 기울임을 제거:
.md-typeset em.en { font-style: normal; }
이렇게 하면 <em class="en">…</em>이 되는데, 시맨틱이 살짝 어긋나므로 권장도는 낮고 ,
가능하면 위의 커스텀 확장(%%…%%) 방식을 추천합니다.
필요하면 구분자 를 ^^나 !! 등으로 바꾼 버전도 바로 만들어 드릴게요. (옵션 한 줄만 바꾸면 됩니다)
No module names 'en_span' 파일을 여기 넣는거 맞아?
맞아요, 그 에러는 MkDocs가en_span 모듈을 import 못 해서 나는 거예요.
핵심은 파일 위치 + 실행 위치 + 파이썬 경로(PYTHONPATH) 입니다. 아래대로 차근차근 해보세요.
0) 올바른 폴더 구조 (가장 단순한 형태)
pgsql
your-project/
├─ mkdocs.yml
├─ en_span.py ← 여기!
└─ docs/
├─ index.md
└─ stylesheets/
└─ translation.css
중요:
en_span.py는docs/안이 아니라mkdocs.yml과 같은 디렉터리(프로젝트 루트) 에 두세요.
1) mkdocs.yml 설정
가장 기본:
markdown_extensions:
- en_span
옵션을 쓰고 싶다면:
markdown_extensions:
- en_span:
delim: "%%"
class: "en"
lang: "en"
또는 클래스 경로로도 가능:
markdown_extensions:
- 'en_span:EnSpanExtension'
2) 올바른 실행 위치에서 서버 시작
반드시 프로젝트 루트(=mkdocs.yml이 있는 위치) 로 이동해서 실행하세요.
- macOS / Linux
cd your-project PYTHONPATH=. mkdocs serve - Windows PowerShell
cd your-project $env:PYTHONPATH="."; mkdocs serve - Windows CMD
bat cd your-project set PYTHONPATH=. mkdocs serve
왜? MkDocs가 확장 모듈을 현재 파이썬 경로 에서 찾는데, 환경/실행 방법에 따라 현재 디렉터리가 경로에 안 잡힐 수 있습니다.
PYTHONPATH=.로 프로젝트 루트를 확실히 넣어주면 import 에러가 사라집니다.
3) 빠른 자가 점검 (문제 계속되면 여기서 잡힙니다)
-
파일명 이 정확히
en_span.py인가요? (en-span.py×) -
en_span.py안에makeExtension(**kwargs)함수가 존재하나요? (필수) -
mkdocs serve를 프로젝트 루트 에서 실행했나요? (다른 폴더에서 실행 ×) -
mkdocs.yml에markdown_extensions: - en_span이 들어갔나요? (오탈자 ×) - 그래도 안 되면, 완전한 모듈 경로 로 시도:
```yaml
markdown_extensions:
- 'en_span:EnSpanExtension' ```
- 여전히 안 되면, 패키지 폴더로 감싸기 :
your-project/ ├─ mkdocs.yml ├─ local_ext/ │ ├─ __init__.py │ └─ en_span.py └─ docs/...
그리고 mkdocs.yml:
yaml
markdown_extensions:
- local_ext.en_span
(이때도 PYTHONPATH=. 권장)
4) 동작 확인용 최소 예제
docs/index.md
안녕하세요. %%Hello. Everyone.%%
en_span.py (핵심 부분만 재확인)
import re
from markdown.extensions import Extension
from markdown.inlinepatterns import InlineProcessor
from xml.etree import ElementTree as etree
class EnSpanInline(InlineProcessor):
def __init__(self, pattern, md, klass="en", lang="en"):
super().__init__(pattern, md)
self.klass = klass
self.lang = lang
def handleMatch(self, m, data):
text = m.group(1).replace(r'\%%', '%%')
el = etree.Element("span")
el.set("class", self.klass)
if self.lang:
el.set("lang", self.lang)
el.text = text
return el, m.start(0), m.end(0)
class EnSpanExtension(Extension):
def __init__(self, **kwargs):
self.config = {
"delim": ["%%", "Inline delimiter"],
"class": ["en", "CSS class"],
"lang": ["en", "lang attribute"],
}
super().__init__(**kwargs)
def extendMarkdown(self, md):
delim = self.getConfig("delim")
pattern = rf"(?<!\\){re.escape(delim)}(.+?)(?<!\\){re.escape(delim)}"
md.inlinePatterns.register(
EnSpanInline(pattern, md,
klass=self.getConfig("class"),
lang=self.getConfig("lang")),
"en-span", 175
)
def makeExtension(**kwargs):
return EnSpanExtension(**kwargs)
5) 그래도 같은 에러면?
-
콘솔에서 한 번 직접 import 해보세요:
- macOS/Linux:
cd your-project PYTHONPATH=. python -c "import en_span; print(en_span)" - Windows PowerShell:
cd your-project $env:PYTHONPATH="."; python -c "import en_span; print(en_span)"
- macOS/Linux:
이게 실패하면 경로 문제 가 확실합니다. 위 2)번(실행 위치)과 3)번(패키지 폴더) 방법으로 정리하면 해결됩니다.
원하시면 지금 프로젝트 트리(대충 tree 결과나 스크린샷)와 mkdocs.yml의 해당 부분을 보내 주세요. 그대로 맞춰서 한 줄도 헷갈리지 않게 수정본 만들어 드릴게요.
그냥 패키지 설치를 해버려야겠어. markdown_en_span 이라는 패키지로 만들어주고 src 폴더와 pyproject.toml을 여기에 포함시켜서 설치할 수 있게 해줘
좋아—로컬에서 바로 설치 가능한 패키지 로 만들어 드릴게요.
패키지명은 요청하신 대로 markdown_en_span, src 레이아웃 + pyproject.toml 포함입니다.
1) 디렉터리 구조 만들기
your-project/
├─ pyproject.toml
└─ src/
└─ markdown_en_span/
└─ __init__.py
2) pyproject.toml
[build-system]
requires = ["hatchling>=1.18"]
build-backend = "hatchling.build"
[project]
name = "markdown_en_span"
version = "0.1.0"
description = "Python-Markdown inline extension to wrap %%text%% as <span class='en' lang='en'>text</span>."
readme = "README.md"
requires-python = ">=3.8"
authors = [{name = "Your Name"}]
dependencies = ["Markdown>=3.3"]
[project.urls]
Homepage = "https://example.com/markdown_en_span"
# Python-Markdown이 이 확장을 'en_span' 이름으로 인식하게 하는 엔트리포인트
[project.entry-points."markdown.extensions"]
en_span = "markdown_en_span:makeExtension"
포인트
엔트리포인트 덕분에
mkdocs.yml에서markdown_extensions: - en_span처럼 짧은 이름 으로 쓸 수 있어요.배포명(
name)과 모듈명(폴더)은 둘 다markdown_en_span로 통일.
3) src/markdown_en_span/__init__.py
import re
from markdown.extensions import Extension
from markdown.inlinepatterns import InlineProcessor
from xml.etree import ElementTree as etree
__all__ = ["EnSpanExtension", "makeExtension"]
__version__ = "0.1.0"
class EnSpanInline(InlineProcessor):
"""
Wraps %%text%% (기본) → <span class="en" lang="en">text</span>
- 구분자는 옵션으로 변경 가능 (예: ^^ 또는 !!)
- \%% 처럼 백슬래시로 이스케이프하면 리터럴 %% 출력
"""
def __init__(self, delim: str, md, css_class: str = "en", lang: str = "en"):
# DOTALL 활성화: 필요 시 여러 줄도 매칭
pattern = rf"(?s)(?<!\\){re.escape(delim)}(.+?)(?<!\\){re.escape(delim)}"
super().__init__(pattern, md)
self.delim = delim
self.css_class = css_class
self.lang = lang
def handleMatch(self, m, data):
# 본문 텍스트
text = m.group(1)
# \%% → %% (사용자가 리터럴 구분자를 넣고 싶을 때)
text = text.replace("\\" + self.delim, self.delim)
el = etree.Element("span")
el.set("class", self.css_class)
if self.lang:
el.set("lang", self.lang)
el.text = text
return el, m.start(0), m.end(0)
class EnSpanExtension(Extension):
"""
확장 옵션:
- delim: 기본 "%%"
- class: 기본 "en"
- lang: 기본 "en" (빈 문자열이면 lang 속성 생략)
"""
def __init__(self, **kwargs):
self.config = {
"delim": ["%%", "Inline delimiter, e.g. %%, ^^, !!"],
"class": ["en", "CSS class name to set on <span>"],
"lang": ["en", "lang attribute value ('' to disable)"],
}
super().__init__(**kwargs)
def extendMarkdown(self, md):
delim = self.getConfig("delim")
css_class = self.getConfig("class")
lang = self.getConfig("lang")
proc = EnSpanInline(delim=delim, md=md, css_class=css_class, lang=lang)
# emphasis(EMPHASIS=170)보다 약간 앞서도록 등록
md.inlinePatterns.register(proc, "en-span", 175)
def makeExtension(**kwargs):
return EnSpanExtension(**kwargs)
4) 설치 (로컬)
# 프로젝트 루트(= pyproject.toml 있는 곳)에서
pip install -e .
# 또는
pip install .
-e는 개발 중 수정사항이 바로 반영되는 editable 설치.
5) MkDocs 사용 예시
mkdocs.yml
theme:
name: material
markdown_extensions:
- en_span # ← 방금 만든 확장 (엔트리포인트로 인식)
# - attr_list # (선택) 다른 용도 필요 시
# - en_span:
# delim: "^^" # 구분자 바꾸고 싶을 때
# class: "en"
# lang: "en"
extra_css:
- stylesheets/translation.css
docs/stylesheets/translation.css
.md-typeset .en {
font-size: 0.85em;
opacity: 0.65;
}
docs/index.md
안녕하세요. %%Hello. Everyone.%%
→ 렌더링:
<p>안녕하세요. <span class="en" lang="en">Hello. Everyone.</span></p>
6) 팁 & 트러블슈팅
- 구분자 바꾸기 :
mkdocs.yml에 ```yaml markdown_extensions:- en_span: delim: "^^" class: "en" lang: "en" ```
→ 안녕하세요. ^^Hello. Everyone.^^
-
리터럴로
%%출력:\%%처럼 이스케이프
예)가격은 \%%20 할인→ 실제 문서에%%20출력 - 확장 로딩 확인 (MkDocs 외에 순수 파이썬에서)
import markdown html = markdown.markdown("안녕 %%Hello%%", extensions=["en_span"]) print(html) -
여전히 인식 안 되면
-
설치 경로 확인:
pip show markdown_en_span -
가상환경 재시작 / MkDocs 재시작
-
Markdown 버전 확인:
python -c "import markdown, sys; print(markdown.__version__)"(3.3 이상 권장)
-
필요하면 README.md/LICENSE 골격도 추가해 드릴게요. 원하시는 구분자(예: ||), 기본 클래스명 변경 등 커스터마이즈 버전도 바로 만들어 줄 수 있어요.
mkdocs 루트 폴더 밑에 너가 만들어준 패키지를 위치 시킬거야. requirements.txt에 상대경로로 인스톨 추가할 수 있어?
가능해! requirements.txt 에서 상대경로 로 로컬 패키지를 설치할 수 있어요.
요청하신 구조(패키지를 MkDocs 루트 바로 아래)에 맞춰 한 번에 되는 절차를 step-by-step으로 정리할게요.
0) 폴더 구조(예시)
your-project/
├─ mkdocs.yml
├─ requirements.txt
├─ markdown_en_span/ # ← 여기 폴더가 “패키지 루트”
│ ├─ pyproject.toml
│ └─ src/
│ └─ markdown_en_span/
│ └─ __init__.py
└─ docs/
└─ stylesheets/translation.css
중요:
pyproject.toml이 들어있는 디렉터리(여기서는./markdown_en_span)가 pip가 인식하는 “패키지 루트”예요.
1) requirements.txt 작성
(A) 개발 중 수정사항을 바로 반영하고 싶을 때 (editable)
# MkDocs(원하면 버전 고정)
mkdocs>=1.6
mkdocs-material>=9.5
# 로컬 확장 패키지(상대경로, editable 모드)
-e ./markdown_en_span
(B) 고정 설치(수정 즉시 반영 필요 없을 때)
mkdocs>=1.6
mkdocs-material>=9.5
./markdown_en_span
두 경우 모두 상대경로는 requirements.txt 파일이 있는 위치 기준 으로 해석됩니다.
2) 설치
프로젝트 루트에서:
python -m pip install -U pip # 권장: 최신 pip (PEP 660 editable 지원)
pip install -r requirements.txt
editable(-e) 설치가 제대로 되려면 pip가 PEP 660 을 지원해야 합니다(대부분 최신 pip OK).
3) mkdocs.yml 설정
엔트리포인트를 등록해둔 상태라면(제가 준 pyproject.toml 기준) 아래처럼 간단히 사용 가능:
theme:
name: material
markdown_extensions:
- en_span # ← 방금 설치한 확장
# - en_span:
# delim: "^^" # 구분자 바꾸고 싶으면 옵션으로
# class: "en"
# lang: "en"
extra_css:
- stylesheets/translation.css
4) 사용 예시
docs/index.md
안녕하세요. %%Hello. Everyone.%%
docs/stylesheets/translation.css
.md-typeset .en {
font-size: 0.85em;
opacity: 0.65;
}
5) 자주 생기는 이슈 & 체크리스트
-
설치가 안 된다면
-
pip --version으로 최신인지 확인(가능하면 21.3+). -
프로젝트 루트에서
pip install -r requirements.txt실행했는지 확인. -
markdown_en_span/pyproject.toml파일이 실제로 존재하는지 확인.
-
-
MkDocs가 확장을 못 찾는다면
-
설치 성공 여부:
pip show markdown_en_span -
mkdocs.yml의markdown_extensions:에en_span이 들어있는지 확인. -
서버 재시작:
mkdocs serve다시 실행.
-
원하시면 제가 제공했던 pyproject.toml/__init__.py 샘플을 그대로 붙여드릴게요(파일 내용 다시 필요하면 말해줘!).