use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use crate::error::XcStringsError;
use crate::io::FileStore;
use crate::service::{formatter, locale, parser};
use crate::tools::FileCache;
use crate::tools::parse::CachedFile;
use crate::tools::resolve_file;
#[derive(Debug, Deserialize, JsonSchema)]
pub(crate) struct ListLocalesParams {
#[serde(default)]
pub file_path: Option<String>,
}
pub(crate) async fn handle_list_locales(
store: &dyn FileStore,
cache: &Mutex<FileCache>,
params: ListLocalesParams,
) -> Result<serde_json::Value, XcStringsError> {
let (_path, file) = resolve_file(store, cache, params.file_path.as_deref()).await?;
let locales = locale::list_locales(&file);
Ok(serde_json::to_value(locales)?)
}
#[derive(Debug, Deserialize, JsonSchema)]
pub(crate) struct AddLocaleParams {
#[serde(default)]
pub file_path: Option<String>,
pub locale: String,
}
#[derive(Debug, Serialize)]
struct AddLocaleResult {
added: usize,
locale: String,
}
pub(crate) async fn handle_add_locale(
store: &dyn FileStore,
cache: &Mutex<FileCache>,
write_lock: &Mutex<()>,
params: AddLocaleParams,
) -> Result<serde_json::Value, XcStringsError> {
let (path, _file) = resolve_file(store, cache, params.file_path.as_deref()).await?;
let _write_guard = write_lock.lock().await;
let raw = store.read(&path)?;
let mut fresh_file = parser::parse(&raw)?;
let added = locale::add_locale(&mut fresh_file, ¶ms.locale)?;
let formatted = formatter::format_xcstrings(&fresh_file)?;
store.write(&path, &formatted)?;
let mtime = store.modified_time(&path)?;
let mut guard = cache.lock().await;
guard.insert(
path.clone(),
CachedFile {
path,
content: fresh_file,
modified: mtime,
},
);
let result = AddLocaleResult {
added,
locale: params.locale,
};
Ok(serde_json::to_value(result)?)
}
#[derive(Debug, Deserialize, JsonSchema)]
pub(crate) struct RemoveLocaleParams {
#[serde(default)]
pub file_path: Option<String>,
pub locale: String,
}
#[derive(Debug, Serialize)]
pub(crate) struct RemoveLocaleResult {
removed: usize,
locale: String,
}
pub(crate) async fn handle_remove_locale(
store: &dyn FileStore,
cache: &Mutex<FileCache>,
write_lock: &Mutex<()>,
params: RemoveLocaleParams,
) -> Result<serde_json::Value, XcStringsError> {
let (path, file) = resolve_file(store, cache, params.file_path.as_deref()).await?;
let source_language = file.source_language.clone();
let _write_guard = write_lock.lock().await;
let raw = store.read(&path)?;
let mut fresh_file = parser::parse(&raw)?;
let removed = locale::remove_locale(&mut fresh_file, ¶ms.locale, &source_language)?;
let formatted = formatter::format_xcstrings(&fresh_file)?;
store.write(&path, &formatted)?;
let mtime = store.modified_time(&path)?;
let mut guard = cache.lock().await;
guard.insert(
path.clone(),
CachedFile {
path,
content: fresh_file,
modified: mtime,
},
);
let result = RemoveLocaleResult {
removed,
locale: params.locale,
};
Ok(serde_json::to_value(result)?)
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::*;
use crate::tools::parse::{ParseParams, handle_parse};
use crate::tools::test_helpers::{MemoryStore, SIMPLE_FIXTURE};
#[tokio::test]
async fn test_list_locales_returns_locales() {
let store = MemoryStore::new();
store.add_file("/test/file.xcstrings", SIMPLE_FIXTURE);
let cache = Mutex::new(FileCache::new());
let params = ListLocalesParams {
file_path: Some("/test/file.xcstrings".to_string()),
};
let result = handle_list_locales(&store, &cache, params).await.unwrap();
let locales = result.as_array().unwrap();
assert!(!locales.is_empty());
assert!(locales.iter().any(|l| l["locale"] == "en"));
assert!(locales.iter().any(|l| l["locale"] == "uk"));
}
#[tokio::test]
async fn test_add_locale_success() {
let store = MemoryStore::new();
store.add_file("/test/file.xcstrings", SIMPLE_FIXTURE);
let cache = Mutex::new(FileCache::new());
let write_lock = Mutex::new(());
let parse_params = ParseParams {
file_path: "/test/file.xcstrings".to_string(),
};
handle_parse(&store, &cache, parse_params).await.unwrap();
let params = AddLocaleParams {
file_path: None,
locale: "fr".to_string(),
};
let result = handle_add_locale(&store, &cache, &write_lock, params)
.await
.unwrap();
assert_eq!(result["locale"], "fr");
assert!(result["added"].as_u64().unwrap() > 0);
let content = store
.get_content(Path::new("/test/file.xcstrings"))
.unwrap();
assert!(content.contains("\"fr\""));
}
#[tokio::test]
async fn test_add_locale_duplicate_error() {
let store = MemoryStore::new();
store.add_file("/test/file.xcstrings", SIMPLE_FIXTURE);
let cache = Mutex::new(FileCache::new());
let write_lock = Mutex::new(());
let parse_params = ParseParams {
file_path: "/test/file.xcstrings".to_string(),
};
handle_parse(&store, &cache, parse_params).await.unwrap();
let params = AddLocaleParams {
file_path: None,
locale: "uk".to_string(),
};
let result = handle_add_locale(&store, &cache, &write_lock, params).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_remove_locale_success() {
let store = MemoryStore::new();
store.add_file("/test/file.xcstrings", SIMPLE_FIXTURE);
let cache = Mutex::new(FileCache::new());
let write_lock = Mutex::new(());
let parse_params = ParseParams {
file_path: "/test/file.xcstrings".to_string(),
};
handle_parse(&store, &cache, parse_params).await.unwrap();
let params = RemoveLocaleParams {
file_path: None,
locale: "uk".to_string(),
};
let result = handle_remove_locale(&store, &cache, &write_lock, params)
.await
.unwrap();
assert_eq!(result["locale"], "uk");
assert!(result["removed"].as_u64().unwrap() > 0);
let content = store
.get_content(Path::new("/test/file.xcstrings"))
.unwrap();
let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
for (_key, entry) in parsed["strings"].as_object().unwrap() {
if let Some(locs) = entry.get("localizations") {
assert!(
locs.get("uk").is_none(),
"uk locale should have been removed"
);
}
}
}
#[tokio::test]
async fn test_remove_source_locale_error() {
let store = MemoryStore::new();
store.add_file("/test/file.xcstrings", SIMPLE_FIXTURE);
let cache = Mutex::new(FileCache::new());
let write_lock = Mutex::new(());
let parse_params = ParseParams {
file_path: "/test/file.xcstrings".to_string(),
};
handle_parse(&store, &cache, parse_params).await.unwrap();
let params = RemoveLocaleParams {
file_path: None,
locale: "en".to_string(),
};
let result = handle_remove_locale(&store, &cache, &write_lock, params).await;
assert!(result.is_err());
}
}