bundle.rs (9869B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use crate::builtins::{FluentDateTime, FluentDateTimeOptions, NumberFormat}; 6 use cstr::cstr; 7 pub use fluent::{FluentArgs, FluentBundle, FluentError, FluentResource, FluentValue}; 8 use fluent_pseudo::transform_dom; 9 pub use intl_memoizer::IntlLangMemoizer; 10 use nsstring::{nsACString, nsCString}; 11 use std::borrow::Cow; 12 use std::ffi::CStr; 13 use std::mem; 14 use std::rc::Rc; 15 use thin_vec::ThinVec; 16 use unic_langid::LanguageIdentifier; 17 use xpcom::interfaces::nsIPrefBranch; 18 19 pub type FluentBundleRc = FluentBundle<Rc<FluentResource>>; 20 21 #[derive(Debug)] 22 #[repr(C, u8)] 23 pub enum FluentArgument<'s> { 24 Double_(f64), 25 String(&'s nsACString), 26 } 27 28 #[derive(Debug)] 29 #[repr(C)] 30 pub struct L10nArg<'s> { 31 pub id: &'s nsACString, 32 pub value: FluentArgument<'s>, 33 } 34 35 fn transform_accented(s: &str) -> Cow<'_, str> { 36 transform_dom(s, false, true, true) 37 } 38 39 fn transform_bidi(s: &str) -> Cow<'_, str> { 40 transform_dom(s, false, false, false) 41 } 42 43 fn format_numbers(num: &FluentValue, intls: &IntlLangMemoizer) -> Option<String> { 44 match num { 45 FluentValue::Number(n) => { 46 let result = intls 47 .with_try_get::<NumberFormat, _, _>((n.options.clone(),), |nf| nf.format(n.value)) 48 .expect("Failed to retrieve a NumberFormat instance."); 49 Some(result) 50 } 51 _ => None, 52 } 53 } 54 55 fn get_string_pref(name: &CStr) -> Option<nsCString> { 56 let mut value = nsCString::new(); 57 let prefs_service = 58 xpcom::get_service::<nsIPrefBranch>(cstr!("@mozilla.org/preferences-service;1"))?; 59 unsafe { 60 prefs_service 61 .GetCharPref(name.as_ptr(), &mut *value) 62 .to_result() 63 .ok()?; 64 } 65 Some(value) 66 } 67 68 fn get_bool_pref(name: &CStr) -> Option<bool> { 69 let mut value = false; 70 let prefs_service = 71 xpcom::get_service::<nsIPrefBranch>(cstr!("@mozilla.org/preferences-service;1"))?; 72 unsafe { 73 prefs_service 74 .GetBoolPref(name.as_ptr(), &mut value) 75 .to_result() 76 .ok()?; 77 } 78 Some(value) 79 } 80 81 pub fn adapt_bundle_for_gecko(bundle: &mut FluentBundleRc, pseudo_strategy: Option<&nsACString>) { 82 bundle.set_formatter(Some(format_numbers)); 83 84 bundle 85 .add_function("PLATFORM", |_args, _named_args| { 86 if cfg!(target_os = "linux") { 87 "linux".into() 88 } else if cfg!(target_os = "windows") { 89 "windows".into() 90 } else if cfg!(target_os = "macos") { 91 "macos".into() 92 } else if cfg!(target_os = "android") { 93 "android".into() 94 } else { 95 "other".into() 96 } 97 }) 98 .expect("Failed to add a function to the bundle."); 99 bundle 100 .add_function("NUMBER", |args, named| { 101 if let Some(FluentValue::Number(n)) = args.get(0) { 102 let mut num = n.clone(); 103 num.options.merge(named); 104 FluentValue::Number(num) 105 } else { 106 FluentValue::None 107 } 108 }) 109 .expect("Failed to add a function to the bundle."); 110 bundle 111 .add_function("DATETIME", |args, named| { 112 if let Some(FluentValue::Number(n)) = args.get(0) { 113 let mut options = FluentDateTimeOptions::default(); 114 options.merge(&named); 115 FluentValue::Custom(Box::new(FluentDateTime::new(n.value, options))) 116 } else { 117 FluentValue::None 118 } 119 }) 120 .expect("Failed to add a function to the bundle."); 121 122 enum PseudoStrategy { 123 Accented, 124 Bidi, 125 None, 126 } 127 // This is quirky because we can't coerce Option<&nsACString> and Option<nsCString> 128 // into bytes easily without allocating. 129 let strategy_kind = match pseudo_strategy.map(|s| &s[..]) { 130 Some(b"accented") => PseudoStrategy::Accented, 131 Some(b"bidi") => PseudoStrategy::Bidi, 132 _ => { 133 if let Some(pseudo_strategy) = get_string_pref(cstr!("intl.l10n.pseudo")) { 134 match &pseudo_strategy[..] { 135 b"accented" => PseudoStrategy::Accented, 136 b"bidi" => PseudoStrategy::Bidi, 137 _ => PseudoStrategy::None, 138 } 139 } else { 140 PseudoStrategy::None 141 } 142 } 143 }; 144 match strategy_kind { 145 PseudoStrategy::Accented => bundle.set_transform(Some(transform_accented)), 146 PseudoStrategy::Bidi => bundle.set_transform(Some(transform_bidi)), 147 PseudoStrategy::None => bundle.set_transform(None), 148 } 149 150 // Temporarily disable bidi isolation due to Microsoft not supporting FSI/PDI. 151 // See bug 1439018 for details. 152 let default_use_isolating = false; 153 let use_isolating = 154 get_bool_pref(cstr!("intl.l10n.enable-bidi-marks")).unwrap_or(default_use_isolating); 155 bundle.set_use_isolating(use_isolating); 156 } 157 158 #[no_mangle] 159 pub extern "C" fn fluent_bundle_new_single( 160 locale: &nsACString, 161 use_isolating: bool, 162 pseudo_strategy: &nsACString, 163 ) -> *mut FluentBundleRc { 164 let id = match locale.to_utf8().parse::<LanguageIdentifier>() { 165 Ok(id) => id, 166 Err(..) => return std::ptr::null_mut(), 167 }; 168 169 Box::into_raw(fluent_bundle_new_internal( 170 &[id], 171 use_isolating, 172 pseudo_strategy, 173 )) 174 } 175 176 #[no_mangle] 177 pub unsafe extern "C" fn fluent_bundle_new( 178 locales: *const nsCString, 179 locale_count: usize, 180 use_isolating: bool, 181 pseudo_strategy: &nsACString, 182 ) -> *mut FluentBundleRc { 183 let mut langids = Vec::with_capacity(locale_count); 184 let locales = std::slice::from_raw_parts(locales, locale_count); 185 for locale in locales { 186 let id = match locale.to_utf8().parse::<LanguageIdentifier>() { 187 Ok(id) => id, 188 Err(..) => return std::ptr::null_mut(), 189 }; 190 langids.push(id); 191 } 192 193 Box::into_raw(fluent_bundle_new_internal( 194 &langids, 195 use_isolating, 196 pseudo_strategy, 197 )) 198 } 199 200 fn fluent_bundle_new_internal( 201 langids: &[LanguageIdentifier], 202 use_isolating: bool, 203 pseudo_strategy: &nsACString, 204 ) -> Box<FluentBundleRc> { 205 let mut bundle = FluentBundle::new(langids.to_vec()); 206 bundle.set_use_isolating(use_isolating); 207 208 bundle.set_formatter(Some(format_numbers)); 209 210 adapt_bundle_for_gecko(&mut bundle, Some(pseudo_strategy)); 211 212 Box::new(bundle) 213 } 214 215 #[no_mangle] 216 pub extern "C" fn fluent_bundle_get_locales( 217 bundle: &FluentBundleRc, 218 result: &mut ThinVec<nsCString>, 219 ) { 220 for locale in &bundle.locales { 221 result.push(locale.to_string().as_str().into()); 222 } 223 } 224 225 #[no_mangle] 226 pub unsafe extern "C" fn fluent_bundle_destroy(bundle: *mut FluentBundleRc) { 227 let _ = Box::from_raw(bundle); 228 } 229 230 #[no_mangle] 231 pub extern "C" fn fluent_bundle_has_message(bundle: &FluentBundleRc, id: &nsACString) -> bool { 232 bundle.has_message(id.to_string().as_str()) 233 } 234 235 #[no_mangle] 236 pub extern "C" fn fluent_bundle_get_message( 237 bundle: &FluentBundleRc, 238 id: &nsACString, 239 has_value: &mut bool, 240 attrs: &mut ThinVec<nsCString>, 241 ) -> bool { 242 match bundle.get_message(&id.to_utf8()) { 243 Some(message) => { 244 attrs.reserve(message.attributes().count()); 245 *has_value = message.value().is_some(); 246 for attr in message.attributes() { 247 attrs.push(attr.id().into()); 248 } 249 true 250 } 251 None => { 252 *has_value = false; 253 false 254 } 255 } 256 } 257 258 #[no_mangle] 259 pub extern "C" fn fluent_bundle_format_pattern( 260 bundle: &FluentBundleRc, 261 id: &nsACString, 262 attr: &nsACString, 263 args: &ThinVec<L10nArg>, 264 ret_val: &mut nsACString, 265 ret_errors: &mut ThinVec<nsCString>, 266 ) -> bool { 267 let args = convert_args(&args); 268 269 let message = match bundle.get_message(&id.to_utf8()) { 270 Some(message) => message, 271 None => return false, 272 }; 273 274 let pattern = if !attr.is_empty() { 275 match message.get_attribute(&attr.to_utf8()) { 276 Some(attr) => attr.value(), 277 None => return false, 278 } 279 } else { 280 match message.value() { 281 Some(value) => value, 282 None => return false, 283 } 284 }; 285 286 let mut errors = vec![]; 287 bundle 288 .write_pattern(ret_val, pattern, args.as_ref(), &mut errors) 289 .expect("Failed to write to a nsCString."); 290 append_fluent_errors_to_ret_errors(ret_errors, &errors); 291 true 292 } 293 294 #[no_mangle] 295 pub unsafe extern "C" fn fluent_bundle_add_resource( 296 bundle: &mut FluentBundleRc, 297 r: *const FluentResource, 298 allow_overrides: bool, 299 ret_errors: &mut ThinVec<nsCString>, 300 ) { 301 // we don't own the resource 302 let r = mem::ManuallyDrop::new(Rc::from_raw(r)); 303 304 if allow_overrides { 305 bundle.add_resource_overriding(Rc::clone(&r)); 306 } else if let Err(errors) = bundle.add_resource(Rc::clone(&r)) { 307 append_fluent_errors_to_ret_errors(ret_errors, &errors); 308 } 309 } 310 311 pub fn convert_args<'s>(args: &[L10nArg<'s>]) -> Option<FluentArgs<'s>> { 312 if args.is_empty() { 313 return None; 314 } 315 316 let mut result = FluentArgs::with_capacity(args.len()); 317 for arg in args { 318 let val = match arg.value { 319 FluentArgument::Double_(d) => FluentValue::from(d), 320 FluentArgument::String(s) => FluentValue::from(s.to_utf8()), 321 }; 322 result.set(arg.id.to_string(), val); 323 } 324 Some(result) 325 } 326 327 fn append_fluent_errors_to_ret_errors(ret_errors: &mut ThinVec<nsCString>, errors: &[FluentError]) { 328 for error in errors { 329 ret_errors.push(error.to_string().into()); 330 } 331 }