Skip to main content

gettext_mcp/model/
entry.rs

1//! A single translation entry within a PO file.
2
3/// Represents a single translation entry in a PO file.
4///
5/// The header entry is also a [`MessageEntry`] — it's the one whose
6/// `msgid` is empty and whose `msgstr` is the `Key: Value\n` header block.
7#[derive(Debug, Clone, Default)]
8pub struct MessageEntry {
9    /// Message ID (singular form, typically English).
10    pub msgid: String,
11    /// Optional context for disambiguation (`msgctxt`).
12    pub msgctxt: Option<String>,
13    /// Translated string (singular form).
14    pub msgstr: String,
15    /// Plural form of the source string (`msgid_plural`).
16    pub msgid_plural: Option<String>,
17    /// Plural translations: `[0]` = singular, `[1]` = plural, etc.
18    pub msgstr_plural: Vec<String>,
19    /// Developer/extracted comments (lines starting with `#.`).
20    pub extracted_comment: Vec<String>,
21    /// Translator comments (lines starting with `# `).
22    pub translator_comment: Vec<String>,
23    /// Source code locations (lines starting with `#:`).
24    pub source_locations: Vec<String>,
25    /// Flags like `fuzzy`, `c-format`, `python-format` (lines starting `#,`).
26    pub flags: Vec<String>,
27    /// Previous untranslated string (line starting with `#|`).
28    pub previous_msgid: Option<String>,
29}
30
31impl MessageEntry {
32    /// True when the entry carries the `fuzzy` flag.
33    pub fn is_fuzzy(&self) -> bool {
34        self.flags.iter().any(|f| f == "fuzzy")
35    }
36
37    /// True when the entry is fully translated per GNU gettext semantics.
38    ///
39    /// Fuzzy entries are *not* considered translated: the gettext runtime
40    /// silently ignores them, so for our purposes they're untranslated.
41    pub fn is_translated(&self) -> bool {
42        if self.is_fuzzy() {
43            return false;
44        }
45        if self.msgid_plural.is_some() {
46            !self.msgstr_plural.is_empty() && self.msgstr_plural.iter().all(|s| !s.is_empty())
47        } else {
48            !self.msgstr.is_empty()
49        }
50    }
51}