diff --git a/Cargo.toml b/Cargo.toml index 3f78d79..505363e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,8 @@ regex = "1" [dev-dependencies] criterion = "0.3" tempfile = "3" + +[workspace] +members = [ + "twine-demo", +] diff --git a/README.md b/README.md index 3972141..075d367 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,12 @@ fn main() { } ``` -3. You need an INI file with your translations. Example with `translations.ini`: +3. You need an INI file with your translations. + Language translations are matched by `two lowercase letter` code (eg: `en`). + Localized language translations are identified by `two lowercase letter` code, + plus `hyphen`, plus `to letter localization` code (eg: `en-gb`). + + The next paragraph is an example `translations.ini` file: ``` [app_ruin_the_band] diff --git a/src/lib.rs b/src/lib.rs index ecec512..dfff78b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,12 @@ //! } //! ``` //! -//! 3. You need an INI file with your translations. Example with `translations.ini`: +//! 3. You need an INI file with your translations. +//! Language translations are matched by `two lowercase letter` code (eg: `en`). +//! Localized language translations are identified by `two lowercase letter` code, +//! plus `hyphen`, plus `to letter localization` code (eg: `en-gb`). +//! +//! The next paragraph is an example `translations.ini` file: //! //! ```text //! [app_ruin_the_band] @@ -54,7 +59,9 @@ //! 4. Now in your project you can use the macro `t!` to translate anything: //! //! ```ignore +//! # /// define valid language varients //! # enum Lang { Fr(&'static str) } +//! # /// desugar the procedural macro call //! # macro_rules! t { //! # ($($tokens:tt)+) => {{ //! # }}; @@ -219,6 +226,16 @@ impl fmt::Display for TwineFormatter { write!( f, r#" + + "#, + )?; + + write!( + f, + r#" + // i18n.rs + + /// Create translation strings for supported language varients. #[macro_export] macro_rules! t {{ "#, @@ -264,7 +281,9 @@ impl fmt::Display for TwineFormatter { write!( f, r#" - #[derive(Debug, Clone, Copy, PartialEq, Hash)] + + /// Valid language variants. + #[derive(Clone, Copy, Hash, Debug, PartialEq)] #[allow(dead_code)] pub enum Lang {{ "#, @@ -282,9 +301,10 @@ impl fmt::Display for TwineFormatter { write!( f, r#" + /// variant {} {}(&'static str), "#, - lang, + lang, lang )?; } @@ -295,6 +315,7 @@ impl fmt::Display for TwineFormatter { }} impl Lang {{ + /// Array with known language identifier. pub fn all_languages() -> &'static [&'static Lang] {{ &[ "#, @@ -325,6 +346,38 @@ impl fmt::Display for TwineFormatter { "#, )?; + // implent default for `Lang` + // the fist in the sorted list should be fine + write!( + f, + r#" + + impl Default for Lang {{ + "#, + )?; + f.indent(1); + + let mut sorted_languages: Vec<_> = all_languages.iter().collect(); + sorted_languages.sort_unstable(); + + let (default_lang, default_region) = sorted_languages[0]; + write!( + f, + r#" + fn default() -> Self {{ Lang::{}({:?}) }} + "#, + default_lang, + default_region.as_deref().unwrap_or(""), + )?; + f.dedent(1); + + write!( + f, + r#" + }} + "#, + )?; + #[cfg(feature = "serde")] { let mut all_regions: Vec<_> = all_languages @@ -450,7 +503,7 @@ impl TwineFormatter { impl<'de> de::Visitor<'de> for LangVisitor {{ type Value = Lang; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {{ + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {{ formatter.write_str("expected string") }} diff --git a/tests/test-crate/Cargo.toml b/tests/test-crate/Cargo.toml index 8bb85b4..1ff9938 100644 --- a/tests/test-crate/Cargo.toml +++ b/tests/test-crate/Cargo.toml @@ -12,3 +12,5 @@ twine = { path = "../..", features = ["serde"] } [dependencies] serde = { version = "1" } serde_json = "1" + +[workspace] diff --git a/twine-demo/Cargo.toml b/twine-demo/Cargo.toml new file mode 100644 index 0000000..14eb374 --- /dev/null +++ b/twine-demo/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "twine-demo" +version = "0.1.0" +authors = ["Ralf Zerres "] +edition = "2018" +build = "build.rs" + +[dependencies] +serde = { version = "^1.0", features = ["derive"] } +serde_json = { version = "^1.0" } +substring = "1.4.5" +tracing = "0.1.25" +tracing-subscriber = "0.2.17" +twine = { version = "^0.3", features = ["serde"] } + +[build-dependencies] +twine = { version = "^0.3", features = ["serde"] } + +[[bin]] +name = "twine-demo" +path = "src/main.rs" diff --git a/twine-demo/build.rs b/twine-demo/build.rs new file mode 100644 index 0000000..791f3c5 --- /dev/null +++ b/twine-demo/build.rs @@ -0,0 +1,6 @@ +use twine::build_translations; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + build_translations(&["./src/i18n/localization.ini"], "i18n.rs").unwrap(); +} diff --git a/twine-demo/data/demo.json b/twine-demo/data/demo.json new file mode 100644 index 0000000..28c5daa --- /dev/null +++ b/twine-demo/data/demo.json @@ -0,0 +1,59 @@ +{ + "_comment1": "The colour structure is an array of colour elements", + "colour": [ + { + "colour_name": "black", + "category": "hue", + "colour_type": "Primary", + "code": { + "rgba": [255,255,255,1], + "hex": "000" + } + }, + { + "colour_name": "white", + "category": "value", + "colour_type": "Primary", + "code": { + "rgba": [0,0,0,1], + "hex": "FFF" + } + }, + { + "colour_name": "red", + "category": "hue", + "colour_type": "Primary", + "code": { + "rgba": [255,0,0,1], + "hex": "FF0" + } + }, + { + "colour_name": "blue", + "category": "hue", + "colour_type": "Primary", + "code": { + "rgba": [0,0,255,1], + "hex": "00F" + } + }, + { + "colour_name": "yellow", + "category": "hue", + "colour_type": "Primary", + "code": { + "rgba": [255,255,0,1], + "hex": "FF0" + } + }, + { + "colour_name": "green", + "category": "hue", + "colour_type": "Secondary", + "code": { + "rgba": [0,255,0,1], + "hex": "0F0" + } + } + ] +} diff --git a/twine-demo/src/i18n/localization.ini b/twine-demo/src/i18n/localization.ini new file mode 100644 index 0000000..f941ba4 --- /dev/null +++ b/twine-demo/src/i18n/localization.ini @@ -0,0 +1,39 @@ +[clock_drift] + de = Uhrzeit ist eventuell rückwärts gestellt worden + en = Clock may have gone backwards +[err_lang_not_found] + de = Konnte Sprachkode nicht auslesen + en = Can not read the language code +[err_import_colours] + de = Import der Farbwert fehlgeschlagen + en = Error importing coulour values +[err_colour_not_found] + de = Farbwert wurde nicht gefunden + en = coulour value not found +[lang_code] + de = Sprachkode + en = language code +[main_started] + de = Programmlogik starten + en = Program logic started +[main_finished] + de = Programmlogik beendet + en = Program logic finished +[import_colours] + de = Import der Farbwerte + en = import coulour values +[import_not_well_formed] + de = JSON hat nicht die erwartete Struktur + en = JSON was not well-formatted +[import_started] + de = Import aus einer Datei + en = Import from a file +[import_finished] + de = Import aus einer Datei beendet + en = Import from a file finished +[state_started] + de = gestartet + en = started +[state_finished] + de = beended + en = finished diff --git a/twine-demo/src/main.rs b/twine-demo/src/main.rs new file mode 100644 index 0000000..9d41476 --- /dev/null +++ b/twine-demo/src/main.rs @@ -0,0 +1,59 @@ +#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)] + +use std::env; +use std::time::SystemTime; +use tracing::{trace, Level}; +use tracing_subscriber::fmt; + +// get the macro (t!) accessing the internationalization strings +include!(concat!(env!("OUT_DIR"), "/i18n.rs")); + +pub mod services; + +use crate::services::imports::*; + +fn main() -> Result<(), Box> { + // initialize the tracing subsystem + let span = tracing::span!(Level::TRACE, "wip_twine"); + let _enter = span.enter(); + let collector = fmt::Subscriber::builder() + .with_max_level(tracing::Level::TRACE) + .finish(); + + // start tracing thread + tracing::subscriber::with_default(collector, || { + // include localization strings + let lang = Lang::De("de"); + let mut state = t!(state_started => lang); + let mut res = t!(lang_code => lang); + + // localized feedback + let time_start = SystemTime::now(); + trace!(target: "twine-demo", state = ?state, + res = ? res, time = ?time_start); + + match json_import::read_colours("data/demo.json") { + Ok(colours) => { + println!("We got {} colour elements", colours.colour.iter().count()); + for (pos, e) in colours.colour.iter().enumerate() { + println!("Element {}: colour name={:?}, rgba-value={:?}", pos, e.colour_name, e.code.rgba); + } + }, + Err(e) => { + println!("Error: {}!", e); + res = t!(err_import_colours => lang); + println!("Error: {}!", res); + }, + } + + state = t!(state_finished => lang); + res = t!(import_colours => lang); + + // localized feedback + let time_end = SystemTime::now(); + let duration = time_end.duration_since(time_start); + trace!(target: "twine-demo", process = ?res, state = ?state, duration = ?duration); + }); + + Ok(()) +} diff --git a/twine-demo/src/services/imports/json_import.rs b/twine-demo/src/services/imports/json_import.rs new file mode 100644 index 0000000..3bf0530 --- /dev/null +++ b/twine-demo/src/services/imports/json_import.rs @@ -0,0 +1,59 @@ +use serde::Deserialize; +use serde_json::Result; +use std::{ + fs::File, + io::BufReader, + path::Path, +}; + +// Valid color types +#[derive(Debug, Deserialize, PartialEq)] +pub enum Type { + /// Primery colour + Primary, + /// Secondary colour + Secondary, +} + +impl Default for Type { + fn default() -> Self { Type::Primary } +} + +// Color codes structure +#[derive(Debug, Deserialize, PartialEq)] +pub struct Code { + /// Color code as an rgba array + pub rgba: Vec, + /// Color code as a hex value + pub hex: String, +} + +// The colour structure +#[derive(Debug, Deserialize, PartialEq)] +pub struct Colour { + pub colour_name: String, + pub category: String, + pub colour_type: Type, + pub code: Code, +} + +// The colours structure +#[derive(Debug, Deserialize, PartialEq)] +pub struct Colours { + pub colour: Vec, +} + +pub fn read_colours

(path: P) -> Result +where + P: AsRef, +{ + // Open the file in read-only mode with buffer + let file = File::open(path).unwrap(); + let reader = BufReader::new(file); + + // Read the JSON contents of the file as an instance of `Colours`. + let colours: Colours = serde_json::from_reader(reader)?; + + // Return the `Colours` structure + Ok(colours) +} diff --git a/twine-demo/src/services/imports/mod.rs b/twine-demo/src/services/imports/mod.rs new file mode 100644 index 0000000..8cd504e --- /dev/null +++ b/twine-demo/src/services/imports/mod.rs @@ -0,0 +1,2 @@ +/// json import service +pub mod json_import; diff --git a/twine-demo/src/services/mod.rs b/twine-demo/src/services/mod.rs new file mode 100644 index 0000000..5ac414a --- /dev/null +++ b/twine-demo/src/services/mod.rs @@ -0,0 +1,2 @@ +/// import modules +pub mod imports;