//! Shared language-resource validation. //! //! Rust embeds the same TOML file that Vite compiles into the browser bundle. //! Backend persistence accepts only locale codes declared by that resource, so //! the database and deployed frontend cannot drift into unsupported values. use std::{collections::HashMap, sync::OnceLock}; use serde::Deserialize; const RESOURCE: &str = include_str!("../../../resources/i18n.toml"); #[derive(Deserialize)] struct Catalog { default_language: String, locales: HashMap, } #[derive(Deserialize)] struct Locale { #[allow(dead_code)] name: String, messages: HashMap, } fn catalog() -> &'static Catalog { static CATALOG: OnceLock = OnceLock::new(); CATALOG.get_or_init(|| { let catalog: Catalog = toml::from_str(RESOURCE).expect("resources/i18n.toml must be valid"); let default = catalog .locales .get(&catalog.default_language) .expect("default language must be declared"); for (code, locale) in &catalog.locales { for key in default.messages.keys() { assert!( locale.messages.contains_key(key), "locale {code} is missing translation key {key}" ); } } catalog }) } pub fn default_language() -> &'static str { &catalog().default_language } pub fn is_supported(language: &str) -> bool { catalog().locales.contains_key(language) } #[cfg(test)] mod tests { use super::*; #[test] fn shared_catalog_is_complete_and_declares_supported_languages() { assert_eq!(default_language(), "zh-CN"); assert!(is_supported("zh-CN")); assert!(is_supported("en-US")); assert!(!is_supported("unknown")); } }