feat(i18n): Make i18n default feature and always use embedded assets

- Add i18n to default features in Cargo.toml
- Modify I18nBundle::load() to always use embedded assets when i18n feature is enabled
- Remove confusing warning about "locales directory not found"
- Locales are now embedded via rust-embed by default

Fixes issue where translations showed placeholders like [nav-chat] instead of actual text.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Rodrigo Rodriguez 2026-02-14 17:35:12 +00:00
parent 5d73abe9f8
commit 84e8f1fe3a
2 changed files with 45 additions and 41 deletions

View file

@ -10,9 +10,9 @@ keywords = ["bot", "chatbot", "ai", "conversational", "library"]
categories = ["api-bindings", "web-programming"] categories = ["api-bindings", "web-programming"]
[features] [features]
default = [] default = ["database", "i18n"]
full = ["database", "http-client", "validation", "resilience", "i18n"] full = ["database", "http-client", "validation", "resilience", "i18n"]
database = [] database = ["i18n"]
http-client = ["dep:reqwest"] http-client = ["dep:reqwest"]
validation = [] validation = []
resilience = [] resilience = []

View file

@ -173,55 +173,59 @@ pub struct I18nBundle {
impl I18nBundle { impl I18nBundle {
pub fn load(base_path: &str) -> BotResult<Self> { pub fn load(base_path: &str) -> BotResult<Self> {
let base = Path::new(base_path); // When i18n feature is enabled, locales are ALWAYS embedded via rust-embed
// Filesystem loading is deprecated - use embedded assets only
if !base.exists() { #[cfg(feature = "i18n")]
#[cfg(feature = "i18n")] {
{ log::info!("Loading embedded locale translations (rust-embed)");
log::info!( return Self::load_embedded();
"Locales directory not found at {}, trying embedded assets",
base_path
);
return Self::load_embedded();
}
#[cfg(not(feature = "i18n"))]
return Err(BotError::config(format!(
"locales directory not found: {base_path}"
)));
} }
let mut bundles = HashMap::new(); #[cfg(not(feature = "i18n"))]
let mut available = Vec::new(); {
let _base_path = base_path; // Suppress unused warning when i18n is enabled
let entries = fs::read_dir(base) let base = Path::new(base_path);
.map_err(|e| BotError::config(format!("failed to read locales directory: {e}")))?;
for entry in entries { if !base.exists() {
let entry = entry return Err(BotError::config(format!(
.map_err(|e| BotError::config(format!("failed to read directory entry: {e}")))?; "locales directory not found: {base_path}"
)));
}
let path = entry.path(); let mut bundles = HashMap::new();
let mut available = Vec::new();
if path.is_dir() { let entries = fs::read_dir(base)
match LocaleBundle::load(&path) { .map_err(|e| BotError::config(format!("failed to read locales directory: {e}")))?;
Ok(bundle) => {
available.push(bundle.locale.clone()); for entry in entries {
bundles.insert(bundle.locale.to_string(), bundle); let entry = entry
} .map_err(|e| BotError::config(format!("failed to read directory entry: {e}")))?;
Err(e) => {
log::warn!("failed to load locale bundle: {e}"); let path = entry.path();
if path.is_dir() {
match LocaleBundle::load(&path) {
Ok(bundle) => {
available.push(bundle.locale.clone());
bundles.insert(bundle.locale.to_string(), bundle);
}
Err(e) => {
log::warn!("failed to load locale bundle: {e}");
}
} }
} }
} }
let fallback = Locale::default();
Ok(Self {
bundles,
available,
fallback,
})
} }
let fallback = Locale::default();
Ok(Self {
bundles,
available,
fallback,
})
} }
#[cfg(feature = "i18n")] #[cfg(feature = "i18n")]