1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::rc::Rc;
use arcstr::ArcStr;
use getset::{CopyGetters, Getters};
use serde::{Deserialize, Serialize};
use xylem::DefaultContext;
use crate::IdString;
pub type Id = crate::Id<Def>;
impl_identifiable!(Def);
pub type ListenerHook = dyn Fn(&Def, &mut DefaultContext) -> anyhow::Result<()>;
pub struct Listener {
pub hook: Rc<ListenerHook>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Getters, CopyGetters)]
#[cfg_attr(feature = "xy", derive(xylem::Xylem))]
#[cfg_attr(feature = "xy", xylem(expose = DefXylem, derive(Deserialize), process))]
pub struct Def {
#[getset(get_copy = "pub")]
#[cfg_attr(feature = "xy", xylem(args(new = true)))]
id: Id,
#[getset(get = "pub")]
#[cfg_attr(feature = "xy", xylem(serde(default)))]
id_str: IdString<Def>,
#[getset(get = "pub")]
languages: BTreeMap<ArcStr, PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Getters, CopyGetters)]
#[cfg_attr(feature = "xy", derive(xylem::Xylem))]
#[cfg_attr(feature = "xy", xylem(derive(Deserialize), process))]
pub struct Item {
#[getset(get_copy = "pub")]
src: Id,
#[getset(get = "pub")]
key: ArcStr,
}
#[cfg(feature = "yew")]
impl yew::html::ImplicitClone for Item {}
#[cfg(feature = "xy")]
pub mod xy {
use std::any::TypeId;
use std::collections::BTreeMap;
use std::rc::Rc;
use fluent::{FluentBundle, FluentResource};
use unic_langid::LanguageIdentifier;
use xylem::{Context, DefaultContext, Processable};
use super::{Def, Id, Item, Listener};
use crate::Schema;
impl Processable<Schema> for Def {
fn postprocess(&mut self, context: &mut DefaultContext) -> anyhow::Result<()> {
let hook = context.get::<Listener>(TypeId::of::<()>()).expect("listener was not setup");
let hook = Rc::clone(&hook.hook);
hook(self, context)
}
}
#[derive(Default)]
pub struct LoadedBundles(BTreeMap<Id, Vec<(LanguageIdentifier, FluentBundle<FluentResource>)>>);
impl LoadedBundles {
pub fn add(
&mut self,
id: Id,
language: LanguageIdentifier,
bundle: FluentBundle<FluentResource>,
) {
let vec = self.0.entry(id).or_default();
vec.push((language, bundle));
}
}
impl Processable<Schema> for Item {
fn postprocess(&mut self, context: &mut DefaultContext) -> anyhow::Result<()> {
use anyhow::Context;
{
let lb = context
.get::<LoadedBundles>(TypeId::of::<()>())
.context("No language bundles loaded yet")?;
let bundles = lb.0.get(&self.src).with_context(|| {
format!("Dangling translation bundle reference {:?}", self.src())
})?;
for (lang, bundle) in bundles {
if bundle.get_message(self.key()).is_none() {
anyhow::bail!(
"Undefined translation key {} in locale {}",
self.key(),
lang
);
}
}
}
Ok(())
}
}
}