es_fluent/traits/
label.rs1use super::FluentLocalizer;
2use crate::registry::{StaticFluentDomain, StaticFluentEntryId};
3
4pub trait FluentLabel {
10 fn fluent_label_domain() -> StaticFluentDomain;
12
13 fn fluent_label_id() -> StaticFluentEntryId;
15
16 fn try_localize_label<L: FluentLocalizer + ?Sized>(localizer: &L) -> Option<String> {
19 try_localize_label(
20 localizer,
21 Self::fluent_label_domain(),
22 Self::fluent_label_id(),
23 )
24 }
25
26 fn localize_label<L: FluentLocalizer + ?Sized>(localizer: &L) -> String {
29 localize_label(
30 localizer,
31 Self::fluent_label_domain(),
32 Self::fluent_label_id(),
33 )
34 }
35
36 fn fallback_label() -> String {
43 fallback_label::<Self>()
44 }
45}
46
47#[doc(hidden)]
48pub fn try_localize_label<L: FluentLocalizer + ?Sized>(
49 localizer: &L,
50 domain: StaticFluentDomain,
51 id: StaticFluentEntryId,
52) -> Option<String> {
53 localizer.localize_in_domain(domain, id, None)
54}
55
56#[doc(hidden)]
57pub fn localize_label<L: FluentLocalizer + ?Sized>(
58 localizer: &L,
59 domain: StaticFluentDomain,
60 id: StaticFluentEntryId,
61) -> String {
62 localizer
63 .localize_in_domain(domain, id, None)
64 .unwrap_or_else(|| {
65 tracing::warn!(
66 domain = domain.as_str(),
67 message_id = id.as_str(),
68 "missing Fluent label"
69 );
70 id.as_str().to_string()
71 })
72}
73
74pub fn fallback_label<T: FluentLabel + ?Sized>() -> String {
81 humanize_fluent_entry_id(T::fluent_label_id())
82}
83
84pub fn humanize_fluent_entry_id(id: StaticFluentEntryId) -> String {
89 humanize_fluent_entry_key(id.as_str())
90}
91
92fn humanize_fluent_entry_key(id: &str) -> String {
93 let id = id.strip_suffix("_label").unwrap_or(id);
94 id.split(['_', '-'])
95 .filter(|part| !part.is_empty())
96 .map(|part| {
97 let mut chars = part.chars();
98 match chars.next() {
99 Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
100 None => String::new(),
101 }
102 })
103 .collect::<Vec<_>>()
104 .join(" ")
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::FluentArgs;
111
112 fn static_domain(value: &'static str) -> StaticFluentDomain {
113 StaticFluentDomain::try_new(value).expect("valid test domain")
114 }
115
116 fn static_entry(value: &'static str) -> StaticFluentEntryId {
117 StaticFluentEntryId::try_new(value).expect("valid test message id")
118 }
119
120 struct LabelLocalizer;
121
122 impl FluentLocalizer for LabelLocalizer {
123 fn localize<'a>(
124 &self,
125 id: StaticFluentEntryId,
126 _args: Option<&FluentArgs<'a>>,
127 ) -> Option<String> {
128 (id == "label-id").then(|| "Label".to_string())
129 }
130
131 fn localize_in_domain<'a>(
132 &self,
133 domain: StaticFluentDomain,
134 id: StaticFluentEntryId,
135 args: Option<&FluentArgs<'a>>,
136 ) -> Option<String> {
137 (domain == "label-domain")
138 .then(|| self.localize(id, args))
139 .flatten()
140 }
141 }
142
143 struct TestLabel;
144
145 impl FluentLabel for TestLabel {
146 fn fluent_label_domain() -> StaticFluentDomain {
147 static_domain("label-domain")
148 }
149
150 fn fluent_label_id() -> StaticFluentEntryId {
151 static_entry("label-id")
152 }
153 }
154
155 #[test]
156 fn label_trait_exposes_typed_metadata_and_localizes_with_fallback() {
157 let localizer = LabelLocalizer;
158
159 assert_eq!(TestLabel::fluent_label_domain(), "label-domain");
160 assert_eq!(TestLabel::fluent_label_id(), "label-id");
161 assert_eq!(
162 TestLabel::try_localize_label(&localizer),
163 Some("Label".into())
164 );
165 assert_eq!(TestLabel::localize_label(&localizer), "Label");
166 }
167
168 #[test]
169 fn localize_label_helpers_return_localized_value_or_id_fallback() {
170 let localizer = LabelLocalizer;
171
172 assert_eq!(
173 try_localize_label(
174 &localizer,
175 static_domain("label-domain"),
176 static_entry("label-id")
177 ),
178 Some("Label".into())
179 );
180 assert_eq!(
181 try_localize_label(
182 &localizer,
183 static_domain("label-domain"),
184 static_entry("missing-id")
185 ),
186 None
187 );
188 assert_eq!(
189 localize_label(
190 &localizer,
191 static_domain("label-domain"),
192 static_entry("label-id")
193 ),
194 "Label"
195 );
196 assert_eq!(
197 localize_label(
198 &localizer,
199 static_domain("label-domain"),
200 static_entry("missing-id")
201 ),
202 "missing-id"
203 );
204 }
205
206 #[test]
207 fn fallback_label_helpers_humanize_typed_label_ids_without_a_localizer() {
208 assert_eq!(TestLabel::fallback_label(), "Label Id");
209 assert_eq!(fallback_label::<TestLabel>(), "Label Id");
210 assert_eq!(
211 humanize_fluent_entry_id(static_entry("sales-order_status_label")),
212 "Sales Order Status"
213 );
214 }
215}