Skip to main content

lingora_core/audit/
workspace.rs

1use std::collections::HashSet;
2
3use crate::{
4    domain::{LanguageRoot, Locale},
5    fluent::FluentFile,
6    rust::RustFile,
7};
8
9/// The complete set of files and configuration used as input to an audit.
10///
11/// A `Workspace` gathers:
12/// - All discovered Fluent translation files (`.ftl`)
13/// - The designated canonical (reference) locale
14/// - Explicitly configured primary locales
15/// - Rust source files to scan for `dioxus_i18n`
16#[derive(Clone, Debug)]
17pub struct Workspace {
18    fluent_files: Vec<FluentFile>,
19    canonical: Locale,
20    primaries: Vec<Locale>,
21    rust_files: Vec<RustFile>,
22}
23
24impl Workspace {
25    /// Creates a new `Workspace` from its constituent parts.
26    pub fn new(
27        fluent_files: Vec<FluentFile>,
28        canonical: Locale,
29        primaries: Vec<Locale>,
30        rust_files: Vec<RustFile>,
31    ) -> Self {
32        Self {
33            fluent_files,
34            canonical,
35            primaries,
36            rust_files,
37        }
38    }
39
40    /// Returns a slice of all Fluent files in the workspace.
41    pub fn fluent_files(&self) -> &[FluentFile] {
42        &self.fluent_files
43    }
44
45    /// Returns an iterator over all **unique** locales found in the Fluent files.
46    pub fn locales(&self) -> impl Iterator<Item = &Locale> {
47        self.fluent_files
48            .iter()
49            .map(|f| f.locale())
50            .collect::<HashSet<_>>()
51            .into_iter()
52    }
53
54    /// Returns an iterator over all unique **language roots** present in the workspace.
55    pub fn language_roots(&self) -> impl Iterator<Item = LanguageRoot> {
56        self.fluent_files
57            .iter()
58            .map(|f| LanguageRoot::from(f.locale()))
59            .collect::<HashSet<_>>()
60            .into_iter()
61    }
62
63    /// Returns an iterator over all locales that belong to the given language root.
64    pub fn locales_by_language_root(&self, root: &LanguageRoot) -> impl Iterator<Item = &Locale> {
65        self.fluent_files.iter().filter_map(|f| {
66            let locale = f.locale();
67            (LanguageRoot::from(locale) == *root).then_some(locale)
68        })
69    }
70
71    /// Returns an iterator over all Fluent files for the exact given locale,
72    /// sorted by file path (stable order).
73    pub fn fluent_files_by_locale(&self, locale: &Locale) -> impl Iterator<Item = &FluentFile> {
74        let mut files = self
75            .fluent_files()
76            .iter()
77            .filter(|f| f.locale() == locale)
78            .collect::<Vec<_>>();
79        files.sort_by_key(|f| f.path());
80        files.into_iter()
81    }
82
83    /// Returns the configured canonical (reference) locale.
84    pub fn canonical_locale(&self) -> &Locale {
85        &self.canonical
86    }
87
88    /// Returns `true` if the given locale is the canonical one.
89    pub fn is_canonical_locale(&self, locale: &Locale) -> bool {
90        locale == self.canonical_locale()
91    }
92
93    /// Returns an iterator over all explicitly configured primary locales.
94    pub fn primary_locales(&self) -> impl Iterator<Item = &Locale> {
95        self.primaries.iter()
96    }
97
98    /// Returns `true` if the given locale is one of the configured primaries.
99    pub fn is_primary_locale(&self, locale: &Locale) -> bool {
100        self.primary_locales().any(|p| p == locale)
101    }
102
103    /// Returns an iterator over all **base** locales — i.e. canonical + primaries.
104    pub fn base_locales(&self) -> impl Iterator<Item = &Locale> {
105        self.primaries
106            .iter()
107            .chain(std::iter::once(&self.canonical))
108    }
109
110    /// Returns `true` if the given locale is a base locale (canonical or primary).
111    pub fn is_base_locale(&self, locale: &Locale) -> bool {
112        self.base_locales().any(|p| p == locale)
113    }
114
115    /// Returns an iterator over all **variant** locales for the given base locale.
116    pub fn variant_locales(&self, base: &Locale) -> impl Iterator<Item = &Locale> {
117        let base_root = LanguageRoot::from(base);
118        self.fluent_files.iter().filter_map(move |f| {
119            let locale = f.locale();
120            let root = LanguageRoot::from(locale);
121            (base_root == root && base != locale).then_some(locale)
122        })
123    }
124
125    /// Returns an iterator over all **orphan** locales.
126    pub fn orphan_locales(&self) -> impl Iterator<Item = &Locale> {
127        let base_roots = Vec::from_iter(self.base_locales().map(LanguageRoot::from));
128        self.fluent_files.iter().filter_map(move |f| {
129            let locale = f.locale();
130            let root = LanguageRoot::from(locale);
131            (!base_roots.contains(&root)).then_some(locale)
132        })
133    }
134
135    /// Returns `true` if the given locale is an orphan (no matching base root).
136    pub fn is_orphan_locale(&self, locale: &Locale) -> bool {
137        self.orphan_locales().any(|p| p == locale)
138    }
139
140    /// Returns a slice of all Rust files included in the workspace.
141    pub fn rust_files(&self) -> &[RustFile] {
142        &self.rust_files
143    }
144}