diff --git a/.gitignore b/.gitignore index 2cdbd84c9..9af46a299 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ result **/generated-* **/.idea +__locales_compiled diff --git a/Cargo.lock b/Cargo.lock index 083dbabc9..e5338fa36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,6 +199,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -252,7 +261,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -431,19 +440,31 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.43" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", ] +[[package]] +name = "clap-i18n-richformatter" +version = "0.1.4" +source = "git+https://github.com/Sk7Str1p3/clap-i18n-richformatter-0.1.4#48171098e9ef30261729925e91a8d874ee3b6062" +dependencies = [ + "clap", + "i18n-embed", + "i18n-embed-fl", + "rust-embed", + "unic-langid", +] + [[package]] name = "clap_builder" -version = "4.5.43" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -463,14 +484,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -504,7 +525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -673,7 +694,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -700,7 +721,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -845,6 +866,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml 0.5.11", +] + [[package]] name = "flate2" version = "1.1.2" @@ -856,6 +886,51 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fluent" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" +dependencies = [ + "memchr", + "thiserror 2.0.12", +] + [[package]] name = "fnv" version = "1.0.7" @@ -903,7 +978,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2197,6 +2272,73 @@ dependencies = [ "libm", ] +[[package]] +name = "i18n-config" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e06b90c8a0d252e203c94344b21e35a30f3a3a85dc7db5af8f8df9f3e0c63ef" +dependencies = [ + "basic-toml", + "log", + "serde", + "serde_derive", + "thiserror 1.0.69", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a217bbb075dcaefb292efa78897fc0678245ca67f265d12c351e42268fcb0305" +dependencies = [ + "arc-swap", + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "log", + "parking_lot", + "rust-embed", + "sys-locale", + "thiserror 1.0.69", + "unic-langid", + "walkdir", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e598ed73b67db92f61e04672e599eef2991a262a40e1666735b8a86d2e7e9f30" +dependencies = [ + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "proc-macro-error2", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.110", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -2403,6 +2545,25 @@ dependencies = [ "similar", ] +[[package]] +name = "intl-memoizer" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + [[package]] name = "inventory" version = "0.3.20" @@ -2481,7 +2642,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2613,7 +2774,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2706,6 +2867,7 @@ dependencies = [ "askalono", "byte-unit", "clap", + "clap-i18n-richformatter", "clap_complete", "criterion", "crossbeam-channel", @@ -2715,6 +2877,8 @@ dependencies = [ "gix-testtools", "globset", "human-panic", + "i18n-embed", + "i18n-embed-fl", "image", "insta", "lazy_static", @@ -2723,12 +2887,16 @@ dependencies = [ "onefetch-image", "onefetch-manifest", "owo-colors", + "proc-macro2", + "quote", "regex", "rstest", + "rust-embed", "serde", "serde_json", "serde_yaml", "strum", + "syn 2.0.110", "tera", "time", "time-humanize", @@ -2865,7 +3033,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3036,11 +3204,33 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -3101,9 +3291,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -3299,10 +3489,44 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.104", + "syn 2.0.110", "unicode-ident", ] +[[package]] +name = "rust-embed" +version = "8.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.110", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rust_decimal" version = "1.37.2" @@ -3325,6 +3549,12 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -3380,6 +3610,12 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "self_cell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" + [[package]] name = "semver" version = "1.0.26" @@ -3403,7 +3639,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3595,7 +3831,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3611,9 +3847,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -3628,7 +3864,16 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", ] [[package]] @@ -3739,7 +3984,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3750,7 +3995,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3949,6 +4194,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash", +] + [[package]] name = "typeid" version = "1.0.3" @@ -3982,7 +4236,7 @@ checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4021,6 +4275,25 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" +[[package]] +name = "unic-langid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658" +dependencies = [ + "serde", + "tinystr", +] + [[package]] name = "unic-segment" version = "0.9.0" @@ -4170,7 +4443,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -4192,7 +4465,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4238,7 +4511,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -4268,7 +4541,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4279,7 +4552,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4647,7 +4920,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "synstructure", ] @@ -4668,7 +4941,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4688,7 +4961,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "synstructure", ] @@ -4722,7 +4995,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6285e30ad..5ff33fd8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,13 @@ time = { version = "0.3.41", features = ["formatting"] } time-humanize = { version = "0.1.3", features = ["time"] } tokei = "13.0.0-alpha.9" typetag = "0.2" +i18n-embed = { version = "0.16.0", features = [ + "desktop-requester", + "fluent-system", +] } +i18n-embed-fl = "0.10.0" +rust-embed = "8.9.0" +clap-i18n-richformatter = { git = "https://github.com/Sk7Str1p3/clap-i18n-richformatter-0.1.4" } [dev-dependencies] criterion = "0.7.0" @@ -81,9 +88,12 @@ harness = false [build-dependencies] lazy_static = "1" +proc-macro2 = "1.0.103" +quote = "1.0.42" regex = "1" serde_json = "1" serde_yaml = "0.9" +syn = "2.0.110" tera = { version = "1", default-features = false } [target.'cfg(windows)'.build-dependencies] diff --git a/build.rs b/build.rs index a52de4df6..9f7846379 100644 --- a/build.rs +++ b/build.rs @@ -7,6 +7,9 @@ use std::path::Path; use std::sync::LazyLock; use tera::{Context, Tera}; +#[path = "locales/build.rs"] +mod locales; + fn main() -> Result<(), Box> { #[cfg(windows)] { @@ -29,6 +32,9 @@ fn main() -> Result<(), Box> { )?; fs::write(output_path, rust_code)?; + locales::concat_locales()?; + locales::generate_consts(&out_dir)?; + Ok(()) } diff --git a/i18n.toml b/i18n.toml new file mode 100644 index 000000000..69c861527 --- /dev/null +++ b/i18n.toml @@ -0,0 +1,4 @@ +fallback_language = "en-US" + +[fluent] +assets_dir = "__locales_compiled" \ No newline at end of file diff --git a/locales/build.rs b/locales/build.rs new file mode 100644 index 000000000..42ec1e3ac --- /dev/null +++ b/locales/build.rs @@ -0,0 +1,123 @@ +use std::{ + collections::BTreeMap, + fs::{create_dir_all, read_to_string, File}, + io::{BufWriter, Write as _}, + path::PathBuf, +}; + +use quote::{format_ident, quote}; + +/// Concatenate all FTL files in the locales directory into a single file for each locale. +pub fn concat_locales() -> Result<(), Box> { + println!("cargo:rerun-if-changed=locales/"); + let locales_dir = PathBuf::from("locales").read_dir()?; + let out_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("__locales_compiled"); + + for dir in locales_dir { + let dir = dir?.path(); + if !dir.is_dir() { + continue; + } + let lang = dir.components().last().unwrap(); + + let out_dir = out_dir.join(lang); + create_dir_all(&out_dir).unwrap(); + + let mut out_file = BufWriter::new(File::create(out_dir.join("onefetch.ftl"))?); + + for ftl in dir.read_dir()? { + let ftl = ftl?.path(); + let contents = read_to_string(&ftl)?; + writeln!(out_file, "{contents}")?; + } + } + Ok(()) +} + +/// Generate Rust constants for all localization keys. +// WARNING: AI slop +pub fn generate_consts(out: &String) -> Result<(), Box> { + let ftl_contents = read_to_string( + PathBuf::from("__locales_compiled") + .join("en-US") + .join("onefetch.ftl"), + )?; + let out_path = PathBuf::from(out).join("locales_consts.rs"); + let mut out_file = BufWriter::new(File::create(&out_path)?); + + let mut keys = Vec::new(); + for line in ftl_contents.lines() { + if !line.trim().contains('=') { + continue; + } + let (key, ..) = line.split_once('=').unwrap(); + keys.push(key.trim().to_string()); + } + + // Используем struct вместо type alias + #[derive(Default)] + struct Module { + constants: Vec<(String, String)>, + submodules: BTreeMap, + } + + let mut root = Module::default(); + + for key in &keys { + let parts: Vec<&str> = key.split('-').collect(); + let const_name = parts.last().unwrap().to_uppercase(); + + let mut current = &mut root; + + // Проходим по всем частям пути (кроме последней) + for part in parts.iter().take(parts.len() - 1) { + current = current + .submodules + .entry(part.to_string()) + .or_insert_with(Module::default); + } + + // Добавляем константу + current.constants.push((const_name, key.clone())); + } + + // Рекурсивная генерация кода + fn generate_tree(module: &Module) -> proc_macro2::TokenStream { + let mut items = Vec::new(); + + // Добавляем константы текущего модуля + for (const_name, const_value) in &module.constants { + let const_ident = format_ident!("{}", const_name); + items.push(quote! { + pub const #const_ident: &str = #const_value; + }); + } + + // Добавляем подмодули + for (mod_name, submodule) in &module.submodules { + let mod_ident = format_ident!("{}", mod_name); + let submodule_code = generate_tree(submodule); + + items.push(quote! { + pub mod #mod_ident { + #submodule_code + } + }); + } + + quote! { #(#items)* } + } + + let module_code = generate_tree(&root); + + let output = quote! { + pub mod locale_keys { + #module_code + } + }; + + writeln!(out_file, "#[rustfmt::skip]")?; + write!(out_file, "{}", output)?; + + Ok(()) +} diff --git a/locales/en-US/cli.ftl b/locales/en-US/cli.ftl new file mode 100644 index 000000000..8ce120d6b --- /dev/null +++ b/locales/en-US/cli.ftl @@ -0,0 +1,103 @@ + +cli-about = Command-line Git information tool +cli-usage-header = Usage +cli-arguments-header = Arguments +cli-arguments-input = Run as if onefetch was started in instead of the current working directory +cli-options-header = Options +cli-options-help = Print help (see more with '--help', see a summary with '-h') +cli-options-version = Print version + +# Value name +cli-value-input = INPUT +cli-value-num = NUM +cli-value-field = FIELD +cli-value-regex = REGEX +cli-value-exclude = EXCLUDE +cli-value-type = TYPE +cli-value-separator = SEPARATOR +cli-value-string = STRING +cli-value-language = LANGUAGE +cli-value-when = WHEN +cli-value-image = IMAGE +cli-value-protocol = PROTOCOL +cli-value-value = VALUE +cli-value-format = FORMAT +cli-value-shell = SHELL + +# INFO +cli-info-heading = INFO +cli-info-disabled_fields = Allows you to disable FIELD(s) from appearing in the output +cli-info-no_title = Hides the title +cli-info-number_of_authors = Maximum NUM of authors to be shown +cli-info-number_of_languages = Maximum NUM of languages to be shown +cli-info-number_of_file_churns = Maximum NUM of file churns to be shown +cli-info-churn_pool_size = + Minimum NUM of commits from HEAD used to compute the churn summary + + By default, the actual value is non-deterministic due to time-based computation + and will be displayed under the info title "Churn (NUM)" +cli-info-exclude = Ignore all files & directories matching EXCLUDE +cli-info-no_bots = Exclude [bot] commits. Use to override the default pattern +cli-info-no_merges = Ignores merge commits +cli-info-email = Show the email address of each author +cli-info-http_url = Display repository URL as HTTP +cli-info-hide_token = Hide token in repository URL +cli-info-include_hidden = Count hidden files and directories +cli-info-type = Filters output by language type + +# TEXT FORMATTING +cli-text-heading = TEXT FORMATTING +cli-text-colors = + Changes the text colors (X X X...) + + Goes in order of title, ~, underline, subtitle, colon, and info + + For example: + + '--text-colors 9 10 11 12 13 14' +cli-text-iso_time = Use ISO 8601 formatted timestamps +cli-text-number_separator = Which thousands SEPARATOR to use +cli-text-no_bold = Turns off bold formatting + +# ASCII +cli-ascii-heading = ASCII +cli-ascii-ascii_input = + Takes a non-empty STRING as input to replace the ASCII logo + + It is possible to pass a generated STRING by command substitution + + For example: + + '--ascii-input "$(fortune | cowsay -W 25)"' +cli-ascii-ascii_colors = Colors (X X X...) to print the ascii art +cli-ascii-ascii_language = Which LANGUAGE's ascii art to print +cli-ascii-true_color = + Specify when to use true color + + If set to auto: true color will be enabled if supported by the terminal + +# IMAGE +cli-image-heading = IMAGE +cli-image-image = Path to the IMAGE file +cli-image-image_protocol = Which image PROTOCOL to use +cli-image-color_resolution = VALUE of color resolution to use with SIXEL backend + +# VISUALS +cli-visuals-heading = VISUALS +cli-visuals-no_color_palette = Hides the color palette +cli-visuals-no_art = Hides the ascii art or image if provided +cli-visuals-nerd_fonts = + Use Nerd Font icons + + Replaces language chips with Nerd Font icons + + +# DEVELOPER +cli-dev-heading = DEVELOPER +cli-dev-output = Outputs Onefetch in a specific format +cli-dev-completion = If provided, outputs the completion file for given SHELL + +# OTHER +cli-other-heading = OTHER +cli-other-languages = Prints out supported languages +cli-other-package_managers = Prints out supported package managers diff --git a/locales/ru-RU/cli.ftl b/locales/ru-RU/cli.ftl new file mode 100644 index 000000000..299f9dcde --- /dev/null +++ b/locales/ru-RU/cli.ftl @@ -0,0 +1,103 @@ + +cli-about = Инструмент командной строки для получения информации о Git +cli-usage-header = Использование +cli-arguments-header = Аргументы +cli-arguments-input = Запустить так, как будто onefetch был запущен в <ввод> вместо текущего рабочего каталога +cli-options-header = Опции +cli-options-help = Показать помощь (подробнее с '--help', кратко с '-h') +cli-options-version = Показать версию + +# Value name +cli-value-input = ВВОД +cli-value-num = КОЛ-ВО +cli-value-field = ПОЛЕ +cli-value-regex = РЕГ. ВЫРАЖЕНИЕ +cli-value-exclude = ИСКЛЮЧЕННОЕ +cli-value-type = ТИП +cli-value-separator = РАЗДЕЛИТЕЛЬ +cli-value-string = СТРОКА +cli-value-language = ЯЗЫК +cli-value-when = КОГДА +cli-value-image = ИЗОБРАЖЕНИЕ +cli-value-protocol = ПРОТОКОЛ +cli-value-value = ЗНАЧЕНИЕ +cli-value-format = ФОРМАТ +cli-value-shell = ОБОЛОЧКА + +# INFO +cli-info-heading = ИНФОРМАЦИЯ +cli-info-disabled_fields = Позволяет отключить отображение ПОЛЯ(ПОЛЕЙ) в выводе +cli-info-no_title = Скрывает заголовок +cli-info-number_of_authors = Максимальное КОЛ-ВО авторов для отображения +cli-info-number_of_languages = Максимальное КОЛ-ВО языков для отображения +cli-info-number_of_file-churns = Максимальное КОЛ-ВО изменений файлов для отображения +cli-info-churn_pool_size = + Минимальное КОЛ-ВО коммитов от HEAD, используемых для вычисления сводки изменений + + По умолчанию фактическое значение недетерминировано из-за вычислений на основе времени + и будет отображаться под заголовком информации "Изменения (КОЛ-ВО)" +cli-info-exclude = Игнорировать все файлы и каталоги, соответствующие ИСКЛЮЧЕННОМУ +cli-info-no_bots = Исключить коммиты [ботов]. Используйте <РЕГ. ВЫРАЖЕНИЕ> для переопределения шаблона по умолчанию +cli-info-no_merges = Игнорировать коммиты слияния +cli-info-email = Показать адрес электронной почты каждого автора +cli-info-http_url = Отображать URL репозитория как HTTP +cli-info-hide_token = Скрыть токен в URL репозитория +cli-info-include_hidden = Учитывать скрытые файлы и каталоги +cli-info-type = Фильтровать вывод по типу языка + +# TEXT FORMATTING +cli-text-heading = ФОРМАТИРОВАНИЕ ТЕКСТА +cli-text-colors = + Изменяет цвета текста (X X X...) + + Идет в порядке заголовка, ~, подчеркивания, подзаголовка, двоеточия и информации + + Например: + + '--text-colors 9 10 11 12 13 14' +cli-text-iso_time = Использовать временные метки в формате ISO 8601 +cli-text-number_separator = Какой РАЗДЕЛИТЕЛЬ тысяч использовать +cli-text-no_bold = Отключает жирное форматирование + +# ASCII +cli-ascii-heading = ASCII +cli-ascii-ascii_input = + Принимает непустую СТРОКУ в качестве входных данных для замены ASCII логотипа + + Можно передать сгенерированную СТРОКУ с помощью подстановки команд + + Например: + + '--ascii-input "$(fortune | cowsay -W 25)"' +cli-ascii-ascii_colors = Цвета (X X X...) для печати ASCII арта +cli-ascii-ascii_language = ASCII арт какого ЯЗЫКА печатать +cli-ascii-true_color = + Указать, когда использовать true-color + + Если установлено в auto: true-color будет включен, если он поддерживается терминалом + +# IMAGE +cli-image-heading = ИЗОБРАЖЕНИЕ +cli-image-image = Путь к файлу ИЗОБРАЖЕНИЯ +cli-image-image_protocol = Какой ПРОТОКОЛ изображения использовать +cli-image-color_resolution = ЗНАЧЕНИЕ разрешения цвета, используемого с бэкендом SIXEL + +# VISUALS +cli-visuals-heading = ВИЗУАЛЬНЫЕ ЭЛЕМЕНТЫ +cli-visuals-no_color_palette = Скрывает цветовую палитру +cli-visuals-no_art = Скрывает ASCII арт или изображение, если оно предоставлено +cli-visuals-nerd_fonts = + Использовать иконки Nerd Font + + Заменяет языковые чипы иконками Nerd Font +# not sure if ↑ this ↑ is right translation for "chips" + +# DEVELOPER +cli-dev-heading = ДЛЯ РАЗРАБОТЧИКОВ +cli-dev-output = Выводит Onefetch в определенном формате +cli-dev-completion = Выводит файл автозаполнения для указанной ОБОЛОЧКИ + +# OTHER +cli-other-heading = ПРОЧЕЕ +cli-other-languages = Выводит поддерживаемые языки +cli-other-package_managers = Выводит поддерживаемые менеджеры пакетов \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index 18551fe32..5e05f0c82 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,8 +1,9 @@ +use crate::tr; use crate::info::langs::language::{Language, LanguageType}; use crate::info::utils::info_field::InfoType; use crate::ui::printer::SerializationFormat; use anyhow::Result; -use clap::builder::PossibleValuesParser; +use clap::builder::{PossibleValuesParser, Styles}; use clap::builder::TypedValueParser as _; use clap::{value_parser, Args, Command, Parser, ValueHint}; use clap_complete::{generate, Generator, Shell}; @@ -16,16 +17,54 @@ use std::io; use std::path::PathBuf; use std::str::FromStr; use strum::IntoEnumIterator; +use crate::i18n::locale_keys::cli::*; const COLOR_RESOLUTIONS: [&str; 5] = ["16", "32", "64", "128", "256"]; pub const NO_BOTS_DEFAULT_REGEX_PATTERN: &str = r"(?:-|\s)[Bb]ot$|\[[Bb]ot\]"; #[derive(Clone, Debug, Parser, PartialEq, Eq)] -#[command(version, about)] +#[command( + about = tr!(ABOUT), + version, + disable_help_flag = true, + disable_version_flag = true, + help_template = format!("\ + {{before-help}}{{about-with-newline}}\ + \n{}{}:{} {{usage}} + \n{{all-args}}{{after-help}}\ + ", + Styles::default().get_usage().render(), + tr!(usage::HEADER), + Styles::default().get_usage().render_reset() + ), + next_help_heading = tr!(arguments::HEADER), + override_usage = format!("onefetch [{}] [{}]", tr!(options::HEADER).to_owned().to_uppercase(), tr!(value::INPUT)) +)] pub struct CliOptions { - /// Run as if onefetch was started in instead of the current working directory - #[arg(default_value = ".", hide_default_value = true, value_hint = ValueHint::DirPath)] + #[arg( + default_value = ".", + hide_default_value = true, + value_hint = ValueHint::DirPath, + help = tr!(arguments::INPUT), + value_name = tr!(value::INPUT) + )] pub input: PathBuf, + #[arg( + action = clap::ArgAction::Help, + long, + short, + help = tr!(options::HELP), + help_heading = tr!(options::HEADER) + )] + pub help: Option, + #[arg( + action = clap::ArgAction::Version, + long, + short = 'V', + help = tr!(options::VERSION), + help_heading = tr!(options::HEADER) + )] + pub version: Option, #[command(flatten)] pub info: InfoCliOptions, #[command(flatten)] @@ -43,201 +82,161 @@ pub struct CliOptions { } #[derive(Clone, Debug, Args, PartialEq, Eq)] -#[command(next_help_heading = "INFO")] +#[command(next_help_heading = tr!(info::HEADING))] pub struct InfoCliOptions { - /// Allows you to disable FIELD(s) from appearing in the output #[arg( long, short, + help = tr!(info::DISABLED_FIELDS), num_args = 1.., hide_possible_values = true, value_enum, - value_name = "FIELD" + value_name = tr!(value::FIELD) )] pub disabled_fields: Vec, - /// Hides the title - #[arg(long)] + #[arg(long, help = tr!(info::NO_TITLE))] pub no_title: bool, - /// Maximum NUM of authors to be shown - #[arg(long, default_value_t = 3usize, value_name = "NUM")] + #[arg(long, default_value_t = 3usize, value_name = tr!(value::NUM), help = tr!(info::NUMBER_OF_AUTHORS))] pub number_of_authors: usize, - /// Maximum NUM of languages to be shown - #[arg(long, default_value_t = 6usize, value_name = "NUM")] + #[arg(long, default_value_t = 6usize, value_name = tr!(value::NUM), help = tr!(info::NUMBER_OF_LANGUAGES))] pub number_of_languages: usize, - /// Maximum NUM of file churns to be shown - #[arg(long, default_value_t = 3usize, value_name = "NUM")] + #[arg(long, default_value_t = 3usize, value_name = tr!(value::NUM), help = tr!(info::NUMBER_OF_FILE_CHURNS))] pub number_of_file_churns: usize, - /// Minimum NUM of commits from HEAD used to compute the churn summary - /// - /// By default, the actual value is non-deterministic due to time-based computation - /// and will be displayed under the info title "Churn (NUM)" - #[arg(long, value_name = "NUM")] + #[arg(long, value_name = tr!(value::NUM), help = tr!(info::CHURN_POOL_SIZE))] pub churn_pool_size: Option, - /// Ignore all files & directories matching EXCLUDE - #[arg(long, short, num_args = 1..)] + #[arg(long, short, num_args = 1.., help = tr!(info::EXCLUDE), value_name = tr!(value::EXCLUDE))] pub exclude: Vec, - /// Exclude [bot] commits. Use to override the default pattern #[arg( long, num_args = 0..=1, require_equals = true, default_missing_value = NO_BOTS_DEFAULT_REGEX_PATTERN, - value_name = "REGEX" + value_name = tr!(value::REGEX), + help = tr!(info::NO_BOTS) )] pub no_bots: Option, - /// Ignores merge commits - #[arg(long)] + #[arg(long, help = tr!(info::NO_MERGES))] pub no_merges: bool, - /// Show the email address of each author - #[arg(long, short = 'E')] + #[arg(long, short = 'E', help = tr!(info::EMAIL))] pub email: bool, - /// Display repository URL as HTTP - #[arg(long)] + #[arg(long, help = tr!(info::HTTP_URL))] pub http_url: bool, - /// Hide token in repository URL - #[arg(long)] + #[arg(long, help = tr!(info::HIDE_TOKEN))] pub hide_token: bool, - /// Count hidden files and directories - #[arg(long)] + #[arg(long, help = tr!(info::INCLUDE_HIDDEN))] pub include_hidden: bool, - /// Filters output by language type #[arg( long, num_args = 1.., + value_name = tr!(value::TYPE), default_values = &["programming", "markup"], short = 'T', value_enum, + help = tr!(info::TYPE) )] pub r#type: Vec, } #[derive(Clone, Debug, Args, PartialEq, Eq)] -#[command(next_help_heading = "ASCII")] +#[command(next_help_heading = tr!(ascii::HEADING))] pub struct AsciiCliOptions { - /// Takes a non-empty STRING as input to replace the ASCII logo - /// - /// It is possible to pass a generated STRING by command substitution - /// - /// For example: - /// - /// '--ascii-input "$(fortune | cowsay -W 25)"' - #[arg(long, value_name = "STRING", value_hint = ValueHint::CommandString)] + #[arg(long, value_name = tr!(value::STRING), value_hint = ValueHint::CommandString, help = tr!(ascii::ASCII_INPUT))] pub ascii_input: Option, - /// Colors (X X X...) to print the ascii art #[arg( long, num_args = 1.., value_name = "X", short = 'c', value_parser = value_parser!(u8).range(..16), + help = tr!(ascii::ASCII_COLORS) )] pub ascii_colors: Vec, - /// Which LANGUAGE's ascii art to print #[arg( long, short, - value_name = "LANGUAGE", + value_name = tr!(value::LANGUAGE), value_enum, - hide_possible_values = true + hide_possible_values = true, + help = tr!(ascii::ASCII_LANGUAGE) )] pub ascii_language: Option, - /// Specify when to use true color - /// - /// If set to auto: true color will be enabled if supported by the terminal - #[arg(long, default_value = "auto", value_name = "WHEN", value_enum)] + #[arg(long, default_value = "auto", value_name = tr!(value::WHEN), value_enum, help = tr!(ascii::TRUE_COLOR))] pub true_color: When, } #[derive(Clone, Debug, Args, PartialEq, Eq)] -#[command(next_help_heading = "IMAGE")] +#[command(next_help_heading = image::HEADING)] pub struct ImageCliOptions { - /// Path to the IMAGE file - #[arg(long, short, value_hint = ValueHint::FilePath)] + #[arg(long, short, value_name = tr!(value::IMAGE), value_hint = ValueHint::FilePath, help = tr!(image::IMAGE))] pub image: Option, - /// Which image PROTOCOL to use - #[arg(long, value_enum, requires = "image", value_name = "PROTOCOL")] + #[arg(long, value_enum, requires = "image", value_name = tr!(value::PROTOCOL), help = tr!(image::IMAGE_PROTOCOL))] pub image_protocol: Option, - /// VALUE of color resolution to use with SIXEL backend #[arg( long, - value_name = "VALUE", + value_name = tr!(value::VALUE), requires = "image", default_value_t = 16usize, value_parser = PossibleValuesParser::new(COLOR_RESOLUTIONS) - .map(|s| s.parse::().unwrap()) + .map(|s| s.parse::().unwrap()), + help = tr!(image::COLOR_RESOLUTION) )] pub color_resolution: usize, } #[derive(Clone, Debug, Args, PartialEq, Eq)] -#[command(next_help_heading = "TEXT FORMATTING")] +#[command(next_help_heading = tr!(text::HEADING))] pub struct TextForamttingCliOptions { - /// Changes the text colors (X X X...) - /// - /// Goes in order of title, ~, underline, subtitle, colon, and info - /// - /// For example: - /// - /// '--text-colors 9 10 11 12 13 14' + #[arg( long, short, value_name = "X", value_parser = value_parser!(u8).range(..16), - num_args = 1..=6 + num_args = 1..=6, + help = tr!(text::COLORS) )] pub text_colors: Vec, - /// Use ISO 8601 formatted timestamps - #[arg(long, short = 'z')] + #[arg(long, short = 'z', help = tr!(text::ISO_TIME))] pub iso_time: bool, - /// Which thousands SEPARATOR to use - #[arg(long, value_name = "SEPARATOR", default_value = "plain", value_enum)] + #[arg(long, value_name = tr!(value::SEPARATOR), default_value = "plain", value_enum, help = tr!(text::NUMBER_SEPARATOR))] pub number_separator: NumberSeparator, - /// Turns off bold formatting - #[arg(long)] + #[arg(long, help = tr!(text::NO_BOLD))] pub no_bold: bool, } #[derive(Clone, Debug, Args, PartialEq, Eq, Default)] -#[command(next_help_heading = "VISUALS")] +#[command(next_help_heading = tr!(visuals::HEADING))] pub struct VisualsCliOptions { - /// Hides the color palette - #[arg(long)] + #[arg(long, help = tr!(visuals::NO_COLOR_PALETTE))] pub no_color_palette: bool, - /// Hides the ascii art or image if provided - #[arg(long)] + #[arg(long, help = tr!(visuals::NO_ART))] pub no_art: bool, - /// Use Nerd Font icons - /// - /// Replaces language chips with Nerd Font icons - #[arg(long)] + #[arg(long, help = tr!(visuals::NERD_FONTS))] pub nerd_fonts: bool, } #[derive(Clone, Debug, Args, PartialEq, Eq, Default)] -#[command(next_help_heading = "DEVELOPER")] +#[command(next_help_heading = tr!(dev::HEADING))] pub struct DeveloperCliOptions { - /// Outputs Onefetch in a specific format - #[arg(long, short, value_name = "FORMAT", value_enum)] + #[arg(long, short, value_name = tr!(value::FORMAT), value_enum, help = tr!(dev::OUTPUT))] pub output: Option, - /// If provided, outputs the completion file for given SHELL - #[arg(long = "generate", value_name = "SHELL", value_enum)] + #[arg(long = "generate", value_name = tr!(value::SHELL), value_enum, help = tr!(dev::COMPLETION))] pub completion: Option, } #[derive(Clone, Debug, Args, PartialEq, Eq, Default)] -#[command(next_help_heading = "OTHER")] +#[command(next_help_heading = tr!(other::HEADING))] pub struct OtherCliOptions { - /// Prints out supported languages - #[arg(long, short)] + #[arg(long, short, help = tr!(other::LANGUAGES))] pub languages: bool, - /// Prints out supported package managers - #[arg(long, short)] + #[arg(long, short, help = tr!(other::PACKAGE_MANAGERS))] pub package_managers: bool, } impl Default for CliOptions { fn default() -> CliOptions { CliOptions { + help: None, + version: None, input: PathBuf::from("."), info: InfoCliOptions::default(), text_formatting: TextForamttingCliOptions::default(), diff --git a/src/i18n.rs b/src/i18n.rs new file mode 100644 index 000000000..339bc72a5 --- /dev/null +++ b/src/i18n.rs @@ -0,0 +1,59 @@ +use std::sync::LazyLock; + +use anyhow::Context as _; + +use i18n_embed::fluent::FluentLanguageLoader; +use i18n_embed::DefaultLocalizer; +use i18n_embed::DesktopLanguageRequester; +use i18n_embed::LanguageLoader; +use i18n_embed::Localizer as _; +use rust_embed::RustEmbed; + +#[derive(RustEmbed)] +#[folder = "__locales_compiled"] +struct Localizations; + +include!(concat!(env!("OUT_DIR"), "/locales_consts.rs")); + +pub static LOADER: LazyLock = LazyLock::new(|| { + let loader = i18n_embed::fluent::fluent_language_loader!(); + loader.load_fallback_language(&Localizations).unwrap(); + loader +}); + +fn localizer() -> DefaultLocalizer<'static> { + DefaultLocalizer::new(&*LOADER, &Localizations) +} + +pub fn init() -> anyhow::Result<()> { + let localizer = localizer(); + let requested = DesktopLanguageRequester::requested_languages(); + localizer + .select(&requested) + .context("Failed to set languages")?; + Ok(()) +} + +#[macro_export] +macro_rules! tr { + ($id:literal) => {{ + i18n_embed_fl::fl!($crate::i18n::LOADER, $id) + }}; + + ($id:literal, $($args:expr),*) => {{ + i18n_embed_fl::fl!($crate::i18n::LOADER, $id, $($args),*) + }}; + + ($id:expr) => {{ + $crate::i18n::LOADER.get($id) + }}; + + // i hope this will work right. this is AI slop too(( and i didn't check yet + ($id:expr, $($args:expr),*) => {{ + let mut args = std::collections::HashMap::new(); + $( + args.insert(stringify!($args).trim_matches('"'), $args); + )* + $crate::i18n::LOADER.get_args($id, args) + }}; +} diff --git a/src/lib.rs b/src/lib.rs index 24ff2a9fc..cbcb4eeb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,3 +2,4 @@ pub mod cli; pub mod info; pub mod ui; +pub mod i18n; diff --git a/src/main.rs b/src/main.rs index 17b228f32..9d6749f32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use anyhow::Result; use clap::{CommandFactory, Parser}; +use clap_i18n_richformatter::{init_clap_rich_formatter_localizer, ClapI18nRichFormatter}; use human_panic::setup_panic; use onefetch::cli::{self, CliOptions}; use onefetch::info::build_info; @@ -10,11 +11,16 @@ use std::io; fn main() -> Result<()> { setup_panic!(); + onefetch::i18n::init()?; + init_clap_rich_formatter_localizer(); #[cfg(windows)] enable_ansi_support::enable_ansi_support()?; - let cli_options = cli::CliOptions::parse(); + let cli_options = cli::CliOptions::try_parse().map_err(|e| { + let e = e.apply::(); + e.exit() + }).unwrap(); if cli_options.other.languages { return cli::print_supported_languages();