general: Add translation infrastructure

This commit is contained in:
Christopher Davis 2022-08-05 18:54:04 -04:00
parent 58a77ec9fa
commit 1db46013d9
9 changed files with 260 additions and 13 deletions

View file

@ -18,6 +18,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use crate::deps::*;
use crate::i18n::*;
use adw::prelude::*;
use adw::subclass::prelude::*;
@ -173,9 +174,9 @@ impl LpApplication {
String::from("Jakub Steiner"),
String::from("Tobias Bernard"),
])
.copyright("© 2021 The GNOME Project")
.copyright(&i18n("© 2021 The GNOME Project"))
.license_type(gtk::License::Gpl30)
.program_name("Image Viewer")
.program_name(&i18n("Image Viewer"))
.logo_icon_name(config::APP_ID)
.version(config::VERSION)
.build();

221
src/i18n.rs Normal file
View 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");
}
}

View file

@ -23,6 +23,7 @@ use gtk::gio::{self, prelude::*};
mod application;
mod config;
mod file_model;
mod i18n;
mod thumbnail;
mod util;
mod widgets;

View file

@ -18,6 +18,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use crate::deps::*;
use crate::i18n::*;
use adw::subclass::prelude::*;
use glib::clone;
@ -375,8 +376,8 @@ impl LpImageView {
wallpaper::SetOn::Background,
)
.await {
Ok(_) => "Set as wallpaper.",
Err(_) => "Could not set wallpaper.",
Ok(_) => i18n("Set as wallpaper."),
Err(_) => i18n("Could not set wallpaper."),
};
view.activate_action(

View file

@ -18,6 +18,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use crate::deps::*;
use crate::i18n::*;
use adw::subclass::prelude::*;
use glib::clone;
@ -180,7 +181,7 @@ mod imp {
obj.set_image_from_file(&file, false);
} else {
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,
);
}
@ -296,7 +297,7 @@ impl LpWindow {
if let Err(e) = imp.image_view.copy() {
log::error!("Failed to copy to clipboard: {}", e);
} 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]
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
.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`.