translation-rs 0.4.1

Safe Rust bindings for Translation.framework — language availability, session workflows, and language recognition on macOS
Documentation
use core::ffi::c_char;
use core::ptr;

use serde::{Deserialize, Deserializer, Serialize};

use crate::ffi;
use crate::language::Language;
use crate::private::{error_from_status, parse_json_ptr, to_cstring};
use crate::translation_error::TranslationError;

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
/// Represents a source/target language pair used by Translation.framework.
pub struct LanguagePair {
    source: Language,
    target: Option<Language>,
}

impl<'de> Deserialize<'de> for LanguagePair {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct LanguagePairPayload {
            source: Language,
            target: Option<Language>,
        }

        let payload = LanguagePairPayload::deserialize(deserializer)?;
        Ok(Self::new(payload.source, payload.target))
    }
}

impl LanguagePair {
    #[must_use]
    /// Creates a language pair with an optional target language.
    pub fn new(source: impl Into<Language>, target: Option<Language>) -> Self {
        Self {
            source: source.into(),
            target,
        }
    }

    #[must_use]
    /// Creates a language pair with both source and target languages.
    pub fn between(source: impl Into<Language>, target: impl Into<Language>) -> Self {
        Self::new(source, Some(target.into()))
    }

    #[must_use]
    /// Returns the source language.
    pub fn source(&self) -> &Language {
        &self.source
    }

    #[must_use]
    /// Returns the target language, if one is set.
    pub fn target(&self) -> Option<&Language> {
        self.target.as_ref()
    }

    /// Returns the Translation.framework-canonicalized language pair.
    pub fn canonicalized(&self) -> Result<Self, TranslationError> {
        let source = to_cstring(self.source.identifier())?;
        let target = self
            .target
            .as_ref()
            .map(|language| to_cstring(language.identifier()))
            .transpose()?;
        let mut pair_json: *mut c_char = ptr::null_mut();
        let mut err_msg: *mut c_char = ptr::null_mut();
        let status = unsafe {
            ffi::trl_language_pair_canonicalize_json(
                source.as_ptr(),
                target.as_ref().map_or(ptr::null(), |value| value.as_ptr()),
                &mut pair_json,
                &mut err_msg,
            )
        };
        if status == ffi::status::OK {
            unsafe { parse_json_ptr(pair_json, "canonicalized language pair") }
        } else {
            Err(unsafe { error_from_status(status, err_msg) })
        }
    }
}