diff --git a/src/components/dialogs/message_dialogs.rs b/src/components/dialogs/message_dialogs.rs index 425ec18f..e4f957ec 100644 --- a/src/components/dialogs/message_dialogs.rs +++ b/src/components/dialogs/message_dialogs.rs @@ -369,24 +369,35 @@ pub(crate) async fn confirm_mute_room_member_dialog( .first() .expect("there should be at least one member") .upcast_ref(); - let count = members.len() as u32; - - let heading = ngettext_f( - // Translators: Do NOT translate the content between '{' and '}', - // this is a variable name. The count cannot be zero. - "Mute {user}?", - "Mute Members?", - count, - &[("user", &first_member.display_name())], - ); - let body = ngettext_f( - // Translators: Do NOT translate the content between '{' and '}', - // this is a variable name. The count cannot be zero. - "Are you sure you want to mute {user_id}? They will not be able to send new messages to this room.", - "Are you sure you want to mute these members? They will not be able to send new messages to this room.", - count, - &[("user_id", first_member.user_id().as_str())], - ); + let is_single_member = members.len() == 1; + + // We don't use the count in the strings so we use separate gettext calls for + // singular and plural rather than using ngettext. + let heading = if is_single_member { + gettext_f( + // Translators: Do NOT translate the content between '{' and '}', + // this is a variable name. + "Mute {user}?", + &[("user", &first_member.display_name())], + ) + } else { + gettext("Mute Members?") + }; + + // We don't use the count in the strings so we use separate gettext calls for + // singular and plural rather than using ngettext. + let body = if is_single_member { + gettext_f( + // Translators: Do NOT translate the content between '{' and '}', + // this is a variable name. + "Are you sure you want to mute {user_id}? They will not be able to send new messages to this room.", + &[("user_id", first_member.user_id().as_str())], + ) + } else { + gettext( + "Are you sure you want to mute these members? They will not be able to send new messages to this room.", + ) + }; // Ask for confirmation. let confirm_dialog = adw::AlertDialog::builder() @@ -418,24 +429,35 @@ pub(crate) async fn confirm_set_room_member_power_level_same_as_own_dialog( .first() .expect("there should be at least one member") .upcast_ref(); - let count = members.len() as u32; - - let heading = ngettext_f( - // Translators: Do NOT translate the content between '{' and '}', - // this is a variable name. The count cannot be zero. - "Promote {user}?", - "Promote Members?", - count, - &[("user", &first_member.display_name())], - ); - let body = ngettext_f( - // Translators: Do NOT translate the content between '{' and '}', - // this is a variable name. The count cannot be zero. - "If you promote {user_id} to the same level as yours, you will not be able to demote them in the future.", - "If you promote these members to the same level as yours, you will not be able to demote them in the future.", - count, - &[("user_id", first_member.user_id().as_str())], - ); + let is_single_member = members.len() == 1; + + // We don't use the count in the strings so we use separate gettext calls for + // singular and plural rather than using ngettext. + let heading = if is_single_member { + gettext_f( + // Translators: Do NOT translate the content between '{' and '}', + // this is a variable name. + "Promote {user}?", + &[("user", &first_member.display_name())], + ) + } else { + gettext("Promote Members?") + }; + + // We don't use the count in the strings so we use separate gettext calls for + // singular and plural rather than using ngettext. + let body = if is_single_member { + gettext_f( + // Translators: Do NOT translate the content between '{' and '}', + // this is a variable name. The count cannot be zero. + "If you promote {user_id} to the same level as yours, you will not be able to demote them in the future.", + &[("user_id", first_member.user_id().as_str())], + ) + } else { + gettext( + "If you promote these members to the same level as yours, you will not be able to demote them in the future.", + ) + }; // Ask for confirmation. let confirm_dialog = adw::AlertDialog::builder() diff --git a/src/session_view/room_details/general_page.rs b/src/session_view/room_details/general_page.rs index 63df7ae2..72e27685 100644 --- a/src/session_view/room_details/general_page.rs +++ b/src/session_view/room_details/general_page.rs @@ -1,5 +1,5 @@ use adw::{prelude::*, subclass::prelude::*}; -use gettextrs::{gettext, ngettext}; +use gettextrs::gettext; use gtk::{ gio, glib::{self, clone}, @@ -458,7 +458,13 @@ mod imp { // the members count to make sure we do not show a list that is too long. let is_direct_with_few_members = room.is_direct() && joined_members_count < 5; if is_direct_with_few_members { - let title = ngettext("Member", "Members", joined_members_count); + // We don't use the count in the strings so we use separate gettext calls for + // singular and plural rather than using ngettext. + let title = if joined_members_count == 1 { + gettext("Member") + } else { + gettext("Members") + }; self.direct_members_group.set_title(&title); // Set model of direct members list dynamically to avoid creating unnecessary @@ -503,8 +509,13 @@ mod imp { server_joined_members_count.max(joined_members_count.into()); self.members_row.set_count(joined_members_count.to_string()); - let n = joined_members_count.try_into().unwrap_or(u32::MAX); - let title = ngettext("Member", "Members", n); + // We don't use the count in the strings so we use separate gettext calls for + // singular and plural rather than using ngettext. + let title = if joined_members_count == 1 { + gettext("Member") + } else { + gettext("Members") + }; self.members_row.set_title(&title); if self.direct_members_list_has_bound_model.get() { diff --git a/src/session_view/room_details/invite_subpage/mod.rs b/src/session_view/room_details/invite_subpage/mod.rs index 4dea1dfb..134cb9e0 100644 --- a/src/session_view/room_details/invite_subpage/mod.rs +++ b/src/session_view/room_details/invite_subpage/mod.rs @@ -1,5 +1,5 @@ use adw::{prelude::*, subclass::prelude::*}; -use gettextrs::ngettext; +use gettextrs::{gettext, ngettext}; use gtk::{gdk, glib, glib::clone}; use tracing::error; @@ -219,22 +219,35 @@ mod imp { ); } + // We don't use the count in the strings so we use separate gettext calls for + // singular and plural rather than using ngettext. if n == 0 { self.close(); - } else { + } else if n == 1 { let first_failed = invite_list.first_invitee().map(|item| item.user()).unwrap(); toast!( self.obj(), - ngettext( + gettext( // Translators: Do NOT translate the content between '{' and '}', these // are variable names. "Could not invite {user} to {room}", + ), + @user = first_failed, + @room, + n, + ); + } else { + toast!( + self.obj(), + ngettext( + // Translators: Do NOT translate the content between '{' and '}', these + // are variable names. The count is always greater than 1. + "Could not invite 1 user to {room}", "Could not invite {n} users to {room}", n as u32, ), - @user = first_failed, @room, n, ); diff --git a/src/session_view/room_details/members_page/members_list_view/membership_subpage_row.rs b/src/session_view/room_details/members_page/members_list_view/membership_subpage_row.rs index cf0636c5..497535de 100644 --- a/src/session_view/room_details/members_page/members_list_view/membership_subpage_row.rs +++ b/src/session_view/room_details/members_page/members_list_view/membership_subpage_row.rs @@ -1,4 +1,4 @@ -use gettextrs::npgettext; +use gettextrs::{gettext, pgettext}; use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*}; use crate::{ @@ -110,14 +110,35 @@ mod imp { let item = self.item.borrow().clone()?; let count = item.model().n_items(); + // We don't use the count in the strings so we use separate pgettext calls for + // singular and plural rather than using npgettext. let label = match item.kind() { MembershipListKind::Join => return None, // Translators: As in 'Invited Room Member(s)'. - MembershipListKind::Invite => npgettext("members", "Invited", "Invited", count), - // Translators: As in 'Banned Room Member(s)'. - MembershipListKind::Ban => npgettext("members", "Banned", "Banned", count), + MembershipListKind::Invite => { + if count == 1 { + // Translators: This is singular, as in 'Invited Room Member'. + pgettext("members", "Invited") + } else { + // Translators: This is plural, as in 'Invited Room Members'. + pgettext("members", "Invited") + } + } + MembershipListKind::Ban => { + if count == 1 { + // Translators: This is singular, as in 'Banned Room Member'. + pgettext("members", "Banned") + } else { + // Translators: This is plural, as in 'Banned Room Members'. + pgettext("members", "Banned") + } + } MembershipListKind::Knock => { - npgettext("members", "Invite Request", "Invite Requests", count) + if count == 1 { + gettext("Invite Request") + } else { + gettext("Invite Requests") + } } }; diff --git a/src/session_view/room_details/members_page/members_list_view/mod.rs b/src/session_view/room_details/members_page/members_list_view/mod.rs index a1bc5142..8440153f 100644 --- a/src/session_view/room_details/members_page/members_list_view/mod.rs +++ b/src/session_view/room_details/members_page/members_list_view/mod.rs @@ -1,5 +1,5 @@ use adw::{prelude::*, subclass::prelude::*}; -use gettextrs::{gettext, ngettext}; +use gettextrs::gettext; use gtk::{ gio, glib, glib::{clone, closure}, @@ -354,15 +354,37 @@ mod imp { let count = membership_list.n_items(); let is_empty = count == 0; + // We don't use the count in the strings so we use separate gettext calls for + // singular and plural rather than using ngettext. let title = match kind { - MembershipListKind::Join => ngettext("Room Member", "Room Members", count), + MembershipListKind::Join => { + if count == 1 { + gettext("Room Member") + } else { + gettext("Room Members") + } + } MembershipListKind::Invite => { - ngettext("Invited Room Member", "Invited Room Members", count) + if count == 1 { + gettext("Invited Room Member") + } else { + gettext("Invited Room Members") + } } MembershipListKind::Ban => { - ngettext("Banned Room Member", "Banned Room Members", count) + if count == 1 { + gettext("Banned Room Member") + } else { + gettext("Banned Room Members") + } + } + MembershipListKind::Knock => { + if count == 1 { + gettext("Invite Request") + } else { + gettext("Invite Requests") + } } - MembershipListKind::Knock => ngettext("Invite Request", "Invite Requests", count), }; self.obj().set_title(&title); diff --git a/src/session_view/room_details/upgrade_dialog/mod.rs b/src/session_view/room_details/upgrade_dialog/mod.rs index ee6f1eb4..c99599c4 100644 --- a/src/session_view/room_details/upgrade_dialog/mod.rs +++ b/src/session_view/room_details/upgrade_dialog/mod.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use adw::{prelude::*, subclass::prelude::*}; -use gettextrs::{gettext, ngettext}; +use gettextrs::gettext; use gtk::{gio, glib, pango}; use ruma::{ OwnedUserId, RoomVersionId, @@ -196,19 +196,25 @@ mod imp { return; } - let other_creators_count = u32::try_from(info.other_creators_count).unwrap_or(u32::MAX); - + // We don't use the count in the strings so we use separate gettext calls for + // singular and plural rather than using ngettext. let text = if info.own_user_is_creator { - ngettext( - "After the upgrade, you will be the only creator in the room. The other creator will be demoted to the default power level.", - "After the upgrade, you will be the only creator in the room. The other creators will be demoted to the default power level.", - other_creators_count, + if info.other_creators_count == 1 { + gettext( + "After the upgrade, you will be the only creator in the room. The other creator will be demoted to the default power level.", + ) + } else { + gettext( + "After the upgrade, you will be the only creator in the room. The other creators will be demoted to the default power level.", + ) + } + } else if info.other_creators_count == 1 { + gettext( + "After the upgrade, you will be the only creator in the room. The current creator will be demoted to the default power level.", ) } else { - ngettext( - "After the upgrade, you will be the only creator in the room. The current creator will be demoted to the default power level.", + gettext( "After the upgrade, you will be the only creator in the room. The current creators will be demoted to the default power level.", - other_creators_count, ) };