Repository: zhiburt/tabled
Pull request #412 (2024-06-26 18:23:37)

feat: add i18n support
Add support for customizing the record template for extended tables so that clients (e.g. https://github.com/theseus-rs/rsql) using this library can localize the template.

zhiburt: Hi @brianheineman 

I tend to think that the original problem is way simpler :smile: just provide a method to replace `"Record"` text.
So you'd be able to use `i18n` on your own by just setting this value.

Something like

```rust
ExtendedTable::new(&data)
    .template(|f, index| write!(f, "[ {} {index} ]", localized("record_name")));
```

What do you think?

And I think we do missing this method, anyway.
Let me know if you'd like to contribute.

PS: Good call with introduction of a feature.

__________________________

Though I am thinking is there somewhere an actual need/feature for `i18n`, we could provide, probably no?
brianheineman: @zhiburt I really like your suggestion and I updated the PR to reflect the change.  Please let me know if there are any other changes you would like to see.

zhiburt: Thanks for contribution.

To be honest I doubt when it will be released.
I have a few important changes on the list, which I am struggle to have done for a few month already ..............
Hopefully soon :disappointed: 

Take care.
Have a good weekend.

Patch:
diff --git a/tabled/src/tables/extended.rs b/tabled/src/tables/extended.rs
index 60af682..6a682ce 100644
--- a/tabled/src/tables/extended.rs
+++ b/tabled/src/tables/extended.rs
@@ -48,8 +48,9 @@
 //! assert_eq!(table, expected);
 //! ```
 
-use std::fmt::{self, Display};
-
+use std::cell::RefCell;
+use std::fmt::{self, Debug, Display};
+use std::rc::Rc;
 use crate::grid::util::string::string_width;
 use crate::Tabled;
 
@@ -77,10 +78,11 @@ use crate::Tabled;
 ///     )
 /// );
 /// ```
-#[derive(Debug, Clone)]
+#[derive(Clone)]
 pub struct ExtendedTable {
     fields: Vec<String>,
     records: Vec<Vec<String>>,
+    template: Rc<RefCell<dyn Fn(usize) -> String>>,
 }
 
 impl ExtendedTable {
@@ -106,6 +108,7 @@ impl ExtendedTable {
         Self {
             records: data,
             fields: header,
+            template: Rc::new(RefCell::new(record_template)),
         }
     }
 
@@ -162,6 +165,15 @@ impl ExtendedTable {
 
         true
     }
+
+    /// Sets the template for a record.
+    pub fn template<F>(mut self, template: F) -> Self
+    where
+        F: Fn(usize) -> String + 'static,
+    {
+        self.template = Rc::new(RefCell::new(template));
+        self
+    }
 }
 
 impl From<Vec<Vec<String>>> for ExtendedTable {
@@ -170,6 +182,7 @@ impl From<Vec<Vec<String>>> for ExtendedTable {
             return Self {
                 fields: vec![],
                 records: vec![],
+                template: Rc::new(RefCell::new(record_template)),
             };
         }
 
@@ -178,6 +191,7 @@ impl From<Vec<Vec<String>>> for ExtendedTable {
         Self {
             fields,
             records: data,
+            template: Rc::new(RefCell::new(record_template)),
         }
     }
 }
@@ -207,7 +221,7 @@ impl Display for ExtendedTable {
             .unwrap_or_default();
 
         for (i, records) in self.records.iter().enumerate() {
-            write_header_template(f, i, max_field_width, max_values_length)?;
+            write_header_template(f, &self.template, i, max_field_width, max_values_length)?;
 
             for (value, field) in records.iter().zip(fields.iter()) {
                 writeln!(f)?;
@@ -224,6 +238,15 @@ impl Display for ExtendedTable {
     }
 }
 
+impl Debug for ExtendedTable {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("ExtendedTable")
+            .field("fields", &self.fields)
+            .field("records", &self.records)
+            .finish_non_exhaustive()
+    }
+}
+
 fn truncate_records(records: &mut Vec<Vec<String>>, max_width: usize, suffix: &str) {
     for fields in records {
         truncate_fields(fields, max_width, suffix);
@@ -238,11 +261,13 @@ fn truncate_fields(records: &mut Vec<String>, max_width: usize, suffix: &str) {
 
 fn write_header_template(
     f: &mut fmt::Formatter<'_>,
+    template: &Rc<RefCell<dyn Fn(usize) -> String>>,
     index: usize,
     max_field_width: usize,
     max_values_length: usize,
 ) -> fmt::Result {
-    let mut template = format!("-[ RECORD {index} ]-");
+    let record_template = template.borrow()(index);
+    let mut template = format!("-{record_template}-");
     let default_template_length = template.len();
 
     // 3 - is responsible for ' | ' formatting
@@ -296,3 +321,7 @@ fn truncate(text: &mut String, max: usize, suffix: &str) {
         text.push_str(suffix);
     }
 }
+
+fn record_template(index: usize) -> String {
+    format!("[ RECORD {index} ]")
+}


Test patch:
diff --git a/tabled/tests/core/extended_table_test.rs b/tabled/tests/core/extended_table_test.rs
index 0a07f6b..6132855 100644
--- a/tabled/tests/core/extended_table_test.rs
+++ b/tabled/tests/core/extended_table_test.rs
@@ -470,3 +470,33 @@ fn display_with_truncate_colored() {
         )
     );
 }
+
+#[test]
+fn record_template() {
+    build_tabled_type!(
+        TestType,
+        3,
+        ["1", "2", "3"],
+        ["a", "b", "c"]
+    );
+    let data: Vec<TestType> = vec![TestType, TestType];
+
+    let description = "ROW";
+    let table = ExtendedTable::new(&data)
+        .template(move |index| format!("< {} {} >", description, index + 1));
+    let table = table.to_string();
+
+    assert_eq!(
+        table,
+        static_table!(
+            "-< ROW 1 >-"
+            "a | 1"
+            "b | 2"
+            "c | 3"
+            "-< ROW 2 >-"
+            "a | 1"
+            "b | 2"
+            "c | 3"
+        )
+    );
+}