Skip to main content

phrasebook_catalog/
catalog.rs

1use crate::Lang;
2
3/// One language's Fluent source, usually embedded with `include_str!`.
4///
5/// It is parsed only when a bundle in its language is first needed.
6#[derive(Debug, Clone, Copy)]
7pub struct Source {
8    lang: Lang,
9    ftl: &'static str,
10}
11
12impl Source {
13    /// The Fluent source `ftl` for `lang`.
14    pub const fn new(lang: Lang, ftl: &'static str) -> Source {
15        Source { lang, ftl }
16    }
17
18    /// The language of this source.
19    pub const fn lang(&self) -> Lang {
20        self.lang
21    }
22
23    /// The Fluent source text.
24    pub const fn ftl(&self) -> &'static str {
25        self.ftl
26    }
27}
28
29/// Everything one crate ships: its name, the prefix every id it defines must
30/// start with (if any), and one [`Source`] per language.
31///
32/// The first source is the catalog's default language, which phrasebook's
33/// [`Phrases::from_catalog`] and [`Phrases::with_backstop`] fall back to.
34///
35/// [`Phrases::from_catalog`]: https://docs.rs/phrasebook/latest/phrasebook/struct.Phrases.html#method.from_catalog
36/// [`Phrases::with_backstop`]: https://docs.rs/phrasebook/latest/phrasebook/struct.Phrases.html#method.with_backstop
37///
38/// # Examples
39///
40/// An app's catalog:
41///
42/// ```
43/// use phrasebook_catalog::{Catalog, Lang, Source};
44///
45/// const SV_SE: Lang = Lang::new("sv-SE");
46///
47/// static L10N: Catalog = Catalog::new("app", &[
48///     Source::new(Lang::EN_US, "hello = Hello"),
49///     Source::new(SV_SE, "hello = Hej"),
50/// ]);
51///
52/// assert_eq!(L10N.languages().collect::<Vec<_>>(), [Lang::EN_US, SV_SE]);
53/// ```
54///
55/// A library's, whose ids all start with its prefix:
56///
57/// ```
58/// use phrasebook_catalog::{Catalog, Lang, Source};
59///
60/// pub static L10N: Catalog = Catalog::new("watchword", &[
61///     Source::new(Lang::EN_US, "watchword-sign-in = Sign in"),
62/// ])
63/// .with_prefix("watchword-");
64///
65/// assert_eq!(L10N.prefix(), Some("watchword-"));
66/// ```
67#[derive(Debug, Clone, Copy)]
68pub struct Catalog {
69    name: &'static str,
70    prefix: Option<&'static str>,
71    sources: &'static [Source],
72}
73
74impl Catalog {
75    /// A catalog named `name` (used in logs and check reports) with one
76    /// source per language, the default language first.
77    pub const fn new(name: &'static str, sources: &'static [Source]) -> Catalog {
78        Catalog {
79            name,
80            prefix: None,
81            sources,
82        }
83    }
84
85    /// Require every message and term id this catalog defines to start with
86    /// `prefix`. In phrasebook, a violation is logged when a bundle is built
87    /// and fails [`check::catalog`].
88    ///
89    /// [`check::catalog`]: https://docs.rs/phrasebook/latest/phrasebook/check/fn.catalog.html
90    pub const fn with_prefix(self, prefix: &'static str) -> Catalog {
91        Catalog {
92            prefix: Some(prefix),
93            ..self
94        }
95    }
96
97    /// The catalog's name.
98    pub const fn name(&self) -> &'static str {
99        self.name
100    }
101
102    /// The prefix its ids must have, if any.
103    pub const fn prefix(&self) -> Option<&'static str> {
104        self.prefix
105    }
106
107    /// Its sources, the default language first.
108    pub const fn sources(&self) -> &'static [Source] {
109        self.sources
110    }
111
112    /// The catalog's default language, that of its first source.
113    pub fn default_lang(&self) -> Option<Lang> {
114        self.sources.first().map(Source::lang)
115    }
116
117    /// The languages it has sources for, in order.
118    pub fn languages(&self) -> impl Iterator<Item = Lang> {
119        self.sources.iter().map(Source::lang)
120    }
121
122    /// The source for `lang`, the first if there are several.
123    pub fn source(&self, lang: Lang) -> Option<&'static Source> {
124        self.sources.iter().find(|s| s.lang == lang)
125    }
126}