translation/
language_availability.rs1use core::ffi::{c_char, c_void};
2use core::ptr;
3
4use crate::ffi;
5use crate::language::Language;
6use crate::language_pair::LanguagePair;
7use crate::private::{error_from_status, parse_json_ptr, to_cstring};
8use crate::translation_error::TranslationError;
9use crate::translation_session::TranslationStrategy;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum LanguageAvailabilityStatus {
14 Installed,
16 Supported,
18 Unsupported,
20 Unknown(i32),
22}
23
24impl LanguageAvailabilityStatus {
25 #[must_use]
26 pub const fn from_raw(raw: i32) -> Self {
28 match raw {
29 0 => Self::Installed,
30 1 => Self::Supported,
31 2 => Self::Unsupported,
32 other => Self::Unknown(other),
33 }
34 }
35}
36
37pub struct LanguageAvailability {
39 token: *mut c_void,
40}
41
42impl Drop for LanguageAvailability {
43 fn drop(&mut self) {
44 if !self.token.is_null() {
45 unsafe { ffi::trl_language_availability_release(self.token) };
46 self.token = ptr::null_mut();
47 }
48 }
49}
50
51impl LanguageAvailability {
52 pub fn new() -> Result<Self, TranslationError> {
54 let token = unsafe { ffi::trl_language_availability_new() };
55 if token.is_null() {
56 return Err(TranslationError::UnavailableOnThisMacOS(
57 "LanguageAvailability requires macOS 15+".to_owned(),
58 ));
59 }
60 Ok(Self { token })
61 }
62
63 pub fn with_preferred_strategy(
65 preferred_strategy: TranslationStrategy,
66 ) -> Result<Self, TranslationError> {
67 let mut token: *mut c_void = ptr::null_mut();
68 let mut err_msg: *mut c_char = ptr::null_mut();
69 let status = unsafe {
70 ffi::trl_language_availability_new_with_preferred_strategy(
71 preferred_strategy.raw(),
72 &mut token,
73 &mut err_msg,
74 )
75 };
76 if status == ffi::status::OK && !token.is_null() {
77 Ok(Self { token })
78 } else {
79 Err(unsafe { error_from_status(status, err_msg) })
80 }
81 }
82
83 #[cfg(feature = "async")]
84 pub(crate) const fn raw_token(&self) -> *mut c_void {
85 self.token
86 }
87
88 pub fn preferred_strategy(&self) -> Result<TranslationStrategy, TranslationError> {
90 let mut raw = 0;
91 let mut err_msg: *mut c_char = ptr::null_mut();
92 let status = unsafe {
93 ffi::trl_language_availability_preferred_strategy(self.token, &mut raw, &mut err_msg)
94 };
95 if status == ffi::status::OK {
96 TranslationStrategy::from_raw(raw).ok_or_else(|| {
97 TranslationError::Unknown(format!(
98 "unknown TranslationSession.Strategy raw value returned by Swift bridge: {raw}"
99 ))
100 })
101 } else {
102 Err(unsafe { error_from_status(status, err_msg) })
103 }
104 }
105
106 pub fn supported_languages(&self) -> Result<Vec<String>, TranslationError> {
108 self.supported_language_objects()
109 .map(|languages| languages.into_iter().map(String::from).collect())
110 }
111
112 pub fn supported_language_objects(&self) -> Result<Vec<Language>, TranslationError> {
114 let mut languages_json: *mut c_char = ptr::null_mut();
115 let mut err_msg: *mut c_char = ptr::null_mut();
116 let status = unsafe {
117 ffi::trl_language_availability_supported_languages_json(
118 self.token,
119 &mut languages_json,
120 &mut err_msg,
121 )
122 };
123 if status == ffi::status::OK {
124 unsafe { parse_json_ptr(languages_json, "supported languages") }
125 } else {
126 Err(unsafe { error_from_status(status, err_msg) })
127 }
128 }
129
130 pub fn status_for_pair(
132 &self,
133 source_language: &str,
134 target_language: &str,
135 ) -> Result<LanguageAvailabilityStatus, TranslationError> {
136 let source_language = Language::from(source_language);
137 let target_language = Language::from(target_language);
138 self.status_for_languages(&source_language, Some(&target_language))
139 }
140
141 pub fn status_for_language_pair(
143 &self,
144 pair: &LanguagePair,
145 ) -> Result<LanguageAvailabilityStatus, TranslationError> {
146 self.status_for_languages(pair.source(), pair.target())
147 }
148
149 pub fn status_for_languages(
151 &self,
152 source_language: &Language,
153 target_language: Option<&Language>,
154 ) -> Result<LanguageAvailabilityStatus, TranslationError> {
155 let source_language = to_cstring(source_language.identifier())?;
156 let target_language = target_language
157 .map(|language| to_cstring(language.identifier()))
158 .transpose()?;
159 let mut status_raw = 0;
160 let mut err_msg: *mut c_char = ptr::null_mut();
161 let status = unsafe {
162 ffi::trl_language_availability_status_from_to(
163 self.token,
164 source_language.as_ptr(),
165 target_language
166 .as_ref()
167 .map_or(ptr::null(), |language| language.as_ptr()),
168 &mut status_raw,
169 &mut err_msg,
170 )
171 };
172 if status == ffi::status::OK {
173 Ok(LanguageAvailabilityStatus::from_raw(status_raw))
174 } else {
175 Err(unsafe { error_from_status(status, err_msg) })
176 }
177 }
178
179 pub fn status_for_text(
181 &self,
182 text: &str,
183 target_language: &str,
184 ) -> Result<LanguageAvailabilityStatus, TranslationError> {
185 let target_language = Language::from(target_language);
186 self.status_for_text_in_language(text, Some(&target_language))
187 }
188
189 pub fn status_for_text_in_language(
191 &self,
192 text: &str,
193 target_language: Option<&Language>,
194 ) -> Result<LanguageAvailabilityStatus, TranslationError> {
195 let text = to_cstring(text)?;
196 let target_language = target_language
197 .map(|language| to_cstring(language.identifier()))
198 .transpose()?;
199 let mut status_raw = 0;
200 let mut err_msg: *mut c_char = ptr::null_mut();
201 let status = unsafe {
202 ffi::trl_language_availability_status_for_text(
203 self.token,
204 text.as_ptr(),
205 target_language
206 .as_ref()
207 .map_or(ptr::null(), |language| language.as_ptr()),
208 &mut status_raw,
209 &mut err_msg,
210 )
211 };
212 if status == ffi::status::OK {
213 Ok(LanguageAvailabilityStatus::from_raw(status_raw))
214 } else {
215 Err(unsafe { error_from_status(status, err_msg) })
216 }
217 }
218}