use ferrocat_icu::{IcuPseudolocalizationOptions, pseudolocalize_icu_message, stringify_icu};
use super::icu_syntax::parse_icu_with_syntax_policy;
use super::{ApiError, CompiledCatalogArtifact, IcuSyntaxPolicy};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct CompiledCatalogPseudolocalizationOptions<'a> {
pub icu_options: IcuPseudolocalizationOptions<'a>,
pub syntax_policy: IcuSyntaxPolicy,
}
impl Default for CompiledCatalogPseudolocalizationOptions<'_> {
fn default() -> Self {
Self {
icu_options: IcuPseudolocalizationOptions::new(),
syntax_policy: IcuSyntaxPolicy::Strict,
}
}
}
impl<'a> CompiledCatalogPseudolocalizationOptions<'a> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_icu_options(mut self, icu_options: IcuPseudolocalizationOptions<'a>) -> Self {
self.icu_options = icu_options;
self
}
#[must_use]
pub fn with_syntax_policy(mut self, syntax_policy: IcuSyntaxPolicy) -> Self {
self.syntax_policy = syntax_policy;
self
}
}
pub fn pseudolocalize_compiled_catalog_artifact(
artifact: &CompiledCatalogArtifact,
options: &CompiledCatalogPseudolocalizationOptions<'_>,
) -> Result<CompiledCatalogArtifact, ApiError> {
let mut pseudolocalized = artifact.clone();
for (key, message) in &mut pseudolocalized.messages {
let parsed =
parse_icu_with_syntax_policy(message, options.syntax_policy).map_err(|error| {
ApiError::Unsupported(format!(
"compiled catalog artifact message {key:?} cannot be pseudolocalized: {error}"
))
})?;
*message = stringify_icu(&pseudolocalize_icu_message(&parsed, &options.icu_options));
}
Ok(pseudolocalized)
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use ferrocat_icu::IcuPseudolocalizationOptions;
use super::{
CompiledCatalogPseudolocalizationOptions, pseudolocalize_compiled_catalog_artifact,
};
use crate::api::{
ApiError, CatalogMessageKey, CompiledCatalogArtifact, CompiledCatalogMissingMessage,
IcuSyntaxPolicy,
};
#[test]
fn pseudolocalize_compiled_artifact_transforms_messages_only() {
let artifact = CompiledCatalogArtifact {
messages: BTreeMap::from([("runtime-key".to_owned(), "Hello {name}".to_owned())]),
missing: vec![CompiledCatalogMissingMessage {
key: "runtime-key".to_owned(),
source_key: CatalogMessageKey::new("Hello {name}", None),
requested_locale: "qps".to_owned(),
resolved_locale: Some("en".to_owned()),
}],
diagnostics: Vec::new(),
};
let pseudolocalized = pseudolocalize_compiled_catalog_artifact(
&artifact,
&CompiledCatalogPseudolocalizationOptions::new()
.with_icu_options(IcuPseudolocalizationOptions::new().with_expansion_percent(0)),
)
.expect("pseudolocalize artifact");
assert_eq!(
pseudolocalized.messages["runtime-key"],
"[!! Ĥéļļö {name} !!]"
);
assert_eq!(pseudolocalized.missing, artifact.missing);
assert_eq!(pseudolocalized.diagnostics, artifact.diagnostics);
}
#[test]
fn pseudolocalize_compiled_artifact_reports_invalid_runtime_messages() {
let artifact = CompiledCatalogArtifact {
messages: BTreeMap::from([("runtime-key".to_owned(), "{broken".to_owned())]),
missing: Vec::new(),
diagnostics: Vec::new(),
};
let error = pseudolocalize_compiled_catalog_artifact(
&artifact,
&CompiledCatalogPseudolocalizationOptions::new(),
)
.expect_err("invalid runtime message");
assert!(
matches!(error, ApiError::Unsupported(message) if message.contains("message \"runtime-key\" cannot be pseudolocalized"))
);
}
#[test]
fn pseudolocalize_compiled_artifact_uses_runtime_apostrophe_policy() {
let artifact = CompiledCatalogArtifact {
messages: BTreeMap::from([("runtime-key".to_owned(), "You're {name}".to_owned())]),
missing: Vec::new(),
diagnostics: Vec::new(),
};
let pseudolocalized = pseudolocalize_compiled_catalog_artifact(
&artifact,
&CompiledCatalogPseudolocalizationOptions::new()
.with_icu_options(IcuPseudolocalizationOptions::new().with_expansion_percent(0))
.with_syntax_policy(IcuSyntaxPolicy::RuntimeLiteralApostrophes),
)
.expect("pseudolocalize artifact");
assert!(pseudolocalized.messages["runtime-key"].contains("{name}"));
assert_ne!(
pseudolocalized.messages["runtime-key"],
artifact.messages["runtime-key"]
);
}
}