Skip to main content

dioxus_dnd/core/
strings.rs

1#![doc = include_str!("../../docs/api/localization.md")]
2
3use std::rc::Rc;
4
5use dioxus::prelude::*;
6
7/// A phrase taking two names (the zone, then its parent).
8pub type TwoNamePhrase = Rc<dyn Fn(&str, &str) -> String>;
9
10/// The crate's voice, one field per phrase. Every field is a function so
11/// translations can reorder, inflect or pluralize freely; build it with
12/// struct-update syntax over [`Default::default`] to override only what
13/// you translate.
14#[derive(Clone)]
15pub struct DndStrings {
16    /// Voiced when a keyboard drag picks an item up. Receives the
17    /// draggable's `label`. This is also the user's manual - keep the
18    /// key instructions (arrows, Enter, Escape) in the translation.
19    pub picked_up: Rc<dyn Fn(&str) -> String>,
20    /// Voiced when keyboard navigation reaches a zone. Receives the zone's
21    /// name.
22    pub over: Rc<dyn Fn(&str) -> String>,
23    /// Voiced when keyboard navigation reaches a zone nested in a labeled
24    /// parent. Receives the zone's name, then the parent's.
25    pub over_inside: TwoNamePhrase,
26    /// Voiced when an arrow key finds nowhere to go.
27    pub no_targets: Rc<dyn Fn() -> String>,
28    /// Voiced when Enter is pressed with no zone selected.
29    pub no_target_selected: Rc<dyn Fn() -> String>,
30    /// Voiced when a keyboard drop lands. Receives the zone's name.
31    pub dropped_in: Rc<dyn Fn(&str) -> String>,
32    /// Voiced when Escape cancels the drag.
33    pub cancelled: Rc<dyn Fn() -> String>,
34    /// Fallback name for a draggable with no `label`.
35    pub item: Rc<dyn Fn() -> String>,
36    /// Fallback name for a zone with no `label`. Receives the zone id's
37    /// number.
38    pub zone: Rc<dyn Fn(u64) -> String>,
39    /// `ReorderButtons`: the up button's `aria-label`. Receives the row's
40    /// name.
41    pub move_up: Rc<dyn Fn(&str) -> String>,
42    /// `ReorderButtons`: the down button's `aria-label`. Receives the
43    /// row's name.
44    pub move_down: Rc<dyn Fn(&str) -> String>,
45    /// `ReorderButtons`: fallback name for a row with no `label`. Receives
46    /// the 1-based row number.
47    pub row: Rc<dyn Fn(usize) -> String>,
48    /// `SelectionCount`: the badge text. Receives how many items are in
49    /// flight - your chance at real plural rules.
50    pub selection_count: Rc<dyn Fn(usize) -> String>,
51}
52
53impl Default for DndStrings {
54    /// The built-in English.
55    fn default() -> Self {
56        Self {
57            picked_up: Rc::new(|name| {
58                format!(
59                    "Picked up {name}. Use arrow keys to choose a drop target, \
60                     Enter to drop, Escape to cancel."
61                )
62            }),
63            over: Rc::new(|name| format!("Over {name}.")),
64            over_inside: Rc::new(|name, parent| format!("Over {name}, inside {parent}.")),
65            no_targets: Rc::new(|| "No drop targets available.".to_string()),
66            no_target_selected: Rc::new(|| "No drop target selected.".to_string()),
67            dropped_in: Rc::new(|name| format!("Dropped in {name}.")),
68            cancelled: Rc::new(|| "Drag cancelled.".to_string()),
69            item: Rc::new(|| "item".to_string()),
70            zone: Rc::new(|n| format!("zone {n}")),
71            move_up: Rc::new(|name| format!("Move {name} up")),
72            move_down: Rc::new(|name| format!("Move {name} down")),
73            row: Rc::new(|n| format!("item {n}")),
74            selection_count: Rc::new(|n| format!("{n} item(s)")),
75        }
76    }
77}
78
79impl std::fmt::Debug for DndStrings {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        f.write_str("DndStrings")
82    }
83}
84
85/// The subtree's [`DndStrings`], or the English defaults when no ancestor
86/// provided one. Captured once per component instance - localize by having
87/// the provided closures read your locale state, not by re-providing the
88/// struct. Public so custom components voice themselves consistently.
89pub fn use_dnd_strings() -> DndStrings {
90    use_hook(|| try_consume_context::<DndStrings>().unwrap_or_default())
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    /// The built-in English, pinned - these are user-facing contracts apps
98    /// may key translations off.
99    #[test]
100    fn english_defaults() {
101        let s = DndStrings::default();
102        assert_eq!(
103            (s.picked_up)("Piranesi"),
104            "Picked up Piranesi. Use arrow keys to choose a drop target, \
105             Enter to drop, Escape to cancel."
106        );
107        assert_eq!((s.over)("Done"), "Over Done.");
108        assert_eq!((s.over_inside)("Done", "Board"), "Over Done, inside Board.");
109        assert_eq!((s.no_targets)(), "No drop targets available.");
110        assert_eq!((s.no_target_selected)(), "No drop target selected.");
111        assert_eq!((s.dropped_in)("Done"), "Dropped in Done.");
112        assert_eq!((s.cancelled)(), "Drag cancelled.");
113        assert_eq!((s.item)(), "item");
114        assert_eq!((s.zone)(7), "zone 7");
115        assert_eq!((s.move_up)("Draft"), "Move Draft up");
116        assert_eq!((s.move_down)("Draft"), "Move Draft down");
117        assert_eq!((s.row)(3), "item 3");
118        assert_eq!((s.selection_count)(3), "3 item(s)");
119    }
120}