mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-14 19:26:59 -04:00
i18n: Korean (ko) interface catalog (#508)
Adds Korean as an interface language: Edit ▸ Language ▸ 한국어, Settings ▸ General ▸ Interface Language, and System Language for ko / ko-KR locales. - crates/ui-egui/src/i18n/ko.tsv: 3,573 entries, the Spanish catalog's sources plus the macOS Settings item, written clean-room from the plain meaning of the English text (sidecar ko.tsv.attribution, ATTRIBUTION.md row). - Hangul is drawn with the installed system face cjk.rs already adds (Malgun Gothic, Apple SD Gothic Neo, Noto Sans CJK KR…); switching is refused with a message when there is none, like Chinese. - Test: korean_covers_the_menus_and_has_no_dead_entries. - Docs: localization-parity, ROADMAP, ui-egui README. Native-speaker review welcome. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013XWUReif4gqcjU5GsjYioc
This commit is contained in:
1 parent
71438c627b
commit
b97bb74330
11 files changed
+3657
-5
No files matched your search
@@ -10,6 +10,7 @@ iconography, images or other Adobe assets; open licences only; every asset attri
|
||||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `crates/ui-egui/src/i18n/id.tsv` | FilmCraft contributors | Original work: clean-room Indonesian interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/ko.tsv` | FilmCraft contributors | Original work: clean-room Korean interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/fr.tsv` | FilmCraft contributors | Original work: clean-room French interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/pt-br.tsv` | FilmCraft contributors | Original work: clean-room Brazilian Portuguese interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/ru.tsv` | FilmCraft contributors | Original work: clean-room Russian interface translations | MIT OR Apache-2.0 |
|
||||
|
||||
@@ -84,6 +84,7 @@ Premiere ships 10 interface languages. Measured from our catalogs (3,498 strings
|
||||
| French | partial (whole interface; native review welcome) | ~100% |
|
||||
| Portuguese (Brazil) | partial (whole interface; native review welcome) | ~100% |
|
||||
| Indonesian | partial (whole interface; native review welcome) | ~100% |
|
||||
| Korean | partial (whole interface; needs an installed Hangul font; native review welcome) | ~100% |
|
||||
| Japanese | partial (whole interface) | 99.7% |
|
||||
| German | menus only (native-reviewed) | 10.6% |
|
||||
| Korean | none | 0% |
|
||||
|
||||
@@ -131,6 +131,8 @@ pub fn install(app: &FilmcraftApp, ctx: egui::Context) -> (Receiver<String>, Men
|
||||
filmcraft_ui_egui::i18n::Language::Fr
|
||||
} else if checked("app.language.indonesian") {
|
||||
filmcraft_ui_egui::i18n::Language::Id
|
||||
} else if checked("app.language.korean") {
|
||||
filmcraft_ui_egui::i18n::Language::Ko
|
||||
} else {
|
||||
filmcraft_ui_egui::i18n::Language::En
|
||||
};
|
||||
|
||||
@@ -1007,6 +1007,7 @@ static CATEGORIES: &[Category] = &[
|
||||
("ru", "Русский"),
|
||||
("fr", "Français"),
|
||||
("id", "Bahasa Indonesia"),
|
||||
("ko", "한국어"),
|
||||
]),
|
||||
true,
|
||||
),
|
||||
|
||||
@@ -18,7 +18,7 @@ the committed span and undo. Set `FILMCRAFT_UI_SNAPSHOT_DIR` to render screensho
|
||||
## Localisation
|
||||
|
||||
Edit > Language offers English, Japanese, Spanish, Brazilian Portuguese, Ukrainian, Simplified
|
||||
Chinese, German, Russian, French and Indonesian (which covers the whole interface). The language is stored in the engine's `general.interfaceLanguage` preference,
|
||||
Chinese, German, Russian, French, Indonesian and Korean (which cover the whole interface). The language is stored in the engine's `general.interfaceLanguage` preference,
|
||||
restored on startup, and also available in Settings > General.
|
||||
Its default, System Language (`system`), follows the operating system: the first of the user's
|
||||
preferred languages that the interface has (the host supplies them through
|
||||
|
||||
@@ -55,6 +55,12 @@ fn find_system_font(sample: &str, preferred: &[&str]) -> Option<Arc<FontData>> {
|
||||
|
||||
/// Whether [`install`] has a Chinese face to add: craft-fonts Hans/Hant faces, or an installed
|
||||
/// system face (scanned once per process). The Simplified Chinese interface needs one.
|
||||
/// Whether [`install`] has a Hangul face to add (an installed system face; none is bundled).
|
||||
/// The Korean interface needs one.
|
||||
pub(crate) fn korean_available() -> bool {
|
||||
korean_font().is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn available() -> bool {
|
||||
craft_fonts().next().is_some() || system_font().is_some()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,8 @@
|
||||
asset: ko.tsv
|
||||
title: Korean interface translation catalog
|
||||
author: FilmCraft contributors
|
||||
source: original work
|
||||
license: MIT OR Apache-2.0
|
||||
license-file: LICENSE-MIT and LICENSE-APACHE
|
||||
added: 2026-10-13
|
||||
notes: Original English-to-Korean interface translations using clean-room NLE vocabulary.
|
||||
@@ -46,6 +46,8 @@ pub enum Language {
|
||||
Ru,
|
||||
Fr,
|
||||
Id,
|
||||
/// Korean; Hangul needs an installed system font ([`korean_font_available`]).
|
||||
Ko,
|
||||
}
|
||||
|
||||
static JAPANESE: OnceLock<Catalog> = OnceLock::new();
|
||||
@@ -56,10 +58,11 @@ static CHINESE: OnceLock<Catalog> = OnceLock::new();
|
||||
static GERMAN: OnceLock<Catalog> = OnceLock::new();
|
||||
static RUSSIAN: OnceLock<Catalog> = OnceLock::new();
|
||||
static FRENCH: OnceLock<Catalog> = OnceLock::new();
|
||||
static KOREAN: OnceLock<Catalog> = OnceLock::new();
|
||||
static INDONESIAN: OnceLock<Catalog> = OnceLock::new();
|
||||
|
||||
impl Language {
|
||||
pub const ALL: [Self; 10] = [Self::En, Self::Ja, Self::Es, Self::PtBr, Self::Uk, Self::ZhCn, Self::De, Self::Ru, Self::Fr, Self::Id];
|
||||
pub const ALL: [Self; 11] = [Self::En, Self::Ja, Self::Es, Self::PtBr, Self::Uk, Self::ZhCn, Self::De, Self::Ru, Self::Fr, Self::Id, Self::Ko];
|
||||
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
@@ -73,6 +76,7 @@ impl Language {
|
||||
Self::Ru => "Русский",
|
||||
Self::Fr => "Français",
|
||||
Self::Id => "Bahasa Indonesia",
|
||||
Self::Ko => "한국어",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +92,7 @@ impl Language {
|
||||
"ru" => Some(Self::Ru),
|
||||
"fr" => Some(Self::Fr),
|
||||
"id" => Some(Self::Id),
|
||||
"ko" => Some(Self::Ko),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -101,7 +106,7 @@ impl Language {
|
||||
/// only for Simplified locales (`zh`, `zh-CN`, `zh-SG`, `zh-Hans…`); Traditional ones (`zh-TW`,
|
||||
/// `zh-HK`, `zh-MO`, `zh-Hant…`) are skipped.
|
||||
pub fn from_locales(tags: &[String]) -> Self {
|
||||
const PRIMARY: [(&str, Language); 10] = [
|
||||
const PRIMARY: [(&str, Language); 11] = [
|
||||
("en", Language::En),
|
||||
("ja", Language::Ja),
|
||||
("es", Language::Es),
|
||||
@@ -112,6 +117,7 @@ impl Language {
|
||||
("fr", Language::Fr),
|
||||
("id", Language::Id),
|
||||
("in", Language::Id),
|
||||
("ko", Language::Ko),
|
||||
];
|
||||
tags.iter()
|
||||
.find_map(|tag| {
|
||||
@@ -142,6 +148,7 @@ impl Language {
|
||||
Self::Ru => "ru",
|
||||
Self::Fr => "fr",
|
||||
Self::Id => "id",
|
||||
Self::Ko => "ko",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +165,7 @@ impl Language {
|
||||
Self::Ru => Some(RUSSIAN.get_or_init(|| Catalog::parse(include_str!("ru.tsv")))),
|
||||
Self::Fr => Some(FRENCH.get_or_init(|| Catalog::parse(include_str!("fr.tsv")))),
|
||||
Self::Id => Some(INDONESIAN.get_or_init(|| Catalog::parse(include_str!("id.tsv")))),
|
||||
Self::Ko => Some(KOREAN.get_or_init(|| Catalog::parse(include_str!("ko.tsv")))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,6 +321,13 @@ pub fn chinese_font_available() -> bool {
|
||||
crate::cjk::available()
|
||||
}
|
||||
|
||||
/// Whether the interface can show Korean: `theme::install` adds an installed system Hangul face
|
||||
/// (Malgun Gothic, Apple SD Gothic Neo, Noto Sans CJK KR…, see `cjk.rs`) when there is one; none
|
||||
/// is bundled. False on systems (and the web) without one.
|
||||
pub fn korean_font_available() -> bool {
|
||||
crate::cjk::korean_available()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -329,6 +344,7 @@ mod tests {
|
||||
("ru", include_str!("ru.tsv")),
|
||||
("fr", include_str!("fr.tsv")),
|
||||
("id", include_str!("id.tsv")),
|
||||
("ko", include_str!("ko.tsv")),
|
||||
] {
|
||||
let (entries, errors) = catalog::parse_entries(text);
|
||||
assert!(errors.is_empty(), "{code}: {errors:?}");
|
||||
@@ -1094,6 +1110,32 @@ mod tests {
|
||||
set_current(Language::En);
|
||||
}
|
||||
|
||||
/// Korean covers the whole interface (the Spanish catalog's entries and the native macOS
|
||||
/// Settings menu); like French, the tests require the core menus, and every entry must still
|
||||
/// have a Spanish counterpart so a renamed or removed source can't leave a dead Korean entry.
|
||||
#[test]
|
||||
fn korean_covers_the_menus_and_has_no_dead_entries() {
|
||||
let (entries, errors) = catalog::parse_entries(include_str!("ko.tsv"));
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
for (ctx, en, _) in &entries {
|
||||
assert!(ctx.is_empty() && (Language::Es.has(en) || en == "Settings"), "Korean entry without a Spanish one: {ctx:?} {en:?}");
|
||||
}
|
||||
for en in core_menu_labels() {
|
||||
assert!(Language::Ko.has(&en), "missing Korean menu label: {en}");
|
||||
}
|
||||
for en in crate::menus::MENUS.into_iter().chain(["Audio", "Settings"]) {
|
||||
assert!(Language::Ko.has(en), "untranslated Korean menu: {en}");
|
||||
}
|
||||
for (en, ko) in [("File", "파일"), ("Edit", "편집"), ("Sequence", "시퀀스"), ("Settings", "설정")] {
|
||||
assert_eq!(Language::Ko.tr(en), ko);
|
||||
}
|
||||
assert_eq!(Language::Ko.name(), "한국어");
|
||||
assert_eq!(Language::parse("ko"), Some(Language::Ko));
|
||||
for tag in ["ko", "ko-KR", "ko_KR.UTF-8"] {
|
||||
assert_eq!(Language::from_locales(&[tag.to_string()]), Language::Ko, "{tag}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn russian_entries_cover_the_original_menu_catalog() {
|
||||
let app = crate::FilmcraftApp::new(filmcraft_engine::Session::default());
|
||||
|
||||
@@ -36,6 +36,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[
|
||||
uic!("app.language.russian", "Русский", ["Edit", "Language"], None),
|
||||
uic!("app.language.french", "Français", ["Edit", "Language"], None),
|
||||
uic!("app.language.indonesian", "Bahasa Indonesia", ["Edit", "Language"], None),
|
||||
uic!("app.language.korean", "한국어", ["Edit", "Language"], None),
|
||||
uic!("source.playback.toggle", "Source Play/Stop", [], None),
|
||||
uic!("source.playback.play", "Play Source", [], None),
|
||||
uic!("source.playback.stop", "Stop Source", [], None),
|
||||
@@ -195,6 +196,7 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params:
|
||||
| "app.language.russian"
|
||||
| "app.language.french"
|
||||
| "app.language.indonesian"
|
||||
| "app.language.korean"
|
||||
) {
|
||||
// Japanese needs the craft-fonts (built with CRAFT_FONTS_DIR) or a font installed on the system
|
||||
if id == "app.language.japanese" && !crate::i18n::install_japanese_font(ctx) {
|
||||
@@ -204,6 +206,10 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params:
|
||||
if id == "app.language.chinese" && !crate::i18n::chinese_font_available() {
|
||||
return Err("no Chinese font is installed on this system (for example Noto Sans CJK SC or Microsoft YaHei); the interface stays in English".into());
|
||||
}
|
||||
// Korean needs an installed Hangul font (none is bundled; see cjk.rs)
|
||||
if id == "app.language.korean" && !crate::i18n::korean_font_available() {
|
||||
return Err("no Korean font is installed on this system (for example Malgun Gothic or Noto Sans CJK KR); the interface stays in English".into());
|
||||
}
|
||||
let language = match id {
|
||||
"app.language.japanese" => crate::i18n::Language::Ja,
|
||||
"app.language.spanish" => crate::i18n::Language::Es,
|
||||
@@ -214,6 +220,7 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params:
|
||||
"app.language.russian" => crate::i18n::Language::Ru,
|
||||
"app.language.french" => crate::i18n::Language::Fr,
|
||||
"app.language.indonesian" => crate::i18n::Language::Id,
|
||||
"app.language.korean" => crate::i18n::Language::Ko,
|
||||
_ => crate::i18n::Language::En,
|
||||
};
|
||||
// The preference is updated in memory before it is written, so a failed write (read-only
|
||||
@@ -674,6 +681,7 @@ pub fn menu_items(app: &FilmcraftApp) -> Vec<MenuItem> {
|
||||
"app.language.russian" => it.checked = Some(app.ui.language == crate::i18n::Language::Ru),
|
||||
"app.language.french" => it.checked = Some(app.ui.language == crate::i18n::Language::Fr),
|
||||
"app.language.indonesian" => it.checked = Some(app.ui.language == crate::i18n::Language::Id),
|
||||
"app.language.korean" => it.checked = Some(app.ui.language == crate::i18n::Language::Ko),
|
||||
_ => {}
|
||||
}
|
||||
if it.id.starts_with("view.") {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (German ships the menus, #630) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Korean covers the whole interface, #508) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
|
||||
|
||||
Per-language status of FilmCraft's interface. Premiere 26.5.2 ships 10 interface languages (the
|
||||
installed bundle's `.lproj` folders): German, English, Spanish, French, Italian, Japanese, Korean,
|
||||
@@ -51,7 +51,7 @@ whole interface, one PR) at ~4–8 h per language for the catalog plus tests.
|
||||
| Indonesian | id | 3,541 (same entries as Spanish, plus the macOS Settings item); must cover the core menus, no entry without a Spanish one | as Spanish | Latin (bundled fonts); System Language also maps the legacy `in` code | not recorded; welcome | partial (#867) | review |
|
||||
| Japanese | ja | 3,487 (99.7%) | as zh-cn; system-font kanji may pick the Chinese fallback (#547) | CJK fonts, vertical title text | not recorded | partial | 4–8 h + review |
|
||||
| German | de | 371 (10.6%) | menus only | Latin | yes (#630) | menus only | 5–9 h + review |
|
||||
| Korean | ko | 0 (0%) | — | needs Hangul fonts in craft-fonts and IME testing | — | none (requested #508) | 10–20 h + review |
|
||||
| Korean | ko | 3,573 (same entries as Spanish, plus the macOS Settings item) | as Spanish | installed system Hangul font (Malgun Gothic, Apple SD Gothic Neo, Noto Sans CJK KR…); refused without one | not recorded; welcome | partial (#508) | review; Hangul in craft-fonts for the web |
|
||||
| Vietnamese | vi | 0 (0%) | — | Latin with stacked diacritics (font coverage to check) | — | none | 6–12 h + review |
|
||||
| *Other shipped:* Ukrainian | uk | 351 (10.0%) | menus only | Cyrillic (bundled) | not recorded | menus only | 5–9 h |
|
||||
|
||||
|
||||
Reference in new issue
Block a user