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 12 months ago committed by GitHub
parent 5f774e03bd
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"

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

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

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

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

@ -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))
}

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

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

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

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

@ -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,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");

@ -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();

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

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

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

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