Skip to main content

lingora_core/fluent/
document.rs

1use std::cell::OnceCell;
2
3use fluent4rs::{ast::*, prelude::Walker};
4
5use crate::{
6    domain::{HasLocale, Locale},
7    fluent::{Definitions, ParsedFluentFile, QualifiedIdentifier, Signature},
8};
9
10/// A normalized, representation of all Fluent translations for **one locale**.
11///
12/// `FluentDocument` aggregates entries from one or more `ParsedFluentFile`s belonging
13/// to the same locale (e.g. split files like `en-GB/auth.ftl`, `en-GB/ui.ftl`, `en-GB/errors.ftl` all for `en-GB`).
14#[derive(Clone, Debug, Default, PartialEq, Eq)]
15pub struct FluentDocument {
16    locale: Locale,
17    resource: Resource,
18    analysis: OnceCell<Definitions>,
19}
20
21impl FluentDocument {
22    /// Constructs a `FluentDocument` by merging entries from all `ParsedFluentFile`s
23    /// that match the given locale.
24    pub fn from_parsed_files(locale: &Locale, files: &[ParsedFluentFile]) -> Self {
25        let entries = files
26            .iter()
27            .filter(|f| f.locale() == locale)
28            .filter_map(|f| f.resource())
29            .flat_map(|r| r.entries().into_iter().cloned())
30            .collect::<Vec<_>>();
31
32        let locale = locale.clone();
33        let resource = Resource::from(entries);
34        let analysis = OnceCell::default();
35
36        Self {
37            locale,
38            resource,
39            analysis,
40        }
41    }
42
43    /// Returns the locale this document represents.
44    pub fn locale(&self) -> &Locale {
45        &self.locale
46    }
47
48    /// Returns a reference to the `Definitions` analysis.
49    fn definitions(&self) -> &Definitions {
50        self.analysis.get_or_init(|| {
51            let mut analysis = Definitions::default();
52            Walker::walk(&self.resource, &mut analysis);
53            analysis
54        })
55    }
56
57    /// Returns an iterator over all **top-level** qualified identifiers defined in this document.
58    pub fn entry_identifiers(&self) -> impl Iterator<Item = QualifiedIdentifier> {
59        self.definitions().entry_identifiers()
60    }
61
62    /// Returns an iterator over **all** identifiers used or defined (including placeholders
63    /// and references inside patterns).
64    pub fn all_identifiers(&self) -> impl Iterator<Item = QualifiedIdentifier> {
65        self.definitions().all_identifiers()
66    }
67
68    /// Returns an iterator over identifiers that appear more than once in the document.
69    pub fn duplicate_identifier_names(&self) -> impl Iterator<Item = QualifiedIdentifier> {
70        self.definitions().duplicate_identifiers().into_iter()
71    }
72
73    /// Returns an iterator over identifiers that are referenced, e.g. `{ $var }`,
74    /// but **not defined** elsewhere in the document.
75    pub fn invalid_references(&self) -> impl Iterator<Item = QualifiedIdentifier> {
76        self.definitions().invalid_references().into_iter()
77    }
78
79    /// Returns the placeholder signature (arguments/variables) for the given identifier,
80    /// if it is defined in this document.
81    pub fn signature(&self, identifier: &QualifiedIdentifier) -> Option<&Signature> {
82        self.definitions().signature(identifier)
83    }
84
85    /// Returns an iterator over all AST `Entry` nodes that define the given identifier.
86    pub fn entries(&self, identifier: &QualifiedIdentifier) -> impl Iterator<Item = &Entry> {
87        self.definitions().entries(identifier)
88    }
89}
90
91impl HasLocale for FluentDocument {
92    fn locale(&self) -> &Locale {
93        &self.locale
94    }
95}