Why doesn’t Iced support Chinese out of the box? 🤔

Hey fellow Rustaceans,

I’ve been really enjoying using Iced for building cross-platform GUI apps—it’s clean, functional, and feels very “Rusty.” But I just hit a wall: it doesn’t seem to support Chinese characters by default.

When I tried displaying some basic Chinese text (e.g., “你好,世界!”), it either rendered as blank boxes or fell back to tofu (☐☐☐). After digging a bit, I realized this is likely because Iced relies on system fonts or requires explicit font loading—and many default fonts simply don’t include CJK glyphs.

I get that full internationalization is hard, but for a modern GUI library, basic Unicode support (especially for widely used languages like Chinese) feels like a must-have, not a nice-to-have. Is there a straightforward way to enable Chinese text without jumping through hoops? Or is this a known limitation that’s being worked on?

Am I missing something obvious? Any tips or workarounds would be greatly appreciated!

(And yes, I know about font-kit and custom font loading—but shouldn’t “just working” be the goal?)

Can you provide some code snippets?

People have had a lot of issues with Chinese using iced. But it seems like these have been resolved for the most part and that you should be able to get it working using advanced text shaping.

Anyway, as the README says, Iced is currently experimental software. Developed largely by a single person. You should expect to hit roadblocks. If you'd like to see it improved, you can always sponsor hecrj and/or contribute.

1 Like

fn update(state:&mut AppState,message:Message) -> Task<Message> {
    match message {
        Message::Exit => window::get_latest().and_then(window::close),
        Message::CD(path_buf) => {
            state.current_dir = path_buf;
            state.current_files = get_files(&state.current_dir);
            Task::none()
        },
        Message::JRIP(path_buf) => {
            if let Some(parent)=path_buf.parent() {
                let mut new_file = parent.to_path_buf();
                new_file.push("output.mp3");
                if let Ok(output) = Command::new("ffmpeg")
                    .args(["-i",
                    path_buf.to_str().unwrap_or("/home"),
                    "-y",
                    new_file.to_str().unwrap_or("/home")]).status()
                {
                    if output.success() {
                        state.popup = Some(String::from("Audioi has beed ripped"));
                    }else{
                        state.popup = Some(String::from("Video cant ripped"))
                    }
                }
            }
            Task::none()
        },
        Message::ClosePopup => {
            state.popup = None;
            Task::none()
        }
    }
}

fn view(state: &AppState)->Element<Message>{
    let mut content = column![//一个纵列分布
        row![
            text(state.current_dir.to_str().unwrap_or("Unknow Directory"))
            .size(32)
            .width(Fill),

            button(text("Up").size(24)).on_press(Message::CD(
                state.current_dir.parent().unwrap_or(&state.current_dir).to_path_buf()
            )),
            button(text("Exit").size(24)).on_press(Message::Exit)
        ].spacing(8)//设置间距
    ].spacing(2).padding(4);

    content = content.push(horizontal_rule(2));//顶部文本下方增加了个分隔线
    if let Some(pat) = &state.popup {
        content = content.push(row![text(pat).width(Fill),button("closed").on_press(Message::ClosePopup)]);
        content = content.push(horizontal_rule(2));
    }
    for file in &state.current_files{
        let file_name = text(&file.0).size(18);
        let mut file_path = state.current_dir.clone();
        file_path.push(&file.0);
        if file.1 {
            content = content.push(
              button(file_name).style(dir_button_style()).on_press(Message::CD(file_path))
            );
        }else{
            content = content.push(
                row![file_name.width(Fill),button(text("Arctic")).on_press(Message::JRIP(file_path))]
            );
        }

    }
    content.into()

}

fn get_files(path: &PathBuf)->Vec<(String,bool)>{
    let mut dirs = Vec::default();
    let mut files = Vec::default();

    if let Ok(read_dir) = fs::read_dir(path){
        for read in read_dir{
            if let Ok(dir_entry) = read {
                if let Some(name) = dir_entry.file_name().to_str() {
                    if dir_entry.path().is_dir() {
                        dirs.push((name.to_string(),true));
                    }else{
                        if name.ends_with("mkv") {
                            files.push((name.to_string(),false));
                        }

                    }
                }
            }
        }
    }

    dirs.append(&mut files);
    dirs
}

fn main()->iced::Result{
    iced::application("Arctic",update,view)
        .theme(|_| iced::Theme::KanagawaDragon)
        .run()
}

Hi, thanks for your reply!
The problem is that any Chinese characters in file paths (like “视频” or “文档”) show up as tofu (☐☐☐) .

Iced uses Fira Sans as default font it seems. Fira Sans doesn't have Chinese support from what I can tell. You will have to set a different font.

could u point it to me about A simple demo or code snippet showing how to load and use a custom font in Iced?

Here's an example: How can I use a TTF file to apply a custom font to the application? - Learn - iced

thank you so much

Notably PoP OS' COSMIC is built on iced but seemingly replaced the entire widget library on top of their own text rendering crate cosmic-text (and probably for other reasons too)

It doesn't look like there's a nice plug-in way to use it with basic iced widgets currently, unfortunately, since it supports all sorts of "advanced" features like what you would probably expect here, font fallback.