Browse Source

Add support for translations (#469)

* 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.
pull/498/head
Rafał Mikrut 6 months ago committed by GitHub
parent
commit
77a48ca6aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 354
      Cargo.lock
  2. 2
      README.md
  3. 5
      czkawka_core/Cargo.toml
  4. 13
      czkawka_core/i18n.toml
  5. 1
      czkawka_core/src/lib.rs
  6. 34
      czkawka_core/src/localizer.rs
  7. 25
      czkawka_core/src/similar_images.rs
  8. 6
      czkawka_gui/Cargo.toml
  9. 13
      czkawka_gui/i18n.toml
  10. 99
      czkawka_gui/src/compute_results.rs
  11. 5
      czkawka_gui/src/connect_button_delete.rs
  12. 5
      czkawka_gui/src/connect_button_hardlink.rs
  13. 3
      czkawka_gui/src/connect_button_move.rs
  14. 3
      czkawka_gui/src/connect_button_search.rs
  15. 46
      czkawka_gui/src/connect_change_language.rs
  16. 13
      czkawka_gui/src/connect_notebook_tabs.rs
  17. 46
      czkawka_gui/src/connect_popovers.rs
  18. 45
      czkawka_gui/src/connect_progress_window.rs
  19. 9
      czkawka_gui/src/connect_selection_of_directories.rs
  20. 13
      czkawka_gui/src/gui_about.rs
  21. 38
      czkawka_gui/src/gui_bottom_buttons.rs
  22. 31
      czkawka_gui/src/gui_data.rs
  23. 9
      czkawka_gui/src/gui_header.rs
  24. 218
      czkawka_gui/src/gui_main_notebook.rs
  25. 14
      czkawka_gui/src/gui_popovers.rs
  26. 17
      czkawka_gui/src/gui_progress_dialog.rs
  27. 120
      czkawka_gui/src/gui_settings.rs
  28. 121
      czkawka_gui/src/gui_upper_notebook.rs
  29. 82
      czkawka_gui/src/gui_upper_notepad.rs
  30. 11
      czkawka_gui/src/help_functions.rs
  31. 9
      czkawka_gui/src/initialize_gui.rs
  32. 26
      czkawka_gui/src/language_functions.rs
  33. 8
      czkawka_gui/src/main.rs
  34. 30
      czkawka_gui/src/notebook_enums.rs
  35. 39
      czkawka_gui/src/saving_loading.rs
  36. 170
      czkawka_gui/ui/main_window.glade
  37. 6
      czkawka_gui/ui/progress.glade
  38. 62
      czkawka_gui/ui/settings.glade
  39. 13
      i18n.toml
  40. 1
      i18n/en/czkawka_core.ftl
  41. 379
      i18n/en/czkawka_gui.ftl
  42. 1
      i18n/pl/czkawka_core.ftl
  43. 366
      i18n/pl/czkawka_gui.ftl
  44. 67
      instructions/Translations.md

354
Cargo.lock generated

@ -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"
@ -1097,11 +1261,31 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "intl-memoizer"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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.1"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
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"

2
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/)

5
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 = []

13
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"

1
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");

34
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<FluentLanguageLoader> = 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<dyn Localizer> {
Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
}

25
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!();
}

6
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"] }

13
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"

99
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
{

5
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: &gtk::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: &gtk::Window) -> (Dialog, CheckBu
fn create_dialog_group_deletion(window_main: &gtk::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?"));

5
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: &gtk::TreeView, column_file_name: i32, column
fn create_dialog_non_group(window_main: &gtk::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: &gtk::C
fn create_dialog_ask_for_linking(window_main: &gtk::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");

3
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: &gtk::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();

3
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);

46
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);
// }
// }
}

13
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(&current_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(&current_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();
}
}
}
});
}

46
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: &gtk::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: &gtk::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!("popove