mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
text, ui: follow a language change, and resolve the language before the fonts
Review follow-ups on #312: - `crates/ui-egui/src/theme.rs`'s `japanese_font_tests` still called `install` with two arguments, so `cargo test` did not compile. Every call site now passes a language. - The call sites passed the stored `general.language` preference. With the default `system` that is `""`, which handed the CJK choice back to the environment locale — one macOS and Windows usually do not set. They now pass `crate::i18n::language(app)`, the resolved language, so Match System draws Chinese on a Chinese system. - Changing Settings ▸ General ▸ Language only reached the fonts on the first frame. `EffectcraftApp` now remembers the language its fonts were installed for and installs them again when it changes, so the switch applies without a restart. - Documented that `fonts::set_cjk_locale` is process-global: the Han preference therefore applies to composition text as well as the interface chrome (docs/preferences.md and the function's own comment). Rebased onto main after #309 landed — `header.rs` gained an `install` call site in the meantime, and the catalogs now resolve through `i18n::language`.
This commit is contained in:
1 parent
a4d1e8d0b3
commit
ad3da3c8de
11 files changed
+32
-19
No files matched your search
@@ -576,7 +576,8 @@ static REQUESTED_CJK_LOCALE: RwLock<Option<String>> = RwLock::new(None);
|
||||
/// environment, so a Chinese interface is drawn with a Chinese face on a machine whose locale is
|
||||
/// Japanese (and the other way round). The interface sets this from its language setting; an empty
|
||||
/// tag, `c` or `posix` clears the request and returns to the environment. Process-global, like the
|
||||
/// environment it replaces.
|
||||
/// environment it replaces: every layout in the process follows it, so composition text is drawn
|
||||
/// with the same language's Han forms as the interface, not only the interface chrome.
|
||||
pub fn set_cjk_locale(tag: &str) {
|
||||
let tag = tag.trim().to_ascii_lowercase();
|
||||
let requested = if tag.is_empty() || tag == "c" || tag == "posix" { None } else { Some(tag) };
|
||||
|
||||
@@ -615,7 +615,7 @@ mod tests {
|
||||
fn long_press_type_keeps_the_tool_until_vertical_is_selected() {
|
||||
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install(&ctx, &app.tokens);
|
||||
crate::theme::install(&ctx, &app.tokens, crate::i18n::language(app));
|
||||
frame(&mut app, &ctx, 0.0, vec![]);
|
||||
frame(&mut app, &ctx, 0.1, vec![]);
|
||||
let center = |element: &crate::automation::Element| {
|
||||
|
||||
@@ -2244,7 +2244,7 @@ mod tests {
|
||||
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
let ctx = egui::Context::default();
|
||||
let tokens = crate::theme::Tokens::for_kind(crate::theme::ThemeKind::Dark);
|
||||
crate::theme::install(&ctx, &tokens, &app.session.prefs.general.language);
|
||||
crate::theme::install(&ctx, &tokens, language(app));
|
||||
crate::panels::settings::open(&mut app, "general");
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |_| {
|
||||
crate::panels::settings::show(&mut app, &ctx, &tokens);
|
||||
|
||||
@@ -212,6 +212,8 @@ pub struct EffectcraftApp {
|
||||
last_time: f64,
|
||||
styled: bool,
|
||||
fonts_ready: bool,
|
||||
/// The interface language the fonts were installed for; `""` until the first frame.
|
||||
styled_language: &'static str,
|
||||
pub(crate) control_rx: Option<Receiver<ControlRequest>>,
|
||||
pub(crate) deferred: Vec<(ControlRequest, f64)>,
|
||||
pub(crate) synthetic: Vec<egui::Event>,
|
||||
@@ -298,6 +300,7 @@ impl EffectcraftApp {
|
||||
last_time: 0.0,
|
||||
styled: false,
|
||||
fonts_ready: false,
|
||||
styled_language: "",
|
||||
control_rx: None,
|
||||
deferred: vec![],
|
||||
synthetic: vec![],
|
||||
@@ -1568,10 +1571,16 @@ impl eframe::App for EffectcraftApp {
|
||||
|
||||
fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
self.apply_gpu_failure(ctx);
|
||||
if !self.styled {
|
||||
theme::install(ctx, &self.tokens, &self.session.prefs.general.language);
|
||||
fit_window(ctx);
|
||||
self.styled = true;
|
||||
// The CJK fallback fonts depend on the interface language, so they follow a change of
|
||||
// Settings ▸ General ▸ Language instead of only the first frame.
|
||||
let language = crate::i18n::language(self);
|
||||
if !self.styled || self.styled_language != language {
|
||||
theme::install(ctx, &self.tokens, language);
|
||||
self.styled_language = language;
|
||||
if !self.styled {
|
||||
fit_window(ctx);
|
||||
self.styled = true;
|
||||
}
|
||||
ctx.request_repaint();
|
||||
} else {
|
||||
self.fonts_ready = true;
|
||||
|
||||
@@ -548,7 +548,7 @@ mod tests {
|
||||
fn app() -> (EffectcraftApp, egui::Context) {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
crate::theme::install(&ctx, &app.tokens, &app.session.prefs.general.language);
|
||||
crate::theme::install(&ctx, &app.tokens, crate::i18n::language(app));
|
||||
app.session.execute("comp.new", json!({"name": "Main", "width": 640, "height": 360})).unwrap();
|
||||
(app, ctx)
|
||||
}
|
||||
|
||||
@@ -844,7 +844,7 @@ mod tests {
|
||||
fn premiere_export_dialog() {
|
||||
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install(&ctx, &app.tokens, &app.session.prefs.general.language);
|
||||
crate::theme::install(&ctx, &app.tokens, crate::i18n::language(app));
|
||||
app.session.execute("comp.new", json!({"name": "Spot", "width": 64, "height": 36, "frameRate": 25, "duration": 1})).unwrap();
|
||||
app.session.execute("layer.newSolid", json!({"color": "#ff0000"})).unwrap();
|
||||
crate::menus::invoke(&mut app, &ctx, "file.exportTimeline", json!({})).unwrap();
|
||||
|
||||
@@ -540,7 +540,7 @@ mod tests {
|
||||
s.prefs.push_recent("/projects/Alpha.ecproj");
|
||||
let mut app = EffectcraftApp::new(s);
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install(&ctx, &app.tokens, &app.session.prefs.general.language);
|
||||
crate::theme::install(&ctx, &app.tokens, crate::i18n::language(app));
|
||||
ctx.run_ui(Default::default(), |_| {}).textures_delta.clear();
|
||||
ctx.run_ui(Default::default(), |ui| {
|
||||
app.auto.begin_frame();
|
||||
@@ -573,7 +573,7 @@ mod tests {
|
||||
app.ui.start_screen = true;
|
||||
app.ui.home_learn = true;
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install(&ctx, &app.tokens, &app.session.prefs.general.language);
|
||||
crate::theme::install(&ctx, &app.tokens, crate::i18n::language(app));
|
||||
let frame = |app: &mut EffectcraftApp| {
|
||||
ctx.run_ui(Default::default(), |ui| {
|
||||
app.auto.begin_frame();
|
||||
|
||||
@@ -194,7 +194,7 @@ mod tests {
|
||||
fn gallery_lists_renders_and_opens_templates() {
|
||||
let mut app = app_with_store();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install(&ctx, &app.tokens, &app.session.prefs.general.language);
|
||||
crate::theme::install(&ctx, &app.tokens, crate::i18n::language(app));
|
||||
// File ▸ New ▸ New Project from Template… opens the Templates tab.
|
||||
let ev = app.session.execute("file.newFromTemplate", json!({})).unwrap();
|
||||
assert_eq!(ev["frontend"], "file.newFromTemplate");
|
||||
@@ -250,7 +250,7 @@ mod tests {
|
||||
fn my_custom_rgb_dialog() {
|
||||
let mut app = app_with_store();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install(&ctx, &app.tokens, &app.session.prefs.general.language);
|
||||
crate::theme::install(&ctx, &app.tokens, crate::i18n::language(app));
|
||||
// Menu check marks follow the active comp's viewer.
|
||||
app.session.execute("comp.new", json!({"name": "C", "width": 64, "height": 48})).unwrap();
|
||||
let item = crate::menus::menu_items(&app).into_iter().find(|m| m.id == "view.customRgb").expect("menu item");
|
||||
|
||||
@@ -170,7 +170,7 @@ mod tests {
|
||||
fn app() -> (EffectcraftApp, egui::Context) {
|
||||
let ctx = egui::Context::default();
|
||||
let app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
crate::theme::install(&ctx, &app.tokens, &app.session.prefs.general.language);
|
||||
crate::theme::install(&ctx, &app.tokens, crate::i18n::language(app));
|
||||
(app, ctx)
|
||||
}
|
||||
|
||||
|
||||
@@ -274,9 +274,12 @@ static INTER_SEMIBOLD: &[u8] = include_bytes!("../../../assets/fonts/Inter-SemiB
|
||||
static JETBRAINS_MONO: &[u8] = include_bytes!("../../../assets/fonts/JetBrainsMono-Regular.ttf");
|
||||
|
||||
/// Install fonts (Inter, Inter Medium/SemiBold, JetBrains Mono) and egui visuals. `language` is the
|
||||
/// stored `general.language` value (`en`, `ja`, `zh-hans`, `zh-hant` or `system`): the CJK fallback
|
||||
/// that draws the interface's own script is registered first, so a Chinese interface is drawn by a
|
||||
/// Chinese face and a Japanese one by a Japanese face.
|
||||
/// resolved interface language from [`crate::i18n::language`] (`en`, `ja`, `zh-hans` or `zh-hant`),
|
||||
/// not the stored `general.language` preference: `system` is already resolved there, so a Chinese
|
||||
/// system draws Chinese with the default setting. The CJK fallback that draws the interface's own
|
||||
/// script is registered first, so a Chinese interface is drawn by a Chinese face and a Japanese one
|
||||
/// by a Japanese face. `install` is called again when the language changes (see the `EffectcraftApp`
|
||||
/// frame loop), so the fonts do not need a restart.
|
||||
pub fn install(ctx: &egui::Context, t: &Tokens, language: &str) {
|
||||
let mut fonts = FontDefinitions::default();
|
||||
fonts.font_data.insert("inter".into(), Arc::new(FontData::from_static(INTER_REGULAR)));
|
||||
@@ -425,7 +428,7 @@ mod japanese_font_tests {
|
||||
return;
|
||||
}
|
||||
let ctx = egui::Context::default();
|
||||
super::install(&ctx, &super::Tokens::for_kind(super::ThemeKind::Dark));
|
||||
super::install(&ctx, &super::Tokens::for_kind(super::ThemeKind::Dark), "ja");
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |_| {});
|
||||
// No renderer here: drop the frame's texture uploads (egui asserts on unhandled ones in debug).
|
||||
out.textures_delta.clear();
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ Commands (CLI, MCP, control channel):
|
||||
|
||||
### Settings that change behaviour
|
||||
|
||||
- `general.language`: interface language (`system` / `en` / `ja` / `zh-hans` / `zh-hant`), Settings ▸ General ▸ Language; menu labels change immediately. `system` (Match System, the default) follows the operating system's interface language where EffectCraft has a translation and is English otherwise: a Simplified Chinese locale (`zh`, `zh-CN`, `zh_CN.UTF-8`, `zh-Hans-CN`, `zh-SG`) selects `zh-hans`, a Traditional one (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) selects `zh-hant`; the browser build stays in English with it. Native Japanese, Simplified and Traditional Chinese UI uses installed system fonts; the web host must supply a Japanese font.
|
||||
- `general.language`: interface language (`system` / `en` / `ja` / `zh-hans` / `zh-hant`), Settings ▸ General ▸ Language; menu labels change immediately. `system` (Match System, the default) follows the operating system's interface language where EffectCraft has a translation and is English otherwise: a Simplified Chinese locale (`zh`, `zh-CN`, `zh_CN.UTF-8`, `zh-Hans-CN`, `zh-SG`) selects `zh-hans`, a Traditional one (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) selects `zh-hant`; the browser build stays in English with it. Native Japanese, Simplified and Traditional Chinese UI uses installed system fonts; the web host must supply a Japanese font. The same setting picks the CJK fallback for the interface *and* the Han preference of the text engine, so the composition's own text is drawn with the same language's glyph forms (the text engine's preference is process-global); swapping the language reinstalls the fonts, so it applies without a restart.
|
||||
- `general.undoLevels`: Levels of Undo
|
||||
- `general.pathPointSize`: Path Point and Handle Size
|
||||
- `general.recentItems`: Recent Projects Shown
|
||||
|
||||
Reference in new issue
Block a user