general: Add translation infrastructure
This commit is contained in:
parent
58a77ec9fa
commit
1db46013d9
9 changed files with 260 additions and 13 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -879,6 +879,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
"regex",
|
||||||
"svg_metadata",
|
"svg_metadata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1151,9 +1152,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.4"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -1162,9 +1163,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.25"
|
version = "0.6.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ pretty_env_logger = "0.4.0"
|
||||||
futures-channel = "0.3.19"
|
futures-channel = "0.3.19"
|
||||||
futures = "0.3.21"
|
futures = "0.3.21"
|
||||||
svg_metadata = "0.4.2"
|
svg_metadata = "0.4.2"
|
||||||
|
regex = "1.6.0"
|
||||||
|
|
||||||
[dependencies.ashpd]
|
[dependencies.ashpd]
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
|
||||||
10
po/POTFILES
10
po/POTFILES
|
|
@ -1,4 +1,12 @@
|
||||||
|
data/gtk/help_overlay.ui
|
||||||
|
data/gtk/image_page.ui
|
||||||
|
data/gtk/image_view.ui
|
||||||
|
data/gtk/properties_view.ui
|
||||||
|
data/gtk/window.ui
|
||||||
data/org.gnome.Loupe.desktop.in
|
data/org.gnome.Loupe.desktop.in
|
||||||
data/org.gnome.Loupe.appdata.xml.in
|
data/org.gnome.Loupe.appdata.xml.in
|
||||||
data/org.gnome.Loupe.gschema.xml
|
data/org.gnome.Loupe.gschema.xml
|
||||||
src/window.ui
|
src/widgets/image_view.rs
|
||||||
|
src/widgets/properties_view.rs
|
||||||
|
src/widgets/application.rs
|
||||||
|
src/widgets/window.rs
|
||||||
|
|
@ -1 +1,13 @@
|
||||||
i18n.gettext('loupe', preset: 'glib')
|
i18n.gettext(
|
||||||
|
meson.project_name(),
|
||||||
|
args: [
|
||||||
|
'--keyword=i18n',
|
||||||
|
'--keyword=i18n_f',
|
||||||
|
'--keyword=i18n_k',
|
||||||
|
'--keyword=ni18n:1,2',
|
||||||
|
'--keyword=ni18n_f:1,2',
|
||||||
|
'--keyword=ni18n_k:1,2'
|
||||||
|
],
|
||||||
|
preset: 'glib'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use crate::deps::*;
|
use crate::deps::*;
|
||||||
|
use crate::i18n::*;
|
||||||
|
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
|
|
@ -173,9 +174,9 @@ impl LpApplication {
|
||||||
String::from("Jakub Steiner"),
|
String::from("Jakub Steiner"),
|
||||||
String::from("Tobias Bernard"),
|
String::from("Tobias Bernard"),
|
||||||
])
|
])
|
||||||
.copyright("© 2021 The GNOME Project")
|
.copyright(&i18n("© 2021 The GNOME Project"))
|
||||||
.license_type(gtk::License::Gpl30)
|
.license_type(gtk::License::Gpl30)
|
||||||
.program_name("Image Viewer")
|
.program_name(&i18n("Image Viewer"))
|
||||||
.logo_icon_name(config::APP_ID)
|
.logo_icon_name(config::APP_ID)
|
||||||
.version(config::VERSION)
|
.version(config::VERSION)
|
||||||
.build();
|
.build();
|
||||||
|
|
|
||||||
221
src/i18n.rs
Normal file
221
src/i18n.rs
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
// i18n.rs
|
||||||
|
//
|
||||||
|
// Copyright 2018 Daniel García Moreno <danigm@wadobo.com>
|
||||||
|
//
|
||||||
|
// Copied from https://gitlab.gnome.org/GNOME/fractal/-/blob/376248fd4a3bff05897ef39111a4e6d1a558f333/fractal-gtk/src/util/i18n.rs
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
use gettextrs::gettext;
|
||||||
|
use gettextrs::ngettext;
|
||||||
|
use gettextrs::npgettext;
|
||||||
|
use gettextrs::pgettext;
|
||||||
|
use regex::Captures;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn freplace(input: String, args: &[&str]) -> String {
|
||||||
|
let mut parts = input.split("{}");
|
||||||
|
let mut output = parts.next().unwrap_or_default().to_string();
|
||||||
|
for (p, a) in parts.zip(args.iter()) {
|
||||||
|
output += &(a.to_string() + &p.to_string());
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn kreplace(input: String, kwargs: &[(&str, &str)]) -> String {
|
||||||
|
let mut s = input;
|
||||||
|
for (k, v) in kwargs {
|
||||||
|
if let Ok(re) = Regex::new(&format!("\\{{{}\\}}", k)) {
|
||||||
|
s = re
|
||||||
|
.replace_all(&s, |_: &Captures<'_>| v.to_string())
|
||||||
|
.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple translations functions
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn i18n(format: &str) -> String {
|
||||||
|
gettext(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn i18n_f(format: &str, args: &[&str]) -> String {
|
||||||
|
let s = gettext(format);
|
||||||
|
freplace(s, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn i18n_k(format: &str, kwargs: &[(&str, &str)]) -> String {
|
||||||
|
let s = gettext(format);
|
||||||
|
kreplace(s, kwargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singular and plural translations functions
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn ni18n(single: &str, multiple: &str, number: u32) -> String {
|
||||||
|
ngettext(single, multiple, number)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn ni18n_f(single: &str, multiple: &str, number: u32, args: &[&str]) -> String {
|
||||||
|
let s = ngettext(single, multiple, number);
|
||||||
|
freplace(s, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn ni18n_k(single: &str, multiple: &str, number: u32, kwargs: &[(&str, &str)]) -> String {
|
||||||
|
let s = ngettext(single, multiple, number);
|
||||||
|
kreplace(s, kwargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translations with context functions
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn pi18n(ctx: &str, format: &str) -> String {
|
||||||
|
pgettext(ctx, format)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn pi18n_f(ctx: &str, format: &str, args: &[&str]) -> String {
|
||||||
|
let s = pgettext(ctx, format);
|
||||||
|
freplace(s, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn pi18n_k(ctx: &str, format: &str, kwargs: &[(&str, &str)]) -> String {
|
||||||
|
let s = pgettext(ctx, format);
|
||||||
|
kreplace(s, kwargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singular and plural with context
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn pni18n(ctx: &str, single: &str, multiple: &str, number: u32) -> String {
|
||||||
|
npgettext(ctx, single, multiple, number)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn pni18n_f(ctx: &str, single: &str, multiple: &str, number: u32, args: &[&str]) -> String {
|
||||||
|
let s = npgettext(ctx, single, multiple, number);
|
||||||
|
freplace(s, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn pni18n_k(
|
||||||
|
ctx: &str,
|
||||||
|
single: &str,
|
||||||
|
multiple: &str,
|
||||||
|
number: u32,
|
||||||
|
kwargs: &[(&str, &str)],
|
||||||
|
) -> String {
|
||||||
|
let s = npgettext(ctx, single, multiple, number);
|
||||||
|
kreplace(s, kwargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_i18n() {
|
||||||
|
let out = i18n("translate1");
|
||||||
|
assert_eq!(out, "translate1");
|
||||||
|
|
||||||
|
let out = ni18n("translate1", "translate multi", 1);
|
||||||
|
assert_eq!(out, "translate1");
|
||||||
|
let out = ni18n("translate1", "translate multi", 2);
|
||||||
|
assert_eq!(out, "translate multi");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_i18n_f() {
|
||||||
|
let out = i18n_f("{} param", &["one"]);
|
||||||
|
assert_eq!(out, "one param");
|
||||||
|
|
||||||
|
let out = i18n_f("middle {} param", &["one"]);
|
||||||
|
assert_eq!(out, "middle one param");
|
||||||
|
|
||||||
|
let out = i18n_f("end {}", &["one"]);
|
||||||
|
assert_eq!(out, "end one");
|
||||||
|
|
||||||
|
let out = i18n_f("multiple {} and {}", &["one", "two"]);
|
||||||
|
assert_eq!(out, "multiple one and two");
|
||||||
|
|
||||||
|
let out = ni18n_f("singular {} and {}", "plural {} and {}", 2, &["one", "two"]);
|
||||||
|
assert_eq!(out, "plural one and two");
|
||||||
|
let out = ni18n_f("singular {} and {}", "plural {} and {}", 1, &["one", "two"]);
|
||||||
|
assert_eq!(out, "singular one and two");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_i18n_k() {
|
||||||
|
let out = i18n_k("{one} param", &[("one", "one")]);
|
||||||
|
assert_eq!(out, "one param");
|
||||||
|
|
||||||
|
let out = i18n_k("middle {one} param", &[("one", "one")]);
|
||||||
|
assert_eq!(out, "middle one param");
|
||||||
|
|
||||||
|
let out = i18n_k("end {one}", &[("one", "one")]);
|
||||||
|
assert_eq!(out, "end one");
|
||||||
|
|
||||||
|
let out = i18n_k("multiple {one} and {two}", &[("one", "1"), ("two", "two")]);
|
||||||
|
assert_eq!(out, "multiple 1 and two");
|
||||||
|
|
||||||
|
let out = i18n_k("multiple {two} and {one}", &[("one", "1"), ("two", "two")]);
|
||||||
|
assert_eq!(out, "multiple two and 1");
|
||||||
|
|
||||||
|
let out = i18n_k("multiple {one} and {one}", &[("one", "1"), ("two", "two")]);
|
||||||
|
assert_eq!(out, "multiple 1 and 1");
|
||||||
|
|
||||||
|
let out = ni18n_k(
|
||||||
|
"singular {one} and {two}",
|
||||||
|
"plural {one} and {two}",
|
||||||
|
1,
|
||||||
|
&[("one", "1"), ("two", "two")],
|
||||||
|
);
|
||||||
|
assert_eq!(out, "singular 1 and two");
|
||||||
|
let out = ni18n_k(
|
||||||
|
"singular {one} and {two}",
|
||||||
|
"plural {one} and {two}",
|
||||||
|
2,
|
||||||
|
&[("one", "1"), ("two", "two")],
|
||||||
|
);
|
||||||
|
assert_eq!(out, "plural 1 and two");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pi18n() {
|
||||||
|
let out = pi18n("This is the context", "translate1");
|
||||||
|
assert_eq!(out, "translate1");
|
||||||
|
|
||||||
|
let out = pni18n("context", "translate1", "translate multi", 1);
|
||||||
|
assert_eq!(out, "translate1");
|
||||||
|
let out = pni18n("The context string", "translate1", "translate multi", 2);
|
||||||
|
assert_eq!(out, "translate multi");
|
||||||
|
|
||||||
|
let out = pi18n_f("Context for translation", "{} param", &["one"]);
|
||||||
|
assert_eq!(out, "one param");
|
||||||
|
|
||||||
|
let out = pi18n_k("context", "{one} param", &[("one", "one")]);
|
||||||
|
assert_eq!(out, "one param");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ use gtk::gio::{self, prelude::*};
|
||||||
mod application;
|
mod application;
|
||||||
mod config;
|
mod config;
|
||||||
mod file_model;
|
mod file_model;
|
||||||
|
mod i18n;
|
||||||
mod thumbnail;
|
mod thumbnail;
|
||||||
mod util;
|
mod util;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use crate::deps::*;
|
use crate::deps::*;
|
||||||
|
use crate::i18n::*;
|
||||||
|
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
|
|
@ -375,8 +376,8 @@ impl LpImageView {
|
||||||
wallpaper::SetOn::Background,
|
wallpaper::SetOn::Background,
|
||||||
)
|
)
|
||||||
.await {
|
.await {
|
||||||
Ok(_) => "Set as wallpaper.",
|
Ok(_) => i18n("Set as wallpaper."),
|
||||||
Err(_) => "Could not set wallpaper.",
|
Err(_) => i18n("Could not set wallpaper."),
|
||||||
};
|
};
|
||||||
|
|
||||||
view.activate_action(
|
view.activate_action(
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use crate::deps::*;
|
use crate::deps::*;
|
||||||
|
use crate::i18n::*;
|
||||||
|
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
|
|
@ -180,7 +181,7 @@ mod imp {
|
||||||
obj.set_image_from_file(&file, false);
|
obj.set_image_from_file(&file, false);
|
||||||
} else {
|
} else {
|
||||||
obj.show_toast(
|
obj.show_toast(
|
||||||
format!("\"{}\" is not a valid image.", info.display_name()),
|
i18n_f("\"{}\" is not a valid image.", &[&info.display_name()]),
|
||||||
adw::ToastPriority::High,
|
adw::ToastPriority::High,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -296,7 +297,7 @@ impl LpWindow {
|
||||||
if let Err(e) = imp.image_view.copy() {
|
if let Err(e) = imp.image_view.copy() {
|
||||||
log::error!("Failed to copy to clipboard: {}", e);
|
log::error!("Failed to copy to clipboard: {}", e);
|
||||||
} else {
|
} else {
|
||||||
self.show_toast("Image copied to clipboard", adw::ToastPriority::High);
|
self.show_toast(i18n("Image copied to clipboard"), adw::ToastPriority::High);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,7 +381,7 @@ impl LpWindow {
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn window_title(&self, file: Option<gio::File>) -> String {
|
fn window_title(&self, file: Option<gio::File>) -> String {
|
||||||
file.and_then(|f| util::get_file_display_name(&f)) // If the file exists, get display name
|
file.and_then(|f| util::get_file_display_name(&f)) // If the file exists, get display name
|
||||||
.unwrap_or_else(|| String::from("Image Viewer")) // Return that or the default if there's nothing
|
.unwrap_or_else(|| i18n("Image Viewer")) // Return that or the default if there's nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
// We also have a closure that returns `adw::FlapFoldPolicy`.
|
// We also have a closure that returns `adw::FlapFoldPolicy`.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue