From 77a48ca6aa1b1b34908a98ff97c6e05d17457df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= <41945903+qarmin@users.noreply.github.com> Date: Sat, 11 Dec 2021 16:16:14 +0100 Subject: [PATCH] Add support for translations (#469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reformat code with idea tool * Pierwsza działająca wersja tłumaczeń * Działa? I dobrze, bo ma działać * Ćma szła i się potkła * Ściął śmiałek źółty rząd pąków. --- Cargo.lock | 356 +++++++++++++++- README.md | 2 + czkawka_core/Cargo.toml | 5 + czkawka_core/i18n.toml | 13 + czkawka_core/src/lib.rs | 1 + czkawka_core/src/localizer.rs | 34 ++ czkawka_core/src/similar_images.rs | 25 +- czkawka_gui/Cargo.toml | 6 + czkawka_gui/i18n.toml | 13 + czkawka_gui/src/compute_results.rs | 99 +++-- czkawka_gui/src/connect_button_delete.rs | 5 +- czkawka_gui/src/connect_button_hardlink.rs | 5 +- czkawka_gui/src/connect_button_move.rs | 3 +- czkawka_gui/src/connect_button_search.rs | 3 +- czkawka_gui/src/connect_change_language.rs | 46 +++ czkawka_gui/src/connect_notebook_tabs.rs | 13 - czkawka_gui/src/connect_popovers.rs | 46 +-- czkawka_gui/src/connect_progress_window.rs | 45 +-- .../src/connect_selection_of_directories.rs | 9 +- czkawka_gui/src/gui_about.rs | 13 +- czkawka_gui/src/gui_bottom_buttons.rs | 38 +- czkawka_gui/src/gui_data.rs | 31 +- czkawka_gui/src/gui_header.rs | 9 +- czkawka_gui/src/gui_main_notebook.rs | 218 +++++++++- czkawka_gui/src/gui_popovers.rs | 14 + czkawka_gui/src/gui_progress_dialog.rs | 17 +- czkawka_gui/src/gui_settings.rs | 120 ++++-- czkawka_gui/src/gui_upper_notebook.rs | 121 ++++++ czkawka_gui/src/gui_upper_notepad.rs | 82 ---- czkawka_gui/src/help_functions.rs | 11 + czkawka_gui/src/initialize_gui.rs | 9 + czkawka_gui/src/language_functions.rs | 26 ++ czkawka_gui/src/main.rs | 8 +- czkawka_gui/src/notebook_enums.rs | 30 +- czkawka_gui/src/saving_loading.rs | 39 +- czkawka_gui/ui/main_window.glade | 170 ++------ czkawka_gui/ui/progress.glade | 6 +- czkawka_gui/ui/settings.glade | 62 ++- i18n.toml | 13 + i18n/en/czkawka_core.ftl | 1 + i18n/en/czkawka_gui.ftl | 379 ++++++++++++++++++ i18n/pl/czkawka_core.ftl | 1 + i18n/pl/czkawka_gui.ftl | 366 +++++++++++++++++ instructions/Translations.md | 67 ++++ 44 files changed, 2105 insertions(+), 475 deletions(-) create mode 100644 czkawka_core/i18n.toml create mode 100644 czkawka_core/src/localizer.rs create mode 100644 czkawka_gui/i18n.toml create mode 100644 czkawka_gui/src/connect_change_language.rs create mode 100644 czkawka_gui/src/gui_upper_notebook.rs delete mode 100644 czkawka_gui/src/gui_upper_notepad.rs create mode 100644 czkawka_gui/src/language_functions.rs create mode 100644 i18n.toml create mode 120000 i18n/en/czkawka_core.ftl create mode 100644 i18n/en/czkawka_gui.ftl create mode 120000 i18n/pl/czkawka_core.ftl create mode 100644 i18n/pl/czkawka_gui.ftl create mode 100644 instructions/Translations.md diff --git a/Cargo.lock b/Cargo.lock index 8962da8..c077e99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,21 @@ dependencies = [ "digest", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.8.0" @@ -421,6 +436,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.3.0" @@ -499,10 +523,14 @@ dependencies = [ "futures", "hamming", "humansize", + "i18n-embed", + "i18n-embed-fl", "image", "img_hash", + "once_cell", "rayon", "rodio", + "rust-embed", "serde", "serde_json", "tempfile", @@ -525,10 +553,14 @@ dependencies = [ "glib", "gtk", "humansize", + "i18n-embed", + "i18n-embed-fl", "image", "img_hash", + "once_cell", "open", "regex", + "rust-embed", "trash", "winapi", ] @@ -568,6 +600,16 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if", + "num_cpus", +] + [[package]] name = "deflate" version = "0.8.6" @@ -648,6 +690,15 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml", +] + [[package]] name = "flate2" version = "1.0.22" @@ -660,6 +711,50 @@ dependencies = [ "miniz_oxide 0.4.4", ] +[[package]] +name = "fluent" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f69378194459db76abd2ce3952b790db103ceb003008d3d50d97c41ff847a7" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78" +dependencies = [ + "thiserror", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1039,6 +1134,75 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" +[[package]] +name = "i18n-config" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62affcd43abfb51f3cbd8736f9407908dc5b44fc558a9be07460bbfd104d983" +dependencies = [ + "log", + "serde", + "serde_derive", + "thiserror", + "toml", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8be76966dc82fc0c1fe50bac057170294594ab2a78a0e5d82f85227248a19a1" +dependencies = [ + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "lazy_static", + "locale_config", + "log", + "parking_lot", + "rust-embed", + "thiserror", + "unic-langid", + "walkdir", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c1d347d2f7f9f2f977385b43b204d59ebeb1b2f93ce73cd23622df2d2da1033" +dependencies = [ + "dashmap", + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0db2330e035808eb064afb67e6743ddce353763af3e0f2bdfc2476e00ce76136" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "id3" version = "0.5.3" @@ -1098,10 +1262,30 @@ dependencies = [ ] [[package]] -name = "itertools" -version = "0.10.1" +name = "intl-memoizer" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf" +dependencies = [ + "tinystr", + "unic-langid", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] @@ -1198,6 +1382,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "locale_config" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934" +dependencies = [ + "lazy_static", + "objc", + "objc-foundation", + "regex", + "winapi", +] + [[package]] name = "lock_api" version = "0.4.5" @@ -1225,6 +1422,15 @@ dependencies = [ "libc", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.4.1" @@ -1503,6 +1709,35 @@ dependencies = [ "syn", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "oboe" version = "0.4.4" @@ -1541,6 +1776,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "open" version = "2.0.2" @@ -1636,9 +1877,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" +checksum = "d1a3ea4f0dd7f1f3e512cf97bf100819aa547f36a6eccac8dbaae839eb92363e" [[package]] name = "png" @@ -1712,9 +1953,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" dependencies = [ "unicode-xid", ] @@ -1851,6 +2092,40 @@ dependencies = [ "minimp3", ] +[[package]] +name = "rust-embed" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40377bff8cceee81e28ddb73ac97f5c2856ce5522f0b260b763f434cdfae602" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e763e24ba2bf0c72bc6be883f967f794a019fafd1b86ba1daff9c91a7edd30" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad22c7226e4829104deab21df575e995bfbc4adfad13a595e387477f238c1aec" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1938,6 +2213,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "self_cell" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af" + [[package]] name = "semver" version = "0.11.0" @@ -1958,18 +2239,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.130" +version = "1.0.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" dependencies = [ "proc-macro2", "quote", @@ -1987,6 +2268,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + [[package]] name = "shlex" version = "0.1.1" @@ -2040,6 +2334,12 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "structopt" version = "0.3.25" @@ -2176,6 +2476,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "tinystr" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" + [[package]] name = "tinyvec" version = "1.5.1" @@ -2231,6 +2537,15 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622b09ce2fe2df4618636fb92176d205662f59803f39e70d1c333393082de96c" +[[package]] +name = "type-map" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46" +dependencies = [ + "rustc-hash", +] + [[package]] name = "typenum" version = "1.14.0" @@ -2243,6 +2558,25 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "unic-langid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" +dependencies = [ + "serde", + "tinystr", +] + [[package]] name = "unicode-segmentation" version = "1.8.0" diff --git a/README.md b/README.md index 997c5f1..6b6e751 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ - CLI frontend - for easy automation - GUI frontend - uses modern GTK 3 and looks similar to FSlint - No spying - Czkawka does not have access to the Internet, nor does it collect any user information or statistics +- Multilingual - app support multiple languages - Multiple tools to use: - Duplicates - Finds duplicates based on file name, size or hash - Empty Folders - Finds empty folders with the help of an advanced algorithm @@ -131,6 +132,7 @@ You can help by creating: - Pull Requests - implementing a new feature yourself or fixing bugs. If the change is bigger, then it's a good idea to open a new issue to discuss changes. - Documentation - There is an [instruction](instructions/Instruction.md) which you can improve. +- Translations - Instruction how to translate files is available [here](instructions/Translations.md) You can also help by doing different things: - Creating text articles - [LinuxUprising](https://www.linuxuprising.com/2021/03/find-and-remove-duplicate-files-similar.html) or [Ubunlog](https://ubunlog.com/en/czkawka-finds-and-removes-empty-and-broken-duplicate-files/) diff --git a/czkawka_core/Cargo.toml b/czkawka_core/Cargo.toml index d9a067f..c6b40a0 100644 --- a/czkawka_core/Cargo.toml +++ b/czkawka_core/Cargo.toml @@ -50,6 +50,11 @@ serde = "1.0.130" bincode = "1.3.3" serde_json = "1.0.72" +# Language +i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] } +i18n-embed-fl = "0.6" +rust-embed = "6.2.0" +once_cell = "1.8.0" [features] default = [] diff --git a/czkawka_core/i18n.toml b/czkawka_core/i18n.toml new file mode 100644 index 0000000..111a110 --- /dev/null +++ b/czkawka_core/i18n.toml @@ -0,0 +1,13 @@ +# (Required) The language identifier of the language used in the +# source code for gettext system, and the primary fallback language +# (for which all strings must be present) when using the fluent +# system. +fallback_language = "en" + +# Use the fluent localization system. +[fluent] +# (Required) The path to the assets directory. +# The paths inside the assets directory should be structured like so: +# `assets_dir/{language}/{domain}.ftl` +assets_dir = "../i18n" + diff --git a/czkawka_core/src/lib.rs b/czkawka_core/src/lib.rs index 3f81054..0b7fac7 100644 --- a/czkawka_core/src/lib.rs +++ b/czkawka_core/src/lib.rs @@ -18,5 +18,6 @@ pub mod common_extensions; pub mod common_items; pub mod common_messages; pub mod common_traits; +pub mod localizer; pub const CZKAWKA_VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/czkawka_core/src/localizer.rs b/czkawka_core/src/localizer.rs new file mode 100644 index 0000000..3560fb1 --- /dev/null +++ b/czkawka_core/src/localizer.rs @@ -0,0 +1,34 @@ +use i18n_embed::{ + fluent::{fluent_language_loader, FluentLanguageLoader}, + DefaultLocalizer, LanguageLoader, Localizer, +}; +use once_cell::sync::Lazy; +use rust_embed::RustEmbed; + +#[derive(RustEmbed)] +#[folder = "../i18n/"] +struct Localizations; + +pub static LANGUAGE_LOADER: Lazy = Lazy::new(|| { + let loader: FluentLanguageLoader = fluent_language_loader!(); + + loader.load_fallback_language(&Localizations).expect("Error while loading fallback language"); + + loader +}); + +#[macro_export] +macro_rules! fl { + ($message_id:literal) => {{ + i18n_embed_fl::fl!($crate::localizer::LANGUAGE_LOADER, $message_id) + }}; + + ($message_id:literal, $($args:expr),*) => {{ + i18n_embed_fl::fl!($crate::localizer::LANGUAGE_LOADER, $message_id, $($args), *) + }}; +} + +// Get the `Localizer` to be used for localizing this library. +pub fn localizer() -> Box { + Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations)) +} diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index cafc388..cc660a3 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -23,6 +23,7 @@ use crate::common_directory::Directories; use crate::common_items::ExcludedItems; use crate::common_messages::Messages; use crate::common_traits::{DebugPrint, PrintResults, SaveResults}; +use crate::fl; // TODO check for better values pub const SIMILAR_VALUES: [[u32; 6]; 4] = [ @@ -905,17 +906,17 @@ pub fn get_string_from_similarity(similarity: &Similarity, hash_size: u8) -> Str #[cfg(debug_assertions)] { if *h <= SIMILAR_VALUES[index_preset][0] { - format!("Very High {}", *h) + format!("{} {}", fl!("core_similarity_very_high"), *h) } else if *h <= SIMILAR_VALUES[index_preset][1] { - format!("High {}", *h) + format!("{} {}", fl!("core_similarity_high"), *h) } else if *h <= SIMILAR_VALUES[index_preset][2] { - format!("Medium {}", *h) + format!("{} {}", fl!("core_similarity_medium"), *h) } else if *h <= SIMILAR_VALUES[index_preset][3] { - format!("Small {}", *h) + format!("{} {}", fl!("core_similarity_small"), *h) } else if *h <= SIMILAR_VALUES[index_preset][4] { - format!("Very Small {}", *h) + format!("{} {}", fl!("core_similarity_very_small"), *h) } else if *h <= SIMILAR_VALUES[index_preset][5] { - format!("Minimal {}", *h) + format!("{} {}", fl!("core_similarity_minimal"), *h) } else { panic!(); } @@ -923,17 +924,17 @@ pub fn get_string_from_similarity(similarity: &Similarity, hash_size: u8) -> Str #[cfg(not(debug_assertions))] { if *h <= SIMILAR_VALUES[index_preset][0] { - format!("Very High") + fl!("core_similarity_very_high") } else if *h <= SIMILAR_VALUES[index_preset][1] { - format!("High") + fl!("core_similarity_high") } else if *h <= SIMILAR_VALUES[index_preset][2] { - format!("Medium") + fl!("core_similarity_medium") } else if *h <= SIMILAR_VALUES[index_preset][3] { - format!("Small") + fl!("core_similarity_small") } else if *h <= SIMILAR_VALUES[index_preset][4] { - format!("Very Small") + fl!("core_similarity_very_small") } else if *h <= SIMILAR_VALUES[index_preset][5] { - format!("Minimal") + fl!("core_similarity_minimal") } else { panic!(); } diff --git a/czkawka_gui/Cargo.toml b/czkawka_gui/Cargo.toml index 9f76a89..696a6ed 100644 --- a/czkawka_gui/Cargo.toml +++ b/czkawka_gui/Cargo.toml @@ -43,6 +43,12 @@ trash = "1.3.0" # For moving files(why std::fs doesn't have such features) fs_extra = "1.2.0" +# Language +i18n-embed = { version = "0.13", features = ["fluent-system", "desktop-requester"] } +i18n-embed-fl = "0.6" +rust-embed = "6.2.0" +once_cell = "1.8.0" + [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.9", features = ["combaseapi", "objbase", "shobjidl_core", "windef", "winerror", "wtypesbase", "winuser"] } diff --git a/czkawka_gui/i18n.toml b/czkawka_gui/i18n.toml new file mode 100644 index 0000000..111a110 --- /dev/null +++ b/czkawka_gui/i18n.toml @@ -0,0 +1,13 @@ +# (Required) The language identifier of the language used in the +# source code for gettext system, and the primary fallback language +# (for which all strings must be present) when using the fluent +# system. +fallback_language = "en" + +# Use the fluent localization system. +[fluent] +# (Required) The path to the assets directory. +# The paths inside the assets directory should be structured like so: +# `assets_dir/{language}/{domain}.ftl` +assets_dir = "../i18n" + diff --git a/czkawka_gui/src/compute_results.rs b/czkawka_gui/src/compute_results.rs index c7a3f16..0400185 100644 --- a/czkawka_gui/src/compute_results.rs +++ b/czkawka_gui/src/compute_results.rs @@ -8,6 +8,7 @@ use glib::Receiver; use gtk::prelude::*; use humansize::{file_size_opts as options, FileSize}; +use crate::fl; use czkawka_core::duplicate::CheckingMethod; use czkawka_core::same_music::MusicSimilarity; use czkawka_core::similar_images; @@ -85,7 +86,7 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< match msg { Message::Duplicates(df) => { if df.get_stopped_search() { - entry_info.set_text("Searching for duplicates was stopped by user"); + entry_info.set_text(&fl!("compute_stopped_by_user")); } else { let information = df.get_information(); let text_messages = df.get_text_messages(); @@ -94,32 +95,57 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let duplicates_size: u64; let duplicates_group: usize; + let fl_found = fl!("compute_found"); + let fl_groups = fl!("compute_groups"); + let fl_groups_which_took = fl!("compute_groups_which_took"); + let fl_duplicated_files_in = fl!("compute_duplicated_files_in"); + match df.get_check_method() { CheckingMethod::Name => { duplicates_number = information.number_of_duplicated_files_by_name; - duplicates_size = 0; + // duplicates_size = 0; duplicates_group = information.number_of_groups_by_name; - entry_info.set_text(format!("Found {} files in {} groups which have same names.", duplicates_number, duplicates_group).as_str()); + entry_info.set_text(format!("{} {} {} {} {}", fl_found, duplicates_number, fl_duplicated_files_in, duplicates_group, fl_groups).as_str()); } CheckingMethod::Hash => { duplicates_number = information.number_of_duplicated_files_by_hash; duplicates_size = information.lost_space_by_hash; duplicates_group = information.number_of_groups_by_hash; - entry_info.set_text(format!("Found {} duplicates files in {} groups which took {}.", duplicates_number, duplicates_group, duplicates_size.file_size(options::BINARY).unwrap()).as_str()); + entry_info.set_text( + format!( + "{} {} {} {} {} {}.", + fl_found, + duplicates_number, + fl_duplicated_files_in, + duplicates_group, + fl_groups_which_took, + duplicates_size.file_size(options::BINARY).unwrap() + ) + .as_str(), + ); } CheckingMethod::Size => { duplicates_number = information.number_of_duplicated_files_by_size; duplicates_size = information.lost_space_by_size; duplicates_group = information.number_of_groups_by_size; - entry_info.set_text(format!("Found {} duplicates files in {} groups which took {}.", duplicates_number, duplicates_group, duplicates_size.file_size(options::BINARY).unwrap()).as_str()); + entry_info.set_text( + format!( + "{} {} {} {} {} {}.", + fl_found, + duplicates_number, + fl_duplicated_files_in, + duplicates_group, + fl_groups_which_took, + duplicates_size.file_size(options::BINARY).unwrap() + ) + .as_str(), + ); } CheckingMethod::None => { panic!(); } } - entry_info.set_text(format!("Found {} duplicates files in {} groups which took {}.", duplicates_number, duplicates_group, duplicates_size.file_size(options::BINARY).unwrap()).as_str()); - // Create GUI { let list_store = get_list_store(&tree_view_duplicate_finder); @@ -192,10 +218,16 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let values: [(u32, &dyn ToValue); 8] = [ (ColumnsDuplicates::ActivatableSelectButton as u32, &false), (ColumnsDuplicates::SelectionButton as u32, &false), - (ColumnsDuplicates::Name as u32, &(format!("{} x {} ({} bytes)", vector.len(), size.file_size(options::BINARY).unwrap(), size))), + (ColumnsDuplicates::Name as u32, &(format!("{} x {} ({} {})", vector.len(), size.file_size(options::BINARY).unwrap(), size, fl!("general_bytes")))), ( ColumnsDuplicates::Path as u32, - &(format!("{} ({} bytes) lost", ((vector.len() - 1) as u64 * *size as u64).file_size(options::BINARY).unwrap(), (vector.len() - 1) as u64 * *size as u64)), + &(format!( + "{} ({} {}) {}", + ((vector.len() - 1) as u64 * *size as u64).file_size(options::BINARY).unwrap(), + (vector.len() - 1) as u64 * *size as u64, + fl!("general_bytes"), + fl!("general_lost") + )), ), (ColumnsDuplicates::Modification as u32, &"".to_string()), // No text in 3 column (ColumnsDuplicates::ModificationAsSecs as u32, &(0)), @@ -241,10 +273,16 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let values: [(u32, &dyn ToValue); 8] = [ (ColumnsDuplicates::ActivatableSelectButton as u32, &false), (ColumnsDuplicates::SelectionButton as u32, &false), - (ColumnsDuplicates::Name as u32, &(format!("{} x {} ({} bytes)", vector.len(), size.file_size(options::BINARY).unwrap(), size))), + (ColumnsDuplicates::Name as u32, &(format!("{} x {} ({} {})", vector.len(), size.file_size(options::BINARY).unwrap(), size, fl!("general_bytes")))), ( ColumnsDuplicates::Path as u32, - &(format!("{} ({} bytes) lost", ((vector.len() - 1) as u64 * *size as u64).file_size(options::BINARY).unwrap(), (vector.len() - 1) as u64 * *size as u64)), + &(format!( + "{} ({} {}) {}", + ((vector.len() - 1) as u64 * *size as u64).file_size(options::BINARY).unwrap(), + (vector.len() - 1) as u64 * *size as u64, + fl!("general_bytes"), + fl!("general_lost") + )), ), (ColumnsDuplicates::Modification as u32, &"".to_string()), // No text in 3 column (ColumnsDuplicates::ModificationAsSecs as u32, &(0)), // Not used here @@ -289,14 +327,14 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< } Message::EmptyFolders(ef) => { if ef.get_stopped_search() { - entry_info.set_text("Searching for empty folders was stopped by user"); + entry_info.set_text(&fl!("compute_stopped_by_user")); } else { let information = ef.get_information(); let text_messages = ef.get_text_messages(); let empty_folder_number: usize = information.number_of_empty_folders; - entry_info.set_text(format!("Found {} empty folders.", empty_folder_number).as_str()); + entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), empty_folder_number, fl!("compute_empty_folders")).as_str()); // Create GUI { @@ -336,14 +374,14 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< } Message::EmptyFiles(vf) => { if vf.get_stopped_search() { - entry_info.set_text("Searching for empty files was stopped by user"); + entry_info.set_text(&fl!("compute_stopped_by_user")); } else { let information = vf.get_information(); let text_messages = vf.get_text_messages(); let empty_files_number: usize = information.number_of_empty_files; - entry_info.set_text(format!("Found {} empty files.", empty_files_number).as_str()); + entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), empty_files_number, fl!("compute_empty_files")).as_str()); // Create GUI { @@ -384,14 +422,14 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< } Message::BigFiles(bf) => { if bf.get_stopped_search() { - entry_info.set_text("Searching for big files was stopped by user"); + entry_info.set_text(&fl!("compute_stopped_by_user")); } else { let information = bf.get_information(); let text_messages = bf.get_text_messages(); let biggest_files_number: usize = information.number_of_real_files; - entry_info.set_text(format!("Found {} biggest files.", biggest_files_number).as_str()); + entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), biggest_files_number, fl!("compute_biggest_files")).as_str()); // Create GUI { @@ -409,7 +447,7 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let (directory, file) = split_path(&file_entry.path); let values: [(u32, &dyn ToValue); 7] = [ (ColumnsBigFiles::SelectionButton as u32, &false), - (ColumnsBigFiles::Size as u32, &(format!("{} ({} bytes)", size.file_size(options::BINARY).unwrap(), size))), + (ColumnsBigFiles::Size as u32, &(format!("{} ({} {})", size.file_size(options::BINARY).unwrap(), size, fl!("general_bytes")))), (ColumnsBigFiles::Name as u32, &file), (ColumnsBigFiles::Path as u32, &directory), (ColumnsBigFiles::Modification as u32, &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string())), @@ -434,14 +472,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< } Message::Temporary(tf) => { if tf.get_stopped_search() { - entry_info.set_text("Searching for temporary files was stopped by user"); + entry_info.set_text(&fl!("compute_stopped_by_user")); } else { let information = tf.get_information(); let text_messages = tf.get_text_messages(); let temporary_files_number: usize = information.number_of_temporary_files; - - entry_info.set_text(format!("Found {} temporary files.", temporary_files_number).as_str()); + entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), temporary_files_number, fl!("compute_temporary_files")).as_str()); // Create GUI { @@ -482,14 +519,14 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< } Message::SimilarImages(sf) => { if sf.get_stopped_search() { - entry_info.set_text("Searching for similar images was stopped by user"); + entry_info.set_text(&fl!("compute_stopped_by_user")); } else { //let information = sf.get_information(); let text_messages = sf.get_text_messages(); let base_images_size = sf.get_similar_images().len(); - entry_info.set_text(format!("Found similar pictures for {} images.", base_images_size).as_str()); + entry_info.set_text(format!("{} {} {} {}.", fl!("compute_found"), fl!("compute_duplicates_for"), base_images_size, fl!("compute_similar_image")).as_str()); // Create GUI { @@ -563,14 +600,14 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< } Message::SimilarVideos(ff) => { if ff.get_stopped_search() { - entry_info.set_text("Searching for similar videos was stopped by user"); + entry_info.set_text(&fl!("compute_stopped_by_user")); } else { //let information = ff.get_information(); let text_messages = ff.get_text_messages(); let base_videos_size = ff.get_similar_videos().len(); - entry_info.set_text(format!("Found similar videos for {} videos.", base_videos_size).as_str()); + entry_info.set_text(format!("{} {} {} {}.", fl!("compute_found"), fl!("compute_duplicates_for"), base_videos_size, fl!("compute_similar_videos")).as_str()); // Create GUI { @@ -640,14 +677,14 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< } Message::SameMusic(mf) => { if mf.get_stopped_search() { - entry_info.set_text("Searching for same music was stopped by user"); + entry_info.set_text(&fl!("compute_stopped_by_user")); } else { let information = mf.get_information(); let text_messages = mf.get_text_messages(); let same_music_number: usize = information.number_of_duplicates_music_files; - entry_info.set_text(format!("Found {} duplicated music files.", same_music_number).as_str()); + entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), same_music_number, fl!("compute_music_files")).as_str()); // Create GUI { @@ -763,14 +800,14 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< } Message::InvalidSymlinks(ifs) => { if ifs.get_stopped_search() { - entry_info.set_text("Searching for invalid symlink was stopped by user"); + entry_info.set_text(&fl!("compute_stopped_by_user")); } else { let information = ifs.get_information(); let text_messages = ifs.get_text_messages(); let invalid_symlinks: usize = information.number_of_invalid_symlinks; - entry_info.set_text(format!("Found {} invalid symlinks.", invalid_symlinks).as_str()); + entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), invalid_symlinks, fl!("compute_symlinks")).as_str()); // Create GUI { @@ -814,14 +851,14 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< } Message::BrokenFiles(br) => { if br.get_stopped_search() { - entry_info.set_text("Searching for broken files was stopped by user"); + entry_info.set_text(&fl!("compute_stopped_by_user")); } else { let information = br.get_information(); let text_messages = br.get_text_messages(); let broken_files_number: usize = information.number_of_broken_files; - entry_info.set_text(format!("Found {} broken files.", broken_files_number).as_str()); + entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), broken_files_number, fl!("compute_broken_files")).as_str()); // Create GUI { diff --git a/czkawka_gui/src/connect_button_delete.rs b/czkawka_gui/src/connect_button_delete.rs index 8399cfa..4df9591 100644 --- a/czkawka_gui/src/connect_button_delete.rs +++ b/czkawka_gui/src/connect_button_delete.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::fs; use std::fs::Metadata; +use czkawka_core::fl; use gtk::prelude::*; use gtk::{Align, CheckButton, Dialog, ResponseType, TextView}; @@ -103,7 +104,7 @@ pub async fn check_if_can_delete_files(check_button_settings_confirm_deletion: & fn create_dialog_ask_for_deletion(window_main: >k::Window) -> (Dialog, CheckButton) { let dialog = gtk::Dialog::builder().title("Delete confirmation").transient_for(window_main).modal(true).build(); let button_ok = dialog.add_button("Ok", ResponseType::Ok); - dialog.add_button("Close", ResponseType::Cancel); + dialog.add_button(&fl!("general_close_button"), ResponseType::Cancel); let label: gtk::Label = gtk::Label::new(Some("Are you sure that you want to delete files?")); let check_button: gtk::CheckButton = gtk::CheckButton::with_label("Ask next time"); @@ -123,7 +124,7 @@ fn create_dialog_ask_for_deletion(window_main: >k::Window) -> (Dialog, CheckBu fn create_dialog_group_deletion(window_main: >k::Window) -> (Dialog, CheckButton) { let dialog = gtk::Dialog::builder().title("Confirmation of deleting all files in group").transient_for(window_main).modal(true).build(); let button_ok = dialog.add_button("Ok", ResponseType::Ok); - dialog.add_button("Close", ResponseType::Cancel); + dialog.add_button(&fl!("general_close_button"), ResponseType::Cancel); let label: gtk::Label = gtk::Label::new(Some("In some groups there are selected all records.")); let label2: gtk::Label = gtk::Label::new(Some("Are you sure that you want to delete them?")); diff --git a/czkawka_gui/src/connect_button_hardlink.rs b/czkawka_gui/src/connect_button_hardlink.rs index 0564e48..fb1dd9c 100644 --- a/czkawka_gui/src/connect_button_hardlink.rs +++ b/czkawka_gui/src/connect_button_hardlink.rs @@ -5,6 +5,7 @@ use gtk::prelude::*; use gtk::{Align, CheckButton, Dialog, ResponseType, TextView, TreeIter, TreePath}; use czkawka_core::duplicate::make_hard_link; +use czkawka_core::fl; use crate::gui_data::GuiData; use crate::help_functions::*; @@ -220,7 +221,7 @@ pub fn hardlink_symlink(tree_view: >k::TreeView, column_file_name: i32, column fn create_dialog_non_group(window_main: >k::Window) -> Dialog { let dialog = gtk::Dialog::builder().title("Invalid selection with some groups").transient_for(window_main).modal(true).build(); let button_ok = dialog.add_button("Ok", ResponseType::Ok); - dialog.add_button("Close", ResponseType::Cancel); + dialog.add_button(&fl!("general_close_button"), ResponseType::Cancel); let label: gtk::Label = gtk::Label::new(Some("In some groups there is only 1 record selected and it will be ignored.")); let label2: gtk::Label = gtk::Label::new(Some("To be able to hard/sym link this files, at least 2 results in group needs to be selected.")); @@ -324,7 +325,7 @@ pub async fn check_if_can_link_files(check_button_settings_confirm_link: >k::C fn create_dialog_ask_for_linking(window_main: >k::Window) -> (Dialog, CheckButton) { let dialog = gtk::Dialog::builder().title("Link confirmation").transient_for(window_main).modal(true).build(); let button_ok = dialog.add_button("Ok", ResponseType::Ok); - dialog.add_button("Close", ResponseType::Cancel); + dialog.add_button(&fl!("general_close_button"), ResponseType::Cancel); let label: gtk::Label = gtk::Label::new(Some("Are you sure that you want to link this files?")); let check_button: gtk::CheckButton = gtk::CheckButton::with_label("Ask next time"); diff --git a/czkawka_gui/src/connect_button_move.rs b/czkawka_gui/src/connect_button_move.rs index 0ebd904..fbcaa69 100644 --- a/czkawka_gui/src/connect_button_move.rs +++ b/czkawka_gui/src/connect_button_move.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use czkawka_core::fl; use gtk::prelude::*; use gtk::{ResponseType, TreePath}; @@ -64,7 +65,7 @@ fn move_things(tree_view: >k::TreeView, column_file_name: i32, column_path: i3 .modal(true) .build(); chooser.add_button("Ok", ResponseType::Ok); - chooser.add_button("Close", ResponseType::Cancel); + chooser.add_button(&fl!("general_close_button"), ResponseType::Cancel); chooser.set_select_multiple(false); chooser.show_all(); diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index 88f5652..f2782b4 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -6,6 +6,7 @@ use glib::Sender; use gtk::prelude::*; use img_hash::{FilterType, HashAlg}; +use crate::fl; use czkawka_core::big_file::BigFile; use czkawka_core::broken_files::BrokenFiles; use czkawka_core::duplicate::{DuplicateFinder, HashType}; @@ -137,7 +138,7 @@ pub fn connect_button_search( button_settings.set_sensitive(false); button_app_info.set_sensitive(false); - entry_info.set_text("Searching data, it may take a while, please wait..."); + entry_info.set_text(&fl!("searching_for_data")); // Resets progress bars progress_bar_all_stages.set_fraction(0 as f64); diff --git a/czkawka_gui/src/connect_change_language.rs b/czkawka_gui/src/connect_change_language.rs new file mode 100644 index 0000000..7ff0bb7 --- /dev/null +++ b/czkawka_gui/src/connect_change_language.rs @@ -0,0 +1,46 @@ +use crate::language_functions::get_language_from_combo_box_text; +use crate::GuiData; +use gtk::prelude::*; +use i18n_embed::unic_langid::LanguageIdentifier; +// use i18n_embed::{DesktopLanguageRequester, Localizer}; + +pub fn connect_change_language(gui_data: &GuiData) { + change_language(gui_data); + + let combo_box_settings_language = gui_data.settings.combo_box_settings_language.clone(); + let gui_data = gui_data.clone(); + combo_box_settings_language.connect_changed(move |_| { + change_language(&gui_data); + }); +} + +fn change_language(gui_data: &GuiData) { + let localizers = vec![("czkawka_gui", czkawka_core::localizer::localizer())]; + + let lang_short = get_language_from_combo_box_text(gui_data.settings.combo_box_settings_language.active_text().unwrap().to_string()).short_text; + + let lang_identifier = vec![LanguageIdentifier::from_bytes(lang_short.as_bytes()).unwrap()]; + // let available_languages = Localizer::available_languages(); + // println!("{:?}", available_languages); + for (lib, localizer) in localizers { + if let Err(error) = localizer.select(&lang_identifier) { + eprintln!("Error while loadings languages for {} {:?}", lib, error); + } + } + gui_data.update_language(); + + // Try to use default OS + // let requested_languages = DesktopLanguageRequester::requested_languages(); + // let localizers = vec![("czkawka_gui", crate::localizer::localizer())]; + // + // println!("Requested Languages{:?}", requested_languages); + // + // let lang_identifier = LanguageIdentifier::from_bytes("pl".as_bytes()); + // // let available_languages = Localizer::available_languages(); + // // println!("{:?}", available_languages); + // for (lib, localizer) in localizers { + // if let Err(error) = localizer.select(&requested_languages) { + // eprintln!("Error while loadings languages for {} {:?}", lib, error); + // } + // } +} diff --git a/czkawka_gui/src/connect_notebook_tabs.rs b/czkawka_gui/src/connect_notebook_tabs.rs index 773b450..f9d2271 100644 --- a/czkawka_gui/src/connect_notebook_tabs.rs +++ b/czkawka_gui/src/connect_notebook_tabs.rs @@ -9,24 +9,11 @@ pub fn connect_notebook_tabs(gui_data: &GuiData) { let buttons_array = gui_data.bottom_buttons.buttons_array.clone(); let notebook_main_clone = gui_data.main_notebook.notebook_main.clone(); let buttons_names = gui_data.bottom_buttons.buttons_names.clone(); - let shared_upper_notebooks = gui_data.shared_upper_notebooks.clone(); - let notebook_upper = gui_data.upper_notebook.notebook_upper.clone(); notebook_main_clone.connect_switch_page(move |_, _, number| { let current_tab_in_main_notebook = to_notebook_main_enum(number); // Buttons set_buttons(&mut *shared_buttons.borrow_mut().get_mut(¤t_tab_in_main_notebook).unwrap(), &buttons_array, &buttons_names); - - // Upper notebook - { - for (index, upper_tab) in get_all_upper_tabs().iter().enumerate() { - if *shared_upper_notebooks.borrow_mut().get_mut(¤t_tab_in_main_notebook).unwrap().get_mut(upper_tab).unwrap() { - notebook_upper.children().get(index).unwrap().show(); // TODO find alternative for children - } else { - notebook_upper.children().get(index).unwrap().hide(); - } - } - } }); } diff --git a/czkawka_gui/src/connect_popovers.rs b/czkawka_gui/src/connect_popovers.rs index c70f896..6f12d44 100644 --- a/czkawka_gui/src/connect_popovers.rs +++ b/czkawka_gui/src/connect_popovers.rs @@ -2,6 +2,7 @@ use gtk::prelude::*; use gtk::{ResponseType, TreeIter, Window}; use regex::Regex; +use crate::fl; use czkawka_core::common::Common; use crate::gui_data::GuiData; @@ -218,21 +219,21 @@ fn popover_custom_select_unselect(popover: >k::Popover, window_main: &Window, popover.popdown(); let window_title = match select_things { - false => "Unselect Custom", - true => "Select Custom", + false => fl!("popover_custom_mode_unselect"), + true => fl!("popover_custom_mode_select"), }; // Dialog for select/unselect items { - let dialog = gtk::Dialog::builder().title(window_title).transient_for(window_main).modal(true).build(); - dialog.add_button("Ok", ResponseType::Ok); - dialog.add_button("Close", ResponseType::Cancel); + let dialog = gtk::Dialog::builder().title(&window_title).transient_for(window_main).modal(true).build(); + dialog.add_button(&fl!("general_ok_button"), ResponseType::Ok); + dialog.add_button(&fl!("general_close_button"), ResponseType::Cancel); - let check_button_path = gtk::CheckButton::builder().label("Path").build(); - let check_button_name = gtk::CheckButton::builder().label("Name").build(); - let check_button_rust_regex = gtk::CheckButton::builder().label("Regex Path + Name").build(); + let check_button_path = gtk::CheckButton::builder().label(&fl!("popover_custom_regex_path_label")).build(); + let check_button_name = gtk::CheckButton::builder().label(&fl!("popover_custom_regex_name_label")).build(); + let check_button_rust_regex = gtk::CheckButton::builder().label(&fl!("popover_custom_regex_regex_label")).build(); - let check_button_select_not_all_results = gtk::CheckButton::builder().label("Don't select all records in group").build(); + let check_button_select_not_all_results = gtk::CheckButton::builder().label(&fl!("popover_custom_all_in_group_label")).build(); check_button_select_not_all_results.set_active(true); let entry_path = gtk::Entry::new(); @@ -244,21 +245,16 @@ fn popover_custom_select_unselect(popover: >k::Popover, window_main: &Window, // Tooltips { - let tooltip_path = "Allows to select records by its path.\n\nExample usage:\n/home/pimpek/rzecz.txt can be found with /home/pim*"; - let tooltip_name = "Allows to select records by file names.\n\nExample usage:\n/usr/ping/pong.txt can be found with *ong*"; - let tooltip_regex = "Allows to select records by specified Regex.\n\nWith this mode, searched text is Path with Name\n\nExample usage:\n/usr/bin/ziemniak.txt can be found with /ziem[a-z]+\n\nThis use default Rust regex implementation, so you can read more about it in https://docs.rs/regex."; - let tooltip_group_button = "Prevents from selecting all records in group.\n\n This is enabled by default, because in most of situations user don't want to delete both original and duplicates files, but want to leave at least one file.\n\nWarning: This setting don't work if already user selected all results in group manually."; + check_button_path.set_tooltip_text(Some(&fl!("popover_custom_path_check_button_entry_tooltip"))); + entry_path.set_tooltip_text(Some(&fl!("popover_custom_path_check_button_entry_tooltip"))); - check_button_path.set_tooltip_text(Some(tooltip_path)); - entry_path.set_tooltip_text(Some(tooltip_path)); + check_button_name.set_tooltip_text(Some(&fl!("popover_custom_name_check_button_entry_tooltip"))); + entry_name.set_tooltip_text(Some(&fl!("popover_custom_name_check_button_entry_tooltip"))); - check_button_name.set_tooltip_text(Some(tooltip_name)); - entry_name.set_tooltip_text(Some(tooltip_name)); + check_button_rust_regex.set_tooltip_text(Some(&fl!("popover_custom_regex_check_button_entry_tooltip"))); + entry_rust_regex.set_tooltip_text(Some(&fl!("popover_custom_regex_check_button_entry_tooltip"))); - check_button_rust_regex.set_tooltip_text(Some(tooltip_regex)); - entry_rust_regex.set_tooltip_text(Some(tooltip_regex)); - - check_button_select_not_all_results.set_tooltip_text(Some(tooltip_group_button)); + check_button_select_not_all_results.set_tooltip_text(Some(&fl!("popover_custom_not_all_check_button_tooltip"))); } { let label_regex_valid = label_regex_valid.clone(); @@ -266,11 +262,11 @@ fn popover_custom_select_unselect(popover: >k::Popover, window_main: &Window, let message; let text_to_check = entry_rust_regex.text().to_string(); if text_to_check.is_empty() { - message = ""; + message = "".to_string(); } else { match Regex::new(&text_to_check) { - Ok(_) => message = "Regex is valid", - Err(_) => message = "Regex is invalid", + Ok(_) => message = fl!("popover_valid_regex"), + Err(_) => message = fl!("popover_invalid_regex"), } } @@ -280,7 +276,7 @@ fn popover_custom_select_unselect(popover: >k::Popover, window_main: &Window, // let attribute = PangoAttrFontDesc { attr }; // attributes_list.insert(attribute); // label_regex_valid.set_attributes(Some(&attributes_list)); - label_regex_valid.set_text(message); + label_regex_valid.set_text(&message); }); } diff --git a/czkawka_gui/src/connect_progress_window.rs b/czkawka_gui/src/connect_progress_window.rs index 5a2ae50..af6e265 100644 --- a/czkawka_gui/src/connect_progress_window.rs +++ b/czkawka_gui/src/connect_progress_window.rs @@ -1,6 +1,7 @@ use futures::StreamExt; use gtk::prelude::*; +use crate::fl; use czkawka_core::{big_file, broken_files, duplicate, empty_files, empty_folder, invalid_symlinks, same_music, similar_images, similar_videos, temporary}; use crate::gui_data::GuiData; @@ -41,7 +42,7 @@ pub fn connect_progress_window( progress_bar_current_stage.hide(); // progress_bar_all_stages.hide(); progress_bar_all_stages.set_fraction(0 as f64); - label_stage.set_text(format!("Scanned size of {} files", item.files_checked).as_str()); + label_stage.set_text(format!("{} {} {} {}", fl!("progress_scanned"), fl!("progress_size"), item.files_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } // Hash - first 1KB file @@ -57,9 +58,9 @@ pub fn connect_progress_window( progress_bar_current_stage.set_fraction(0f64); taskbar_state.borrow().set_progress_value(1, 1 + item.max_stage as u64); } - label_stage.set_text(format!("Analyzed partial hash of {}/{} files", item.files_checked, item.files_to_check).as_str()); + label_stage.set_text(format!("{} {}/{} {}", fl!("progress_analyzed_partial_hash"), item.files_checked, item.files_to_check, fl!("progress_files")).as_str()); } - // Hash - first 1MB of file or normal hash + // Hash - normal hash 2 => { if item.files_to_check != 0 { progress_bar_all_stages.set_fraction((2f64 + (item.files_checked) as f64 / item.files_to_check as f64) / (item.max_stage + 1) as f64); @@ -73,11 +74,7 @@ pub fn connect_progress_window( taskbar_state.borrow().set_progress_value(2, 1 + item.max_stage as u64); } - if item.checking_method == duplicate::CheckingMethod::Hash { - label_stage.set_text(format!("Analyzed full hash of {}/{} files", item.files_checked, item.files_to_check).as_str()); - } else { - label_stage.set_text(format!("Analyzed hash of {}/{} files", item.files_checked, item.files_to_check).as_str()); - } + label_stage.set_text(format!("{} {}/{} {}", fl!("progress_analyzed_full_hash"), item.files_checked, item.files_to_check, fl!("progress_files")).as_str()); } _ => { panic!("Not available current_stage"); @@ -88,14 +85,14 @@ pub fn connect_progress_window( label_stage.show(); grid_progress_stages.hide(); - label_stage.set_text(format!("Scanned name of {} files", item.files_checked).as_str()); + label_stage.set_text(format!("{} {} {} {}", fl!("progress_scanned"), fl!("progress_name"), item.files_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } duplicate::CheckingMethod::Size => { label_stage.show(); grid_progress_stages.hide(); - label_stage.set_text(format!("Scanned size {} files", item.files_checked).as_str()); + label_stage.set_text(format!("{} {} {} {}", fl!("progress_scanned"), fl!("progress_size"), item.files_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } duplicate::CheckingMethod::None => { @@ -112,7 +109,7 @@ pub fn connect_progress_window( let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_empty_files.next().await { - label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + label_stage.set_text(format!("{} {} {}", fl!("progress_scanned"), item.files_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; @@ -124,7 +121,7 @@ pub fn connect_progress_window( let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_empty_folder.next().await { - label_stage.set_text(format!("Scanned {} folders", item.folders_checked).as_str()); + label_stage.set_text(format!("{} {} {}", fl!("progress_scanned"), item.folders_checked, fl!("progress_folders")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; @@ -136,7 +133,7 @@ pub fn connect_progress_window( let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_big_files.next().await { - label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + label_stage.set_text(format!("{} {} {}", fl!("progress_scanned"), item.files_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; @@ -153,7 +150,7 @@ pub fn connect_progress_window( match item.current_stage { 0 => { progress_bar_current_stage.hide(); - label_stage.set_text(format!("Scanned {} files", item.music_checked).as_str()); + label_stage.set_text(format!("{} {} {}", fl!("progress_scanned"), item.music_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { @@ -167,7 +164,7 @@ pub fn connect_progress_window( progress_bar_current_stage.set_fraction(0f64); taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64); } - label_stage.set_text(format!("Reading tags of {}/{} music files", item.music_checked, item.music_to_check).as_str()); + label_stage.set_text(format!("{} {}/{} {}", fl!("progress_tags"), item.music_checked, item.music_to_check, fl!("progress_files")).as_str()); } 2 => { if item.music_to_check != 0 { @@ -181,7 +178,7 @@ pub fn connect_progress_window( progress_bar_current_stage.set_fraction(0f64); taskbar_state.borrow().set_progress_value(2, (item.max_stage + 1) as u64); } - label_stage.set_text(format!("Checking for duplicates of {}/{} music files", item.music_checked, item.music_to_check).as_str()); + label_stage.set_text(format!("{} {}/{} {}", fl!("progress_checking"), item.music_checked, item.music_to_check, fl!("progress_files")).as_str()); } _ => { panic!(); @@ -202,7 +199,7 @@ pub fn connect_progress_window( match item.current_stage { 0 => { progress_bar_current_stage.hide(); - label_stage.set_text(format!("Scanned {} files", item.images_checked).as_str()); + label_stage.set_text(format!("{} {} {}", fl!("progress_scanned"), item.images_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { @@ -218,7 +215,7 @@ pub fn connect_progress_window( progress_bar_current_stage.set_fraction(0f64); taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64); } - label_stage.set_text(format!("Hashing {}/{} image", item.images_checked, item.images_to_check).as_str()); + label_stage.set_text(format!("{} {}/{} {}", fl!("progress_hashing"), item.images_checked, item.images_to_check, fl!("progress_files")).as_str()); } _ => { panic!(); @@ -239,7 +236,7 @@ pub fn connect_progress_window( match item.current_stage { 0 => { progress_bar_current_stage.hide(); - label_stage.set_text(format!("Scanned {} files", item.videos_checked).as_str()); + label_stage.set_text(format!("{} {} {}", fl!("progress_scanned"), item.videos_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { @@ -255,7 +252,7 @@ pub fn connect_progress_window( progress_bar_current_stage.set_fraction(0f64); taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64); } - label_stage.set_text(format!("Hashing {}/{} video", item.videos_checked, item.videos_to_check).as_str()); + label_stage.set_text(format!("{} {}/{} {}", fl!("progress_hashing"), item.videos_checked, item.videos_to_check, fl!("progress_files")).as_str()); } _ => { panic!(); @@ -271,7 +268,7 @@ pub fn connect_progress_window( let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_temporary.next().await { - label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + label_stage.set_text(format!("{} {} {}", fl!("progress_scanned"), item.files_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; @@ -283,7 +280,7 @@ pub fn connect_progress_window( let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_invalid_symlinks.next().await { - label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + label_stage.set_text(format!("{} {} {}", fl!("progress_scanned"), item.files_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; @@ -300,7 +297,7 @@ pub fn connect_progress_window( match item.current_stage { 0 => { progress_bar_current_stage.hide(); - label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + label_stage.set_text(format!("{} {} {}", fl!("progress_scanned"), item.files_checked, fl!("progress_files")).as_str()); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { @@ -314,7 +311,7 @@ pub fn connect_progress_window( progress_bar_current_stage.set_fraction(0f64); taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64); } - label_stage.set_text(format!("Checking {}/{} files", item.files_checked, item.files_to_check).as_str()); + label_stage.set_text(format!("{} {}/{} {}", fl!("progress_checking"), item.files_checked, item.files_to_check, fl!("progress_files")).as_str()); } _ => { panic!(); diff --git a/czkawka_gui/src/connect_selection_of_directories.rs b/czkawka_gui/src/connect_selection_of_directories.rs index 6834792..214660e 100644 --- a/czkawka_gui/src/connect_selection_of_directories.rs +++ b/czkawka_gui/src/connect_selection_of_directories.rs @@ -3,6 +3,7 @@ use gtk::{ResponseType, TreeView, Window}; #[cfg(target_family = "windows")] use czkawka_core::common::Common; +use czkawka_core::fl; use crate::gui_data::GuiData; use crate::help_functions::{get_dialog_box_child, get_list_store, ColumnsDirectory}; @@ -12,8 +13,8 @@ pub fn connect_selection_of_directories(gui_data: &GuiData) { { let tree_view_included_directories = gui_data.upper_notebook.tree_view_included_directories.clone(); let window_main = gui_data.window_main.clone(); - let buttons_manual_add_directory = gui_data.upper_notebook.buttons_manual_add_directory.clone(); - buttons_manual_add_directory.connect_clicked(move |_| { + let buttons_manual_add_included_directory = gui_data.upper_notebook.buttons_manual_add_included_directory.clone(); + buttons_manual_add_included_directory.connect_clicked(move |_| { add_manually_directories(&window_main, &tree_view_included_directories); }); } @@ -81,7 +82,7 @@ fn add_chosen_directories(window_main: &Window, tree_view: &TreeView, excluded_i let chooser = gtk::FileChooserDialog::builder().title(folders_to).action(gtk::FileChooserAction::SelectFolder).transient_for(window_main).modal(true).build(); chooser.add_button("Ok", ResponseType::Ok); - chooser.add_button("Close", ResponseType::Cancel); + chooser.add_button(&fl!("general_close_button"), ResponseType::Cancel); chooser.set_select_multiple(true); chooser.show_all(); @@ -105,7 +106,7 @@ fn add_chosen_directories(window_main: &Window, tree_view: &TreeView, excluded_i fn add_manually_directories(window_main: &Window, tree_view: &TreeView) { let dialog = gtk::Dialog::builder().title("Add directory manually").transient_for(window_main).modal(true).build(); dialog.add_button("Ok", ResponseType::Ok); - dialog.add_button("Close", ResponseType::Cancel); + dialog.add_button(&fl!("general_close_button"), ResponseType::Cancel); let entry: gtk::Entry = gtk::Entry::new(); diff --git a/czkawka_gui/src/gui_about.rs b/czkawka_gui/src/gui_about.rs index 3341f32..63b26b1 100644 --- a/czkawka_gui/src/gui_about.rs +++ b/czkawka_gui/src/gui_about.rs @@ -1,3 +1,4 @@ +use crate::fl; use gtk::prelude::*; use gtk::{Builder, Window}; @@ -20,11 +21,8 @@ impl GuiAbout { about_dialog.set_transient_for(Some(window_main)); let button_repository: gtk::Button = builder.object("button_repository").unwrap(); - button_repository.set_tooltip_text(Some("Link to repository page with source code.")); let button_donation: gtk::Button = builder.object("button_donation").unwrap(); - button_donation.set_tooltip_text(Some("Link to donation page.")); let button_instruction: gtk::Button = builder.object("button_instruction").unwrap(); - button_instruction.set_tooltip_text(Some("Link to instruction page.")); Self { about_dialog, @@ -33,4 +31,13 @@ impl GuiAbout { button_instruction, } } + pub fn update_language(&self) { + self.button_repository.set_tooltip_text(Some(&fl!("about_repository_button_tooltip"))); + self.button_donation.set_tooltip_text(Some(&fl!("about_donation_button_tooltip"))); + self.button_instruction.set_tooltip_text(Some(&fl!("about_instruction_button_tooltip"))); + + self.button_repository.set_label(&fl!("about_repository_button")); + self.button_donation.set_label(&fl!("about_donation_button")); + self.button_instruction.set_label(&fl!("about_instruction_button")); + } } diff --git a/czkawka_gui/src/gui_bottom_buttons.rs b/czkawka_gui/src/gui_bottom_buttons.rs index e923f5d..fa18021 100644 --- a/czkawka_gui/src/gui_bottom_buttons.rs +++ b/czkawka_gui/src/gui_bottom_buttons.rs @@ -1,5 +1,7 @@ +use crate::fl; +use crate::help_functions::get_custom_label_from_label_with_image; use gtk::prelude::*; -use gtk::Widget; +use gtk::{Bin, Widget}; #[derive(Clone)] pub struct GuiBottomButtons { @@ -26,22 +28,8 @@ impl GuiBottomButtons { let buttons_hardlink: gtk::Button = builder.object("buttons_hardlink").unwrap(); let buttons_move: gtk::Button = builder.object("buttons_move").unwrap(); - buttons_search.set_tooltip_text(Some("Start to search for files/folders")); - buttons_select.set_tooltip_text(Some("Selects records\nOnly selected files/folders can be later processed.")); - buttons_delete.set_tooltip_text(Some("Delete selected files/folders")); - buttons_save.set_tooltip_text(Some("Save data about search to file")); - buttons_symlink.set_tooltip_text(Some( - "Creates symbolic links\nOnly works when at least 2 results in group are selected\nFirst is unchanged and second and later are symlinked to first", - )); - buttons_hardlink.set_tooltip_text(Some("Creates hardlinks\nOnly works when at least 2 results in group are selected\nFirst is unchanged and second and later are hardlinked to first")); - buttons_move.set_tooltip_text(Some( - "Moves files to chosen folder\nIt copy all files to folder without preserving directory tree\nWhen trying to move 2 files with identical name to folder, second will fail and show error", - )); - let buttons_show_errors: gtk::Button = builder.object("buttons_show_errors").unwrap(); - buttons_show_errors.set_tooltip_text(Some("Show/Hide bottom error panel.")); let buttons_show_upper_notebook: gtk::Button = builder.object("buttons_show_upper_notebook").unwrap(); - buttons_show_upper_notebook.set_tooltip_text(Some("Show/Hide upper notebook panel.")); let buttons_names = [ "search".to_string(), @@ -78,4 +66,24 @@ impl GuiBottomButtons { buttons_array, } } + pub fn update_language(&self) { + get_custom_label_from_label_with_image(&self.buttons_search.clone().upcast::()).set_text(&fl!("bottom_search_button")); + get_custom_label_from_label_with_image(&self.buttons_select.clone().upcast::()).set_text(&fl!("bottom_select_button")); + get_custom_label_from_label_with_image(&self.buttons_delete.clone().upcast::()).set_text(&fl!("bottom_delete_button")); + get_custom_label_from_label_with_image(&self.buttons_save.clone().upcast::()).set_text(&fl!("bottom_save_button")); + get_custom_label_from_label_with_image(&self.buttons_symlink.clone().upcast::()).set_text(&fl!("bottom_symlink_button")); + get_custom_label_from_label_with_image(&self.buttons_hardlink.clone().upcast::()).set_text(&fl!("bottom_hardlink_button")); + get_custom_label_from_label_with_image(&self.buttons_move.clone().upcast::()).set_text(&fl!("bottom_move_button")); + + self.buttons_search.set_tooltip_text(Some(&fl!("bottom_search_button_tooltip"))); + self.buttons_select.set_tooltip_text(Some(&fl!("bottom_select_button_tooltip"))); + self.buttons_delete.set_tooltip_text(Some(&fl!("bottom_delete_button_tooltip"))); + self.buttons_save.set_tooltip_text(Some(&fl!("bottom_save_button_tooltip"))); + self.buttons_symlink.set_tooltip_text(Some(&fl!("bottom_symlink_button_tooltip"))); + self.buttons_hardlink.set_tooltip_text(Some(&fl!("bottom_hardlink_button_tooltip"))); + self.buttons_move.set_tooltip_text(Some(&fl!("bottom_move_button_tooltip"))); + + self.buttons_show_errors.set_tooltip_text(Some(&fl!("bottom_show_errors_tooltip"))); + self.buttons_show_upper_notebook.set_tooltip_text(Some(&fl!("bottom_show_upper_notebook_tooltip"))); + } } diff --git a/czkawka_gui/src/gui_data.rs b/czkawka_gui/src/gui_data.rs index 240bf86..350ae1e 100644 --- a/czkawka_gui/src/gui_data.rs +++ b/czkawka_gui/src/gui_data.rs @@ -24,7 +24,7 @@ use crate::gui_main_notebook::GuiMainNotebook; use crate::gui_popovers::GuiPopovers; use crate::gui_progress_dialog::GuiProgressDialog; use crate::gui_settings::GuiSettings; -use crate::gui_upper_notepad::GuiUpperNotebook; +use crate::gui_upper_notebook::GuiUpperNotebook; use crate::notebook_enums::*; use crate::taskbar_progress::TaskbarProgress; @@ -52,9 +52,6 @@ pub struct GuiData { // Buttons state pub shared_buttons: Rc>>>, - // Upper Notebook state - pub shared_upper_notebooks: Rc>>>, - // State of search results pub shared_duplication_state: Rc>, pub shared_empty_folders_state: Rc>, @@ -91,6 +88,7 @@ impl GuiData { let window_main: gtk::Window = builder.object("window_main").unwrap(); window_main.show_all(); window_main.set_title("Czkawka"); + window_main.set_application(Some(application)); let main_notebook = GuiMainNotebook::create_from_builder(&builder); @@ -123,19 +121,6 @@ impl GuiData { shared_buttons.borrow_mut().insert(i.clone(), temp_hashmap); } - // Upper Notebook state - let shared_upper_notebooks: Rc> = Rc::new(RefCell::new(HashMap::>::new())); - - for i in get_all_main_tabs().iter() { - let mut temp_hashmap: HashMap = Default::default(); - for j in get_all_upper_tabs().iter() { - temp_hashmap.insert(j.clone(), true); - } - shared_upper_notebooks.borrow_mut().insert(i.clone(), temp_hashmap); - } - // Some upper notebook tabs are disabled - *shared_upper_notebooks.borrow_mut().get_mut(&NotebookMainEnum::Temporary).unwrap().get_mut(&NotebookUpperEnum::AllowedExtensions).unwrap() = false; - // State of search results let shared_duplication_state: Rc> = Rc::new(RefCell::new(DuplicateFinder::new())); @@ -176,7 +161,6 @@ impl GuiData { header, taskbar_state, shared_buttons, - shared_upper_notebooks, shared_duplication_state, shared_empty_folders_state, shared_empty_files_state, @@ -195,4 +179,15 @@ impl GuiData { stop_receiver, } } + + pub fn update_language(&self) { + self.main_notebook.update_language(); + self.upper_notebook.update_language(); + self.popovers.update_language(); + self.bottom_buttons.update_language(); + self.progress_window.update_language(); + self.about.update_language(); + self.header.update_language(); + self.settings.update_language(); + } } diff --git a/czkawka_gui/src/gui_header.rs b/czkawka_gui/src/gui_header.rs index c41ddff..f9bd50b 100644 --- a/czkawka_gui/src/gui_header.rs +++ b/czkawka_gui/src/gui_header.rs @@ -1,3 +1,4 @@ +use crate::fl; use gtk::prelude::*; #[derive(Clone)] @@ -11,9 +12,11 @@ impl GuiHeader { let button_settings: gtk::Button = builder.object("button_settings").unwrap(); let button_app_info: gtk::Button = builder.object("button_app_info").unwrap(); - button_settings.set_tooltip_text(Some("Opens settings dialog")); - button_app_info.set_tooltip_text(Some("Opens dialog with info about app")); - Self { button_settings, button_app_info } } + + pub fn update_language(&self) { + self.button_settings.set_tooltip_text(Some(&fl!("header_setting_button_tooltip"))); + self.button_app_info.set_tooltip_text(Some(&fl!("header_about_button_tooltip"))); + } } diff --git a/czkawka_gui/src/gui_main_notebook.rs b/czkawka_gui/src/gui_main_notebook.rs index 5497fa7..3f0171a 100644 --- a/czkawka_gui/src/gui_main_notebook.rs +++ b/czkawka_gui/src/gui_main_notebook.rs @@ -1,7 +1,8 @@ +use crate::fl; use gtk::prelude::*; use gtk::{EventControllerKey, TreeView}; -use crate::notebook_enums::NUMBER_OF_NOTEBOOK_MAIN_TABS; +use crate::notebook_enums::{NotebookMainEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS}; #[derive(Clone)] pub struct GuiMainNotebook { @@ -93,6 +94,30 @@ pub struct GuiMainNotebook { pub label_similar_images_minimal_similarity: gtk::Label, + pub label_duplicate_check_method: gtk::Label, + pub label_duplicate_hash_type: gtk::Label, + pub label_duplicate_size_bytes: gtk::Label, + pub label_duplicate_min_size: gtk::Label, + pub label_duplicate_max_size: gtk::Label, + pub label_big_shown_files: gtk::Label, + pub label_image_resize_algorithm: gtk::Label, + pub label_image_hash_type: gtk::Label, + pub label_image_hash_size: gtk::Label, + pub label_image_size_bytes: gtk::Label, + pub label_image_min_size: gtk::Label, + pub label_image_max_size: gtk::Label, + pub label_image_similarity: gtk::Label, + pub label_image_similarity_max: gtk::Label, + pub label_video_similarity: gtk::Label, + pub label_video_similarity_min: gtk::Label, + pub label_video_similarity_max: gtk::Label, + pub label_video_size_bytes: gtk::Label, + pub label_video_min_size: gtk::Label, + pub label_video_max_size: gtk::Label, + pub label_music_size_bytes: gtk::Label, + pub label_music_min_size: gtk::Label, + pub label_music_max_size: gtk::Label, + pub image_preview_similar_images: gtk::Image, pub image_preview_duplicates: gtk::Image, } @@ -181,12 +206,6 @@ impl GuiMainNotebook { let radio_button_duplicates_size: gtk::RadioButton = builder.object("radio_button_duplicates_size").unwrap(); let radio_button_duplicates_hash: gtk::RadioButton = builder.object("radio_button_duplicates_hash").unwrap(); - radio_button_duplicates_name.set_tooltip_text(Some("Finds files which have same name.\n\nThis mode not checking what file contain inside, so be carefully when using it.")); - radio_button_duplicates_size.set_tooltip_text(Some("Finds files which have same size.\n\nThis mode not checking what file contain inside, so be carefully when using it.")); - radio_button_duplicates_hash.set_tooltip_text(Some( - "Finds files which have the same content.\n\nThis mode hashes file and later compare this hashes to find duplicates.\n\nTool heavily uses cache, so second and further scans of same data should be a lot of faster that first.", - )); - let scale_similarity_similar_images: gtk::Scale = builder.object("scale_similarity_similar_images").unwrap(); let scale_similarity_similar_videos: gtk::Scale = builder.object("scale_similarity_similar_videos").unwrap(); @@ -194,10 +213,6 @@ impl GuiMainNotebook { let radio_button_hash_type_crc32: gtk::RadioButton = builder.object("radio_button_hash_type_crc32").unwrap(); let radio_button_hash_type_xxh3: gtk::RadioButton = builder.object("radio_button_hash_type_xxh3").unwrap(); - radio_button_hash_type_blake3.set_tooltip_text(Some("Blake3 is cryptographic hash function. It is used as default hash algorithm, because it is very fast.")); - radio_button_hash_type_crc32.set_tooltip_text(Some("CRC32 is simple hash function. It should be faster than Blake3, but probably may have very rarely some collisions.")); - radio_button_hash_type_xxh3.set_tooltip_text(Some("XXH3 is very similar in case of performance and hash quality to Blake3, so such modes can be easily used .")); - let radio_button_resize_algorithm_lanczos3: gtk::RadioButton = builder.object("radio_button_resize_algorithm_lanczos3").unwrap(); let radio_button_resize_algorithm_nearest: gtk::RadioButton = builder.object("radio_button_resize_algorithm_nearest").unwrap(); let radio_button_resize_algorithm_triangle: gtk::RadioButton = builder.object("radio_button_resize_algorithm_triangle").unwrap(); @@ -215,15 +230,34 @@ impl GuiMainNotebook { let radio_button_similar_hash_size_32: gtk::RadioButton = builder.object("radio_button_similar_hash_size_32").unwrap(); let radio_button_similar_hash_size_64: gtk::RadioButton = builder.object("radio_button_similar_hash_size_64").unwrap(); - radio_button_similar_hash_size_8.set_tooltip_text(Some("Default hash size, with very high similarity it produce quite good results and don't save too much data too cache.")); - radio_button_similar_hash_size_16.set_tooltip_text(Some("More precise than 8, so can be used to find very similar pictures, but create bigger cache entries.")); - radio_button_similar_hash_size_32.set_tooltip_text(Some("Hash of this size provide very big similarity which is more than enough for most usages.")); - radio_button_similar_hash_size_64.set_tooltip_text(Some("Paranoid mode, such tool create really big cache files and will catch almost same images.")); - let check_button_image_ignore_same_size: gtk::CheckButton = builder.object("check_button_image_ignore_same_size").unwrap(); let label_similar_images_minimal_similarity: gtk::Label = builder.object("label_similar_images_minimal_similarity").unwrap(); + let label_duplicate_check_method: gtk::Label = builder.object("label_duplicate_check_method").unwrap(); + let label_duplicate_hash_type: gtk::Label = builder.object("label_duplicate_hash_type").unwrap(); + let label_duplicate_size_bytes: gtk::Label = builder.object("label_duplicate_size_bytes").unwrap(); + let label_duplicate_min_size: gtk::Label = builder.object("label_duplicate_min_size").unwrap(); + let label_duplicate_max_size: gtk::Label = builder.object("label_duplicate_max_size").unwrap(); + let label_big_shown_files: gtk::Label = builder.object("label_big_shown_files").unwrap(); + let label_image_resize_algorithm: gtk::Label = builder.object("label_image_resize_algorithm").unwrap(); + let label_image_hash_type: gtk::Label = builder.object("label_image_hash_type").unwrap(); + let label_image_hash_size: gtk::Label = builder.object("label_image_hash_size").unwrap(); + let label_image_size_bytes: gtk::Label = builder.object("label_image_size_bytes").unwrap(); + let label_image_min_size: gtk::Label = builder.object("label_image_min_size").unwrap(); + let label_image_max_size: gtk::Label = builder.object("label_image_max_size").unwrap(); + let label_image_similarity: gtk::Label = builder.object("label_image_similarity").unwrap(); + let label_image_similarity_max: gtk::Label = builder.object("label_image_similarity_max").unwrap(); + let label_video_similarity: gtk::Label = builder.object("label_video_similarity").unwrap(); + let label_video_similarity_min: gtk::Label = builder.object("label_video_similarity_min").unwrap(); + let label_video_similarity_max: gtk::Label = builder.object("label_video_similarity_max").unwrap(); + let label_video_size_bytes: gtk::Label = builder.object("label_video_size_bytes").unwrap(); + let label_video_min_size: gtk::Label = builder.object("label_video_min_size").unwrap(); + let label_video_max_size: gtk::Label = builder.object("label_video_max_size").unwrap(); + let label_music_size_bytes: gtk::Label = builder.object("label_music_size_bytes").unwrap(); + let label_music_min_size: gtk::Label = builder.object("label_music_min_size").unwrap(); + let label_music_max_size: gtk::Label = builder.object("label_music_max_size").unwrap(); + let image_preview_similar_images: gtk::Image = builder.object("image_preview_similar_images").unwrap(); let image_preview_duplicates: gtk::Image = builder.object("image_preview_duplicates").unwrap(); @@ -296,6 +330,29 @@ impl GuiMainNotebook { radio_button_similar_hash_size_64, check_button_image_ignore_same_size, label_similar_images_minimal_similarity, + label_duplicate_check_method, + label_duplicate_hash_type, + label_duplicate_size_bytes, + label_duplicate_min_size, + label_duplicate_max_size, + label_big_shown_files, + label_image_resize_algorithm, + label_image_hash_type, + label_image_hash_size, + label_image_size_bytes, + label_image_min_size, + label_image_max_size, + label_image_similarity, + label_image_similarity_max, + label_video_similarity, + label_video_similarity_min, + label_video_similarity_max, + label_video_size_bytes, + label_video_min_size, + label_video_max_size, + label_music_size_bytes, + label_music_min_size, + label_music_max_size, image_preview_similar_images, entry_duplicate_maximal_size, entry_same_music_maximal_size, @@ -317,4 +374,133 @@ impl GuiMainNotebook { self.tree_view_broken_files.clone(), ] } + + pub fn update_language(&self) { + self.check_button_music_title.set_label(&fl!("music_title_checkbox")); + self.check_button_music_artist.set_label(&fl!("music_artist_checkbox")); + self.check_button_music_album_title.set_label(&fl!("music_album_title_checkbox")); + self.check_button_music_album_artist.set_label(&fl!("music_album_artist_checkbox")); + self.check_button_music_year.set_label(&fl!("music_year_checkbox")); + self.check_button_music_approximate_comparison.set_label(&fl!("music_comparison_checkbox")); + + self.radio_button_duplicates_name.set_label(&fl!("duplicate_mode_name_checkbox")); + self.radio_button_duplicates_size.set_label(&fl!("duplicate_mode_size_checkbox")); + self.radio_button_duplicates_hash.set_label(&fl!("duplicate_mode_hash_checkbox")); + + self.radio_button_duplicates_name.set_tooltip_text(Some(&fl!("duplicate_mode_name_checkbox_tooltip"))); + self.radio_button_duplicates_size.set_tooltip_text(Some(&fl!("duplicate_mode_size_checkbox_tooltip"))); + self.radio_button_duplicates_hash.set_tooltip_text(Some(&fl!("duplicate_mode_hash_checkbox_tooltip"))); + + self.radio_button_hash_type_blake3.set_tooltip_text(Some(&fl!("duplicate_hash_checkbox_blake3"))); + self.radio_button_hash_type_crc32.set_tooltip_text(Some(&fl!("duplicate_hash_checkbox_crc32"))); + self.radio_button_hash_type_xxh3.set_tooltip_text(Some(&fl!("duplicate_hash_checkbox_xxh3"))); + + self.radio_button_similar_hash_size_8.set_tooltip_text(Some(&fl!("image_hash_checkbox_8"))); + self.radio_button_similar_hash_size_16.set_tooltip_text(Some(&fl!("image_hash_checkbox_16"))); + self.radio_button_similar_hash_size_32.set_tooltip_text(Some(&fl!("image_hash_checkbox_32"))); + self.radio_button_similar_hash_size_64.set_tooltip_text(Some(&fl!("image_hash_checkbox_64"))); + + self.label_duplicate_check_method.set_label(&fl!("main_label_check_method")); + self.label_duplicate_hash_type.set_label(&fl!("main_label_hash_type")); + self.label_duplicate_size_bytes.set_label(&fl!("main_label_size_bytes")); + self.label_duplicate_min_size.set_label(&fl!("main_label_min_size")); + self.label_duplicate_max_size.set_label(&fl!("main_label_max_size")); + self.label_big_shown_files.set_label(&fl!("main_label_shown_files")); + self.label_image_resize_algorithm.set_label(&fl!("main_label_resize_algorithm")); + self.label_image_hash_type.set_label(&fl!("main_label_hash_type")); + self.label_image_hash_size.set_label(&fl!("main_label_hash_size")); + self.label_image_size_bytes.set_label(&fl!("main_label_size_bytes")); + self.label_image_min_size.set_label(&fl!("main_label_min_size")); + self.label_image_max_size.set_label(&fl!("main_label_max_size")); + self.label_image_similarity.set_label(&fl!("main_label_similarity")); + self.label_image_similarity_max.set_label(&fl!("core_similarity_very_high")); + self.label_video_similarity.set_label(&fl!("main_label_similarity")); + self.label_video_similarity_min.set_label(&fl!("core_similarity_minimal")); + self.label_video_similarity_max.set_label(&fl!("core_similarity_very_high")); + self.label_video_size_bytes.set_label(&fl!("main_label_size_bytes")); + self.label_video_min_size.set_label(&fl!("main_label_min_size")); + self.label_video_max_size.set_label(&fl!("main_label_max_size")); + self.label_music_size_bytes.set_label(&fl!("main_label_size_bytes")); + self.label_music_min_size.set_label(&fl!("main_label_min_size")); + self.label_music_max_size.set_label(&fl!("main_label_max_size")); + + // Change name of main notebook tabs + let vec_children: Vec = self.notebook_main.children(); + + for (main_enum, fl_thing) in [ + (NotebookMainEnum::Duplicate as usize, fl!("main_notebook_duplicates")), + (NotebookMainEnum::EmptyDirectories as usize, fl!("main_notebook_empty_directories")), + (NotebookMainEnum::BigFiles as usize, fl!("main_notebook_big_files")), + (NotebookMainEnum::EmptyFiles as usize, fl!("main_notebook_empty_files")), + (NotebookMainEnum::Temporary as usize, fl!("main_notebook_temporary")), + (NotebookMainEnum::SimilarImages as usize, fl!("main_notebook_similar_images")), + (NotebookMainEnum::SimilarVideos as usize, fl!("main_notebook_similar_videos")), + (NotebookMainEnum::SameMusic as usize, fl!("main_notebook_same_music")), + (NotebookMainEnum::Symlinks as usize, fl!("main_notebook_symlinks")), + (NotebookMainEnum::BrokenFiles as usize, fl!("main_notebook_broken_files")), + ] { + self.notebook_main.tab_label(&vec_children[main_enum]).unwrap().downcast::().unwrap().set_text(&fl_thing); + } + + // Change names of columns + let names_of_columns = [ + vec![fl!("main_tree_view_column_file_name"), fl!("main_tree_view_column_path"), fl!("main_tree_view_column_modification")], // Duplicates + vec![fl!("main_tree_view_column_folder_name"), fl!("main_tree_view_column_path"), fl!("main_tree_view_column_modification")], // Empty Folders + vec![ + fl!("main_tree_view_column_size"), + fl!("main_tree_view_column_file_name"), + fl!("main_tree_view_column_path"), + fl!("main_tree_view_column_modification"), + ], // Big files + vec![fl!("main_tree_view_column_file_name"), fl!("main_tree_view_column_path"), fl!("main_tree_view_column_modification")], // Empty files + vec![fl!("main_tree_view_column_file_name"), fl!("main_tree_view_column_path"), fl!("main_tree_view_column_modification")], // Temporary Files + vec![ + fl!("main_tree_view_column_similarity"), + fl!("main_tree_view_column_size"), + fl!("main_tree_view_column_dimensions"), + fl!("main_tree_view_column_file_name"), + fl!("main_tree_view_column_path"), + fl!("main_tree_view_column_modification"), + ], // Similar Images + vec![ + fl!("main_tree_view_column_size"), + fl!("main_tree_view_column_file_name"), + fl!("main_tree_view_column_path"), + fl!("main_tree_view_column_modification"), + ], // Similar Videos + vec![ + fl!("main_tree_view_column_size"), + fl!("main_tree_view_column_file_name"), + fl!("main_tree_view_column_path"), + fl!("main_tree_view_column_title"), + fl!("main_tree_view_column_artist"), + fl!("main_tree_view_column_year"), + fl!("main_tree_view_column_album_title"), + fl!("main_tree_view_column_album_artist"), + fl!("main_tree_view_column_modification"), + ], // Music Dupliactes + vec![ + fl!("main_tree_view_column_symlink_file_name"), + fl!("main_tree_view_column_symlink_folder"), + fl!("main_tree_view_column_destination_path"), + fl!("main_tree_view_column_type_of_error"), + fl!("main_tree_view_column_modification"), + ], // Invalid Symlinks + vec![ + fl!("main_tree_view_column_file_name"), + fl!("main_tree_view_column_path"), + fl!("main_tree_view_column_type_of_error"), + fl!("main_tree_view_column_modification"), + ], // Broken Files + ]; + + for (notebook_index, tree_view) in self.get_main_tree_views().iter().enumerate() { + for (column_index, column) in tree_view.columns().iter().enumerate() { + if column_index == 0 { + continue; // Selection button + } + column.set_title(&names_of_columns[notebook_index][column_index - 1]); + } + } + } } diff --git a/czkawka_gui/src/gui_popovers.rs b/czkawka_gui/src/gui_popovers.rs index da4d01b..4fe4e29 100644 --- a/czkawka_gui/src/gui_popovers.rs +++ b/czkawka_gui/src/gui_popovers.rs @@ -1,3 +1,4 @@ +use crate::fl; use gtk::prelude::*; use gtk::Builder; @@ -82,4 +83,17 @@ impl GuiPopovers { popover_right_click, } } + pub fn update_language(&self) { + self.buttons_popover_select_all.set_label(&fl!("popover_select_all")); + self.buttons_popover_unselect_all.set_label(&fl!("popover_unselect_all")); + self.buttons_popover_reverse.set_label(&fl!("popover_reverse")); + self.buttons_popover_select_all_except_oldest.set_label(&fl!("popover_select_all_except_oldest")); + self.buttons_popover_select_all_except_newest.set_label(&fl!("popover_select_all_except_newest")); + self.buttons_popover_select_one_oldest.set_label(&fl!("popover_select_one_oldest")); + self.buttons_popover_select_one_newest.set_label(&fl!("popover_select_one_newest")); + self.buttons_popover_select_custom.set_label(&fl!("popover_select_custom")); + self.buttons_popover_unselect_custom.set_label(&fl!("popover_unselect_custom")); + self.buttons_popover_select_all_images_except_biggest.set_label(&fl!("popover_select_all_images_except_biggest")); + self.buttons_popover_select_all_images_except_smallest.set_label(&fl!("popover_select_all_images_except_smallest")); + } } diff --git a/czkawka_gui/src/gui_progress_dialog.rs b/czkawka_gui/src/gui_progress_dialog.rs index d1fd69c..a79ebf3 100644 --- a/czkawka_gui/src/gui_progress_dialog.rs +++ b/czkawka_gui/src/gui_progress_dialog.rs @@ -1,5 +1,7 @@ +use crate::fl; +use crate::help_functions::get_custom_label_from_label_with_image; use gtk::prelude::*; -use gtk::{Builder, EventControllerKey, Window}; +use gtk::{Bin, Builder, EventControllerKey, Window}; #[derive(Clone)] pub struct GuiProgressDialog { @@ -9,6 +11,8 @@ pub struct GuiProgressDialog { pub progress_bar_all_stages: gtk::ProgressBar, pub label_stage: gtk::Label, + pub label_progress_current_stage: gtk::Label, + pub label_progress_all_stages: gtk::Label, pub grid_progress_stages: gtk::Grid, @@ -23,11 +27,14 @@ impl GuiProgressDialog { let window_progress: gtk::Dialog = builder.object("window_progress").unwrap(); window_progress.set_transient_for(Some(window_main)); + window_progress.set_title("Czkawka"); let progress_bar_current_stage: gtk::ProgressBar = builder.object("progress_bar_current_stage").unwrap(); let progress_bar_all_stages: gtk::ProgressBar = builder.object("progress_bar_all_stages").unwrap(); let label_stage: gtk::Label = builder.object("label_stage").unwrap(); + let label_progress_current_stage: gtk::Label = builder.object("label_progress_current_stage").unwrap(); + let label_progress_all_stages: gtk::Label = builder.object("label_progress_all_stages").unwrap(); let grid_progress_stages: gtk::Grid = builder.object("grid_progress_stages").unwrap(); @@ -39,9 +46,17 @@ impl GuiProgressDialog { progress_bar_current_stage, progress_bar_all_stages, label_stage, + label_progress_current_stage, + label_progress_all_stages, grid_progress_stages, button_stop_in_dialog, evk_button_stop_in_dialog, } } + pub fn update_language(&self) { + get_custom_label_from_label_with_image(&self.button_stop_in_dialog.clone().upcast::()).set_text(&fl!("progress_stop_button")); + + self.label_progress_current_stage.set_label(&fl!("progress_current_stage")); + self.label_progress_all_stages.set_label(&fl!("progress_all_stages")); + } } diff --git a/czkawka_gui/src/gui_settings.rs b/czkawka_gui/src/gui_settings.rs index 6a3b463..f6d553f 100644 --- a/czkawka_gui/src/gui_settings.rs +++ b/czkawka_gui/src/gui_settings.rs @@ -1,3 +1,4 @@ +use crate::fl; use gtk::prelude::*; use gtk::{Builder, Window}; @@ -14,6 +15,8 @@ pub struct GuiSettings { pub check_button_settings_show_text_view: gtk::CheckButton, pub check_button_settings_use_cache: gtk::CheckButton, pub check_button_settings_use_trash: gtk::CheckButton, + pub label_settings_general_language: gtk::Label, + pub combo_box_settings_language: gtk::ComboBoxText, // Duplicates pub check_button_settings_hide_hard_links: gtk::CheckButton, @@ -23,6 +26,8 @@ pub struct GuiSettings { pub check_button_settings_show_preview_duplicates: gtk::CheckButton, pub check_button_settings_duplicates_delete_outdated_cache: gtk::CheckButton, pub button_settings_duplicates_clear_cache: gtk::Button, + pub label_settings_duplicate_minimal_size_cache: gtk::Label, + pub label_settings_duplicate_minimal_size_cache_prehash: gtk::Label, // Similar Images pub check_button_settings_show_preview_similar_images: gtk::CheckButton, @@ -60,15 +65,8 @@ impl GuiSettings { let check_button_settings_show_text_view: gtk::CheckButton = builder.object("check_button_settings_show_text_view").unwrap(); let check_button_settings_use_cache: gtk::CheckButton = builder.object("check_button_settings_use_cache").unwrap(); let check_button_settings_use_trash: gtk::CheckButton = builder.object("check_button_settings_use_trash").unwrap(); - - check_button_settings_save_at_exit.set_tooltip_text(Some("Saves configuration to file when closing app.")); - check_button_settings_load_at_start.set_tooltip_text(Some("Loading at start configuration from file.\n\nNot selecting this option will load default settings.")); - check_button_settings_confirm_deletion.set_tooltip_text(Some("Shows confirmation dialog when clicking at delete button.")); - check_button_settings_confirm_link.set_tooltip_text(Some("Shows confirmation dialog when clicking at hard/symlink button.")); - check_button_settings_confirm_group_deletion.set_tooltip_text(Some("Shows dialog when trying to remove all records from group.")); - check_button_settings_show_text_view.set_tooltip_text(Some("Shows error panel at bottom.")); - check_button_settings_use_cache.set_tooltip_text(Some("Option to which allows to not use cache feature.")); - check_button_settings_use_trash.set_tooltip_text(Some("When enabled it moves files to trash instead deleting them permanently.")); + let label_settings_general_language: gtk::Label = builder.object("label_settings_general_language").unwrap(); + let combo_box_settings_language: gtk::ComboBoxText = builder.object("combo_box_settings_language").unwrap(); // Duplicates let check_button_settings_hide_hard_links: gtk::CheckButton = builder.object("check_button_settings_hide_hard_links").unwrap(); @@ -78,53 +76,26 @@ impl GuiSettings { let button_settings_duplicates_clear_cache: gtk::Button = builder.object("button_settings_duplicates_clear_cache").unwrap(); let check_button_duplicates_use_prehash_cache: gtk::CheckButton = builder.object("check_button_duplicates_use_prehash_cache").unwrap(); let entry_settings_prehash_cache_file_minimal_size: gtk::Entry = builder.object("entry_settings_prehash_cache_file_minimal_size").unwrap(); - - check_button_settings_hide_hard_links.set_tooltip_text(Some( - "Hides all files except one, if are points to same data(are hardlinked).\n\nE.g. in case where on disk there is 7 files which are hardlinked to specific data and one different file with same data but different inode, then in duplicate finder will be visible only one unique file and one file from hardlinked ones.", - )); - entry_settings_cache_file_minimal_size.set_tooltip_text(Some( - "Allows to set minimal size of file, which will be cached.\n\nChoosing smaller value, will generate more records which will speedup search, but slowdown cache loading/saving.", - )); - check_button_settings_show_preview_duplicates.set_tooltip_text(Some("Shows preview at right side, when selecting image file.")); - check_button_settings_duplicates_delete_outdated_cache.set_tooltip_text(Some("Allows to delete outdated cache results which points to non-existent files.\n\nWhen enabled, app make sure when loading records, that all points to valid files and ignore broken ones.\n\nDisabling this option, will help to scan files on external drives, so cache entries about them will not be purged in next scan.\n\nIn case of having hundred of thousands records in cache, it is suggested to enable this option, to speedup cache loading and saving at start and end of scan.")); - button_settings_duplicates_clear_cache.set_tooltip_text(Some("Manually clear cache from outdated entries.\n\nShould be used only if automatic clearing was disabled.")); - check_button_duplicates_use_prehash_cache.set_tooltip_text(Some( - "Enables caching of prehash(hash computed from small part of file) which allows to earlier throw out non duplicated results.\n\nIt is disabled by default because can cause in some situations slowdowns.\n\nIt is heavily recommended to use it when scanning hundred of thousands or million files, because it can speedup search multiple times.", - )); + let label_settings_duplicate_minimal_size_cache: gtk::Label = builder.object("label_settings_duplicate_minimal_size_cache").unwrap(); + let label_settings_duplicate_minimal_size_cache_prehash: gtk::Label = builder.object("label_settings_duplicate_minimal_size_cache_prehash").unwrap(); // Similar Images let check_button_settings_show_preview_similar_images: gtk::CheckButton = builder.object("check_button_settings_show_preview_similar_images").unwrap(); let check_button_settings_similar_images_delete_outdated_cache: gtk::CheckButton = builder.object("check_button_settings_similar_images_delete_outdated_cache").unwrap(); let button_settings_similar_images_clear_cache: gtk::Button = builder.object("button_settings_similar_images_clear_cache").unwrap(); - check_button_settings_show_preview_similar_images.set_tooltip_text(Some("Shows preview at right side, when selecting image file.")); - check_button_settings_similar_images_delete_outdated_cache.set_tooltip_text(Some("Allows to delete outdated cache results which points to non-existent files.\n\nWhen enabled, app make sure when loading records, that all points to valid files and ignore broken ones.\n\nDisabling this option, will help to scan files on external drives, so cache entries about them will not be purged in next scan.\n\nIn case of having hundred of thousands records in cache, it is suggested to enable this option, to speedup cache loading and saving at start and end of scan.")); - button_settings_similar_images_clear_cache.set_tooltip_text(Some("Manually clear cache from outdated entries.\nShould be used only if automatic clearing was disabled.")); - // Similar Videos let check_button_settings_similar_videos_delete_outdated_cache: gtk::CheckButton = builder.object("check_button_settings_similar_videos_delete_outdated_cache").unwrap(); let button_settings_similar_videos_clear_cache: gtk::Button = builder.object("button_settings_similar_videos_clear_cache").unwrap(); - check_button_settings_similar_videos_delete_outdated_cache.set_tooltip_text(Some("Allows to delete outdated cache results which points to non-existent files.\n\nWhen enabled, app make sure when loading records, that all points to valid files and ignore broken ones.\n\nDisabling this option, will help to scan files on external drives, so cache entries about them will not be purged in next scan.\n\nIn case of having hundred of thousands records in cache, it is suggested to enable this option, to speedup cache loading and saving at start and end of scan.")); - button_settings_similar_videos_clear_cache.set_tooltip_text(Some("Manually clear cache from outdated entries.\nShould be used only if automatic clearing was disabled.")); - // Saving/Loading/Resetting configuration let button_settings_save_configuration: gtk::Button = builder.object("button_settings_save_configuration").unwrap(); let button_settings_load_configuration: gtk::Button = builder.object("button_settings_load_configuration").unwrap(); let button_settings_reset_configuration: gtk::Button = builder.object("button_settings_reset_configuration").unwrap(); - button_settings_save_configuration.set_tooltip_text(Some("Save current settings configuration to file.")); - button_settings_load_configuration.set_tooltip_text(Some("Load settings from file and replace current configuration with them.")); - button_settings_reset_configuration.set_tooltip_text(Some("Reset current configuration to default one.")); - let button_settings_open_cache_folder: gtk::Button = builder.object("button_settings_open_cache_folder").unwrap(); let button_settings_open_settings_folder: gtk::Button = builder.object("button_settings_open_settings_folder").unwrap(); - button_settings_open_cache_folder.set_tooltip_text(Some( - "Opens folder where are stored txt files with cache.\n\nModifying them may cause to show invalid results but also modifying e.g. path may save time when moving big amount of files to different place.\n\nYou can copy this files between computers to save time on scanning again for files(of course if they have similar directory structure).\n\nIn case of problems with cache, this files can be removed, so app will automatically regenerate them.", - )); - button_settings_open_settings_folder.set_tooltip_text(Some("Opens folder where Czkawka config are stored.\n\nModifying them, may cause to show.")); - Self { window_settings, check_button_settings_save_at_exit, @@ -135,6 +106,8 @@ impl GuiSettings { check_button_settings_show_text_view, check_button_settings_use_cache, check_button_settings_use_trash, + label_settings_general_language, + combo_box_settings_language, check_button_settings_hide_hard_links, entry_settings_cache_file_minimal_size, entry_settings_prehash_cache_file_minimal_size, @@ -142,6 +115,8 @@ impl GuiSettings { check_button_settings_show_preview_duplicates, check_button_settings_duplicates_delete_outdated_cache, button_settings_duplicates_clear_cache, + label_settings_duplicate_minimal_size_cache, + label_settings_duplicate_minimal_size_cache_prehash, check_button_settings_show_preview_similar_images, check_button_settings_similar_images_delete_outdated_cache, button_settings_similar_images_clear_cache, @@ -154,4 +129,73 @@ impl GuiSettings { button_settings_open_settings_folder, } } + + pub fn update_language(&self) { + self.check_button_settings_save_at_exit.set_label(&fl!("settings_save_at_exit_button")); + self.check_button_settings_load_at_start.set_label(&fl!("settings_load_at_start_button")); + self.check_button_settings_confirm_deletion.set_label(&fl!("settings_confirm_deletion_button")); + self.check_button_settings_confirm_link.set_label(&fl!("settings_confirm_link_button")); + self.check_button_settings_confirm_group_deletion.set_label(&fl!("settings_confirm_group_deletion_button")); + self.check_button_settings_show_text_view.set_label(&fl!("settings_show_text_view_button")); + self.check_button_settings_use_cache.set_label(&fl!("settings_use_cache_button")); + self.check_button_settings_use_trash.set_label(&fl!("settings_use_trash_button")); + self.label_settings_general_language.set_label(&fl!("settings_language_label")); + + self.check_button_settings_save_at_exit.set_tooltip_text(Some(&fl!("settings_save_at_exit_button_tooltip"))); + self.check_button_settings_load_at_start.set_tooltip_text(Some(&fl!("settings_load_at_start_button_tooltip"))); + self.check_button_settings_confirm_deletion.set_tooltip_text(Some(&fl!("settings_confirm_deletion_button_tooltip"))); + self.check_button_settings_confirm_link.set_tooltip_text(Some(&fl!("settings_confirm_link_button_tooltip"))); + self.check_button_settings_confirm_group_deletion.set_tooltip_text(Some(&fl!("settings_confirm_group_deletion_button_tooltip"))); + self.check_button_settings_show_text_view.set_tooltip_text(Some(&fl!("settings_show_text_view_button_tooltip"))); + self.check_button_settings_use_cache.set_tooltip_text(Some(&fl!("settings_use_cache_button_tooltip"))); + self.check_button_settings_use_trash.set_tooltip_text(Some(&fl!("settings_use_trash_button_tooltip"))); + self.label_settings_general_language.set_tooltip_text(Some(&fl!("settings_language_label_tooltip"))); + + self.check_button_settings_hide_hard_links.set_label(&fl!("settings_duplicates_hide_hard_link_button")); + self.check_button_settings_show_preview_duplicates.set_label(&fl!("settings_multiple_image_preview_checkbutton")); + self.check_button_settings_duplicates_delete_outdated_cache.set_label(&fl!("settings_multiple_delete_outdated_cache_checkbutton")); + self.button_settings_duplicates_clear_cache.set_label(&fl!("settings_multiple_clear_cache_button")); + self.check_button_duplicates_use_prehash_cache.set_label(&fl!("settings_duplicates_prehash_checkbutton")); + self.label_settings_duplicate_minimal_size_cache.set_label(&fl!("settings_duplicates_minimal_size_cache_label")); + self.label_settings_duplicate_minimal_size_cache_prehash.set_label(&fl!("settings_duplicates_minimal_size_cache_prehash_label")); + + self.check_button_settings_hide_hard_links.set_tooltip_text(Some(&fl!("settings_duplicates_hide_hard_link_button_tooltip"))); + self.entry_settings_cache_file_minimal_size.set_tooltip_text(Some(&fl!("settings_duplicates_minimal_size_entry_tooltip"))); + self.check_button_settings_show_preview_duplicates.set_tooltip_text(Some(&fl!("settings_multiple_image_preview_checkbutton_tooltip"))); + self.check_button_settings_duplicates_delete_outdated_cache + .set_tooltip_text(Some(&fl!("settings_multiple_delete_outdated_cache_checkbutton_tooltip"))); + self.button_settings_duplicates_clear_cache.set_tooltip_text(Some(&fl!("settings_multiple_clear_cache_button_tooltip"))); + self.check_button_duplicates_use_prehash_cache.set_tooltip_text(Some(&fl!("settings_duplicates_prehash_checkbutton_tooltip"))); + self.entry_settings_prehash_cache_file_minimal_size.set_tooltip_text(Some(&fl!("settings_duplicates_prehash_minimal_entry_tooltip"))); + + self.check_button_settings_show_preview_similar_images.set_label(&fl!("settings_multiple_image_preview_checkbutton")); + self.check_button_settings_similar_images_delete_outdated_cache.set_label(&fl!("settings_multiple_delete_outdated_cache_checkbutton")); + self.button_settings_similar_images_clear_cache.set_label(&fl!("settings_multiple_clear_cache_button")); + + self.check_button_settings_show_preview_similar_images.set_tooltip_text(Some(&fl!("settings_multiple_image_preview_checkbutton_tooltip"))); + self.check_button_settings_similar_images_delete_outdated_cache + .set_tooltip_text(Some(&fl!("settings_multiple_delete_outdated_cache_checkbutton_tooltip"))); + self.button_settings_similar_images_clear_cache.set_tooltip_text(Some(&fl!("settings_multiple_clear_cache_button_tooltip"))); + + self.check_button_settings_similar_videos_delete_outdated_cache.set_label(&fl!("settings_multiple_delete_outdated_cache_checkbutton")); + self.button_settings_similar_videos_clear_cache.set_label(&fl!("settings_multiple_clear_cache_button")); + + self.check_button_settings_similar_videos_delete_outdated_cache + .set_tooltip_text(Some(&fl!("settings_multiple_delete_outdated_cache_checkbutton_tooltip"))); + self.button_settings_similar_videos_clear_cache.set_tooltip_text(Some(&fl!("settings_multiple_clear_cache_button_tooltip"))); + + self.button_settings_save_configuration.set_label(&fl!("settings_saving_button")); + self.button_settings_load_configuration.set_label(&fl!("settings_loading_button")); + self.button_settings_reset_configuration.set_label(&fl!("settings_reset_button")); + + self.button_settings_save_configuration.set_tooltip_text(Some(&fl!("settings_saving_button_tooltip"))); + self.button_settings_load_configuration.set_tooltip_text(Some(&fl!("settings_loading_button_tooltip"))); + self.button_settings_reset_configuration.set_tooltip_text(Some(&fl!("settings_reset_button_tooltip"))); + + self.button_settings_open_cache_folder.set_label(&fl!("settings_folder_cache_open")); + self.button_settings_open_settings_folder.set_label(&fl!("settings_folder_settings_open")); + + self.button_settings_open_cache_folder.set_tooltip_text(Some(&fl!("settings_folder_cache_open_tooltip"))); + self.button_settings_open_settings_folder.set_tooltip_text(Some(&fl!("settings_folder_settings_open_tooltip"))); + } } diff --git a/czkawka_gui/src/gui_upper_notebook.rs b/czkawka_gui/src/gui_upper_notebook.rs new file mode 100644 index 0000000..417d796 --- /dev/null +++ b/czkawka_gui/src/gui_upper_notebook.rs @@ -0,0 +1,121 @@ +use crate::fl; +use crate::help_functions::get_custom_label_from_label_with_image; +use crate::notebook_enums::NotebookUpperEnum; +use gtk::prelude::*; +use gtk::{Bin, EventControllerKey, TreeView}; + +#[derive(Clone)] +pub struct GuiUpperNotebook { + pub notebook_upper: gtk::Notebook, + + pub scrolled_window_included_directories: gtk::ScrolledWindow, + pub scrolled_window_excluded_directories: gtk::ScrolledWindow, + + pub tree_view_included_directories: gtk::TreeView, + pub tree_view_excluded_directories: gtk::TreeView, + + pub evk_tree_view_included_directories: gtk::EventControllerKey, + pub evk_tree_view_excluded_directories: gtk::EventControllerKey, + + pub entry_excluded_items: gtk::Entry, + pub entry_allowed_extensions: gtk::Entry, + + pub check_button_recursive: gtk::CheckButton, + + pub buttons_manual_add_included_directory: gtk::Button, + pub buttons_add_included_directory: gtk::Button, + pub buttons_remove_included_directory: gtk::Button, + pub buttons_manual_add_excluded_directory: gtk::Button, + pub buttons_add_excluded_directory: gtk::Button, + pub buttons_remove_excluded_directory: gtk::Button, + + pub label_excluded_items: gtk::Label, + pub label_allowed_extensions: gtk::Label, +} + +impl GuiUpperNotebook { + pub fn create_from_builder(builder: >k::Builder) -> Self { + let notebook_upper: gtk::Notebook = builder.object("notebook_upper").unwrap(); + + let scrolled_window_included_directories: gtk::ScrolledWindow = builder.object("scrolled_window_included_directories").unwrap(); + let scrolled_window_excluded_directories: gtk::ScrolledWindow = builder.object("scrolled_window_excluded_directories").unwrap(); + + let tree_view_included_directories: gtk::TreeView = TreeView::new(); + let tree_view_excluded_directories: gtk::TreeView = TreeView::new(); + + let evk_tree_view_included_directories: gtk::EventControllerKey = EventControllerKey::new(&tree_view_included_directories); + let evk_tree_view_excluded_directories: gtk::EventControllerKey = EventControllerKey::new(&tree_view_excluded_directories); + + let entry_allowed_extensions: gtk::Entry = builder.object("entry_allowed_extensions").unwrap(); + let entry_excluded_items: gtk::Entry = builder.object("entry_excluded_items").unwrap(); + + let check_button_recursive: gtk::CheckButton = builder.object("check_button_recursive").unwrap(); + + let buttons_manual_add_included_directory: gtk::Button = builder.object("buttons_manual_add_included_directory").unwrap(); + let buttons_add_included_directory: gtk::Button = builder.object("buttons_add_included_directory").unwrap(); + let buttons_remove_included_directory: gtk::Button = builder.object("buttons_remove_included_directory").unwrap(); + let buttons_manual_add_excluded_directory: gtk::Button = builder.object("buttons_manual_add_excluded_directory").unwrap(); + let buttons_add_excluded_directory: gtk::Button = builder.object("buttons_add_excluded_directory").unwrap(); + let buttons_remove_excluded_directory: gtk::Button = builder.object("buttons_remove_excluded_directory").unwrap(); + + let label_excluded_items: gtk::Label = builder.object("label_excluded_items").unwrap(); + let label_allowed_extensions: gtk::Label = builder.object("label_allowed_extensions").unwrap(); + + Self { + notebook_upper, + scrolled_window_included_directories, + scrolled_window_excluded_directories, + tree_view_included_directories, + tree_view_excluded_directories, + evk_tree_view_included_directories, + evk_tree_view_excluded_directories, + entry_excluded_items, + entry_allowed_extensions, + check_button_recursive, + buttons_manual_add_included_directory, + buttons_add_included_directory, + buttons_remove_included_directory, + buttons_manual_add_excluded_directory, + buttons_add_excluded_directory, + buttons_remove_excluded_directory, + label_excluded_items, + label_allowed_extensions, + } + } + pub fn update_language(&self) { + self.check_button_recursive.set_label(&fl!("upper_recursive_button")); + self.check_button_recursive.set_tooltip_text(Some(&fl!("upper_recursive_button_tooltip"))); + + get_custom_label_from_label_with_image(&self.buttons_manual_add_included_directory.clone().upcast::()).set_text(&fl!("upper_manual_add_included_button")); + get_custom_label_from_label_with_image(&self.buttons_add_included_directory.clone().upcast::()).set_text(&fl!("upper_add_included_button")); + get_custom_label_from_label_with_image(&self.buttons_remove_included_directory.clone().upcast::()).set_text(&fl!("upper_remove_included_button")); + get_custom_label_from_label_with_image(&self.buttons_manual_add_excluded_directory.clone().upcast::()).set_text(&fl!("upper_manual_add_excluded_button")); + get_custom_label_from_label_with_image(&self.buttons_add_excluded_directory.clone().upcast::()).set_text(&fl!("upper_add_excluded_button")); + get_custom_label_from_label_with_image(&self.buttons_remove_excluded_directory.clone().upcast::()).set_text(&fl!("upper_remove_excluded_button")); + + self.buttons_manual_add_included_directory.set_tooltip_text(Some(&fl!("upper_manual_add_included_button_tooltip"))); + self.buttons_add_included_directory.set_tooltip_text(Some(&fl!("upper_add_included_button_tooltip"))); + self.buttons_remove_included_directory.set_tooltip_text(Some(&fl!("upper_remove_included_button_tooltip"))); + self.buttons_manual_add_excluded_directory.set_tooltip_text(Some(&fl!("upper_manual_add_excluded_button_tooltip"))); + self.buttons_add_excluded_directory.set_tooltip_text(Some(&fl!("upper_add_excluded_button_tooltip"))); + self.buttons_remove_excluded_directory.set_tooltip_text(Some(&fl!("upper_remove_excluded_button_tooltip"))); + + self.label_allowed_extensions.set_tooltip_text(Some(&fl!("upper_allowed_extensions_tooltip"))); + self.entry_allowed_extensions.set_tooltip_text(Some(&fl!("upper_allowed_extensions_tooltip"))); + self.label_excluded_items.set_tooltip_text(Some(&fl!("upper_excluded_items_tooltip"))); + self.entry_excluded_items.set_tooltip_text(Some(&fl!("upper_excluded_items_tooltip"))); + + self.label_excluded_items.set_label(&fl!("upper_excluded_items")); + self.label_allowed_extensions.set_label(&fl!("upper_allowed_extensions")); + + let vec_children: Vec = self.notebook_upper.children(); + + for (upper_enum, fl_thing) in [ + (NotebookUpperEnum::ItemsConfiguration as usize, fl!("upper_notebook_items_configuration")), + (NotebookUpperEnum::ExcludedDirectories as usize, fl!("upper_notebook_excluded_directories")), + (NotebookUpperEnum::IncludedDirectories as usize, fl!("upper_notebook_included_directories")), + ] { + self.notebook_upper.tab_label(&vec_children[upper_enum]).unwrap().downcast::().unwrap().set_text(&fl_thing); + } + } +} diff --git a/czkawka_gui/src/gui_upper_notepad.rs b/czkawka_gui/src/gui_upper_notepad.rs deleted file mode 100644 index cbd1657..0000000 --- a/czkawka_gui/src/gui_upper_notepad.rs +++ /dev/null @@ -1,82 +0,0 @@ -use gtk::prelude::*; -use gtk::{EventControllerKey, TreeView}; - -#[derive(Clone)] -pub struct GuiUpperNotebook { - pub notebook_upper: gtk::Notebook, - - pub scrolled_window_included_directories: gtk::ScrolledWindow, - pub scrolled_window_excluded_directories: gtk::ScrolledWindow, - - pub tree_view_included_directories: gtk::TreeView, - pub tree_view_excluded_directories: gtk::TreeView, - - pub evk_tree_view_included_directories: gtk::EventControllerKey, - pub evk_tree_view_excluded_directories: gtk::EventControllerKey, - - pub entry_excluded_items: gtk::Entry, - pub entry_allowed_extensions: gtk::Entry, - - pub check_button_recursive: gtk::CheckButton, - - pub buttons_manual_add_directory: gtk::Button, - pub buttons_add_included_directory: gtk::Button, - pub buttons_remove_included_directory: gtk::Button, - pub buttons_manual_add_excluded_directory: gtk::Button, - pub buttons_add_excluded_directory: gtk::Button, - pub buttons_remove_excluded_directory: gtk::Button, -} - -impl GuiUpperNotebook { - pub fn create_from_builder(builder: >k::Builder) -> Self { - let notebook_upper: gtk::Notebook = builder.object("notebook_upper").unwrap(); - - let scrolled_window_included_directories: gtk::ScrolledWindow = builder.object("scrolled_window_included_directories").unwrap(); - let scrolled_window_excluded_directories: gtk::ScrolledWindow = builder.object("scrolled_window_excluded_directories").unwrap(); - - let tree_view_included_directories: gtk::TreeView = TreeView::new(); - let tree_view_excluded_directories: gtk::TreeView = TreeView::new(); - - let evk_tree_view_included_directories: gtk::EventControllerKey = EventControllerKey::new(&tree_view_included_directories); - let evk_tree_view_excluded_directories: gtk::EventControllerKey = EventControllerKey::new(&tree_view_excluded_directories); - - let entry_allowed_extensions: gtk::Entry = builder.object("entry_allowed_extensions").unwrap(); - let entry_excluded_items: gtk::Entry = builder.object("entry_excluded_items").unwrap(); - - let check_button_recursive: gtk::CheckButton = builder.object("check_button_recursive").unwrap(); - check_button_recursive.set_tooltip_text(Some("If selected, search also for files which are not placed directly under chosen folders")); - - let buttons_manual_add_directory: gtk::Button = builder.object("buttons_manual_add_directory").unwrap(); - let buttons_add_included_directory: gtk::Button = builder.object("buttons_add_included_directory").unwrap(); - let buttons_remove_included_directory: gtk::Button = builder.object("buttons_remove_included_directory").unwrap(); - let buttons_manual_add_excluded_directory: gtk::Button = builder.object("buttons_manual_add_excluded_directory").unwrap(); - let buttons_add_excluded_directory: gtk::Button = builder.object("buttons_add_excluded_directory").unwrap(); - let buttons_remove_excluded_directory: gtk::Button = builder.object("buttons_remove_excluded_directory").unwrap(); - - buttons_manual_add_directory.set_tooltip_text(Some("Allows to add directory name by hand")); - buttons_add_included_directory.set_tooltip_text(Some("Add new directory to search")); - buttons_remove_included_directory.set_tooltip_text(Some("Delete directory from search")); - buttons_manual_add_excluded_directory.set_tooltip_text(Some("Allows to add directory name by hand")); - buttons_add_excluded_directory.set_tooltip_text(Some("Add directory to be excluded in search")); - buttons_remove_excluded_directory.set_tooltip_text(Some("Delete directory from excluded list")); - - Self { - notebook_upper, - scrolled_window_included_directories, - scrolled_window_excluded_directories, - tree_view_included_directories, - tree_view_excluded_directories, - evk_tree_view_included_directories, - evk_tree_view_excluded_directories, - entry_excluded_items, - entry_allowed_extensions, - check_button_recursive, - buttons_manual_add_directory, - buttons_add_included_directory, - buttons_remove_included_directory, - buttons_manual_add_excluded_directory, - buttons_add_excluded_directory, - buttons_remove_excluded_directory, - } - } -} diff --git a/czkawka_gui/src/help_functions.rs b/czkawka_gui/src/help_functions.rs index 206d8b2..411db4d 100644 --- a/czkawka_gui/src/help_functions.rs +++ b/czkawka_gui/src/help_functions.rs @@ -541,3 +541,14 @@ pub fn clean_invalid_headers(model: >k::ListStore, column_color: i32) { } } } + +// TODO children are not available in GTK 4 +pub fn get_custom_label_from_label_with_image(button: >k::Bin) -> gtk::Label { + let internal_box = button.child().unwrap().downcast::().unwrap(); + for child in internal_box.children() { + if let Ok(t) = child.downcast::() { + return t; + } + } + panic!("Button doesn't have proper custom label child"); +} diff --git a/czkawka_gui/src/initialize_gui.rs b/czkawka_gui/src/initialize_gui.rs index e2aaee0..9742039 100644 --- a/czkawka_gui/src/initialize_gui.rs +++ b/czkawka_gui/src/initialize_gui.rs @@ -18,6 +18,7 @@ use crate::create_tree_view::*; use crate::delete_things; use crate::gui_data::*; use crate::help_functions::*; +use crate::language_functions::LANGUAGES_ALL; use crate::notebook_enums::NotebookMainEnum; use crate::opening_selecting_records::*; @@ -41,6 +42,14 @@ pub fn initialize_gui(gui_data: &mut GuiData) { buttons_hardlink.hide(); buttons_move.hide(); } + //// Initialize language combobox + { + let combo_box_settings_language = gui_data.settings.combo_box_settings_language.clone(); + for lang in LANGUAGES_ALL { + combo_box_settings_language.append_text(lang.combo_box_text); + } + combo_box_settings_language.set_active(Some(0)); + } //// Initialize main scrolled view with notebook { diff --git a/czkawka_gui/src/language_functions.rs b/czkawka_gui/src/language_functions.rs new file mode 100644 index 0000000..99cccec --- /dev/null +++ b/czkawka_gui/src/language_functions.rs @@ -0,0 +1,26 @@ +#[derive(Clone)] +pub struct Language { + pub combo_box_text: &'static str, + pub short_text: &'static str, +} + +pub const LANGUAGES_ALL: [Language; 2] = [ + Language { + combo_box_text: "English (en)", + short_text: "en", + }, + Language { + combo_box_text: "Polski (pl)", + short_text: "pl", + }, +]; + +pub fn get_language_from_combo_box_text(combo_box_text: String) -> Language { + for lang in LANGUAGES_ALL { + if lang.combo_box_text == combo_box_text { + return lang; + } + } + + panic!("Not found proper text"); +} diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs index de1cf58..f5ba92f 100644 --- a/czkawka_gui/src/main.rs +++ b/czkawka_gui/src/main.rs @@ -16,6 +16,7 @@ use crate::connect_button_save::*; use crate::connect_button_search::*; use crate::connect_button_select::*; use crate::connect_button_stop::*; +use crate::connect_change_language::*; use crate::connect_duplicate_buttons::*; use crate::connect_header_buttons::*; use crate::connect_notebook_tabs::*; @@ -39,6 +40,7 @@ mod connect_button_save; mod connect_button_search; mod connect_button_select; mod connect_button_stop; +mod connect_change_language; mod connect_duplicate_buttons; mod connect_header_buttons; mod connect_notebook_tabs; @@ -57,9 +59,10 @@ mod gui_main_notebook; mod gui_popovers; mod gui_progress_dialog; mod gui_settings; -mod gui_upper_notepad; +mod gui_upper_notebook; mod help_functions; mod initialize_gui; +mod language_functions; mod notebook_enums; mod opening_selecting_records; mod saving_loading; @@ -101,6 +104,9 @@ fn main() { reset_configuration(false, &gui_data.upper_notebook, &gui_data.settings, &gui_data.text_view_errors); // Fallback for invalid loading setting project load_configuration(false, &gui_data.upper_notebook, &gui_data.settings, &gui_data.text_view_errors, &gui_data.scrolled_window_errors); + // Needs to run when entire GUI is initialized and + connect_change_language(&gui_data); + connect_button_delete(&gui_data); connect_button_save(&gui_data); connect_button_search( diff --git a/czkawka_gui/src/notebook_enums.rs b/czkawka_gui/src/notebook_enums.rs index b4fa05a..87c9113 100644 --- a/czkawka_gui/src/notebook_enums.rs +++ b/czkawka_gui/src/notebook_enums.rs @@ -1,5 +1,5 @@ pub const NUMBER_OF_NOTEBOOK_MAIN_TABS: usize = 10; -pub const NUMBER_OF_NOTEBOOK_UPPER_TABS: usize = 4; +// pub const NUMBER_OF_NOTEBOOK_UPPER_TABS: usize = 3; // Needs to be updated when changed order of notebook tabs #[derive(Eq, PartialEq, Hash, Clone, Debug)] @@ -51,20 +51,18 @@ pub fn get_all_main_tabs() -> [NotebookMainEnum; NUMBER_OF_NOTEBOOK_MAIN_TABS] { pub enum NotebookUpperEnum { IncludedDirectories = 0, ExcludedDirectories, - ExcludedItems, - AllowedExtensions, + ItemsConfiguration, } -pub fn to_notebook_upper_enum(notebook_number: u32) -> NotebookUpperEnum { - match notebook_number { - 0 => NotebookUpperEnum::IncludedDirectories, - 1 => NotebookUpperEnum::ExcludedDirectories, - 2 => NotebookUpperEnum::ExcludedItems, - 3 => NotebookUpperEnum::AllowedExtensions, - _ => panic!("Invalid Upper Notebook Tab"), - } -} - -pub fn get_all_upper_tabs() -> [NotebookUpperEnum; NUMBER_OF_NOTEBOOK_UPPER_TABS] { - [to_notebook_upper_enum(0), to_notebook_upper_enum(1), to_notebook_upper_enum(2), to_notebook_upper_enum(3)] -} +// pub fn to_notebook_upper_enum(notebook_number: u32) -> NotebookUpperEnum { +// match notebook_number { +// 0 => NotebookUpperEnum::IncludedDirectories, +// 1 => NotebookUpperEnum::ExcludedDirectories, +// 2 => NotebookUpperEnum::ItemsConfiguration, +// _ => panic!("Invalid Upper Notebook Tab"), +// } +// } +// +// pub fn get_all_upper_tabs() -> [NotebookUpperEnum; NUMBER_OF_NOTEBOOK_UPPER_TABS] { +// [to_notebook_upper_enum(0), to_notebook_upper_enum(1), to_notebook_upper_enum(2)] +// } diff --git a/czkawka_gui/src/saving_loading.rs b/czkawka_gui/src/saving_loading.rs index 3adb398..a08efa5 100644 --- a/czkawka_gui/src/saving_loading.rs +++ b/czkawka_gui/src/saving_loading.rs @@ -3,13 +3,15 @@ use std::io::Write; use std::path::Path; use std::{env, fs}; +use czkawka_core::fl; use directories_next::ProjectDirs; use gtk::prelude::*; use gtk::{ScrolledWindow, TextView}; use crate::gui_settings::GuiSettings; -use crate::gui_upper_notepad::GuiUpperNotebook; +use crate::gui_upper_notebook::GuiUpperNotebook; use crate::help_functions::*; +use crate::language_functions::{get_language_from_combo_box_text, LANGUAGES_ALL}; // TODO organize this better, add specific functions that will allow to load from files specific strings const SAVE_FILE_NAME: &str = "czkawka_gui_config.txt"; @@ -167,6 +169,11 @@ pub fn save_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb data_to_save.push("--cache_prehash_minimal_file_size:".to_string()); let entry_settings_prehash_cache_file_minimal_size = settings.entry_settings_prehash_cache_file_minimal_size.clone(); data_to_save.push(entry_settings_prehash_cache_file_minimal_size.text().as_str().parse::().unwrap_or(0).to_string()); + + //// language + data_to_save.push("--language:".to_string()); + let combo_box_settings_language = settings.combo_box_settings_language.clone(); + data_to_save.push(get_language_from_combo_box_text(combo_box_settings_language.active_text().unwrap().to_string()).short_text.to_string()); } // Creating/Opening config file @@ -194,7 +201,7 @@ pub fn save_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb } } if data_saved { - add_text_to_text_view(&text_view_errors, format!("Saved configuration to file {}", config_file.display()).as_str()); + add_text_to_text_view(&text_view_errors, format!("{} {}", fl!("saving_loading_saving_success"), config_file.display()).as_str()); } else { add_text_to_text_view(&text_view_errors, format!("Failed to save configuration data to file {}", config_file.display()).as_str()); } @@ -225,6 +232,7 @@ enum TypeOfLoadedData { DeleteCacheSimilarVideos, UsePrehashCache, CachePrehashMinimalSize, + Language, } pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNotebook, settings: &GuiSettings, text_view_errors: &TextView, scrolled_window_errors: &ScrolledWindow) { @@ -278,6 +286,7 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb let mut delete_outdated_cache_similar_videos: bool = false; let mut use_prehash_cache: bool = false; let mut cache_prehash_minimal_size: u64 = 0; + let mut short_language: String = "en".to_string(); let mut current_type = TypeOfLoadedData::None; for (line_number, line) in loaded_data.replace("\r\n", "\n").split('\n').enumerate() { @@ -325,6 +334,8 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb current_type = TypeOfLoadedData::UsePrehashCache; } else if line.starts_with("--cache_prehash_minimal_file_size") { current_type = TypeOfLoadedData::CachePrehashMinimalSize; + } else if line.starts_with("--language") { + current_type = TypeOfLoadedData::Language; } else if line.starts_with("--") { current_type = TypeOfLoadedData::None; add_text_to_text_view( @@ -553,6 +564,16 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb ); } } + TypeOfLoadedData::Language => { + if LANGUAGES_ALL.iter().any(|e| e.short_text == line) { + short_language = line; + } else { + add_text_to_text_view( + &text_view_errors, + format!("Found invalid data in line {} \"{}\" isn't proper language value when loading file {:?}", line_number, line, config_file).as_str(), + ); + } + } } } } @@ -587,6 +608,15 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb let entry_allowed_extensions = upper_notebook.entry_allowed_extensions.clone(); entry_allowed_extensions.set_text(allowed_extensions.iter().map(|e| e.to_string() + ",").collect::().as_str()); + //// ComboText + { + for (index, lang) in LANGUAGES_ALL.iter().enumerate() { + if short_language == lang.short_text { + settings.combo_box_settings_language.set_active(Some(index as u32)); + } + } + } + //// Buttons settings.check_button_settings_load_at_start.set_active(loading_at_start); settings.check_button_settings_save_at_exit.set_active(saving_at_exit); @@ -616,7 +646,7 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb } if manual_execution { - add_text_to_text_view(&text_view_errors, format!("Properly loaded configuration from file {:?}", config_file).as_str()); + add_text_to_text_view(&text_view_errors, format!("{} {:?}", &fl!("saving_loading_reset_configuration"), config_file).as_str()); } } else { add_text_to_text_view(&text_view_errors, "Failed to get home directory, so can't load file."); @@ -699,8 +729,9 @@ pub fn reset_configuration(manual_clearing: bool, upper_notebook: &GuiUpperNoteb settings.check_button_settings_duplicates_delete_outdated_cache.set_active(true); settings.check_button_duplicates_use_prehash_cache.set_active(false); settings.entry_settings_prehash_cache_file_minimal_size.set_text("0"); + settings.combo_box_settings_language.set_active(Some(0)); } if manual_clearing { - add_text_to_text_view(&text_view_errors, "Current configuration was cleared."); + add_text_to_text_view(&text_view_errors, &fl!("saving_loading_reset_configuration")); } } diff --git a/czkawka_gui/ui/main_window.glade b/czkawka_gui/ui/main_window.glade index 97a5a98..8a8a4be 100644 --- a/czkawka_gui/ui/main_window.glade +++ b/czkawka_gui/ui/main_window.glade @@ -158,7 +158,7 @@ Author: Rafał Mikrut - + True True True @@ -442,40 +442,8 @@ Author: Rafał Mikrut True False + center vertical - - - True - False - 5 - 5 - Excluded items must contains * wildcard and should be separated by commas. - - - - - - False - True - 0 - - - - - True - False - 5 - This is slower than Excluded Directories, so use it carefully. - - - - - - False - True - 1 - - True @@ -484,7 +452,7 @@ Author: Rafał Mikrut 5 5 - + True False Excluded items @@ -514,76 +482,6 @@ Author: Rafał Mikrut 2 - - - 2 - - - - - True - False - Excluded Items - - - 2 - False - - - - - True - False - vertical - - - True - False - 5 - 5 - Allowed extensions must be separated by commas(by default all are available) - - - - - - False - True - 0 - - - - - True - False - 5 - Macros IMAGE, VIDEO, MUSIC, TEXT are available - - - - - - False - True - 1 - - - - - True - False - 5 - Usage example ".exe, IMAGE, VIDEO, .rar, 7z" - - - - - - False - True - 2 - - True @@ -592,7 +490,7 @@ Author: Rafał Mikrut 5 5 - + True False Allowed Extensions @@ -623,17 +521,17 @@ Author: Rafał Mikrut - 3 + 2 True False - Allowed Extensions + Items Configuration - 3 + 2 False @@ -666,7 +564,7 @@ Author: Rafał Mikrut 5 2 - + True False Check method: @@ -736,7 +634,7 @@ Author: Rafał Mikrut 5 5 - + True False Hash type: @@ -808,7 +706,7 @@ Author: Rafał Mikrut 2 8 - + True False Size(bytes) @@ -820,7 +718,7 @@ Author: Rafał Mikrut - + True False Min: @@ -847,7 +745,7 @@ Author: Rafał Mikrut - + True False Max: @@ -967,7 +865,7 @@ Author: Rafał Mikrut 2 8 - + True False Number of shown files @@ -1096,7 +994,7 @@ Author: Rafał Mikrut 5 2 - + True False Resize algorithm: @@ -1200,7 +1098,7 @@ Author: Rafał Mikrut 5 5 - + True False Hash type: @@ -1304,7 +1202,7 @@ Author: Rafał Mikrut 5 5 - + True False Hash size: @@ -1391,7 +1289,7 @@ Author: Rafał Mikrut 5 8 - + True False Size(bytes) @@ -1403,7 +1301,7 @@ Author: Rafał Mikrut - + True False Min: @@ -1430,7 +1328,7 @@ Author: Rafał Mikrut - + True False Max: @@ -1485,7 +1383,7 @@ Author: Rafał Mikrut 5 2 - + True False Similarity @@ -1497,7 +1395,7 @@ Author: Rafał Mikrut - + True False Very High @@ -1527,7 +1425,9 @@ Author: Rafał Mikrut True False - Minimal + 5 + 5 + Minimal False @@ -1609,7 +1509,7 @@ Author: Rafał Mikrut 2 8 - + True False Size(bytes) @@ -1621,7 +1521,7 @@ Author: Rafał Mikrut - + True False Min: @@ -1648,7 +1548,7 @@ Author: Rafał Mikrut - + True False Max: @@ -1689,7 +1589,7 @@ Author: Rafał Mikrut 5 2 - + True False Similarity @@ -1701,7 +1601,7 @@ Author: Rafał Mikrut - + True False Very High @@ -1728,10 +1628,12 @@ Author: Rafał Mikrut - + True False - Minimal + 5 + 5 + Minimal False @@ -1791,7 +1693,7 @@ Author: Rafał Mikrut 2 8 - + True False Size(bytes) @@ -1803,7 +1705,7 @@ Author: Rafał Mikrut - + True False Min: @@ -1830,7 +1732,7 @@ Author: Rafał Mikrut - + True False Max: diff --git a/czkawka_gui/ui/progress.glade b/czkawka_gui/ui/progress.glade index 6ea2f16..6afe4b4 100644 --- a/czkawka_gui/ui/progress.glade +++ b/czkawka_gui/ui/progress.glade @@ -78,7 +78,8 @@ Author: Rafał Mikrut 2 2 - + + label_progress_all_stages True False All stages: @@ -102,7 +103,8 @@ Author: Rafał Mikrut - + + label_progress_current_stage True False Current stage: diff --git a/czkawka_gui/ui/settings.glade b/czkawka_gui/ui/settings.glade index a4bfec9..df4254f 100644 --- a/czkawka_gui/ui/settings.glade +++ b/czkawka_gui/ui/settings.glade @@ -102,6 +102,42 @@ Author: Rafał Mikrut True False vertical + + + True + False + + + True + False + 5 + 10 + Language + + + False + True + 0 + + + + + True + False + + + True + True + 1 + + + + + False + True + 0 + + Load configuration at start @@ -114,7 +150,7 @@ Author: Rafał Mikrut False True - 0 + 1 @@ -129,7 +165,7 @@ Author: Rafał Mikrut False True - 1 + 2 @@ -144,7 +180,7 @@ Author: Rafał Mikrut False True - 2 + 3 @@ -159,7 +195,7 @@ Author: Rafał Mikrut False True - 3 + 4 @@ -174,7 +210,7 @@ Author: Rafał Mikrut False True - 4 + 5 @@ -189,7 +225,7 @@ Author: Rafał Mikrut False False - 5 + 6 @@ -204,7 +240,7 @@ Author: Rafał Mikrut False True - 6 + 7 @@ -219,7 +255,7 @@ Author: Rafał Mikrut False True - 7 + 8 @@ -334,12 +370,10 @@ Author: Rafał Mikrut True False - 4 - 4 4 4 - + True False Minimal size of files in bytes saved to cache @@ -405,12 +439,10 @@ Author: Rafał Mikrut True False - 4 - 4 4 4 - + True False Minimal size of files in bytes saved to prehash cache @@ -585,8 +617,6 @@ Author: Rafał Mikrut True False - 3 - 3 3 3 3 diff --git a/i18n.toml b/i18n.toml new file mode 100644 index 0000000..f2456c8 --- /dev/null +++ b/i18n.toml @@ -0,0 +1,13 @@ +# (Required) The language identifier of the language used in the +# source code for gettext system, and the primary fallback language +# (for which all strings must be present) when using the fluent +# system. +fallback_language = "en" + +# Use the fluent localization system. +[fluent] +# (Required) The path to the assets directory. +# The paths inside the assets directory should be structured like so: +# `assets_dir/{language}/{domain}.ftl` +assets_dir = "i18n" + diff --git a/i18n/en/czkawka_core.ftl b/i18n/en/czkawka_core.ftl new file mode 120000 index 0000000..32c5da8 --- /dev/null +++ b/i18n/en/czkawka_core.ftl @@ -0,0 +1 @@ +czkawka_gui.ftl \ No newline at end of file diff --git a/i18n/en/czkawka_gui.ftl b/i18n/en/czkawka_gui.ftl new file mode 100644 index 0000000..8a8f77f --- /dev/null +++ b/i18n/en/czkawka_gui.ftl @@ -0,0 +1,379 @@ +# Core +core_similarity_very_high = Very High +core_similarity_high = High +core_similarity_medium = Medium +core_similarity_small = Small +core_similarity_very_small = Very Small +core_similarity_minimal = Minimal + +# General +general_ok_button = Ok +general_close_button = Close + + +general_bytes = bytes +general_lost = lost + +# Main window +music_title_checkbox = Title +music_artist_checkbox = Artist +music_album_title_checkbox = Album Title +music_album_artist_checkbox = Album Artist +music_year_checkbox = Year +music_comparison_checkbox = Approximate Comparison + +duplicate_mode_name_checkbox = Name +duplicate_mode_size_checkbox = Size +duplicate_mode_hash_checkbox = Hash + +duplicate_mode_name_checkbox_tooltip = + Finds files which have same name. + + This mode not checking what file contain inside, so be carefully when using it. + +duplicate_mode_size_checkbox_tooltip = + Finds files which have same size. + + This mode not checking what file contain inside, so be carefully when using it. + +duplicate_mode_hash_checkbox_tooltip = + Finds files which have the same content. + + This mode hashes file and later compare this hashes to find duplicates. + + Tool heavily uses cache, so second and further scans of same data should be a lot of faster that first. + +duplicate_hash_checkbox_blake3 = Blake3 is cryptographic hash function. It is used as default hash algorithm, because it is very fast. +duplicate_hash_checkbox_crc32 = CRC32 is simple hash function. It should be faster than Blake3, but probably may have very rarely some collisions. +duplicate_hash_checkbox_xxh3 = XXH3 is very similar in case of performance and hash quality to Blake3, so such modes can be easily used. + +image_hash_checkbox_8 = Default hash size, with very high similarity it produce quite good results and don't save too much data too cache. +image_hash_checkbox_16 = More precise than 8, so can be used to find very similar pictures, but create bigger cache entries. +image_hash_checkbox_32 = Hash of this size provide very big similarity which is more than enough for most usages. +image_hash_checkbox_64 = Paranoid mode, such tool create really big cache files and will catch almost same images. + +main_notebook_duplicates = Duplicate Files +main_notebook_empty_directories = Empty Directories +main_notebook_big_files = Big Files +main_notebook_empty_files = Empty Files +main_notebook_temporary = Temporary Files +main_notebook_similar_images = Similar Images +main_notebook_similar_videos = Similar Videos +main_notebook_same_music = Music Duplicates +main_notebook_symlinks = Invalid Symlinks +main_notebook_broken_files = Broken Files + +main_tree_view_column_file_name = File Name +main_tree_view_column_folder_name = Folder Name +main_tree_view_column_path = Path +main_tree_view_column_modification = Modification Date +main_tree_view_column_size = Size +main_tree_view_column_similarity = Similarity +main_tree_view_column_dimensions = Dimensions +main_tree_view_column_title = Title +main_tree_view_column_artist = Artist +main_tree_view_column_year = Year +main_tree_view_column_album_title = Album Title +main_tree_view_column_album_artist = Album Artist +main_tree_view_column_symlink_file_name = Symlink File Name +main_tree_view_column_symlink_folder = Symlnik Folder +main_tree_view_column_destination_path = Destination Path +main_tree_view_column_type_of_error = Type Of Error + +main_label_check_method = Check method: +main_label_hash_type = Hash type: +main_label_hash_size = Hash size: +main_label_size_bytes = Size(bytes) +main_label_min_size = Min: +main_label_max_size = Max: +main_label_shown_files = Number of shown files: +main_label_resize_algorithm = Resize algorithm: +main_label_similarity = Similarity{" "} + +# Upper window +upper_recursive_button = Recursive +upper_recursive_button_tooltip = If selected, search also for files which are not placed directly under chosen folders + +upper_manual_add_included_button = Manual Add +upper_add_included_button = Add +upper_remove_included_button = Remove +upper_manual_add_excluded_button = Manual Add +upper_add_excluded_button = Add +upper_remove_excluded_button = Remove + +upper_manual_add_included_button_tooltip = Allows to add directory name to search by hand +upper_add_included_button_tooltip = Add new directory to search +upper_remove_included_button_tooltip = Delete directory from search +upper_manual_add_excluded_button_tooltip = Allows to add excluded directory name by hand +upper_add_excluded_button_tooltip = Add directory to be excluded in search +upper_remove_excluded_button_tooltip = Delete directory from excluded + +upper_notebook_items_configuration = Items Configuration +upper_notebook_excluded_directories = Excluded Directories +upper_notebook_included_directories = Included Directories + +upper_allowed_extensions_tooltip = + Allowed extensions must be separated by commas(by default all are available) + + Macros IMAGE, VIDEO, MUSIC, TEXT which adds multiple extensions at once are also available. + + Usage example ".exe, IMAGE, VIDEO, .rar, 7z" - this means that image(e.g. jpg, png), video(e.g. avi, mp4), exe, rar and 7z files will be scanned. + +upper_excluded_items_tooltip = + Excluded items must contains * wildcard and should be separated by commas. + This is slower than Excluded Directories, so use it carefully. + +upper_excluded_items = Excluded Items: +upper_allowed_extensions = Allowed Extensions: + + +# Popovers +popover_select_all = Select all +popover_unselect_all = Unselect all +popover_reverse = Reverse Selection +popover_select_all_except_oldest = Select all except oldest +popover_select_all_except_newest = Select all except newest +popover_select_one_oldest = Select one oldest +popover_select_one_newest = Select one newest +popover_select_custom = Select custom +popover_unselect_custom = Unselect custom +popover_select_all_images_except_biggest = Select all except biggest +popover_select_all_images_except_smallest = Select all except smallest + +popover_custom_path_check_button_entry_tooltip = + Allows to select records by its path. + + Example usage: + /home/pimpek/rzecz.txt can be found with /home/pim* + +popover_custom_name_check_button_entry_tooltip = + Allows to select records by file names. + + Example usage: + /usr/ping/pong.txt can be found with *ong* + +popover_custom_regex_check_button_entry_tooltip = + Allows to select records by specified Regex. + + With this mode, searched text is Path with Name + + Example usage: + /usr/bin/ziemniak.txt can be found with /ziem[a-z]+ + + This use default Rust regex implementation, so you can read more about it in https://docs.rs/regex. + +popover_custom_not_all_check_button_tooltip = + Prevents from selecting all records in group. + + This is enabled by default, because in most of situations user don't want to delete both original and duplicates files, but want to leave at least one file. + + Warning: This setting don't work if already user selected all results in group manually. + +popover_custom_regex_path_label = Path +popover_custom_regex_name_label = Name +popover_custom_regex_regex_label = Regex Path + Name +popover_custom_all_in_group_label = Don't select all records in group + +popover_custom_mode_unselect = Unselect Custom +popover_custom_mode_select = Select Custom + + +popover_invalid_regex = Regex is invalid +popover_valid_regex = Regex is valid + +# Bottom buttons +bottom_search_button = Search +bottom_select_button = Select +bottom_delete_button = Delete +bottom_save_button = Save +bottom_symlink_button = Symlink +bottom_hardlink_button = Hardlink +bottom_move_button = Move + +bottom_search_button_tooltip = Start to search for files/folders +bottom_select_button_tooltip = Selects records. Only selected files/folders can be later processed. +bottom_delete_button_tooltip = Delete selected files/folders +bottom_save_button_tooltip = Save data about search to file +bottom_symlink_button_tooltip = + Creates symbolic links. + Only works when at least 2 results in group are selected. + First is unchanged and second and later are symlinked to first. +bottom_hardlink_button_tooltip = + Creates hardlinks. + Only works when at least 2 results in group are selected. + First is unchanged and second and later are hardlinked to first. +bottom_move_button_tooltip = + Moves files to chosen folder. + It copy all files to folder without preserving directory tree. + When trying to move 2 files with identical name to folder, second will fail and show error. + +bottom_show_errors_tooltip = Show/Hide bottom error panel. +bottom_show_upper_notebook_tooltip = Show/Hide upper notebook panel. + +# Progress Window +progress_stop_button = Stop + +# About Window +about_repository_button_tooltip = Link to repository page with source code. +about_donation_button_tooltip = Link to donation page. +about_instruction_button_tooltip = Link to instruction page. + +about_repository_button = Repository +about_donation_button = Donation +about_instruction_button = Instruction + +# Header +header_setting_button_tooltip = Opens settings dialog. +header_about_button_tooltip = Opens dialog with info about app. + +# Settings +## General +settings_save_at_exit_button_tooltip = Saves configuration to file when closing app. +settings_load_at_start_button_tooltip = + Loading at start configuration from file. + + Not selecting this option will load default settings. +settings_confirm_deletion_button_tooltip = Shows confirmation dialog when clicking at delete button. +settings_confirm_link_button_tooltip = Shows confirmation dialog when clicking at hard/symlink button. +settings_confirm_group_deletion_button_tooltip = Shows dialog when trying to remove all records from group. +settings_show_text_view_button_tooltip = Shows error panel at bottom. +settings_use_cache_button_tooltip = Option to which allows to not use cache feature. +settings_use_trash_button_tooltip = When enabled it moves files to trash instead deleting them permanently. +settings_language_label_tooltip = Allows to choose language of interface from available ones. + +settings_save_at_exit_button = Save configuration at exit +settings_load_at_start_button = Load configuration at start +settings_confirm_deletion_button = Show confirm dialog when deleting any files +settings_confirm_link_button = Show confirm dialog when hard/symlinks any files +settings_confirm_group_deletion_button = Show confirm dialog when deleting all files in group +settings_show_text_view_button = Show bottom text panel +settings_use_cache_button = Use cache +settings_use_trash_button = Move deleted files to trash +settings_language_label = Language + +settings_multiple_delete_outdated_cache_checkbutton = Delete outdated cache entries automatically +settings_multiple_delete_outdated_cache_checkbutton_tooltip = + Allows to delete outdated cache results which points to non-existent files. + + When enabled, app make sure when loading records, that all points to valid files and ignore broken ones. + + Disabling this option, will help to scan files on external drives, so cache entries about them will not be purged in next scan. + + In case of having hundred of thousands records in cache, it is suggested to enable this option, to speedup cache loading and saving at start and end of scan. + +## Multiple - settings used in multiple tabs +settings_multiple_delete_outdated_cache_checkbutton = Delete outdated cache entries automatically +settings_multiple_delete_outdated_cache_checkbutton_tooltip = + Allows to delete outdated cache results which points to non-existent files. + + When enabled, app make sure when loading records, that all points to valid files and ignore broken ones. + + Disabling this option, will help to scan files on external drives, so cache entries about them will not be purged in next scan. + + In case of having hundred of thousands records in cache, it is suggested to enable this option, to speedup cache loading and saving at start and end of scan. + +settings_multiple_image_preview_checkbutton_tooltip = Shows preview at right side, when selecting image file. +settings_multiple_image_preview_checkbutton = Show image preview + +settings_multiple_clear_cache_button_tooltip = + Manually clear cache from outdated entries. + Should be used only if automatic clearing was disabled. + +settings_multiple_clear_cache_button = Remove outdated results from images cache + +## Duplicates +settings_duplicates_hide_hard_link_button_tooltip = + Hides all files except one, if are points to same data(are hardlinked). + + E.g. in case where on disk there is 7 files which are hardlinked to specific data and one different file with same data but different inode, then in duplicate finder will be visible only one unique file and one file from hardlinked ones. + +settings_duplicates_minimal_size_entry_tooltip = + Allows to set minimal size of file, which will be cached. + + Choosing smaller value, will generate more records which will speedup search, but slowdown cache loading/saving. + +settings_duplicates_prehash_checkbutton_tooltip = + Enables caching of prehash(hash computed from small part of file) which allows to earlier throw out non duplicated results. + + It is disabled by default because can cause in some situations slowdowns. + + It is heavily recommended to use it when scanning hundred of thousands or million files, because it can speedup search multiple times. + +settings_duplicates_prehash_minimal_entry_tooltip = Minimal size of cached entry. + +settings_duplicates_hide_hard_link_button = Hide hard links(only Linux and MacOS) +settings_duplicates_prehash_checkbutton = Use prehash cache + +settings_duplicates_minimal_size_cache_label = Minimal size of files in bytes saved to cache +settings_duplicates_minimal_size_cache_prehash_label = Minimal size of files in bytes saved to prehash cache + +## Saving/Loading settings +settings_saving_button_tooltip = Save current settings configuration to file. +settings_loading_button_tooltip = Load settings from file and replace current configuration with them. +settings_reset_button_tooltip = Reset current configuration to default one. + +settings_saving_button = Save configuration +settings_loading_button = Load configuration +settings_reset_button = Reset configuration + + +## Opening cache/config folders +settings_folder_cache_open_tooltip = + Opens folder where are stored txt files with cache. + + Modifying them may cause to show invalid results but also modifying e.g. path may save time when moving big amount of files to different place. + + You can copy this files between computers to save time on scanning again for files(of course if they have similar directory structure). + + In case of problems with cache, this files can be removed, so app will automatically regenerate them. + +settings_folder_settings_open_tooltip = + Opens folder where Czkawka config are stored. + + Modifying them by hand, may cause to break your workflow. + +settings_folder_cache_open = Open cache folder +settings_folder_settings_open = Open settings folder + +# Compute results +compute_stopped_by_user = Searching was stopped by user + +compute_found = Found +compute_duplicated_files_in = duplicated files in +compute_groups_which_took = groups which took +compute_groups = groups +compute_duplicates_for = duplicates for + +compute_empty_folders = empty folders +compute_empty_files = empty files +compute_biggest_files = biggest files +compute_temporary_files = temporary files +compute_similar_image = images +compute_similar_videos = videos +compute_music_files = music files +compute_symlinks = invalid symlinks +compute_broken_files = broken files + +# Progress window +progress_scanned = Scanned +progress_files = file +progress_folders = folders +progress_tags = Reading tags of +progress_hashing = Hashing +progress_checking = Checking +progress_size = size +progress_name = name +progress_analyzed_full_hash = Analyzed full hash of +progress_analyzed_partial_hash = Analyzed partial hash of + +# Saving loading +saving_loading_saving_success = Saved configuration to file +saving_loading_reset_configuration = Current configuration was cleared. +saving_loading_loading_success = Properly loaded configuration from file + +progress_current_stage = Current Stage:{" "} +progress_all_stages = All Stages:{" "} + +# Other +searching_for_data = Searching data, it may take a while, please wait... + diff --git a/i18n/pl/czkawka_core.ftl b/i18n/pl/czkawka_core.ftl new file mode 120000 index 0000000..32c5da8 --- /dev/null +++ b/i18n/pl/czkawka_core.ftl @@ -0,0 +1 @@ +czkawka_gui.ftl \ No newline at end of file diff --git a/i18n/pl/czkawka_gui.ftl b/i18n/pl/czkawka_gui.ftl new file mode 100644 index 0000000..816abe8 --- /dev/null +++ b/i18n/pl/czkawka_gui.ftl @@ -0,0 +1,366 @@ +# Core +core_similarity_very_high = Bardzo Duże +core_similarity_high = Duże +core_similarity_medium = Średnie +core_similarity_small = Małe +core_similarity_very_small = Bardzo Małe +core_similarity_minimal = Minimalne + +# Różne +general_ok_button = Ok +general_close_button = Zamknij + + +general_bytes = bajtów +general_lost = zaprzepaszczono + +# Główne okno(środek) +music_title_checkbox = Tytuł +music_artist_checkbox = Wykonawca +music_album_title_checkbox = Tytuł Albumu +music_album_artist_checkbox = Wykonawca Albumu +music_year_checkbox = Rok +music_comparison_checkbox = Przybliżone Porównywanie + +duplicate_mode_name_checkbox = Nazwa +duplicate_mode_size_checkbox = Rozmiar +duplicate_mode_hash_checkbox = Hash + + +duplicate_mode_name_checkbox_tooltip = + Służy do znajdowania plików o identycznych nazwach. + + Ten tryb nie sprawdza zawartości pliku, dlatego należy uważać przy jego stosowaniu. + +duplicate_mode_size_checkbox_tooltip = + Służy do znajdowania plików o identycznych rozmiarach. + + Ten tryb nie sprawdza zawartości pliku, dlatego należy uważać przy jego stosowaniu. + +duplicate_mode_hash_checkbox_tooltip = + Znajduje pliki z identyczną zawartością, bez względu na nazwę i rozszerzenie pliku. + + W tym trybie, każdy plik jest hasowany a następnie każdy hash jest porównywany z innymi. + + Ten tryb używa pamięci podręcznej do przechowywania raz obliczonych hashy, dlatego drugie i kolejne skanowanie, powinno być o wiele szybsze niż pierwsze. + +duplicate_hash_checkbox_blake3 = Blake3 jest kryptograficzną funkcją haszującą. Z racji połączenia szybkości i niskiej ilości kolizji jest to domyślny tryb. +duplicate_hash_checkbox_crc32 = CRC32 to prosta funkcja haszująca. Powinna być szybsza niż Blake3, lecz bardzo rzadko może mogą wystąpić kolizje hashy. +duplicate_hash_checkbox_xxh3 = XXH3 zarówno pod względem jakości hashu jak i wydajności jest podobny do Blake3, dlatego te algorytmy mogą być używane wymiennie. + +image_hash_checkbox_8 = Domyślna wielkość hasha, pozwala na wyszukanie obrazów od bardzo do minimalnie podobnych. Przy niższych poziomach podobieństwa, może grupować wiele niepodobnych obrazków. +image_hash_checkbox_16 = Bardziej precyzyjny od 8. Przydatny gdy ze względu na dużą ilość zdjęć i kolizji między hashami niższy hash wyświetla nieprawidłowe dane. +image_hash_checkbox_32 = Umożliwia wyszukiwanie niemal identycznych obrazów, różniących się niewielkimi szczegółami. +image_hash_checkbox_64 = Tryb predatora, tworzy ogromne hashe które porównywane, wczytywane i zapisywane znacznie dłużej od poprzedników. Z racji wad nie powinien być raczej stosowany. + +main_notebook_duplicates = Duplikaty +main_notebook_empty_directories = Puste Katalogi +main_notebook_big_files = Duże Pliki +main_notebook_empty_files = Puste Pliki +main_notebook_temporary = Pliki Tymczasowe +main_notebook_similar_images = Podobne Obrazy +main_notebook_similar_videos = Podobne Wideo +main_notebook_same_music = Podobna Muzyka +main_notebook_symlinks = Niepoprawne Symlinki +main_notebook_broken_files = Zepsute Pliki + +main_tree_view_column_file_name = Nazwa +main_tree_view_column_folder_name = Nazwa +main_tree_view_column_path = Ścieżka +main_tree_view_column_modification = Data Modyfikacji +main_tree_view_column_size = Rozmiar +main_tree_view_column_similarity = Podobieństwo +main_tree_view_column_dimensions = Wymiary +main_tree_view_column_title = Tytuł +main_tree_view_column_artist = Wykonawca +main_tree_view_column_year = Rok +main_tree_view_column_album_title = Tytuł Albumu +main_tree_view_column_album_artist = Wykonawca Albumu +main_tree_view_column_symlink_file_name = Nazwa Symlinka +main_tree_view_column_symlink_folder = Folder Symlinka +main_tree_view_column_destination_path = Docelowa Ścieżka +main_tree_view_column_type_of_error = Typ Błędu + +main_label_check_method = Metoda sprawdzania: +main_label_hash_type = Typ hashu: +main_label_hash_size = Rozmiar hashu: +main_label_size_bytes = Rozmiar(bajty) +main_label_min_size = Min: +main_label_max_size = Max: +main_label_shown_files = Liczba wyświetlanych plików: +main_label_resize_algorithm = Algorytm zmiany rozmiaru: +main_label_similarity = Podobieństwo{" "} +# Górne okno +upper_recursive_button = Rekursywnie +upper_recursive_button_tooltip = Jeśli zaznaczony, szuka plików i folderów również w katalogach wewnątrz, nawet jeśli nie znajdują się one bezpośrednio w tym folderze. + +upper_manual_add_included_button = Ręcznie Dodaj +upper_add_included_button = Dodaj +upper_remove_included_button = Usuń +upper_manual_add_excluded_button = Ręcznie Dodaj +upper_add_excluded_button = Dodaj +upper_remove_excluded_button = Usuń + +upper_manual_add_included_button_tooltip = Pozwala ręcznie dodać foldery do skanowania +upper_add_included_button_tooltip = Dodaje wybrany folder do przeskanowania +upper_remove_included_button_tooltip = Usuwa zaznaczony folder z listy do przeskanowania +upper_manual_add_excluded_button_tooltip = Pozwala ręcznie dodać foldery do ignorowania +upper_add_excluded_button_tooltip = Dodaje wybrany folder do ignorowanych +upper_remove_excluded_button_tooltip = Usuwa zaznaczony folder z ignorowanych + +upper_notebook_items_configuration = Konfiguracja Skanowania +upper_notebook_excluded_directories = Ignorowane Foldery +upper_notebook_included_directories = Przeszukiwane Foldery + +upper_allowed_extensions_tooltip = + Dozwolone rozszerzenia muszą być oddzielone za pomocą przecinków - brak rozszerzeń oznacza ż wszystkie rozszerzenia są używane. + + Makra IMAGE, VIDEO, MUSIC, TEXT które dodają rozrzerzenia w paczkach, również są wspierane + + Przykładowe użycie ".exe, IMAGE, VIDEO, .rar, 7z" oznacza że obrazy(np. jpg, png), widea(np. avi, mp4) oraz pliki z roszerzeniami exe, rar i 7z będą przeskanowane + +upper_excluded_items_tooltip = + Ignorowane obiekty mogą zawierać *(oznaczający dowolny ciąg znaków) i muszą być oddzielone za pomocą przecinków. + Działa o wiele wolniej niż Ignorowane Foldery, dlatego należy używać tego ostrożnie. + +upper_excluded_items = Ignorowane Obiekty: +upper_allowed_extensions = Dozwolone Rozszerzenia: + +# Zaznaczanie elementów +popover_select_all = Zaznacz wszystko +popover_unselect_all = Odznacz wszystko +popover_reverse = Odwróć zaznaczenie +popover_select_all_except_oldest = Zaznacz wszystkie oprócz najstarszego +popover_select_all_except_newest = Zaznacz wszystkie oprócz najnowszego +popover_select_one_oldest = Zaznacz jedno najstarsze +popover_select_one_newest = Zaznacz jedno najnowsze +popover_select_custom = Własne zaznaczanie +popover_unselect_custom = Własne odznaczanie +popover_select_all_images_except_biggest = Zaznacz wszystkie oprócz największego +popover_select_all_images_except_smallest = Zaznacz wszystkie oprócz najmniejszego + + +popover_custom_path_check_button_entry_tooltip = + Pozwala zaznaczać rekordy według ścieżki. + + Przykładowe użycie: + /home/pimpek/rzecz.txt może zostać znalezione poprzez /home/pim* + +popover_custom_name_check_button_entry_tooltip = + Pozwala zaznaczać rekordy według ścieżki. + + Przykładowe użycie: + /usr/ping/pong.txt może zostać znalezione poprzez *ong* + +popover_custom_regex_check_button_entry_tooltip = + Pozwala wyszukiwać rekordy za pomocą regexów. + + W tym trybie przeszukiwanym tekstem jest pełna ścieżka(nazwa + ścieżka) + + Example usage: + /usr/bin/ziemniak.txt może zostać znalezione with /ziem[a-z]+ + + Używana jest domyśla implementacja Rust regex o której użyciu można więcej przeczytać tutaj - https://docs.rs/regex. + +popover_custom_not_all_check_button_tooltip = + Zapobiega przed zaznaczeniem wszystkich rekordów w danej grupie. + + Tryb jest domyślnie aktywny, ponieważ w większości przypadków zaznaczenie wszystkich rekordów w grupie nie jest czymś czego oczekuje użytkownik. + + Uwaga: To ustawienie nie powoduje pomija grupy w których to użytkownik wcześniej zaznaczył wszystkie rekordy ręcznie. + + +popover_custom_regex_path_label = Ścieżka +popover_custom_regex_name_label = Nazwa +popover_custom_regex_regex_label = Regex - Pełna ścieżka +popover_custom_all_in_group_label = Nie zaznaczaj wszystkich rekordów w grupie + +popover_custom_mode_unselect = Własne odznaczanie +popover_custom_mode_select = Własne zaznaczanie + + +popover_invalid_regex = Regex jest niepoprawny +popover_valid_regex = Regex jest poprawny +# Przyciski na dole +bottom_search_button = Szukaj +bottom_select_button = Zaznacz +bottom_delete_button = Usuń +bottom_save_button = Zapisz +bottom_symlink_button = Symlink +bottom_hardlink_button = Hardlink +bottom_move_button = Przenieś + +bottom_search_button_tooltip = Rozpocznij przeszukiwanie. +bottom_select_button_tooltip = Zaznacz elementy. +bottom_delete_button_tooltip = Usuń zaznaczone elementy. +bottom_save_button_tooltip = Zapisz informacje o skanowaniu. +bottom_symlink_button_tooltip = + Tworzy linki symboliczne. + Działa tylko jeśli przynajmniej 2 wyniki są zaznaczone w danej grupie. + Pierwszy zaznaczony nie jest modyfikowany, a drugi i kolejne są usuwane i tworzone linki symboliczne w ich miejscach. +bottom_hardlink_button_tooltip = + Tworzy twarde linki. + Działa tylko jeśli przynajmniej 2 wyniki są zaznaczone w danej grupie. + Pierwszy zaznaczony nie jest modyfikowany, a drugi i kolejne są usuwane i tworzone twarde linki w ich miejscach. +bottom_move_button_tooltip = + Przenosi pliki do podanej lokalizacji. + Kopiuje pliki bez zachowywania struktury katalogów. + Podczas próby skopiowania 2 plików z identycznymi nazwami, drugi nie zostanie przeniesiony i błąd zostanie wyświetlony. + +bottom_show_errors_tooltip = Pokazuje/ukrywa dolny panel z informacjami. +bottom_show_upper_notebook_tooltip = Pokazuje/ukrywa górny panel. + +# Progress Window +progress_stop_button = Stop + +# About Window +about_repository_button_tooltip = Link do repozytorium z kodem źródłowym +about_donation_button_tooltip = Link do strony z dotacjami. +about_instruction_button_tooltip = Link do strony z instrukcją. + +about_repository_button = Repozytorium +about_donation_button = Dotacje +about_instruction_button = Instrukcja(ENG) + +# Header +header_setting_button_tooltip = Otwórz okno z ustawieniami programu. +header_about_button_tooltip = Otwórz okno z informacjami o programie. + +# Settings +## General +settings_save_at_exit_button_tooltip = Zapisuje konfigurację programu podczas wychodzenia z niego. +settings_load_at_start_button_tooltip = + Ładowanie plików na starcie z plików. + + Nie zaznaczenie tej opcji, spowoduje załadowanie domyślnych ustawień. +settings_confirm_deletion_button_tooltip = Wyświetla okno potwierdzające usuwanie plików. +settings_confirm_link_button_tooltip = Wyświetla okno potwierdzające usuwanie, gdy tworzone są hard/symlinki. +settings_confirm_group_deletion_button_tooltip = Wyświetla okno potwierdzające usuwanie, gdy wszystkie rekordy w danej grupie są zaznaczone. +settings_show_text_view_button_tooltip = Pokazuje na dole ekranu panel tekstowy. +settings_use_cache_button_tooltip = Umożliwia zapisywanie rekordów do pamięci podręcznej. +settings_use_trash_button_tooltip = Przenosi pliki do kosza zamiast usuwać je permanentnie. + +settings_save_at_exit_button = Zapisuj konfigurację przy wyłączaniu +settings_load_at_start_button = Ładuj ustawienia na starcie z pliku +settings_confirm_deletion_button = Pokazuj okno potwierdzające usuwanie plików +settings_confirm_link_button = Pokazuj potwierdzenie usuwania hard/symlinków +settings_confirm_group_deletion_button = Pokazuj okno potwierdzające usuwanie wszystkich obiektów w grupie +settings_show_text_view_button = Pokazuj panel tekstowy na dole +settings_use_cache_button = Używaj pamięci podręcznej +settings_use_trash_button = Przenoś pliki do kosza + + +## Multiple - ustawienia wskazywane przez większość zakładek +settings_multiple_delete_outdated_cache_checkbutton = Usuwaj automatycznie nieaktualne rekordy z pamięci podręcznej +settings_multiple_delete_outdated_cache_checkbutton_tooltip = + Pozwala na automatyczne usuwanie rekordów, które wskazują na nieaktualne pliki. + + W przypadku gdy pole jest zaznaczone, upewnij się, że wszystkie dyski zewnętrzne są podpięte by nie stracić zapisanych hashów z pamięci podręcznej. + + Wyłączenie tej opcji spowoduje, że nawet przy skanowaniu odpiętych dysków, rekordy w pamięci podręcznej nie będą usuwane. + + W przypadku posiadania dziesiątek czy setek tysięcy rekordów w pamięci podręcznej, zalecane jest zaznaczenie tej opcji, ponieważ przyspiesza to ładowanie i zapisywanie pamięci podręcznej. + +settings_multiple_image_preview_checkbutton_tooltip = Pokazuje podgląd obrazów po ich zaznaczeniu po prawej stronie aplikacji. +settings_multiple_image_preview_checkbutton = Pokazuj podgląd obrazów + +settings_multiple_clear_cache_button_tooltip = + Ręcznie czyści pamięć podręczną z nieaktualnych danych. + Opcja powinna być używana tylko gdy automatyczne czyszczenie pamięci podręcznej jest wyłączone. + +settings_multiple_clear_cache_button = Usuń nieaktualne dane z pamięci podręcznej + +## Duplicates +settings_duplicates_hide_hard_link_button_tooltip = + Ukrywa wszystkie pliki oprócz jednego, jeśli wskazują na dokładnie ten sam plik(to samo inode). + + Przykładowo - gdy program znalazł 7 plików na dokładnie ten sam plik(to samo inode) a dodatkowo jeden na inny, to w wynikach wyszukiwania wyświetlą się jedynie 2 wyniki - jeden z pierwszej grupy i drugi z drugiej. + +settings_duplicates_minimal_size_entry_tooltip = + Opcja umożliwia ustawienie minimalnej wielkości pliku, której hash będzie zapisywany do pamięci podręcznej. + + Im mniejsza wartość, tym hash większej ilości plików będzie przechowywany w pamięci podręcznej, co przyspieszy wyszukiwanie plików lecz jednocześnie spowolni wczytywanie/zapisywanie hashu do pliku. + +settings_duplicates_prehash_checkbutton_tooltip = + Umożliwia zapisywanie cząstkowego hashu do pamięci podręcznej, który umożliwia na wcześniejsze wyrzucenie plików z unikalnymi rozmiarami. + + Domyślnie jest zablokowane, ponieważ może powodować spowolnione skanowanie w niektórych sytuacjach. + + Jest mocno polecane osobom które skanują tylko i wyłącznie katalogi zawierające dziesiątki lub setki tysięcy lub nawet miliony plików, ponieważ może to wielokrotnie przyspieszyć proces skanowania. + +settings_duplicates_prehash_minimal_entry_tooltip = Minimalny rozmiar pliku, którego cząstowy hash będzie zapisywany do pamięci podręcznej. + +settings_duplicates_hide_hard_link_button = Ukrywaj twarde dowiązania(nie działa na Windowsie) +settings_duplicates_prehash_checkbutton = Używaj pamięci podręcznej dla hashy cząstkowych + +settings_duplicates_minimal_size_cache_label = Wielkość pliku, od którego hash będzie zapisywany w pamięci podręcznej +settings_duplicates_minimal_size_cache_prehash_label = Wielkość pliku, od którego cząstkowy hash będzie zapisywany w pamięci podręcznej + +## Saving/Loading settings +settings_saving_button_tooltip = Zapisuje aktualne ustawienia do pliku. +settings_loading_button_tooltip = Ładuje ustawienia z pliku. +settings_reset_button_tooltip = Resetuje aktualne ustawienia do domyślnie używanych przez aplikację. + +settings_saving_button = Zapisanie ustawień +settings_loading_button = Załadowanie ustawień +settings_reset_button = Reset ustawień + + +## Opening cache/config folders +settings_folder_cache_open_tooltip = + Otwiera folder gdzie przechowywana jest pamięć podręczna aplikacji. + + Jej ręczne modyfikowanie może powodować wyświetlanie niepoprawnych wyników lub jej uszkodzenie spowoduje koniecznośc ponownej generacji, lecz umożliwia też oszczędzenie czasu przy przesuwaniu większej ilości plików. + + Można pliki kopiować pomiędzy komputerami by zaoszczędzić czas na hashowaniu plików(oczywiście tylko gdy dane są przechowywane w identycznej strukturze katalogów na komputerach). + +settings_folder_settings_open_tooltip = + Otwiera folder gdzie Czkawka przechowuje ustawienia. + + Ich ręczna zmiana, może spowodować różne błędy i kataklizmy, którym fiziologom się nie śniły. + +settings_folder_cache_open = Otwórz folder pamięci podręcznej +settings_folder_settings_open = Otwórz folder ustawień + +# Compute results +compute_stopped_by_user = Przeszukiwanie zostało zatrzymane przez użytkownika + +compute_found = Znaleziono +compute_duplicated_files_in = duplikatów w +compute_groups_which_took = grupach, które zabrały +compute_groups = grup +compute_duplicates_for = duplikatów dla + +compute_empty_folders = pustych folderów +compute_empty_files = pustych plików +compute_biggest_files = największych plików +compute_temporary_files = plików tymczasowych +compute_similar_image = obrazów +compute_similar_videos = plików wideo +compute_music_files = plików muzycznych +compute_symlinks = niepoprawnych linków symbolicznych +compute_broken_files = zepsutych plików + +# Progress window +progress_scanned = Przeskanowano +progress_files = plików +progress_folders = folderów +progress_tags = Sczytano tagi z +progress_hashing = Przehashowano +progress_checking = Sprawdzono +progress_size = rozmiar +progress_name = nazwa +progress_analyzed_full_hash = Przeanalizowano pełny hash +progress_analyzed_partial_hash = Przeanalizowano częściowy hash + +progress_current_stage = Aktualny Etap:{" "} +progress_all_stages = Wszystkie Etapy:{" "} + + +# Other +searching_for_data = Przeszukiwanie dysku, może to potrwać chwilę, proszę czekać... + +# Saving loading +saving_loading_saving_success = Zapisano konfigurację do pliku +saving_loading_reset_configuration = Przywrócono domyślą konfigurację. +saving_loading_loading_success = Poprawnie załadowano konfigurację z pliku diff --git a/instructions/Translations.md b/instructions/Translations.md new file mode 100644 index 0000000..5df4d6f --- /dev/null +++ b/instructions/Translations.md @@ -0,0 +1,67 @@ +# Translations + +New feature in Czkawka 4.0 is ability to translate interface of GTK app. + +App use Fluent localization system - https://projectfluent.org/ + +Main/Default language is English, but also Polish is officially supported. + +## How to translate Czkawka? + +Base translatable strings are placed under `i18n/en/czkawka_gui.ftl` file. +`czkawka_core.ftl` is just a symlink which point to file from above(I had problems with splitting string into two separate crates) + +Since such strings are heavily integrated into build system, so to check status of translation it is required to recompile Czkawka. + +`czkawka_gui.ftl` file contains lines in this format: +`id_of_message` = `translated_string` +e.g. +``` +upper_manual_add_included_button_tooltip = Allows to add directory name to search by hand +``` + +to create new folder with translations, it is required to create copy of `i18n/en` folder and give it name from [ISO 639-1 code standard](https://www.loc.gov/standards/iso639-2/php/code_list.php) - e.g. pl, en, ru, fr etc. + +Next only translated strings needs to be changed + +## Testing translation +### Replacing en folder +The simplest method is to remove `en` folder and replace it with needed one. +Next Czkawka needs to be compiled and run. + +### Adding new translation +Recommended way to test translation, is to add it directly to Czkawka source code. + +After creating proper and well named folder and translating string inside it, to be able to be able to choose this language file it is required to modify `czkawka_gui/src/language_functions.rs` file. + +```rust +pub const LANGUAGES_ALL: [Language; 2] = [ + Language { + combo_box_text: "English (en)", + short_text: "en", + }, + Language { + combo_box_text: "Polski (pl)", + short_text: "pl", + }, +]; +``` + +The only thing which is required to change is `LANGUAGES_ALL` constant. + +Number of items must be changed `[Language; 2]` -> `[Language; 3]`. + +Next new record must be added to array. +`combo_box_text` must contains translated language name(so `Polski` is used instead `Polish`), to help find people to find their native(or not) language. +`short_text` is `ISO 639-1` code which need to match with county code and name of folder inside `i18n`. +``` + Language { + combo_box_text: "Polski (pl)", + short_text: "pl", + }, +``` + + + + +