Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
}
},
"css.lint.unknownAtRules": "ignore",
}
21 changes: 15 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
<title>Docker Korea</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script type="text/javascript">
<meta name="description" content="Docker 공식 문서의 한국어 번역 프로젝트. 컨테이너(Container)와 이미지(Image) 개념, 설치 방법, 실습 튜토리얼, CLI, Compose, Swarm 등 DevOps와 개발자를 위한 최신 Docker 가이드와 베스트 프랙티스, 실전 예제, 문제 해결, 오픈소스 커뮤니티 정보까지 모두 제공합니다. 초보자와 전문가 모두를 위한 학습 자료와 FAQ 수록." />
<script type="text/javascript" async>
!(function (cfg){function e(){cfg.onInit&&cfg.onInit(n)}var x,w,D,t,E,n,C=window,O=document,b=C.location,q="script",I="ingestionendpoint",L="disableExceptionTracking",j="ai.device.";"instrumentationKey"[x="toLowerCase"](),w="crossOrigin",D="POST",t="appInsightsSDK",E=cfg.name||"appInsights",(cfg.name||C[t])&&(C[t]=E),n=C[E]||function(g){var f=!1,m=!1,h={initialize:!0,queue:[],sv:"8",version:2,config:g};function v(e,t){var n={},i="Browser";function a(e){e=""+e;return 1===e.length?"0"+e:e}return n[j+"id"]=i[x](),n[j+"type"]=i,n["ai.operation.name"]=b&&b.pathname||"_unknown_",n["ai.internal.sdkVersion"]="javascript:snippet_"+(h.sv||h.version),{time:(i=new Date).getUTCFullYear()+"-"+a(1+i.getUTCMonth())+"-"+a(i.getUTCDate())+"T"+a(i.getUTCHours())+":"+a(i.getUTCMinutes())+":"+a(i.getUTCSeconds())+"."+(i.getUTCMilliseconds()/1e3).toFixed(3).slice(2,5)+"Z",iKey:e,name:"Microsoft.ApplicationInsights."+e.replace(/-/g,"")+"."+t,sampleRate:100,tags:n,data:{baseData:{ver:2}},ver:undefined,seq:"1",aiDataContract:undefined}}var n,i,t,a,y=-1,T=0,S=["js.monitor.azure.com","js.cdn.applicationinsights.io","js.cdn.monitor.azure.com","js0.cdn.applicationinsights.io","js0.cdn.monitor.azure.com","js2.cdn.applicationinsights.io","js2.cdn.monitor.azure.com","az416426.vo.msecnd.net"],o=g.url||cfg.src,r=function(){return s(o,null)};function s(d,t){if((n=navigator)&&(~(n=(n.userAgent||"").toLowerCase()).indexOf("msie")||~n.indexOf("trident/"))&&~d.indexOf("ai.3")&&(d=d.replace(/(\/)(ai\.3\.)([^\d]*)$/,function(e,t,n){return t+"ai.2"+n})),!1!==cfg.cr)for(var e=0;e<S.length;e++)if(0<d.indexOf(S[e])){y=e;break}var n,i=function(e){var a,t,n,i,o,r,s,c,u,l;h.queue=[],m||(0<=y&&T+1<S.length?(a=(y+T+1)%S.length,p(d.replace(/^(.*\/\/)([\w\.]*)(\/.*)$/,function(e,t,n,i){return t+S[a]+i})),T+=1):(f=m=!0,s=d,!0!==cfg.dle&&(c=(t=function(){var e,t={},n=g.connectionString;if(n)for(var i=n.split(";"),a=0;a<i.length;a++){var o=i[a].split("=");2===o.length&&(t[o[0][x]()]=o[1])}return t[I]||(e=(n=t.endpointsuffix)?t.location:null,t[I]="https://"+(e?e+".":"")+"dc."+(n||"services.visualstudio.com")),t}()).instrumentationkey||g.instrumentationKey||"",t=(t=(t=t[I])&&"/"===t.slice(-1)?t.slice(0,-1):t)?t+"/v2/track":g.endpointUrl,t=g.userOverrideEndpointUrl||t,(n=[]).push((i="SDK LOAD Failure: Failed to load Application Insights SDK script (See stack for details)",o=s,u=t,(l=(r=v(c,"Exception")).data).baseType="ExceptionData",l.baseData.exceptions=[{typeName:"SDKLoadFailed",message:i.replace(/\./g,"-"),hasFullStack:!1,stack:i+"\nSnippet failed to load ["+o+"] -- Telemetry is disabled\nHelp Link: https://go.microsoft.com/fwlink/?linkid=2128109\nHost: "+(b&&b.pathname||"_unknown_")+"\nEndpoint: "+u,parsedStack:[]}],r)),n.push((l=s,i=t,(u=(o=v(c,"Message")).data).baseType="MessageData",(r=u.baseData).message='AI (Internal): 99 message:"'+("SDK LOAD Failure: Failed to load Application Insights SDK script (See stack for details) ("+l+")").replace(/\"/g,"")+'"',r.properties={endpoint:i},o)),s=n,c=t,JSON&&((u=C.fetch)&&!cfg.useXhr?u(c,{method:D,body:JSON.stringify(s),mode:"cors"}):XMLHttpRequest&&((l=new XMLHttpRequest).open(D,c),l.setRequestHeader("Content-type","application/json"),l.send(JSON.stringify(s)))))))},a=function(e,t){m||setTimeout(function(){!t&&h.core||i()},500),f=!1},p=function(e){var n=O.createElement(q),e=(n.src=e,t&&(n.integrity=t),n.setAttribute("data-ai-name",E),cfg[w]);return!e&&""!==e||"undefined"==n[w]||(n[w]=e),n.onload=a,n.onerror=i,n.onreadystatechange=function(e,t){"loaded"!==n.readyState&&"complete"!==n.readyState||a(0,t)},cfg.ld&&cfg.ld<0?O.getElementsByTagName("head")[0].appendChild(n):setTimeout(function(){O.getElementsByTagName(q)[0].parentNode.appendChild(n)},cfg.ld||0),n};p(d)}cfg.sri&&(n=o.match(/^((http[s]?:\/\/.*\/)\w+(\.\d+){1,5})\.(([\w]+\.){0,2}js)$/))&&6===n.length?(d="".concat(n[1],".integrity.json"),i="@".concat(n[4]),l=window.fetch,t=function(e){if(!e.ext||!e.ext[i]||!e.ext[i].file)throw Error("Error Loading JSON response");var t=e.ext[i].integrity||null;s(o=n[2]+e.ext[i].file,t)},l&&!cfg.useXhr?l(d,{method:"GET",mode:"cors"}).then(function(e){return e.json()["catch"](function(){return{}})}).then(t)["catch"](r):XMLHttpRequest&&((a=new XMLHttpRequest).open("GET",d),a.onreadystatechange=function(){if(a.readyState===XMLHttpRequest.DONE)if(200===a.status)try{t(JSON.parse(a.responseText))}catch(e){r()}else r()},a.send())):o&&r();try{h.cookie=O.cookie}catch(k){}function e(e){for(;e.length;)!function(t){h[t]=function(){var e=arguments;f||h.queue.push(function(){h[t].apply(h,e)})}}(e.pop())}var c,u,l="track",d="TrackPage",p="TrackEvent",l=(e([l+"Event",l+"PageView",l+"Exception",l+"Trace",l+"DependencyData",l+"Metric",l+"PageViewPerformance","start"+d,"stop"+d,"start"+p,"stop"+p,"addTelemetryInitializer","setAuthenticatedUserContext","clearAuthenticatedUserContext","flush"]),h.SeverityLevel={Verbose:0,Information:1,Warning:2,Error:3,Critical:4},(g.extensionConfig||{}).ApplicationInsightsAnalytics||{});return!0!==g[L]&&!0!==l[L]&&(e(["_"+(c="onerror")]),u=C[c],C[c]=function(e,t,n,i,a){var o=u&&u(e,t,n,i,a);return!0!==o&&h["_"+c]({message:e,url:t,lineNumber:n,columnNumber:i,error:a,evt:C.event}),o},g.autoExceptionInstrumented=!0),h}(cfg.cfg),(C[E]=n).queue&&0===n.queue.length?(n.queue.push(e),n.trackPageView({})):e();})({
src: "https://js.monitor.azure.com/scripts/b/ai.3.gbl.min.js",
// name: "appInsights", // Global SDK Instance name defaults to "appInsights" when not supplied
Expand All @@ -18,11 +19,7 @@
connectionString: "InstrumentationKey=7bea0293-01dc-409c-9471-3a65567b11ed;IngestionEndpoint=https://koreacentral-0.in.applicationinsights.azure.com/;LiveEndpoint=https://koreacentral.livediagnostics.monitor.azure.com/;ApplicationId=ddbe985c-4d7a-41e4-80a8-a3961068933b"
}});
</script>
<script
src="https://kit.fontawesome.com/6984a5c98e.js"
crossorigin="anonymous"
></script>
<script type="module" src="./src/scripts/main.ts"></script>
<script type="module" src="./src/scripts/main.ts" defer></script>
<!-- Application Insights 스크립트는 main.ts에서 운영 환경에서만 동적으로 삽입됩니다. -->
<link
rel="icon"
Expand Down Expand Up @@ -81,6 +78,8 @@
</div>
<button
class="inline-flex h-7 w-7 items-center justify-center rounded hover:cursor-pointer hover:bg-gray-400 hover:dark:bg-gray-400"
aria-label="Toggle section"
Copy link

Copilot AI Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add aria-expanded and aria-controls attributes to these toggles to communicate the expanded/collapsed state and associated content region to assistive technologies.

Suggested change
aria-label="Toggle section"
aria-label="Toggle section"
aria-expanded="false"
aria-controls="section__wrapper"

Copilot uses AI. Check for mistakes.
aria-expanded="false"
>
<span>
<svg
Expand Down Expand Up @@ -165,6 +164,8 @@
</div>
<button
class="inline-flex h-7 w-7 items-center justify-center rounded hover:cursor-pointer hover:bg-gray-400 hover:dark:bg-gray-400"
aria-label="Toggle section"
aria-expanded="false"
>
<span>
<svg
Expand Down Expand Up @@ -207,6 +208,8 @@
</div>
<button
class="inline-flex h-7 w-7 items-center justify-center rounded hover:cursor-pointer hover:bg-gray-400 hover:dark:bg-gray-400"
aria-label="Toggle section"
aria-expanded="false"
>
<span>
<svg
Expand Down Expand Up @@ -292,6 +295,8 @@
</div>
<button
class="inline-flex h-7 w-7 items-center justify-center rounded hover:cursor-pointer hover:bg-gray-400 hover:dark:bg-gray-400"
aria-label="Toggle section"
aria-expanded="false"
>
<span>
<svg
Expand Down Expand Up @@ -387,6 +392,8 @@
</div>
<button
class="inline-flex h-7 w-7 items-center justify-center rounded hover:cursor-pointer hover:bg-gray-400 hover:dark:bg-gray-400"
aria-label="Toggle section"
aria-expanded="false"
>
<span>
<svg
Expand Down Expand Up @@ -483,6 +490,8 @@
</div>
<button
class="inline-flex h-7 w-7 items-center justify-center rounded hover:cursor-pointer hover:bg-gray-400 hover:dark:bg-gray-400"
aria-label="Toggle section"
aria-expanded="false"
>
<span>
<svg
Expand Down
192 changes: 153 additions & 39 deletions src/data/breadcrumb.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,156 @@
{
"breadcrumb": {
"get-started": "시작하기",
"get-docker": "Docker 설치",
"docker-overview": "Docker 개요",
"introduction": "소개",
"build-and-push-first-image": "첫 번째 이미지 빌드 및 푸시",
"develop-with-containers": "컨테이너로 개발",
"get-docker-desktop": "Docker Desktop 설치",
"whats-next": "다음 단계",
"docker-concepts": "Docker 개념",
"the-basics": "기본 사항",
"what-is-a-container": "컨테이너란?",
"what-is-a-registry": "레지스트리란?",
"what-is-an-image": "이미지란?",
"what-is-docker-compose": "Docker Compose란?",
"building-images": "이미지 빌드",
"understanding-image-layers": "이미지 레이어 이해",
"writing-a-dockerfile": "Dockerfile 작성",
"build-tag-and-publish-an-image": "이미지 빌드, 태그, 게시",
"using-the-build-cache": "빌드 캐시 사용",
"multi-stage-builds": "멀티 스테이지 빌드",
"running-containers": "컨테이너 실행",
"multi-container-applications": "멀티 컨테이너 애플리케이션",
"overriding-container-defaults": "컨테이너 기본값 재정의",
"persisting-container-data": "컨테이너 데이터 유지",
"publishing-ports": "포트 게시",
"sharing-local-files": "로컬 파일 공유",
"docker-workshop": "Docker 워크샵",
"resources": "리소스",
"workshop": "워크샵",
"02_our_app": "애플리케이션 컨테이너화",
"03_updating_app": "애플리케이션 업데이트",
"04_sharing_app": "애플리케이션 공유",
"05_persisting_data": "데이터베이스 유지",
"06_bind_mounts": "바인드 마운트 사용",
"07_multi_container": "멀티 컨테이너 애플리케이션",
"08_using_compose": "Docker Compose 사용",
"09_image_best": "이미지 빌드 모범 사례",
"10_what_next": "Docker 워크숍 이후 단계"
"segments": {
"get-started": {
"name": "시작하기",
"linkable": true
},
"get-docker": {
"name": "Docker 설치",
"linkable": true
},
"docker-overview": {
"name": "Docker 개요",
"linkable": true
},
"introduction": {
"name": "소개",
"linkable": true
},
"build-and-push-first-image": {
"name": "첫 번째 이미지 빌드 및 푸시",
"linkable": true
},
"develop-with-containers": {
"name": "컨테이너로 개발",
"linkable": true
},
"get-docker-desktop": {
"name": "Docker Desktop 설치",
"linkable": true
},
"whats-next": {
"name": "다음 단계",
"linkable": true
},
"docker-concepts": {
"name": "Docker 개념",
"linkable": false
},
"the-basics": {
"name": "기본 사항",
"linkable": false
},
"what-is-a-container": {
"name": "컨테이너란?",
"linkable": true
},
"what-is-a-registry": {
"name": "레지스트리란?",
"linkable": true
},
"what-is-an-image": {
"name": "이미지란?",
"linkable": true
},
"what-is-docker-compose": {
"name": "Docker Compose란?",
"linkable": true
},
"building-images": {
"name": "이미지 빌드",
"linkable": false
},
"understanding-image-layers": {
"name": "이미지 레이어 이해",
"linkable": true
},
"writing-a-dockerfile": {
"name": "Dockerfile 작성",
"linkable": true
},
"build-tag-and-publish-an-image": {
"name": "이미지 빌드, 태그, 게시",
"linkable": true
},
"using-the-build-cache": {
"name": "빌드 캐시 사용",
"linkable": true
},
"multi-stage-builds": {
"name": "멀티 스테이지 빌드",
"linkable": true
},
"running-containers": {
"name": "컨테이너 실행",
"linkable": false
},
"multi-container-applications": {
"name": "멀티 컨테이너 애플리케이션",
"linkable": true
},
"overriding-container-defaults": {
"name": "컨테이너 기본값 재정의",
"linkable": true
},
"persisting-container-data": {
"name": "컨테이너 데이터 유지",
"linkable": true
},
"publishing-ports": {
"name": "포트 게시",
"linkable": true
},
"sharing-local-files": {
"name": "로컬 파일 공유",
"linkable": true
},
"docker-workshop": {
"name": "Docker 워크샵",
"linkable": true
},
"resources": {
"name": "리소스",
"linkable": true
},
"workshop": {
"name": "워크샵",
"linkable": false
},
"02_our_app": {
"name": "애플리케이션 컨테이너화",
"linkable": true
},
"03_updating_app": {
"name": "애플리케이션 업데이트",
"linkable": true
},
"04_sharing_app": {
"name": "애플리케이션 공유",
"linkable": true
},
"05_persisting_data": {
"name": "데이터베이스 유지",
"linkable": true
},
"06_bind_mounts": {
"name": "바인드 마운트 사용",
"linkable": true
},
"07_multi_container": {
"name": "멀티 컨테이너 애플리케이션",
"linkable": true
},
"08_using_compose": {
"name": "Docker Compose 사용",
"linkable": true
},
"09_image_best": {
"name": "이미지 빌드 모범 사례",
"linkable": true
},
"10_what_next": {
"name": "Docker 워크숍 이후 단계",
"linkable": true
}
}
}
41 changes: 29 additions & 12 deletions src/scripts/breadcrumb.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
import translations from '../data/breadcrumb.json';

interface BreadcrumbItem {
interface SegmentData {
name: string;
linkable: boolean;
}

interface BreadcrumbItem extends SegmentData {
path: string;
}

interface TranslationData {
breadcrumb: Record<string, string>;
segments: Record<string, SegmentData>;
}

/**
* 경로 세그먼트를 번역합니다.
* 경로 세그먼트를 번역하고 링크 가능 여부를 확인합니다.
* @param segment 번역할 세그먼트
* @returns 번역된 텍스트 또는 원본 텍스트
* @returns 세그먼트 데이터 또는 기본값
*/
function translatePathSegment(segment: string): string {
function getSegmentData(segment: string): SegmentData {
const translationData = translations as TranslationData;
return translationData.breadcrumb[segment] || segment;
return (
translationData.segments[segment] || {
name: segment,
linkable: false,
}
);
}

/**
Expand All @@ -26,23 +35,26 @@ function generateBreadcrumbItems(): BreadcrumbItem[] {
const hash = window.location.hash.slice(1); // # 제거

if (!hash || hash === '/') {
return [{ name: '홈', path: '#/' }];
return [{ name: '홈', path: '#/', linkable: true }];
}

const pathSegments = hash.split('/').filter((segment) => segment !== '');
const breadcrumbItems: BreadcrumbItem[] = [{ name: '홈', path: '#/' }];
const breadcrumbItems: BreadcrumbItem[] = [
{ name: '홈', path: '#/', linkable: true },
];

let currentPath = '';

pathSegments.forEach((segment) => {
currentPath += `/${segment}`;

// 세그먼트 이름을 한국어로 변환
const displayName = translatePathSegment(segment);
// 세그먼트 데이터 가져오기 (이름과 링크 가능 여부)
const segmentData = getSegmentData(segment);

breadcrumbItems.push({
name: displayName,
name: segmentData.name,
path: `#${currentPath}`,
linkable: segmentData.linkable,
});
});

Expand All @@ -66,8 +78,13 @@ function createBreadcrumbElement(items: BreadcrumbItem[]): HTMLElement {
if (isLast) {
// 현재 페이지는 span으로 표시
return `<span class="truncate">${item.name}</span>`;
}

if (!item.linkable) {
// linkable이 false인 경우 span으로 표시 (링크 없음)
return `<span class="truncate text-blue-500">${item.name}</span> / `;
Copy link

Copilot AI Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] 비클릭(non-linkable) breadcrumb 세그먼트에 파란색(text-blue-500)을 사용하는 것은 사용자가 클릭 가능한 요소로 오해할 수 있습니다. 비링크 항목에는 보다 중립적인 색상 스타일을 고려해보세요.

Suggested change
return `<span class="truncate text-blue-500">${item.name}</span> / `;
return `<span class="truncate text-gray-500">${item.name}</span> / `;

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using a blue text color on non-linkable breadcrumb items may imply they are clickable; consider a neutral color or alternative styling to clearly differentiate non-clickable items.

Suggested change
return `<span class="truncate text-blue-500">${item.name}</span> / `;
return `<span class="truncate text-gray-500">${item.name}</span> / `;

Copilot uses AI. Check for mistakes.
} else {
// 이전 페이지들은 링크로 표시 + 구분자
// 링크 가능한 이전 페이지들은 링크로 표시 + 구분자
return `<a href="${item.path}" class="link truncate">${item.name}</a> / `;
}
})
Expand Down
13 changes: 11 additions & 2 deletions src/scripts/table-contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,20 @@ export const initializeTableContents = () => {
'font-extralight',
'hover:bg-gray-300',
'hover:font-semibold',
'cursor-pointer'
);
const link = document.createElement('button');
link.classList.add(
'flex',
'justify-start',
'items-stretch',
'p-1',
'cursor-pointer',
'w-full',
'truncate'
);
const link = document.createElement('a');
link.classList.add('flex', 'justify-start', 'items-stretch', 'p-1');
link.setAttribute('aria-label', heading.textContent || 'Heading Link');
link.setAttribute('role', 'link');

const headingText = heading.textContent || '';
link.textContent =
Expand Down
Loading