Internationalize user-facing strings and export them for translations

Changelog-Added: Internationalized user-facing strings and exported them for translations
Signed-off-by: Terry Yiu <git@tyiu.xyz>
This commit is contained in:
2025-06-26 23:13:31 -04:00
committed by William Casarin
parent d07c3e9135
commit 3f5036bd32
37 changed files with 2198 additions and 437 deletions

View File

@@ -6,7 +6,7 @@ use egui::{
};
use egui_winit::clipboard::Clipboard;
use enostr::Keypair;
use notedeck::{fonts::get_font_size, AppAction, NotedeckTextStyle};
use notedeck::{fonts::get_font_size, tr, AppAction, NotedeckTextStyle};
use notedeck_ui::{
app_images,
context_menu::{input_context, PasteBehavior},
@@ -58,7 +58,7 @@ impl<'a> AccountLoginView<'a> {
ui.with_layout(Layout::left_to_right(Align::TOP), |ui| {
let help_text_style = NotedeckTextStyle::Small;
ui.add(egui::Label::new(
RichText::new("Enter your public key (npub), nostr address (e.g. vrod@damus.io), or private key (nsec). You must enter your private key to be able to post, reply, etc.")
RichText::new(tr!("Enter your public key (npub), nostr address (e.g. {address}), or private key (nsec). You must enter your private key to be able to post, reply, etc.", "Instructions for entering Nostr credentials", address="vrod@damus.io"))
.text_style(help_text_style.text_style())
.size(get_font_size(ui.ctx(), &help_text_style)).color(ui.visuals().weak_text_color()),
).wrap())
@@ -73,13 +73,13 @@ impl<'a> AccountLoginView<'a> {
ui.horizontal(|ui| {
ui.label(
RichText::new("New to Nostr?")
RichText::new(tr!("New to Nostr?", "Label asking if the user is new to Nostr. Underneath this label is a button to create an account."))
.color(ui.style().visuals.noninteractive().fg_stroke.color)
.text_style(NotedeckTextStyle::Body.text_style()),
);
if ui
.add(Button::new(RichText::new("Create Account")).frame(false))
.add(Button::new(RichText::new(tr!("Create Account", "Button to create a new account"))).frame(false))
.clicked()
{
self.manager.should_create_new();
@@ -99,20 +99,20 @@ impl<'a> AccountLoginView<'a> {
}
fn login_title_text() -> RichText {
RichText::new("Login")
RichText::new(tr!("Login", "Login page title"))
.text_style(NotedeckTextStyle::Heading2.text_style())
.strong()
}
fn login_textedit_info_text() -> RichText {
RichText::new("Enter your key")
RichText::new(tr!("Enter your key", "Label for key input field. Key can be public key (npub), private key (nsec), or Nostr address (NIP-05)."))
.strong()
.text_style(NotedeckTextStyle::Body.text_style())
}
fn login_button() -> Button<'static> {
Button::new(
RichText::new("Login now — let's do this!")
RichText::new(tr!("Login now — let's do this!", "Login button text"))
.text_style(NotedeckTextStyle::Body.text_style())
.strong(),
)
@@ -124,7 +124,11 @@ fn login_textedit(manager: &mut AcquireKeyState) -> TextEdit {
let create_textedit: fn(&mut dyn TextBuffer) -> TextEdit = |text| {
egui::TextEdit::singleline(text)
.hint_text(
RichText::new("Your key here...").text_style(NotedeckTextStyle::Body.text_style()),
RichText::new(tr!(
"Your key here...",
"Placeholder text for key input field"
))
.text_style(NotedeckTextStyle::Body.text_style()),
)
.vertical_align(Align::Center)
.min_size(Vec2::new(0.0, 40.0))

View File

@@ -3,7 +3,7 @@ use egui::{
};
use enostr::Pubkey;
use nostrdb::{Ndb, Transaction};
use notedeck::{Accounts, Images};
use notedeck::{tr, Accounts, Images};
use notedeck_ui::colors::PINK;
use notedeck_ui::app_images;
@@ -171,7 +171,7 @@ fn scroll_area() -> ScrollArea {
fn add_account_button() -> Button<'static> {
Button::image_and_text(
app_images::add_account_image().fit_to_exact_size(Vec2::new(48.0, 48.0)),
RichText::new(" Add account")
RichText::new(tr!("Add account", "Button label to add a new account"))
.size(16.0)
// TODO: this color should not be hard coded. Find some way to add it to the visuals
.color(PINK),
@@ -180,5 +180,8 @@ fn add_account_button() -> Button<'static> {
}
fn sign_out_button() -> egui::Button<'static> {
egui::Button::new(RichText::new("Sign out"))
egui::Button::new(RichText::new(tr!(
"Sign out",
"Button label to sign out of account"
)))
}

View File

@@ -17,7 +17,7 @@ use crate::{
Damus,
};
use notedeck::{AppContext, Images, NotedeckTextStyle, UserAccount};
use notedeck::{tr, AppContext, Images, NotedeckTextStyle, UserAccount};
use notedeck_ui::{anim::ICON_EXPANSION_MULTIPLE, app_images};
use tokenator::{ParseError, TokenParser, TokenSerializable, TokenWriter};
@@ -229,8 +229,11 @@ impl<'a> AddColumnView<'a> {
deck_author: Pubkey,
) -> Option<AddColumnResponse> {
let algo_option = ColumnOptionData {
title: "Contact List",
description: "Source the last note for each user in your contact list",
title: tr!("Contact List", "Title for contact list column"),
description: tr!(
"Source the last note for each user in your contact list",
"Description for contact list column"
),
icon: app_images::home_image(),
option: AddColumnOption::Algo(AlgoOption::LastPerPubkey(Decision::Decided(
ListKind::contact_list(deck_author),
@@ -245,8 +248,11 @@ impl<'a> AddColumnView<'a> {
fn algo_ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> {
let algo_option = ColumnOptionData {
title: "Last Note per User",
description: "Show the last note for each user from a list",
title: tr!("Last Note per User", "Title for last note per user column"),
description: tr!(
"Show the last note for each user from a list",
"Description for last note per user column"
),
icon: app_images::algo_image(),
option: AddColumnOption::Algo(AlgoOption::LastPerPubkey(Decision::Undecided)),
};
@@ -291,8 +297,11 @@ impl<'a> AddColumnView<'a> {
let text_edit = key_state.get_acquire_textedit(|text| {
egui::TextEdit::singleline(text)
.hint_text(
RichText::new("Enter the user's key (npub, hex, nip05) here...")
.text_style(NotedeckTextStyle::Body.text_style()),
RichText::new(tr!(
"Enter the user's key (npub, hex, nip05) here...",
"Hint text to prompt entering the user's public key."
))
.text_style(NotedeckTextStyle::Body.text_style()),
)
.vertical_align(Align::Center)
.desired_width(f32::INFINITY)
@@ -386,7 +395,8 @@ impl<'a> AddColumnView<'a> {
title_font_max_size + inter_text_padding + desc_font_max_size + (2.0 * height_padding)
};
let helper = AnimationHelper::new(ui, data.title, vec2(max_width, max_height));
let title = data.title.clone();
let helper = AnimationHelper::new(ui, title.clone(), vec2(max_width, max_height));
let animation_rect = helper.get_animation_rect();
let cur_icon_width = helper.scale_1d_pos(min_icon_width);
@@ -445,8 +455,11 @@ impl<'a> AddColumnView<'a> {
fn get_base_options(&self, ui: &mut Ui) -> Vec<ColumnOptionData> {
let mut vec = Vec::new();
vec.push(ColumnOptionData {
title: "Home",
description: "See notes from your contacts",
title: tr!("Home", "Title for Home column"),
description: tr!(
"See notes from your contacts",
"Description for Home column"
),
icon: app_images::home_image(),
option: AddColumnOption::Contacts(if self.cur_account.key.secret_key.is_some() {
PubkeySource::DeckAuthor
@@ -455,32 +468,47 @@ impl<'a> AddColumnView<'a> {
}),
});
vec.push(ColumnOptionData {
title: "Notifications",
description: "Stay up to date with notifications and mentions",
title: tr!("Notifications", "Title for notifications column"),
description: tr!(
"Stay up to date with notifications and mentions",
"Description for notifications column"
),
icon: app_images::notifications_image(ui.visuals().dark_mode),
option: AddColumnOption::UndecidedNotification,
});
vec.push(ColumnOptionData {
title: "Universe",
description: "See the whole nostr universe",
title: tr!("Universe", "Title for universe column"),
description: tr!(
"See the whole nostr universe",
"Description for universe column"
),
icon: app_images::universe_image(),
option: AddColumnOption::Universe,
});
vec.push(ColumnOptionData {
title: "Hashtags",
description: "Stay up to date with a certain hashtag",
title: tr!("Hashtags", "Title for hashtags column"),
description: tr!(
"Stay up to date with a certain hashtag",
"Description for hashtags column"
),
icon: app_images::hashtag_image(),
option: AddColumnOption::UndecidedHashtag,
});
vec.push(ColumnOptionData {
title: "Individual",
description: "Stay up to date with someone's notes & replies",
title: tr!("Individual", "Title for individual user column"),
description: tr!(
"Stay up to date with someone's notes & replies",
"Description for individual user column"
),
icon: app_images::profile_image(),
option: AddColumnOption::UndecidedIndividual,
});
vec.push(ColumnOptionData {
title: "Algo",
description: "Algorithmic feeds to aid in note discovery",
title: tr!("Algo", "Title for algorithmic feeds column"),
description: tr!(
"Algorithmic feeds to aid in note discovery",
"Description for algorithmic feeds column"
),
icon: app_images::algo_image(),
option: AddColumnOption::Algo(AlgoOption::LastPerPubkey(Decision::Undecided)),
});
@@ -498,15 +526,24 @@ impl<'a> AddColumnView<'a> {
};
vec.push(ColumnOptionData {
title: "Your Notifications",
description: "Stay up to date with your notifications and mentions",
title: tr!("Your Notifications", "Title for your notifications column"),
description: tr!(
"Stay up to date with your notifications and mentions",
"Description for your notifications column"
),
icon: app_images::notifications_image(ui.visuals().dark_mode),
option: AddColumnOption::Notification(source),
});
vec.push(ColumnOptionData {
title: "Someone else's Notifications",
description: "Stay up to date with someone else's notifications and mentions",
title: tr!(
"Someone else's Notifications",
"Title for someone else's notifications column"
),
description: tr!(
"Stay up to date with someone else's notifications and mentions",
"Description for someone else's notifications column"
),
icon: app_images::notifications_image(ui.visuals().dark_mode),
option: AddColumnOption::ExternalNotification,
});
@@ -524,15 +561,24 @@ impl<'a> AddColumnView<'a> {
};
vec.push(ColumnOptionData {
title: "Your Notes",
description: "Keep track of your notes & replies",
title: tr!("Your Notes", "Title for your notes column"),
description: tr!(
"Keep track of your notes & replies",
"Description for your notes column"
),
icon: app_images::profile_image(),
option: AddColumnOption::Individual(source),
});
vec.push(ColumnOptionData {
title: "Someone else's Notes",
description: "Stay up to date with someone else's notes & replies",
title: tr!(
"Someone else's Notes",
"Title for someone else's notes column"
),
description: tr!(
"Stay up to date with someone else's notes & replies",
"Description for someone else's notes column"
),
icon: app_images::profile_image(),
option: AddColumnOption::ExternalIndividual,
});
@@ -542,11 +588,15 @@ impl<'a> AddColumnView<'a> {
}
fn find_user_button() -> impl Widget {
styled_button("Find User", notedeck_ui::colors::PINK)
let label = tr!("Find User", "Label for find user button");
let color = notedeck_ui::colors::PINK;
move |ui: &mut egui::Ui| styled_button(label.as_str(), color).ui(ui)
}
fn add_column_button() -> impl Widget {
styled_button("Add", notedeck_ui::colors::PINK)
let label = tr!("Add", "Label for add column button");
let color = notedeck_ui::colors::PINK;
move |ui: &mut egui::Ui| styled_button(label.as_str(), color).ui(ui)
}
/*
@@ -571,8 +621,8 @@ pub(crate) fn sized_button(text: &str) -> impl Widget + '_ {
*/
struct ColumnOptionData {
title: &'static str,
description: &'static str,
title: String,
description: String,
icon: Image<'static>,
option: AddColumnOption,
}
@@ -648,7 +698,7 @@ pub fn render_add_column_routes(
}
// We have a decision on where we want the last per pubkey
// source to be, so let;s create a timeline from that and
// source to be, so let's create a timeline from that and
// add it to our list of timelines
AlgoOption::LastPerPubkey(Decision::Decided(list_kind)) => {
let txn = Transaction::new(ctx.ndb).unwrap();
@@ -734,8 +784,11 @@ pub fn hashtag_ui(
let text_edit = egui::TextEdit::singleline(text_buffer)
.hint_text(
RichText::new("Enter the desired hashtags here (for multiple space-separated)")
.text_style(NotedeckTextStyle::Body.text_style()),
RichText::new(tr!(
"Enter the desired hashtags here (for multiple space-separated)",
"Placeholder for hashtag input field"
))
.text_style(NotedeckTextStyle::Body.text_style()),
)
.vertical_align(Align::Center)
.desired_width(f32::INFINITY)
@@ -790,7 +843,7 @@ mod tests {
let data_str = "column:algo_selection:last_per_pubkey";
let data = &data_str.split(":").collect::<Vec<&str>>();
let mut token_writer = TokenWriter::default();
let mut parser = TokenParser::new(&data);
let mut parser = TokenParser::new(data);
let parsed = AddColumnRoute::parse_from_tokens(&mut parser).unwrap();
let expected = AddColumnRoute::Algo(AddAlgoRoute::LastPerPubkey);
parsed.serialize_tokens(&mut token_writer);

View File

@@ -12,6 +12,7 @@ use crate::{
use egui::{Margin, Response, RichText, Sense, Stroke, UiBuilder};
use enostr::Pubkey;
use nostrdb::{Ndb, Transaction};
use notedeck::tr;
use notedeck::{Images, NotedeckTextStyle};
use notedeck_ui::app_images;
use notedeck_ui::{
@@ -192,12 +193,16 @@ impl<'a> NavTitle<'a> {
if ui.data_mut(|d| *d.get_temp_mut_or_default(id)) {
let mut confirm_pressed = false;
delete_button_resp.show_tooltip_ui(|ui| {
let confirm_resp = ui.button("Confirm");
let confirm_resp = ui.button(tr!("Confirm", "Button label to confirm an action"));
if confirm_resp.clicked() {
confirm_pressed = true;
}
if confirm_resp.clicked() || ui.button("Cancel").clicked() {
if confirm_resp.clicked()
|| ui
.button(tr!("Cancel", "Button label to cancel an action"))
.clicked()
{
ui.data_mut(|d| d.insert_temp(id, false));
}
});
@@ -206,7 +211,8 @@ impl<'a> NavTitle<'a> {
}
confirm_pressed
} else {
delete_button_resp.on_hover_text("Delete this column");
delete_button_resp
.on_hover_text(tr!("Delete this column", "Tooltip for deleting a column"));
false
}
}
@@ -220,7 +226,10 @@ impl<'a> NavTitle<'a> {
// showing the hover text while showing the move tooltip causes some weird visuals
if ui.data(|d| d.get_temp::<bool>(cur_id).is_none()) {
move_resp = move_resp.on_hover_text("Moves this column to another positon");
move_resp = move_resp.on_hover_text(tr!(
"Moves this column to another position",
"Tooltip for moving a column"
));
}
if move_resp.clicked() {

View File

@@ -1,5 +1,6 @@
use crate::{app_style::deck_icon_font_sized, deck_state::DeckState};
use egui::{vec2, Button, Color32, Label, RichText, Stroke, Ui, Widget};
use notedeck::tr;
use notedeck::{NamedFontFamily, NotedeckTextStyle};
use notedeck_ui::{
anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
@@ -17,18 +18,16 @@ pub struct ConfigureDeckResponse {
pub name: String,
}
static CREATE_TEXT: &str = "Create Deck";
impl<'a> ConfigureDeckView<'a> {
pub fn new(state: &'a mut DeckState) -> Self {
Self {
state,
create_button_text: CREATE_TEXT.to_owned(),
create_button_text: tr!("Create Deck", "Button label to create a new deck"),
}
}
pub fn with_create_text(mut self, text: &str) -> Self {
self.create_button_text = text.to_owned();
pub fn with_create_text(mut self, text: String) -> Self {
self.create_button_text = text;
self
}
@@ -39,22 +38,28 @@ impl<'a> ConfigureDeckView<'a> {
);
padding(16.0, ui, |ui| {
ui.add(Label::new(
RichText::new("Deck name").font(title_font.clone()),
RichText::new(tr!("Deck name", "Label for deck name input field"))
.font(title_font.clone()),
));
ui.add_space(8.0);
ui.text_edit_singleline(&mut self.state.deck_name);
ui.add_space(8.0);
ui.add(Label::new(
RichText::new("We recommend short names")
.color(ui.visuals().noninteractive().fg_stroke.color)
.size(notedeck::fonts::get_font_size(
ui.ctx(),
&NotedeckTextStyle::Small,
)),
RichText::new(tr!(
"We recommend short names",
"Hint for deck name input field"
))
.color(ui.visuals().noninteractive().fg_stroke.color)
.size(notedeck::fonts::get_font_size(
ui.ctx(),
&NotedeckTextStyle::Small,
)),
));
ui.add_space(32.0);
ui.add(Label::new(RichText::new("Icon").font(title_font)));
ui.add(Label::new(
RichText::new(tr!("Icon", "Label for deck icon selection")).font(title_font),
));
if ui
.add(deck_icon(
@@ -121,28 +126,27 @@ impl<'a> ConfigureDeckView<'a> {
}
fn show_warnings(ui: &mut Ui, warn_no_icon: bool, warn_no_title: bool) {
if warn_no_icon || warn_no_title {
let messages = [
if warn_no_title {
"create a name for the deck"
} else {
""
},
if warn_no_icon { "select an icon" } else { "" },
];
let message = messages
.iter()
.filter(|&&m| !m.is_empty())
.copied()
.collect::<Vec<_>>()
.join(" and ");
let warning = if warn_no_title && warn_no_icon {
tr!(
"Please create a name for the deck and select an icon.",
"Error message for missing deck name and icon"
)
} else if warn_no_title {
tr!(
"Please create a name for the deck.",
"Error message for missing deck name"
)
} else if warn_no_icon {
tr!(
"Please select an icon.",
"Error message for missing deck icon"
)
} else {
String::new()
};
ui.add(
egui::Label::new(
RichText::new(format!("Please {message}.")).color(ui.visuals().error_fg_color),
)
.wrap(),
);
if !warning.is_empty() {
ui.add(egui::Label::new(RichText::new(warning).color(ui.visuals().error_fg_color)).wrap());
}
}

View File

@@ -3,14 +3,13 @@ use egui::Widget;
use crate::deck_state::DeckState;
use super::configure_deck::{ConfigureDeckResponse, ConfigureDeckView};
use notedeck::tr;
use notedeck_ui::padding;
pub struct EditDeckView<'a> {
config_view: ConfigureDeckView<'a>,
}
static EDIT_TEXT: &str = "Edit Deck";
pub enum EditDeckResponse {
Edit(ConfigureDeckResponse),
Delete,
@@ -18,7 +17,8 @@ pub enum EditDeckResponse {
impl<'a> EditDeckView<'a> {
pub fn new(state: &'a mut DeckState) -> Self {
let config_view = ConfigureDeckView::new(state).with_create_text(EDIT_TEXT);
let config_view = ConfigureDeckView::new(state)
.with_create_text(tr!("Edit Deck", "Button label to edit a deck"));
Self { config_view }
}
@@ -44,7 +44,7 @@ fn delete_button() -> impl Widget {
let size = egui::vec2(108.0, 40.0);
ui.allocate_ui_with_layout(size, egui::Layout::top_down(egui::Align::Center), |ui| {
ui.add(
egui::Button::new("Delete Deck")
egui::Button::new(tr!("Delete Deck", "Button label to delete a deck"))
.fill(ui.visuals().error_fg_color)
.min_size(size),
)

View File

@@ -7,7 +7,7 @@ use egui::{
use enostr::Pubkey;
use nostrdb::{Ndb, ProfileRecord, Transaction};
use notedeck::{
fonts::get_font_size, get_profile_url, name::get_display_name, Images, NotedeckTextStyle,
fonts::get_font_size, get_profile_url, name::get_display_name, tr, Images, NotedeckTextStyle,
};
use notedeck_ui::{
app_images, colors, profile::display_name_widget, widgets::styled_button_toggleable,
@@ -110,7 +110,7 @@ impl<'a> CustomZapView<'a> {
ui.data_mut(|d| d.insert_temp(id, cur_amount));
let resp = ui.add(styled_button_toggleable(
"Send",
&tr!("Send", "Button label to send a zap"),
colors::PINK,
is_valid_zap(maybe_sats),
));
@@ -158,7 +158,8 @@ fn show_title(ui: &mut egui::Ui) {
ui.add_space(8.0);
ui.add(egui::Label::new(
egui::RichText::new("Zap").text_style(NotedeckTextStyle::Heading2.text_style()),
egui::RichText::new(tr!("Zap", "Heading for zap (tip) action"))
.text_style(NotedeckTextStyle::Heading2.text_style()),
));
},
);
@@ -190,7 +191,10 @@ fn show_amount(ui: &mut egui::Ui, id: egui::Id, user_input: &mut String, width:
let painter = ui.painter();
let sats_galley = painter.layout_no_wrap(
"SATS".to_owned(),
tr!(
"SATS",
"Label for satoshis (Bitcoin unit) for custom zap amount input field"
),
NotedeckTextStyle::Heading4.get_font_id(ui.ctx()),
ui.visuals().noninteractive().text_color(),
);
@@ -215,7 +219,7 @@ fn show_amount(ui: &mut egui::Ui, id: egui::Id, user_input: &mut String, width:
.font(user_input_font);
let amount_resp = ui.add(Label::new(
egui::RichText::new("Amount")
egui::RichText::new(tr!("Amount", "Label for zap amount input field"))
.text_style(NotedeckTextStyle::Heading3.text_style())
.color(ui.visuals().noninteractive().text_color()),
));
@@ -398,11 +402,11 @@ impl Display for ZapSelectionButton {
ZapSelectionButton::First => write!(f, "69"),
ZapSelectionButton::Second => write!(f, "100"),
ZapSelectionButton::Third => write!(f, "420"),
ZapSelectionButton::Fourth => write!(f, "5K"),
ZapSelectionButton::Fifth => write!(f, "10K"),
ZapSelectionButton::Sixth => write!(f, "20K"),
ZapSelectionButton::Seventh => write!(f, "50K"),
ZapSelectionButton::Eighth => write!(f, "100K"),
ZapSelectionButton::Fourth => write!(f, "{}", tr!("5K", "Zap amount button for 5000 sats. Abbreviated because the button is too small to display the full amount.")),
ZapSelectionButton::Fifth => write!(f, "{}", tr!("10K", "Zap amount button for 10000 sats. Abbreviated because the button is too small to display the full amount.")),
ZapSelectionButton::Sixth => write!(f, "{}", tr!("20K", "Zap amount button for 20000 sats. Abbreviated because the button is too small to display the full amount.")),
ZapSelectionButton::Seventh => write!(f, "{}", tr!("50K", "Zap amount button for 50000 sats. Abbreviated because the button is too small to display the full amount.")),
ZapSelectionButton::Eighth => write!(f, "{}", tr!("100K", "Zap amount button for 100000 sats. Abbreviated because the button is too small to display the full amount.")),
}
}
}

View File

@@ -25,7 +25,7 @@ use notedeck_ui::{
NoteOptions, ProfilePic,
};
use notedeck::{name::get_display_name, supported_mime_hosted_at_url, NoteAction, NoteContext};
use notedeck::{name::get_display_name, supported_mime_hosted_at_url, tr, NoteAction, NoteContext};
use tracing::error;
pub struct PostView<'a, 'd> {
@@ -180,7 +180,13 @@ impl<'a, 'd> PostView<'a, 'd> {
};
let textedit = TextEdit::multiline(&mut self.draft.buffer)
.hint_text(egui::RichText::new("Write a banger note here...").weak())
.hint_text(
egui::RichText::new(tr!(
"Write a banger note here...",
"Placeholder for note input field"
))
.weak(),
)
.frame(false)
.desired_width(ui.available_width())
.layouter(&mut layouter);
@@ -605,7 +611,7 @@ fn render_post_view_media(
fn post_button(interactive: bool) -> impl egui::Widget {
move |ui: &mut egui::Ui| {
let button = egui::Button::new("Post now");
let button = egui::Button::new(tr!("Post now", "Button label to post a note"));
if interactive {
ui.add(button)
} else {

View File

@@ -2,7 +2,7 @@ use core::f32;
use egui::{vec2, Button, CornerRadius, Layout, Margin, RichText, ScrollArea, TextEdit};
use enostr::ProfileState;
use notedeck::{profile::unwrap_profile_url, Images, NotedeckTextStyle};
use notedeck::{profile::unwrap_profile_url, tr, Images, NotedeckTextStyle};
use notedeck_ui::{profile::banner, ProfilePic};
pub struct EditProfileView<'a> {
@@ -32,7 +32,14 @@ impl<'a> EditProfileView<'a> {
notedeck_ui::padding(padding, ui, |ui| {
ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| {
if ui
.add(button("Save changes", 119.0).fill(notedeck_ui::colors::PINK))
.add(
button(
tr!("Save changes", "Button label to save profile changes")
.as_str(),
119.0,
)
.fill(notedeck_ui::colors::PINK),
)
.clicked()
{
save = true;
@@ -62,42 +69,66 @@ impl<'a> EditProfileView<'a> {
);
in_frame(ui, |ui| {
ui.add(label("Display name"));
ui.add(label(
tr!("Display name", "Profile display name field label").as_str(),
));
ui.add(singleline_textedit(self.state.str_mut("display_name")));
});
in_frame(ui, |ui| {
ui.add(label("Username"));
ui.add(label(
tr!("Username", "Profile username field label").as_str(),
));
ui.add(singleline_textedit(self.state.str_mut("name")));
});
in_frame(ui, |ui| {
ui.add(label("Profile picture"));
ui.add(label(
tr!("Profile picture", "Profile picture URL field label").as_str(),
));
ui.add(multiline_textedit(self.state.str_mut("picture")));
});
in_frame(ui, |ui| {
ui.add(label("Banner"));
ui.add(label(
tr!("Banner", "Profile banner URL field label").as_str(),
));
ui.add(multiline_textedit(self.state.str_mut("banner")));
});
in_frame(ui, |ui| {
ui.add(label("About"));
ui.add(label(
tr!("About", "Profile about/bio field label").as_str(),
));
ui.add(multiline_textedit(self.state.str_mut("about")));
});
in_frame(ui, |ui| {
ui.add(label("Website"));
ui.add(label(
tr!("Website", "Profile website field label").as_str(),
));
ui.add(singleline_textedit(self.state.str_mut("website")));
});
in_frame(ui, |ui| {
ui.add(label("Lightning network address (lud16)"));
ui.add(label(
tr!(
"Lightning network address (lud16)",
"Bitcoin Lightning network address field label"
)
.as_str(),
));
ui.add(multiline_textedit(self.state.str_mut("lud16")));
});
in_frame(ui, |ui| {
ui.add(label("Nostr address (NIP-05 identity)"));
ui.add(label(
tr!(
"Nostr address (NIP-05 identity)",
"NIP-05 identity field label"
)
.as_str(),
));
ui.add(singleline_textedit(self.state.str_mut("nip05")));
let Some(nip05) = self.state.nip05() else {
@@ -121,9 +152,18 @@ impl<'a> EditProfileView<'a> {
ui.colored_label(
ui.visuals().noninteractive().fg_stroke.color,
RichText::new(if use_domain {
format!("\"{suffix}\" will be used for identification")
tr!(
"\"{domain}\" will be used for identification",
"Domain identification message",
domain = suffix
)
} else {
format!("\"{prefix}\" at \"{suffix}\" will be used for identification")
tr!(
"\"{username}\" at \"{domain}\" will be used for identification",
"Username and domain identification message",
username = prefix,
domain = suffix
)
}),
);
});

View File

@@ -4,6 +4,7 @@ pub use edit::EditProfileView;
use egui::{vec2, Color32, CornerRadius, Layout, Rect, RichText, ScrollArea, Sense, Stroke};
use enostr::Pubkey;
use nostrdb::{ProfileRecord, Transaction};
use notedeck::tr;
use notedeck_ui::profile::follow_button;
use tracing::error;
@@ -362,7 +363,7 @@ fn edit_profile_button() -> impl egui::Widget + 'static {
let edit_icon_size = vec2(16.0, 16.0);
let galley = painter.layout(
"Edit Profile".to_owned(),
tr!("Edit Profile", "Button label to edit user profile"),
NotedeckTextStyle::Button.get_font_id(ui.ctx()),
ui.visuals().text_color(),
rect.width(),

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use crate::ui::{Preview, PreviewConfig};
use egui::{Align, Button, CornerRadius, Frame, Id, Layout, Margin, Rgba, RichText, Ui, Vec2};
use enostr::{RelayPool, RelayStatus};
use notedeck::{NotedeckTextStyle, RelayAction};
use notedeck::{tr, NotedeckTextStyle, RelayAction};
use notedeck_ui::app_images;
use notedeck_ui::{colors::PINK, padding};
use tracing::debug;
@@ -26,7 +26,7 @@ impl RelayView<'_> {
ui.horizontal(|ui| {
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
ui.label(
RichText::new("Relays")
RichText::new(tr!("Relays", "Label for relay list section"))
.text_style(NotedeckTextStyle::Heading2.text_style()),
);
});
@@ -150,8 +150,11 @@ impl<'a> RelayView<'a> {
let is_enabled = self.pool.is_valid_url(text_buffer);
let text_edit = egui::TextEdit::singleline(text_buffer)
.hint_text(
RichText::new("Enter the relay here")
.text_style(NotedeckTextStyle::Body.text_style()),
RichText::new(tr!(
"Enter the relay here",
"Placeholder for relay input field"
))
.text_style(NotedeckTextStyle::Body.text_style()),
)
.vertical_align(Align::Center)
.desired_width(f32::INFINITY)
@@ -175,7 +178,7 @@ impl<'a> RelayView<'a> {
fn add_relay_button() -> Button<'static> {
Button::image_and_text(
app_images::add_relay_image().fit_to_exact_size(Vec2::new(48.0, 48.0)),
RichText::new(" Add relay")
RichText::new(tr!("Add relay", "Button label to add a relay"))
.size(16.0)
// TODO: this color should not be hard coded. Find some way to add it to the visuals
.color(PINK),
@@ -185,7 +188,8 @@ fn add_relay_button() -> Button<'static> {
fn add_relay_button2(is_enabled: bool) -> impl egui::Widget + 'static {
move |ui: &mut egui::Ui| -> egui::Response {
let button_widget = styled_button("Add", notedeck_ui::colors::PINK);
let add_text = tr!("Add", "Button label to add a relay");
let button_widget = styled_button(add_text.as_str(), notedeck_ui::colors::PINK);
ui.add_enabled(is_enabled, button_widget)
}
}
@@ -224,9 +228,9 @@ fn show_connection_status(ui: &mut Ui, status: RelayStatus) {
let bg_color = egui::lerp(Rgba::from(fg_color)..=Rgba::BLACK, 0.8).into();
let label_text = match status {
RelayStatus::Connected => "Connected",
RelayStatus::Connecting => "Connecting...",
RelayStatus::Disconnected => "Not Connected",
RelayStatus::Connected => tr!("Connected", "Status label for connected relay"),
RelayStatus::Connecting => tr!("Connecting...", "Status label for connecting relay"),
RelayStatus::Disconnected => tr!("Not Connected", "Status label for disconnected relay"),
};
let frame = Frame::new()

View File

@@ -5,7 +5,7 @@ use state::TypingType;
use crate::{timeline::TimelineTab, ui::timeline::TimelineTabView};
use egui_winit::clipboard::Clipboard;
use nostrdb::{Filter, Ndb, Transaction};
use notedeck::{NoteAction, NoteContext, NoteRef};
use notedeck::{tr, tr_plural, NoteAction, NoteContext, NoteRef};
use notedeck_ui::{
context_menu::{input_context, PasteBehavior},
icons::search_icon,
@@ -119,15 +119,21 @@ impl<'a, 'd> SearchView<'a, 'd> {
note_action = self.show_search_results(ui);
}
SearchState::Searched => {
ui.label(format!(
"Got {} results for '{}'",
self.query.notes.notes.len(),
&self.query.string
ui.label(tr_plural!(
"Got {count} result for '{query}'", // one
"Got {count} results for '{query}'", // other
"Search results count", // comment
self.query.notes.notes.len(), // count
query = &self.query.string
));
note_action = self.show_search_results(ui);
}
SearchState::Typing(TypingType::AutoSearch) => {
ui.label(format!("Searching for '{}'", &self.query.string));
ui.label(tr!(
"Searching for '{query}'",
"Search in progress message",
query = &self.query.string
));
note_action = self.show_search_results(ui);
}
@@ -282,7 +288,13 @@ fn search_box(
let response = ui.add_sized(
[ui.available_width(), search_height],
TextEdit::singleline(input)
.hint_text(RichText::new("Search notes...").weak())
.hint_text(
RichText::new(tr!(
"Search notes...",
"Placeholder for search notes input field"
))
.weak(),
)
//.desired_width(available_width - 32.0)
//.font(egui::FontId::new(font_size, egui::FontFamily::Proportional))
.margin(vec2(0.0, 8.0))

View File

@@ -12,7 +12,7 @@ use crate::{
route::Route,
};
use notedeck::{Accounts, UserAccount};
use notedeck::{tr, Accounts, UserAccount};
use notedeck_ui::{
anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
app_images, colors, View,
@@ -105,7 +105,7 @@ impl<'a> DesktopSidePanel<'a> {
ui.add_space(8.0);
ui.add(egui::Label::new(
RichText::new("DECKS")
RichText::new(tr!("DECKS", "Label for decks section in side panel"))
.size(11.0)
.color(ui.visuals().noninteractive().fg_stroke.color),
));

View File

@@ -1,5 +1,5 @@
use egui::{vec2, Button, Label, Layout, RichText};
use notedeck::{NamedFontFamily, NotedeckTextStyle};
use notedeck::{tr, NamedFontFamily, NotedeckTextStyle};
use notedeck_ui::{colors::PINK, padding};
use tracing::error;
@@ -21,10 +21,18 @@ impl<'a> SupportView<'a> {
notedeck::fonts::get_font_size(ui.ctx(), &NotedeckTextStyle::Body),
egui::FontFamily::Name(NamedFontFamily::Bold.as_str().into()),
);
ui.add(Label::new(RichText::new("Running into a bug?").font(font)));
ui.label(RichText::new("Step 1").text_style(NotedeckTextStyle::Heading3.text_style()));
ui.add(Label::new(
RichText::new(tr!("Running into a bug?", "Heading for support section")).font(font),
));
ui.label(
RichText::new(tr!("Step 1", "Step 1 label in support instructions"))
.text_style(NotedeckTextStyle::Heading3.text_style()),
);
padding(8.0, ui, |ui| {
ui.label("Open your default email client to get help from the Damus team");
ui.label(tr!(
"Open your default email client to get help from the Damus team",
"Instruction to open email client"
));
let size = vec2(120.0, 40.0);
ui.allocate_ui_with_layout(size, Layout::top_down(egui::Align::Center), |ui| {
let font_size =
@@ -47,16 +55,19 @@ impl<'a> SupportView<'a> {
if let Some(logs) = self.support.get_most_recent_log() {
ui.label(
RichText::new("Step 2").text_style(NotedeckTextStyle::Heading3.text_style()),
RichText::new(tr!("Step 2", "Step 2 label in support instructions"))
.text_style(NotedeckTextStyle::Heading3.text_style()),
);
let size = vec2(80.0, 40.0);
let copy_button = Button::new(RichText::new("Copy").size(
notedeck::fonts::get_font_size(ui.ctx(), &NotedeckTextStyle::Body),
))
let copy_button = Button::new(
RichText::new(tr!("Copy", "Button label to copy logs")).size(
notedeck::fonts::get_font_size(ui.ctx(), &NotedeckTextStyle::Body),
),
)
.fill(PINK)
.min_size(size);
padding(8.0, ui, |ui| {
ui.add(Label::new("Press the button below to copy your most recent logs to your system's clipboard. Then paste it into your email.").wrap());
ui.add(Label::new(RichText::new(tr!("Press the button below to copy your most recent logs to your system's clipboard. Then paste it into your email.", "Instruction for copying logs"))).wrap());
ui.allocate_ui_with_layout(size, Layout::top_down(egui::Align::Center), |ui| {
if ui.add(copy_button).clicked() {
ui.ctx().copy_text(logs.to_string());
@@ -76,7 +87,9 @@ impl<'a> SupportView<'a> {
}
fn open_email_button(font_size: f32, size: egui::Vec2) -> impl egui::Widget {
Button::new(RichText::new("Open Email").size(font_size))
.fill(PINK)
.min_size(size)
Button::new(
RichText::new(tr!("Open Email", "Button label to open email client")).size(font_size),
)
.fill(PINK)
.min_size(size)
}

View File

@@ -8,7 +8,7 @@ use std::f32::consts::PI;
use tracing::{error, warn};
use crate::timeline::{TimelineCache, TimelineKind, TimelineTab, ViewFilter};
use notedeck::{note::root_note_id_from_selected_id, NoteAction, NoteContext, ScrollInfo};
use notedeck::{note::root_note_id_from_selected_id, tr, NoteAction, NoteContext, ScrollInfo};
use notedeck_ui::{
anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
NoteOptions, NoteView,
@@ -281,17 +281,19 @@ pub fn tabs_ui(ui: &mut egui::Ui, selected: usize, views: &[TimelineTab]) -> usi
let ind = state.index();
let txt = match views[ind as usize].filter {
ViewFilter::Notes => "Notes",
ViewFilter::NotesAndReplies => "Notes & Replies",
ViewFilter::Notes => tr!("Notes", "Label for notes-only filter"),
ViewFilter::NotesAndReplies => {
tr!("Notes & Replies", "Label for notes and replies filter")
}
};
let res = ui.add(egui::Label::new(txt).selectable(false));
let res = ui.add(egui::Label::new(txt.clone()).selectable(false));
// underline
if state.is_selected() {
let rect = res.rect;
let underline =
shrink_range_to_width(rect.x_range(), get_label_width(ui, txt) * 1.15);
shrink_range_to_width(rect.x_range(), get_label_width(ui, &txt) * 1.15);
#[allow(deprecated)]
let underline_y = ui.painter().round_to_pixel(rect.bottom()) - 1.5;
return (underline, underline_y);

View File

@@ -1,6 +1,6 @@
use egui::{vec2, CornerRadius, Layout};
use notedeck::{
get_current_wallet, Accounts, DefaultZapMsats, GlobalWallet, NotedeckTextStyle,
get_current_wallet, tr, Accounts, DefaultZapMsats, GlobalWallet, NotedeckTextStyle,
PendingDefaultZapState, Wallet, WalletError, WalletUIState, ZapWallet,
};
@@ -202,8 +202,11 @@ fn show_no_wallet(
ui.horizontal_wrapped(|ui| 's: {
let text_edit = egui::TextEdit::singleline(&mut state.buf)
.hint_text(
egui::RichText::new("Paste your NWC URI here...")
.text_style(notedeck::NotedeckTextStyle::Body.text_style()),
egui::RichText::new(tr!(
"Paste your NWC URI here...",
"Placeholder text for NWC URI input"
))
.text_style(notedeck::NotedeckTextStyle::Body.text_style()),
)
.vertical_align(egui::Align::Center)
.desired_width(f32::INFINITY)
@@ -218,8 +221,14 @@ fn show_no_wallet(
};
let error_str = match error_msg {
WalletError::InvalidURI => "Invalid NWC URI",
WalletError::NoWallet => "Add a wallet to continue",
WalletError::InvalidURI => tr!(
"Invalid NWC URI",
"Error message for invalid Nostr Wallet Connect URI"
),
WalletError::NoWallet => tr!(
"Add a wallet to continue",
"Error message for missing wallet"
),
};
ui.colored_label(ui.visuals().warn_fg_color, error_str);
});
@@ -229,15 +238,21 @@ fn show_no_wallet(
if show_local_only {
ui.checkbox(
&mut state.for_local_only,
"Use this wallet for the current account only",
tr!(
"Use this wallet for the current account only",
"Checkbox label for using wallet only for current account"
),
);
ui.add_space(8.0);
}
ui.with_layout(Layout::top_down(egui::Align::Center), |ui| {
ui.add(styled_button("Add Wallet", notedeck_ui::colors::PINK))
.clicked()
.then_some(WalletAction::SaveURI)
ui.add(styled_button(
tr!("Add Wallet", "Button label to add a wallet").as_str(),
notedeck_ui::colors::PINK,
))
.clicked()
.then_some(WalletAction::SaveURI)
})
.inner
}
@@ -268,7 +283,10 @@ fn show_with_wallet(
ui.with_layout(Layout::bottom_up(egui::Align::Min), |ui| 's: {
if ui
.add(styled_button("Delete Wallet", ui.visuals().window_fill))
.add(styled_button(
tr!("Delete Wallet", "Button label to delete a wallet").as_str(),
ui.visuals().window_fill,
))
.clicked()
{
action = Some(WalletAction::Delete);
@@ -280,7 +298,10 @@ fn show_with_wallet(
&& ui
.checkbox(
&mut false,
"Add a different wallet that will only be used for this account",
tr!(
"Add a different wallet that will only be used for this account",
"Button label to add a different wallet"
),
)
.clicked()
{
@@ -308,7 +329,7 @@ fn show_default_zap(ui: &mut egui::Ui, state: &mut DefaultZapState) -> Option<Wa
vec2(ui.available_width(), 50.0),
egui::Layout::left_to_right(egui::Align::Center).with_main_wrap(true),
|ui| {
ui.label("Default amount per zap: ");
ui.label(tr!("Default amount per zap: ", "Label for default zap amount input"));
match state {
DefaultZapState::Pending(pending_default_zap_state) => {
let text = &mut pending_default_zap_state.amount_sats;
@@ -340,10 +361,10 @@ fn show_default_zap(ui: &mut egui::Ui, state: &mut DefaultZapState) -> Option<Wa
ui.memory_mut(|m| m.request_focus(id));
ui.label(" sats");
ui.label(tr!("sats", "Unit label for satoshis (Bitcoin unit) for configuring default zap amount in wallet settings."));
if ui
.add(styled_button("Save", ui.visuals().widgets.active.bg_fill))
.add(styled_button(tr!("Save", "Button to save default zap amount").as_str(), ui.visuals().widgets.active.bg_fill))
.clicked()
{
action = Some(WalletAction::SetDefaultZapSats(text.to_string()));
@@ -353,14 +374,14 @@ fn show_default_zap(ui: &mut egui::Ui, state: &mut DefaultZapState) -> Option<Wa
if let Some(wallet_action) = show_valid_msats(ui, **msats) {
action = Some(wallet_action);
}
ui.label(" sats");
ui.label(tr!("sats", "Unit label for satoshis (Bitcoin unit) for configuring default zap amount in wallet settings."));
}
}
if let DefaultZapState::Pending(pending) = state {
if let Some(error_message) = &pending.error_message {
let msg_str = match error_message {
notedeck::DefaultZapError::InvalidUserInput => "Invalid amount",
notedeck::DefaultZapError::InvalidUserInput => tr!("Invalid amount", "Error message for invalid zap amount"),
};
ui.colored_label(ui.visuals().warn_fg_color, msg_str);
@@ -388,7 +409,7 @@ fn show_valid_msats(ui: &mut egui::Ui, msats: u64) -> Option<WalletAction> {
let resp = resp
.on_hover_cursor(egui::CursorIcon::PointingHand)
.on_hover_text_at_pointer("Click to edit");
.on_hover_text_at_pointer(tr!("Click to edit", "Hover text for editable zap amount"));
let painter = ui.painter_at(resp.rect);