From aed2ae9615b5dd9dffef715bfa0432f57d5de25d Mon Sep 17 00:00:00 2001 From: huk082 <89136256+huk082@users.noreply.github.com> Date: Tue, 13 Oct 2026 19:52:26 +0900 Subject: [PATCH] Add Korean interface localization and Hangul font support (#857) * Show Thai file names and text in the interface with an installed Thai font A drawing whose file name is Thai showed boxes in its file tab and in the window title: the interface loaded one Latin system font (plus a CJK font for the Chinese interface), and neither has Thai. install_fonts now also adds an installed Thai font (Thonburi, Leelawadee UI, Noto Sans Thai, Loma...) after the Latin one, in any interface language, since file names and drawing text are Thai whatever the interface is in. No font is bundled; without one installed nothing changes. Co-Authored-By: Claude Opus 5.5 * Add Korean interface localization and Hangul font support --------- Co-authored-by: Chanthawat Kiriyadee Co-authored-by: Claude Opus 5.5 Co-authored-by: Brandon Thomas --- ATTRIBUTION.md | 1 + README.md | 2 +- crates/fonts/src/craft_fonts.rs | 1 + crates/ui-egui/src/i18n/ko.tsv | 1240 +++++++++++++++++++++++++++++ crates/ui-egui/src/i18n/mod.rs | 23 +- crates/ui-egui/src/i18n/tests.rs | 179 ++++- crates/ui-egui/src/lib.rs | 16 +- crates/ui-egui/src/menus.rs | 8 +- crates/ui-egui/src/options/mod.rs | 2 +- crates/ui-egui/src/theme.rs | 160 +++- docs/control-protocol.md | 5 +- docs/localization-parity.md | 9 +- docs/localization.md | 28 +- 13 files changed, 1639 insertions(+), 35 deletions(-) create mode 100644 crates/ui-egui/src/i18n/ko.tsv diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index b49d56b..dddd4ea 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -65,3 +65,4 @@ Generated-in-code assets are original and have no file to list: | `crates/ui-egui/src/i18n/fr.tsv` | @pechnikov2026-star | Original French translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied | | `crates/ui-egui/src/i18n/zh.tsv` | @mayloglog | Original Simplified Chinese translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied | | `crates/ui-egui/src/i18n/it.tsv` | Emanuele Bottelli (AI-assisted) | Original Italian translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied | +| `crates/ui-egui/src/i18n/ko.tsv` | @huk082 (AI-assisted) | Original Korean translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied; no font assets added | diff --git a/README.md b/README.md index 9dffe2e..56317a0 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ numbers): | Layouts & plotting | Paper-space layouts, viewports (scale, lock, per-viewport layer freeze), MSPACE/PSPACE through viewports, page setups, PLOT and EXPORTPDF | | Parametric | Geometric and dimensional constraints, AUTOCONSTRAIN, PARAMETERS with expressions, conflict detection; constraints re-solve after every edit | | Grips | Hot grips with Space to cycle Stretch, Move, Rotate, Scale and Mirror | -| Interface languages | English, Ukrainian (Українська), Brazilian Portuguese (Português (Brasil)), German (Deutsch), Spanish (Español), Czech (Čeština), Russian (Русский), French (Français), Simplified Chinese (简体中文, with an installed CJK font) and Italian (Italiano), live switching and a remembered Auto/system-language choice; [details](docs/localization.md) | +| Interface languages | English, Ukrainian (Українська), Brazilian Portuguese (Português (Brasil)), German (Deutsch), Spanish (Español), Czech (Čeština), Russian (Русский), French (Français), Simplified Chinese (简体中文, with an installed CJK font), Italian (Italiano) and Korean (한국어, with an installed Hangul font), live switching and a remembered Auto/system-language choice; [details](docs/localization.md) | | Automation | MCP server, JSON control channel, `cadcraft-cli` (info, convert, run, commands, mcp) | ## Quick start diff --git a/crates/fonts/src/craft_fonts.rs b/crates/fonts/src/craft_fonts.rs index 05bed1b..fb29077 100644 --- a/crates/fonts/src/craft_fonts.rs +++ b/crates/fonts/src/craft_fonts.rs @@ -83,6 +83,7 @@ mod tests { let sample = match f.scripts.first().copied() { Some("Jpan") => '\u{3042}', Some("Arab") => '\u{0628}', + Some("Hang" | "Kore") => '가', _ => 'A', }; assert!(face.is_some_and(|x| x.has_glyph(sample)), "{} lacks {sample}", f.family); diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv new file mode 100644 index 0000000..d60c7e1 --- /dev/null +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -0,0 +1,1240 @@ +# CADCraft Korean interface catalog — original Korean translations. +# License: MIT OR Apache-2.0 (the repository LICENSE-MIT and LICENSE-APACHE). +# Written from CADCraft English strings; no proprietary translations copied. +# UTF-8: contextEnglish sourceKorean translation. +# @plural uses a single Korean form with {n}. + Apply 적용 + Attach DWG... DWG 부착... + Bind 결합 + Detach 분리 + Loaded 로드됨 + Make it a block of this drawing, keeping its layer names 도면층 이름을 유지하여 이 도면의 블록으로 만들기 + Make it a block of this drawing; its layers become NAME$0$LAYER 이 도면의 블록으로 만들기; 도면층 이름은 NAME$0$LAYER로 변경됨 + No external references are attached. 부착된 외부 참조가 없습니다. + Not Found 찾을 수 없음 + Not loaded 로드되지 않음 + Open 열기 + Overlay 중첩 + Reference Manager 참조 관리자 + Reference Name 참조 이름 + References: {n} 참조: {n}개 + Reload 다시 로드 + Reload All 모두 다시 로드 + Saved Path 저장된 경로 + Unload 언로드 + Unloaded 언로드됨 + Unreferenced 참조되지 않음 + Unresolved 경로 확인 실패 + Cancel 취소 + OK 확인 + or [ 또는 [ + % of all commits 전체 커밋 중 비율(%) + * Wildcard Match * 와일드카드 일치 + *VARIES* *다양함* + + | Top | 2D Wireframe + | 평면 | 2D 와이어프레임 + +Bin +Bin + +LOC +LOC + 1 Viewport 뷰포트 1개 + 2 Points 2점 + 2 Viewports 뷰포트 2개 + 2D Solid 2D 솔리드 + 3 Points 3점 + 3 Viewports 뷰포트 3개 + 3D Face 3D 면 + 3D Operations 3D 작업 + 3D Polyline 3D 폴리선 + 4 Viewports 뷰포트 4개 + < Less than < 미만 + <> Not Equal <> 같지 않음 + = Equals = 같음 + > Greater than > 초과 + A variable name starts with a letter and has only letters, digits and _ 변수 이름은 문자로 시작하며 문자, 숫자, _만 사용할 수 있습니다 + About 정보 + About CADCraft CADCraft 정보 + Absolute 절대 + Add Leader 지시선 추가 + Additional angles 추가 각도 + Align 정렬 + Align Text 문자 정렬 + Aligned 정렬 + All 모두 + All Annotation Objects 모든 주석 객체 + All Used Layers 사용 중인 모든 도면층 + All files 모든 파일 + Angle 각도 + Angular 각도 + Annotation Visibility 주석 표시 + Annotation scale 주석 축척 + Apparent Intersection 가상 교차점 + Append to current selection set 현재 선택 세트에 추가 + Apply to: 적용 대상: + Apply true color 트루 컬러 적용 + Arc 호 + Arc Length 호 길이 + Area 면적 + Arrange Panel 패널 배치 + Array 배열 + Attach 부착 + Attribute 속성 + Attribute Definition 속성 정의 + Attribute Display 속성 표시 + Audit 검사 + Auto (system language) 자동(시스템 언어) + Auto Constrain 자동 구속 + AutoConstrain 자동 구속 + Axis, End 축, 끝점 + Base 기준 + Baseline 기준선 + Batch Publish 일괄 게시 + Binary assets added 추가된 바이너리 자산 + Binary assets removed 삭제된 바이너리 자산 + Bind... 결합... + Blend 곡선 연결 + Block 블록 + Block Attribute Manager... 블록 속성 관리자... + Block Editor 블록 편집기 + Block Reference 블록 참조 + Block... 블록... + Blocks 블록 + Blue 파란색 + Bold 굵게 + Boolean 불리언 + Boundary 경계 + Boundary... 경계... + Box 직육면체 + Break 끊기 + Break At Point 한 점에서 끊기 + Bring Above Objects 객체 위로 가져오기 + Bring Annotations to Front 주석을 맨 앞으로 가져오기 + Bring to Front 맨 앞으로 가져오기 + Bulleted list 글머리 기호 목록 + ByBlock 블록별 + ByColor 색상별 + ByLayer 도면층별 + CADCraft Help CADCraft 도움말 + Cancel 취소 + Cartesian (x, y) 직교 좌표(x, y) + Center 중심 + Center Line 중심선 + Center Mark 중심 표식 + Center X 중심 X + Center Y 중심 Y + Center Z 중심 Z + Center, Diameter 중심, 지름 + Center, Radius 중심, 반지름 + Center, Start, Angle 중심, 시작점, 각도 + Center, Start, End 중심, 시작점, 끝점 + Center, Start, Length 중심, 시작점, 길이 + Chamfer 모따기 + Change Space 공간 변경 + Change to Current Layer 현재 도면층으로 변경 + Changed: {n} 변경됨: {n}개 + Circle 원 + Circumference 둘레 + Clear History 기록 지우기 + Clear 지우기 + Clip 자르기 + Clip Viewport 뷰포트 자르기 + Clipboard 클립보드 + Close 닫기 + Close All 모두 닫기 + Close All Other Drawings 다른 도면 모두 닫기 + Closed 닫힘 + Close Comparison 비교 닫기 + Coincident 일치 + Collapse Palettes 팔레트 접기 + Collapse Tool Sets 도구 세트 접기 + Collinear 동일 선상 + Color 색상 + Color 8 색상 8 + Color 9 색상 9 + Color Index 색상 인덱스 + Color... 색상... + Command Aliases 명령 별칭 + Command History 명령 기록 + Clear Command History 명령 기록 지우기 + Command Line 명령행 + Command Line History Lines 명령행 기록 줄 수 + Command Reference 명령 참조 + Commits 커밋 + Company 회사 + Compare Drawings... 도면 비교... + Computer-aided design and drafting: an open-source, clean-room CAD application written in pure Rust. 컴퓨터 지원 설계 및 제도를 위한 순수 Rust 기반 오픈 소스 CAD 프로그램입니다. 독립적으로 개발되었습니다. + Concentric 동심 + Cone 원뿔 + Constraint Bars 구속조건 막대 + Constraint Settings 구속조건 설정 + Construction Line 구성선 + Contents 내용 + Continue 계속 + Contributors 기여자 + Control Vertices 조정 정점 + Convert 변환 + Copy 복사 + Copy Full File Path 전체 파일 경로 복사 + Copy History 기록 복사 + Copy Layout 배치 복사 + Copy Nested Objects 내포된 객체 복사 + Copy Selection 선택 항목 복사 + Copy with Base Point 기준점을 지정하여 복사 + Count 개수 + Count List 개수 목록 + Count Table 개수 표 + Create Block 블록 작성 + Creates a user parameter 사용자 매개변수를 만듭니다 + Ctrl+Enter to finish Ctrl+Enter로 완료 + Current layer 현재 도면층 + Current selection 현재 선택 항목 + Current: {color} 현재: {color} + Cut 잘라내기 + Cyan 청록색 + Cylinder 원기둥 + DWG DWG + DWG Reference... DWG 참조... + DXF DXF + Dark 어둡게 + Data Extraction... 데이터 추출... + Default 기본값 + Data Link... 데이터 링크... + Default Save Format 기본 저장 형식 + Default Save Version 기본 저장 버전 + Define Attributes 속성 정의 + Define Attributes... 속성 정의... + Defined width 지정된 폭 + Degree 도 기호 + Degrees of Freedom 자유도 + Degrees of freedom: {n} 자유도: {n} + Delete 삭제 + Delete Column 열 삭제 + Delete Constraints 구속조건 삭제 + Delete Dimension Style 치수 스타일 삭제 + Delete Duplicate Objects 중복 객체 삭제 + Delete Layer 도면층 삭제 + Delete Layer State 도면층 상태 삭제 + Delete Layout 배치 삭제 + Delete Row 행 삭제 + Delete Text Style 문자 스타일 삭제 + Delta X X 변화량 + Delta Y Y 변화량 + Description 설명 + Deselect All 모두 선택 해제 + Diameter 지름 + Dim style 치수 스타일 + Dimension Break 치수 끊기 + Dimension Edit 치수 편집 + Dimension 치수 + Dimension Objects Only 치수 객체만 + Dimension Space 치수 간격 + Dimension Style Override 치수 스타일 재지정 + Dimension Style... 치수 스타일... + Dimension Text Edit 치수 문자 편집 + Dimension style 치수 스타일 + Dimensional Constraint 치수 구속조건 + Dimensional Constraint Parameters 치수 구속조건 매개변수 + Dimensional Constraints 치수 구속조건 + Disassociate Dimensions 치수 연관 해제 + Display name 표시 이름 + Distance 거리 + Divide 등분할 + Do not plot (click: plot) 플롯 안 함(클릭: 플롯) + Donut 도넛 + Double-click to edit the description 두 번 클릭하여 설명 편집 + Down 아래로 + Drafting 제도 + Drafting Settings 제도 설정 + Drafting Settings... 제도 설정... + Draw 그리기 + Draw Order 그리기 순서 + Drawing (DWG) 도면(DWG) + Drawing (DXF) 도면(DXF) + Drawing Limits 도면 한계 + Drawing Template (DWT) 도면 템플릿(DWT) + Drawing Utilities 도면 유틸리티 + Drawings (DWG, DXF) 도면(DWG, DXF) + SVG drawings (SVG, SVGZ) SVG 도면(SVG, SVGZ) + Dynamic 동적 + Dynamic Dimensions 동적 치수 + Dynamic Input 동적 입력 + Dynamic Input (F12) 동적 입력(F12) + E 동 + Edit 편집 + Edit Attributes... 속성 편집... + Edit Text... 문자 편집... + Edit Table Cell 표 셀 편집 + Elevation 고도 + Ellipse 타원 + Elliptical Arc 타원 호 + Enable Dynamic Input (F12) 동적 입력 사용(F12) + End Object Isolation 객체 분리 종료 + End Width 끝 폭 + End X 끝점 X + End Y 끝점 Y + End angle 끝 각도 + Endpoint 끝점 + Enter Enter + Enter Top, Zero or Bottom. Top, Zero 또는 Bottom을 입력하세요. + Enter a fit data option 맞춤 데이터 옵션 입력 + Enter a pattern name 패턴 이름 입력 + Enter a vertex editing option 정점 편집 옵션 입력 + Enter an option 옵션 입력 + Enter angle of xline 구성선 각도 입력 + Enter angle of xline (0) or 구성선 각도 입력(0) 또는 + Enter area of rectangle in current units 현재 단위로 직사각형 면적 입력 + Enter attribute prompt 속성 프롬프트 입력 + Enter attribute tag name 속성 태그 이름 입력 + Enter attribute value 속성 값 입력 + Enter block name 블록 이름 입력 + Enter constraint type 구속조건 유형 입력 + Enter continuity 연속성 입력 + Enter default attribute value 기본 속성 값 입력 + Enter degree of spline 스플라인 차수 입력 + Enter delta angle 각도 변화량 입력 + Enter delta length or 길이 변화량 입력 또는 + Enter dimension text 치수 문자 입력 + Enter dimension text (<> = measurement) 치수 문자 입력(<> = 측정값) + Enter dimension value or [name=expression] 치수 값 또는 [name=expression] 입력 + Enter dimension variable name to override 재지정할 치수 변수 이름 입력 + Enter justification type 자리맞추기 유형 입력 + Enter leader text 지시선 문자 입력 + Enter mline scale 다중선 축척 입력 + Enter mode 모드 입력 + Enter name of block to insert 삽입할 블록 이름 입력 + Enter new text 새 문자 입력 + Enter next point 다음 점 입력 + Enter number of columns 열 수 입력 + Enter number of data rows 데이터 행 수 입력 + Enter number of items 항목 수 입력 + Enter number of items along path 경로를 따라 배치할 항목 수 입력 + Enter number of sides 변의 수 입력 + Enter number of turns 회전 수 입력 + Enter percentage length 길이 비율(%) 입력 + Enter rectangle length 직사각형 길이 입력 + Enter rectangle width 직사각형 폭 입력 + Enter scale factor 축척 비율 입력 + Enter text 문자 입력 + Enter text (use \P for new paragraphs) 문자 입력(새 단락은 \P 사용) + Enter the number of segments 구간 수 입력 + Enter twist direction of helix 나선의 회전 방향 입력 + Entire drawing 전체 도면 + Equal 같음 + Erase 지우기 + Evaluate 계산 + Exclude from new selection set 새 선택 세트에서 제외 + Explode 분해 + Export 내보내기 + Export Table 표 내보내기 + Export to PDF... PDF로 내보내기... + Expression 표현식 + Extend 연장 + Extension 연장선 + Extents 범위 + External Reference 외부 참조 + External References 외부 참조 + Extrude 돌출 + File 파일 + File Tab 파일 탭 + Fillet 모깎기 + Filter 필터 + Filters 필터 + Find and Replace 찾기 및 바꾸기 + Find... 찾기... + First 첫 번째 + First commit 첫 커밋 + Fit Points 맞춤점 + Fix 고정 + Flatten Objects 객체 평면화 + Format 형식 + Freehand 자유곡선 + Freeze 동결 + Freeze Layer 도면층 동결 + Freeze in new viewports 새 뷰포트에서 동결 + Freeze/Thaw 동결/동결 해제 + Freeze/Thaw in all viewports 모든 뷰포트에서 동결/동결 해제 + Freeze/Thaw in the current viewport 현재 뷰포트에서 동결/동결 해제 + From 기준 + From Template... 템플릿에서... + General 일반 + Geometric Center 기하학적 중심 + Geometric Constraint 기하 구속조건 + Geometric Constraints 기하 구속조건 + Geometry 형상 + Get from the Drawing 도면에서 가져오기 + Get Variable 변수 조회 + Get View 뷰 조회 + Global 전체 + Global width 전체 폭 + Grab bag 모아 보기 + Gradient 그라데이션 + Gradient... 그라데이션... + Green 초록색 + Grid (F7) 그리드(F7) + Grid Display 그리드 표시 + Grid On (F7) 그리드 켜기(F7) + Grid snap 그리드 스냅 + Grid spacing 그리드 간격 + Grip Edit 그립 편집 + Grip Mirror 그립 대칭 복사 + Grip Option 그립 옵션 + Grip Rotate 그립 회전 + Grip Scale 그립 축척 + Grip Stretch 그립 신축 + Group 그룹 + Group Edit 그룹 편집 + Group Options 그룹 옵션 + Group Selection 그룹 선택 + Hatch 해치 + Hatch Edit 해치 편집 + Hatch Patterns 해치 패턴 + Hatch... 해치... + Height 높이 + Height: {height} 높이: {height} + Helix 나선 + Help 도움말 + Hide All 모두 숨기기 + Hide All Constraint Bars 모든 구속조건 막대 숨기기 + Hide All Dynamic Dimensions 모든 동적 치수 숨기기 + Hide Layer List 도면층 목록 숨기기 + Hide Objects 객체 숨기기 + Hide Palettes 팔레트 숨기기 + History 기록 + Home 홈 + Horizontal 수평 + HorizontalDistance 수평 거리 + How to apply: 적용 방법: + ID Point 점 좌표 조회 + Import 가져오기 + In 확대 + In-window Menu Bar 창 내부 메뉴 막대 + Include in new selection set 새 선택 세트에 포함 + Increment angle 증분 각도 + Index Color 인덱스 색상 + Index color: {index} 인덱스 색상: {index} + Infer Constraints 구속조건 추론 + Inquiry 조회 + Insert 삽입 + Insert Block 블록 삽입 + Insert Column 열 삽입 + Insert Row 행 삽입 + Insert tab 탭 삽입 + Insert this block 이 블록 삽입 + Insert Table Formula 표 수식 삽입 + Insertion 삽입점 + Insertion X 삽입점 X + Insertion Y 삽입점 Y + Insertion Z 삽입점 Z + Inspect Constraints 구속조건 검사 + Inspect Drawing 도면 검사 + Inspection 검사 + Interface Language 인터페이스 언어 + Interface Theme 인터페이스 테마 + Intersect 교집합 + Intersection 교차점 + Isolate 분리 + Isolate Layer 도면층 분리 + Isolate Objects 객체 분리 + Isometric Drafting 등각 제도 + Jogged 꺾어진 반지름 + Jogged Linear 꺾어진 선형 + Join 결합 + Join the ArtCraft Discord… ArtCraft Discord 참여하기… + Join us on Discord Discord에서 함께하기 + Isometric Plane 등각 평면 + Isometric snap 등각 스냅 + Language 언어 + Last 마지막 + Last commit 마지막 커밋 + Layer 도면층 + Layer Delete 도면층 삭제 + Layer Match 도면층 일치 + Layer Merge 도면층 병합 + Layer Off 도면층 끄기 + Layer Properties 도면층 특성 + Layer Properties Manager 도면층 특성 관리자 + Layer Properties Manager — current viewport 도면층 특성 관리자 — 현재 뷰포트 + Layer State 도면층 상태 + Layer States Manager... 도면층 상태 관리자... + Layer Tools 도면층 도구 + Layer contains objects (double-click to make current) 객체가 있는 도면층(두 번 클릭: 현재 도면층으로 설정) + Layer is empty (double-click to make current) 비어 있는 도면층(두 번 클릭: 현재 도면층으로 설정) + Layers 도면층 + Layers Palette 도면층 팔레트 + Hide Palette 팔레트 숨기기 + Layers displayed: {shown} of {total}. Current layer: {layer} 표시된 도면층: {total}개 중 {shown}개. 현재 도면층: {layer} + Layout 배치 + Layout list 배치 목록 + Leader 지시선 + Leaders Only 지시선만 + Left 왼쪽 + Length 길이 + Length X X 방향 길이 + Length Y Y 방향 길이 + Lengthen 길이 조정 + Light 밝게 + Line 선 + Line delta 코드 줄 변화량 + Linear 선형 + Lines +/− 코드 줄 +/− + Lines added 추가된 코드 줄 + Lines deleted 삭제된 코드 줄 + Lines of Command History 명령 기록 줄 수 + Linetype 선종류 + Linetype Scale 선종류 축척 + Linetype scale 선종류 축척 + Ltscale 선종류 축척 + Linetype... 선종류... + Lineweight 선가중치 + Lineweight... 선가중치... + List 목록 + List Blocks 블록 목록 + List Dimension Styles 치수 스타일 목록 + List Groups 그룹 목록 + List Layer States 도면층 상태 목록 + List Layouts 배치 목록 + List System Variables 시스템 변수 목록 + List Text Styles 문자 스타일 목록 + Load all 모두 로드 + Load... 로드... + Loaded linetypes 로드된 선종류 + Lock 잠금 + Lock Layer 도면층 잠금 + Lock viewport 뷰포트 잠금 + Lock/Unlock 잠금/잠금 해제 + Loft 로프트 + MIT OR Apache-2.0. Not affiliated with Autodesk, Inc. MIT OR Apache-2.0. Autodesk, Inc.와 관련이 없습니다. + MODEL 모형 + MText 여러 줄 문자 + Magenta 자홍색 + Major line every 주 그리드선 간격 + Make Current 현재로 설정 + Make Object's Layer Current 객체의 도면층을 현재로 설정 + Make... 만들기... + Match Layer 도면층 일치 + Match Properties 특성 일치 + Measure 길이 분할 + Measure Distance 거리 측정 + Measure Geometry 형상 측정 + Merge Cells 셀 병합 + Merged PRs 병합된 PR + Mid Between 2 Points 두 점 사이의 중간점 + Midpoint 중간점 + Mirror 대칭 복사 + Model 모형 + Model Space (in viewport) 모형 공간(뷰포트 내부) + Modeling 모델링 + Models 모델 + Modify 수정 + Move 이동 + Multileader 다중 지시선 + Multileader Style... 다중 지시선 스타일... + Multileader style 다중 지시선 스타일 + Multiline 다중선 + Multiline Text 여러 줄 문자 + Multiline Text... 여러 줄 문자... + Multiple 다중 + Multiple Point 여러 점 + My 내 특성 + My properties: not available yet 내 특성: 아직 사용할 수 없습니다 + N 북 + Name 이름 + Name (A–Z) 이름(A–Z) + Named Views... 명명된 뷰... + Nearest 근처점 + New 새로 만들기 + New (metric) 새로 만들기(미터법) + New Drawing 새 도면 + New Drawing (⌘N) 새 도면(⌘N) + New Drawing... 새 도면... + New Layer 새 도면층 + New Layout 새 배치 + New VP Freeze 새 뷰포트 동결 + New Viewports... 새 뷰포트... + New from Template... 템플릿에서 새로 만들기... + New layout 새 배치 + New state name 새 상태 이름 + Next Change 다음 변경 사항 + No 아니요 + No blocks defined in this drawing. 이 도면에 정의된 블록이 없습니다. + No contributor data was built into this copy. 이 빌드에는 기여자 정보가 포함되어 있지 않습니다. + No model credits were built into this copy. 이 빌드에는 모델 크레딧이 포함되어 있지 않습니다. + No saved layer states 저장된 도면층 상태 없음 + No selection 선택 항목 없음 + Node 노드 + None 없음 + Not available 사용할 수 없음 + Not available yet 아직 사용할 수 없음 + Numbered list 번호 매기기 목록 + OK 확인 + Object 객체 + Object Snap 객체 스냅 + Object Snap (F3) 객체 스냅(F3) + Object Snap On (F3) 객체 스냅 켜기(F3) + Object Snap Tracking 객체 스냅 추적 + Object Snap Tracking (F11) 객체 스냅 추적(F11) + Object Snap Tracking Settings 객체 스냅 추적 설정 + Object type: 객체 유형: + Oblique 기울이기 + Off 끄기 + Offset 간격띄우기 + On 켜기 + On/Off 켜기/끄기 + One of the Crafting Apps by the ArtCraft team and community. ArtCraft 팀과 커뮤니티가 만드는 Crafting Apps 제품군의 하나입니다. + Only in this drawing: {n} 이 도면에만 있음: {n}개 + Only in {name}: {n} {name}에만 있음: {n}개 + Open (⌘O) 열기(⌘O) + Open Sample Drawing 예제 도면 열기 + Open sample drawing 예제 도면 열기 + Open Xref 외부 참조 열기 + Open... 열기... + Operator: 연산자: + Orbit 궤도 회전 + Ordinate 좌표 + Ortho (F8) 직교(F8) + Ortho Mode 직교 모드 + Ortho Mode (F8) 직교 모드(F8) + Osnap Settings... 객체 스냅 설정... + Out 축소 + Over-constrained 과도한 구속 + Override 재지정 + PAPER 도면 + Percent 백분율 + Polar (distance < angle) 극좌표(거리 < 각도) + PRs PR + Page Setup Manager 페이지 설정 관리자 + Page Setup Manager... 페이지 설정 관리자... + Pan 초점 이동 + Pan Down 아래로 초점 이동 + Pan Left 왼쪽으로 초점 이동 + Pan Right 오른쪽으로 초점 이동 + Pan Up 위로 초점 이동 + Paper Space 도면 공간 + Parallel 평행 + Parameters Manager 매개변수 관리자 + Parametric 파라메트릭 + Paste 붙여넣기 + Paste as Block 블록으로 붙여넣기 + Paste to Command Line 명령행에 붙여넣기 + Paste to Original Coordinates 원래 좌표로 붙여넣기 + Path Array 경로 배열 + Pattern 패턴 + Perpendicular 수직 + Plot 플롯 + Plot (click: do not plot) 플롯(클릭: 플롯 안 함) + Plot Preview 플롯 미리보기 + Plot Window 플롯 창 + Plot style 플롯 스타일 + Plot style attached to 플롯 스타일 적용 대상 + Plot style table 플롯 스타일 테이블 + Plot Style... 플롯 스타일... + Plot Style Manager 플롯 스타일 관리자 + Plot Style Manager... 플롯 스타일 관리자... + Plot Styles... 플롯 스타일... + Plotter Manager 플로터 관리자 + Plotter Manager... 플로터 관리자... + New CTB 새 CTB + New STB 새 STB + New style 새 스타일 + Reset Panel Layout 패널 배치 초기화 + Send Hatches to Back 해치를 맨 뒤로 보내기 + Send Under Objects 객체 아래로 보내기 + Text Objects Only 문자 객체만 + Use for this layout 이 배치에 사용 + Display plot styles 플롯 스타일 표시 + No plot style tables. 플롯 스타일 테이블이 없습니다. + A built-in table: editing it keeps an edited copy in the drawing. 기본 제공 테이블입니다. 편집하면 수정된 사본을 도면에 보관합니다. + Keep an editable copy of this table in the drawing 이 테이블의 편집 가능한 사본을 도면에 보관 + Layout {name}: {table} 배치 {name}: {table} + Grayscale 회색조 + Screening 출력 농도 + Use object linetype 객체 선종류 사용 + Use object lineweight 객체 선가중치 사용 + Remove 제거 + Add 추가 + Open a drawing first. 먼저 도면을 여세요. + Plot table type 플롯 테이블 유형 + Color-dependent 색상 종속 + Named 명명된 스타일 + Plus/minus 플러스/마이너스 + Point 점 + Point Filters 점 필터 + Point Style... 점 스타일... + Polar Array 원형 배열 + Polar Tracking 극좌표 추적 + Polar Tracking (F10) 극좌표 추적(F10) + Polar Tracking On (F10) 극좌표 추적 켜기(F10) + Polar angle measurement 극좌표 각도 측정 + Polar distance 극좌표 거리 + Polar: {distance} < {angle}° 극좌표: {distance} < {angle}° + PolarSnap 극좌표 스냅 + Polygon 다각형 + Polygonal 다각형 + Polygonal Viewport 다각형 뷰포트 + Polyline 폴리선 + Polyline Edit 폴리선 편집 + Position X 위치 X + Position Y 위치 Y + Position Z 위치 Z + Previous 이전 + Previous Change 이전 변경 사항 + Previous Layer 이전 도면층 + Primitives 기본 형상 + Print 인쇄 + Print... 인쇄... + Properties 특성 + Properties Inspector 특성 검사기 + Properties: 특성: + Proxy 프록시 + Purge 소거 + Pyramid 각뿔 + Quadrant 사분점 + Query Entities 객체 조회 + Quick 빠른 + Quick Dimension 빠른 치수 + Quick Properties 빠른 특성 + Quick Select 빠른 선택 + Quick Select Properties 빠른 선택 특성 + Quick Select... 빠른 선택... + QuickCalc 빠른 계산기 + QuickCalc Pick 빠른 계산기 점 지정 + Quit CADCraft CADCraft 종료 + Radius 반지름 + Raster Image 래스터 이미지 + Ray 반직선 + Read-only 읽기 전용 + Real name 실명 + Realtime 실시간 + Reassociate Dimensions 치수 재연관 + Recent 최근 + Recent Commands 최근 명령 + Recent Input 최근 입력 + Recover... 복구... + Rectangle 직사각형 + Recalculate Table Formulas 표 수식 다시 계산 + Rectangular 직사각형 + Rectangular snap 직사각형 스냅 + Rectangular Array 직사각형 배열 + Red 빨간색 + Redo 다시 실행 + Redo (⇧⌘Z) 다시 실행(Shift+⌘Z) + Redraw 다시 그리기 + Regen 재생성 + Regen All 모두 재생성 + Region 영역 + Region/Mass Properties 영역/질량 특성 + Reinitialize 다시 초기화 + Relative 상대 + Relative to last segment 마지막 선분 기준 + Remove Leader 지시선 제거 + Remove the viewport override 뷰포트 재지정 제거 + Rename Dimension Style 치수 스타일 이름 바꾸기 + Rename Layer 도면층 이름 바꾸기 + Rename Layer State 도면층 상태 이름 바꾸기 + Rename Layout 배치 이름 바꾸기 + Rename Text Style 문자 스타일 이름 바꾸기 + Rename... 이름 바꾸기... + Repeat {command} {command} 반복 + Requires a point (x,y, @dx,dy or @d 두 번째 점 지정 또는 <첫 번째 점을 변위로 사용> + Specify second source point 두 번째 원본점 지정 + Specify side to offset 간격띄우기할 쪽 지정 + Specify start angle 시작 각도 지정 + Specify start point 시작점 지정 + Specify start point of arc 호 시작점 지정 + Specify start point of polyline 폴리선 시작점 지정 + Specify start point of text 문자 시작점 지정 + Specify starting width 시작 폭 지정 + Specify starting width for next segment 다음 구간의 시작 폭 지정 + Specify tangent direction for the start point of arc 호 시작점의 접선 방향 지정 + Specify the elevation for rectangles 직사각형 고도 지정 + Specify thickness for rectangles 직사각형 두께 지정 + Specify third point on circle 원 위의 세 번째 점 지정 + Specify third point on circle: _tan to 원 위의 세 번째 점 지정: _tan 접점 + Specify third source point or 세 번째 원본점 지정 또는 <계속> + Specify through point 통과점 지정 + Specify top diameter 윗면 지름 지정 + Specify top radius or 윗면 반지름 지정 또는 + Specify total angle 전체 각도 지정 + Specify total length or 전체 길이 지정 또는 + Specify width for rectangles 직사각형 폭 지정 + Sphere 구 + Spline 스플라인 + Spline Edit 스플라인 편집 + Stack (type 1/2 then select) 분수 쌓기(1/2 입력 후 선택) + Standard 표준 + Start 시작 + Start Width 시작 폭 + Start X 시작점 X + Start Y 시작점 Y + Start angle 시작 각도 + Start, Center, Angle 시작점, 중심, 각도 + Start, Center, End 시작점, 중심, 끝점 + Start, Center, Length 시작점, 중심, 길이 + Start, End, Angle 시작점, 끝점, 각도 + Start, End, Direction 시작점, 끝점, 방향 + Start, End, Radius 시작점, 끝점, 반지름 + Status 상태 + Status Bar 상태 막대 + Stretch 신축 + Style: Standard 스타일: Standard + Subtract 차집합 + Sweep 스윕 + Switch Drawing 도면 전환 + Switch Layout 배치 전환 + Switch between model space in a viewport and paper space 뷰포트 내 모형 공간과 도면 공간 간 전환 + Symmetric 대칭 + Synchronize Attributes 속성 동기화 + To 대상 + Toggle PICKADD (off: each pick replaces the selection) PICKADD 전환(꺼짐: 선택할 때마다 기존 선택 대체) + Toggle PICKADD (on: picks add to the selection) PICKADD 전환(켜짐: 선택할 때마다 기존 선택에 추가) + TOP 평면 + Table 표 + Table Breaks 표 분할 + Table Cell Style 표 셀 스타일 + Table Style... 표 스타일... + Table style 표 스타일 + Table... 표... + Tan, Tan, Radius 접선, 접선, 반지름 + Tan, Tan, Tan 접선, 접선, 접선 + Tangent 접선 + Temporary track point 임시 추적점 + Text 문자 + Text Editor 문자 편집기 + Text Height 문자 높이 + Text Style... 문자 스타일... + Text height 문자 높이 + Text override 문자 재지정 + Text style 문자 스타일 + Thaw All Layers 모든 도면층 동결 해제 + Thaw Layer 도면층 동결 해제 + There is nothing here yet. 아직 아무것도 없습니다. + Thickness 두께 + Time 시간 + To get started, create or open a drawing. 시작하려면 도면을 만들거나 여세요. + Tool Bar 도구 막대 + Tool Sets 도구 세트 + Tools 도구 + Torus 원환체 + Trace 트레이스 + Track orthogonally only 직교 방향으로만 추적 + Track using all polar angle settings 모든 극좌표 각도 설정을 사용하여 추적 + Transparency 투명도 + Trim 자르기 + True Color 트루 컬러 + Turn All Layers On 모든 도면층 켜기 + Turn Off Layer 도면층 끄기 + Turn On Layer 도면층 켜기 + Turn Quick Properties off (QPMODE) 빠른 특성 끄기(QPMODE) + Type 유형 + Type a command 명령 입력 + Type text; Enter starts a new paragraph 문자를 입력하세요. Enter를 누르면 새 단락이 시작됩니다 + UCS Icon UCS 아이콘 + Undo 실행 취소 + Undo (⌘Z) 실행 취소(⌘Z) + Ungroup 그룹 해제 + Union 합집합 + Unisolate Layer 도면층 분리 해제 + Units Conversion 단위 변환 + Units... 단위... + Unlock Layer 도면층 잠금 해제 + Unlock viewport 뷰포트 잠금 해제 + Unmerge Cells 셀 병합 해제 + Unsaved Layer State 저장되지 않은 도면층 상태 + Up 위로 + Update Data Links 데이터 링크 업데이트 + Update 업데이트 + Use System Setting 시스템 설정 사용 + User 사용자 + User Parameters 사용자 매개변수 + Username 사용자 이름 + Volume 체적 + VP Color 뷰포트 색상 + VP Freeze 뷰포트 동결 + Value 값 + Value: 값: + Variables 변수 + Version 버전 + Version 2000 버전 2000 + Version 2004 버전 2004 + Version 2007 버전 2007 + Version 2010 버전 2010 + Version 2013 버전 2013 + Version 2018 버전 2018 + Version {version} 버전 {version} + Vertical 수직 + VerticalDistance 수직 거리 + View 뷰 + ViewCube 뷰 큐브 + Viewport 뷰포트 + Viewport Layer Freeze 뷰포트 도면층 동결 + Viewport Properties 뷰포트 특성 + Viewport is locked 뷰포트가 잠겨 있습니다 + Viewport scale 뷰포트 축척 + Viewport {id} 뷰포트 {id} + Viewports 뷰포트 + W 서 + WCS ⌄ WCS 선택 + White 흰색 + Width factor 폭 비율 + Window 창 + Wipeout 가리기 + Workspace Switching 작업공간 전환 + Write Block 블록 쓰기 + Xline 구성선 + Yellow 노란색 + Yes 예 + Zoom 줌 + Zoom All 전체 줌 + Zoom Dynamic 동적 줌 + Zoom Extents 범위 줌 + Zoom In 확대 + Zoom Object 객체 줌 + Zoom Out 축소 + Zoom Previous 이전 줌 + Zoom Realtime 실시간 줌 + Zoom Window 창 줌 + error 오류 + {contributors} · {commits} {contributors} · {commits} + {kind} (constraint {id}) {kind}(구속조건 {id}) + {kind} constraint {name} = {expression} (double-click to edit) {kind} 구속조건 {name} = {expression} (두 번 클릭하여 편집) + {name}: {distance} < {angle}° {name}: {distance} < {angle}° + ƒx New User Parameter ƒx 새 사용자 매개변수 + ΔLOC ΔLOC + −Bin −Bin + −LOC −LOC + ⌘↩ to finish ⌘↩로 완료 +@plural {n} commit 커밋 {n}개 +@plural {n} contributor 기여자 {n}명 + Drawing as bytes 도면 바이트 데이터 + Services 서비스 + Hide CADCraft CADCraft 숨기기 + Hide Others 다른 앱 숨기기 + Accessibility 접근성 + Show System Cursor 시스템 커서 표시 + Import... 가져오기... + Export... 내보내기... + Batch Publish... 일괄 게시... + Scale List... 축척 목록... + Annotative Object Scale 주석 객체 축척 + Add Current Scale 현재 축척 추가 + Delete Current Scale 현재 축척 삭제 + Add/Delete Scales... 축척 추가/삭제... + Synchronize Multiple-scale Positions 여러 축척의 위치 동기화 + Annotative Update 주석 업데이트 + Show Annotative Objects 주석 객체 표시 + Current Scale Only 현재 축척만 + All Scales 모든 축척 + Drawing Recovery Manager... 도면 복구 관리자... + Tolerance... 공차... + Add Vertex 정점 추가 + Remove Vertex 정점 제거 + Convert to Arc 호로 변환 + Convert to Line 선으로 변환 + Stretch Vertex 정점 신축 + Edit Array 배열 편집 + Close Array Editing 배열 편집 종료 + Multiline... 다중선... + Multiline Style... 다중선 스타일... + Solid Editing 솔리드 편집 + 3D Mouse... 3D 마우스... + 3D Mouse 3D 마우스 + Calibrate 3D Mouse 3D 마우스 보정 + 3D Mouse Navigation 3D 마우스 탐색 + 3D mouse input is unavailable on this platform 이 플랫폼에서는 3D 마우스 입력을 사용할 수 없습니다 + No supported 3D mouse found 지원되는 3D 마우스를 찾을 수 없습니다 + Release the 3D mouse to calibrate its resting position 3D 마우스에서 손을 떼어 중립 위치를 보정하세요 + 3D mouse connected 3D 마우스 연결됨 + 3D mouse busy, possibly 3DxWare or missing device permissions 3D 마우스를 사용할 수 없습니다. 3DxWare가 사용 중이거나 장치 권한이 없을 수 있습니다 + 3D mouse disconnected 3D 마우스 연결 끊김 + 3D mouse input could not be started 3D 마우스 입력을 시작하지 못했습니다 + Enable 3D mouse navigation 3D 마우스 탐색 사용 + Pan sensitivity 초점 이동 감도 + Zoom sensitivity 줌 감도 + Reverse zoom direction 줌 방향 반전 + Calibrate 보정 + Move sideways to pan; push/pull or twist to zoom. 옆으로 움직이면 초점이 이동하고, 밀거나 당기거나 비틀면 확대 또는 축소됩니다. + Left device button: zoom extents. Right device button: calibrate. 장치 왼쪽 버튼: 범위 줌. 장치 오른쪽 버튼: 보정. + More toolbar commands 도구 막대 명령 더 보기 + Default save version 기본 저장 버전 + Automatic save every 자동 저장 간격 + min 분 + Automatic save file location 자동 저장 파일 위치 + Drawings with no file yet are saved in the default version. 0 minutes turns automatic save off. 아직 파일로 저장하지 않은 도면은 기본 버전으로 저장됩니다. 0분으로 설정하면 자동 저장이 꺼집니다. + Time-sensitive right-click: a quick click is Enter, holding opens the menu 오른쪽 클릭 시간에 따라 동작 구분: 짧게 클릭하면 Enter, 길게 누르면 메뉴 열기 + Turned off, a right-click while a command runs always opens the shortcut menu. 끄면 명령 실행 중 오른쪽 클릭 시 항상 바로 가기 메뉴가 열립니다. + Hatch Pattern 해치 패턴 + Search for pattern 패턴 검색 + Run Script... 스크립트 실행... + X X + Y Y + B B + CADCraft CADCraft +menu View 보기 +menu Draw 그리기 +menu Dimension 치수 +menu Modify 수정 diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index 983de93..35a5f1e 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -33,10 +33,12 @@ pub enum Preference { Chinese, #[serde(rename = "it")] Italian, + #[serde(rename = "ko")] + Korean, } impl Preference { - pub const ALL: [Self; 11] = [ + pub const ALL: [Self; 12] = [ Self::Auto, Self::English, Self::Ukrainian, @@ -48,6 +50,7 @@ impl Preference { Self::French, Self::Chinese, Self::Italian, + Self::Korean, ]; /// The codes a preference can be given as, for messages ("auto, en or uk"). @@ -72,6 +75,7 @@ impl Preference { Self::French => "fr", Self::Chinese => "zh", Self::Italian => "it", + Self::Korean => "ko", } } @@ -88,6 +92,13 @@ impl Preference { Self::French => "Français", Self::Chinese => "简体中文", Self::Italian => "Italiano", + Self::Korean => { + if crate::theme::shows_hangul(CURRENT.with(Cell::get)) { + "한국어" + } else { + "Korean" + } + } } } @@ -112,6 +123,7 @@ impl Preference { Self::French => "fr", Self::Chinese => "zh", Self::Italian => "it", + Self::Korean => "ko", Self::Auto => locales.iter().find_map(|s| locale_language(s)).unwrap_or("en"), } } @@ -128,7 +140,7 @@ pub struct Language { catalog: OnceLock>, } -pub static LANGUAGES: [Language; 10] = [ +pub static LANGUAGES: [Language; 11] = [ Language { code: "en", name: "English", source: "", plural: plural_en, plural_forms: 2, catalog: OnceLock::new() }, Language { code: "uk", name: "Українська", source: include_str!("uk.tsv"), plural: plural_uk, plural_forms: 3, catalog: OnceLock::new() @@ -155,6 +167,8 @@ pub static LANGUAGES: [Language; 10] = [ Language { code: "zh", name: "简体中文", source: include_str!("zh.tsv"), plural: plural_en, plural_forms: 2, catalog: OnceLock::new() }, // Italian integer plurals (CLDR `it`) follow the English rule: one (1), other. Language { code: "it", name: "Italiano", source: include_str!("it.tsv"), plural: plural_en, plural_forms: 2, catalog: OnceLock::new() }, + // Korean has one integer plural form and needs a Hangul-capable interface font. + Language { code: "ko", name: "한국어", source: include_str!("ko.tsv"), plural: plural_ko, plural_forms: 1, catalog: OnceLock::new() }, ]; /// BCP 47 and POSIX locale tags, including `uk-UA`, `uk_UA.UTF-8` and `pt_BR.UTF-8`. @@ -266,6 +280,11 @@ pub fn plural_pt(n: u64) -> usize { usize::from(n > 1) } +/// Korean uses the same count form for every integer. +fn plural_ko(_n: u64) -> usize { + 0 +} + fn plural_en(n: u64) -> usize { usize::from(n != 1) } diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index 6c46147..71c2bff 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -39,7 +39,7 @@ fn catalog_is_valid_and_preserves_placeholders_and_ellipses() { /// to English for what they don't cover yet; their gaps are reported, not enforced, so adding a /// command doesn't require a row in every language. const REQUIRED: &[&str] = &["uk"]; -const REPORTED: &[&str] = &["pt-BR", "de", "es", "cs", "ru", "fr", "zh", "it"]; +const REPORTED: &[&str] = &["pt-BR", "de", "es", "cs", "ru", "fr", "zh", "it", "ko"]; fn check(language: &str, what: &str, missing: Vec) { if REQUIRED.contains(&language) { @@ -602,3 +602,180 @@ fn chinese_needs_an_installed_cjk_font() { fn hatch_dialog_extents_label_says_extents_in_ukrainian() { assert_eq!(tr("uk", "Default to boundary extents"), "Типово за габаритами контуру"); } + +#[test] +fn korean_representative_translations_remain_available() { + let ko = catalog("ko").unwrap(); + // Fixed regression examples; new source strings remain reported, not required for Korean. + assert!(REPORTED.contains(&"ko")); + for (context, source, expected) in [ + ("", "File", "파일"), + ("menu", "View", "보기"), + ("menu", "Draw", "그리기"), + ("menu", "Dimension", "치수"), + ("menu", "Modify", "수정"), + ("", "Options...", "옵션..."), + ("", "User Preferences", "사용자 기본 설정"), + ("", "Interface Language", "인터페이스 언어"), + ("", "Properties", "특성"), + ("", "Degree", "도 기호"), + ("", "Style: Standard", "스타일: Standard"), + ("", "WCS ⌄", "WCS 선택"), + ("@plural", "{n} contributor", "기여자 {n}명"), + ("@plural", "{n} commit", "커밋 {n}개"), + ] { + assert_eq!(ko.get(&(context, source)).copied(), Some(expected), "{context}: {source}"); + } + assert_eq!(tr("ko", "future untranslated label"), "future untranslated label"); +} + +#[test] +fn korean_locale_saved_preference_and_single_count_form() { + for tag in ["ko", "ko-KR", "ko_KR.UTF-8", "KO-kr", " ko-KR ", "ko_KR@euro"] { + assert_eq!(locale_language(tag), Some("ko"), "{tag}"); + } + for tag in ["kr", "korean", "kotlin"] { + assert_eq!(locale_language(tag), None, "{tag}"); + } + assert_eq!(Preference::parse("ko"), Some(Preference::Korean)); + assert_eq!(Preference::Korean.resolve(&["en-US".into()]), "ko"); + assert_eq!(Preference::Auto.resolve(&["pl-PL".into(), "ko-KR".into(), "en-US".into()]), "ko"); + let mut app = CadApp::new(Session::new(), Services::default()); + app.system_languages = vec!["ko-KR".into()]; + app.run("ui.language", json!({"lang": "ko"})).unwrap(); + let mut restored = CadApp::new(Session::new(), Services::default()); + restored.system_languages = vec!["en-US".into()]; + restored.load_prefs(&app.prefs_json()); + assert_eq!(restored.ui.interface_language, Preference::Korean); + assert_eq!(restored.language(), app.language()); + with_language("ko", || { + for n in [0, 1, 2, 5, 11, 21, 101, u64::MAX] { + assert_eq!(plural_ko(n), 0); + assert_eq!(count(n, "{n} contributor", "{n} contributors"), format!("기여자 {n}명")); + } + assert_eq!(format("Current: {color}", &[("color", "{n} Open".into())]), "현재: {n} Open"); + }); +} + +#[test] +fn korean_live_switch_keeps_automation_and_drawing_and_falls_back_without_font() { + let mut app = CadApp::new(Session::new(), Services::default()); + app.ui.in_window_menu = true; + app.session.state_mut().unwrap().title = "Open".into(); + let ctx = egui::Context::default(); + app.logic(&ctx); + assert!(render(&mut app, &ctx).contains("Properties")); + app.start("line"); + let prompt = crate::control::cmdline_state(&app); + let commands = crate::control::all_commands(&app); + let result = app.run("ui.language", json!({"lang": "ko"})).unwrap(); + let supported = crate::theme::can_show("ko"); + assert_eq!(result["shown"], if supported { "ko" } else { "en" }); + assert_eq!(app.ui.interface_language, Preference::Korean); + app.logic(&ctx); + assert_eq!(app.fonts_language, Some(app.language())); + let text = render(&mut app, &ctx); + if supported { + for label in ["특성", "파일", "첫 번째 점 지정"] { + assert!(text.contains(label), "missing {label}: {text}"); + } + } else { + assert!(text.contains("Properties")); + assert!(app.status.as_ref().is_some_and(|(s, _)| s.contains("No font covers the Korean interface"))); + assert_eq!(Preference::Korean.name(), "Korean"); + } + assert_eq!(app.session.state().unwrap().title, "Open"); + assert_eq!(crate::control::cmdline_state(&app), prompt); + assert_eq!(crate::control::all_commands(&app), commands); + assert_eq!(t("Properties"), "Properties"); + assert!(app.run("ui.language", json!({"lang": "bad"})).is_err()); + app.run("ui.resetpalettes", json!({})).unwrap(); + assert_eq!(app.ui.interface_language, Preference::Korean); + // The general ui.set route uses the same saved preference and font installation. + let (request, _) = crate::ControlRequest::new("ui.set", json!({"interfaceLanguage": "zh"})); + let _ = crate::control::handle(&mut app, &ctx, &request); + assert_eq!(app.ui.interface_language, Preference::Chinese); + app.logic(&ctx); + assert_eq!(app.fonts_language, Some(if crate::theme::can_show("zh") { "zh" } else { "en" })); + render(&mut app, &ctx); + let (request, _) = crate::ControlRequest::new("ui.set", json!({"interfaceLanguage": "ko"})); + let _ = crate::control::handle(&mut app, &ctx, &request); + assert_eq!(app.ui.interface_language, Preference::Korean); + app.logic(&ctx); + assert_eq!(app.fonts_language, Some(app.language())); + let text = render(&mut app, &ctx); + assert!(text.contains(if supported { "특성" } else { "Properties" })); + let (request, _) = crate::ControlRequest::new("ui.set", json!({"interfaceLanguage": "en"})); + let _ = crate::control::handle(&mut app, &ctx, &request); + app.logic(&ctx); + assert_eq!(app.fonts_language, Some("en")); + assert_eq!(app.ui.interface_language, Preference::English); + assert!(render(&mut app, &ctx).contains("Properties")); + assert_eq!(crate::menus::tree(&app).first().unwrap().0, "File"); +} + +#[test] +fn korean_dialogs_keep_source_ids() { + if !crate::theme::can_show("ko") { + return; // The fallback path is covered above; do not claim glyph rendering here. + } + for (dialog, source) in [ + ("dsettings", "Drafting Settings"), + ("about", "About CADCraft"), + ("commands", "Command Reference"), + ("blocks", "Blocks"), + ("layers", "Layer Properties Manager"), + ("qselect", "Quick Select"), + ("parameters", "Parameters Manager"), + ("language", "Interface Language"), + ("options:preferences", "User Preferences"), + ] { + let mut app = CadApp::new(Session::new(), Services::default()); + app.ui.interface_language = Preference::Korean; + app.ui.dialog = Some(dialog.into()); + let ctx = egui::Context::default(); + app.logic(&ctx); + let text = render(&mut app, &ctx); + assert!(text.contains(tr("ko", source)), "{dialog}: {text}"); + if dialog == "language" { + assert!(text.contains("한국어"), "{dialog}: {text}"); + } + assert_eq!(app.ui.dialog.as_deref(), Some(dialog)); + } +} + +#[test] +fn korean_ui_fonts_cover_catalog_and_language_picker_in_both_families() { + if !crate::theme::can_show("ko") { + return; + } + let letters: BTreeSet<_> = include_str!("ko.tsv") + .lines() + .filter_map(|line| line.split('\t').nth(2)) + .flat_map(str::chars) + .chain("한국어".chars()) + .filter(|c| !c.is_ascii()) + .collect(); + // The picker shows 한국어 in an English interface only when the CJK fonts loaded in every + // language cover Hangul; the Korean interface itself always does, with a fallback font. + let ctx = egui::Context::default(); + let english = crate::theme::cjk_covers_hangul(); + assert_eq!(with_language("en", || Preference::Korean.name()), if english { "한국어" } else { "Korean" }); + assert_eq!(with_language("ko", || Preference::Korean.name()), "한국어"); + crate::theme::install_fonts(&ctx, if english { "en" } else { "ko" }); + let mut output = ctx.run_ui(Default::default(), |_| {}); + output.textures_delta.clear(); + let missing = ctx.fonts_mut(|view| { + let mut missing = Vec::new(); + for family in [egui::FontFamily::Proportional, egui::FontFamily::Monospace] { + let mut font = view.fonts.font(&family); + for c in &letters { + if !font.characters().contains_key(c) { + missing.push(format!("{family:?}: {c} (U+{:04X})", u32::from(*c))); + } + } + } + missing + }); + assert!(missing.is_empty(), "missing Korean UI glyphs: {}", missing.join(", ")); +} diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 7e52f53..40e2036 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -216,8 +216,8 @@ pub struct CadApp { /// Caps the frame rate when presenting without vsync (see [`gpu::FrameCap`]). #[cfg(not(target_arch = "wasm32"))] pub frame_cap: Option, - /// Interface fonts installed, and whether with a CJK font (`None` until the first frame). - fonts_cjk: Option, + /// Language whose interface fonts are installed (`None` until the first frame). + fonts_language: Option<&'static str>, /// The theme last installed into egui (`None` until the first frame). shown_theme: Option, /// Whether the last applied theme was handed to egui as the System preference. @@ -267,7 +267,7 @@ impl CadApp { synthetic: Vec::new(), #[cfg(not(target_arch = "wasm32"))] frame_cap: None, - fonts_cjk: None, + fonts_language: None, shown_theme: None, shown_follows_system: false, shown_animations: None, @@ -285,7 +285,7 @@ impl CadApp { } /// The interface language shown: the chosen one, or English when this computer has no font - /// for it (Chinese without an installed CJK font would be boxes). + /// for it (Chinese or Korean without a suitable font would be boxes). pub fn language(&self) -> &'static str { let l = self.ui.interface_language.resolve(&self.system_languages); if theme::can_show(l) { l } else { "en" } @@ -535,10 +535,10 @@ impl CadApp { if ctx.input(|i| i.viewport().close_requested()) && self.request_quit() { ctx.send_viewport_cmd(egui::ViewportCommand::CancelClose); } - let cjk = self.language().starts_with("zh"); - if self.fonts_cjk != Some(cjk) { - theme::install_fonts(ctx, cjk); - self.fonts_cjk = Some(cjk); + let language = self.language(); + if self.fonts_language != Some(language) { + theme::install_fonts(ctx, language); + self.fonts_language = Some(language); } // Resolved every frame, so a System choice follows the OS appearance live (winit and the // browser report changes as input); the saved choice itself never changes here. diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index eb5ba6a..9672270 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -137,9 +137,11 @@ fn run_ui_command_inner(app: &mut CadApp, id: &str, params: &Value) -> Option FontId { /// Load a system UI font at run time when one is installed (not bundled), else keep egui's; /// then CJK fonts after the Latin ones, so Latin text keeps its look, in every interface /// language: file, layout and layer names can be Japanese or Chinese whatever the interface -/// shows (#513). With `chinese` (a Chinese interface) the Chinese font comes first, else a +/// shows (#513). In a Chinese interface (`language` "zh…") the Chinese font comes first, else a /// Japanese one, so names take their own glyph forms. A font embedded from craft-fonts comes -/// last, for systems without any (and the web build, which has no system fonts). -pub fn install_fonts(ctx: &egui::Context, chinese: bool) { +/// last, for systems without any (and the web build, which has no system fonts). A Korean +/// interface adds a Hangul font only when the CJK font found doesn't cover Hangul +/// ([`korean_font`]). +pub fn install_fonts(ctx: &egui::Context, language: &str) { let mut fonts = egui::FontDefinitions::default(); let mut added: Vec<(String, std::sync::Arc)> = Vec::new(); - #[cfg(target_arch = "wasm32")] - let _ = chinese; + #[cfg(not(target_arch = "wasm32"))] + let chinese = language.starts_with("zh"); #[cfg(not(target_arch = "wasm32"))] { let candidates: &[&str] = if cfg!(target_os = "macos") { @@ -357,6 +359,15 @@ pub fn install_fonts(ctx: &egui::Context, chinese: bool) { // A sans-serif face reads best in the interface. embedded.sort_by_key(|f| !f.family.contains("Gothic") && !f.family.contains("Sans")); added.extend(embedded.first().map(|f| (format!("craft-{}", f.stem()), std::sync::Arc::new(egui::FontData::from_static(f.bytes))))); + // Korean: Noto Sans CJK and most CJK fonts above already cover Hangul; else a Hangul font. + if language == "ko" + && !cjk_covers_hangul() + && let Some(face) = korean_font() + { + let mut data = egui::FontData::from_owned(face.bytes.as_ref().clone()); + data.index = face.index; + added.push(("korean-ui".into(), std::sync::Arc::new(data))); + } for (name, data) in added { fonts.font_data.insert(name.clone(), data); for family in [FontFamily::Proportional, FontFamily::Monospace] { @@ -368,17 +379,21 @@ pub fn install_fonts(ctx: &egui::Context, chinese: bool) { ctx.set_fonts(fonts); } -/// Largest interface font file read (CJK collections are 15–60 MB). -#[cfg(not(target_arch = "wasm32"))] +/// Largest interface font file read or embedded face copied (CJK collections are 15–60 MB). const MAX_UI_FONT_BYTES: u64 = 96 * 1024 * 1024; #[cfg(not(target_arch = "wasm32"))] fn read_capped(path: &std::path::Path, max: u64) -> Option> { - let len = std::fs::metadata(path).ok()?.len(); + use std::io::Read as _; + let file = std::fs::File::open(path).ok()?; + let len = file.metadata().ok()?.len(); if len == 0 || len > max { return None; } - std::fs::read(path).ok() + // Enforce the cap during the read too, even if the file grows after metadata was read. + let mut bytes = Vec::new(); + file.take(max.saturating_add(1)).read_to_end(&mut bytes).ok()?; + (!bytes.is_empty() && bytes.len() as u64 <= max).then_some(bytes) } /// An installed font with Chinese characters, for the Chinese interface; fonts are never bundled @@ -442,9 +457,122 @@ fn japanese_font() -> Option<&'static std::path::Path> { None } -/// Whether the interface can show `language`: Chinese needs an installed CJK font. +/// The Hangul letters the Korean interface shows: its catalog and its own name. +fn hangul_letters() -> &'static std::collections::BTreeSet { + static LETTERS: std::sync::OnceLock> = std::sync::OnceLock::new(); + LETTERS.get_or_init(|| { + include_str!("i18n/ko.tsv") + .lines() + .filter_map(|line| line.split('\t').nth(2)) + .flat_map(str::chars) + .chain("한국어".chars()) + .filter(|c| matches!(c, '\u{1100}'..='\u{11ff}' | '\u{3130}'..='\u{318f}' | '\u{a960}'..='\u{a97f}' | '\u{ac00}'..='\u{d7ff}')) + .collect() + }) +} + +/// Whether `face` has every Hangul letter the Korean interface shows (rejects malformed fonts +/// and Chinese- or Japanese-only faces). +fn covers_hangul(face: &cadcraft_fonts::ttf::Face) -> bool { + hangul_letters().iter().all(|c| face.has_glyph(*c)) +} + +/// The first face of a font file (the one egui uses) when it covers the Korean interface. +fn korean_face(bytes: std::sync::Arc>) -> Option { + cadcraft_fonts::ttf::faces(&bytes).into_iter().find(covers_hangul) +} + +/// Whether the CJK fonts [`install_fonts`] loads in every language already show Hangul (Noto Sans +/// CJK, Microsoft YaHei, PingFang do). Only asked about Korean, so other interfaces never read the +/// font twice. Checked once. +pub fn cjk_covers_hangul() -> bool { + static COVERS: std::sync::OnceLock = std::sync::OnceLock::new(); + *COVERS.get_or_init(|| { + #[cfg(not(target_arch = "wasm32"))] + { + // egui uses a collection's first face, and each letter from the first font with it. + let first_covers = |p: &std::path::Path| { + read_capped(p, MAX_UI_FONT_BYTES) + .and_then(|bytes| cadcraft_fonts::ttf::faces(&std::sync::Arc::new(bytes)).into_iter().next()) + .is_some_and(|face| covers_hangul(&face)) + }; + if cjk_font().is_some_and(first_covers) { + return true; + } + } + cadcraft_fonts::CRAFT_FONTS + .iter() + .filter(|f| f.style == "Regular" && f.scripts.iter().any(|s| matches!(*s, "Jpan" | "Hans" | "Hant"))) + .any(|f| cadcraft_fonts::ttf::faces(&std::sync::Arc::new(f.bytes.to_vec())).first().is_some_and(covers_hangul)) + }) +} + +/// A Hangul font for the Korean interface when the CJK fonts don't cover it: a Korean face from +/// the optional craft-fonts input, else one installed where the OS or common packages put it. +/// Nothing is bundled or downloaded; without one the interface stays English. Only looked for +/// when Korean is asked about. Checked once; collections are scanned with the fonts crate's +/// face-count cap and reads are capped. +pub fn korean_font() -> Option<&'static cadcraft_fonts::ttf::Face> { + static FOUND: std::sync::OnceLock> = std::sync::OnceLock::new(); + FOUND + .get_or_init(|| { + for font in cadcraft_fonts::CRAFT_FONTS.iter().filter(|f| f.scripts.iter().any(|s| matches!(*s, "Hang" | "Kore"))) { + if font.bytes.len() as u64 > MAX_UI_FONT_BYTES { + continue; + } + if let Some(face) = korean_face(std::sync::Arc::new(font.bytes.to_vec())) { + return Some(face); + } + } + #[cfg(not(target_arch = "wasm32"))] + { + let candidates: &[&str] = if cfg!(target_os = "macos") { + &[ + "/System/Library/Fonts/AppleSDGothicNeo.ttc", + "/System/Library/Fonts/Supplemental/AppleGothic.ttf", + "/Library/Fonts/NanumGothic.ttf", + ] + } else if cfg!(windows) { + &["C:\\Windows\\Fonts\\malgun.ttf", "C:\\Windows\\Fonts\\gulim.ttc"] + } else { + &[ + "/usr/share/fonts/truetype/nanum/NanumGothic.ttf", + "/usr/share/fonts/nanum/NanumGothic.ttf", + "/usr/share/fonts/opentype/noto/NotoSansCJKkr-Regular.otf", + "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc", + "/usr/local/share/fonts/nanum/NanumGothic.ttf", + "/usr/local/share/fonts/noto/NotoSansCJK-Regular.ttc", + ] + }; + for path in candidates { + if let Some(face) = + read_capped(std::path::Path::new(path), MAX_UI_FONT_BYTES).and_then(|bytes| korean_face(std::sync::Arc::new(bytes))) + { + return Some(face); + } + } + } + None + }) + .as_ref() +} + +/// Whether the interface in `language` shows Hangul (the Korean name in the language picker): +/// in every language when the CJK fonts cover it, in a Korean interface with [`korean_font`]. +pub fn shows_hangul(language: &str) -> bool { + cjk_covers_hangul() || (language == "ko" && korean_font().is_some()) +} + +/// Whether the interface can show `language` without missing-glyph boxes: Chinese needs an +/// installed CJK font, Korean a font with Hangul. pub fn can_show(language: &str) -> bool { - !language.starts_with("zh") || cjk_font().is_some() + match language { + "ko" => shows_hangul("ko"), + l if l.starts_with("zh") => cjk_font().is_some(), + _ => true, + } } /// egui's visuals for `theme`, from its tokens. @@ -498,3 +626,13 @@ pub fn apply(ctx: &egui::Context, theme: Theme, follow_system: bool) { s.text_styles.insert(egui::TextStyle::Monospace, mono()); }); } + +#[cfg(test)] +mod tests { + #[test] + fn korean_font_rejects_invalid_or_glyphless_files() { + for bytes in [vec![], vec![0xff; 32], vec![0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] { + assert!(super::korean_face(std::sync::Arc::new(bytes)).is_none()); + } + } +} diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 2efcacf..50c07d8 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -152,7 +152,10 @@ Select a language with `engine.execute` and the UI command `ui.language`: {"id":1,"method":"engine.execute","params":{"command":"ui.language","params":{"lang":"uk"}}} ``` -Supported values are `auto`, `en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru`, `fr`, `zh` and `it` (Chinese needs an installed CJK font; without one the result's `shown` is `en`). `ui.set` also accepts +Supported values are `auto`, `en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru`, `fr`, `zh`, `it` and `ko`. +Chinese needs an installed CJK font; Korean needs a font covering the Korean interface from the +optional craft-fonts input or the operating system. Without a suitable font, the saved choice is +retained and the result's `shown` is `en`. `ui.set` also accepts `{"interfaceLanguage":"uk"}`; `ui.inspect` reports the saved choice in its UI state. Invalid choices leave the current language unchanged. Menus, command labels and prompt text returned by the control channel retain their English source text. See [localization.md](localization.md). diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 291fa95..9fd2371 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -1,6 +1,6 @@ # Localization parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (Czech catalog added: reference/literal coverage, Czech plurals and menu headings) · **Target:** Autodesk AutoCAD 2027 for Mac (26.0) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (complete current Korean catalog; locale/preferences/live-switch support and Hangul font coverage) · **Target:** Autodesk AutoCAD 2027 for Mac (26.0) How far CADCraft's interface and drawing text are from AutoCAD's languages. Summarised in [ROADMAP.md](../ROADMAP.md#languages); work items in [gaps.md](gaps.md#localization). @@ -62,16 +62,16 @@ plus native review by a human; extending the catalog to the remaining English-on | Indonesian | id | 0 (0%) | 0% | Latin: yes | no | none | 3–5 h | | Japanese | ja | 0 (0%) | 0% | CJK glyphs render; IME pending; no vertical text | no | none | 4–6 h | | German | de | 989 catalog rows (≈ 100% of the catalog; ≈ 80% of all UI strings, estimated: engine diagnostics and uncatalogued text stay English) | dialogs and tooltips yes; command reference searchable in German | Latin: yes (bundled egui fonts cover ä ö ü Ä Ö Ü ß; long strings will need layout checks) | no native review | partial | 1–2 h | -| Korean | ko | 0 (0%) | 0% | Hangul renders; macOS Korean IME fixes pending (#118, #119) | no | none | 4–6 h | +| Korean | ko | 1,235 rows (1,229 ordinary, 4 menu, 2 count); full existing catalog union and marked UI coverage | dialogs, tooltips, Options and translated command-reference labels | UI requires catalog-covering installed or optional craft-fonts Hang/Kore face; English fallback without one; IME fixes still pending | original AI-assisted translation; independent native proofreading pending | complete current catalog; English-only interface gaps remain | native review + platform layout/IME validation | | Vietnamese | vi | 0 (0%) | 0% | Latin with stacked diacritics: renders per code point, untested | no | none | 3–5 h | | Ukrainian | uk | 989 catalog rows (≈ 100% of the catalog; ≈ 80% of all UI strings, estimated: engine diagnostics and uncatalogued text stay English) | dialogs and tooltips yes; command reference searchable in Ukrainian | Cyrillic: yes (bundled egui fonts) | translated by a contributor; no second review | partial | 2–4 h | | Czech | cs | 1,202 catalog rows; reference catalog and marked UI literals covered | dialogs and tooltips yes; contextual menu headings; command reference searchable in Czech | Latin: yes (bundled egui proportional and monospace fonts; Czech letters and quotes tested) | AI-assisted by @Losminek; independent review pass; not yet proofread by a native speaker | complete current catalog; English-only interface gaps remain | native review + shared English-only UI work | | Italian | it | 1,227 catalog rows; reference catalog and marked UI literals covered | dialogs and tooltips yes; contextual menu headings (Visualizza / Vista, Edita / Modifica); command reference searchable in Italian | Latin: yes (bundled egui fonts; accented letters tested) | AI-assisted by Emanuele Bottelli (Claude), written from the English strings with Italian desktop-CAD vocabulary; no native proofreading pass yet | complete current catalog; English-only interface gaps remain | native review + shared English-only UI work | -Other languages shipped: Ukrainian, Brazilian Portuguese, German, Czech, Russian, French and Italian. Czech and +Other languages shipped: Ukrainian, Brazilian Portuguese, German, Czech, Russian, French, Italian and Korean. Czech and Brazilian Portuguese cover the reference catalog and every marked UI literal; engine diagnostics and uncatalogued text remain English. AutoCAD for Mac ships 7 -non-English languages (zh-Hans, es, fr, ja, de, ko, it); of those, CADCraft now ships German, +non-English languages (zh-Hans, es, fr, ja, de, ko, it); of those, CADCraft now ships German and Korean, and additionally Ukrainian, which AutoCAD for Mac doesn't ship. **Total to put all twelve at `full`:** about 55–95 Opus 5.5 hours (catalog extension 4–6 h, @@ -82,6 +82,7 @@ each language, which needs humans. Translations parallelise fully once the catal | Date | Change | Summary | |---|---|---| +| 2026-10-13 | minor | Korean added: full 1,235-row existing catalog union, single count form, locale/persistence/live-switch tests, and validated Hangul UI fonts; native review and platform verification pending | | 2026-10-12 | minor | Italian added: 1,227 rows, one/other plurals, two `menu` headings, locale/preference/plural/font tests; AI-assisted, native proofreading pending | | 2026-10-12 | minor | Brazilian Portuguese catalog completed by @RicSchonfelder: 238 rows added (1,231 total), now covering the reference catalog and every marked UI literal, gaps 0; bundled-font glyph test for the catalog's accented letters | | 2026-10-11 | minor | Czech added: 1,202 rows, one/few/other plurals, locale/preferences/menu/font/render tests; independent review pass; native proofreading pending | diff --git a/docs/localization.md b/docs/localization.md index f4f7cb8..4dc2e61 100644 --- a/docs/localization.md +++ b/docs/localization.md @@ -1,7 +1,7 @@ # Interface languages Choose **Edit → Interface Language** and select **Auto (system language)**, **English**, -**Українська**, **Português (Brasil)**, **Deutsch**, **Español**, **Čeština** (Czech), **Русский**, **Français**, **简体中文**, or **Italiano**. +**Українська**, **Português (Brasil)**, **Deutsch**, **Español**, **Čeština** (Czech), **Русский**, **Français**, **简体中文**, **Italiano**, or **한국어** (Korean). The interface updates without restarting, including the native macOS menus. The choice is saved with the other interface preferences (`cadcraft.prefs`, beside the theme): eframe storage on the desktop, browser local storage on the web. Installations without a saved @@ -10,7 +10,7 @@ choice use Auto. Auto uses the first supported language in the operating system's preferred locales, or the browser's `navigator.languages` on the web. BCP 47 and POSIX tags select their primary language: `uk-UA` → `uk`, `de_AT.UTF-8` → `de`, `es-MX` → `es`, `pt_BR.UTF-8` → `pt-BR`, -`cs-CZ` and `cs_CZ.UTF-8` → `cs`, `it-IT` and `it_CH.UTF-8` → `it`. Unsupported languages fall back to English. +`cs-CZ` and `cs_CZ.UTF-8` → `cs`, `it-IT` and `it_CH.UTF-8` → `it`, `ko-KR` and `ko_KR.UTF-8` → `ko`. Unsupported languages fall back to English. Set `CADCRAFT_LOCALE=cs-CZ` to override the desktop's locale for a launch. An explicit saved language choice takes precedence over that override. @@ -21,7 +21,7 @@ Automation can select the language through the existing control channel: ``` The `ui.set` method also accepts `{"interfaceLanguage":"cs"}`. Supported values are `auto`, -`en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru`, `fr`, `zh`, and `it`; invalid values are rejected without changing the current +`en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru`, `fr`, `zh`, `it`, and `ko`; invalid values are rejected without changing the current selection. Resetting palettes keeps the language choice. ## Translation coverage @@ -38,6 +38,13 @@ labels: **Zobrazení / Pohled**, **Kreslení / Kreslit**, **Kótování / Kóta* The translation is AI-assisted work by @Losminek, reviewed against Czech desktop and CAD UI conventions and an independent review pass; it has not yet been proofread by a native speaker. +Korean has **1,235 rows**: 1,229 ordinary labels, four contextual menu headings, and +two single-form count templates. It covers the union of the existing catalogs, including +the Ukrainian reference, the current Czech catalog, Options, prompts, dialogs, and tooltips. +The translations are original, AI-assisted Korean CAD terminology (for example, 도면층, +치수, 모깎기, 모따기, 간격띄우기 and 객체 스냅). Independent native proofreading and +platform-specific layout/IME validation remain follow-up work. + Brazilian Portuguese has 1,231 catalog rows and also covers the reference catalog and every marked UI literal: commands, menus, tool groups and tooltips, object snaps, drafting prompts, dialogs and Preferences; its gaps are now zero. Its plural rule is `one|other` (0 and 1 are `one`), as CLDR defines @@ -56,6 +63,20 @@ The bundled egui fonts cover Ukrainian (`Ґґ Єє Іі Її`), German (`ÄÖÜ (`ÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ áčďéěíňóřšťúůýž`, including typographic quotes `„…“`). The translations add no font assets and work on the web without system fonts. +Korean needs a font that covers **every Hangul character in its catalog** and the language +picker label. The CJK font loaded in every language (Noto Sans CJK, Microsoft YaHei, PingFang) +usually covers Hangul already, and then nothing more is loaded. Otherwise, only when Korean is +chosen, desktop builds look for a `Hang`/`Kore` face from the optional `CRAFT_FONTS_DIR` input, +then installed fonts: Malgun Gothic/Gulim on Windows, Apple SD Gothic Neo/AppleGothic on macOS, +and Nanum Gothic or Noto CJK on Linux/FreeBSD. Collection faces are checked individually, and +file reads/face scans are bounded. No system fonts or new font assets are redistributed. + +Web builds cannot read installed fonts and embed no Korean face (to stay within hosting size +limits), so the web interface stays English when Korean is chosen. Without a font covering the +catalog, the saved `ko` preference is kept but the interface falls back to English; the language +command reports that a font is needed, and the picker uses the readable label **Korean** wherever +Hangul can't be shown. This change does not fix platform IME issues or localize engine output. + ## Adding translations The implementation follows the display-time catalog pattern used by @@ -70,6 +91,7 @@ The implementation follows the display-time catalog pattern used by - `@plural` entries hold the language's forms for integer counts: Ukrainian `one|few|many`, German and Spanish `one|other`, Brazilian Portuguese `one|other` (0 and 1 are `one`), Czech `one|few|other` (1; 2–4; everything else, including 0, 11, 21 and 22). + Korean has one form (`other`) for all integer counts. The API takes `u64`, so fractional counts are not accepted. - Register a new catalog in `i18n::LANGUAGES`, add its preference variant and code/name, and set its integer plural rule and form count on the `Language` row.