From 459ed266496bfe671d764375c0457a359be3ab64 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Sun, 16 Nov 2025 18:00:15 +0000 Subject: [PATCH 01/10] feat: setup localizations --- .gitignore | 1 + Cargo.lock | 272 +++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 7 ++ build.rs | 31 ++++- i18n.toml | 4 + locales/en-US/cli.ftl | 1 + locales/ru-RU/cli.ftl | 1 + src/cli.rs | 3 +- src/i18n.rs | 45 +++++++ src/lib.rs | 1 + src/main.rs | 1 + 11 files changed, 362 insertions(+), 5 deletions(-) create mode 100644 i18n.toml create mode 100644 locales/en-US/cli.ftl create mode 100644 locales/ru-RU/cli.ftl create mode 100644 src/i18n.rs 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..55aa92221 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" @@ -504,7 +513,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]] @@ -845,6 +854,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 +874,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" @@ -2197,6 +2260,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.104", + "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.104", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -2403,6 +2533,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" @@ -2605,6 +2754,16 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "macro-vis" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce501641c89150590dd52eb8b38cf2e9208c9015998e889f1142d7914b32c34f" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "maybe-async" version = "0.2.10" @@ -2715,9 +2874,12 @@ dependencies = [ "gix-testtools", "globset", "human-panic", + "i18n-embed", + "i18n-embed-fl", "image", "insta", "lazy_static", + "macro-vis", "num-format", "onefetch-ascii", "onefetch-image", @@ -2725,6 +2887,7 @@ dependencies = [ "owo-colors", "regex", "rstest", + "rust-embed", "serde", "serde_json", "serde_yaml", @@ -3036,6 +3199,28 @@ 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.104", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -3303,6 +3488,40 @@ dependencies = [ "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.104", + "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 +3544,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 +3605,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" @@ -3631,6 +3862,15 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + [[package]] name = "table_formatter" version = "0.6.1" @@ -3949,6 +4189,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" @@ -4021,6 +4270,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" @@ -4238,7 +4506,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]] diff --git a/Cargo.toml b/Cargo.toml index 6285e30ad..595cb6bca 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" +macro-vis = "0.1.1" [dev-dependencies] criterion = "0.7.0" diff --git a/build.rs b/build.rs index a52de4df6..fc98d9bca 100644 --- a/build.rs +++ b/build.rs @@ -1,9 +1,11 @@ use regex::Regex; use std::collections::HashMap; use std::env; +use std::io::Write; use std::error::Error; -use std::fs::{self, File}; -use std::path::Path; +use std::fs::{self, File, create_dir_all, read_to_string}; +use std::io::BufWriter; +use std::path::{Path, PathBuf}; use std::sync::LazyLock; use tera::{Context, Tera}; @@ -29,6 +31,31 @@ fn main() -> Result<(), Box> { )?; fs::write(output_path, rust_code)?; + 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(); + let lang = dir + .components() + .last() + .unwrap() + .as_os_str() + .to_str() + .unwrap(); + create_dir_all(out_dir.join(lang)).unwrap(); + + let mut out_file = + BufWriter::new(File::create(out_dir.join(lang).join("onefetch.ftl"))?); + + for ftl in dir.read_dir()? { + let ftl = ftl?.path(); + let contents = read_to_string(&ftl)?; + writeln!(out_file, "{contents}")?; + } + } + 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/en-US/cli.ftl b/locales/en-US/cli.ftl new file mode 100644 index 000000000..f9e7683f5 --- /dev/null +++ b/locales/en-US/cli.ftl @@ -0,0 +1 @@ +cli-input = Run as if onefetch was started in instead of the current working directory diff --git a/locales/ru-RU/cli.ftl b/locales/ru-RU/cli.ftl new file mode 100644 index 000000000..4bbb29f15 --- /dev/null +++ b/locales/ru-RU/cli.ftl @@ -0,0 +1 @@ +cli-input = Запустить так, как будто onefetch был запущен в вместо текущего рабочего каталога \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index 18551fe32..f7a006cbe 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -9,6 +9,7 @@ use clap_complete::{generate, Generator, Shell}; use num_format::CustomFormat; use onefetch_image::ImageProtocol; use onefetch_manifest::ManifestType; +use crate::i18n::tr; use regex::Regex; use serde::Serialize; use std::env; @@ -23,7 +24,7 @@ pub const NO_BOTS_DEFAULT_REGEX_PATTERN: &str = r"(?:-|\s)[Bb]ot$|\[[Bb]ot\]"; #[derive(Clone, Debug, Parser, PartialEq, Eq)] #[command(version, about)] pub struct CliOptions { - /// Run as if onefetch was started in instead of the current working directory + #[arg(help = tr!("cli-input"))] #[arg(default_value = ".", hide_default_value = true, value_hint = ValueHint::DirPath)] pub input: PathBuf, #[command(flatten)] diff --git a/src/i18n.rs b/src/i18n.rs new file mode 100644 index 000000000..63fe0633a --- /dev/null +++ b/src/i18n.rs @@ -0,0 +1,45 @@ +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; + +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(()) +} + +// TODO: suppress warning "`macro` is experimental" +#[macro_vis::macro_vis(pub)] +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),*) + }} +} 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..933024076 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use std::io; fn main() -> Result<()> { setup_panic!(); + onefetch::i18n::init()?; #[cfg(windows)] enable_ansi_support::enable_ansi_support()?; From 09523466f92c45a655d096787b0d4075f35678aa Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Sun, 16 Nov 2025 19:50:52 +0000 Subject: [PATCH 02/10] translate cli info section to eng and rus --- locales/en-US/cli.ftl | 27 ++++++++++++++++++++ locales/ru-RU/cli.ftl | 29 +++++++++++++++++++++- src/cli.rs | 58 ++++++++++++++++++------------------------- 3 files changed, 79 insertions(+), 35 deletions(-) diff --git a/locales/en-US/cli.ftl b/locales/en-US/cli.ftl index f9e7683f5..caef858a3 100644 --- a/locales/en-US/cli.ftl +++ b/locales/en-US/cli.ftl @@ -1 +1,28 @@ cli-input = Run as if onefetch was started in instead of the current working directory + +# Value name +cli-value-num = NUM +cli-value-field = FIELD +cli-value-regex = REGEX +cli-value-exclude = EXCLUDE + +# 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 \ No newline at end of file diff --git a/locales/ru-RU/cli.ftl b/locales/ru-RU/cli.ftl index 4bbb29f15..3f6f1356f 100644 --- a/locales/ru-RU/cli.ftl +++ b/locales/ru-RU/cli.ftl @@ -1 +1,28 @@ -cli-input = Запустить так, как будто onefetch был запущен в вместо текущего рабочего каталога \ No newline at end of file +cli-input = Запустить так, как будто onefetch был запущен в вместо текущего рабочего каталога + +# Value name +cli-value-num = КОЛ-ВО +cli-value-field = ПОЛЕ +cli-value-regex = РЕГ. ВЫРАЖЕНИЕ +cli-value-exclude = ИСКЛЮЧЕННОЕ + +# 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 = Фильтровать вывод по типу языка \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index f7a006cbe..7eb9932e5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,4 @@ +use crate::i18n::tr; use crate::info::langs::language::{Language, LanguageType}; use crate::info::utils::info_field::InfoType; use crate::ui::printer::SerializationFormat; @@ -9,7 +10,6 @@ use clap_complete::{generate, Generator, Shell}; use num_format::CustomFormat; use onefetch_image::ImageProtocol; use onefetch_manifest::ManifestType; -use crate::i18n::tr; use regex::Regex; use serde::Serialize; use std::env; @@ -24,8 +24,12 @@ pub const NO_BOTS_DEFAULT_REGEX_PATTERN: &str = r"(?:-|\s)[Bb]ot$|\[[Bb]ot\]"; #[derive(Clone, Debug, Parser, PartialEq, Eq)] #[command(version, about)] pub struct CliOptions { - #[arg(help = tr!("cli-input"))] - #[arg(default_value = ".", hide_default_value = true, value_hint = ValueHint::DirPath)] + #[arg( + default_value = ".", + hide_default_value = true, + value_hint = ValueHint::DirPath, + help = tr!("cli-input") + )] pub input: PathBuf, #[command(flatten)] pub info: InfoCliOptions, @@ -44,70 +48,56 @@ pub struct CliOptions { } #[derive(Clone, Debug, Args, PartialEq, Eq)] -#[command(next_help_heading = "INFO")] +#[command(next_help_heading = tr!("cli-info-heading"))] pub struct InfoCliOptions { - /// Allows you to disable FIELD(s) from appearing in the output #[arg( long, short, + help = tr!("cli-info-disabled-fields"), num_args = 1.., hide_possible_values = true, value_enum, - value_name = "FIELD" + value_name = tr!("cli-value-field") )] pub disabled_fields: Vec, - /// Hides the title - #[arg(long)] + #[arg(long, help = tr!("cli-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!("cli-value-num"), help = tr!("cli-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!("cli-value-num"), help = tr!("cli-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!("cli-value-num"), help = tr!("cli-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 = "NUM", help = tr!("cli-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!("cli-info-exclude"), value_name = tr!("cli-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!("cli-value-regex"), + help = tr!("cli-info-no-bots") )] pub no_bots: Option, - /// Ignores merge commits - #[arg(long)] + #[arg(long, help = tr!("cli-info-no-merges"))] pub no_merges: bool, - /// Show the email address of each author - #[arg(long, short = 'E')] + #[arg(long, short = 'E', help = tr!("cli-info-email"))] pub email: bool, - /// Display repository URL as HTTP - #[arg(long)] + #[arg(long, help = tr!("cli-info-http-url"))] pub http_url: bool, - /// Hide token in repository URL - #[arg(long)] + #[arg(long, help = tr!("cli-info-hide-token"))] pub hide_token: bool, - /// Count hidden files and directories - #[arg(long)] + #[arg(long, help = tr!("cli-info-include-hidden"))] pub include_hidden: bool, - /// Filters output by language type #[arg( long, num_args = 1.., default_values = &["programming", "markup"], short = 'T', value_enum, + help = tr!("cli-info-type") )] pub r#type: Vec, } From 17472a5195dedf970b9f76fc0a7c4dfc89bb0ad2 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Sun, 16 Nov 2025 20:27:31 +0000 Subject: [PATCH 03/10] localize clap parsing error --- Cargo.lock | 25 +++++++++++++++++++------ Cargo.toml | 1 + src/main.rs | 7 ++++++- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55aa92221..22a8502f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -440,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", @@ -472,9 +484,9 @@ 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", @@ -2865,6 +2877,7 @@ dependencies = [ "askalono", "byte-unit", "clap", + "clap-i18n-richformatter", "clap_complete", "criterion", "crossbeam-channel", diff --git a/Cargo.toml b/Cargo.toml index 595cb6bca..1e1f8bf6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ i18n-embed = { version = "0.16.0", features = [ i18n-embed-fl = "0.10.0" rust-embed = "8.9.0" macro-vis = "0.1.1" +clap-i18n-richformatter = { git = "https://github.com/Sk7Str1p3/clap-i18n-richformatter-0.1.4" } [dev-dependencies] criterion = "0.7.0" diff --git a/src/main.rs b/src/main.rs index 933024076..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; @@ -11,11 +12,15 @@ 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(); From 3579a1e7f6aa3985d760c77e7f6cc6f1c5068c9c Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Thu, 20 Nov 2025 21:59:09 +0300 Subject: [PATCH 04/10] minor `build.rs` fixes --- build.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/build.rs b/build.rs index fc98d9bca..2dc97bd69 100644 --- a/build.rs +++ b/build.rs @@ -1,10 +1,10 @@ use regex::Regex; use std::collections::HashMap; use std::env; -use std::io::Write; use std::error::Error; -use std::fs::{self, File, create_dir_all, read_to_string}; +use std::fs::{self, create_dir_all, read_to_string, File}; use std::io::BufWriter; +use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::LazyLock; use tera::{Context, Tera}; @@ -37,17 +37,12 @@ fn main() -> Result<(), Box> { for dir in locales_dir { let dir = dir?.path(); - let lang = dir - .components() - .last() - .unwrap() - .as_os_str() - .to_str() - .unwrap(); - create_dir_all(out_dir.join(lang)).unwrap(); + 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(lang).join("onefetch.ftl"))?); + let mut out_file = BufWriter::new(File::create(out_dir.join("onefetch.ftl"))?); for ftl in dir.read_dir()? { let ftl = ftl?.path(); From a44cc3808e3bcbae0814ba90c1c24d23374dbb3d Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Sat, 22 Nov 2025 07:45:47 +0000 Subject: [PATCH 05/10] fix: remove `macro_vis` --- Cargo.lock | 11 ----------- Cargo.toml | 1 - src/cli.rs | 2 +- src/i18n.rs | 3 +-- 4 files changed, 2 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22a8502f5..6a5a5d5f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2766,16 +2766,6 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -[[package]] -name = "macro-vis" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce501641c89150590dd52eb8b38cf2e9208c9015998e889f1142d7914b32c34f" -dependencies = [ - "proc-macro2", - "quote", -] - [[package]] name = "maybe-async" version = "0.2.10" @@ -2892,7 +2882,6 @@ dependencies = [ "image", "insta", "lazy_static", - "macro-vis", "num-format", "onefetch-ascii", "onefetch-image", diff --git a/Cargo.toml b/Cargo.toml index 1e1f8bf6e..e918a219a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,6 @@ i18n-embed = { version = "0.16.0", features = [ ] } i18n-embed-fl = "0.10.0" rust-embed = "8.9.0" -macro-vis = "0.1.1" clap-i18n-richformatter = { git = "https://github.com/Sk7Str1p3/clap-i18n-richformatter-0.1.4" } [dev-dependencies] diff --git a/src/cli.rs b/src/cli.rs index 7eb9932e5..d4f478c7e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,4 @@ -use crate::i18n::tr; +use crate::tr; use crate::info::langs::language::{Language, LanguageType}; use crate::info::utils::info_field::InfoType; use crate::ui::printer::SerializationFormat; diff --git a/src/i18n.rs b/src/i18n.rs index 63fe0633a..b98412b65 100644 --- a/src/i18n.rs +++ b/src/i18n.rs @@ -32,8 +32,7 @@ pub fn init() -> anyhow::Result<()> { Ok(()) } -// TODO: suppress warning "`macro` is experimental" -#[macro_vis::macro_vis(pub)] +#[macro_export] macro_rules! tr { ($id:literal) => {{ i18n_embed_fl::fl!($crate::i18n::LOADER, $id) From a4e19c9ad4369da4c4c344d69b44dbd230b59e5e Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Sat, 22 Nov 2025 08:02:24 +0000 Subject: [PATCH 06/10] translate value names --- locales/en-US/cli.ftl | 10 ++++++++++ locales/ru-RU/cli.ftl | 10 ++++++++++ src/cli.rs | 21 +++++++++++---------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/locales/en-US/cli.ftl b/locales/en-US/cli.ftl index caef858a3..a7d9fea5c 100644 --- a/locales/en-US/cli.ftl +++ b/locales/en-US/cli.ftl @@ -5,6 +5,16 @@ 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 diff --git a/locales/ru-RU/cli.ftl b/locales/ru-RU/cli.ftl index 3f6f1356f..2752d89d5 100644 --- a/locales/ru-RU/cli.ftl +++ b/locales/ru-RU/cli.ftl @@ -5,6 +5,16 @@ 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 = ИНФОРМАЦИЯ diff --git a/src/cli.rs b/src/cli.rs index d4f478c7e..b2a1b05cf 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -68,7 +68,7 @@ pub struct InfoCliOptions { pub number_of_languages: usize, #[arg(long, default_value_t = 3usize, value_name = tr!("cli-value-num"), help = tr!("cli-info-number-of-file-churns"))] pub number_of_file_churns: usize, - #[arg(long, value_name = "NUM", help = tr!("cli-info-churn-pool-size"))] + #[arg(long, value_name = tr!("cli-value-num"), help = tr!("cli-info-churn-pool-size"))] pub churn_pool_size: Option, #[arg(long, short, num_args = 1.., help = tr!("cli-info-exclude"), value_name = tr!("cli-value-exclude"))] pub exclude: Vec, @@ -94,6 +94,7 @@ pub struct InfoCliOptions { #[arg( long, num_args = 1.., + value_name = tr!("cli-value-type"), default_values = &["programming", "markup"], short = 'T', value_enum, @@ -112,7 +113,7 @@ pub struct AsciiCliOptions { /// For example: /// /// '--ascii-input "$(fortune | cowsay -W 25)"' - #[arg(long, value_name = "STRING", value_hint = ValueHint::CommandString)] + #[arg(long, value_name = tr!("cli-value-string"), value_hint = ValueHint::CommandString)] pub ascii_input: Option, /// Colors (X X X...) to print the ascii art #[arg( @@ -127,7 +128,7 @@ pub struct AsciiCliOptions { #[arg( long, short, - value_name = "LANGUAGE", + value_name = tr!("cli-value-language"), value_enum, hide_possible_values = true )] @@ -135,7 +136,7 @@ pub struct AsciiCliOptions { /// 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!("cli-value-when"), value_enum)] pub true_color: When, } @@ -143,15 +144,15 @@ pub struct AsciiCliOptions { #[command(next_help_heading = "IMAGE")] pub struct ImageCliOptions { /// Path to the IMAGE file - #[arg(long, short, value_hint = ValueHint::FilePath)] + #[arg(long, short, value_name = tr!("cli-value-image"), value_hint = ValueHint::FilePath)] 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!("cli-value-protocol"))] pub image_protocol: Option, /// VALUE of color resolution to use with SIXEL backend #[arg( long, - value_name = "VALUE", + value_name = tr!("cli-value-value"), requires = "image", default_value_t = 16usize, value_parser = PossibleValuesParser::new(COLOR_RESOLUTIONS) @@ -182,7 +183,7 @@ pub struct TextForamttingCliOptions { #[arg(long, short = 'z')] pub iso_time: bool, /// Which thousands SEPARATOR to use - #[arg(long, value_name = "SEPARATOR", default_value = "plain", value_enum)] + #[arg(long, value_name = tr!("cli-value-separator"), default_value = "plain", value_enum)] pub number_separator: NumberSeparator, /// Turns off bold formatting #[arg(long)] @@ -208,10 +209,10 @@ pub struct VisualsCliOptions { #[command(next_help_heading = "DEVELOPER")] pub struct DeveloperCliOptions { /// Outputs Onefetch in a specific format - #[arg(long, short, value_name = "FORMAT", value_enum)] + #[arg(long, short, value_name = tr!("cli-value-format"), value_enum)] 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!("cli-value-shell"), value_enum)] pub completion: Option, } From 2386d1b97405a706da45ffe201cf416a7b27fe68 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Sat, 22 Nov 2025 08:37:10 +0000 Subject: [PATCH 07/10] translate cli --- locales/en-US/cli.ftl | 53 ++++++++++++++++++++++++++++++- locales/ru-RU/cli.ftl | 53 ++++++++++++++++++++++++++++++- src/cli.rs | 73 ++++++++++++++----------------------------- 3 files changed, 127 insertions(+), 52 deletions(-) diff --git a/locales/en-US/cli.ftl b/locales/en-US/cli.ftl index a7d9fea5c..fcdee66a3 100644 --- a/locales/en-US/cli.ftl +++ b/locales/en-US/cli.ftl @@ -35,4 +35,55 @@ 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 \ No newline at end of file +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 + +# 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 index 2752d89d5..bbbf972c6 100644 --- a/locales/ru-RU/cli.ftl +++ b/locales/ru-RU/cli.ftl @@ -35,4 +35,55 @@ cli-info-email = Показать адрес электронной почты cli-info-http-url = Отображать URL репозитория как HTTP cli-info-hide-token = Скрыть токен в URL репозитория cli-info-include-hidden = Учитывать скрытые файлы и каталоги -cli-info-type = Фильтровать вывод по типу языка \ No newline at end of file +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 будет включен, если он поддерживается терминалом + +# 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 b2a1b05cf..0aac4819f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -104,39 +104,29 @@ pub struct InfoCliOptions { } #[derive(Clone, Debug, Args, PartialEq, Eq)] -#[command(next_help_heading = "ASCII")] +#[command(next_help_heading = tr!("cli-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 = tr!("cli-value-string"), value_hint = ValueHint::CommandString)] + #[arg(long, value_name = tr!("cli-value-string"), value_hint = ValueHint::CommandString, help = tr!("cli-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!("cli-ascii-ascii-colors") )] pub ascii_colors: Vec, - /// Which LANGUAGE's ascii art to print #[arg( long, short, value_name = tr!("cli-value-language"), value_enum, - hide_possible_values = true + hide_possible_values = true, + help = tr!("cli-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 = tr!("cli-value-when"), value_enum)] + #[arg(long, default_value = "auto", value_name = tr!("cli-value-when"), value_enum, help = tr!("cli-ascii-true-color"))] pub true_color: When, } @@ -162,68 +152,51 @@ pub struct ImageCliOptions { } #[derive(Clone, Debug, Args, PartialEq, Eq)] -#[command(next_help_heading = "TEXT FORMATTING")] +#[command(next_help_heading = tr!("cli-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!("cli-text-colors") )] pub text_colors: Vec, - /// Use ISO 8601 formatted timestamps - #[arg(long, short = 'z')] + #[arg(long, short = 'z', help = tr!("cli-text-iso-time"))] pub iso_time: bool, - /// Which thousands SEPARATOR to use - #[arg(long, value_name = tr!("cli-value-separator"), default_value = "plain", value_enum)] + #[arg(long, value_name = tr!("cli-value-separator"), default_value = "plain", value_enum, help = tr!("cli-text-number-separator"))] pub number_separator: NumberSeparator, - /// Turns off bold formatting - #[arg(long)] + #[arg(long, help = tr!("cli-text-no-bold"))] pub no_bold: bool, } #[derive(Clone, Debug, Args, PartialEq, Eq, Default)] -#[command(next_help_heading = "VISUALS")] +#[command(next_help_heading = tr!("cli-visuals-heading"))] pub struct VisualsCliOptions { - /// Hides the color palette - #[arg(long)] + #[arg(long, help = tr!("cli-visuals-no-color-palette"))] pub no_color_palette: bool, - /// Hides the ascii art or image if provided - #[arg(long)] + #[arg(long, help = tr!("cli-visuals-no-art"))] pub no_art: bool, - /// Use Nerd Font icons - /// - /// Replaces language chips with Nerd Font icons - #[arg(long)] + #[arg(long, help = tr!("cli-visuals-nerd-fonts"))] pub nerd_fonts: bool, } #[derive(Clone, Debug, Args, PartialEq, Eq, Default)] -#[command(next_help_heading = "DEVELOPER")] +#[command(next_help_heading = tr!("cli-dev-heading"))] pub struct DeveloperCliOptions { - /// Outputs Onefetch in a specific format - #[arg(long, short, value_name = tr!("cli-value-format"), value_enum)] + #[arg(long, short, value_name = tr!("cli-value-format"), value_enum, help = tr!("cli-dev-output"))] pub output: Option, - /// If provided, outputs the completion file for given SHELL - #[arg(long = "generate", value_name = tr!("cli-value-shell"), value_enum)] + #[arg(long = "generate", value_name = tr!("cli-value-shell"), value_enum, help = tr!("cli-dev-completion"))] pub completion: Option, } #[derive(Clone, Debug, Args, PartialEq, Eq, Default)] -#[command(next_help_heading = "OTHER")] +#[command(next_help_heading = tr!("cli-other-heading"))] pub struct OtherCliOptions { - /// Prints out supported languages - #[arg(long, short)] + #[arg(long, short, help = tr!("cli-other-languages"))] pub languages: bool, - /// Prints out supported package managers - #[arg(long, short)] + #[arg(long, short, help = tr!("cli-other-package-managers"))] pub package_managers: bool, } From b57985352c5abbaa1b76ddfab45c8caa36bfd6a0 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Sat, 22 Nov 2025 12:51:12 +0000 Subject: [PATCH 08/10] feat: translate `cli` TODO: deal with `default` and `possible values` --- locales/en-US/cli.ftl | 8 ++++++++ locales/ru-RU/cli.ftl | 10 +++++++++- src/cli.rs | 41 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/locales/en-US/cli.ftl b/locales/en-US/cli.ftl index fcdee66a3..b64cc26c4 100644 --- a/locales/en-US/cli.ftl +++ b/locales/en-US/cli.ftl @@ -1,6 +1,14 @@ cli-input = Run as if onefetch was started in instead of the current working directory +cli-about = Command-line Git information tool +cli-usage-header = Usage +cli-arguments-header = Arguments +cli-options-header = Options +cli-help = Print help (see more with '--help', see a summary with '-h') +cli-version = Print version + # Value name +cli-value-input = INPUT cli-value-num = NUM cli-value-field = FIELD cli-value-regex = REGEX diff --git a/locales/ru-RU/cli.ftl b/locales/ru-RU/cli.ftl index bbbf972c6..41a124a64 100644 --- a/locales/ru-RU/cli.ftl +++ b/locales/ru-RU/cli.ftl @@ -1,6 +1,14 @@ -cli-input = Запустить так, как будто onefetch был запущен в вместо текущего рабочего каталога +cli-input = Запустить так, как будто onefetch был запущен в <ввод> вместо текущего рабочего каталога + +cli-about = Инструмент командной строки для получения информации о Git +cli-usage-header = Использование +cli-arguments-header = Аргументы +cli-options-header = Опции +cli-help = Показать помощь (подробнее с '--help', кратко с '-h') +cli-version = Показать версию # Value name +cli-value-input = ВВОД cli-value-num = КОЛ-ВО cli-value-field = ПОЛЕ cli-value-regex = РЕГ. ВЫРАЖЕНИЕ diff --git a/src/cli.rs b/src/cli.rs index 0aac4819f..0a8960936 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3,7 +3,7 @@ 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}; @@ -22,15 +22,48 @@ 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!("cli-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!("cli-usage-header"), + Styles::default().get_usage().render_reset() + ), + next_help_heading = tr!("cli-arguments-header"), + override_usage = format!("onefetch [{}] [{}]", tr!("cli-options-header").to_owned().to_uppercase(), tr!("cli-value-input")) +)] pub struct CliOptions { #[arg( default_value = ".", hide_default_value = true, value_hint = ValueHint::DirPath, - help = tr!("cli-input") + help = tr!("cli-input"), + value_name = tr!("cli-value-input") )] pub input: PathBuf, + #[arg( + action = clap::ArgAction::Help, + long, + short, + help = tr!("cli-help"), + help_heading = tr!("cli-options-header") + )] + pub help: Option, + #[arg( + action = clap::ArgAction::Version, + long, + short = 'V', + help = tr!("cli-version"), + help_heading = tr!("cli-options-header") + )] + pub version: Option, #[command(flatten)] pub info: InfoCliOptions, #[command(flatten)] @@ -203,6 +236,8 @@ pub struct OtherCliOptions { impl Default for CliOptions { fn default() -> CliOptions { CliOptions { + help: None, + version: None, input: PathBuf::from("."), info: InfoCliOptions::default(), text_formatting: TextForamttingCliOptions::default(), From a6e8bdb90b51636fdc9fbf3e6cf07e01fc55c2a9 Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Sat, 22 Nov 2025 20:05:50 +0000 Subject: [PATCH 09/10] fix: translate "image" cli field --- locales/en-US/cli.ftl | 5 +++++ locales/ru-RU/cli.ftl | 5 +++++ src/cli.rs | 9 ++++----- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/locales/en-US/cli.ftl b/locales/en-US/cli.ftl index b64cc26c4..ab00beacc 100644 --- a/locales/en-US/cli.ftl +++ b/locales/en-US/cli.ftl @@ -76,6 +76,11 @@ cli-ascii-true-color = If set to auto: true color will be enabled if supported by the terminal +# 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 diff --git a/locales/ru-RU/cli.ftl b/locales/ru-RU/cli.ftl index 41a124a64..534b33fef 100644 --- a/locales/ru-RU/cli.ftl +++ b/locales/ru-RU/cli.ftl @@ -76,6 +76,11 @@ cli-ascii-true-color = Если установлено в auto: true-color будет включен, если он поддерживается терминалом +# IMAGE +cli-image-image = Путь к файлу ИЗОБРАЖЕНИЯ +cli-image-image_protocol = Какой ПРОТОКОЛ изображения использовать +cli-image-color_resolution = ЗНАЧЕНИЕ разрешения цвета, используемого с бэкендом SIXEL + # VISUALS cli-visuals-heading = ВИЗУАЛЬНЫЕ ЭЛЕМЕНТЫ cli-visuals-no-color-palette = Скрывает цветовую палитру diff --git a/src/cli.rs b/src/cli.rs index 0a8960936..3a73be8c5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -166,11 +166,9 @@ pub struct AsciiCliOptions { #[derive(Clone, Debug, Args, PartialEq, Eq)] #[command(next_help_heading = "IMAGE")] pub struct ImageCliOptions { - /// Path to the IMAGE file - #[arg(long, short, value_name = tr!("cli-value-image"), value_hint = ValueHint::FilePath)] + #[arg(long, short, value_name = tr!("cli-value-image"), value_hint = ValueHint::FilePath, help = tr!("cli-image-image"))] pub image: Option, - /// Which image PROTOCOL to use - #[arg(long, value_enum, requires = "image", value_name = tr!("cli-value-protocol"))] + #[arg(long, value_enum, requires = "image", value_name = tr!("cli-value-protocol"), help = tr!("cli-image-image_protocol"))] pub image_protocol: Option, /// VALUE of color resolution to use with SIXEL backend #[arg( @@ -179,7 +177,8 @@ pub struct ImageCliOptions { requires = "image", default_value_t = 16usize, value_parser = PossibleValuesParser::new(COLOR_RESOLUTIONS) - .map(|s| s.parse::().unwrap()) + .map(|s| s.parse::().unwrap()), + help = tr!("cli-image-color_resolution") )] pub color_resolution: usize, } From ba344176d795f248da60764f3e3ce348049840dd Mon Sep 17 00:00:00 2001 From: Sk7Str1p3 Date: Sun, 23 Nov 2025 16:24:24 +0000 Subject: [PATCH 10/10] feat: move locales build script to separate file and generate constants for locale keys --- Cargo.lock | 69 ++++++++++++------------ Cargo.toml | 3 ++ build.rs | 30 +++-------- locales/build.rs | 123 ++++++++++++++++++++++++++++++++++++++++++ locales/en-US/cli.ftl | 51 +++++++++--------- locales/ru-RU/cli.ftl | 51 +++++++++--------- src/cli.rs | 110 ++++++++++++++++++------------------- src/i18n.rs | 17 +++++- 8 files changed, 292 insertions(+), 162 deletions(-) create mode 100644 locales/build.rs diff --git a/Cargo.lock b/Cargo.lock index 6a5a5d5f1..e5338fa36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,7 +261,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -491,7 +491,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -694,7 +694,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -721,7 +721,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -978,7 +978,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2322,7 +2322,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn 2.0.110", "unic-langid", ] @@ -2336,7 +2336,7 @@ dependencies = [ "i18n-config", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2642,7 +2642,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2774,7 +2774,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2887,6 +2887,8 @@ dependencies = [ "onefetch-image", "onefetch-manifest", "owo-colors", + "proc-macro2", + "quote", "regex", "rstest", "rust-embed", @@ -2894,6 +2896,7 @@ dependencies = [ "serde_json", "serde_yaml", "strum", + "syn 2.0.110", "tera", "time", "time-humanize", @@ -3030,7 +3033,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3220,14 +3223,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.104", + "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", ] @@ -3288,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", ] @@ -3486,7 +3489,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.104", + "syn 2.0.110", "unicode-ident", ] @@ -3510,7 +3513,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.104", + "syn 2.0.110", "walkdir", ] @@ -3636,7 +3639,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3828,7 +3831,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3844,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", @@ -3861,7 +3864,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3981,7 +3984,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3992,7 +3995,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4233,7 +4236,7 @@ checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4440,7 +4443,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -4462,7 +4465,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4538,7 +4541,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4549,7 +4552,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4917,7 +4920,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "synstructure", ] @@ -4938,7 +4941,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4958,7 +4961,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "synstructure", ] @@ -4992,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 e918a219a..5ff33fd8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,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 2dc97bd69..9f7846379 100644 --- a/build.rs +++ b/build.rs @@ -2,13 +2,14 @@ use regex::Regex; use std::collections::HashMap; use std::env; use std::error::Error; -use std::fs::{self, create_dir_all, read_to_string, File}; -use std::io::BufWriter; -use std::io::Write; -use std::path::{Path, PathBuf}; +use std::fs::{self, File}; +use std::path::Path; use std::sync::LazyLock; use tera::{Context, Tera}; +#[path = "locales/build.rs"] +mod locales; + fn main() -> Result<(), Box> { #[cfg(windows)] { @@ -31,25 +32,8 @@ fn main() -> Result<(), Box> { )?; fs::write(output_path, rust_code)?; - 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(); - 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}")?; - } - } + locales::concat_locales()?; + locales::generate_consts(&out_dir)?; Ok(()) } 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 index ab00beacc..8ce120d6b 100644 --- a/locales/en-US/cli.ftl +++ b/locales/en-US/cli.ftl @@ -1,11 +1,11 @@ -cli-input = Run as if onefetch was started in instead of the current working directory 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-help = Print help (see more with '--help', see a summary with '-h') -cli-version = Print version +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 @@ -26,23 +26,23 @@ 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 = +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-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-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 @@ -55,13 +55,13 @@ cli-text-colors = 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 +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 = +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 @@ -69,23 +69,24 @@ cli-ascii-ascii-input = 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 = +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 = +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 @@ -99,4 +100,4 @@ 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 +cli-other-package_managers = Prints out supported package managers diff --git a/locales/ru-RU/cli.ftl b/locales/ru-RU/cli.ftl index 534b33fef..299f9dcde 100644 --- a/locales/ru-RU/cli.ftl +++ b/locales/ru-RU/cli.ftl @@ -1,11 +1,11 @@ -cli-input = Запустить так, как будто onefetch был запущен в <ввод> вместо текущего рабочего каталога cli-about = Инструмент командной строки для получения информации о Git cli-usage-header = Использование cli-arguments-header = Аргументы +cli-arguments-input = Запустить так, как будто onefetch был запущен в <ввод> вместо текущего рабочего каталога cli-options-header = Опции -cli-help = Показать помощь (подробнее с '--help', кратко с '-h') -cli-version = Показать версию +cli-options-help = Показать помощь (подробнее с '--help', кратко с '-h') +cli-options-version = Показать версию # Value name cli-value-input = ВВОД @@ -26,23 +26,23 @@ 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 = +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-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-http_url = Отображать URL репозитория как HTTP +cli-info-hide_token = Скрыть токен в URL репозитория +cli-info-include_hidden = Учитывать скрытые файлы и каталоги cli-info-type = Фильтровать вывод по типу языка # TEXT FORMATTING @@ -55,13 +55,13 @@ cli-text-colors = Например: '--text-colors 9 10 11 12 13 14' -cli-text-iso-time = Использовать временные метки в формате ISO 8601 -cli-text-number-separator = Какой РАЗДЕЛИТЕЛЬ тысяч использовать -cli-text-no-bold = Отключает жирное форматирование +cli-text-iso_time = Использовать временные метки в формате ISO 8601 +cli-text-number_separator = Какой РАЗДЕЛИТЕЛЬ тысяч использовать +cli-text-no_bold = Отключает жирное форматирование # ASCII cli-ascii-heading = ASCII -cli-ascii-ascii-input = +cli-ascii-ascii_input = Принимает непустую СТРОКУ в качестве входных данных для замены ASCII логотипа Можно передать сгенерированную СТРОКУ с помощью подстановки команд @@ -69,23 +69,24 @@ cli-ascii-ascii-input = Например: '--ascii-input "$(fortune | cowsay -W 25)"' -cli-ascii-ascii-colors = Цвета (X X X...) для печати ASCII арта -cli-ascii-ascii-language = ASCII арт какого ЯЗЫКА печатать -cli-ascii-true-color = +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 = +cli-visuals-no_color_palette = Скрывает цветовую палитру +cli-visuals-no_art = Скрывает ASCII арт или изображение, если оно предоставлено +cli-visuals-nerd_fonts = Использовать иконки Nerd Font Заменяет языковые чипы иконками Nerd Font @@ -99,4 +100,4 @@ cli-dev-completion = Выводит файл автозаполнения для # OTHER cli-other-heading = ПРОЧЕЕ cli-other-languages = Выводит поддерживаемые языки -cli-other-package-managers = Выводит поддерживаемые менеджеры пакетов \ No newline at end of file +cli-other-package_managers = Выводит поддерживаемые менеджеры пакетов \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index 3a73be8c5..5e05f0c82 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -17,13 +17,14 @@ 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( - about = tr!("cli-about"), + about = tr!(ABOUT), version, disable_help_flag = true, disable_version_flag = true, @@ -33,35 +34,35 @@ pub const NO_BOTS_DEFAULT_REGEX_PATTERN: &str = r"(?:-|\s)[Bb]ot$|\[[Bb]ot\]"; \n{{all-args}}{{after-help}}\ ", Styles::default().get_usage().render(), - tr!("cli-usage-header"), + tr!(usage::HEADER), Styles::default().get_usage().render_reset() ), - next_help_heading = tr!("cli-arguments-header"), - override_usage = format!("onefetch [{}] [{}]", tr!("cli-options-header").to_owned().to_uppercase(), tr!("cli-value-input")) + next_help_heading = tr!(arguments::HEADER), + override_usage = format!("onefetch [{}] [{}]", tr!(options::HEADER).to_owned().to_uppercase(), tr!(value::INPUT)) )] pub struct CliOptions { #[arg( default_value = ".", hide_default_value = true, value_hint = ValueHint::DirPath, - help = tr!("cli-input"), - value_name = tr!("cli-value-input") + help = tr!(arguments::INPUT), + value_name = tr!(value::INPUT) )] pub input: PathBuf, #[arg( action = clap::ArgAction::Help, long, short, - help = tr!("cli-help"), - help_heading = tr!("cli-options-header") + help = tr!(options::HELP), + help_heading = tr!(options::HEADER) )] pub help: Option, #[arg( action = clap::ArgAction::Version, long, short = 'V', - help = tr!("cli-version"), - help_heading = tr!("cli-options-header") + help = tr!(options::VERSION), + help_heading = tr!(options::HEADER) )] pub version: Option, #[command(flatten)] @@ -81,65 +82,65 @@ pub struct CliOptions { } #[derive(Clone, Debug, Args, PartialEq, Eq)] -#[command(next_help_heading = tr!("cli-info-heading"))] +#[command(next_help_heading = tr!(info::HEADING))] pub struct InfoCliOptions { #[arg( long, short, - help = tr!("cli-info-disabled-fields"), + help = tr!(info::DISABLED_FIELDS), num_args = 1.., hide_possible_values = true, value_enum, - value_name = tr!("cli-value-field") + value_name = tr!(value::FIELD) )] pub disabled_fields: Vec, - #[arg(long, help = tr!("cli-info-no-title"))] + #[arg(long, help = tr!(info::NO_TITLE))] pub no_title: bool, - #[arg(long, default_value_t = 3usize, value_name = tr!("cli-value-num"), help = tr!("cli-info-number-of-authors"))] + #[arg(long, default_value_t = 3usize, value_name = tr!(value::NUM), help = tr!(info::NUMBER_OF_AUTHORS))] pub number_of_authors: usize, - #[arg(long, default_value_t = 6usize, value_name = tr!("cli-value-num"), help = tr!("cli-info-number-of-languages"))] + #[arg(long, default_value_t = 6usize, value_name = tr!(value::NUM), help = tr!(info::NUMBER_OF_LANGUAGES))] pub number_of_languages: usize, - #[arg(long, default_value_t = 3usize, value_name = tr!("cli-value-num"), help = tr!("cli-info-number-of-file-churns"))] + #[arg(long, default_value_t = 3usize, value_name = tr!(value::NUM), help = tr!(info::NUMBER_OF_FILE_CHURNS))] pub number_of_file_churns: usize, - #[arg(long, value_name = tr!("cli-value-num"), help = tr!("cli-info-churn-pool-size"))] + #[arg(long, value_name = tr!(value::NUM), help = tr!(info::CHURN_POOL_SIZE))] pub churn_pool_size: Option, - #[arg(long, short, num_args = 1.., help = tr!("cli-info-exclude"), value_name = tr!("cli-value-exclude"))] + #[arg(long, short, num_args = 1.., help = tr!(info::EXCLUDE), value_name = tr!(value::EXCLUDE))] pub exclude: Vec, #[arg( long, num_args = 0..=1, require_equals = true, default_missing_value = NO_BOTS_DEFAULT_REGEX_PATTERN, - value_name = tr!("cli-value-regex"), - help = tr!("cli-info-no-bots") + value_name = tr!(value::REGEX), + help = tr!(info::NO_BOTS) )] pub no_bots: Option, - #[arg(long, help = tr!("cli-info-no-merges"))] + #[arg(long, help = tr!(info::NO_MERGES))] pub no_merges: bool, - #[arg(long, short = 'E', help = tr!("cli-info-email"))] + #[arg(long, short = 'E', help = tr!(info::EMAIL))] pub email: bool, - #[arg(long, help = tr!("cli-info-http-url"))] + #[arg(long, help = tr!(info::HTTP_URL))] pub http_url: bool, - #[arg(long, help = tr!("cli-info-hide-token"))] + #[arg(long, help = tr!(info::HIDE_TOKEN))] pub hide_token: bool, - #[arg(long, help = tr!("cli-info-include-hidden"))] + #[arg(long, help = tr!(info::INCLUDE_HIDDEN))] pub include_hidden: bool, #[arg( long, num_args = 1.., - value_name = tr!("cli-value-type"), + value_name = tr!(value::TYPE), default_values = &["programming", "markup"], short = 'T', value_enum, - help = tr!("cli-info-type") + help = tr!(info::TYPE) )] pub r#type: Vec, } #[derive(Clone, Debug, Args, PartialEq, Eq)] -#[command(next_help_heading = tr!("cli-ascii-heading"))] +#[command(next_help_heading = tr!(ascii::HEADING))] pub struct AsciiCliOptions { - #[arg(long, value_name = tr!("cli-value-string"), value_hint = ValueHint::CommandString, help = tr!("cli-ascii-ascii-input"))] + #[arg(long, value_name = tr!(value::STRING), value_hint = ValueHint::CommandString, help = tr!(ascii::ASCII_INPUT))] pub ascii_input: Option, #[arg( long, @@ -147,44 +148,43 @@ pub struct AsciiCliOptions { value_name = "X", short = 'c', value_parser = value_parser!(u8).range(..16), - help = tr!("cli-ascii-ascii-colors") + help = tr!(ascii::ASCII_COLORS) )] pub ascii_colors: Vec, #[arg( long, short, - value_name = tr!("cli-value-language"), + value_name = tr!(value::LANGUAGE), value_enum, hide_possible_values = true, - help = tr!("cli-ascii-ascii-language") + help = tr!(ascii::ASCII_LANGUAGE) )] pub ascii_language: Option, - #[arg(long, default_value = "auto", value_name = tr!("cli-value-when"), value_enum, help = tr!("cli-ascii-true-color"))] + #[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 { - #[arg(long, short, value_name = tr!("cli-value-image"), value_hint = ValueHint::FilePath, help = tr!("cli-image-image"))] + #[arg(long, short, value_name = tr!(value::IMAGE), value_hint = ValueHint::FilePath, help = tr!(image::IMAGE))] pub image: Option, - #[arg(long, value_enum, requires = "image", value_name = tr!("cli-value-protocol"), help = tr!("cli-image-image_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 = tr!("cli-value-value"), + value_name = tr!(value::VALUE), requires = "image", default_value_t = 16usize, value_parser = PossibleValuesParser::new(COLOR_RESOLUTIONS) .map(|s| s.parse::().unwrap()), - help = tr!("cli-image-color_resolution") + help = tr!(image::COLOR_RESOLUTION) )] pub color_resolution: usize, } #[derive(Clone, Debug, Args, PartialEq, Eq)] -#[command(next_help_heading = tr!("cli-text-heading"))] +#[command(next_help_heading = tr!(text::HEADING))] pub struct TextForamttingCliOptions { #[arg( @@ -193,42 +193,42 @@ pub struct TextForamttingCliOptions { value_name = "X", value_parser = value_parser!(u8).range(..16), num_args = 1..=6, - help = tr!("cli-text-colors") + help = tr!(text::COLORS) )] pub text_colors: Vec, - #[arg(long, short = 'z', help = tr!("cli-text-iso-time"))] + #[arg(long, short = 'z', help = tr!(text::ISO_TIME))] pub iso_time: bool, - #[arg(long, value_name = tr!("cli-value-separator"), default_value = "plain", value_enum, help = tr!("cli-text-number-separator"))] + #[arg(long, value_name = tr!(value::SEPARATOR), default_value = "plain", value_enum, help = tr!(text::NUMBER_SEPARATOR))] pub number_separator: NumberSeparator, - #[arg(long, help = tr!("cli-text-no-bold"))] + #[arg(long, help = tr!(text::NO_BOLD))] pub no_bold: bool, } #[derive(Clone, Debug, Args, PartialEq, Eq, Default)] -#[command(next_help_heading = tr!("cli-visuals-heading"))] +#[command(next_help_heading = tr!(visuals::HEADING))] pub struct VisualsCliOptions { - #[arg(long, help = tr!("cli-visuals-no-color-palette"))] + #[arg(long, help = tr!(visuals::NO_COLOR_PALETTE))] pub no_color_palette: bool, - #[arg(long, help = tr!("cli-visuals-no-art"))] + #[arg(long, help = tr!(visuals::NO_ART))] pub no_art: bool, - #[arg(long, help = tr!("cli-visuals-nerd-fonts"))] + #[arg(long, help = tr!(visuals::NERD_FONTS))] pub nerd_fonts: bool, } #[derive(Clone, Debug, Args, PartialEq, Eq, Default)] -#[command(next_help_heading = tr!("cli-dev-heading"))] +#[command(next_help_heading = tr!(dev::HEADING))] pub struct DeveloperCliOptions { - #[arg(long, short, value_name = tr!("cli-value-format"), value_enum, help = tr!("cli-dev-output"))] + #[arg(long, short, value_name = tr!(value::FORMAT), value_enum, help = tr!(dev::OUTPUT))] pub output: Option, - #[arg(long = "generate", value_name = tr!("cli-value-shell"), value_enum, help = tr!("cli-dev-completion"))] + #[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 = tr!("cli-other-heading"))] +#[command(next_help_heading = tr!(other::HEADING))] pub struct OtherCliOptions { - #[arg(long, short, help = tr!("cli-other-languages"))] + #[arg(long, short, help = tr!(other::LANGUAGES))] pub languages: bool, - #[arg(long, short, help = tr!("cli-other-package-managers"))] + #[arg(long, short, help = tr!(other::PACKAGE_MANAGERS))] pub package_managers: bool, } diff --git a/src/i18n.rs b/src/i18n.rs index b98412b65..339bc72a5 100644 --- a/src/i18n.rs +++ b/src/i18n.rs @@ -13,6 +13,8 @@ use rust_embed::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(); @@ -40,5 +42,18 @@ macro_rules! tr { ($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) + }}; }