i18n: add Chinese, Korean and Russian interface languages
This commit is contained in:
parent
e8e010b446
commit
12bfdb0798
|
|
@ -1530,6 +1530,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"sys-locale",
|
||||
"toml",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ pub fn start() {
|
|||
let _ = session.execute("file.new", serde_json::json!({"sample": "sales"}));
|
||||
}
|
||||
let app = SheetApp::new(session, services(inbox.clone(), cc.egui_ctx.clone()));
|
||||
SheetApp::setup_context(&cc.egui_ctx, false);
|
||||
SheetApp::setup_context_for_language(&cc.egui_ctx, false, app.ui.language);
|
||||
Ok(Box::new(WebShell { app, inbox }))
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ fn main() -> eframe::Result<()> {
|
|||
"GridCraft",
|
||||
options,
|
||||
Box::new(move |cc| {
|
||||
SheetApp::setup_context(&cc.egui_ctx, app.ui.dark);
|
||||
SheetApp::setup_context_for_language(&cc.egui_ctx, app.ui.dark, app.ui.language);
|
||||
if let Some(port) = control_port {
|
||||
match control_server::start(port, cc.egui_ctx.clone()) {
|
||||
Ok(rx) => app.control_rx = Some(rx),
|
||||
|
|
|
|||
|
|
@ -21,10 +21,13 @@ image = { workspace = true }
|
|||
egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
# The desktop's preferred language, for the first-run interface language. The web build has no
|
||||
# system locale, so the crate is native-only (see i18n::Language::system).
|
||||
# The desktop's preferred language for the first-run interface language.
|
||||
sys-locale = { workspace = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# Read the browser's preferred language for the first-run web interface language.
|
||||
web-sys = { version = "0.3", features = ["Window", "Navigator"] }
|
||||
|
||||
[build-dependencies]
|
||||
serde_json = { workspace = true }
|
||||
toml = "1"
|
||||
|
|
|
|||
|
|
@ -4,43 +4,63 @@
|
|||
//! The interface language is a preference ([`crate::UiState::language`]): on first run it defaults
|
||||
//! to the system's language ([`Language::system`]), and the View tab switches it by hand.
|
||||
//!
|
||||
//! No Japanese font is bundled and none is installed here: the CJK faces [`crate::theme`] appends
|
||||
//! to every family already cover kana and kanji, so Japanese interface text renders without help.
|
||||
//! CJK fonts stay system-provided: [`crate::theme`] appends available CJK faces to every family so
|
||||
//! Japanese, Chinese and Korean labels and workbook text can render without bundling font files.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod ko;
|
||||
mod ru;
|
||||
mod zh;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Language {
|
||||
#[default]
|
||||
En,
|
||||
Zh,
|
||||
Ja,
|
||||
Ko,
|
||||
Ru,
|
||||
}
|
||||
|
||||
impl Language {
|
||||
pub const ALL: [Self; 2] = [Self::En, Self::Ja];
|
||||
pub const ALL: [Self; 5] = [Self::En, Self::Zh, Self::Ja, Self::Ko, Self::Ru];
|
||||
|
||||
/// The language's own name, shown in the switcher.
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::En => "English",
|
||||
Self::Zh => "简体中文",
|
||||
Self::Ja => "日本語",
|
||||
Self::Ko => "한국어",
|
||||
Self::Ru => "Русский",
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn code(self) -> &'static str {
|
||||
match self {
|
||||
Self::En => "en",
|
||||
Self::Zh => "zh",
|
||||
Self::Ja => "ja",
|
||||
Self::Ko => "ko",
|
||||
Self::Ru => "ru",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(code: &str) -> Option<Self> {
|
||||
match code {
|
||||
"en" => Some(Self::En),
|
||||
"ja" => Some(Self::Ja),
|
||||
_ => None,
|
||||
}
|
||||
Self::from_tag(code)
|
||||
}
|
||||
|
||||
/// A language tag (`ja`, `ja-JP`, `en_US`) reduced to a supported language. Native only: the web
|
||||
/// build has no system locale to read and tests exercise it on the host.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn from_tag(tag: &str) -> Option<Self> {
|
||||
Self::parse(&tag.split(['-', '_']).next().unwrap_or("").to_ascii_lowercase())
|
||||
match tag.trim().split(['-', '_']).next()?.to_ascii_lowercase().as_str() {
|
||||
"en" => Some(Self::En),
|
||||
"zh" => Some(Self::Zh),
|
||||
"ja" => Some(Self::Ja),
|
||||
"ko" => Some(Self::Ko),
|
||||
"ru" => Some(Self::Ru),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The system's preferred interface language, best effort; English when it is unknown or
|
||||
|
|
@ -52,17 +72,21 @@ impl Language {
|
|||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
Self::En
|
||||
web_sys::window().and_then(|window| window.navigator().language()).and_then(|tag| Self::from_tag(&tag)).unwrap_or(Self::En)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tr(self, text: &str) -> &str {
|
||||
if self == Self::Ja
|
||||
&& let Some((_, japanese)) = JAPANESE.iter().find(|(english, _)| *english == text)
|
||||
{
|
||||
return japanese;
|
||||
if self == Self::En {
|
||||
return text;
|
||||
}
|
||||
match self {
|
||||
Self::En => text,
|
||||
Self::Ja => JAPANESE.iter().find(|(english, _)| *english == text).map(|(_, translated)| *translated).unwrap_or(text),
|
||||
Self::Zh => zh::translate(text).unwrap_or(text),
|
||||
Self::Ko => ko::translate(text).unwrap_or(text),
|
||||
Self::Ru => ru::translate(text).unwrap_or(text),
|
||||
}
|
||||
text
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -344,6 +368,8 @@ const JAPANESE: &[(&str, &str)] = &[
|
|||
("Lookup &\nReference", "検索/行列"),
|
||||
("Math &\nTrig", "数学/三角"),
|
||||
("More\nFunctions", "その他の関数"),
|
||||
("Screen", "画面"),
|
||||
("Print", "印刷"),
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -357,8 +383,19 @@ mod tests {
|
|||
assert!(JAPANESE.iter().take(i).all(|(other, _)| en != other));
|
||||
assert_eq!(Language::En.tr(en), *en);
|
||||
}
|
||||
for (english, _) in JAPANESE {
|
||||
assert!(zh::translate(english).is_some_and(|text| !text.is_empty()), "missing Simplified Chinese: {english}");
|
||||
assert!(ko::translate(english).is_some_and(|text| !text.is_empty()), "missing Korean: {english}");
|
||||
assert!(ru::translate(english).is_some_and(|text| !text.is_empty()), "missing Russian: {english}");
|
||||
}
|
||||
assert_eq!(Language::Ja.tr("Data"), "データ");
|
||||
assert_eq!(Language::Zh.tr("Data"), "数据");
|
||||
assert_eq!(Language::Ko.tr("Data"), "데이터");
|
||||
assert_eq!(Language::Ru.tr("Data"), "Данные");
|
||||
assert_eq!(Language::Ja.tr("Sheet1!A1"), "Sheet1!A1");
|
||||
assert_eq!(Language::Zh.tr("Sheet1!A1"), "Sheet1!A1");
|
||||
assert_eq!(Language::Ko.tr("Sheet1!A1"), "Sheet1!A1");
|
||||
assert_eq!(Language::Ru.tr("Sheet1!A1"), "Sheet1!A1");
|
||||
assert_eq!(Language::parse("xx"), None);
|
||||
}
|
||||
|
||||
|
|
@ -366,17 +403,26 @@ mod tests {
|
|||
fn tags_reduce_to_a_supported_language() {
|
||||
assert_eq!(Language::from_tag("ja-JP"), Some(Language::Ja));
|
||||
assert_eq!(Language::from_tag("en_US"), Some(Language::En));
|
||||
assert_eq!(Language::from_tag("zh-Hans-CN"), Some(Language::Zh));
|
||||
assert_eq!(Language::from_tag("ko-KR"), Some(Language::Ko));
|
||||
assert_eq!(Language::from_tag("RU_ru"), Some(Language::Ru));
|
||||
assert_eq!(Language::from_tag("de-DE"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_unset_language_preference_uses_the_system_default() {
|
||||
let restored: crate::UiState = serde_json::from_str("{}").unwrap();
|
||||
assert_eq!(restored.language, Language::system());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn language_commands_switch_and_persist() {
|
||||
let mut app = crate::SheetApp::new(gridcraft_engine::Session::default(), Default::default());
|
||||
app.run("app.language.japanese", serde_json::json!({})).unwrap();
|
||||
assert_eq!(app.ui.language, Language::Ja);
|
||||
let restored: crate::UiState = serde_json::from_str(&serde_json::to_string(&app.ui).unwrap()).unwrap();
|
||||
assert_eq!(restored.language, Language::Ja);
|
||||
app.run("app.language.english", serde_json::json!({})).unwrap();
|
||||
assert_eq!(app.ui.language, Language::En);
|
||||
for language in Language::ALL {
|
||||
app.run("app.language.set", serde_json::json!({"language": language.code()})).unwrap();
|
||||
assert_eq!(app.ui.language, language);
|
||||
let restored: crate::UiState = serde_json::from_str(&serde_json::to_string(&app.ui).unwrap()).unwrap();
|
||||
assert_eq!(restored.language, language);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,268 @@
|
|||
//! Korean translations for the interface strings in i18n.rs.
|
||||
|
||||
pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
Some(match text {
|
||||
"Paste" => "붙여넣기",
|
||||
"Paste Values" => "값 붙여넣기",
|
||||
"Paste Formulas" => "수식 붙여넣기",
|
||||
"Paste Formatting" => "서식 붙여넣기",
|
||||
"Transpose" => "행/열 바꾸기",
|
||||
"Paste Link" => "연결하여 붙여넣기",
|
||||
"Paste Special…" => "선택하여 붙여넣기…",
|
||||
"Cut (⌘X)" => "잘라내기 (⌘X)",
|
||||
"Copy (⌘C)" => "복사 (⌘C)",
|
||||
"Format Painter (double-click to keep it on)" => "서식 복사 (두 번 클릭하여 유지)",
|
||||
"Bold (⌘B)" => "굵게 (⌘B)",
|
||||
"Italic (⌘I)" => "기울임꼴 (⌘I)",
|
||||
"Underline (⌘U)" => "밑줄 (⌘U)",
|
||||
"Double Underline" => "이중 밑줄",
|
||||
"Strikethrough" => "취소선",
|
||||
"Underline" => "밑줄",
|
||||
"Increase Font Size" => "글꼴 크기 늘리기",
|
||||
"Decrease Font Size" => "글꼴 크기 줄이기",
|
||||
"Wrap Text" => "텍스트 줄 바꿈",
|
||||
"Merge & Center" => "병합하고 가운데 맞춤",
|
||||
"Merge Cells" => "셀 병합",
|
||||
"Merge Across" => "행 방향 병합",
|
||||
"Unmerge Cells" => "셀 병합 취소",
|
||||
"Fill Color" => "채우기 색",
|
||||
"Font Color" => "글꼴 색",
|
||||
"Borders" => "테두리",
|
||||
"Top\nAlign" => "위쪽\n맞춤",
|
||||
"Middle\nAlign" => "가운데\n맞춤",
|
||||
"Bottom\nAlign" => "아래쪽\n맞춤",
|
||||
"Left" => "왼쪽",
|
||||
"Right" => "오른쪽",
|
||||
"Center" => "가운데",
|
||||
"Horizontal" => "가로",
|
||||
"Vertical Text" => "세로 텍스트",
|
||||
"Decrease Indent" => "들여쓰기 줄이기",
|
||||
"Increase Indent" => "들여쓰기 늘리기",
|
||||
"Orientation" => "방향",
|
||||
"Angle Counterclockwise" => "시계 반대 방향 각도",
|
||||
"Angle Clockwise" => "시계 방향 각도",
|
||||
"Rotate Text Up" => "텍스트 위쪽으로 회전",
|
||||
"Rotate Text Down" => "텍스트 아래쪽으로 회전",
|
||||
"Accounting Number Format" => "회계 숫자 서식",
|
||||
"Percent Style" => "백분율 스타일",
|
||||
"Comma Style" => "쉼표 스타일",
|
||||
"Increase Decimal" => "소수 자릿수 늘리기",
|
||||
"Decrease Decimal" => "소수 자릿수 줄이기",
|
||||
"More Accounting Formats…" => "회계 형식 더 보기…",
|
||||
"Conditional\nFormatting" => "조건부\n서식",
|
||||
"Conditional Formatting" => "조건부 서식",
|
||||
"Format\nas Table" => "표 서식\n지정",
|
||||
"Cell\nStyles" => "셀\n스타일",
|
||||
"Format Cells…" => "셀 서식…",
|
||||
"Format Cell Alignment…" => "셀 맞춤 서식…",
|
||||
"Format\nPane" => "서식\n창",
|
||||
"Format" => "서식",
|
||||
"Insert" => "삽입",
|
||||
"Insert Cells…" => "셀 삽입…",
|
||||
"Delete" => "삭제",
|
||||
"Delete Cells…" => "셀 삭제…",
|
||||
"Insert Sheet Rows" => "시트 행 삽입",
|
||||
"Insert Sheet Columns" => "시트 열 삽입",
|
||||
"Delete Sheet Rows" => "시트 행 삭제",
|
||||
"Delete Sheet Columns" => "시트 열 삭제",
|
||||
"Row Height…" => "행 높이…",
|
||||
"AutoFit Row Height" => "행 높이 자동 맞춤",
|
||||
"Column Width…" => "열 너비…",
|
||||
"AutoFit Column Width" => "열 너비 자동 맞춤",
|
||||
"Default Width…" => "기본 너비…",
|
||||
"Hide Rows" => "행 숨기기",
|
||||
"Unhide Rows" => "행 숨기기 취소",
|
||||
"Hide Columns" => "열 숨기기",
|
||||
"Unhide Columns" => "열 숨기기 취소",
|
||||
"Lock Cell" => "셀 잠금",
|
||||
"Clear" => "지우기",
|
||||
"Clear All" => "모두 지우기",
|
||||
"Clear Formats" => "서식 지우기",
|
||||
"Clear Contents" => "내용 지우기",
|
||||
"Clear Comments and Notes" => "댓글 및 메모 지우기",
|
||||
"Clear Hyperlinks" => "하이퍼링크 지우기",
|
||||
"AutoSum" => "자동 합계",
|
||||
"AutoSum (⌘⇧T)" => "자동 합계 (⌘⇧T)",
|
||||
"Flash\nFill" => "빠른\n채우기",
|
||||
"Flash Fill" => "빠른 채우기",
|
||||
"Fill" => "채우기",
|
||||
"Clear Print Area" => "인쇄 영역 지우기",
|
||||
"Find &\nSelect" => "찾기 및\n선택",
|
||||
"Find…" => "찾기…",
|
||||
"Replace…" => "바꾸기…",
|
||||
"Go To…" => "이동…",
|
||||
"Go To Special…" => "선택하여 이동…",
|
||||
"PivotTable" => "피벗 테이블",
|
||||
"Recommended\nCharts" => "추천\n차트",
|
||||
"Table" => "표",
|
||||
"Pictures" => "그림",
|
||||
"Shapes" => "도형",
|
||||
"Icons" => "아이콘",
|
||||
"Text\nBox" => "텍스트\n상자",
|
||||
"Comment" => "댓글",
|
||||
"New\nComment" => "새 댓글",
|
||||
"New Note" => "새 메모",
|
||||
"Show/Hide Note" => "메모 표시/숨기기",
|
||||
"Header &\nFooter" => "머리글 및\n바닥글",
|
||||
"Text to\nColumns" => "텍스트\n나누기",
|
||||
"Link" => "링크",
|
||||
"Symbol" => "기호",
|
||||
"Sparklines" => "스파크라인",
|
||||
"Header & Footer" => "머리글 및 바닥글",
|
||||
"Draw with ink" => "잉크로 그리기",
|
||||
"Eraser" => "지우개",
|
||||
"Ink to\nShape" => "잉크를\n도형으로",
|
||||
"Select\nObjects" => "개체\n선택",
|
||||
"Themes" => "테마",
|
||||
"Margins" => "여백",
|
||||
"Size" => "용지 크기",
|
||||
"Print\nArea" => "인쇄\n영역",
|
||||
"Set Print Area" => "인쇄 영역 설정",
|
||||
"Breaks" => "나누기",
|
||||
"Insert Page Break" => "페이지 나누기 삽입",
|
||||
"Remove Page Break" => "페이지 나누기 제거",
|
||||
"Reset All Page Breaks" => "모든 페이지 나누기 다시 설정",
|
||||
"Print\nTitles" => "인쇄\n제목",
|
||||
"Narrow" => "좁게",
|
||||
"Wide" => "넓게",
|
||||
"Normal" => "보통",
|
||||
"Portrait" => "세로",
|
||||
"Landscape" => "가로",
|
||||
"Custom Margins…" => "사용자 지정 여백…",
|
||||
"Insert\nFunction" => "함수\n삽입",
|
||||
"Name\nManager" => "이름\n관리자",
|
||||
"Define Name" => "이름 정의",
|
||||
"Use in Formula" => "수식에서 사용",
|
||||
"Create from Selection" => "선택 영역에서 만들기",
|
||||
"Trace Precedents" => "선행 셀 추적",
|
||||
"Trace Dependents" => "종속 셀 추적",
|
||||
"Remove Arrows" => "화살표 제거",
|
||||
"Show Formulas" => "수식 표시",
|
||||
"Error Checking" => "오류 검사",
|
||||
"Evaluate Formula" => "수식 계산",
|
||||
"Watch Window" => "감시 창",
|
||||
"Calculation\nOptions" => "계산\n옵션",
|
||||
"Calculate Now" => "지금 계산",
|
||||
"Calculate Sheet" => "시트 계산",
|
||||
"Automatic" => "자동",
|
||||
"Automatic Except for Data Tables" => "데이터 표를 제외한 자동",
|
||||
"Manual" => "수동",
|
||||
"Sort &\nFilter" => "정렬 및\n필터",
|
||||
"Sort" => "정렬",
|
||||
"Sort A to Z" => "오름차순 정렬",
|
||||
"Sort Z to A" => "내림차순 정렬",
|
||||
"Custom Sort…" => "사용자 지정 정렬…",
|
||||
"Filter" => "필터",
|
||||
"Reapply" => "다시 적용",
|
||||
"Get Data\n(Text/CSV)" => "데이터 가져오기\n(텍스트/CSV)",
|
||||
"Remove\nDuplicates" => "중복\n항목 제거",
|
||||
"Data\nValidation" => "데이터\n유효성 검사",
|
||||
"Data Validation" => "데이터 유효성 검사",
|
||||
"Data Validation…" => "데이터 유효성 검사…",
|
||||
"Circle Invalid Data" => "잘못된 데이터 표시",
|
||||
"Clear Validation" => "유효성 검사 지우기",
|
||||
"Group" => "그룹",
|
||||
"Ungroup" => "그룹 해제",
|
||||
"Subtotal" => "부분합",
|
||||
"What-If\nAnalysis" => "가상 분석",
|
||||
"Data Table…" => "데이터 표…",
|
||||
"Scenario Manager…" => "시나리오 관리자…",
|
||||
"Goal Seek…" => "목표값 찾기…",
|
||||
"Spelling" => "맞춤법 검사",
|
||||
"Check\nAccessibility" => "접근성\n검사",
|
||||
"Threaded Comments" => "스레드 댓글",
|
||||
"Show\nComments" => "댓글\n표시",
|
||||
"Protect\nWorkbook" => "통합 문서\n보호",
|
||||
"Unprotect\nSheet" => "시트 보호\n해제",
|
||||
"Protect Sheet…" => "시트 보호…",
|
||||
"Comments" => "댓글",
|
||||
"Notes" => "메모",
|
||||
"Page Break\nPreview" => "페이지 나누기\n미리 보기",
|
||||
"Page\nLayout" => "페이지\n레이아웃",
|
||||
"Formula Bar" => "수식 입력줄",
|
||||
"Gridlines" => "눈금선",
|
||||
"Headings" => "머리글",
|
||||
"Zoom" => "확대/축소",
|
||||
"100%" => "100%",
|
||||
"Zoom to\nSelection" => "선택 영역에\n맞춤",
|
||||
"Freeze\nPanes" => "틀\n고정",
|
||||
"Freeze Panes" => "틀 고정",
|
||||
"Freeze Top Row" => "첫 행 고정",
|
||||
"Freeze First Column" => "첫 열 고정",
|
||||
"Unfreeze Panes" => "틀 고정 취소",
|
||||
"Dark Mode" => "다크 모드",
|
||||
"Interface language" => "인터페이스 언어",
|
||||
"Command\nPalette" => "명령\n팔레트",
|
||||
"Agent\nControl" => "에이전트\n제어",
|
||||
"Action\nJournal" => "작업\n기록",
|
||||
"About\nGridCraft" => "GridCraft\n정보",
|
||||
"Share" => "공유",
|
||||
"Add Chart\nElement" => "차트\n요소 추가",
|
||||
"Change\nChart Type" => "차트 종류\n변경",
|
||||
"Switch\nRow/Column" => "행/열\n전환",
|
||||
"Data Labels: None" => "데이터 레이블: 없음",
|
||||
"Data Labels: Show" => "데이터 레이블: 표시",
|
||||
"Legend: None" => "범례: 없음",
|
||||
"Legend: Bottom" => "범례: 아래쪽",
|
||||
"Legend: Top" => "범례: 위쪽",
|
||||
"Legend: Right" => "범례: 오른쪽",
|
||||
"Gridlines: None" => "눈금선: 없음",
|
||||
"Gridlines: Show" => "눈금선: 표시",
|
||||
"Chart\nTitle" => "차트\n제목",
|
||||
"Delete\nChart" => "차트\n삭제",
|
||||
"Convert\nto Range" => "범위로\n변환",
|
||||
"Table\nStyles" => "표\n스타일",
|
||||
"Top 10 Items" => "상위 10개 항목",
|
||||
"Top 10%" => "상위 10%",
|
||||
"Bottom 10 Items" => "하위 10개 항목",
|
||||
"Bottom 10%" => "하위 10%",
|
||||
"Above Average" => "평균 초과",
|
||||
"Below Average" => "평균 미만",
|
||||
"Clear Rules from Entire Sheet" => "시트 전체에서 규칙 지우기",
|
||||
"Clear Rules from Selected Cells" => "선택한 셀에서 규칙 지우기",
|
||||
"Constants" => "상수",
|
||||
"Data Bars" => "데이터 막대",
|
||||
"Color Scales" => "색조",
|
||||
"Icon Sets" => "아이콘 집합",
|
||||
"Home" => "홈",
|
||||
"Draw" => "그리기",
|
||||
"Page Layout" => "페이지 레이아웃",
|
||||
"Formulas" => "수식",
|
||||
"Data" => "데이터",
|
||||
"Review" => "검토",
|
||||
"View" => "보기",
|
||||
"Automate" => "자동화",
|
||||
"Table Design" => "표 디자인",
|
||||
"Chart Design" => "차트 디자인",
|
||||
"Ready" => "준비",
|
||||
"Enter" => "입력",
|
||||
"Edit" => "편집",
|
||||
"Point" => "가리키기",
|
||||
"Calculate" => "계산",
|
||||
"Select destination and press Enter or choose Paste" => "대상을 선택하고 Enter 키를 누르거나 [붙여넣기]를 선택하세요",
|
||||
"General" => "일반",
|
||||
"Number" => "숫자",
|
||||
"Currency" => "통화",
|
||||
"Accounting" => "회계",
|
||||
"Date" => "날짜",
|
||||
"Time" => "시간",
|
||||
"Percentage" => "백분율",
|
||||
"Fraction" => "분수",
|
||||
"Scientific" => "과학",
|
||||
"Text" => "텍스트",
|
||||
"Special" => "특수",
|
||||
"Custom" => "사용자 지정",
|
||||
"Short Date" => "간단한 날짜",
|
||||
"Long Date" => "자세한 날짜",
|
||||
"More Number Formats…" => "숫자 서식 더 보기…",
|
||||
"Financial" => "재무",
|
||||
"Logical" => "논리",
|
||||
"Date &\nTime" => "날짜 및\n시간",
|
||||
"Lookup &\nReference" => "조회 및\n참조",
|
||||
"Math &\nTrig" => "수학 및\n삼각",
|
||||
"More\nFunctions" => "기타\n함수",
|
||||
"Screen" => "화면",
|
||||
"Print" => "인쇄",
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
//! Russian translations for the interface strings in i18n.rs.
|
||||
|
||||
pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
Some(match text {
|
||||
"Paste" => "Вставить",
|
||||
"Paste Values" => "Вставить значения",
|
||||
"Paste Formulas" => "Вставить формулы",
|
||||
"Paste Formatting" => "Вставить форматирование",
|
||||
"Transpose" => "Транспонировать",
|
||||
"Paste Link" => "Вставить связь",
|
||||
"Paste Special…" => "Специальная вставка…",
|
||||
"Cut (⌘X)" => "Вырезать (⌘X)",
|
||||
"Copy (⌘C)" => "Копировать (⌘C)",
|
||||
"Format Painter (double-click to keep it on)" => "Формат по образцу (дважды щёлкните, чтобы закрепить)",
|
||||
"Bold (⌘B)" => "Полужирный (⌘B)",
|
||||
"Italic (⌘I)" => "Курсив (⌘I)",
|
||||
"Underline (⌘U)" => "Подчёркивание (⌘U)",
|
||||
"Double Underline" => "Двойное подчёркивание",
|
||||
"Strikethrough" => "Зачёркивание",
|
||||
"Underline" => "Подчёркивание",
|
||||
"Increase Font Size" => "Увеличить размер шрифта",
|
||||
"Decrease Font Size" => "Уменьшить размер шрифта",
|
||||
"Wrap Text" => "Переносить текст",
|
||||
"Merge & Center" => "Объединить и поместить в центре",
|
||||
"Merge Cells" => "Объединить ячейки",
|
||||
"Merge Across" => "Объединить по строкам",
|
||||
"Unmerge Cells" => "Отменить объединение ячеек",
|
||||
"Fill Color" => "Цвет заливки",
|
||||
"Font Color" => "Цвет шрифта",
|
||||
"Borders" => "Границы",
|
||||
"Top\nAlign" => "По верхнему\nкраю",
|
||||
"Middle\nAlign" => "По центру\nвертикали",
|
||||
"Bottom\nAlign" => "По нижнему\nкраю",
|
||||
"Left" => "Слева",
|
||||
"Right" => "Справа",
|
||||
"Center" => "По центру",
|
||||
"Horizontal" => "По горизонтали",
|
||||
"Vertical Text" => "Вертикальный текст",
|
||||
"Decrease Indent" => "Уменьшить отступ",
|
||||
"Increase Indent" => "Увеличить отступ",
|
||||
"Orientation" => "Ориентация",
|
||||
"Angle Counterclockwise" => "Повернуть против часовой стрелки",
|
||||
"Angle Clockwise" => "Повернуть по часовой стрелке",
|
||||
"Rotate Text Up" => "Повернуть текст вверх",
|
||||
"Rotate Text Down" => "Повернуть текст вниз",
|
||||
"Accounting Number Format" => "Финансовый числовой формат",
|
||||
"Percent Style" => "Процентный формат",
|
||||
"Comma Style" => "Разделитель разрядов",
|
||||
"Increase Decimal" => "Увеличить разрядность",
|
||||
"Decrease Decimal" => "Уменьшить разрядность",
|
||||
"More Accounting Formats…" => "Другие финансовые форматы…",
|
||||
"Conditional\nFormatting" => "Условное\nформатирование",
|
||||
"Conditional Formatting" => "Условное форматирование",
|
||||
"Format\nas Table" => "Форматировать\nкак таблицу",
|
||||
"Cell\nStyles" => "Стили\nячеек",
|
||||
"Format Cells…" => "Формат ячеек…",
|
||||
"Format Cell Alignment…" => "Выравнивание ячеек…",
|
||||
"Format\nPane" => "Область\nформатирования",
|
||||
"Format" => "Формат",
|
||||
"Insert" => "Вставить",
|
||||
"Insert Cells…" => "Вставить ячейки…",
|
||||
"Delete" => "Удалить",
|
||||
"Delete Cells…" => "Удалить ячейки…",
|
||||
"Insert Sheet Rows" => "Вставить строки листа",
|
||||
"Insert Sheet Columns" => "Вставить столбцы листа",
|
||||
"Delete Sheet Rows" => "Удалить строки листа",
|
||||
"Delete Sheet Columns" => "Удалить столбцы листа",
|
||||
"Row Height…" => "Высота строки…",
|
||||
"AutoFit Row Height" => "Автоподбор высоты строки",
|
||||
"Column Width…" => "Ширина столбца…",
|
||||
"AutoFit Column Width" => "Автоподбор ширины столбца",
|
||||
"Default Width…" => "Ширина по умолчанию…",
|
||||
"Hide Rows" => "Скрыть строки",
|
||||
"Unhide Rows" => "Показать строки",
|
||||
"Hide Columns" => "Скрыть столбцы",
|
||||
"Unhide Columns" => "Показать столбцы",
|
||||
"Lock Cell" => "Заблокировать ячейку",
|
||||
"Clear" => "Очистить",
|
||||
"Clear All" => "Очистить всё",
|
||||
"Clear Formats" => "Очистить форматы",
|
||||
"Clear Contents" => "Очистить содержимое",
|
||||
"Clear Comments and Notes" => "Удалить комментарии и заметки",
|
||||
"Clear Hyperlinks" => "Удалить гиперссылки",
|
||||
"AutoSum" => "Автосумма",
|
||||
"AutoSum (⌘⇧T)" => "Автосумма (⌘⇧T)",
|
||||
"Flash\nFill" => "Мгновенное\nзаполнение",
|
||||
"Flash Fill" => "Мгновенное заполнение",
|
||||
"Fill" => "Заполнить",
|
||||
"Clear Print Area" => "Очистить область печати",
|
||||
"Find &\nSelect" => "Найти и\nвыделить",
|
||||
"Find…" => "Найти…",
|
||||
"Replace…" => "Заменить…",
|
||||
"Go To…" => "Перейти…",
|
||||
"Go To Special…" => "Выделить группу ячеек…",
|
||||
"PivotTable" => "Сводная таблица",
|
||||
"Recommended\nCharts" => "Рекомендуемые\nдиаграммы",
|
||||
"Table" => "Таблица",
|
||||
"Pictures" => "Изображения",
|
||||
"Shapes" => "Фигуры",
|
||||
"Icons" => "Значки",
|
||||
"Text\nBox" => "Текстовое\nполе",
|
||||
"Comment" => "Комментарий",
|
||||
"New\nComment" => "Новый\nкомментарий",
|
||||
"New Note" => "Новая заметка",
|
||||
"Show/Hide Note" => "Показать/скрыть заметку",
|
||||
"Header &\nFooter" => "Верхний и\nнижний колонтитулы",
|
||||
"Text to\nColumns" => "Текст\nпо столбцам",
|
||||
"Link" => "Ссылка",
|
||||
"Symbol" => "Символ",
|
||||
"Sparklines" => "Спарклайны",
|
||||
"Header & Footer" => "Верхний и нижний колонтитулы",
|
||||
"Draw with ink" => "Рисование от руки",
|
||||
"Eraser" => "Ластик",
|
||||
"Ink to\nShape" => "Преобразовать рукописный\nввод в фигуру",
|
||||
"Select\nObjects" => "Выбрать\nобъекты",
|
||||
"Themes" => "Темы",
|
||||
"Margins" => "Поля",
|
||||
"Size" => "Размер",
|
||||
"Print\nArea" => "Область\nпечати",
|
||||
"Set Print Area" => "Задать область печати",
|
||||
"Breaks" => "Разрывы",
|
||||
"Insert Page Break" => "Вставить разрыв страницы",
|
||||
"Remove Page Break" => "Удалить разрыв страницы",
|
||||
"Reset All Page Breaks" => "Сбросить все разрывы страниц",
|
||||
"Print\nTitles" => "Печатать\nзаголовки",
|
||||
"Narrow" => "Узкие",
|
||||
"Wide" => "Широкие",
|
||||
"Normal" => "Обычные",
|
||||
"Portrait" => "Книжная",
|
||||
"Landscape" => "Альбомная",
|
||||
"Custom Margins…" => "Настраиваемые поля…",
|
||||
"Insert\nFunction" => "Вставить\nфункцию",
|
||||
"Name\nManager" => "Диспетчер\nимён",
|
||||
"Define Name" => "Присвоить имя",
|
||||
"Use in Formula" => "Использовать в формуле",
|
||||
"Create from Selection" => "Создать из выделенного",
|
||||
"Trace Precedents" => "Влияющие ячейки",
|
||||
"Trace Dependents" => "Зависимые ячейки",
|
||||
"Remove Arrows" => "Убрать стрелки",
|
||||
"Show Formulas" => "Показать формулы",
|
||||
"Error Checking" => "Проверка ошибок",
|
||||
"Evaluate Formula" => "Вычислить формулу",
|
||||
"Watch Window" => "Окно контрольных значений",
|
||||
"Calculation\nOptions" => "Параметры\nвычислений",
|
||||
"Calculate Now" => "Вычислить сейчас",
|
||||
"Calculate Sheet" => "Вычислить лист",
|
||||
"Automatic" => "Автоматически",
|
||||
"Automatic Except for Data Tables" => "Автоматически, кроме таблиц данных",
|
||||
"Manual" => "Вручную",
|
||||
"Sort &\nFilter" => "Сортировка и\nфильтр",
|
||||
"Sort" => "Сортировка",
|
||||
"Sort A to Z" => "Сортировка от А до Я",
|
||||
"Sort Z to A" => "Сортировка от Я до А",
|
||||
"Custom Sort…" => "Настраиваемая сортировка…",
|
||||
"Filter" => "Фильтр",
|
||||
"Reapply" => "Повторить",
|
||||
"Get Data\n(Text/CSV)" => "Получить данные\n(текст/CSV)",
|
||||
"Remove\nDuplicates" => "Удалить\nдубликаты",
|
||||
"Data\nValidation" => "Проверка\nданных",
|
||||
"Data Validation" => "Проверка данных",
|
||||
"Data Validation…" => "Проверка данных…",
|
||||
"Circle Invalid Data" => "Обвести неверные данные",
|
||||
"Clear Validation" => "Очистить проверку данных",
|
||||
"Group" => "Группировать",
|
||||
"Ungroup" => "Разгруппировать",
|
||||
"Subtotal" => "Промежуточный итог",
|
||||
"What-If\nAnalysis" => "Анализ «что\nесли»",
|
||||
"Data Table…" => "Таблица данных…",
|
||||
"Scenario Manager…" => "Диспетчер сценариев…",
|
||||
"Goal Seek…" => "Подбор параметра…",
|
||||
"Spelling" => "Орфография",
|
||||
"Check\nAccessibility" => "Проверка\nдоступности",
|
||||
"Threaded Comments" => "Комментарии с ветками",
|
||||
"Show\nComments" => "Показать\nкомментарии",
|
||||
"Protect\nWorkbook" => "Защитить\nкнигу",
|
||||
"Unprotect\nSheet" => "Снять защиту\nлиста",
|
||||
"Protect Sheet…" => "Защитить лист…",
|
||||
"Comments" => "Комментарии",
|
||||
"Notes" => "Заметки",
|
||||
"Page Break\nPreview" => "Предварительный просмотр\nразрывов страниц",
|
||||
"Page\nLayout" => "Разметка\nстраницы",
|
||||
"Formula Bar" => "Строка формул",
|
||||
"Gridlines" => "Сетка",
|
||||
"Headings" => "Заголовки",
|
||||
"Zoom" => "Масштаб",
|
||||
"100%" => "100%",
|
||||
"Zoom to\nSelection" => "Масштаб по\nвыделению",
|
||||
"Freeze\nPanes" => "Закрепить\nобласти",
|
||||
"Freeze Panes" => "Закрепить области",
|
||||
"Freeze Top Row" => "Закрепить верхнюю строку",
|
||||
"Freeze First Column" => "Закрепить первый столбец",
|
||||
"Unfreeze Panes" => "Снять закрепление областей",
|
||||
"Dark Mode" => "Тёмный режим",
|
||||
"Interface language" => "Язык интерфейса",
|
||||
"Command\nPalette" => "Палитра\nкоманд",
|
||||
"Agent\nControl" => "Управление\nагентом",
|
||||
"Action\nJournal" => "Журнал\nдействий",
|
||||
"About\nGridCraft" => "О\nGridCraft",
|
||||
"Share" => "Поделиться",
|
||||
"Add Chart\nElement" => "Добавить элемент\nдиаграммы",
|
||||
"Change\nChart Type" => "Изменить тип\nдиаграммы",
|
||||
"Switch\nRow/Column" => "Поменять\nстроки/столбцы",
|
||||
"Data Labels: None" => "Подписи данных: нет",
|
||||
"Data Labels: Show" => "Подписи данных: показать",
|
||||
"Legend: None" => "Легенда: нет",
|
||||
"Legend: Bottom" => "Легенда: снизу",
|
||||
"Legend: Top" => "Легенда: сверху",
|
||||
"Legend: Right" => "Легенда: справа",
|
||||
"Gridlines: None" => "Линии сетки: нет",
|
||||
"Gridlines: Show" => "Линии сетки: показать",
|
||||
"Chart\nTitle" => "Название\nдиаграммы",
|
||||
"Delete\nChart" => "Удалить\nдиаграмму",
|
||||
"Convert\nto Range" => "Преобразовать\nв диапазон",
|
||||
"Table\nStyles" => "Стили\nтаблиц",
|
||||
"Top 10 Items" => "Первые 10 элементов",
|
||||
"Top 10%" => "Верхние 10%",
|
||||
"Bottom 10 Items" => "Последние 10 элементов",
|
||||
"Bottom 10%" => "Нижние 10%",
|
||||
"Above Average" => "Выше среднего",
|
||||
"Below Average" => "Ниже среднего",
|
||||
"Clear Rules from Entire Sheet" => "Очистить правила на всём листе",
|
||||
"Clear Rules from Selected Cells" => "Очистить правила для выделенных ячеек",
|
||||
"Constants" => "Константы",
|
||||
"Data Bars" => "Гистограммы",
|
||||
"Color Scales" => "Цветовые шкалы",
|
||||
"Icon Sets" => "Наборы значков",
|
||||
"Home" => "Главная",
|
||||
"Draw" => "Рисование",
|
||||
"Page Layout" => "Разметка страницы",
|
||||
"Formulas" => "Формулы",
|
||||
"Data" => "Данные",
|
||||
"Review" => "Рецензирование",
|
||||
"View" => "Вид",
|
||||
"Automate" => "Автоматизация",
|
||||
"Table Design" => "Конструктор таблиц",
|
||||
"Chart Design" => "Конструктор диаграмм",
|
||||
"Ready" => "Готово",
|
||||
"Enter" => "Ввод",
|
||||
"Edit" => "Изменение",
|
||||
"Point" => "Указание",
|
||||
"Calculate" => "Вычисление",
|
||||
"Select destination and press Enter or choose Paste" => "Выберите место вставки и нажмите Enter или выберите «Вставить»",
|
||||
"General" => "Общий",
|
||||
"Number" => "Числовой",
|
||||
"Currency" => "Денежный",
|
||||
"Accounting" => "Финансовый",
|
||||
"Date" => "Дата",
|
||||
"Time" => "Время",
|
||||
"Percentage" => "Процентный",
|
||||
"Fraction" => "Дробный",
|
||||
"Scientific" => "Экспоненциальный",
|
||||
"Text" => "Текстовый",
|
||||
"Special" => "Дополнительный",
|
||||
"Custom" => "Пользовательский",
|
||||
"Short Date" => "Краткая дата",
|
||||
"Long Date" => "Длинная дата",
|
||||
"More Number Formats…" => "Другие числовые форматы…",
|
||||
"Financial" => "Финансовые",
|
||||
"Logical" => "Логические",
|
||||
"Date &\nTime" => "Дата и\nвремя",
|
||||
"Lookup &\nReference" => "Поиск и\nссылки",
|
||||
"Math &\nTrig" => "Математические и\nтригонометрические",
|
||||
"More\nFunctions" => "Другие\nфункции",
|
||||
"Screen" => "На экране",
|
||||
"Print" => "Печать",
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
//! Simplified Chinese translations for the interface strings in i18n.rs.
|
||||
|
||||
pub(super) fn translate(text: &str) -> Option<&'static str> {
|
||||
Some(match text {
|
||||
"Paste" => "粘贴",
|
||||
"Paste Values" => "粘贴值",
|
||||
"Paste Formulas" => "粘贴公式",
|
||||
"Paste Formatting" => "粘贴格式",
|
||||
"Transpose" => "转置",
|
||||
"Paste Link" => "粘贴链接",
|
||||
"Paste Special…" => "选择性粘贴…",
|
||||
"Cut (⌘X)" => "剪切 (⌘X)",
|
||||
"Copy (⌘C)" => "复制 (⌘C)",
|
||||
"Format Painter (double-click to keep it on)" => "格式刷(双击可持续使用)",
|
||||
"Bold (⌘B)" => "粗体 (⌘B)",
|
||||
"Italic (⌘I)" => "斜体 (⌘I)",
|
||||
"Underline (⌘U)" => "下划线 (⌘U)",
|
||||
"Double Underline" => "双下划线",
|
||||
"Strikethrough" => "删除线",
|
||||
"Underline" => "下划线",
|
||||
"Increase Font Size" => "增大字号",
|
||||
"Decrease Font Size" => "减小字号",
|
||||
"Wrap Text" => "自动换行",
|
||||
"Merge & Center" => "合并后居中",
|
||||
"Merge Cells" => "合并单元格",
|
||||
"Merge Across" => "跨行合并",
|
||||
"Unmerge Cells" => "取消合并单元格",
|
||||
"Fill Color" => "填充颜色",
|
||||
"Font Color" => "字体颜色",
|
||||
"Borders" => "边框",
|
||||
"Top\nAlign" => "顶端对齐",
|
||||
"Middle\nAlign" => "垂直居中",
|
||||
"Bottom\nAlign" => "底端对齐",
|
||||
"Left" => "左对齐",
|
||||
"Right" => "右对齐",
|
||||
"Center" => "居中",
|
||||
"Horizontal" => "水平",
|
||||
"Vertical Text" => "竖排文字",
|
||||
"Decrease Indent" => "减少缩进",
|
||||
"Increase Indent" => "增加缩进",
|
||||
"Orientation" => "文字方向",
|
||||
"Angle Counterclockwise" => "逆时针旋转",
|
||||
"Angle Clockwise" => "顺时针旋转",
|
||||
"Rotate Text Up" => "文字向上旋转",
|
||||
"Rotate Text Down" => "文字向下旋转",
|
||||
"Accounting Number Format" => "会计数字格式",
|
||||
"Percent Style" => "百分比样式",
|
||||
"Comma Style" => "千位分隔样式",
|
||||
"Increase Decimal" => "增加小数位数",
|
||||
"Decrease Decimal" => "减少小数位数",
|
||||
"More Accounting Formats…" => "更多会计格式…",
|
||||
"Conditional\nFormatting" => "条件\n格式",
|
||||
"Conditional Formatting" => "条件格式",
|
||||
"Format\nas Table" => "套用表格\n格式",
|
||||
"Cell\nStyles" => "单元格\n样式",
|
||||
"Format Cells…" => "设置单元格格式…",
|
||||
"Format Cell Alignment…" => "设置单元格对齐方式…",
|
||||
"Format\nPane" => "格式\n窗格",
|
||||
"Format" => "格式",
|
||||
"Insert" => "插入",
|
||||
"Insert Cells…" => "插入单元格…",
|
||||
"Delete" => "删除",
|
||||
"Delete Cells…" => "删除单元格…",
|
||||
"Insert Sheet Rows" => "插入工作表行",
|
||||
"Insert Sheet Columns" => "插入工作表列",
|
||||
"Delete Sheet Rows" => "删除工作表行",
|
||||
"Delete Sheet Columns" => "删除工作表列",
|
||||
"Row Height…" => "行高…",
|
||||
"AutoFit Row Height" => "自动调整行高",
|
||||
"Column Width…" => "列宽…",
|
||||
"AutoFit Column Width" => "自动调整列宽",
|
||||
"Default Width…" => "默认列宽…",
|
||||
"Hide Rows" => "隐藏行",
|
||||
"Unhide Rows" => "取消隐藏行",
|
||||
"Hide Columns" => "隐藏列",
|
||||
"Unhide Columns" => "取消隐藏列",
|
||||
"Lock Cell" => "锁定单元格",
|
||||
"Clear" => "清除",
|
||||
"Clear All" => "全部清除",
|
||||
"Clear Formats" => "清除格式",
|
||||
"Clear Contents" => "清除内容",
|
||||
"Clear Comments and Notes" => "清除批注和备注",
|
||||
"Clear Hyperlinks" => "清除超链接",
|
||||
"AutoSum" => "自动求和",
|
||||
"AutoSum (⌘⇧T)" => "自动求和 (⌘⇧T)",
|
||||
"Flash\nFill" => "快速\n填充",
|
||||
"Flash Fill" => "快速填充",
|
||||
"Fill" => "填充",
|
||||
"Clear Print Area" => "清除打印区域",
|
||||
"Find &\nSelect" => "查找与\n选择",
|
||||
"Find…" => "查找…",
|
||||
"Replace…" => "替换…",
|
||||
"Go To…" => "定位…",
|
||||
"Go To Special…" => "定位条件…",
|
||||
"PivotTable" => "数据透视表",
|
||||
"Recommended\nCharts" => "推荐\n图表",
|
||||
"Table" => "表格",
|
||||
"Pictures" => "图片",
|
||||
"Shapes" => "形状",
|
||||
"Icons" => "图标",
|
||||
"Text\nBox" => "文本\n框",
|
||||
"Comment" => "批注",
|
||||
"New\nComment" => "新建\n批注",
|
||||
"New Note" => "新建备注",
|
||||
"Show/Hide Note" => "显示/隐藏备注",
|
||||
"Header &\nFooter" => "页眉和\n页脚",
|
||||
"Text to\nColumns" => "分列",
|
||||
"Link" => "链接",
|
||||
"Symbol" => "符号",
|
||||
"Sparklines" => "迷你图",
|
||||
"Header & Footer" => "页眉和页脚",
|
||||
"Draw with ink" => "墨迹绘制",
|
||||
"Eraser" => "橡皮擦",
|
||||
"Ink to\nShape" => "墨迹转\n形状",
|
||||
"Select\nObjects" => "选择\n对象",
|
||||
"Themes" => "主题",
|
||||
"Margins" => "页边距",
|
||||
"Size" => "纸张大小",
|
||||
"Print\nArea" => "打印\n区域",
|
||||
"Set Print Area" => "设置打印区域",
|
||||
"Breaks" => "分页符",
|
||||
"Insert Page Break" => "插入分页符",
|
||||
"Remove Page Break" => "删除分页符",
|
||||
"Reset All Page Breaks" => "重置所有分页符",
|
||||
"Print\nTitles" => "打印\n标题",
|
||||
"Narrow" => "窄",
|
||||
"Wide" => "宽",
|
||||
"Normal" => "普通",
|
||||
"Portrait" => "纵向",
|
||||
"Landscape" => "横向",
|
||||
"Custom Margins…" => "自定义页边距…",
|
||||
"Insert\nFunction" => "插入\n函数",
|
||||
"Name\nManager" => "名称\n管理器",
|
||||
"Define Name" => "定义名称",
|
||||
"Use in Formula" => "在公式中使用",
|
||||
"Create from Selection" => "根据所选内容创建",
|
||||
"Trace Precedents" => "追踪引用单元格",
|
||||
"Trace Dependents" => "追踪从属单元格",
|
||||
"Remove Arrows" => "删除箭头",
|
||||
"Show Formulas" => "显示公式",
|
||||
"Error Checking" => "错误检查",
|
||||
"Evaluate Formula" => "公式求值",
|
||||
"Watch Window" => "监视窗口",
|
||||
"Calculation\nOptions" => "计算\n选项",
|
||||
"Calculate Now" => "立即计算",
|
||||
"Calculate Sheet" => "计算工作表",
|
||||
"Automatic" => "自动",
|
||||
"Automatic Except for Data Tables" => "除数据表以外自动",
|
||||
"Manual" => "手动",
|
||||
"Sort &\nFilter" => "排序和\n筛选",
|
||||
"Sort" => "排序",
|
||||
"Sort A to Z" => "升序排序",
|
||||
"Sort Z to A" => "降序排序",
|
||||
"Custom Sort…" => "自定义排序…",
|
||||
"Filter" => "筛选",
|
||||
"Reapply" => "重新应用",
|
||||
"Get Data\n(Text/CSV)" => "获取数据\n(文本/CSV)",
|
||||
"Remove\nDuplicates" => "删除\n重复项",
|
||||
"Data\nValidation" => "数据\n验证",
|
||||
"Data Validation" => "数据验证",
|
||||
"Data Validation…" => "数据验证…",
|
||||
"Circle Invalid Data" => "圈出无效数据",
|
||||
"Clear Validation" => "清除验证",
|
||||
"Group" => "组合",
|
||||
"Ungroup" => "取消组合",
|
||||
"Subtotal" => "分类汇总",
|
||||
"What-If\nAnalysis" => "假设分析",
|
||||
"Data Table…" => "数据表…",
|
||||
"Scenario Manager…" => "方案管理器…",
|
||||
"Goal Seek…" => "单变量求解…",
|
||||
"Spelling" => "拼写检查",
|
||||
"Check\nAccessibility" => "检查\n辅助功能",
|
||||
"Threaded Comments" => "线程式批注",
|
||||
"Show\nComments" => "显示\n批注",
|
||||
"Protect\nWorkbook" => "保护\n工作簿",
|
||||
"Unprotect\nSheet" => "撤销\n工作表保护",
|
||||
"Protect Sheet…" => "保护工作表…",
|
||||
"Comments" => "批注",
|
||||
"Notes" => "备注",
|
||||
"Page Break\nPreview" => "分页符\n预览",
|
||||
"Page\nLayout" => "页面\n布局",
|
||||
"Formula Bar" => "编辑栏",
|
||||
"Gridlines" => "网格线",
|
||||
"Headings" => "标题",
|
||||
"Zoom" => "缩放",
|
||||
"100%" => "100%",
|
||||
"Zoom to\nSelection" => "缩放到\n所选内容",
|
||||
"Freeze\nPanes" => "冻结\n窗格",
|
||||
"Freeze Panes" => "冻结窗格",
|
||||
"Freeze Top Row" => "冻结首行",
|
||||
"Freeze First Column" => "冻结首列",
|
||||
"Unfreeze Panes" => "取消冻结窗格",
|
||||
"Dark Mode" => "深色模式",
|
||||
"Interface language" => "界面语言",
|
||||
"Command\nPalette" => "命令\n面板",
|
||||
"Agent\nControl" => "智能体\n控制",
|
||||
"Action\nJournal" => "操作\n日志",
|
||||
"About\nGridCraft" => "关于\nGridCraft",
|
||||
"Share" => "分享",
|
||||
"Add Chart\nElement" => "添加图表\n元素",
|
||||
"Change\nChart Type" => "更改\n图表类型",
|
||||
"Switch\nRow/Column" => "切换\n行/列",
|
||||
"Data Labels: None" => "数据标签:无",
|
||||
"Data Labels: Show" => "数据标签:显示",
|
||||
"Legend: None" => "图例:无",
|
||||
"Legend: Bottom" => "图例:底部",
|
||||
"Legend: Top" => "图例:顶部",
|
||||
"Legend: Right" => "图例:右侧",
|
||||
"Gridlines: None" => "网格线:无",
|
||||
"Gridlines: Show" => "网格线:显示",
|
||||
"Chart\nTitle" => "图表\n标题",
|
||||
"Delete\nChart" => "删除\n图表",
|
||||
"Convert\nto Range" => "转换\n为区域",
|
||||
"Table\nStyles" => "表格\n样式",
|
||||
"Top 10 Items" => "前 10 项",
|
||||
"Top 10%" => "前 10%",
|
||||
"Bottom 10 Items" => "后 10 项",
|
||||
"Bottom 10%" => "后 10%",
|
||||
"Above Average" => "高于平均值",
|
||||
"Below Average" => "低于平均值",
|
||||
"Clear Rules from Entire Sheet" => "清除整张工作表的规则",
|
||||
"Clear Rules from Selected Cells" => "清除所选单元格的规则",
|
||||
"Constants" => "常量",
|
||||
"Data Bars" => "数据条",
|
||||
"Color Scales" => "色阶",
|
||||
"Icon Sets" => "图标集",
|
||||
"Home" => "开始",
|
||||
"Draw" => "绘图",
|
||||
"Page Layout" => "页面布局",
|
||||
"Formulas" => "公式",
|
||||
"Data" => "数据",
|
||||
"Review" => "审阅",
|
||||
"View" => "视图",
|
||||
"Automate" => "自动化",
|
||||
"Table Design" => "表格设计",
|
||||
"Chart Design" => "图表设计",
|
||||
"Ready" => "就绪",
|
||||
"Enter" => "输入",
|
||||
"Edit" => "编辑",
|
||||
"Point" => "指向",
|
||||
"Calculate" => "计算",
|
||||
"Select destination and press Enter or choose Paste" => "选择目标位置并按 Enter,或选择“粘贴”",
|
||||
"General" => "常规",
|
||||
"Number" => "数值",
|
||||
"Currency" => "货币",
|
||||
"Accounting" => "会计专用",
|
||||
"Date" => "日期",
|
||||
"Time" => "时间",
|
||||
"Percentage" => "百分比",
|
||||
"Fraction" => "分数",
|
||||
"Scientific" => "科学记数法",
|
||||
"Text" => "文本",
|
||||
"Special" => "特殊",
|
||||
"Custom" => "自定义",
|
||||
"Short Date" => "短日期",
|
||||
"Long Date" => "长日期",
|
||||
"More Number Formats…" => "更多数字格式…",
|
||||
"Financial" => "财务",
|
||||
"Logical" => "逻辑",
|
||||
"Date &\nTime" => "日期和\n时间",
|
||||
"Lookup &\nReference" => "查找和\n引用",
|
||||
"Math &\nTrig" => "数学和\n三角函数",
|
||||
"More\nFunctions" => "更多\n函数",
|
||||
"Screen" => "屏幕",
|
||||
"Print" => "打印",
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
@ -109,6 +109,7 @@ pub struct SheetApp {
|
|||
pub perf: Perf,
|
||||
pub fonts_ready: bool,
|
||||
fonts_set: bool,
|
||||
fonts_language: Option<i18n::Language>,
|
||||
pub name_box: Option<String>,
|
||||
pub(crate) shots: control::Shots,
|
||||
/// Chart selected on the sheet (id).
|
||||
|
|
@ -138,6 +139,7 @@ impl SheetApp {
|
|||
perf: Perf::default(),
|
||||
fonts_ready: false,
|
||||
fonts_set: false,
|
||||
fonts_language: None,
|
||||
name_box: None,
|
||||
shots: control::Shots::default(),
|
||||
selected_chart: None,
|
||||
|
|
@ -145,9 +147,14 @@ impl SheetApp {
|
|||
}
|
||||
}
|
||||
|
||||
/// One-time context setup: fonts and visuals.
|
||||
/// One-time context setup: fonts and visuals, using the system's preferred language.
|
||||
pub fn setup_context(ctx: &egui::Context, dark: bool) {
|
||||
ctx.set_fonts(theme::font_definitions());
|
||||
Self::setup_context_for_language(ctx, dark, i18n::Language::system());
|
||||
}
|
||||
|
||||
/// Sets up fonts and visuals for a specific persisted interface language.
|
||||
pub fn setup_context_for_language(ctx: &egui::Context, dark: bool, language: i18n::Language) {
|
||||
ctx.set_fonts(theme::font_definitions_for_language(language));
|
||||
theme::apply(ctx, dark);
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +206,10 @@ impl SheetApp {
|
|||
self.ui.language = l;
|
||||
Ok(json!({"language": l}))
|
||||
}
|
||||
None => Err(format!("unknown language {:?} (use \"en\" or \"ja\")", p.get("language").and_then(Json::as_str).unwrap_or(""))),
|
||||
None => Err(format!(
|
||||
"unknown language {:?} (use \"en\", \"zh\", \"ja\", \"ko\" or \"ru\")",
|
||||
p.get("language").and_then(Json::as_str).unwrap_or("")
|
||||
)),
|
||||
}
|
||||
}
|
||||
"app.language.english" => {
|
||||
|
|
@ -375,13 +385,18 @@ impl SheetApp {
|
|||
|
||||
/// Per-frame logic (control channel, screenshots). Call before `ui`.
|
||||
pub fn logic(&mut self, ctx: &egui::Context) {
|
||||
if self.fonts_language.is_some_and(|language| language != self.ui.language) {
|
||||
self.fonts_ready = false;
|
||||
self.fonts_set = false;
|
||||
}
|
||||
if !self.fonts_ready {
|
||||
// New fonts apply from the next frame on: paint nothing until then.
|
||||
if self.fonts_set {
|
||||
self.fonts_ready = true;
|
||||
} else {
|
||||
SheetApp::setup_context(ctx, self.ui.dark);
|
||||
SheetApp::setup_context_for_language(ctx, self.ui.dark, self.ui.language);
|
||||
self.fonts_set = true;
|
||||
self.fonts_language = Some(self.ui.language);
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1268,7 +1268,8 @@ fn page_layout(app: &mut SheetApp, ui: &mut Ui) {
|
|||
.unwrap_or((true, true, false, false));
|
||||
// These two checkboxes mean the screen view vs. the printed page, so they do not share the
|
||||
// "View" tab label; pick per language rather than through the shared table.
|
||||
let (screen, print) = if app.ui.language == crate::i18n::Language::Ja { ("画面", "印刷") } else { ("View", "Print") };
|
||||
let screen = app.ui.language.tr("Screen");
|
||||
let print = app.ui.language.tr("Print");
|
||||
ui.vertical(|ui| {
|
||||
ui.label(egui::RichText::new(app.ui.language.tr("Gridlines")).strong().small());
|
||||
let mut v = sh.0;
|
||||
|
|
@ -1588,8 +1589,7 @@ fn view(app: &mut SheetApp, ui: &mut Ui) {
|
|||
|ui| {
|
||||
for l in crate::i18n::Language::ALL {
|
||||
if ui.selectable_label(l == lang, l.name()).clicked() {
|
||||
let code = if l == crate::i18n::Language::Ja { "app.language.japanese" } else { "app.language.english" };
|
||||
act(app, code, json!({}));
|
||||
act(app, "app.language.set", json!({"language": l.code()}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -289,12 +289,18 @@ fn candidates(role: &str) -> Vec<(String, u32)> {
|
|||
|
||||
/// Builds the font set from system fonts (with egui's defaults as fallback).
|
||||
pub fn font_definitions() -> FontDefinitions {
|
||||
font_definitions_for_language(crate::i18n::Language::system())
|
||||
}
|
||||
|
||||
/// Builds the font set with CJK glyph shapes ordered for the selected interface language.
|
||||
pub fn font_definitions_for_language(language: crate::i18n::Language) -> FontDefinitions {
|
||||
let mut fonts = FontDefinitions::default();
|
||||
let base_prop: Vec<String> = fonts.families.get(&FontFamily::Proportional).cloned().unwrap_or_default();
|
||||
let base_mono: Vec<String> = fonts.families.get(&FontFamily::Monospace).cloned().unwrap_or_default();
|
||||
// CJK has to sit at the end of each chain: a Latin font owns Latin, and epaint picks the first
|
||||
// face that has the glyph, so the CJK faces only ever catch Han/Kana/Hangul.
|
||||
let cjk = load_cjk(&mut fonts);
|
||||
// face that has the glyph, so the CJK faces only ever catch Han/Kana/Hangul. Keep both
|
||||
// simplified-Chinese and Japanese Han faces: either font may lack a less common glyph.
|
||||
let cjk = load_cjk(&mut fonts, language);
|
||||
for role in [UI, UI_BOLD, CELL, CELL_BOLD, CELL_ITALIC, CELL_BOLD_ITALIC, SERIF, MONO] {
|
||||
let paths = candidates(role);
|
||||
let refs: Vec<(&str, u32)> = paths.iter().map(|(p, i)| (p.as_str(), *i)).collect();
|
||||
|
|
@ -324,40 +330,64 @@ pub fn font_definitions() -> FontDefinitions {
|
|||
/// family can carry them as fallback. egui's bundled fonts have no CJK coverage, so without this a
|
||||
/// Japanese or Chinese workbook renders as tofu boxes even though the text was read correctly.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn load_cjk(fonts: &mut FontDefinitions) -> Vec<String> {
|
||||
let mut keys = Vec::new();
|
||||
for (key, cands) in [("sys-cjk-han", cjk_han()), ("sys-cjk-hangul", cjk_hangul())] {
|
||||
let refs: Vec<(&str, u32)> = cands.iter().map(|(p, i)| (p.as_str(), *i)).collect();
|
||||
if let Some(fd) = try_load(&refs) {
|
||||
fonts.font_data.insert(key.to_string(), Arc::new(fd));
|
||||
keys.push(key.to_string());
|
||||
}
|
||||
}
|
||||
keys
|
||||
fn load_cjk(fonts: &mut FontDefinitions, language: crate::i18n::Language) -> Vec<String> {
|
||||
let han_ja = load_cjk_face(fonts, "sys-cjk-han-ja", cjk_han_japanese());
|
||||
let han_zh = load_cjk_face(fonts, "sys-cjk-han-zh", cjk_han_simplified());
|
||||
let hangul = load_cjk_face(fonts, "sys-cjk-hangul", cjk_hangul());
|
||||
let order = match language {
|
||||
crate::i18n::Language::Ja => [han_ja, han_zh, hangul],
|
||||
crate::i18n::Language::Ko => [hangul, han_zh, han_ja],
|
||||
_ => [han_zh, han_ja, hangul],
|
||||
};
|
||||
order.into_iter().flatten().collect()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn load_cjk(_fonts: &mut FontDefinitions) -> Vec<String> {
|
||||
fn load_cjk(_fonts: &mut FontDefinitions, _language: crate::i18n::Language) -> Vec<String> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Han + Kana faces, in fallback order. A Japanese face comes first (kana and JIS kanji render
|
||||
/// with Japanese glyph shapes); a Chinese face follows to widen coverage. Every CJK face carries
|
||||
/// kana, so any of them renders Japanese; the order only decides kanji glyph style.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn cjk_han() -> Vec<(String, u32)> {
|
||||
let mac = "/System/Library/Fonts";
|
||||
fn load_cjk_face(fonts: &mut FontDefinitions, key: &str, candidates: Vec<(String, u32)>) -> Option<String> {
|
||||
let refs: Vec<(&str, u32)> = candidates.iter().map(|(path, index)| (path.as_str(), *index)).collect();
|
||||
let fd = try_load(&refs)?;
|
||||
fonts.font_data.insert(key.to_string(), Arc::new(fd));
|
||||
Some(key.to_string())
|
||||
}
|
||||
|
||||
/// Japanese Han/Kana faces. A Japanese face goes first when the interface is Japanese so kanji
|
||||
/// use Japanese glyph shapes; Chinese remains later in the chain for glyphs it does not contain.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn cjk_han_japanese() -> Vec<(String, u32)> {
|
||||
let win = std::env::var("WINDIR").map(|w| format!("{w}\\Fonts")).unwrap_or_else(|_| "C:\\Windows\\Fonts".into());
|
||||
let lin = ["/usr/share/fonts/opentype/noto", "/usr/share/fonts/truetype/noto", "/usr/share/fonts/truetype", "/usr/share/fonts"];
|
||||
let mut v: Vec<(String, u32)> = Vec::new();
|
||||
v.push((format!("{win}\\YuGothR.ttc"), 0)); // Yu Gothic (Japanese)
|
||||
v.push((format!("{win}\\msgothic.ttc"), 0)); // MS Gothic (Japanese)
|
||||
v.push((format!("{win}\\msyh.ttc"), 0)); // Microsoft YaHei (Chinese)
|
||||
v.push((format!("{mac}/Hiragino Sans GB.ttc"), 0));
|
||||
v.push((format!("{mac}/PingFang.ttc"), 0));
|
||||
v.push(("/System/Library/Fonts/Hiragino Sans W3.ttc".into(), 0));
|
||||
v.push(("/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc".into(), 0));
|
||||
let lin = ["/usr/share/fonts/opentype/noto", "/usr/share/fonts/truetype/noto", "/usr/share/fonts/truetype", "/usr/share/fonts"];
|
||||
for d in lin {
|
||||
v.push((format!("{d}/NotoSansCJK-Regular.ttc"), 0));
|
||||
v.push((format!("{d}/NotoSansCJKjp-Regular.otf"), 0));
|
||||
v.push((format!("{d}/NotoSansCJK-Regular.ttc"), 0));
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
/// Simplified Chinese Han faces. These are loaded separately from Japanese fonts so a missing
|
||||
/// glyph in the first face can continue through the family chain instead of becoming tofu.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn cjk_han_simplified() -> Vec<(String, u32)> {
|
||||
let win = std::env::var("WINDIR").map(|w| format!("{w}\\Fonts")).unwrap_or_else(|_| "C:\\Windows\\Fonts".into());
|
||||
let mut v: Vec<(String, u32)> = Vec::new();
|
||||
v.push((format!("{win}\\msyh.ttc"), 0)); // Microsoft YaHei
|
||||
v.push((format!("{win}\\simsun.ttc"), 0)); // SimSun
|
||||
v.push((format!("{win}\\simhei.ttf"), 0)); // SimHei
|
||||
v.push(("/System/Library/Fonts/PingFang.ttc".into(), 0));
|
||||
v.push(("/System/Library/Fonts/Hiragino Sans GB.ttc".into(), 0));
|
||||
let lin = ["/usr/share/fonts/opentype/noto", "/usr/share/fonts/truetype/noto", "/usr/share/fonts/truetype", "/usr/share/fonts"];
|
||||
for d in lin {
|
||||
v.push((format!("{d}/NotoSansCJKsc-Regular.otf"), 0));
|
||||
v.push((format!("{d}/NotoSansCJK-Regular.ttc"), 2)); // Simplified Chinese face in the Noto collection
|
||||
v.push((format!("{d}/wqy-microhei.ttc"), 0));
|
||||
}
|
||||
v
|
||||
|
|
@ -374,7 +404,7 @@ fn cjk_hangul() -> Vec<(String, u32)> {
|
|||
v.push((format!("{mac}/AppleSDGothicNeo.ttc"), 0));
|
||||
for d in lin {
|
||||
v.push((format!("{d}/NotoSansCJKkr-Regular.otf"), 0));
|
||||
v.push((format!("{d}/NotoSansCJK-Regular.ttc"), 0));
|
||||
v.push((format!("{d}/NotoSansCJK-Regular.ttc"), 1)); // Korean face in the Noto collection
|
||||
}
|
||||
v
|
||||
}
|
||||
|
|
@ -423,32 +453,55 @@ mod tests {
|
|||
fn cjk_fallback_is_appended_to_every_family() {
|
||||
// Structural: whatever loaded, the CJK keys must sit at the end of each chain (after the
|
||||
// Latin/default fonts) so Latin still wins for Latin text.
|
||||
let fonts = font_definitions();
|
||||
let cjk_keys: Vec<String> =
|
||||
["sys-cjk-han", "sys-cjk-hangul"].into_iter().filter(|k| fonts.font_data.contains_key(*k)).map(str::to_string).collect();
|
||||
let language = crate::i18n::Language::Zh;
|
||||
let fonts = font_definitions_for_language(language);
|
||||
let cjk_keys: Vec<String> = ["sys-cjk-han-zh", "sys-cjk-han-ja", "sys-cjk-hangul"]
|
||||
.into_iter()
|
||||
.filter(|key| fonts.font_data.contains_key(*key))
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
if cjk_keys.is_empty() {
|
||||
return; // no CJK face on this machine (minimal Linux)
|
||||
}
|
||||
for role in [UI, UI_BOLD, CELL, CELL_BOLD, CELL_ITALIC, CELL_BOLD_ITALIC, SERIF, MONO] {
|
||||
let chain = fonts.families.get(&FontFamily::Name(role.into())).map(Vec::as_slice).unwrap_or_default();
|
||||
assert!(chain.len() >= cjk_keys.len(), "{role} chain too short for its CJK fallback");
|
||||
let tail = &chain[chain.len() - cjk_keys.len()..];
|
||||
assert_eq!(tail, cjk_keys.as_slice(), "{role} must end its chain with the CJK faces");
|
||||
let first_cjk = chain.iter().position(|key| key.starts_with("sys-cjk-")).unwrap_or(chain.len());
|
||||
let tail = &chain[first_cjk..];
|
||||
assert_eq!(tail, cjk_keys.as_slice(), "{role} must end its chain with CJK faces in the language order");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cjk_fallback_covers_japanese() {
|
||||
// Real render check: the proportional family (what cells and the UI use) must resolve
|
||||
// Japanese kana/kanji to a glyph, not the tofu replacement char.
|
||||
if cjk_han().iter().all(|(p, _)| std::fs::read(p).is_err()) {
|
||||
return; // no CJK face on this machine (minimal Linux); nothing to assert
|
||||
fn system_fallback_covers_supported_scripts() {
|
||||
let cases = [
|
||||
(crate::i18n::Language::Zh, cjk_han_simplified(), &["页面布局", "自动化", "粘贴格式"][..]),
|
||||
(crate::i18n::Language::Ja, cjk_han_japanese(), &["カテゴリ", "貼り付け"][..]),
|
||||
(crate::i18n::Language::Ko, cjk_hangul(), &["한국어"][..]),
|
||||
];
|
||||
for (language, paths, samples) in cases {
|
||||
if paths.iter().all(|(path, _)| std::fs::read(path).is_err()) {
|
||||
continue; // this machine has no system face for this script
|
||||
}
|
||||
let ctx = egui::Context::default();
|
||||
ctx.set_fonts(font_definitions_for_language(language));
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |_ui| {});
|
||||
out.textures_delta.clear(); // nothing consumes the atlas in a headless test
|
||||
for sample in samples {
|
||||
let covered = ctx.fonts_mut(|f| f.has_glyphs(&FontId::proportional(14.0), sample));
|
||||
assert!(covered, "{sample:?} must render with a real glyph, not tofu");
|
||||
}
|
||||
if language == crate::i18n::Language::Zh {
|
||||
let cell_font = FontId::new(14.0, FontFamily::Name(CELL.into()));
|
||||
let covered = ctx.fonts_mut(|f| f.has_glyphs(&cell_font, "客户端配置 中文字体测试"));
|
||||
assert!(covered, "Chinese workbook text must render with a real glyph, not tofu");
|
||||
}
|
||||
}
|
||||
let ctx = egui::Context::default();
|
||||
ctx.set_fonts(font_definitions());
|
||||
ctx.set_fonts(font_definitions_for_language(crate::i18n::Language::Ru));
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |_ui| {});
|
||||
out.textures_delta.clear(); // nothing consumes the atlas in a headless test
|
||||
let covered = ctx.fonts_mut(|f| f.has_glyphs(&FontId::proportional(14.0), "カテゴリ"));
|
||||
assert!(covered, "Japanese text must render with a real glyph, not tofu");
|
||||
out.textures_delta.clear();
|
||||
let cyrillic = ctx.fonts_mut(|f| f.has_glyphs(&ui_font(14.0), "Русский"));
|
||||
assert!(cyrillic, "Russian interface text must render with a real glyph, not tofu");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,15 +102,16 @@ fn renders_every_ribbon_tab_without_panicking() {
|
|||
fn language_switch_translates_the_ribbon_and_persists() {
|
||||
use gridcraft_ui_egui::i18n::Language;
|
||||
let mut h = harness(blank());
|
||||
// English by default on a machine whose system language is not Japanese.
|
||||
assert_ne!(h.state().ui.language, Language::Ja);
|
||||
h.state_mut().run("app.language.japanese", json!({})).unwrap();
|
||||
h.run_steps(2); // the ribbon renders the Japanese tabs without panicking
|
||||
assert_eq!(h.state().ui.language, Language::Ja);
|
||||
// The preference round-trips through the saved UiState.
|
||||
let restored: gridcraft_ui_egui::UiState = serde_json::from_str(&serde_json::to_string(&h.state().ui).unwrap()).unwrap();
|
||||
assert_eq!(restored.language, Language::Ja);
|
||||
h.state_mut().run("app.language.english", json!({})).unwrap();
|
||||
assert_eq!(h.state().ui.language, Language::system());
|
||||
for (language, expected) in [(Language::Zh, "数据"), (Language::Ja, "データ"), (Language::Ko, "데이터"), (Language::Ru, "Данные")] {
|
||||
h.state_mut().run("app.language.set", json!({"language": language.code()})).unwrap();
|
||||
h.run_steps(2); // every locale renders the ribbon without panicking
|
||||
assert_eq!(h.state().ui.language, language);
|
||||
assert_eq!(language.tr("Data"), expected);
|
||||
let restored: gridcraft_ui_egui::UiState = serde_json::from_str(&serde_json::to_string(&h.state().ui).unwrap()).unwrap();
|
||||
assert_eq!(restored.language, language);
|
||||
}
|
||||
h.state_mut().run("app.language.set", json!({"language": "en"})).unwrap();
|
||||
assert_eq!(h.state().ui.language, Language::En);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue