#![warn(missing_docs)]
#![allow(clippy::needless_doctest_main)]
mod export_impl;
mod locale_family;
use icu_provider::export::ExporterCloseMetadata;
pub use locale_family::*;
#[cfg(feature = "baked_exporter")]
pub use icu_provider_baked::export as baked_exporter;
#[cfg(feature = "blob_exporter")]
pub use icu_provider_blob::export as blob_exporter;
#[cfg(feature = "fs_exporter")]
pub use icu_provider_fs::export as fs_exporter;
pub mod prelude {
#[doc(no_inline)]
pub use crate::{
DataLocaleFamily, DeduplicationStrategy, ExportDriver, FallbackOptions, NoFallbackOptions,
};
#[doc(no_inline)]
pub use icu_locale_core::{data_locale, locale};
#[doc(no_inline)]
pub use icu_locale_fallback::LocaleFallbacker;
#[doc(no_inline)]
pub use icu_provider::{DataLocale, DataMarker, DataMarkerInfo, export::DataExporter};
}
use icu_locale_fallback::LocaleFallbacker;
use icu_provider::export::DataExporter;
use icu_provider::export::ExportableProvider;
use icu_provider::prelude::*;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::collections::HashSet;
use std::hash::Hash;
use std::sync::Arc;
#[derive(Clone)]
pub struct ExportDriver {
markers: Option<BTreeSet<DataMarkerInfo>>,
requested_families: HashMap<DataLocale, DataLocaleFamilyAnnotations>,
#[expect(clippy::type_complexity)] attributes_filters:
HashMap<String, Arc<Box<dyn Fn(&DataMarkerAttributes) -> bool + Send + Sync + 'static>>>,
fallbacker: LocaleFallbacker,
include_full: bool,
deduplication_strategy: DeduplicationStrategy,
}
impl core::fmt::Debug for ExportDriver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExportDriver")
.field("markers", &self.markers)
.field("requested_families", &self.requested_families)
.field("attributes_filters", &self.attributes_filters.keys())
.field("fallbacker", &self.fallbacker)
.field("include_full", &self.include_full)
.field("deduplication_strategy", &self.deduplication_strategy)
.finish()
}
}
impl ExportDriver {
pub fn new(
locales: impl IntoIterator<Item = DataLocaleFamily>,
options: FallbackOptions,
fallbacker: LocaleFallbacker,
) -> Self {
let mut include_full = false;
Self {
markers: Default::default(),
requested_families: locales
.into_iter()
.filter_map(|family| {
Some((
family.locale.or_else(|| {
debug_assert_eq!(
family.annotations,
DataLocaleFamily::FULL.annotations
);
include_full = true;
None
})?,
family.annotations,
))
})
.collect(),
attributes_filters: Default::default(),
include_full,
fallbacker,
deduplication_strategy: options.deduplication_strategy,
}
.with_recommended_segmenter_models()
.with_additional_collations([])
}
pub fn with_marker_attributes_filter(
mut self,
domain: &str,
filter: impl Fn(&DataMarkerAttributes) -> bool + Send + Sync + 'static,
) -> Self {
let old_value = self
.attributes_filters
.insert(String::from(domain), Arc::new(Box::new(filter)));
if old_value.is_some() {
log::warn!(
"Filter applied to domain '{domain}' multiple times; ignoring all but the last filter"
);
}
self
}
pub fn with_markers(self, markers: impl IntoIterator<Item = DataMarkerInfo>) -> Self {
Self {
markers: Some(markers.into_iter().collect()),
..self
}
}
pub fn with_additional_collations(
self,
additional_collations: impl IntoIterator<Item = String>,
) -> Self {
let set = additional_collations.into_iter().collect::<HashSet<_>>();
self.with_marker_attributes_filter("collator", move |attrs| {
attrs.is_empty()
|| set.contains(attrs.as_str())
|| !attrs.as_str().starts_with("search")
|| set.contains("search*")
})
}
pub fn with_recommended_segmenter_models(self) -> Self {
self.with_segmenter_models([
"cjdict".into(),
"burmesedict".into(),
"khmerdict".into(),
"laodict".into(),
"thaidict".into(),
"Burmese_codepoints_exclusive_model4_heavy".into(),
"Khmer_codepoints_exclusive_model4_heavy".into(),
"Lao_codepoints_exclusive_model4_heavy".into(),
"Thai_codepoints_exclusive_model4_heavy".into(),
])
}
pub fn with_segmenter_models(self, models: impl IntoIterator<Item = String>) -> Self {
let set = models.into_iter().collect::<HashSet<_>>();
self.with_marker_attributes_filter("segmenter", move |attrs| set.contains(attrs.as_str()))
}
pub fn export(
self,
provider: &impl ExportableProvider,
mut sink: impl DataExporter,
) -> Result<ExportMetadata, DataError> {
self.export_dyn(provider, &mut sink)
}
}
#[non_exhaustive]
#[derive(Debug)]
pub struct ExportMetadata {
pub exporter: ExporterCloseMetadata,
}
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct NoFallbackOptions {}
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum DeduplicationStrategy {
Maximal,
RetainBaseLanguages,
None,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct FallbackOptions {
pub deduplication_strategy: DeduplicationStrategy,
}
impl From<DeduplicationStrategy> for FallbackOptions {
fn from(deduplication_strategy: DeduplicationStrategy) -> Self {
Self {
deduplication_strategy,
}
}
}
#[test]
fn test_family_precedence() {
let driver = ExportDriver::new(
[
"en".parse().unwrap(),
"%en".parse().unwrap(),
"@en".parse().unwrap(),
"%zh-TW".parse().unwrap(),
"^zh-TW".parse().unwrap(),
],
DeduplicationStrategy::None.into(),
LocaleFallbacker::new_without_data(),
);
assert_eq!(
driver.requested_families,
[
(
icu::locale::data_locale!("en"),
DataLocaleFamilyAnnotations::single()
),
(
icu::locale::data_locale!("zh-TW"),
DataLocaleFamilyAnnotations::without_descendants()
),
]
.into_iter()
.collect::<HashMap<_, _>>()
);
}