From d512f59826d6106edd70142698b4764d5819bce6 Mon Sep 17 00:00:00 2001 From: kwakseongjae Date: Fri, 10 Apr 2026 16:00:25 +0900 Subject: [PATCH] feat(i18n): add Korean (ko-KR) localization - Add complete Korean locale across all 7 i18n namespaces - All translation keys match the English baseline 1:1 - Register ko-KR in SUPPORTED_LOCALES and i18n-check validation Refs siddharthvaddem/openscreen#406 --- scripts/i18n-check.mjs | 2 +- src/i18n/config.ts | 2 +- src/i18n/locales/ko-KR/common.json | 29 +++++ src/i18n/locales/ko-KR/dialogs.json | 70 +++++++++++ src/i18n/locales/ko-KR/editor.json | 41 +++++++ src/i18n/locales/ko-KR/launch.json | 37 ++++++ src/i18n/locales/ko-KR/settings.json | 162 ++++++++++++++++++++++++++ src/i18n/locales/ko-KR/shortcuts.json | 36 ++++++ src/i18n/locales/ko-KR/timeline.json | 50 ++++++++ 9 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 src/i18n/locales/ko-KR/common.json create mode 100644 src/i18n/locales/ko-KR/dialogs.json create mode 100644 src/i18n/locales/ko-KR/editor.json create mode 100644 src/i18n/locales/ko-KR/launch.json create mode 100644 src/i18n/locales/ko-KR/settings.json create mode 100644 src/i18n/locales/ko-KR/shortcuts.json create mode 100644 src/i18n/locales/ko-KR/timeline.json diff --git a/scripts/i18n-check.mjs b/scripts/i18n-check.mjs index c320946..ca73b23 100644 --- a/scripts/i18n-check.mjs +++ b/scripts/i18n-check.mjs @@ -11,7 +11,7 @@ import path from "node:path"; const LOCALES_DIR = path.resolve("src/i18n/locales"); const BASE_LOCALE = "en"; -const COMPARE_LOCALES = ["zh-CN", "es", "tr"]; +const COMPARE_LOCALES = ["zh-CN", "es", "tr", "ko-KR"]; function getKeys(obj, prefix = "") { const keys = []; diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 636d727..0933569 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -1,5 +1,5 @@ export const DEFAULT_LOCALE = "en" as const; -export const SUPPORTED_LOCALES = ["en", "zh-CN", "es", , "fr", "tr"] as const; +export const SUPPORTED_LOCALES = ["en", "zh-CN", "es", "fr", "tr", "ko-KR"] as const; export const I18N_NAMESPACES = [ "common", "dialogs", diff --git a/src/i18n/locales/ko-KR/common.json b/src/i18n/locales/ko-KR/common.json new file mode 100644 index 0000000..b83cb44 --- /dev/null +++ b/src/i18n/locales/ko-KR/common.json @@ -0,0 +1,29 @@ +{ + "actions": { + "cancel": "취소", + "save": "저장", + "delete": "삭제", + "close": "닫기", + "share": "공유", + "done": "완료", + "open": "열기", + "upload": "업로드", + "export": "내보내기", + "file": "파일", + "edit": "편집", + "view": "보기", + "window": "창", + "quit": "종료", + "stopRecording": "녹화 중지" + }, + "playback": { + "play": "재생", + "pause": "일시정지", + "fullscreen": "전체화면", + "exitFullscreen": "전체화면 종료" + }, + "locale": { + "name": "한국어", + "short": "KO" + } +} diff --git a/src/i18n/locales/ko-KR/dialogs.json b/src/i18n/locales/ko-KR/dialogs.json new file mode 100644 index 0000000..3093cdf --- /dev/null +++ b/src/i18n/locales/ko-KR/dialogs.json @@ -0,0 +1,70 @@ +{ + "export": { + "complete": "내보내기 완료", + "yourFormatReady": "{{format}} 파일이 준비되었습니다", + "showInFolder": "폴더에서 보기", + "finalizingVideo": "비디오 내보내기 마무리 중...", + "compilingGifProgress": "GIF 생성 중... {{progress}}%", + "compilingGifWait": "GIF 생성 중... 잠시 시간이 걸릴 수 있습니다", + "takeMoment": "잠시 기다려 주세요...", + "failed": "내보내기 실패", + "tryAgain": "다시 시도해 주세요", + "finalizingVideoTitle": "비디오 마무리 중", + "compilingGif": "GIF 생성 중", + "exportingFormat": "{{format}} 내보내는 중", + "compiling": "생성 중...", + "renderingFrames": "프레임 렌더링 중", + "processing": "처리 중...", + "finalizing": "마무리 중...", + "compilingStatus": "생성 중...", + "status": "상태", + "format": "형식", + "frames": "프레임", + "cancelExport": "내보내기 취소", + "savedSuccessfully": "{{format}} 저장이 완료되었습니다!" + }, + "tutorial": { + "triggerLabel": "트리밍 사용법", + "title": "트리밍 사용법", + "description": "비디오에서 불필요한 부분을 잘라내는 방법을 알아보세요.", + "explanationBefore": "트림 도구는 제거할 구간을", + "remove": "지정", + "explanationMiddle": "하는 방식으로 동작합니다 —", + "covered": "빨간 트림 구간으로 덮인", + "explanationAfter": "부분은 내보낼 때 잘려나갑니다.", + "visualExample": "화면 예시", + "removed": "제거됨", + "kept": "유지됨", + "part1": "파트 1", + "part2": "파트 2", + "part3": "파트 3", + "finalVideo": "최종 비디오", + "step1Title": "1. 트림 추가", + "step1DescriptionBefore": "", + "step1DescriptionAfter": "키를 누르거나 가위 아이콘을 클릭해 제거할 구간을 표시하세요.", + "step2Title": "2. 조정", + "step2Description": "빨간 구간의 가장자리를 드래그해 잘라낼 범위를 설정하세요." + }, + "unsavedChanges": { + "title": "저장되지 않은 변경 사항", + "message": "저장되지 않은 변경 사항이 있습니다.", + "detail": "닫기 전에 프로젝트를 저장하시겠습니까?", + "saveAndClose": "저장 후 닫기", + "discardAndClose": "저장하지 않고 닫기", + "loadProject": "프로젝트 불러오기...", + "saveProject": "프로젝트 저장...", + "saveProjectAs": "다른 이름으로 프로젝트 저장..." + }, + "fileDialogs": { + "saveGif": "내보낸 GIF 저장", + "saveVideo": "내보낸 비디오 저장", + "selectVideo": "비디오 파일 선택", + "saveProject": "OpenScreen 프로젝트 저장", + "openProject": "OpenScreen 프로젝트 열기", + "gifImage": "GIF 이미지", + "mp4Video": "MP4 비디오", + "videoFiles": "비디오 파일", + "openscreenProject": "OpenScreen 프로젝트", + "allFiles": "모든 파일" + } +} diff --git a/src/i18n/locales/ko-KR/editor.json b/src/i18n/locales/ko-KR/editor.json new file mode 100644 index 0000000..4db7d1f --- /dev/null +++ b/src/i18n/locales/ko-KR/editor.json @@ -0,0 +1,41 @@ +{ + "newRecording": { + "title": "녹화로 돌아가기", + "description": "현재 세션이 저장되었습니다.", + "cancel": "취소", + "confirm": "확인" + }, + "errors": { + "noVideoLoaded": "불러온 비디오가 없습니다", + "videoNotReady": "비디오가 준비되지 않았습니다", + "unableToDetermineSourcePath": "소스 비디오 경로를 확인할 수 없습니다", + "failedToSaveGif": "GIF 저장에 실패했습니다", + "gifExportFailed": "GIF 내보내기에 실패했습니다", + "failedToSaveVideo": "비디오 저장에 실패했습니다", + "exportFailed": "내보내기에 실패했습니다", + "exportFailedWithError": "내보내기 실패: {{error}}", + "failedToSaveExport": "내보낸 파일 저장에 실패했습니다", + "failedToSaveExportedVideo": "내보낸 비디오 저장에 실패했습니다", + "failedToRevealInFolder": "폴더에서 파일 표시 오류: {{error}}" + }, + "export": { + "canceled": "내보내기가 취소되었습니다", + "exportedSuccessfully": "{{format}} 내보내기가 완료되었습니다" + }, + "project": { + "saveCanceled": "프로젝트 저장이 취소되었습니다", + "failedToSave": "프로젝트 저장에 실패했습니다", + "savedTo": "프로젝트가 {{path}}에 저장되었습니다", + "failedToLoad": "프로젝트 불러오기에 실패했습니다", + "invalidFormat": "유효하지 않은 프로젝트 파일 형식입니다", + "loadedFrom": "{{path}}에서 프로젝트를 불러왔습니다" + }, + "recording": { + "failedCameraAccess": "카메라 접근 권한 요청에 실패했습니다.", + "cameraBlocked": "카메라 접근이 차단되어 있습니다. 시스템 설정에서 권한을 허용해 주세요.", + "systemAudioUnavailable": "시스템 오디오를 사용할 수 없습니다. 시스템 오디오 없이 녹화합니다.", + "microphoneDenied": "마이크 접근이 거부되었습니다. 오디오 없이 녹화를 계속합니다.", + "cameraDenied": "카메라 접근이 거부되었습니다. 웹캠 없이 녹화를 계속합니다.", + "permissionDenied": "녹화 권한이 거부되었습니다. 화면 녹화를 허용해 주세요." + } +} diff --git a/src/i18n/locales/ko-KR/launch.json b/src/i18n/locales/ko-KR/launch.json new file mode 100644 index 0000000..1cc695e --- /dev/null +++ b/src/i18n/locales/ko-KR/launch.json @@ -0,0 +1,37 @@ +{ + "tooltips": { + "hideHUD": "HUD 숨기기", + "closeApp": "앱 닫기", + "restartRecording": "녹화 다시 시작", + "cancelRecording": "녹화 취소", + "pauseRecording": "녹화 일시정지", + "resumeRecording": "녹화 재개", + "openVideoFile": "비디오 파일 열기", + "openProject": "프로젝트 열기" + }, + "audio": { + "enableSystemAudio": "시스템 오디오 활성화", + "disableSystemAudio": "시스템 오디오 비활성화", + "enableMicrophone": "마이크 활성화", + "disableMicrophone": "마이크 비활성화", + "defaultMicrophone": "기본 마이크" + }, + "webcam": { + "enableWebcam": "웹캠 활성화", + "disableWebcam": "웹캠 비활성화", + "defaultCamera": "기본 카메라", + "searching": "검색 중...", + "noneFound": "카메라를 찾을 수 없음", + "unavailable": "카메라를 사용할 수 없음" + }, + "sourceSelector": { + "loading": "소스 불러오는 중...", + "screens": "화면 ({{count}}개)", + "windows": "창 ({{count}}개)", + "defaultSourceName": "화면" + }, + "recording": { + "selectSource": "녹화할 소스를 선택해 주세요" + }, + "language": "언어" +} diff --git a/src/i18n/locales/ko-KR/settings.json b/src/i18n/locales/ko-KR/settings.json new file mode 100644 index 0000000..cd9f734 --- /dev/null +++ b/src/i18n/locales/ko-KR/settings.json @@ -0,0 +1,162 @@ +{ + "zoom": { + "level": "줌 레벨", + "selectRegion": "조정할 줌 구간을 선택하세요", + "deleteZoom": "줌 삭제", + "focusMode": { + "title": "포커스 모드", + "manual": "수동", + "auto": "자동", + "autoDescription": "녹화된 커서 위치를 따라 카메라가 이동합니다" + } + }, + "speed": { + "playbackSpeed": "재생 속도", + "selectRegion": "조정할 속도 구간을 선택하세요", + "deleteRegion": "속도 구간 삭제", + "customPlaybackSpeed": "재생 속도 직접 입력", + "maxSpeedError": "속도는 16×를 초과할 수 없습니다" + }, + "trim": { + "deleteRegion": "트림 구간 삭제" + }, + "layout": { + "title": "레이아웃", + "preset": "프리셋", + "selectPreset": "프리셋 선택", + "pictureInPicture": "화면 속 화면", + "verticalStack": "세로 배치", + "webcamShape": "카메라 모양", + "webcamSize": "웹캠 크기" + }, + "effects": { + "title": "비디오 효과", + "blurBg": "배경 흐림", + "motionBlur": "모션 블러", + "off": "끄기", + "shadow": "그림자", + "roundness": "모서리 둥글기", + "padding": "여백" + }, + "background": { + "title": "배경", + "image": "이미지", + "color": "색상", + "gradient": "그라디언트", + "uploadCustom": "직접 업로드", + "gradientLabel": "그라디언트 {{index}}" + }, + "crop": { + "title": "자르기", + "cropVideo": "비디오 자르기", + "dragInstruction": "각 면을 드래그해 자르기 영역을 조정하세요", + "ratio": "비율", + "free": "자유", + "done": "완료", + "lockAspectRatio": "화면 비율 고정", + "unlockAspectRatio": "화면 비율 해제" + }, + "exportFormat": { + "mp4": "MP4", + "gif": "GIF", + "mp4Video": "MP4 비디오", + "mp4Description": "고화질 비디오 파일", + "gifAnimation": "GIF 애니메이션", + "gifDescription": "공유용 애니메이션 이미지" + }, + "exportQuality": { + "title": "내보내기 품질", + "low": "낮음", + "medium": "보통", + "high": "높음" + }, + "gifSettings": { + "frameRate": "GIF 프레임 속도", + "size": "GIF 크기", + "loop": "GIF 반복" + }, + "project": { + "save": "프로젝트 저장", + "load": "프로젝트 불러오기" + }, + "export": { + "videoButton": "비디오 내보내기", + "gifButton": "GIF 내보내기", + "chooseSaveLocation": "저장 위치 선택" + }, + "links": { + "reportBug": "버그 신고", + "starOnGithub": "GitHub에 Star 남기기" + }, + "imageUpload": { + "invalidFileType": "지원하지 않는 파일 형식입니다", + "jpgOnly": "JPG 또는 JPEG 이미지 파일을 업로드해 주세요.", + "uploadSuccess": "커스텀 이미지가 성공적으로 업로드되었습니다!", + "failedToUpload": "이미지 업로드에 실패했습니다", + "errorReading": "파일을 읽는 중 오류가 발생했습니다." + }, + "annotation": { + "title": "주석 설정", + "active": "활성", + "typeText": "텍스트", + "typeImage": "이미지", + "typeArrow": "화살표", + "textContent": "텍스트 내용", + "textPlaceholder": "텍스트를 입력하세요...", + "fontStyle": "폰트 스타일", + "selectStyle": "스타일 선택", + "size": "크기", + "customFonts": "커스텀 폰트", + "textColor": "텍스트 색상", + "background": "배경", + "none": "없음", + "color": "색상", + "clearBackground": "배경 지우기", + "uploadImage": "이미지 업로드", + "supportedFormats": "지원 형식: JPG, PNG, GIF, WebP", + "arrowDirection": "화살표 방향", + "strokeWidth": "선 두께: {{width}}px", + "arrowColor": "화살표 색상", + "deleteAnnotation": "주석 삭제", + "shortcutsAndTips": "단축키 및 팁", + "tipMovePlayhead": "재생 헤드를 주석 구간으로 옮겨 항목을 선택하세요.", + "tipTabCycle": "Tab 키로 겹치는 항목을 순환할 수 있습니다.", + "tipShiftTabCycle": "Shift+Tab으로 역방향 순환할 수 있습니다.", + "invalidImageType": "지원하지 않는 파일 형식입니다", + "imageFormatsOnly": "JPG, PNG, GIF 또는 WebP 이미지 파일을 업로드해 주세요.", + "imageUploadSuccess": "이미지가 성공적으로 업로드되었습니다!", + "failedImageUpload": "이미지 업로드에 실패했습니다" + }, + "fontStyles": { + "classic": "클래식", + "editor": "에디터", + "strong": "강조", + "typewriter": "타자기", + "deco": "데코", + "simple": "심플", + "modern": "모던", + "clean": "클린" + }, + "customFont": { + "dialogTitle": "Google 폰트 추가", + "urlLabel": "Google Fonts 가져오기 URL", + "urlPlaceholder": "https://fonts.googleapis.com/css2?family=Roboto&display=swap", + "urlHelp": "Google Fonts에서 폰트 선택 → \"폰트 가져오기\" 클릭 → @import URL 복사", + "nameLabel": "표시 이름", + "namePlaceholder": "내 커스텀 폰트", + "nameHelp": "폰트 선택기에서 표시될 이름입니다", + "addButton": "폰트 추가", + "addingButton": "추가 중...", + "errorEmptyUrl": "Google Fonts 가져오기 URL을 입력해 주세요", + "errorInvalidUrl": "유효한 Google Fonts URL을 입력해 주세요", + "errorEmptyName": "폰트 이름을 입력해 주세요", + "errorExtractFailed": "URL에서 폰트 패밀리를 추출할 수 없습니다", + "successMessage": "\"{{fontName}}\" 폰트가 성공적으로 추가되었습니다", + "failedToAdd": "폰트 추가에 실패했습니다", + "errorTimeout": "폰트 로딩 시간이 초과되었습니다. URL을 확인하고 다시 시도해 주세요.", + "errorLoadFailed": "폰트를 불러올 수 없습니다. Google Fonts URL이 올바른지 확인해 주세요." + }, + "language": { + "title": "언어" + } +} diff --git a/src/i18n/locales/ko-KR/shortcuts.json b/src/i18n/locales/ko-KR/shortcuts.json new file mode 100644 index 0000000..1760d8f --- /dev/null +++ b/src/i18n/locales/ko-KR/shortcuts.json @@ -0,0 +1,36 @@ +{ + "title": "키보드 단축키", + "customize": "사용자 지정", + "configurable": "변경 가능", + "fixed": "고정", + "pressKey": "키를 누르세요...", + "clickToChange": "클릭해서 변경", + "pressEscToCancel": "Esc를 눌러 취소", + "helpText": "단축키를 클릭한 후 새 키 조합을 누르세요. 취소하려면 Esc를 누르세요.", + "resetToDefaults": "기본값으로 초기화", + "alreadyUsedBy": "이미 {{action}}에서 사용 중입니다", + "swap": "교체", + "reservedShortcut": "이 단축키는 \"{{label}}\"에 예약되어 있어 변경할 수 없습니다.", + "savedToast": "키보드 단축키가 저장되었습니다", + "resetToast": "기본 단축키로 초기화되었습니다 — 저장을 클릭해 적용하세요", + "actions": { + "addZoom": "줌 추가", + "addTrim": "트림 추가", + "addSpeed": "속도 추가", + "addAnnotation": "주석 추가", + "addKeyframe": "키프레임 추가", + "deleteSelected": "선택 항목 삭제", + "playPause": "재생 / 일시정지" + }, + "fixedActions": { + "undo": "실행 취소", + "redo": "다시 실행", + "cycleAnnotationsForward": "주석 앞으로 순환", + "cycleAnnotationsBackward": "주석 뒤로 순환", + "deleteSelectedAlt": "선택 항목 삭제 (대체)", + "panTimeline": "타임라인 이동", + "zoomTimeline": "타임라인 확대/축소", + "frameBack": "이전 프레임", + "frameForward": "다음 프레임" + } +} diff --git a/src/i18n/locales/ko-KR/timeline.json b/src/i18n/locales/ko-KR/timeline.json new file mode 100644 index 0000000..167c26f --- /dev/null +++ b/src/i18n/locales/ko-KR/timeline.json @@ -0,0 +1,50 @@ +{ + "buttons": { + "addZoom": "줌 추가 (Z)", + "suggestZooms": "커서 기반 줌 제안", + "addTrim": "트림 추가 (T)", + "addAnnotation": "주석 추가 (A)", + "addSpeed": "속도 추가 (S)" + }, + "hints": { + "pressZoom": "Z를 눌러 줌 추가", + "pressTrim": "T를 눌러 트림 추가", + "pressAnnotation": "A를 눌러 주석 추가", + "pressSpeed": "S를 눌러 속도 추가" + }, + "labels": { + "pan": "이동", + "zoom": "줌", + "zoomItem": "줌 {{index}}", + "trimItem": "트림 {{index}}", + "speedItem": "속도 {{index}}", + "annotationItem": "주석", + "imageItem": "이미지", + "emptyText": "빈 텍스트" + }, + "emptyState": { + "noVideo": "불러온 비디오 없음", + "dragAndDrop": "비디오를 드래그 앤 드롭해서 편집을 시작하세요" + }, + "errors": { + "cannotPlaceZoom": "이 위치에 줌을 추가할 수 없습니다", + "zoomExistsAtLocation": "이 위치에 이미 줌이 있거나 공간이 부족합니다.", + "zoomSuggestionUnavailable": "줌 제안 기능을 사용할 수 없습니다", + "noCursorTelemetry": "커서 데이터가 없습니다", + "noCursorTelemetryDescription": "커서 기반 제안을 생성하려면 먼저 화면을 녹화해 주세요.", + "noUsableTelemetry": "사용 가능한 커서 데이터가 없습니다", + "noUsableTelemetryDescription": "녹화에 충분한 커서 이동 데이터가 포함되어 있지 않습니다.", + "noDwellMoments": "명확한 커서 정지 구간을 찾을 수 없습니다", + "noDwellMomentsDescription": "중요한 동작에서 커서를 천천히 멈추며 녹화해 보세요.", + "noAutoZoomSlots": "자동 줌 슬롯이 없습니다", + "noAutoZoomSlotsDescription": "감지된 정지 지점이 기존 줌 구간과 겹칩니다.", + "cannotPlaceTrim": "이 위치에 트림을 추가할 수 없습니다", + "trimExistsAtLocation": "이 위치에 이미 트림이 있거나 공간이 부족합니다.", + "cannotPlaceSpeed": "이 위치에 속도를 추가할 수 없습니다", + "speedExistsAtLocation": "이 위치에 이미 속도 구간이 있거나 공간이 부족합니다." + }, + "success": { + "addedZoomSuggestions": "커서 기반 줌 제안 {{count}}개가 추가되었습니다", + "addedZoomSuggestionsPlural": "커서 기반 줌 제안 {{count}}개가 추가되었습니다" + } +}