fn main() {
#[cfg(target_os = "macos")]
{
test_supported_languages_async();
test_availability_status_async();
if is_macos_26_or_later() {
test_translate_async();
test_translations_batch_async();
test_prepare_translation_async();
} else {
println!("INFO: Skipping session async tests (require macOS 26+)");
}
}
#[cfg(not(target_os = "macos"))]
println!("SKIP: Translation.framework is macOS-only");
}
#[cfg(target_os = "macos")]
fn test_supported_languages_async() {
use translation::async_api::AsyncLanguageAvailability;
use translation::LanguageAvailability;
let availability = match LanguageAvailability::new() {
Ok(availability) => availability,
Err(error) => {
println!("SKIP test_supported_languages_async: {error}");
return;
}
};
let async_availability = AsyncLanguageAvailability::new(&availability);
let result = block_on_with_main_run_loop(async_availability.supported_languages());
match result {
Ok(languages) => {
assert!(
!languages.is_empty(),
"expected non-empty supported languages"
);
println!(
"PASS test_supported_languages_async: {} languages",
languages.len()
);
}
Err(error) => panic!("FAIL test_supported_languages_async: {error}"),
}
}
#[cfg(target_os = "macos")]
fn test_availability_status_async() {
use translation::async_api::AsyncLanguageAvailability;
use translation::{Language, LanguageAvailability};
let availability = match LanguageAvailability::new() {
Ok(availability) => availability,
Err(error) => {
println!("SKIP test_availability_status_async: {error}");
return;
}
};
let async_availability = AsyncLanguageAvailability::new(&availability);
let en = Language::from("en");
let fr = Language::from("fr");
let result = block_on_with_main_run_loop(
async_availability
.status(&en, Some(&fr))
.expect("status future creation failed"),
);
match result {
Ok(status) => println!("PASS test_availability_status_async: en→fr = {status:?}"),
Err(error) => panic!("FAIL test_availability_status_async: {error}"),
}
}
#[cfg(target_os = "macos")]
fn test_translate_async() {
use translation::async_api::AsyncTranslationSession;
use translation::{TranslationSession, TranslationSessionConfiguration};
let config = TranslationSessionConfiguration::new("en", "fr");
let session = match TranslationSession::new(config) {
Ok(session) => session,
Err(error) => {
println!("SKIP test_translate_async: {error}");
return;
}
};
let async_session = AsyncTranslationSession::new(&session);
let result = match async_session.translate("Hello") {
Ok(future) => block_on_with_main_run_loop(future),
Err(error) => Err(error),
};
match result {
Ok(response) => println!("PASS test_translate_async: '{}'", response.target_text()),
Err(error) => println!("INFO test_translate_async error (may need download): {error}"),
}
}
#[cfg(target_os = "macos")]
fn test_translations_batch_async() {
use translation::async_api::AsyncTranslationSession;
use translation::{TranslationRequest, TranslationSession, TranslationSessionConfiguration};
let config = TranslationSessionConfiguration::new("en", "fr");
let session = match TranslationSession::new(config) {
Ok(session) => session,
Err(error) => {
println!("SKIP test_translations_batch_async: {error}");
return;
}
};
let async_session = AsyncTranslationSession::new(&session);
let requests = [
TranslationRequest::new("Hello"),
TranslationRequest::new("World"),
];
let result = match async_session.translations(&requests) {
Ok(future) => block_on_with_main_run_loop(future),
Err(error) => Err(error),
};
match result {
Ok(responses) => {
assert_eq!(responses.len(), requests.len());
println!("PASS test_translations_batch_async");
}
Err(error) => println!("INFO test_translations_batch_async error: {error}"),
}
}
#[cfg(target_os = "macos")]
fn test_prepare_translation_async() {
use translation::async_api::AsyncTranslationSession;
use translation::{TranslationSession, TranslationSessionConfiguration};
let config = TranslationSessionConfiguration::new("en", "fr");
let session = match TranslationSession::new(config) {
Ok(session) => session,
Err(error) => {
println!("SKIP test_prepare_translation_async: {error}");
return;
}
};
let async_session = AsyncTranslationSession::new(&session);
let result = block_on_with_main_run_loop(async_session.prepare_translation());
match result {
Ok(()) => println!("PASS test_prepare_translation_async"),
Err(error) => println!("INFO test_prepare_translation_async error: {error}"),
}
}
#[cfg(target_os = "macos")]
fn block_on_with_main_run_loop<F, T>(future: F) -> T
where
F: std::future::Future<Output = T> + Send,
T: Send,
{
use std::ffi::c_void;
use std::sync::mpsc::sync_channel;
#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern "C" {
static kCFRunLoopDefaultMode: *const c_void;
fn CFRunLoopRunInMode(
mode: *const c_void,
seconds: f64,
return_after_source_handled: u8,
) -> i32;
}
let (tx, rx) = sync_channel(1);
std::thread::scope(|scope| {
scope.spawn(move || if tx.send(pollster::block_on(future)).is_err() {});
loop {
if let Ok(result) = rx.try_recv() {
break result;
}
unsafe {
let _ = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, 1);
}
}
})
}
fn is_macos_26_or_later() -> bool {
std::process::Command::new("sw_vers")
.arg("-productVersion")
.output()
.ok()
.and_then(|output| {
let version = String::from_utf8_lossy(&output.stdout);
version.trim().split('.').next()?.parse::<u32>().ok()
})
.is_some_and(|major| major >= 26)
}