i18n: Use gettext function instead of wrapper

No need to deviate from the default here.

Part-of: <https://gitlab.gnome.org/Incubator/loupe/-/merge_requests/170>
This commit is contained in:
Sophie Herold 2023-04-11 00:02:28 +02:00 • committed by Christopher Davis
parent 695693cf74
commit f8cc9f5f84
16 changed files with 83 additions and 274 deletions

View file

@ -18,7 +18,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use crate::deps::*;
use crate::i18n::*;
use crate::util::gettext::*;
use adw::prelude::*;
use adw::subclass::prelude::*;
@ -184,10 +184,10 @@ impl LpApplication {
// an object and set all relevant properties very
// easily in a way that's idiomatic to Rust.
let about = adw::AboutWindow::builder()
.application_name(i18n("Loupe"))
.application_name(gettext("Loupe"))
.application_icon(config::APP_ID)
.version(config::VERSION)
.developer_name(i18n("The Loupe Team"))
.developer_name(gettext("The Loupe Team"))
.website("https://gitlab.gnome.org/Incubator/loupe")
.issue_url("https://gitlab.gnome.org/Incubator/loupe/-/issues/new")
.developers([
@ -196,8 +196,8 @@ impl LpApplication {
])
.designers(["Allan Day", "Jakub Steiner", "Tobias Bernard"])
// Translators: Replace "translator-credits" with your names, one name per line
.translator_credits(i18n("translator-credits"))
.copyright(i18n("Copyright © 2020–2023 Christopher Davis et al."))
.translator_credits(gettext("translator-credits"))
.copyright(gettext("Copyright © 2020–2023 Christopher Davis et al."))
.license_type(gtk::License::Gpl30)
.build();

View file

@ -2,7 +2,7 @@
use super::*;
use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*;
use crate::i18n::*;
use crate::util::gettext::*;
use gtk::prelude::*;
use anyhow::Context;
@ -28,7 +28,7 @@ impl Heif {
log::debug!("Loading HEIF");
updater.spawn_error_handled(move || {
let ctx = HeifContext::read_from_file(&path.display().to_string())
.context(i18n("Failed to read image"))?;
.context(gettext("Failed to read image"))?;
let handle = ctx.primary_image_handle()?;
tiles.set_original_dimensions((handle.width(), handle.height()));
@ -61,7 +61,7 @@ impl Heif {
let mut image = LIBHEIF
.decode(&handle, ColorSpace::Rgb(rgb_chroma), None)
.context(i18n("Failed to decode image"))?;
.context(gettext("Failed to decode image"))?;
let plane = image
.planes_mut()

View file

@ -2,7 +2,7 @@
use super::*;
use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*;
use crate::i18n::*;
use crate::util::gettext::*;
use anyhow::Context;
use arc_swap::ArcSwap;
@ -35,7 +35,8 @@ impl ImageRsOther {
path: &Path,
format: image_rs::ImageFormat,
) -> anyhow::Result<image_rs::io::Reader<BufReader<File>>> {
let mut reader = image_rs::io::Reader::open(path).context(i18n("Failed to open image"))?;
let mut reader =
image_rs::io::Reader::open(path).context(gettext("Failed to open image"))?;
reader.set_format(format);
Ok(reader)
}
@ -88,7 +89,7 @@ impl ImageRsOther {
reader.limits(image_rs::io::Limits::no_limits());
let dynamic_image = reader.decode().context(i18n("Failed to decode image"))?;
let dynamic_image = reader.decode().context(gettext("Failed to decode image"))?;
let decoded_image = Decoded { dynamic_image };

View file

@ -7,7 +7,7 @@ pub use image_rs_other::ImageRsOther;
pub use svg::{Svg, RSVG_MAX_SIZE};
use super::{Decoder, DecoderUpdate, UpdateSender};
use crate::i18n::*;
use crate::util::gettext::*;
#[derive(Clone, Copy, Debug)]
pub enum ImageFormat {
@ -52,9 +52,9 @@ impl std::fmt::Display for ImageFormat {
write!(f, "{}", debug)
}
}
Self::AnimatedGif => write!(f, "{}", i18n("Animated GIF")),
Self::AnimatedWebP => write!(f, "{}", i18n("Animated WebP")),
Self::AnimatedPng => write!(f, "{}", i18n("Animated PNG")),
Self::AnimatedGif => write!(f, "{}", gettext("Animated GIF")),
Self::AnimatedWebP => write!(f, "{}", gettext("Animated WebP")),
Self::AnimatedPng => write!(f, "{}", gettext("Animated PNG")),
Self::Heif => write!(f, "HEIF Container"),
Self::Svg => write!(f, "SVG"),
}

View file

@ -5,8 +5,8 @@ pub mod tiling;
pub use formats::ImageDimensionDetails;
use crate::deps::*;
use crate::i18n::*;
use crate::image_metadata::ImageMetadata;
use crate::util::gettext::*;
use formats::*;
use tiling::FrameBufferExt;
@ -116,10 +116,10 @@ impl Decoder {
update_sender.send(DecoderUpdate::Metadata(ImageMetadata::load(&path)));
let mut buf = Vec::new();
let file = std::fs::File::open(&path).context(i18n("Could not open image"))?;
let file = std::fs::File::open(&path).context(gettext("Could not open image"))?;
file.take(64)
.read_to_end(&mut buf)
.context(i18n("Could not read image"))?;
.context(gettext("Could not read image"))?;
// Try magic bytes first and than file name extension
let format =
@ -166,12 +166,15 @@ impl Decoder {
update_sender.send(DecoderUpdate::Format(ImageFormat::Heif));
return Ok(FormatDecoder::Heif(Heif::new(path, update_sender, tiles)));
} else {
bail!(i18n_f("Unknown image format: {}", &[content_type.as_str()]));
bail!(gettext_f(
"Unknown image format: {}",
&[content_type.as_str()]
));
}
}
}
bail!(i18n("Unknown image format"))
bail!(gettext("Unknown image format"))
}
/// Request missing tiles

View file

@ -18,8 +18,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use crate::deps::*;
use crate::i18n::*;
use crate::util;
use crate::util::gettext::*;
use std::path::{Path, PathBuf};
@ -111,7 +111,7 @@ impl LpFileModel {
gio::FileQueryInfoFlags::NONE,
gio::Cancellable::NONE,
)
.context(i18n("Could not list other files in directory."))?;
.context(gettext("Could not list other files in directory."))?;
enumerator.for_each(|info| {
if let Ok(info) = info {

View file

@ -1,213 +0,0 @@
// 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;
#[allow(dead_code)]
fn freplace(mut s: String, args: &[&str]) -> String {
for arg in args {
s = s.replacen("{}", arg, 1);
}
s
}
#[allow(dead_code)]
fn kreplace(mut s: String, kwargs: &[(&str, &str)]) -> String {
for (k, v) in kwargs {
s = s.replace(&format!("{{{k}}}"), v);
}
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");
}
}

View file

@ -1,4 +1,4 @@
use crate::i18n::*;
use crate::util::gettext::*;
#[derive(Debug, Clone, Copy)]
pub struct GPSLocation {
@ -133,10 +133,10 @@ impl GPSLocation {
let coord = self.latitude;
let reference = if coord.sing {
// Translators: short for "north" in GPS coordinate
i18n("N")
gettext("N")
} else {
// Translators: short for "south" in GPS coordinate
i18n("S")
gettext("S")
};
coord.display(&reference)
@ -146,10 +146,10 @@ impl GPSLocation {
let coord = self.longitude;
let reference = if coord.sing {
// Translators: short for "east" in GPS coordinate
i18n("E")
gettext("E")
} else {
// Translators: short for "west" in GPS coordinate
i18n("W")
gettext("W")
};
coord.display(&reference)

View file

@ -9,8 +9,8 @@ mod obj;
pub use gps::GPSLocation;
pub use obj::LpImageMetadata;
use crate::i18n::*;
use crate::util;
use crate::util::gettext::*;
use gtk::glib;
@ -126,7 +126,7 @@ impl ImageMetadata {
let exposure = format!("{:.0}", 1. / rational.first()?.to_f32());
// Translators: Unit for exposure time in seconds
return Some(i18n_f("1\u{2215}{}\u{202F}s", &[&exposure]));
return Some(gettext_f("1\u{2215}{}\u{202F}s", &[&exposure]));
}
}
@ -151,7 +151,7 @@ impl ImageMetadata {
if let exif::Value::Rational(rational) = &field.value {
let length = format!("{:.0}", rational.first()?.to_f32());
// Translators: Unit for focal length in millimeters
return Some(i18n_f("{}\u{202F}mm", &[&length]));
return Some(gettext_f("{}\u{202F}mm", &[&length]));
}
}

View file

@ -53,7 +53,6 @@ mod application;
mod config;
mod decoder;
mod file_model;
mod i18n;
mod image_metadata;
mod util;
mod widgets;

14
src/util/gettext.rs Normal file
View file

@ -0,0 +1,14 @@
pub use gettextrs::gettext;
fn freplace(mut s: String, args: &[&str]) -> String {
for arg in args {
s = s.replacen("{}", arg, 1);
}
s
}
pub fn gettext_f(format: &str, args: &[&str]) -> String {
let s = gettextrs::gettext(format);
freplace(s, args)
}

View file

@ -1,5 +1,7 @@
pub mod gettext;
use crate::deps::*;
use crate::i18n::*;
use crate::util::gettext::*;
use anyhow::{anyhow, bail, Context};
use gio::prelude::*;
@ -13,7 +15,7 @@ pub fn datetime_fmt(datetime: &glib::DateTime) -> Option<String> {
// Translators: This is the date and time format we use in metadata output etc.
// The format has to follow <https://docs.gtk.org/glib/method.DateTime.format.html>
// The default is already translated. Don't change if you are not sure what to use.
let datetime_format = i18n("%x %X");
let datetime_format = gettext("%x %X");
let fmt = datetime.format(&datetime_format);
@ -146,7 +148,7 @@ pub async fn untrash(path: &Path) -> anyhow::Result<()> {
)
.0
.await
.context(i18n("Failed to restore image from trash"));
.context(gettext("Failed to restore image from trash"));
break;
}

View file

@ -31,8 +31,8 @@ use crate::deps::*;
use crate::decoder::tiling::FrameBufferExt;
use crate::decoder::{self, tiling, Decoder, DecoderUpdate};
use crate::i18n::i18n;
use crate::image_metadata::LpImageMetadata;
use crate::util::gettext::*;
use crate::util::Gesture;
use crate::util::spawn;
@ -748,7 +748,7 @@ impl LpImage {
self.set_file(&file);
if !path.is_file() {
self.set_error(anyhow::Error::msg(i18n("File does not exist")));
self.set_error(anyhow::Error::msg(gettext("File does not exist")));
return;
}

View file

@ -27,7 +27,7 @@
use crate::deps::*;
use crate::file_model::LpFileModel;
use crate::i18n::*;
use crate::util::gettext::*;
use crate::util::{Direction, Position};
use crate::widgets::{LpImage, LpImagePage, LpSlidingView};
@ -555,7 +555,7 @@ impl LpImageView {
let background = self
.current_path()
.and_then(|p| std::fs::File::open(p).ok())
.context(i18n("Failed to open new background image"))?;
.context(gettext("Failed to open new background image"))?;
let native = self.native().expect("View should have a GtkNative");
let id = WindowIdentifier::from_native(&native).await;
@ -572,14 +572,14 @@ impl LpImageView {
Ok(_) => self
.activate_action(
"win.show-toast",
Some(&(i18n("Set as background."), 1).to_variant()),
Some(&(gettext("Set as background."), 1).to_variant()),
)
.unwrap(),
Err(err) => {
if !matches!(err, Error::Response(ResponseError::Cancelled)) {
self.activate_action(
"win.show-toast",
Some(&(i18n("Could not set background."), 1).to_variant()),
Some(&(gettext("Could not set background."), 1).to_variant()),
)
.unwrap();
}

View file

@ -18,7 +18,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use crate::deps::*;
use crate::i18n::*;
use crate::util::gettext::*;
use crate::util::spawn;
use adw::prelude::*;
@ -352,7 +352,7 @@ mod imp {
obj.set_image_from_path(&file.path().unwrap());
} else {
obj.show_toast(
i18n_f("“{}” is not a valid image.", &[&info.display_name()]),
gettext_f("“{}” is not a valid image.", &[&info.display_name()]),
adw::ToastPriority::High,
);
}
@ -428,7 +428,7 @@ impl LpWindow {
filter_store.append(&filter);
let chooser = gtk::FileDialog::builder()
.title(i18n("Open Image"))
.title(gettext("Open Image"))
.filters(&filter_store)
.modal(true)
.build();
@ -482,7 +482,10 @@ impl LpWindow {
if let Err(e) = imp.image_view.copy() {
log::error!("Failed to copy to clipboard: {}", e);
} else {
self.show_toast(i18n("Image copied to clipboard"), adw::ToastPriority::High);
self.show_toast(
gettext("Image copied to clipboard"),
adw::ToastPriority::High,
);
}
}
@ -496,8 +499,8 @@ impl LpWindow {
match result {
Ok(()) => {
let toast = adw::Toast::builder()
.title(i18n("Image moved to trash"))
.button_label(i18n("Undo"))
.title(gettext("Image moved to trash"))
.button_label(gettext("Undo"))
.build();
toast.connect_button_clicked(glib::clone!(@weak self as win => move |_| {
let path = path.clone();
@ -508,7 +511,7 @@ impl LpWindow {
Err(err) => {
log::error!("Failed to untrash {path:?}: {err}");
win.show_toast(
i18n("Failed to restore image from trash"),
gettext("Failed to restore image from trash"),
adw::ToastPriority::High,
);
}
@ -523,7 +526,7 @@ impl LpWindow {
} else {
log::error!("Failed to delete file {path:?}: {err}");
self.show_toast(
i18n("Failed to move image to trash"),
gettext("Failed to move image to trash"),
adw::ToastPriority::Normal,
);
}
@ -538,8 +541,8 @@ impl LpWindow {
let dialog = adw::MessageDialog::builder()
.modal(true)
.transient_for(self)
.heading(i18n("Permanently Delete Image?"))
.body(i18n_f(
.heading(gettext("Permanently Delete Image?"))
.body(gettext_f(
"The image “{}” can only be deleted permanently.",
&[&PathBuf::from(&path.file_name().unwrap_or_default())
.display()
@ -547,7 +550,10 @@ impl LpWindow {
))
.build();
dialog.add_responses(&[("cancel", &i18n("Cancel")), ("delete", &i18n("Delete"))]);
dialog.add_responses(&[
("cancel", &gettext("Cancel")),
("delete", &gettext("Delete")),
]);
dialog.set_response_appearance("delete", adw::ResponseAppearance::Destructive);
if "delete" == dialog.choose_future().await {
@ -556,7 +562,10 @@ impl LpWindow {
if let Err(err) = result {
log::error!("Failed to delete file {path:?}: {err}");
self.show_toast(i18n("Failed to delete image"), adw::ToastPriority::Normal);
self.show_toast(
gettext("Failed to delete image"),
adw::ToastPriority::Normal,
);
}
}
}
@ -673,13 +682,13 @@ impl LpWindow {
fn window_title(&self, path: glib::variant::Variant) -> String {
// ensure that templates are initialized
if path.as_maybe().is_none() {
i18n("Loupe")
gettext("Loupe")
} else {
self.imp()
.image_view
.current_file()
.and_then(|f| util::get_file_display_name(&f)) // If the file exists, get display name
.unwrap_or_else(|| i18n("Loupe")) // Return that or the default if there's nothing
.unwrap_or_else(|| gettext("Loupe")) // Return that or the default if there's nothing
}
}