desktop: Add LocalizableText struct

This structure allows specifying localizable text when there's no locale
available, but it will be available in the future (e.g. when rendering).
It has several advantages over specifying bare labels:

1. labels cannot be easily mixed with non-localizable text,
2. it's easy to find labels in use by looking for enum variant usages.

It's also better than providing a function of locale which requires
complicated type definitions and dynamic dispatch.
This commit is contained in:
Kamil Jarosz 2024-08-29 22:51:29 +02:00
parent 646ab4e5bc
commit b4422bffc4
2 changed files with 26 additions and 11 deletions

View File

@ -77,6 +77,22 @@ pub fn text_with_args<'a, T: AsRef<str>>(
}) })
} }
pub enum LocalizableText {
#[allow(dead_code)]
NonLocalizedText(Cow<'static, str>),
LocalizedText(&'static str),
}
impl LocalizableText {
pub fn localize(&self, locale: &LanguageIdentifier) -> Cow<'_, str> {
match self {
LocalizableText::NonLocalizedText(Cow::Borrowed(text)) => Cow::Borrowed(text),
LocalizableText::NonLocalizedText(Cow::Owned(text)) => Cow::Borrowed(text),
LocalizableText::LocalizedText(id) => text(locale, id),
}
}
}
/// Size of the top menu bar in pixels. /// Size of the top menu bar in pixels.
/// This is the offset at which the movie will be shown, /// This is the offset at which the movie will be shown,
/// and added to the window size if trying to match a movie. /// and added to the window size if trying to match a movie.

View File

@ -1,6 +1,6 @@
use crate::custom_event::RuffleEvent; use crate::custom_event::RuffleEvent;
use crate::gui::widgets::PathOrUrlField; use crate::gui::widgets::PathOrUrlField;
use crate::gui::{text, FilePicker}; use crate::gui::{text, FilePicker, LocalizableText};
use crate::player::LaunchOptions; use crate::player::LaunchOptions;
use egui::{ use egui::{
emath, Align2, Button, Checkbox, ComboBox, Grid, Layout, Slider, TextEdit, Ui, Widget, Window, emath, Align2, Button, Checkbox, ComboBox, Grid, Layout, Slider, TextEdit, Ui, Widget, Window,
@ -158,7 +158,7 @@ impl OpenDialog {
} }
}), }),
), ),
Box::new(|locale| text(locale, "align-force")), LocalizableText::LocalizedText("align-force"),
false, false,
), ),
); );
@ -189,10 +189,10 @@ impl OpenDialog {
StageScaleMode::ExactFit => Some(text(locale, "scale-mode-exactfit-tooltip")), StageScaleMode::ExactFit => Some(text(locale, "scale-mode-exactfit-tooltip")),
StageScaleMode::NoBorder => Some(text(locale, "scale-mode-noborder-tooltip")), StageScaleMode::NoBorder => Some(text(locale, "scale-mode-noborder-tooltip")),
})), })),
Box::new(|locale| text(locale, "scale-mode-force")), LocalizableText::LocalizedText("scale-mode-force"),
false, false,
) )
.with_checkbox_tooltip(Box::new(|locale| text(locale, "scale-mode-force-tooltip"))), .with_checkbox_tooltip(LocalizableText::LocalizedText("scale-mode-force-tooltip")),
); );
let load_behavior = OptionalField::new( let load_behavior = OptionalField::new(
defaults.player.load_behavior, defaults.player.load_behavior,
@ -729,7 +729,6 @@ impl<T: emath::Numeric> InnerField for NumberField<T> {
type ValueToTextFn<T> = dyn Fn(T, &LanguageIdentifier) -> Cow<'static, str>; type ValueToTextFn<T> = dyn Fn(T, &LanguageIdentifier) -> Cow<'static, str>;
type ValueToOptTextFn<T> = dyn Fn(T, &LanguageIdentifier) -> Option<Cow<'static, str>>; type ValueToOptTextFn<T> = dyn Fn(T, &LanguageIdentifier) -> Option<Cow<'static, str>>;
type LabelFn = dyn Fn(&LanguageIdentifier) -> Cow<'static, str>;
struct EnumDropdownField<T: Copy> { struct EnumDropdownField<T: Copy> {
id: egui::Id, id: egui::Id,
@ -827,13 +826,13 @@ impl InnerField for BooleanDropdownField {
struct FieldWithCheckbox<T: InnerField> { struct FieldWithCheckbox<T: InnerField> {
field: T, field: T,
checkbox_label: Box<LabelFn>, checkbox_label: LocalizableText,
checkbox_default: bool, checkbox_default: bool,
tooltip_label: Option<Box<LabelFn>>, tooltip_label: Option<LocalizableText>,
} }
impl<T: InnerField> FieldWithCheckbox<T> { impl<T: InnerField> FieldWithCheckbox<T> {
pub fn new(field: T, checkbox_label: Box<LabelFn>, checkbox_default: bool) -> Self { pub fn new(field: T, checkbox_label: LocalizableText, checkbox_default: bool) -> Self {
Self { Self {
field, field,
checkbox_label, checkbox_label,
@ -842,7 +841,7 @@ impl<T: InnerField> FieldWithCheckbox<T> {
} }
} }
pub fn with_checkbox_tooltip(mut self, tooltip_label: Box<LabelFn>) -> Self { pub fn with_checkbox_tooltip(mut self, tooltip_label: LocalizableText) -> Self {
self.tooltip_label = Some(tooltip_label); self.tooltip_label = Some(tooltip_label);
self self
} }
@ -858,9 +857,9 @@ impl<T: InnerField> InnerField for FieldWithCheckbox<T> {
fn ui(&self, ui: &mut Ui, value: &mut Self::Value, error: bool, locale: &LanguageIdentifier) { fn ui(&self, ui: &mut Ui, value: &mut Self::Value, error: bool, locale: &LanguageIdentifier) {
self.field.ui(ui, &mut value.0, error, locale); self.field.ui(ui, &mut value.0, error, locale);
let response = ui.checkbox(&mut value.1, (self.checkbox_label)(locale)); let response = ui.checkbox(&mut value.1, self.checkbox_label.localize(locale));
if let Some(ref tooltip_label) = self.tooltip_label { if let Some(ref tooltip_label) = self.tooltip_label {
response.on_hover_text_at_pointer(tooltip_label(locale)); response.on_hover_text_at_pointer(tooltip_label.localize(locale));
} }
} }