advotracker_client: adopt usage of crate twine

* i18n crate
* twine::build_translations(): generate the i18n.rs with all
  language translation strings
* twine::build_translations_from_readers(): generates to t!() macro
  readers: the INI files with the translation strings
* twine::t() consumes the strings
This commit is contained in:
2021-03-21 21:20:54 +01:00
parent 60a7fc2d84
commit 067257e0dd
8 changed files with 673 additions and 925 deletions

View File

@@ -9,22 +9,12 @@ edition = "2018"
default-run = "advotracker" default-run = "advotracker"
build = "build.rs" build = "build.rs"
#[target.x86_64-pc-windows-gnu] [target.x86_64-pc-windows-gnu]
#linker = "lld-link" linker = "lld-link"
#[target.x86_64-pc-windows-msvc]
#linker = "lld-link"
# linker path on msvc set via system environment
#linker = "link.exe"
##windres = { git = "https://github.com/FaultyRAM/windres-rs", branch = "master" }
##diesel = { version = "1.4", features = [ "postgres", "sqlite" ] }
##winapi = { version = "0.3", features = [ "winuser" ] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = [ "winuser" ] } winapi = { version = "0.3", features = [ "winuser" ] }
windres = { git = "https://github.com/FaultyRAM/windres-rs", branch = "master" } windres = { git = "https://github.com/FaultyRAM/windres-rs", branch = "master" }
#diesel = { version = "1.4", features = [ "postgres", "sqlite" ] }
[dependencies] [dependencies]
async-stream = "~0.2" async-stream = "~0.2"
@@ -34,29 +24,23 @@ clap = { version = "~2.33", features = ["suggestions", "color"] }
csv = { version = "~1.1" } csv = { version = "~1.1" }
dotenv = { version = "~0.15.0" } dotenv = { version = "~0.15.0" }
envy = { version = "~0.4" } envy = { version = "~0.4" }
#lettre ={ version = "0.10.0-beta.1", features = ["smtp-transport", "native-tls", "hostname", "r2d2", "builder"] }
lettre ={ version = "0.10.0-beta.1" } lettre ={ version = "0.10.0-beta.1" }
lazy_static = { version = "~1.4.0" } lazy_static = { version = "~1.4.0" }
log = { version = "~0.4.8" } log = { version = "~0.4.8" }
locales = { version = "~0.1" }
maud = { version = "~0.22.1" } maud = { version = "~0.22.1" }
#orbtk = { version = "~0.3.1-alpha4" }
orbtk = { git = "https://github.com/redox-os/orbtk.git", branch = "develop" } orbtk = { git = "https://github.com/redox-os/orbtk.git", branch = "develop" }
#orbtk = { path = "../../../orbtk" } #orbtk = { path = "../../../orbtk" }
serde = { version = "~1.0", features = ["derive"] } serde = { version = "~1", features = ["derive"] }
sdl2 = { version = "~0.34", features = ["bundled"] } sdl2 = { version = "~0.34", features = ["bundled"] }
substring = { version = "~1" } substring = { version = "~1" }
#tokio = { version = "~0.2", features = ["macros", "rt-threaded", "stream", "time"] }
tracing = { version = "~0.1" } tracing = { version = "~0.1" }
tracing-subscriber = { version = "~0.2.0", features = ["tracing-log"] } tracing-subscriber = { version = "~0.2.0", features = ["tracing-log"] }
viperus = { git = "https://github.com/maurocordioli/viperus", features = ["cache", "fmt-clap", "fmt-env", "global", "watch"] } viperus = { git = "https://github.com/maurocordioli/viperus", features = ["cache", "fmt-clap", "fmt-env", "global", "watch"] }
twine = { version = "~0.3.8", features = ["serde"] }
[build-dependencies] [build-dependencies]
#winres = { version = "0.1" }
winres = { version = "0.1.11" } winres = { version = "0.1.11" }
#[dev-dependencies]
[features] [features]
default = [] default = []
debug = ["orbtk/debug"] debug = ["orbtk/debug"]
@@ -71,10 +55,6 @@ description = "Supports lawyers to capture relevant data encountered during an o
[package.metadata.winres] [package.metadata.winres]
LegalCopyright = "Copyright © 2020-2021" LegalCopyright = "Copyright © 2020-2021"
#[[bin]]
#//name = "policycheck"
#path = "src/bin/policycheck.rs"
[[bin]] [[bin]]
name = "advotracker" name = "advotracker"
path = "src/main.rs" path = "src/main.rs"

View File

@@ -6,8 +6,14 @@
*/ */
extern crate winres; extern crate winres;
extern crate twine;
use twine::build_translations;
fn main() { fn main() {
println!("cargo:rerun-if-changed=build.rs");
twine::build_translations(&["./i18n/localization.ini"], "i18n.rs").unwrap();
if cfg!(target_os = "windows") { if cfg!(target_os = "windows") {
let mut res = winres::WindowsResource::new(); let mut res = winres::WindowsResource::new();
//res.set_icon(".\assets\icons\adovtracker\advotracker.ico") //res.set_icon(".\assets\icons\adovtracker\advotracker.ico")

View File

@@ -1,248 +0,0 @@
{
"err.lang.not_found": {
"de_DE.UTF-8": "Konnte Sprachkode nicht auslesen",
"de_DE": "Konnte Sprachkode nicht auslesen",
"C": "Couldn't read LANG"
},
"err.user.not_found": {
"fr": "Utilisateur introuvable: $email, $id",
"de_DE.UTF-8": "Anwender nicht gefunden: $email, $id",
"de_DE": "Anwender nicht gefunden: $email, $id",
"C": "User not found: $email, $id"
},
"main.started": {
"de_DE.UTF-8": "Programmlogik starten",
"de_DE": "Programmlogik starten",
"C": "Program logic started"
},
"main.finished": {
"de_DE.UTF-8": "Programmlogik beendet",
"de_DE": "Programmlogik beendet",
"C": "Program logic finished"
},
"parse.arguments": {
"de_DE.UTF-8": "Programmargumente prüfen",
"de_DE": "Programmargumente prüfen",
"C": "Parsing arguments"
},
"parse.environment": {
"de_DE.UTF-8": "Umgebungsvariablen prüfen",
"de_DE": "Umgebungsvariablen prüfen",
"C": "Parsing environment"
},
"parse.results": {
"de_DE.UTF-8": "Ergebnisse der Konfigurations-Parameterprüfung",
"de_DE": "Ergebnisse der Konfigurationsparameterprüfung",
"C": "Config parsing results"
},
"config.name": {
"de_DE.UTF-8": "Konfigurationswert für",
"de_DE": "Konfigurationswert für",
"C": "Config Value for"
},
"config.name.lang": {
"de_DE.UTF-8": "Sprach-Code_DE",
"de_DE": "Sprach-Code_DE",
"C": "Language code_DE"
},
"config.name.verbositylevel": {
"de_DE.UTF-8": "Ausgabe-Ebene",
"de_DE": "Ausgabe-Ebene",
"C": "verbosity level"
},
"config.name.environment": {
"de_DE.UTF-8": "Umgebungsvariablen",
"de_DE": "Umgebungsvariablen",
"C": "environment"
},
"config.name.configfile": {
"de_DE.UTF-8": "Konfigurations-Datei",
"de_DE": "Konfigurations-Datei",
"C": "config file"
},
"config.name.dbdriver": {
"de_DE.UTF-8": "Datenbank-Treiber",
"de_DE": "Datenbank-Treiber",
"C": "database driver"
},
"config.name.redis": {
"de_DE.UTF-8": "Redis Datenbank",
"de_DE": "Redis Datenbank",
"C": "Redis database"
},
"csv.export.started": {
"de_DE.UTF-8": "Export in eine CSV Datei starten",
"de_DE": "Export in eine CSV Datei starten",
"C": "Export to csv file started"
},
"csv.export.finished": {
"de_DE.UTF-8": "Export in eine CSV Datei beendet",
"de_DE": "Export in eine CSV Datei beendet",
"C": "Export to csv file finished"
},
"csv.import.started": {
"de_DE.UTF-8": "Import aus einer CSV Datei",
"de_DE": "Import aus einer CSV Datei",
"C": "Import from a csv file"
},
"csv.import.finished": {
"de_DE.UTF-8": "Import aus einer CSV Datei beendet",
"de_DE": "Import aus einer CSV Datei beendet",
"C": "Import from a csv file finished"
},
"redis.connection.error": {
"de_DE.UTF-8": "Es konnte keine Redis Verbindung aufgebaut werden",
"de_DE": "Es konnte keine Redis Verbindung aufgebaut werden",
"C": "Could not establish a redis connection"
},
"policy.hashmap.success": {
"de_DE.UTF-8": "Hashmap wurde erfolgreich erstellt",
"de_DE": "Hashmap wurde erfolgreich erstellt",
"C": "Hashmap creation was successfull"
},
"policy.hashmap.failed": {
"de_DE.UTF-8": "Hashmap konnte nicht erstellt werden",
"de_DE": "Hashmap konnte nicht erstellt werden",
"C": "Hashmap creation failed"
},
"policy.menu.label": {
"de_DE.UTF-8": "Menü",
"de_DE": "Menü",
"C": "Menu"
},
"policy.menu.label_account": {
"de_DE.UTF-8": "Aktueller Benutzer",
"de_DE": "Aktueller Benutzer",
"C": "Active user"
},
"policy.menu.label_quit": {
"de_DE.UTF-8": "Beenden",
"de_DE": "Beenden",
"C": "Quit"
},
"policy.menu.label_toggle_theme": {
"de_DE.UTF-8": "Thema wechseln",
"de_DE": "Thema wechseln",
"C": "Toggle theme"
},
"policy.string.header": {
"de_DE.UTF-8": "Validierung Versicherungsnummer",
"de_DE": "Validierung Versicherungsnummer",
"C": "Policy number validation"
},
"policy.string.label_policy_data": {
"de_DE.UTF-8": "Importierte Daten",
"de_DE": "Importierte Daten",
"C": "imported data"
},
"policy.string.label_policy_list": {
"de_DE.UTF-8": "Versicherungsnummern-Liste",
"de_DE": "Versicherungsnummern-Liste",
"C": "policy list"
},
"policy.string.label_policy_number": {
"de_DE.UTF-8": "Versicherungsnummer",
"de_DE": "Versicherungsnummer",
"C": "policy number"
},
"policy.string.label_result": {
"de_DE.UTF-8": "Prüfungsergebnis",
"de_DE": "Prüfungsergebnis",
"C": "check result"
},
"policy.string.data_count": {
"de_DE.UTF-8": "Anzahl Prüflistenelemente",
"de_DE": "Anzahl Prüflistenelemente",
"C": "Number of checklist elements"
},
"policy.string.import_data": {
"de_DE.UTF-8": "Importieren der Prüflisten-Elemente",
"de_DE": "Importieren der Prüflisten-Elemente",
"C": "Import checklist elements"
},
"policy.string.progress_time": {
"de_DE.UTF-8": "Bearbeitungszeit",
"de_DE": "Bearbeitungszeit",
"C": "processing time"
},
"policy.string.progress_text": {
"de_DE.UTF-8": "Importiere Daten",
"de_DE": "Importiere Daten",
"C": "Importing data"
},
"policy.validation.button_failed": {
"de_DE.UTF-8": "ungültig",
"de_DE": "ungültig",
"C": "invalid"
},
"policy.validation.button_success": {
"de_DE.UTF-8": "gültig",
"de_DE": "gültig",
"C": "valid"
},
"policy.validation.failed": {
"de_DE.UTF-8": "Die Versicherungsscheinnummber ist ungültig",
"de_DE": "Die Versicherungsscheinnummber ist ungültig",
"C": "The given policy number is invalid"
},
"policy.validation.new": {
"de_DE.UTF-8": "Neue Prüfung",
"de_DE": "Neue Prüfung",
"C": "New validation"
},
"policy.validation.success": {
"de_DE.UTF-8": "Die Versicherungsscheinnummber ist gültig",
"de_DE": "Die Versicherungsscheinnummber ist gültig",
"C": "The given policy number is valid"
},
"policy.validation.started": {
"de_DE.UTF-8": "Die Prüfung der Versicherungsscheinnummber wurde gestartet",
"de_DE": "Die Prüfung der Versicherungsscheinnummber wurde gestartet",
"C": "Validation of the policy number started"
},
"policy.validation.finished": {
"de_DE.UTF-8": "Die Prüfung der Versicherungsscheinnummber wurde beendet",
"de_DE": "Die Prüfung der Versicherungsscheinnummber wurde beendet",
"C": "Validation of the policy number finished"
},
"policy.validation.invalid_input": {
"de_DE.UTF-8": "Nur Nummer sind zulässig",
"de_DE": "Nur Nummer sind zulässig",
"C": "Only numbers are valid"
},
"policy.validation.not_found": {
"de_DE.UTF-8": "Nummer ist nicht aktiviert",
"de_DE": "Nummer ist nicht aktiviert",
"C": "number isn't marked active"
},
"policy.validation.to_long": {
"de_DE.UTF-8": "Die Nummer ist zu lang",
"de_DE": "Die Nummer ist zu lang",
"C": "Policy number is to long"
},
"policy.validation.to_short": {
"de_DE.UTF-8": "Die Nummer ist zu kurz",
"de_DE": "Die Nummer ist zu kurz",
"C": "Policy number is to short"
},
"sendticketdata.export.started":{
"de_DE.UTF-8": "Versenden von ticket daten via eMail gestartet",
"de_DE": "Versenden von ticket daten via eMail gestartet",
"C": "Send ticket data via eMail started"
},
"sendticketdata.export.finished":{
"de_DE.UTF-8": "Versenden von ticket daten via eMail beendet",
"de_DE": "Versenden von ticket daten via eMail beendet",
"C": "Send ticket data via eMail finished"
},
"state.started": {
"de_DE.UTF-8": "gestartet",
"de_DE": "gestartet",
"C": "started"
},
"state.finished": {
"de_DE.UTF-8": "beendet",
"de_DE": "beended",
"C": "finished"
}
}

View File

@@ -8,18 +8,15 @@
// suppress creation of a new console window on window // suppress creation of a new console window on window
#![windows_subsystem = "windows"] #![windows_subsystem = "windows"]
//#[macro_use]
//extern crate lazy_static;
//use chrono::{Local, DateTime}; //use chrono::{Local, DateTime};
use dotenv::dotenv; use dotenv::dotenv;
use locales::t;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use serde::Deserialize; use serde::Deserialize;
use std::env; use std::env;
//use std::process; //use std::process;
use substring::Substring; use substring::Substring;
use tracing::{info, trace, Level}; use tracing::{info, trace, Level};
use twine::t;
use orbtk::{ use orbtk::{
prelude::*, prelude::*,
@@ -49,8 +46,8 @@ struct Environment {
static DEFAULT_DARK_EXT: &str = include_str!("../assets/advotracker/default_dark.ron"); static DEFAULT_DARK_EXT: &str = include_str!("../assets/advotracker/default_dark.ron");
cfg_if! { cfg_if! {
if #[cfg(windows)] { if #[cfg(windows)] {
static FLUENT_DARK_EXT: &str = include_str!("../assets/advotracker/fluent_dark.ron"); static FLUENT_DARK_EXT: &str = include_str!("../assets/advotracker/fluent_dark.ron");
static FLUENT_LIGHT_EXT: &str = include_str!("../assets/advotracker/fluent_light.ron"); static FLUENT_LIGHT_EXT: &str = include_str!("../assets/advotracker/fluent_light.ron");
} }
} }
@@ -60,17 +57,17 @@ static ADVOTRACKER_DE_DE: &str = include_str!("../assets/advotracker/advotracker
/// Get the active language environment. /// Get the active language environment.
fn get_lang() -> String { fn get_lang() -> String {
// get system environment // get system environment
let mut lang = env::var("LANG").unwrap_or_else(|_| "C".to_string()); let mut lang = env::var("LANG").unwrap_or_else(|_| "en".to_string());
lang = lang.substring(0,5).to_string(); // "de_DE.UTF-8" -> "de_DE" lang = lang.substring(0,2).to_string(); // "de_DE.UTF-8" -> "de"
info!("GUI-Language: preset to {:?}", lang); info!("GUI-Language: preset to {:?}", lang);
// testing environment: read from .env file // testing environment: read from .env file
dotenv().ok(); dotenv().ok();
match envy::from_env::<Environment>() { match envy::from_env::<Environment>() {
Ok(environment) => { Ok(environment) => {
if environment.test_lang != lang { lang = environment.test_lang; } if environment.test_lang != lang { lang = environment.test_lang; }
}, },
Err(e) => { info!(target: "advotracker", "{}", e) } Err(e) => { info!(target: "advotracker", "{}", e) }
} }
trace!(target: "advotracker", lang = ?lang); trace!(target: "advotracker", lang = ?lang);
@@ -80,38 +77,38 @@ fn get_lang() -> String {
cfg_if! { cfg_if! {
if #[cfg(windows)] { if #[cfg(windows)] {
/// Extend and register theme assets. /// Extend and register theme assets.
fn theme() -> Theme { fn theme() -> Theme {
register_default_fonts(Theme::from_config( register_default_fonts(Theme::from_config(
ThemeConfig::from(DEFAULT_DARK_EXT) ThemeConfig::from(DEFAULT_DARK_EXT)
.extend(ThemeConfig::from(THEME_DEFAULT)) .extend(ThemeConfig::from(THEME_DEFAULT))
.extend(ThemeConfig::from(THEME_DEFAULT_COLORS_DARK)) .extend(ThemeConfig::from(THEME_DEFAULT_COLORS_DARK))
.extend(ThemeConfig::from(THEME_DEFAULT_FONTS)), .extend(ThemeConfig::from(THEME_DEFAULT_FONTS)),
)) ))
} }
fn theme_fluent() -> Theme { fn theme_fluent() -> Theme {
register_fluent_fonts(Theme::from_config( register_fluent_fonts(Theme::from_config(
ThemeConfig::from(FLUENT_DARK_EXT) ThemeConfig::from(FLUENT_DARK_EXT)
.extend(ThemeConfig::from(THEME_FLUENT)) .extend(ThemeConfig::from(THEME_FLUENT))
.extend(ThemeConfig::from(THEME_FLUENT_COLORS_DARK)) .extend(ThemeConfig::from(THEME_FLUENT_COLORS_DARK))
.extend(ThemeConfig::from(THEME_FLUENT_FONTS)), .extend(ThemeConfig::from(THEME_FLUENT_FONTS)),
)) ))
// register_fluent_fonts(Theme::from_config( // register_fluent_fonts(Theme::from_config(
// ThemeConfig::from(FLUENT_LIGHT_EXT) // ThemeConfig::from(FLUENT_LIGHT_EXT)
// .extend(ThemeConfig::from(THEME_FLUENT)) // .extend(ThemeConfig::from(THEME_FLUENT))
// .extend(ThemeConfig::from(THEME_FLUENT_COLORS_DARK)) // .extend(ThemeConfig::from(THEME_FLUENT_COLORS_DARK))
// .extend(ThemeConfig::from(THEME_FLUENT_FONTS)), // .extend(ThemeConfig::from(THEME_FLUENT_FONTS)),
} }
} else { } else {
/// Extend and register theme assets. /// Extend and register theme assets.
fn theme() -> Theme { fn theme() -> Theme {
register_default_fonts(Theme::from_config( register_default_fonts(Theme::from_config(
ThemeConfig::from(DEFAULT_DARK_EXT) ThemeConfig::from(DEFAULT_DARK_EXT)
.extend(ThemeConfig::from(THEME_DEFAULT)) .extend(ThemeConfig::from(THEME_DEFAULT))
.extend(ThemeConfig::from(THEME_DEFAULT_COLORS_DARK)) .extend(ThemeConfig::from(THEME_DEFAULT_COLORS_DARK))
.extend(ThemeConfig::from(THEME_DEFAULT_FONTS)), .extend(ThemeConfig::from(THEME_DEFAULT_FONTS)),
)) ))
} }
} }
} }
@@ -121,11 +118,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
use viperus::Viperus; use viperus::Viperus;
let machine_kind = if cfg!(unix) { let machine_kind = if cfg!(unix) {
"unix" "unix"
} else if cfg!(windows) { } else if cfg!(windows) {
"windows" "windows"
} else { } else {
"unknown/unsupported" "unknown/unsupported"
}; };
// respect dotenv environment (e.g for testing) // respect dotenv environment (e.g for testing)
@@ -138,79 +135,80 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let span = tracing::span!(Level::TRACE, "advotracker"); let span = tracing::span!(Level::TRACE, "advotracker");
let _enter = span.enter(); let _enter = span.enter();
let subscriber = fmt::Subscriber::builder() let subscriber = fmt::Subscriber::builder()
.with_env_filter(&rust_log) .with_env_filter(&rust_log)
//.with_max_level(tracing::Level::DEBUG) //.with_max_level(tracing::Level::DEBUG)
.finish(); .finish();
tracing::subscriber::with_default(subscriber, || { tracing::subscriber::with_default(subscriber, || {
// get system environment // get system environment
let lang = get_lang(); let lang = get_lang();
let mut state = t!("state.started", lang); // include localization strings
let mut res = t!("parse.environment", lang); include!(concat!(env!("OUTDIR"), "/i18n.rs"));
trace!(target: "advotracker", process = ?res, state = ?state);
trace!(target: "advotracker", environment = "system", lang = ?lang);
trace!(target: "advotracker", machine = ?&machine_kind);
// how to handle unimplemented lang resources?? let mut state = t!(state_started => lang);
res = t!("parse.environment", lang); let mut res = t!(parse_environment => lang);
trace!(target: "advotracker", environment = "envy", lang = ?lang); trace!(target: "advotracker", process = ?res, state = ?state);
state = t!("state.finished", lang); trace!(target: "advotracker", environment = "system", lang = ?lang);
trace!(target: "advotracker", process = ?res, state = ?state); trace!(target: "advotracker", machine = ?&machine_kind);
// initialize viperus structure // how to handle unimplemented lang resources??
let mut viperus = Viperus::new(); res = t!(parse_environment => lang);
trace!(target: "advotracker", environment = "envy", lang = ?lang);
state = t!(state_finished => lang);
trace!(target: "advotracker", process = ?res, state = ?state);
// parse commandline arguments // initialize viperus structure
res = t!("parse.arguments", lang); let mut viperus = Viperus::new();
state = t!("state.started", lang);
trace!(target: "advotracker", process = ?res, state = ?state);
let _ = parse_args(&mut viperus); // parse commandline arguments
state = t!("state.finished", lang); res = t!(parse_arguments => lang);
trace!(target: "advotracker", process = ?res, state = ?state); state = t!(state_started => lang);
trace!(target: "advotracker", process = ?res, state = ?state);
// type conversion (viperus String -> u64) let _ = parse_args(&mut viperus);
let test_policy_number = viperus.get::<String>("test_policy_number").unwrap().parse::<u64>().unwrap(); state = t!(state_finished => lang);
trace!(target: "advotracker", test_policy_number = ?test_policy_number); trace!(target: "advotracker", process = ?res, state = ?state);
// main tasks // type conversion (viperus String -> u64)
res = t!("main.started", lang); let test_policy_number = viperus.get::<String>("test_policy_number").unwrap().parse::<u64>().unwrap();
state = t!("state.started", lang); trace!(target: "advotracker", test_policy_number = ?test_policy_number);
trace!(target: "advotracker", process = ?res, state = ?state);
// use this only if you want to run it as web application. // main tasks
orbtk::initialize(); res = t!(main_started => lang);
state = t!(state_started => lang);
trace!(target: "advotracker", process = ?res, state = ?state);
// Preset localization and language from given environment. // use this only if you want to run it as web application.
// if no dictionary is set for the chosen default language orbtk::initialize();
// the content of the text property will be drawn.
//let language = lang.substring(0,5); // "de_DE.UTF-8" -> "de_DE"
//info!("GUI-Language: preset to {:?}", language);
let localization = RonLocalization::create()
.language(&lang)
.dictionary("de_DE", ADVOTRACKER_DE_DE)
.build();
Application::from_name("nwx.advotracker") // Preset localization and language from given environment.
.localization(localization) // if no dictionary is set for the chosen default language
.theme(theme()) // the content of the text property will be drawn.
.window(|ctx| { let localization = RonLocalization::create()
Window::new() .language(&lang)
.title("AdvoTracker - DirectCall") .dictionary("de_DE", ADVOTRACKER_DE_DE)
.position((500.0, 100.0)) .build();
.size(800.0, 620.0)
//.min_width(460.0)
//.min_height(380.0)
.resizeable(true)
.child(main_view::MainView::new().build(ctx))
.build(ctx)
})
.run();
state = t!("state.finished", lang); Application::from_name("nwx.advotracker")
res = t!("main.finished", lang); .localization(localization)
trace!(target: "advotracker", process = ?res, state = ?state); .theme(theme())
.window(|ctx| {
Window::new()
.title("AdvoTracker - DirectCall")
.position((500.0, 100.0))
.size(800.0, 620.0)
//.min_width(460.0)
//.min_height(380.0)
.resizeable(true)
.child(main_view::MainView::new().build(ctx))
.build(ctx)
})
.run();
state = t!(state_finished => lang);
res = t!(main_finished => lang);
trace!(target: "advotracker", process = ?res, state = ?state);
}); });
Ok(()) Ok(())
@@ -220,6 +218,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
mod tests { mod tests {
#[test] #[test]
fn it_works() { fn it_works() {
assert_eq!(2 + 2, 4); assert_eq!(2 + 2, 4);
} }
} }

View File

@@ -18,8 +18,11 @@ pub fn export(p: &mut String, lang: &str) -> Result<(), Box<dyn Error>> {
use std::path::Path; use std::path::Path;
//use std::ffi::OsStr; //use std::ffi::OsStr;
let mut res = t!("csv.export.started", lang); // include localization strings
let mut state = t!("state.started", lang); include!(concat!(env!("OUT_DIR"), "/i18n.rs"));
let mut res = t!(csv_export_started => lang);
let mut state = t!(state_started => lang);
trace!(target: "csv-export", process = ?res, state = ?state); trace!(target: "csv-export", process = ?res, state = ?state);
// Note: slash syntax also works on Windows! // Note: slash syntax also works on Windows!
@@ -29,8 +32,8 @@ pub fn export(p: &mut String, lang: &str) -> Result<(), Box<dyn Error>> {
let file = File::open(path)?; let file = File::open(path)?;
trace!(target: "csv.export", extension = ?path.extension(), file = ?file); trace!(target: "csv.export", extension = ?path.extension(), file = ?file);
state = t!("state.finished", lang); state = t!(state_finished => lang);
res = t!("csv.export.finished", lang); res = t!(csv_export_finished", lang);
trace!(target: "csv-export", process = ?res, state = ?state); trace!(target: "csv-export", process = ?res, state = ?state);
Ok(()) Ok(())

View File

@@ -20,74 +20,77 @@ use crate::data::structures::Email;
/// send ticket data via eMail /// send ticket data via eMail
pub fn sendticketdata(email: &Email, lang: &str) -> Result<(), Box<dyn Error>> { pub fn sendticketdata(email: &Email, lang: &str) -> Result<(), Box<dyn Error>> {
let mut res = t!("sendticketdata.export.started", lang); // include localization strings
let mut state = t!("state.started", lang); include!(concat!(env!("OUT_DIR"), "/i18n.rs"));
let mut res = t!(sendticketdata_export_started => lang);
let mut state = t!(state_started =>lang);
trace!(target: "sendticketdata", process = ?res, state = ?state); trace!(target: "sendticketdata", process = ?res, state = ?state);
// The html we want to send. // The html we want to send.
// It uses https://crates.io/crates/maud // It uses https://crates.io/crates/maud
let html = html! { let html = html! {
head { head {
title { (email.subject) " (" (email.policy_code) ")" } title { (email.subject) " (" (email.policy_code) ")" }
style type="text/css" { style type="text/css" {
"h2, h4 { font-family: Arial, Helvetica, sans-serif; }" "h2, h4 { font-family: Arial, Helvetica, sans-serif; }"
} }
} }
div style="display: flex; flex-direction: column; align-items: left;" { div style="display: flex; flex-direction: column; align-items: left;" {
// compose with variables and strings // compose with variables and strings
h2 { (email.subject) " (" (email.policy_code) ")" } h2 { (email.subject) " (" (email.policy_code) ")" }
p { "Vers.-Schein/Schadennummer: " (email.policy_code) } p { "Vers.-Schein/Schadennummer: " (email.policy_code) }
p { "Versicherungsnehmer: " (email.policy_holder) } p { "Versicherungsnehmer: " (email.policy_holder) }
p { "Selbstbehalt: " (email.deductible) } p { "Selbstbehalt: " (email.deductible) }
p { "Rückrufnummer: " (email.callback_number) } p { "Rückrufnummer: " (email.callback_number) }
p { "Erreichbarkeit: " (email.callback_date) } p { "Erreichbarkeit: " (email.callback_date) }
p { "Rechtsproblem: " (email.harm_type) } p { "Rechtsproblem: " (email.harm_type) }
p { "Rechtsrat: " (email.ivr_comment) } p { "Rechtsrat: " (email.ivr_comment) }
} }
}; };
let ascii_body = String::new() let ascii_body = String::new()
+ &"Vers.-Schein/Schadennummer".to_string() + &(email.policy_code) + &"\n" + &"Vers.-Schein/Schadennummer".to_string() + &(email.policy_code) + &"\n"
+ &"Versicherungsnehmer: ".to_string() + &(email.policy_holder) + &"\n" + &"Versicherungsnehmer: ".to_string() + &(email.policy_holder) + &"\n"
+ &"Selbstbehalt: ".to_string() + &(email.deductible) + &"\n" + &"Selbstbehalt: ".to_string() + &(email.deductible) + &"\n"
+ &"Rückrufnummer: ".to_string()+ &(email.callback_number) + &"\n" + &"Rückrufnummer: ".to_string()+ &(email.callback_number) + &"\n"
+ &"Erreichbarkeit: ".to_string() + &(email.callback_date) + &"\n" + &"Erreichbarkeit: ".to_string() + &(email.callback_date) + &"\n"
+ &"Rechtsproblem: ".to_string() + &(email.harm_type) + &"\n" + &"Rechtsproblem: ".to_string() + &(email.harm_type) + &"\n"
+ &"Rechtsrat: ".to_string() + &(email.ivr_comment) + &"\n"; + &"Rechtsrat: ".to_string() + &(email.ivr_comment) + &"\n";
info!("email body: {:?}", ascii_body); info!("email body: {:?}", ascii_body);
let message = Message::builder() let message = Message::builder()
.from((email.mail_from).parse().unwrap()) .from((email.mail_from).parse().unwrap())
.reply_to((email.mail_reply).parse().unwrap()) .reply_to((email.mail_reply).parse().unwrap())
.to((email.mail_to).parse().unwrap()) .to((email.mail_to).parse().unwrap())
.cc((email.mail_cc).parse().unwrap()) .cc((email.mail_cc).parse().unwrap())
// we do not use bcc yet // we do not use bcc yet
//.bcc((email.mail_bcc).parse().unwrap()) //.bcc((email.mail_bcc).parse().unwrap())
.subject(String::new() .subject(String::new()
+ &email.subject.to_string() + &email.subject.to_string()
+ &" (".to_string() + &" (".to_string()
+ &email.policy_code.to_string() + &email.policy_code.to_string()
+ &")".to_string() + &")".to_string()
) )
.multipart( .multipart(
MultiPart::alternative() // This is composed of two parts. MultiPart::alternative() // This is composed of two parts.
.singlepart( .singlepart(
SinglePart::builder() SinglePart::builder()
.header(header::ContentType( .header(header::ContentType(
"text/plain; charset=utf8".parse().unwrap(), "text/plain; charset=utf8".parse().unwrap(),
)) ))
.body(String::from(ascii_body)), .body(String::from(ascii_body)),
) )
.singlepart( .singlepart(
SinglePart::builder() SinglePart::builder()
.header(header::ContentType( .header(header::ContentType(
"text/html; charset=utf8".parse().unwrap(), "text/html; charset=utf8".parse().unwrap(),
)) ))
.body(html.into_string()), .body(html.into_string()),
), ),
) )
.expect("failed to build email"); .expect("failed to build email");
info!("message: {:?}", message); info!("message: {:?}", message);
@@ -105,17 +108,17 @@ pub fn sendticketdata(email: &Email, lang: &str) -> Result<(), Box<dyn Error>> {
let relay = "smtp.strato.de"; let relay = "smtp.strato.de";
let mailer = SmtpTransport::relay(relay) let mailer = SmtpTransport::relay(relay)
.unwrap() .unwrap()
.credentials(credentials) .credentials(credentials)
.build(); .build();
trace!(target: "sendticketdata", email = ?email); trace!(target: "sendticketdata", email = ?email);
// Send the email and catch the error if it isn't successfully // Send the email and catch the error if it isn't successfully
let _result = mailer.send(&message)?; let _result = mailer.send(&message)?;
state = t!("state.finished", lang); state = t!(state_finished => lang);
res = t!("sendticketdata.export.finished", lang); res = t!(sendticketdata_export_finished => lang);
trace!(target: "sendticketdata", process = ?res, state = ?state); trace!(target: "sendticketdata", process = ?res, state = ?state);
Ok(()) Ok(())

View File

@@ -21,14 +21,17 @@ use crate::data::structures::{PolicyCode, PolicyDataList, PolicyData};
/// https://docs.rs/csv/1.1.3/csv/cookbook/index.html /// https://docs.rs/csv/1.1.3/csv/cookbook/index.html
/// https://blog.burntsushi.net/csv/ /// https://blog.burntsushi.net/csv/
pub fn import(p: &mut String, data_list: &mut PolicyDataList, pub fn import(p: &mut String, data_list: &mut PolicyDataList,
policy_numbers: &mut HashMap<u64, PolicyCode>, policy_numbers: &mut HashMap<u64, PolicyCode>,
policy_data_count: &mut u64, policy_data_count: &mut u64,
lang: &str) lang: &str)
-> Result<(u64, Duration), Box<dyn Error>> { -> Result<(u64, Duration), Box<dyn Error>> {
use std::fs::File; use std::fs::File;
use std::path::Path; use std::path::Path;
use std::ffi::OsStr; use std::ffi::OsStr;
// include localization strings
include!(concat!(env!("OUT_DIR"), "/i18n.rs"));
let mut res = t!("csv.import.started", lang); let mut res = t!("csv.import.started", lang);
let mut state = t!("state.started", lang); let mut state = t!("state.started", lang);
let time_start = SystemTime::now(); let time_start = SystemTime::now();
@@ -53,41 +56,41 @@ pub fn import(p: &mut String, data_list: &mut PolicyDataList,
// Build the CSV reader // Build the CSV reader
let mut csv_reader = csv::ReaderBuilder::new() let mut csv_reader = csv::ReaderBuilder::new()
.has_headers(true) .has_headers(true)
.delimiter(b' ') .delimiter(b' ')
//.comment(Some(b'#')) //.comment(Some(b'#'))
//.flexible(true) //.flexible(true)
.from_reader(file); .from_reader(file);
{ {
// We nest this call in its own scope because of lifetimes. // We nest this call in its own scope because of lifetimes.
let headers = csv_reader.headers()?; let headers = csv_reader.headers()?;
trace!(target: "csv-import", header = ?headers); trace!(target: "csv-import", header = ?headers);
} }
// Iterate over each record, deserialize und write to our structures // Iterate over each record, deserialize und write to our structures
let mut count : u64 = 0; let mut count : u64 = 0;
for result in csv_reader.deserialize() { for result in csv_reader.deserialize() {
// The iterator yields Result<StringRecord, Error>, so we check the // The iterator yields Result<StringRecord, Error>, so we check the
// error here. // error here.
let record: PolicyData = result?; let record: PolicyData = result?;
//if verbose > 3 { //if verbose > 3 {
// println!("{:?}", record); // println!("{:?}", record);
//} //}
// WIP: write to redis backend // WIP: write to redis backend
// for now: append the policy_number to the HashMap // for now: append the policy_number to the HashMap
policy_numbers.insert(record.policy_number, record.policy_code); policy_numbers.insert(record.policy_number, record.policy_code);
// push record as new vector elements // push record as new vector elements
data_list.push(record); data_list.push(record);
count +=1; count +=1;
*policy_data_count = count; *policy_data_count = count;
}; };
let time_end = SystemTime::now(); let time_end = SystemTime::now();
let duration = time_end.duration_since(time_start) let duration = time_end.duration_since(time_start)
.expect("Clock may have gone backwards"); .expect("Clock may have gone backwards");
trace!(target: "csv-import", record_count = ?count, duration = ?duration); trace!(target: "csv-import", record_count = ?count, duration = ?duration);
state = t!("state.finished", lang); state = t!("state.finished", lang);
@@ -111,23 +114,23 @@ fn test_import() {
let lang = "en".to_string(); let lang = "en".to_string();
match import(&mut csv_import_path, &mut policy_data, match import(&mut csv_import_path, &mut policy_data,
&mut policy_numbers, &mut policy_data_count, &mut policy_numbers, &mut policy_data_count,
&lang) { &lang) {
Ok((count, duration)) => { Ok((count, duration)) => {
println!("import {:?} records. Duration: {:?}", count, duration); println!("import {:?} records. Duration: {:?}", count, duration);
} }
Err(err) => { Err(err) => {
println!("error running CSV-Import: {}", err); println!("error running CSV-Import: {}", err);
} }
}; };
for p in &my_policy_numbers { for p in &my_policy_numbers {
match policy_numbers.get(&p) { match policy_numbers.get(&p) {
Some(policy_code) => { Some(policy_code) => {
println!("Test: Policy-Number {:?} => Policy-Type {:?}, as expected!", println!("Test: Policy-Number {:?} => Policy-Type {:?}, as expected!",
p, policy_code); p, policy_code);
}, },
_ => println!("Test: Policy-Number {:?} => not valid, can't dereference the Policy-Type as expected", p), _ => println!("Test: Policy-Number {:?} => not valid, can't dereference the Policy-Type as expected", p),
} }
} }
} }