use std::collections::BTreeMap;
use std::path::Path;
use anyhow::{Context, Result};
use super::super::types::AssetFiles;
use crate::config::I18nSection;
use crate::ui::{self, DIM, RESET, col};
pub(crate) fn read_i18n_messages(
base_dir: &Path,
i18n: &I18nSection,
) -> Result<BTreeMap<String, serde_json::Value>> {
let mut messages = BTreeMap::new();
for locale in &i18n.locales {
let path = base_dir.join(&i18n.messages_dir).join(format!("{locale}.json"));
let content = std::fs::read_to_string(&path)
.with_context(|| format!("i18n: failed to read {}", path.display()))?;
let parsed: serde_json::Value = serde_json::from_str(&content)
.with_context(|| format!("i18n: invalid JSON in {}", path.display()))?;
let sorted = sort_json_keys(&parsed);
let sorted_json = serde_json::to_string_pretty(&sorted)
.with_context(|| format!("i18n: failed to serialize {locale}"))?;
let sorted_json = format!("{sorted_json}\n");
if sorted_json != content {
std::fs::write(&path, &sorted_json)
.with_context(|| format!("i18n: failed to write {}", path.display()))?;
}
messages.insert(locale.clone(), sorted);
}
Ok(messages)
}
fn sort_json_keys(value: &serde_json::Value) -> serde_json::Value {
match value {
serde_json::Value::Object(obj) => {
let sorted: serde_json::Map<String, serde_json::Value> =
obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
serde_json::Value::Object(sorted)
}
other => other.clone(),
}
}
pub(crate) type LocaleRouteMessages = BTreeMap<String, BTreeMap<String, String>>;
pub(crate) fn export_i18n_memory(
out_dir: &Path,
per_locale: &BTreeMap<String, LocaleRouteMessages>,
) -> Result<()> {
let i18n_dir = out_dir.join("i18n");
std::fs::create_dir_all(&i18n_dir)
.with_context(|| format!("failed to create {}", i18n_dir.display()))?;
for (locale, route_msgs) in per_locale {
let path = i18n_dir.join(format!("{locale}.json"));
let json = serde_json::to_string(route_msgs)
.with_context(|| format!("i18n: failed to serialize {locale}"))?;
let json = seam_server::ascii_escape_json(&json);
std::fs::write(&path, &json)
.with_context(|| format!("i18n: failed to write {}", path.display()))?;
}
Ok(())
}
pub(crate) fn export_i18n_paged(
out_dir: &Path,
per_locale: &BTreeMap<String, LocaleRouteMessages>,
) -> Result<()> {
let i18n_dir = out_dir.join("i18n");
for (locale, route_msgs) in per_locale {
for (route_hash, msgs) in route_msgs {
let dir = i18n_dir.join(route_hash);
std::fs::create_dir_all(&dir)
.with_context(|| format!("failed to create {}", dir.display()))?;
let path = dir.join(format!("{locale}.json"));
let json = serde_json::to_string(msgs)
.with_context(|| format!("i18n: failed to serialize {locale}/{route_hash}"))?;
let json = seam_server::ascii_escape_json(&json);
std::fs::write(&path, &json)
.with_context(|| format!("i18n: failed to write {}", path.display()))?;
}
}
Ok(())
}
pub(super) fn path_to_filename(path: &str) -> String {
let trimmed = path.trim_matches('/');
if trimmed.is_empty() {
return "index.html".to_string();
}
let slug = trimmed.replace('/', "-").replace(':', "");
format!("{slug}.html")
}
pub(crate) fn print_asset_files(base_dir: &Path, dist_dir: &str, assets: &AssetFiles) {
let all_files: Vec<&str> =
assets.js.iter().chain(assets.css.iter()).map(std::string::String::as_str).collect();
for file in all_files {
let full_path = base_dir.join(dist_dir).join(file);
let size = std::fs::metadata(&full_path).map(|m| m.len()).unwrap_or(0);
ui::detail_ok(&format!(
"{}{dist_dir}/{file} ({}){}",
col(DIM),
ui::format_size(size),
col(RESET)
));
}
}