diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index ee0ca5036..57d89ac78 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 @@ -68,7 +68,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb396fda6..9e5ad65bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 diff --git a/.github/workflows/msrv-badge.yml b/.github/workflows/msrv-badge.yml index 3fa749dfb..a3cb33c74 100644 --- a/.github/workflows/msrv-badge.yml +++ b/.github/workflows/msrv-badge.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.ref }} token: ${{ secrets.BADGE_TOKEN }} diff --git a/.github/workflows/web-ci.yml b/.github/workflows/web-ci.yml index 8fffe5b53..145d95ab9 100644 --- a/.github/workflows/web-ci.yml +++ b/.github/workflows/web-ci.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: Setup Node uses: actions/setup-node@v6 diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index 8e2855aa2..5f0217928 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -16,7 +16,7 @@ jobs: wiki: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v5 - uses: spenserblack/actions-wiki@v0.3.0 with: path: docs/wiki 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 62dda2a4a..f54a7e9a1 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 = "2.9.1" @@ -246,7 +255,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -372,7 +381,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -426,9 +435,9 @@ 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", @@ -444,11 +453,23 @@ dependencies = [ "clap", ] +[[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", @@ -468,14 +489,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]] @@ -678,7 +699,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -705,7 +726,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -728,11 +749,11 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "enable-ansi-support" -version = "0.3.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea7457668b3da8a4b702f3d79e131aa3e81cd7e81cc95fb2d54fce9f182ecc77" +checksum = "aa4ff3ae2a9aa54bf7ee0983e59303224de742818c1822d89f07da9856d9bc60" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.42.0", ] [[package]] @@ -856,6 +877,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" @@ -867,6 +897,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" @@ -914,7 +989,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2208,6 +2283,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" @@ -2415,6 +2557,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" @@ -2493,7 +2654,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2625,7 +2786,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -2729,6 +2890,7 @@ dependencies = [ "byte-unit", "clap", "clap-cargo", + "clap-i18n-richformatter", "clap_complete", "criterion", "crossbeam-channel", @@ -2738,6 +2900,8 @@ dependencies = [ "gix-testtools", "globset", "human-panic", + "i18n-embed", + "i18n-embed-fl", "image", "insta", "lazy_static", @@ -2746,12 +2910,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", @@ -2888,7 +3056,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3059,11 +3227,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", ] @@ -3133,9 +3323,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", ] @@ -3228,7 +3418,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3351,10 +3541,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" @@ -3377,6 +3601,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" @@ -3444,6 +3674,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" @@ -3477,7 +3713,7 @@ checksum = "87ff78ab5e8561c9a675bfc1785cb07ae721f0ee53329a595cefd8c04c2ac4e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3669,7 +3905,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3685,9 +3921,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", @@ -3702,7 +3938,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]] @@ -3813,7 +4058,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -3824,7 +4069,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4023,6 +4268,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" @@ -4056,7 +4310,7 @@ checksum = "27a7a9b72ba121f6f1f6c3632b85604cac41aedb5ddc70accbebb6cac83de846" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4074,6 +4328,25 @@ dependencies = [ "arrayvec", ] +[[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 = "unicode-bom" version = "2.0.3" @@ -4200,7 +4473,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -4222,7 +4495,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4285,7 +4558,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.1.3", + "windows-link", "windows-result", "windows-strings", ] @@ -4298,7 +4571,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4309,7 +4582,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4318,19 +4591,13 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - [[package]] name = "windows-result" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -4339,7 +4606,22 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link 0.1.3", + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -4378,15 +4660,6 @@ dependencies = [ "windows-targets 0.53.3", ] -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -4424,7 +4697,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link 0.1.3", + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -4435,6 +4708,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4453,6 +4732,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4471,6 +4756,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4501,6 +4792,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4519,6 +4816,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4537,6 +4840,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4555,6 +4864,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4635,7 +4950,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "synstructure", ] @@ -4656,7 +4971,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", ] [[package]] @@ -4676,7 +4991,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.110", "synstructure", ] @@ -4710,7 +5025,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 d9f74a690..926b962e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,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" @@ -82,9 +89,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/docs/vercel/package-lock.json b/docs/vercel/package-lock.json index 7ea905a97..239ab596e 100644 --- a/docs/vercel/package-lock.json +++ b/docs/vercel/package-lock.json @@ -2332,9 +2332,9 @@ "dev": true }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", "dependencies": { 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/image/Cargo.toml b/image/Cargo.toml index efaa469e5..06138cae4 100644 --- a/image/Cargo.toml +++ b/image/Cargo.toml @@ -15,4 +15,4 @@ image.workspace = true [target.'cfg(not(windows))'.dependencies] color_quant = "1.1.0" base64 = "0.22.1" -libc = "0.2.177" +libc = "0.2.174" diff --git a/locales/build.rs b/locales/build.rs new file mode 100644 index 000000000..fa593dd60 --- /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..9021caa80 --- /dev/null +++ b/locales/en-US/cli.ftl @@ -0,0 +1,124 @@ + +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 { $short -> + [true] (see more with '--help') + *[false] (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-short = Maximum NUM of authors to be shown (default: {$def}) +cli-info-number_of_authors-long = + Maximum NUM of authors to be shown + + (default: {$def}) +cli-info-number_of_languages-short = Maximum NUM of languages to be shown (default: {$def}) +cli-info-number_of_languages-long = + Maximum NUM of languages to be shown + + (default: {$def}) +cli-info-number_of_file_churns-short = Maximum NUM of file churns to be shown (default: {$def}) +cli-info-number_of_file_churns-long = + Maximum NUM of file churns to be shown + + (default: {$def}) +cli-info-churn_pool_size-short = Minimum NUM of commits from HEAD used to compute the churn summary +cli-info-churn_pool_size-long = + 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-tipe-short = Filters output by language type (default: ${def}) (possible values: {$pos}) +cli-info-tipe-long = + Filters output by language type + + (default: ${def}) + (possible values: {$pos}) + +# 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..a508de3a4 --- /dev/null +++ b/locales/ru-RU/cli.ftl @@ -0,0 +1,124 @@ + +cli-about = Инструмент командной строки для получения информации о Git +cli-usage-header = Использование +cli-arguments-header = Аргументы +cli-arguments-input = Запустить так, как будто onefetch был запущен в <ввод> вместо текущего рабочего каталога +cli-options-header = Опции +cli-options-help = Показать помощь { $short -> + [true] (подробнее с '--help') + *[false] (кратко с '-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-short = Максимальное КОЛ-ВО авторов для отображения (по умолчанию: {$def}) +cli-info-number_of_authors-long = + Максимальное КОЛ-ВО авторов для отображения + + (по умолчанию: {$def}) +cli-info-number_of_languages-short = Максимальное КОЛ-ВО языков для отображения (по умолчанию: {$def}) +cli-info-number_of_languages-long = + Максимальное КОЛ-ВО языков для отображения + + (по умолчанию: {$def}) +cli-info-number_of_file_churns-short = Максимальное КОЛ-ВО изменений файлов для отображения (по умолчанию: {$def}) +cli-info-number_of_file_churns-long = + Максимальное КОЛ-ВО изменений файлов для отображения + + (по умолчанию: {$def}) +cli-info-churn_pool_size-short = Минимальное КОЛ-ВО коммитов от HEAD, используемых для вычисления сводки изменений +cli-info-churn_pool_size-long = + Минимальное КОЛ-ВО коммитов от 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-tipe-short = Фильтровать вывод по типу языка (по умолчанию: {$def}) (возможные значения: {$pos}) +cli-info-tipe-long = + Фильтровать вывод по типу языка + + (по умолчанию: {$def}) + (возможные значения: {$pos}) + +# 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 a75863950..315a73531 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,9 +1,11 @@ +use crate::i18n::locale_keys::cli::*; use crate::info::langs::language::{Language, LanguageType}; use crate::info::utils::info_field::InfoType; +use crate::tr; use crate::ui::printer::SerializationFormat; use anyhow::Result; -use clap::builder::PossibleValuesParser; use clap::builder::TypedValueParser as _; +use clap::builder::{PossibleValuesParser, Styles}; use clap::{value_parser, Args, Command, Parser, ValueHint}; use clap_complete::{generate, Generator, Shell}; use num_format::CustomFormat; @@ -20,13 +22,66 @@ use strum::IntoEnumIterator; const COLOR_RESOLUTIONS: [&str; 5] = ["16", "32", "64", "128", "256"]; pub const NO_BOTS_DEFAULT_REGEX_PATTERN: &str = r"(?:-|\s)[Bb]ot$|\[[Bb]ot\]"; +// TODO: check if short help requested more efficiently +use std::sync::LazyLock; +static IS_SHORT: LazyLock = LazyLock::new(|| { + let args = std::env::args(); + let mut v = false; + for value in args { + if value == "-h" { + v = true; + break; + } else if value == "--help" { + break; + } else { + } + } + v +}); + #[derive(Clone, Debug, Parser, PartialEq, Eq)] -#[command(version, about)] -#[command(styles = clap_cargo::style::CLAP_STYLING)] +#[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 = if *IS_SHORT { clap::ArgAction::HelpShort } else { clap::ArgAction::HelpLong }, + long, + short, + help = tr!(options::HELP, short => &*IS_SHORT), + 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)] @@ -44,201 +99,184 @@ 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), hide_default_value = true)] + #[arg( + help = tr!(info::number_of_authors::SHORT, def => 3), + long_help = tr!(info::number_of_authors::LONG, def => 3), + hide_default_value = true + )] 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))] + #[arg( + help = tr!(info::number_of_languages::SHORT, def => 6), + long_help = tr!(info::number_of_languages::LONG, def => 6), + hide_default_value = true + )] 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))] + #[arg( + help = tr!(info::number_of_file_churns::SHORT, def => 3), + long_help = tr!(info::number_of_file_churns::LONG, def => 3), + hide_default_value = true + )] 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))] + #[arg( + help = tr!(info::churn_pool_size::SHORT), + long_help = tr!(info::churn_pool_size::LONG) + )] 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, )] + #[arg( + help = tr!(info::tipe::SHORT, def => "programming, markup", pos => "programming, markup, prose, data"), + long_help = tr!(info::tipe::LONG, def => "programming, markup", pos => "programming, markup, prose, data"), + hide_default_value = true, + hide_possible_values = true, + )] 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 = tr!(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..cfb17266e --- /dev/null +++ b/src/i18n.rs @@ -0,0 +1,58 @@ +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) + }}; + + ($id:expr, $($key:expr => $value:expr),*) => {{ + let mut args = std::collections::HashMap::new(); + $( + args.insert(stringify!($key), $value.to_string()); + )* + $crate::i18n::LOADER.get_args($id, args) + }}; +} diff --git a/src/lib.rs b/src/lib.rs index 24ff2a9fc..c1c57b056 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ // Lib is present to allow for benchmarks and integration tests pub mod cli; +pub mod i18n; pub mod info; pub mod ui; diff --git a/src/main.rs b/src/main.rs index 17b228f32..737c84671 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,18 @@ 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();