Skip to main content

ferrocat_po/api/
message_status.rs

1use std::collections::BTreeSet;
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6use super::{CatalogMessage, CatalogMessageKey, EffectiveTranslationRef, NormalizedParsedCatalog};
7
8/// Canonical catalog message status used by coverage and review reports.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
12#[non_exhaustive]
13pub enum CatalogMessageStatus {
14    /// Active target message exists and has a non-empty translation.
15    Translated,
16    /// No active or obsolete target entry exists for the active source identity.
17    Missing,
18    /// Active target entry exists, but its effective translation is empty.
19    Empty,
20    /// Target entry exists for the active source identity, but is obsolete.
21    Obsolete,
22    /// Active target entry is not present in the active source identity set.
23    Extra,
24}
25
26pub(super) fn active_message_keys(
27    catalog: &NormalizedParsedCatalog,
28) -> BTreeSet<CatalogMessageKey> {
29    catalog
30        .iter()
31        .filter_map(|(key, message)| (message.obsolete.is_none()).then_some(key.clone()))
32        .collect()
33}
34
35pub(super) fn classify_expected_message(
36    target_catalog: &NormalizedParsedCatalog,
37    key: &CatalogMessageKey,
38) -> CatalogMessageStatus {
39    let Some(message) = target_catalog.get(key) else {
40        return CatalogMessageStatus::Missing;
41    };
42    if message.obsolete.is_some() {
43        return CatalogMessageStatus::Obsolete;
44    }
45    if translation_is_empty(message) {
46        return CatalogMessageStatus::Empty;
47    }
48    CatalogMessageStatus::Translated
49}
50
51pub(super) fn is_extra_target_message(
52    source_keys: &BTreeSet<CatalogMessageKey>,
53    key: &CatalogMessageKey,
54    message: &CatalogMessage,
55) -> bool {
56    message.obsolete.is_none() && !source_keys.contains(key)
57}
58
59pub(super) fn translation_is_empty(message: &CatalogMessage) -> bool {
60    match message.effective_translation() {
61        EffectiveTranslationRef::Singular(value) => value.trim().is_empty(),
62        EffectiveTranslationRef::Plural(translations) => {
63            translations.is_empty() || translations.values().any(|value| value.trim().is_empty())
64        }
65    }
66}