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}