Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  lib.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use fluent::FluentValue;
use fluent_fallback::{
    types::{
        L10nAttribute as FluentL10nAttribute, L10nKey as FluentL10nKey,
        L10nMessage as FluentL10nMessage, ResourceId,
    },
    Localization,
};
use fluent_ffi::{convert_args, FluentArgs, FluentArgument, L10nArg};
use l10nregistry_ffi::{
    env::GeckoEnvironment,
    registry::{get_l10n_registry, GeckoL10nRegistry, GeckoResourceId},
};
use nsstring::{nsACString, nsCString};
use std::os::raw::c_void;
use std::{borrow::Cow, cell::RefCell};
use thin_vec::ThinVec;
use unic_langid::LanguageIdentifier;
use xpcom::{
    interfaces::{nsIRunnablePriority},
    RefCounted, RefPtr, Refcnt,
};

#[derive(Debug)]
#[repr(C)]
pub struct L10nKey<'s> {
    id: &'s nsACString,
    args: ThinVec<L10nArg<'s>>,
}

impl<'s> From<&'s L10nKey<'s>> for FluentL10nKey<'static> {
    fn from(input: &'s L10nKey<'s>) -> Self {
        FluentL10nKey {
            id: input.id.to_utf8().to_string().into(),
            args: convert_args_to_owned(&input.args),
        }
    }
}

// This is a variant of `convert_args` from `fluent-ffi` with a 'static constrain
// put on the resulting `FluentArgs` to make it acceptable into `spqwn_current_thread`.
pub fn convert_args_to_owned(args: &[L10nArg]) -> Option<FluentArgs<'static>> {
    if args.is_empty() {
        return None;
    }

    let mut result = FluentArgs::with_capacity(args.len());
    for arg in args {
        let val = match arg.value {
            FluentArgument::Double_(d) => FluentValue::from(d),
            // We need this to be owned because we pass the result into `spawn_local`.
            FluentArgument::String(s) => FluentValue::from(Cow::Owned(s.to_utf8().to_string())),
        };
        result.set(arg.id.to_string(), val);
    }
    Some(result)
}

#[derive(Debug)]
#[repr(C)]
pub struct L10nAttribute {
    name: nsCString,
    value: nsCString,
}

impl From<FluentL10nAttribute<'_>> for L10nAttribute {
    fn from(attr: FluentL10nAttribute<'_>) -> Self {
        Self {
            name: nsCString::from(&*attr.name),
            value: nsCString::from(&*attr.value),
        }
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct L10nMessage {
    value: nsCString,
    attributes: ThinVec<L10nAttribute>,
}

impl std::default::Default for L10nMessage {
    fn default() -> Self {
        Self {
            value: nsCString::new(),
            attributes: ThinVec::new(),
        }
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct OptionalL10nMessage {
    is_present: bool,
    message: L10nMessage,
}

impl From<FluentL10nMessage<'_>> for L10nMessage {
    fn from(input: FluentL10nMessage) -> Self {
        let value = if let Some(value) = input.value {
            value.to_string().into()
        } else {
            let mut s = nsCString::new();
            s.set_is_void(true);
            s
        };
        Self {
            value,
            attributes: input.attributes.into_iter().map(Into::into).collect(),
        }
    }
}

pub struct LocalizationRc {
    inner: RefCell<Localization<GeckoL10nRegistry, GeckoEnvironment>>,
    refcnt: Refcnt,
}

// xpcom::RefPtr support
unsafe impl RefCounted for LocalizationRc {
    unsafe fn addref(&self) {
        localization_addref(self);
    }
    unsafe fn release(&self) {
        localization_release(self);
    }
}

impl LocalizationRc {
    pub fn new(
        res_ids: Vec<ResourceId>,
        is_sync: bool,
        registry: Option<&GeckoL10nRegistry>,
        locales: Option<Vec<LanguageIdentifier>>,
    ) -> RefPtr<Self> {
        let env = GeckoEnvironment::new(locales);
        let inner = if let Some(reg) = registry {
            Localization::with_env(res_ids, is_sync, env, reg.clone())
        } else {
            let reg = (*get_l10n_registry()).clone();
            Localization::with_env(res_ids, is_sync, env, reg)
        };

        let loc = Box::new(LocalizationRc {
            inner: RefCell::new(inner),
            refcnt: unsafe { Refcnt::new() },
        });

        unsafe {
            RefPtr::from_raw(Box::into_raw(loc))
                .expect("Failed to create RefPtr<LocalizationRc> from Box<LocalizationRc>")
        }
    }

    pub fn add_resource_id(&self, res_id: ResourceId) {
        self.inner.borrow_mut().add_resource_id(res_id);
    }

    pub fn add_resource_ids(&self, res_ids: Vec<ResourceId>) {
        self.inner.borrow_mut().add_resource_ids(res_ids);
    }

    pub fn remove_resource_id(&self, res_id: ResourceId) -> usize {
        self.inner.borrow_mut().remove_resource_id(res_id)
    }

    pub fn remove_resource_ids(&self, res_ids: Vec<ResourceId>) -> usize {
        self.inner.borrow_mut().remove_resource_ids(res_ids)
    }

    pub fn set_async(&self) {
        if self.is_sync() {
            self.inner.borrow_mut().set_async();
        }
    }

    pub fn is_sync(&self) -> bool {
        self.inner.borrow().is_sync()
    }

    pub fn on_change(&self) {
        self.inner.borrow_mut().on_change();
    }

    pub fn format_value_sync(
        &self,
        id: &nsACString,
        args: &ThinVec<L10nArg>,
        ret_val: &mut nsACString,
        ret_err: &mut ThinVec<nsCString>,
    ) -> bool {
        let mut errors = vec![];
        let args = convert_args(&args);
        if let Ok(value) = self.inner.borrow().bundles().format_value_sync(
            &id.to_utf8(),
            args.as_ref(),
            &mut errors,
        ) {
            if let Some(value) = value {
                ret_val.assign(&value);
            } else {
                ret_val.set_is_void(true);
            }
            #[cfg(debug_assertions)]
            debug_assert_variables_exist(&errors, &[id], |id| id.to_string());
            ret_err.extend(errors.into_iter().map(|err| err.to_string().into()));
            true
        } else {
            false
        }
    }

    pub fn format_values_sync(
        &self,
        keys: &ThinVec<L10nKey>,
        ret_val: &mut ThinVec<nsCString>,
        ret_err: &mut ThinVec<nsCString>,
    ) -> bool {
        ret_val.reserve(keys.len());
        let keys: Vec<FluentL10nKey> = keys.into_iter().map(|k| k.into()).collect();
        let mut errors = vec![];
        if let Ok(values) = self
            .inner
            .borrow()
            .bundles()
            .format_values_sync(&keys, &mut errors)
        {
            for value in values.iter() {
                if let Some(value) = value {
                    ret_val.push(value.as_ref().into());
                } else {
                    let mut void_string = nsCString::new();
                    void_string.set_is_void(true);
                    ret_val.push(void_string);
                }
            }
            #[cfg(debug_assertions)]
            debug_assert_variables_exist(&errors, &keys, |key| key.id.to_string());
            ret_err.extend(errors.into_iter().map(|err| err.to_string().into()));
            true
        } else {
            false
        }
    }

    pub fn format_messages_sync(
        &self,
        keys: &ThinVec<L10nKey>,
        ret_val: &mut ThinVec<OptionalL10nMessage>,
        ret_err: &mut ThinVec<nsCString>,
    ) -> bool {
        ret_val.reserve(keys.len());
        let mut errors = vec![];
        let keys: Vec<FluentL10nKey> = keys.into_iter().map(|k| k.into()).collect();
        if let Ok(messages) = self
            .inner
            .borrow()
            .bundles()
            .format_messages_sync(&keys, &mut errors)
        {
            for msg in messages {
                ret_val.push(if let Some(msg) = msg {
                    OptionalL10nMessage {
                        is_present: true,
                        message: msg.into(),
                    }
                } else {
                    OptionalL10nMessage {
                        is_present: false,
                        message: L10nMessage::default(),
                    }
                });
            }
            assert_eq!(keys.len(), ret_val.len());
            #[cfg(debug_assertions)]
            debug_assert_variables_exist(&errors, &keys, |key| key.id.to_string());
            ret_err.extend(errors.into_iter().map(|err| err.to_string().into()));
            true
        } else {
            false
        }
    }

    pub fn format_value(
        &self,
        id: &nsACString,
        args: &ThinVec<L10nArg>,
        promise: &xpcom::Promise,
        callback: extern "C" fn(&xpcom::Promise, &nsACString, &ThinVec<nsCString>),
    ) {
        let bundles = self.inner.borrow().bundles().clone();

        let args = convert_args_to_owned(&args);

        let id = nsCString::from(id);
        let strong_promise = RefPtr::new(promise);

        moz_task::TaskBuilder::new("LocalizationRc::format_value", async move {
            let mut errors = vec![];
            let value = if let Some(value) = bundles
                .format_value(&id.to_utf8(), args.as_ref(), &mut errors)
                .await
            {
                let v: nsCString = value.to_string().into();
                v
            } else {
                let mut v = nsCString::new();
                v.set_is_void(true);
                v
            };
            #[cfg(debug_assertions)]
            debug_assert_variables_exist(&errors, &[id], |id| id.to_string());
            let errors = errors
                .into_iter()
                .map(|err| err.to_string().into())
                .collect();
            callback(&strong_promise, &value, &errors);
        })
        .priority(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING as u32)
        .spawn_local()
        .detach();
    }

    pub fn format_values(
        &self,
        keys: &ThinVec<L10nKey>,
        promise: &xpcom::Promise,
        callback: extern "C" fn(&xpcom::Promise, &ThinVec<nsCString>, &ThinVec<nsCString>),
    ) {
        let bundles = self.inner.borrow().bundles().clone();

        let keys: Vec<FluentL10nKey> = keys.into_iter().map(|k| k.into()).collect();

        let strong_promise = RefPtr::new(promise);

        moz_task::TaskBuilder::new("LocalizationRc::format_values", async move {
            let mut errors = vec![];
            let ret_val = bundles
                .format_values(&keys, &mut errors)
                .await
                .into_iter()
                .map(|value| {
                    if let Some(value) = value {
                        nsCString::from(value.as_ref())
                    } else {
                        let mut v = nsCString::new();
                        v.set_is_void(true);
                        v
                    }
                })
                .collect::<ThinVec<_>>();

            assert_eq!(keys.len(), ret_val.len());

            #[cfg(debug_assertions)]
            debug_assert_variables_exist(&errors, &keys, |key| key.id.to_string());
            let errors = errors
                .into_iter()
                .map(|err| err.to_string().into())
                .collect();

            callback(&strong_promise, &ret_val, &errors);
        })
        .priority(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING as u32)
        .spawn_local()
        .detach();
    }

    pub fn format_messages(
        &self,
        keys: &ThinVec<L10nKey>,
        promise: &xpcom::Promise,
        callback: extern "C" fn(
            &xpcom::Promise,
            &ThinVec<OptionalL10nMessage>,
            &ThinVec<nsCString>,
        ),
    ) {
        let bundles = self.inner.borrow().bundles().clone();

        let keys: Vec<FluentL10nKey> = keys.into_iter().map(|k| k.into()).collect();

        let strong_promise = RefPtr::new(promise);

        moz_task::TaskBuilder::new("LocalizationRc::format_messages", async move {
            let mut errors = vec![];
            let ret_val = bundles
                .format_messages(&keys, &mut errors)
                .await
                .into_iter()
                .map(|msg| {
                    if let Some(msg) = msg {
                        OptionalL10nMessage {
                            is_present: true,
                            message: msg.into(),
                        }
                    } else {
                        OptionalL10nMessage {
                            is_present: false,
                            message: L10nMessage::default(),
                        }
                    }
                })
                .collect::<ThinVec<_>>();

            assert_eq!(keys.len(), ret_val.len());

            #[cfg(debug_assertions)]
            debug_assert_variables_exist(&errors, &keys, |key| key.id.to_string());

            let errors = errors
                .into_iter()
                .map(|err| err.to_string().into())
                .collect();

            callback(&strong_promise, &ret_val, &errors);
        })
        .priority(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING as u32)
        .spawn_local()
        .detach();
    }
}

#[no_mangle]
pub extern "C" fn localization_parse_locale(input: &nsCString) -> *const c_void {
    let l: LanguageIdentifier = input.to_utf8().parse().unwrap();
    Box::into_raw(Box::new(l)) as *const c_void
}

#[no_mangle]
pub extern "C" fn localization_new(
    res_ids: &ThinVec<GeckoResourceId>,
    is_sync: bool,
    reg: Option<&GeckoL10nRegistry>,
    result: &mut *const LocalizationRc,
) {
    *result = std::ptr::null();
    let res_ids: Vec<ResourceId> = res_ids.iter().map(ResourceId::from).collect();
    *result = RefPtr::forget_into_raw(LocalizationRc::new(res_ids, is_sync, reg, None));
}

#[no_mangle]
pub extern "C" fn localization_new_with_locales(
    res_ids: &ThinVec<GeckoResourceId>,
    is_sync: bool,
    reg: Option<&GeckoL10nRegistry>,
    locales: Option<&ThinVec<nsCString>>,
    result: &mut *const LocalizationRc,
) -> bool {
    *result = std::ptr::null();
    let res_ids: Vec<ResourceId> = res_ids.iter().map(ResourceId::from).collect();
    let locales: Result<Option<Vec<LanguageIdentifier>>, _> = locales
        .map(|locales| {
            locales
                .iter()
                .map(|s| LanguageIdentifier::from_bytes(&s))
                .collect()
        })
        .transpose();

    if let Ok(locales) = locales {
        *result = RefPtr::forget_into_raw(LocalizationRc::new(res_ids, is_sync, reg, locales));
        true
    } else {
        false
    }
}

#[no_mangle]
pub unsafe extern "C" fn localization_addref(loc: &LocalizationRc) {
    loc.refcnt.inc();
}

#[no_mangle]
pub unsafe extern "C" fn localization_release(loc: *const LocalizationRc) {
    let rc = (*loc).refcnt.dec();
    if rc == 0 {
        std::mem::drop(Box::from_raw(loc as *const _ as *mut LocalizationRc));
    }
}

#[no_mangle]
pub extern "C" fn localization_add_res_id(loc: &LocalizationRc, res_id: &GeckoResourceId) {
    loc.add_resource_id(res_id.into());
}

#[no_mangle]
pub extern "C" fn localization_add_res_ids(loc: &LocalizationRc, res_ids: &ThinVec<GeckoResourceId>) {
    let res_ids = res_ids.iter().map(ResourceId::from).collect();
    loc.add_resource_ids(res_ids);
}

#[no_mangle]
pub extern "C" fn localization_remove_res_id(loc: &LocalizationRc, res_id: &GeckoResourceId) -> usize {
    loc.remove_resource_id(res_id.into())
}

#[no_mangle]
pub extern "C" fn localization_remove_res_ids(
    loc: &LocalizationRc,
    res_ids: &ThinVec<GeckoResourceId>,
) -> usize {
    let res_ids = res_ids.iter().map(ResourceId::from).collect();
    loc.remove_resource_ids(res_ids)
}

#[no_mangle]
pub extern "C" fn localization_format_value_sync(
    loc: &LocalizationRc,
    id: &nsACString,
    args: &ThinVec<L10nArg>,
    ret_val: &mut nsACString,
    ret_err: &mut ThinVec<nsCString>,
) -> bool {
    loc.format_value_sync(id, args, ret_val, ret_err)
}

#[no_mangle]
pub extern "C" fn localization_format_values_sync(
    loc: &LocalizationRc,
    keys: &ThinVec<L10nKey>,
    ret_val: &mut ThinVec<nsCString>,
    ret_err: &mut ThinVec<nsCString>,
) -> bool {
    loc.format_values_sync(keys, ret_val, ret_err)
}

#[no_mangle]
pub extern "C" fn localization_format_messages_sync(
    loc: &LocalizationRc,
    keys: &ThinVec<L10nKey>,
    ret_val: &mut ThinVec<OptionalL10nMessage>,
    ret_err: &mut ThinVec<nsCString>,
) -> bool {
    loc.format_messages_sync(keys, ret_val, ret_err)
}

#[no_mangle]
pub extern "C" fn localization_format_value(
    loc: &LocalizationRc,
    id: &nsACString,
    args: &ThinVec<L10nArg>,
    promise: &xpcom::Promise,
    callback: extern "C" fn(&xpcom::Promise, &nsACString, &ThinVec<nsCString>),
) {
    loc.format_value(id, args, promise, callback);
}

#[no_mangle]
pub extern "C" fn localization_format_values(
    loc: &LocalizationRc,
    keys: &ThinVec<L10nKey>,
    promise: &xpcom::Promise,
    callback: extern "C" fn(&xpcom::Promise, &ThinVec<nsCString>, &ThinVec<nsCString>),
) {
    loc.format_values(keys, promise, callback);
}

#[no_mangle]
pub extern "C" fn localization_format_messages(
    loc: &LocalizationRc,
    keys: &ThinVec<L10nKey>,
    promise: &xpcom::Promise,
    callback: extern "C" fn(&xpcom::Promise, &ThinVec<OptionalL10nMessage>, &ThinVec<nsCString>),
) {
    loc.format_messages(keys, promise, callback);
}

#[no_mangle]
pub extern "C" fn localization_set_async(loc: &LocalizationRc) {
    loc.set_async();
}

#[no_mangle]
pub extern "C" fn localization_is_sync(loc: &LocalizationRc) -> bool {
    loc.is_sync()
}

#[no_mangle]
pub extern "C" fn localization_on_change(loc: &LocalizationRc) {
    loc.on_change();
}

#[cfg(debug_assertions)]
fn debug_assert_variables_exist<K, F>(
    errors: &[fluent_fallback::LocalizationError],
    keys: &[K],
    to_string: F,
) where
    F: Fn(&K) -> String,
{
    for error in errors {
        if let fluent_fallback::LocalizationError::Resolver { errors, .. } = error {
            use fluent::{
                resolver::{errors::ReferenceKind, ResolverError},
                FluentError,
            };
            for error in errors {
                if let FluentError::ResolverError(ResolverError::Reference(
                    ReferenceKind::Variable { id },
                )) = error
                {
                    // This error needs to be actionable for Firefox engineers to fix
                    // their Fluent issues. It might be nicer to share the specific
                    // message, but at this point we don't have that information.
                    eprintln!(
                        "Fluent error, the argument \"${}\" was not provided a value.",
                        id
                    );
                    eprintln!("This error happened while formatting the following messages:");
                    for key in keys {
                        eprintln!("  {:?}", to_string(key))
                    }

                    // Panic with the slightly more cryptic ResolverError.
                    panic!("{}", error.to_string());
                }
            }
        }
    }
}

[ Dauer der Verarbeitung: 0.33 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge