mirror of
https://github.com/orbien-org/orbien.git
synced 2026-09-22 00:01:31 +00:00
180 lines
6.1 KiB
Rust
180 lines
6.1 KiB
Rust
use std::collections::BTreeMap;
|
|
use std::env;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
|
|
fn main() {
|
|
let version = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION");
|
|
println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION");
|
|
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
let mut res = winres::WindowsResource::new();
|
|
res.set_icon("assets/app-icons/icon.ico");
|
|
res.set("ProductName", "Orbien Desktop");
|
|
res.set("FileDescription", "Orbien Desktop");
|
|
res.set("ProductVersion", &version);
|
|
res.set("FileVersion", &version);
|
|
res.compile()
|
|
.expect("failed to compile Windows resources (icon)");
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
embed_macos_plist(&version);
|
|
}
|
|
|
|
generate_slint_version(&version);
|
|
let zh = generate_slint_i18n();
|
|
verify_font_assets(&zh);
|
|
slint_build::compile_with_config(
|
|
"ui/app.slint",
|
|
slint_build::CompilerConfiguration::new()
|
|
.embed_resources(slint_build::EmbedResourcesKind::EmbedFiles),
|
|
)
|
|
.expect("slint compile failed");
|
|
}
|
|
|
|
fn generate_slint_version(version: &str) {
|
|
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
let out = manifest.join("ui/version.slint");
|
|
let content = format!(
|
|
"// @generated from CARGO_PKG_VERSION — do not edit.\n\
|
|
export global AppMeta {{\n\
|
|
out property <string> version: \"{version}\";\n\
|
|
}}\n"
|
|
);
|
|
fs::write(&out, content).expect("write ui/version.slint");
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn embed_macos_plist(version: &str) {
|
|
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
let src = manifest.join("macos/Info.plist");
|
|
println!("cargo:rerun-if-changed={}", src.display());
|
|
let raw = fs::read_to_string(&src).expect("macos/Info.plist missing");
|
|
let rendered = raw.replace("__ORB_VERSION__", version);
|
|
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
let out = out_dir.join("Info.plist");
|
|
fs::write(&out, rendered).expect("write OUT_DIR/Info.plist");
|
|
println!(
|
|
"cargo:rustc-link-arg=-Wl,-sectcreate,__TEXT,__info_plist,{}",
|
|
out.display()
|
|
);
|
|
}
|
|
|
|
fn generate_slint_i18n() -> BTreeMap<String, String> {
|
|
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
let zh_path = manifest.join("i18n/zh_CN.properties");
|
|
let en_path = manifest.join("i18n/en_US.properties");
|
|
println!("cargo:rerun-if-changed={}", zh_path.display());
|
|
println!("cargo:rerun-if-changed={}", en_path.display());
|
|
|
|
let zh = parse_properties(&fs::read_to_string(&zh_path).expect("read zh_CN.properties"));
|
|
let en = parse_properties(&fs::read_to_string(&en_path).expect("read en_US.properties"));
|
|
|
|
for key in zh.keys() {
|
|
assert!(en.contains_key(key), "en_US.properties missing key: {key}");
|
|
}
|
|
for key in en.keys() {
|
|
assert!(zh.contains_key(key), "zh_CN.properties missing key: {key}");
|
|
}
|
|
|
|
let keys: Vec<&String> = zh.keys().filter(|k| !k.starts_with("msg.")).collect();
|
|
|
|
let out = manifest.join("ui/i18n.slint");
|
|
fs::write(&out, render_slint(&keys, &zh, &en)).expect("write ui/i18n.slint");
|
|
zh
|
|
}
|
|
|
|
fn verify_font_assets(zh: &BTreeMap<String, String>) {
|
|
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
let fonts = manifest.join("assets/fonts");
|
|
for name in [
|
|
"NotoSansSC-Regular.subset.otf",
|
|
"NotoSansSC-Bold.subset.otf",
|
|
"OFL.txt",
|
|
"subset-manifest.json",
|
|
] {
|
|
let path = fonts.join(name);
|
|
assert!(
|
|
path.is_file(),
|
|
"missing font asset {} — run: make desktop-font-subset",
|
|
path.display()
|
|
);
|
|
println!("cargo:rerun-if-changed={}", path.display());
|
|
}
|
|
|
|
let meta =
|
|
fs::read_to_string(fonts.join("subset-manifest.json")).expect("read subset-manifest.json");
|
|
if meta.contains("\"i18n-gb2312") {
|
|
for (key, value) in zh {
|
|
for ch in value.chars().filter(|c| needs_embedded_cjk_coverage(*c)) {
|
|
let needle = format!("\"{ch}\"");
|
|
assert!(
|
|
meta.contains(&needle),
|
|
"zh_CN key '{key}' uses '{ch}' missing from font subset; \
|
|
run: make desktop-font-subset"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn needs_embedded_cjk_coverage(ch: char) -> bool {
|
|
matches!(ch as u32, 0x3000..=0x303F | 0x4E00..=0x9FFF | 0xFF00..=0xFFEF)
|
|
|| matches!(ch, '…' | '·' | '—')
|
|
}
|
|
|
|
fn parse_properties(raw: &str) -> BTreeMap<String, String> {
|
|
let mut map = BTreeMap::new();
|
|
for line in raw.lines() {
|
|
let line = line.trim();
|
|
if line.is_empty() || line.starts_with('#') {
|
|
continue;
|
|
}
|
|
let Some((key, value)) = line.split_once('=') else {
|
|
panic!("invalid properties line (expected key=value): {line}");
|
|
};
|
|
let key = key.trim().to_string();
|
|
let value = value.trim().replace("\\n", "\n").replace("\\t", "\t");
|
|
assert!(!key.is_empty(), "empty key in properties");
|
|
assert!(
|
|
map.insert(key.clone(), value).is_none(),
|
|
"duplicate properties key: {key}"
|
|
);
|
|
}
|
|
map
|
|
}
|
|
|
|
fn escape_slint(s: &str) -> String {
|
|
s.replace('\\', "\\\\").replace('"', "\\\"")
|
|
}
|
|
|
|
fn render_slint(
|
|
keys: &[&String],
|
|
zh: &BTreeMap<String, String>,
|
|
en: &BTreeMap<String, String>,
|
|
) -> String {
|
|
let mut out = String::from(
|
|
"// @generated from i18n/*.properties — do not edit.\n\
|
|
// locale-index: 0 = zh_CN, 1 = en_US\n\
|
|
export global Tr {\n\
|
|
in-out property <int> locale-index: 0;\n\
|
|
property <bool> zh: locale-index == 0;\n\n",
|
|
);
|
|
for key in keys {
|
|
let z = escape_slint(zh.get(*key).unwrap());
|
|
let e = escape_slint(en.get(*key).unwrap());
|
|
if z == e {
|
|
out.push_str(&format!(" out property <string> {key}: \"{z}\";\n"));
|
|
} else {
|
|
out.push_str(&format!(
|
|
" out property <string> {key}: zh ? \"{z}\" : \"{e}\";\n"
|
|
));
|
|
}
|
|
}
|
|
out.push_str("}\n");
|
|
out
|
|
}
|