i18n: Add formatting i18n methods compatible with xgettext
This commit is contained in:
parent
eae7359285
commit
5de88e83ff
|
@ -86,8 +86,7 @@
|
|||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="LabelWithWidgets">
|
||||
<property name="label" translatable="yes"><widget> invited you</property>
|
||||
<object class="LabelWithWidgets" id="inviter">
|
||||
<child>
|
||||
<object class="Pill">
|
||||
<binding name="user">
|
||||
|
|
|
@ -52,6 +52,7 @@ src/session/account_settings/user_page/change_password_subpage.rs
|
|||
src/session/account_settings/user_page/deactivate_account_subpage.rs
|
||||
src/session/account_settings/user_page/mod.rs
|
||||
src/session/content/explore/public_room_row.rs
|
||||
src/session/content/invite.rs
|
||||
src/session/content/room_details/member_page/mod.rs
|
||||
src/session/content/room_details/mod.rs
|
||||
src/session/content/room_history/item_row.rs
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# These are files that we don't want to translate
|
||||
# Please keep this file sorted alphabetically.
|
||||
src/i18n.rs
|
|
@ -1 +1,3 @@
|
|||
i18n.gettext(gettext_package, preset: 'glib')
|
||||
i18n.gettext(gettext_package,
|
||||
args: ['--keyword=gettext_f', '--keyword=ngettext_f:1,2',],
|
||||
preset: 'glib')
|
||||
|
|
|
@ -312,8 +312,11 @@ check_potfiles() {
|
|||
# Get UI files with 'translatable="yes"'.
|
||||
ui_files=(`grep -lIr 'translatable="yes"' data/resources/ui/*`)
|
||||
|
||||
# Get Rust files with regex 'gettext[!]?\('.
|
||||
rs_files=(`grep -lIrE 'gettext[!]?\(' src/*`)
|
||||
# Get Rust files with regex 'gettext(_f)?\(', except `src/i18n.rs`.
|
||||
rs_files=(`grep -lIrE 'gettext(_f)?\(' --exclude=i18n.rs src/*`)
|
||||
|
||||
# Get Rust files with macros, regex 'gettext!\('.
|
||||
rs_macro_files=(`grep -lIrE 'gettext!\(' src/*`)
|
||||
|
||||
# Remove common files
|
||||
to_diff1=("${ui_potfiles[@]}")
|
||||
|
@ -352,7 +355,7 @@ check_potfiles() {
|
|||
ret=1
|
||||
elif [[ $files_count -ne 0 ]]; then
|
||||
echo ""
|
||||
echo -e "$error Found $files_count with translatable strings not present in POTFILES.in:"
|
||||
echo -e "$error Found $files_count files with translatable strings not present in POTFILES.in:"
|
||||
ret=1
|
||||
fi
|
||||
for file in ${ui_files[@]}; do
|
||||
|
@ -362,6 +365,20 @@ check_potfiles() {
|
|||
echo $file
|
||||
done
|
||||
|
||||
let rs_macro_count=$((${#rs_macro_files[@]}))
|
||||
if [[ $rs_macro_count -eq 1 ]]; then
|
||||
echo ""
|
||||
echo -e "$error Found 1 Rust file that uses a gettext-rs macro, use the corresponding i18n method instead:"
|
||||
ret=1
|
||||
elif [[ $rs_macro_count -ne 0 ]]; then
|
||||
echo ""
|
||||
echo -e "$error Found $rs_macro_count Rust files that use a gettext-rs macro, use the corresponding i18n method instead:"
|
||||
ret=1
|
||||
fi
|
||||
for file in ${rs_macro_files[@]}; do
|
||||
echo $file
|
||||
done
|
||||
|
||||
if [[ ret -eq 1 ]]; then
|
||||
echo ""
|
||||
echo -e " Checking po/POTFILES.in result: $fail"
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
use gettextrs::{gettext, ngettext};
|
||||
|
||||
fn freplace(s: String, args: &[(&str, &str)]) -> String {
|
||||
let mut s = s;
|
||||
|
||||
for (k, v) in args {
|
||||
s = s.replace(&format!("{{{}}}", k), v);
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// Like `gettext`, but replaces named variables with the given dictionary.
|
||||
///
|
||||
/// The expected format to replace is `{name}`, where `name` is the first string
|
||||
/// in the dictionary entry tuple.
|
||||
pub fn gettext_f(msgid: &str, args: &[(&str, &str)]) -> String {
|
||||
let s = gettext(msgid);
|
||||
freplace(s, args)
|
||||
}
|
||||
|
||||
/// Like `ngettext`, but replaces named variables with the given dictionary.
|
||||
///
|
||||
/// The expected format to replace is `{name}`, where `name` is the first string
|
||||
/// in the dictionary entry tuple.
|
||||
pub fn ngettext_f(msgid: &str, msgid_plural: &str, n: u32, args: &[(&str, &str)]) -> String {
|
||||
let s = ngettext(msgid, msgid_plural, n);
|
||||
freplace(s, args)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gettext_f() {
|
||||
let out = gettext_f("{one} param", &[("one", "one")]);
|
||||
assert_eq!(out, "one param");
|
||||
|
||||
let out = gettext_f("middle {one} param", &[("one", "one")]);
|
||||
assert_eq!(out, "middle one param");
|
||||
|
||||
let out = gettext_f("end {one}", &[("one", "one")]);
|
||||
assert_eq!(out, "end one");
|
||||
|
||||
let out = gettext_f("multiple {one} and {two}", &[("one", "1"), ("two", "two")]);
|
||||
assert_eq!(out, "multiple 1 and two");
|
||||
|
||||
let out = gettext_f("multiple {two} and {one}", &[("one", "1"), ("two", "two")]);
|
||||
assert_eq!(out, "multiple two and 1");
|
||||
|
||||
let out = gettext_f("multiple {one} and {one}", &[("one", "1"), ("two", "two")]);
|
||||
assert_eq!(out, "multiple 1 and 1");
|
||||
|
||||
let out = ngettext_f(
|
||||
"singular {one} and {two}",
|
||||
"plural {one} and {two}",
|
||||
1,
|
||||
&[("one", "1"), ("two", "two")],
|
||||
);
|
||||
assert_eq!(out, "singular 1 and two");
|
||||
let out = ngettext_f(
|
||||
"singular {one} and {two}",
|
||||
"plural {one} and {two}",
|
||||
2,
|
||||
&[("one", "1"), ("two", "two")],
|
||||
);
|
||||
assert_eq!(out, "plural 1 and two");
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ use login_advanced_dialog::LoginAdvancedDialog;
|
|||
|
||||
use crate::{
|
||||
components::{EntryRow, PasswordEntryRow, SpinnerButton, Toast},
|
||||
spawn, spawn_tokio,
|
||||
gettext_f, spawn, spawn_tokio,
|
||||
user_facing_error::UserFacingError,
|
||||
Session,
|
||||
};
|
||||
|
@ -339,7 +339,15 @@ impl Login {
|
|||
));
|
||||
} else {
|
||||
priv_.homeserver_entry.set_title(&gettext("Homeserver URL"));
|
||||
priv_.homeserver_help.set_markup(&gettext("The URL of your Matrix homeserver, for example <span segment=\"word\">https://gnome.modular.im</span>"));
|
||||
priv_.homeserver_help.set_markup(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
"The URL of your Matrix homeserver, for example {address}",
|
||||
&[(
|
||||
"address",
|
||||
"<span segment=\"word\">https://gnome.modular.im</span>",
|
||||
)],
|
||||
));
|
||||
}
|
||||
self.update_next_action();
|
||||
}
|
||||
|
@ -450,24 +458,23 @@ impl Login {
|
|||
|
||||
fn show_password_page(&self) {
|
||||
let priv_ = self.imp();
|
||||
if self.autodiscovery() {
|
||||
// Translators: the variable is a domain name, eg. gnome.org.
|
||||
priv_.password_title.set_markup(&gettext!(
|
||||
"Connecting to {}",
|
||||
format!(
|
||||
"<span segment=\"word\">{}</span>",
|
||||
priv_.homeserver_entry.text()
|
||||
)
|
||||
));
|
||||
|
||||
let domain_name = if self.autodiscovery() {
|
||||
priv_.homeserver_entry.text().to_string()
|
||||
} else {
|
||||
priv_.password_title.set_markup(&gettext!(
|
||||
"Connecting to {}",
|
||||
format!(
|
||||
"<span segment=\"word\">{}</span>",
|
||||
self.homeserver_pretty().unwrap()
|
||||
)
|
||||
));
|
||||
}
|
||||
self.homeserver_pretty().unwrap()
|
||||
};
|
||||
|
||||
priv_.password_title.set_markup(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a variable
|
||||
// name.
|
||||
"Connecting to {domain_name}",
|
||||
&[(
|
||||
"domain_name",
|
||||
&format!("<span segment=\"word\">{}</span>", domain_name),
|
||||
)],
|
||||
));
|
||||
|
||||
self.set_visible_child("password");
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ mod components;
|
|||
mod contrib;
|
||||
mod error_page;
|
||||
mod greeter;
|
||||
mod i18n;
|
||||
mod login;
|
||||
mod secret;
|
||||
mod session;
|
||||
|
@ -28,6 +29,7 @@ use self::{
|
|||
application::Application,
|
||||
error_page::{ErrorPage, ErrorSubpage},
|
||||
greeter::Greeter,
|
||||
i18n::*,
|
||||
login::Login,
|
||||
session::Session,
|
||||
user_facing_error::UserFacingError,
|
||||
|
|
|
@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::error::Error as JsonError;
|
||||
use url::Url;
|
||||
|
||||
use crate::{config::APP_ID, ErrorSubpage};
|
||||
use crate::{config::APP_ID, gettext_f, ErrorSubpage};
|
||||
|
||||
/// Any error that can happen when interacting with the secret service.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -266,8 +266,12 @@ pub async fn store_session(session: &StoredSession) -> Result<(), SecretError> {
|
|||
Some(&schema()),
|
||||
attributes,
|
||||
Some(&COLLECTION_DEFAULT),
|
||||
// Translators: The parameter is a Matrix User ID
|
||||
&gettext!("Fractal: Matrix credentials for {}", session.user_id),
|
||||
&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a variable
|
||||
// name.
|
||||
"Fractal: Matrix credentials for {user_id}",
|
||||
&[("user_id", session.user_id.as_str())],
|
||||
),
|
||||
&secret,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -6,7 +6,7 @@ use log::error;
|
|||
use super::Device;
|
||||
use crate::{
|
||||
components::{AuthError, SpinnerButton, Toast},
|
||||
spawn,
|
||||
gettext_f, spawn,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
|
@ -212,7 +212,8 @@ impl DeviceRow {
|
|||
error!("Failed to disconnect device {}: {err:?}", device.device_id());
|
||||
if let Some(adw_window) = window.and_then(|w| w.downcast::<adw::PreferencesWindow>().ok()) {
|
||||
let device_name = device.display_name();
|
||||
let error_message = gettext!("Failed to disconnect device “{}”", device_name);
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
|
||||
let error_message = gettext_f("Failed to disconnect device “{device_name}”", &[("device_name", device_name)]);
|
||||
adw_window.add_toast(&Toast::new(&error_message).into());
|
||||
}
|
||||
},
|
||||
|
|
|
@ -3,6 +3,7 @@ use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate
|
|||
|
||||
use crate::{
|
||||
components::{Avatar, LabelWithWidgets, Pill, SpinnerButton},
|
||||
gettext_f,
|
||||
session::room::{Room, RoomType},
|
||||
spawn,
|
||||
};
|
||||
|
@ -30,6 +31,8 @@ mod imp {
|
|||
#[template_child]
|
||||
pub room_topic: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub inviter: TemplateChild<LabelWithWidgets>,
|
||||
#[template_child]
|
||||
pub accept_button: TemplateChild<SpinnerButton>,
|
||||
#[template_child]
|
||||
pub reject_button: TemplateChild<SpinnerButton>,
|
||||
|
@ -126,6 +129,11 @@ mod imp {
|
|||
|
||||
self.room_topic
|
||||
.set_visible(!self.room_topic.label().is_empty());
|
||||
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
self.inviter
|
||||
.set_label(Some(gettext_f("{user} invited you", &[("user", "widget")])));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gettextrs::ngettext;
|
||||
use gtk::{
|
||||
glib::{self, clone, closure},
|
||||
subclass::prelude::*,
|
||||
|
@ -13,6 +12,7 @@ mod member_row;
|
|||
use self::{member_menu::MemberMenu, member_row::MemberRow};
|
||||
use crate::{
|
||||
components::{Avatar, Badge},
|
||||
ngettext_f,
|
||||
prelude::*,
|
||||
session::{
|
||||
content::RoomDetails,
|
||||
|
@ -231,7 +231,14 @@ impl MemberPage {
|
|||
let priv_ = self.imp();
|
||||
priv_
|
||||
.member_count
|
||||
.set_text(&ngettext!("{} Member", "{} Members", n, n));
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a variable
|
||||
// name.
|
||||
.set_text(&ngettext_f(
|
||||
"1 Member",
|
||||
"{n} Members",
|
||||
n,
|
||||
&[("n", &n.to_string())],
|
||||
));
|
||||
// FIXME: This won't be needed when we can request the natural height
|
||||
// on AdwPreferencesPage
|
||||
// See: https://gitlab.gnome.org/GNOME/libadwaita/-/issues/77
|
||||
|
@ -283,7 +290,14 @@ impl MemberPage {
|
|||
priv_.invited_section.set_visible(n > 0);
|
||||
priv_
|
||||
.invited_section
|
||||
.set_title(&ngettext!("{} Invited", "{} Invited", n, n));
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a variable
|
||||
// name.
|
||||
.set_title(&ngettext_f(
|
||||
"1 Invited",
|
||||
"{} Invited",
|
||||
n,
|
||||
&[("n", &n.to_string())],
|
||||
));
|
||||
// FIXME: This won't be needed when we can request the natural height
|
||||
// on AdwPreferencesPage
|
||||
// See: https://gitlab.gnome.org/GNOME/libadwaita/-/issues/77
|
||||
|
|
|
@ -10,6 +10,7 @@ use matrix_sdk::ruma::events::{
|
|||
};
|
||||
|
||||
use self::{creation::StateCreation, tombstone::StateTombstone};
|
||||
use crate::gettext_f;
|
||||
|
||||
mod imp {
|
||||
use glib::subclass::InitializingObject;
|
||||
|
@ -87,19 +88,30 @@ impl StateRow {
|
|||
{
|
||||
if let Some(prev_name) = prev.displayname {
|
||||
if event.displayname == None {
|
||||
Some(gettext!("{} removed their display name.", prev_name))
|
||||
Some(gettext_f(
|
||||
// Translators: Do NOT translate the content between
|
||||
// '{' and '}', this is a variable name.
|
||||
"{previous_user_name} removed their display name.",
|
||||
&[("previous_user_name", &prev_name)],
|
||||
))
|
||||
} else {
|
||||
Some(gettext!(
|
||||
"{} changed their display name to {}.",
|
||||
prev_name,
|
||||
display_name
|
||||
Some(gettext_f(
|
||||
// Translators: Do NOT translate the content between
|
||||
// '{' and '}', this is a variable name.
|
||||
"{previous_user_name} changed their display name to {new_user_name}.",
|
||||
&[("previous_user_name", &prev_name),
|
||||
("new_user_name", &display_name)]
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Some(gettext!(
|
||||
"{} set their display name to {}.",
|
||||
state.state_key(),
|
||||
display_name
|
||||
Some(gettext_f(
|
||||
// Translators: Do NOT translate the content between
|
||||
// '{' and '}', this is a variable name.
|
||||
"{user_id} set their display name to {new_user_name}.",
|
||||
&[
|
||||
("user_id", state.state_key()),
|
||||
("new_user_name", &display_name),
|
||||
],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -107,28 +119,50 @@ impl StateRow {
|
|||
if event.avatar_url != prev.avatar_url =>
|
||||
{
|
||||
if prev.avatar_url == None {
|
||||
Some(gettext!("{} set their avatar.", display_name))
|
||||
Some(gettext_f(
|
||||
// Translators: Do NOT translate the content between
|
||||
// '{' and '}', this is a variable name.
|
||||
"{user} set their avatar.",
|
||||
&[("user", &display_name)],
|
||||
))
|
||||
} else if event.avatar_url == None {
|
||||
Some(gettext!("{} removed their avatar.", display_name))
|
||||
Some(gettext_f(
|
||||
// Translators: Do NOT translate the content between
|
||||
// '{' and '}', this is a variable name.
|
||||
"{user} removed their avatar.",
|
||||
&[("user", &display_name)],
|
||||
))
|
||||
} else {
|
||||
Some(gettext!("{} changed their avatar.", display_name))
|
||||
Some(gettext_f(
|
||||
// Translators: Do NOT translate the content between
|
||||
// '{' and '}', this is a variable name.
|
||||
"{user} changed their avatar.",
|
||||
&[("user", &display_name)],
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
WidgetType::Text(
|
||||
message.unwrap_or(gettext!("{} joined this room.", display_name)),
|
||||
)
|
||||
}
|
||||
MembershipState::Invite => {
|
||||
WidgetType::Text(gettext!("{} was invited to this room.", display_name))
|
||||
WidgetType::Text(message.unwrap_or_else(|| {
|
||||
// Translators: Do NOT translate the content between '{' and '}', this
|
||||
// is a variable name.
|
||||
gettext_f("{user} joined this room.", &[("user", &display_name)])
|
||||
}))
|
||||
}
|
||||
MembershipState::Invite => WidgetType::Text(gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is
|
||||
// a variable name.
|
||||
"{user} was invited to this room.",
|
||||
&[("user", &display_name)],
|
||||
)),
|
||||
MembershipState::Knock => {
|
||||
// TODO: Add button to invite the user.
|
||||
WidgetType::Text(gettext!(
|
||||
"{} requested to be invited to this room.",
|
||||
display_name
|
||||
WidgetType::Text(gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this
|
||||
// is a variable name.
|
||||
"{user} requested to be invited to this room.",
|
||||
&[("user", &display_name)],
|
||||
))
|
||||
}
|
||||
MembershipState::Leave => {
|
||||
|
@ -137,30 +171,55 @@ impl StateRow {
|
|||
if prev.membership == MembershipState::Invite =>
|
||||
{
|
||||
if state.state_key() == state.sender() {
|
||||
Some(gettext!("{} rejected the invite.", display_name))
|
||||
Some(gettext_f(
|
||||
// Translators: Do NOT translate the content between
|
||||
// '{' and '}', this is a variable name.
|
||||
"{user} rejected the invite.",
|
||||
&[("user", &display_name)],
|
||||
))
|
||||
} else {
|
||||
Some(gettext!("{}’s invite was revoked'.", display_name))
|
||||
Some(gettext_f(
|
||||
// Translators: Do NOT translate the content between
|
||||
// '{' and '}', this is a variable name.
|
||||
"{user}’s invite was revoked'.",
|
||||
&[("user", &display_name)],
|
||||
))
|
||||
}
|
||||
}
|
||||
Some(AnyStateEventContent::RoomMember(prev))
|
||||
if prev.membership == MembershipState::Ban =>
|
||||
{
|
||||
Some(gettext!("{} was unbanned.", display_name))
|
||||
Some(gettext_f(
|
||||
// Translators: Do NOT translate the content between
|
||||
// '{' and '}', this is a variable name.
|
||||
"{user} was unbanned.",
|
||||
&[("user", &display_name)],
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
WidgetType::Text(message.unwrap_or_else(|| {
|
||||
if state.state_key() == state.sender() {
|
||||
gettext!("{} left the room.", display_name)
|
||||
// Translators: Do NOT translate the content between '{' and '}',
|
||||
// this is a variable name.
|
||||
gettext_f("{user} left the room.", &[("user", &display_name)])
|
||||
} else {
|
||||
gettext!("{} was kicked out of the room.", display_name)
|
||||
gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and
|
||||
// '}', this is a variable name.
|
||||
"{user} was kicked out of the room.",
|
||||
&[("user", &display_name)],
|
||||
)
|
||||
}
|
||||
}))
|
||||
}
|
||||
MembershipState::Ban => {
|
||||
WidgetType::Text(gettext!("{} was banned.", display_name))
|
||||
}
|
||||
MembershipState::Ban => WidgetType::Text(gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is
|
||||
// a variable name.
|
||||
"{user} was banned.",
|
||||
&[("user", &display_name)],
|
||||
)),
|
||||
_ => {
|
||||
warn!("Unsupported room member event: {:?}", state);
|
||||
WidgetType::Text(gettext("An unsupported room member event was received."))
|
||||
|
@ -172,7 +231,12 @@ impl StateRow {
|
|||
s if s.is_empty() => state.state_key().into(),
|
||||
s => s,
|
||||
};
|
||||
WidgetType::Text(gettext!("{} was invited to this room.", display_name))
|
||||
WidgetType::Text(gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
"{user} was invited to this room.",
|
||||
&[("user", &display_name)],
|
||||
))
|
||||
}
|
||||
AnyStateEventContent::RoomTombstone(event) => {
|
||||
WidgetType::Tombstone(StateTombstone::new(&event))
|
||||
|
|
|
@ -2,10 +2,14 @@ use adw::subclass::prelude::*;
|
|||
use gettextrs::gettext;
|
||||
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
|
||||
|
||||
use crate::session::{
|
||||
user::UserExt,
|
||||
verification::{IdentityVerification, VerificationState},
|
||||
use crate::{
|
||||
gettext_f,
|
||||
session::{
|
||||
user::UserExt,
|
||||
verification::{IdentityVerification, VerificationState},
|
||||
},
|
||||
};
|
||||
|
||||
mod imp {
|
||||
use std::cell::RefCell;
|
||||
|
||||
|
@ -160,11 +164,14 @@ impl VerificationInfoBar {
|
|||
if request.is_finished() {
|
||||
false
|
||||
} else if matches!(request.state(), VerificationState::Requested) {
|
||||
// Translators: The value is the display name of the user who wants to be
|
||||
// verified
|
||||
priv_.label.set_markup(&gettext!(
|
||||
"<b>{}</b> wants to be verified",
|
||||
request.user().display_name()
|
||||
priv_.label.set_markup(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
"{user_name} wants to be verified",
|
||||
&[(
|
||||
"user_name",
|
||||
&format!("<b>{}</b>", request.user().display_name()),
|
||||
)],
|
||||
));
|
||||
priv_.accept_btn.set_label(&gettext("Verify"));
|
||||
priv_.cancel_btn.set_label(&gettext("Decline"));
|
||||
|
|
|
@ -8,6 +8,7 @@ use super::Emoji;
|
|||
use crate::{
|
||||
components::SpinnerButton,
|
||||
contrib::{QRCode, QRCodeExt, QrCodeScanner},
|
||||
gettext_f,
|
||||
session::{
|
||||
user::UserExt,
|
||||
verification::{
|
||||
|
@ -547,32 +548,54 @@ impl IdentityVerificationWidget {
|
|||
priv_.label1.set_markup(&gettext("Verification Request"));
|
||||
priv_
|
||||
.label2
|
||||
.set_markup(&gettext!("<b>{}</b> asked do be verified. Verifying an user increases the security of the conversation.", name));
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
.set_markup(&gettext_f("{user} asked to be verified. Verifying a user increases the security of the conversation.", &[("user", &format!("<b>{}</b>", name))]));
|
||||
priv_.label3.set_markup(&gettext("Verification Request"));
|
||||
priv_.label4.set_markup(&gettext!(
|
||||
"Scan the QR code shown on the device of <b>{}</b>.",
|
||||
name
|
||||
priv_.label4.set_markup(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
"Scan the QR code shown on the device of {user}.",
|
||||
&[("user", &format!("<b>{}</b>", name))],
|
||||
));
|
||||
priv_.label5.set_markup(&gettext!("You scanned the QR code successfully. <b>{}</b> may need to confirm the verification.", name));
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
priv_.label5.set_markup(&gettext_f("You scanned the QR code successfully. {user} may need to confirm the verification.", &[("user", &format!("<b>{}</b>", name))]));
|
||||
priv_.label8.set_markup(&gettext("Verification Request"));
|
||||
priv_.label9.set_markup(&gettext(
|
||||
"Ask <b>{}</b> to scan this QR code from their session.",
|
||||
priv_.label9.set_markup(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
"Ask {user} to scan this QR code from their session.",
|
||||
&[("user", &format!("<b>{}</b>", name))],
|
||||
));
|
||||
priv_.label10.set_markup(&gettext("Verification Request"));
|
||||
priv_.label11.set_markup(&gettext!(
|
||||
"Ask <b>{}</b> if they see the following emoji appear in the same order on their screen.",
|
||||
name
|
||||
priv_.label11.set_markup(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
"Ask {user} if they see the following emoji appear in the same order on their screen.",
|
||||
&[("user", &format!("<b>{}</b>", name))]
|
||||
));
|
||||
priv_.label12.set_markup(&gettext("Verification Complete"));
|
||||
priv_.label13.set_markup(&gettext!("<b>{}</b> is verified and you can now be sure that your communication will be private.", name));
|
||||
priv_.label14.set_markup(&gettext!("Waiting for {}", name));
|
||||
priv_.label15.set_markup(&gettext!(
|
||||
"Ask <b>{}</b> to accept the verification request.",
|
||||
name
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
priv_.label13.set_markup(&gettext_f("{user} is verified and you can now be sure that your communication will be private.", &[("user", &format!("<b>{}</b>", name))]));
|
||||
priv_.label14.set_markup(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
"Waiting for {user}",
|
||||
&[("user", &format!("<b>{}</b>", name))],
|
||||
));
|
||||
priv_.label16.set_markup(&gettext!(
|
||||
"Does <b>{}</b> see a confirmation shield on their session?",
|
||||
name
|
||||
priv_.label15.set_markup(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
"Ask {user} to accept the verification request.",
|
||||
&[("user", &format!("<b>{}</b>", name))],
|
||||
));
|
||||
priv_.label16.set_markup(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a
|
||||
// variable name.
|
||||
"Does {user} see a confirmation shield on their session?",
|
||||
&[("user", &format!("<b>{}</b>", name))],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -460,7 +460,7 @@ impl Session {
|
|||
warn!("Couldn't store session: {:?}", error);
|
||||
if let Some(window) = self.parent_window() {
|
||||
window.switch_to_error_page(
|
||||
&gettext!("Unable to store session: {}", error),
|
||||
&format!("{}\n\n{}", gettext("Unable to store session"), error),
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ pub use self::{
|
|||
};
|
||||
use crate::{
|
||||
components::{Pill, Toast},
|
||||
gettext_f, ngettext_f,
|
||||
prelude::*,
|
||||
session::{
|
||||
avatar::update_room_avatar_from_file, room::member_list::MemberList, Avatar, Session, User,
|
||||
|
@ -415,7 +416,8 @@ impl Room {
|
|||
|
||||
let room_pill = Pill::for_room(&obj);
|
||||
let error = Toast::builder()
|
||||
.title(&gettext("Failed to forget <widget>."))
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
|
||||
.title(&gettext_f("Failed to forget {room}.", &[("room", "<widget>")]))
|
||||
.widgets(&[&room_pill])
|
||||
.build();
|
||||
|
||||
|
@ -560,10 +562,10 @@ impl Room {
|
|||
|
||||
let room_pill = Pill::for_room(&obj);
|
||||
let error = Toast::builder()
|
||||
.title(&gettext!(
|
||||
"Failed to move <widget> from {} to {}.",
|
||||
previous_category.to_string(),
|
||||
category.to_string()
|
||||
.title(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
|
||||
"Failed to move {room} from {previous_category} to {new_category}.",
|
||||
&[("room", "<widget>"),("previous_category", &previous_category.to_string()), ("new_category", &category.to_string())],
|
||||
))
|
||||
.widgets(&[&room_pill])
|
||||
.build();
|
||||
|
@ -1269,8 +1271,11 @@ impl Room {
|
|||
|
||||
let room_pill = Pill::for_room(self);
|
||||
let error = Toast::builder()
|
||||
.title(&gettext(
|
||||
"Failed to accept invitation for <widget>. Try again later.",
|
||||
.title(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this
|
||||
// is a variable name.
|
||||
"Failed to accept invitation for {room}. Try again later.",
|
||||
&[("room", "<widget>")],
|
||||
))
|
||||
.widgets(&[&room_pill])
|
||||
.build();
|
||||
|
@ -1300,8 +1305,11 @@ impl Room {
|
|||
|
||||
let room_pill = Pill::for_room(self);
|
||||
let error = Toast::builder()
|
||||
.title(&gettext(
|
||||
"Failed to reject invitation for <widget>. Try again later.",
|
||||
.title(&gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this
|
||||
// is a variable name.
|
||||
"Failed to reject invitation for {room}. Try again later.",
|
||||
&[("room", "<widget>")],
|
||||
))
|
||||
.widgets(&[&room_pill])
|
||||
.build();
|
||||
|
@ -1465,13 +1473,25 @@ impl Room {
|
|||
let first_failed = failed_invites.first().unwrap();
|
||||
|
||||
// TODO: should we show all the failed users?
|
||||
let error_message = if no_failed == 1 {
|
||||
gettext("Failed to invite <widget> to <widget>. Try again later.")
|
||||
} else if no_failed == 2 {
|
||||
gettext("Failed to invite <widget> and some other user to <widget>. Try again later.")
|
||||
} else {
|
||||
gettext("Failed to invite <widget> and some other users to <widget>. Try again later.")
|
||||
};
|
||||
let error_message =
|
||||
if no_failed == 1 {
|
||||
gettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this
|
||||
// is a variable name.
|
||||
"Failed to invite {user} to {room}. Try again later.",
|
||||
&[("user", "<widget>"), ("room", "<widget>")],
|
||||
)
|
||||
} else {
|
||||
let n = (no_failed - 1) as u32;
|
||||
ngettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}', this
|
||||
// is a variable name.
|
||||
"Failed to invite {user} and 1 other user to {room}. Try again later.",
|
||||
"Failed to invite {user} and {n} other users to {room}. Try again later.",
|
||||
n,
|
||||
&[("user", "<widget>"), ("room", "<widget>"), ("n", &n.to_string())],
|
||||
)
|
||||
};
|
||||
let user_pill = Pill::for_user(first_failed);
|
||||
let room_pill = Pill::for_room(self);
|
||||
let error = Toast::builder()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::{cell::Cell, collections::HashSet};
|
||||
|
||||
use gettextrs::gettext;
|
||||
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
|
||||
use indexmap::map::IndexMap;
|
||||
use log::error;
|
||||
|
@ -11,6 +10,7 @@ use matrix_sdk::{
|
|||
|
||||
use crate::{
|
||||
components::Toast,
|
||||
gettext_f,
|
||||
session::{room::Room, Session},
|
||||
spawn, spawn_tokio,
|
||||
};
|
||||
|
@ -322,7 +322,8 @@ impl RoomList {
|
|||
obj.pending_rooms_remove(&identifier);
|
||||
error!("Joining room {} failed: {}", identifier, error);
|
||||
let error = Toast::new(
|
||||
&gettext!("Failed to join room {}. Try again later.", identifier)
|
||||
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
|
||||
&gettext_f("Failed to join room {room_name}. Try again later.", &[("room_name", identifier.as_str())])
|
||||
);
|
||||
|
||||
if let Some(window) = obj.session().parent_window() {
|
||||
|
|
|
@ -8,6 +8,8 @@ use matrix_sdk::{
|
|||
ClientBuildError, Error, HttpError,
|
||||
};
|
||||
|
||||
use crate::ngettext_f;
|
||||
|
||||
pub trait UserFacingError {
|
||||
fn to_user_facing(self) -> String;
|
||||
}
|
||||
|
@ -29,9 +31,14 @@ impl UserFacingError for HttpError {
|
|||
UserDeactivated => gettext("The account is deactivated."),
|
||||
LimitExceeded { retry_after_ms } => {
|
||||
if let Some(ms) = retry_after_ms {
|
||||
gettext!(
|
||||
"You exceeded the homeserver’s rate limit, retry in {} seconds.",
|
||||
ms.as_secs()
|
||||
let secs = ms.as_secs() as u32;
|
||||
ngettext_f(
|
||||
// Translators: Do NOT translate the content between '{' and '}',
|
||||
// this is a variable name.
|
||||
"You exceeded the homeserver’s rate limit, retry in 1 second.",
|
||||
"You exceeded the homeserver’s rate limit, retry in {n} seconds.",
|
||||
secs,
|
||||
&[("n", &secs.to_string())],
|
||||
)
|
||||
} else {
|
||||
gettext("You exceeded the homeserver’s rate limit, try again later.")
|
||||
|
|
|
@ -199,7 +199,11 @@ impl Window {
|
|||
Err(error) => {
|
||||
warn!("Failed to restore previous sessions: {:?}", error);
|
||||
self.switch_to_error_page(
|
||||
&gettext!("Failed to restore previous sessions: {}", error),
|
||||
&format!(
|
||||
"{}\n\n{}",
|
||||
gettext("Failed to restore previous sessions"),
|
||||
error,
|
||||
),
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue