Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Localization of Widgets strings #426

Closed
wants to merge 14 commits into from
Closed
29 changes: 28 additions & 1 deletion egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
frame_state::FrameState,
input_state::*,
layers::GraphicLayers,
localization::{Language, Localization},
mutex::{Mutex, MutexGuard},
*,
};
Expand Down Expand Up @@ -326,6 +327,7 @@ pub struct Context {
fonts: Option<Arc<Fonts>>,
memory: Arc<Mutex<Memory>>,
animation_manager: Arc<Mutex<AnimationManager>>,
localization: Arc<Mutex<Localization>>,

input: InputState,

Expand Down Expand Up @@ -354,6 +356,7 @@ impl Clone for Context {
output: self.output.clone(),
paint_stats: self.paint_stats.clone(),
repaint_requests: self.repaint_requests.load(SeqCst).into(),
localization: self.localization.clone(),
}
}
}
Expand Down Expand Up @@ -559,9 +562,11 @@ impl Context {
input.pixels_per_point = new_pixels_per_point;
}

let lang = self.memory().new_language.take().unwrap_or_default();
self.set_localization(lang);

self.input = input.begin_frame(new_raw_input);
self.frame_state.lock().begin_frame(&self.input);

{
// Load new fonts if required:
let new_font_definitions = self.memory().new_font_definitions.take();
Expand Down Expand Up @@ -950,3 +955,25 @@ impl Context {
self.set_style(style);
}
}

/// ## Localization
impl Context {
/// Sets the current language of the default texts within [`crate::widgets`] and [`crate::containers`].
///
/// The languages available for localization can be seen in [`crate::localization::Language`].
pub fn set_localization(&self, lang: Language) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn set_localization(&self, lang: Language) {
pub fn set_localization(&self, localization: Localization) {

self.localization().set_localization(lang.clone());
self.memory().new_language = Some(lang);
}

/// Use this to access the `Localization` struct stored within context and also all the texts stored within it.
pub fn localization(&self) -> MutexGuard<'_, Localization> {
self.localization.lock()
}

/// Returns the language that will be set in the next frame
pub fn lang(&self) -> Language {
let new_lang = self.memory().new_language.clone().unwrap_or_default();
new_lang
}
}
1 change: 1 addition & 0 deletions egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ pub mod widgets;
pub use epaint;
pub use epaint::emath;

pub mod localization;
// Can't add deprecation notice due to https://github.com/rust-lang/rust/issues/30827
pub use epaint as paint; // historical reasons

Expand Down
100 changes: 100 additions & 0 deletions egui/src/localization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! Localization of texts in widgets and containers.
use std::default::Default;

/// Handles the localization of default texts in [`crate::widgets`] and [`crate::containers`].
///
/// You can set the current language with [`crate::Context::set_localization`]. For example: `ctx.set_localization(Language::BahasaMalaysia)`.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Localization {
/// The current language used for texts.
pub lang: Language,

// Texts for sliders
pub slider_tooltip: &'static str,

// Texts for colour pickers
pub click_copy: &'static str,
pub cp_edit: &'static str,
pub cp_blending: &'static str,
pub cp_additive: &'static str,
pub cp_normal: &'static str,
pub cp_selected_color: &'static str,
pub cp_hue: &'static str,
pub cp_saturation: &'static str,
pub cp_value: &'static str,
pub lang_text: &'static str,
}

impl Default for Localization {
/// Sets English as the default language for texts.
///
/// It can also be used to switch from another language to English.
fn default() -> Self {
Self {
lang: Language::English,
slider_tooltip: "Drag to edit or click to enter a value.\nPress 'Shift' while dragging for better control",
click_copy: "Click to copy",
cp_edit: "Click to edit color",
cp_blending: "Blending",
cp_additive: "Additive",
cp_normal: "Normal",
cp_selected_color: "Selected color",
cp_hue: "Hue",
cp_saturation: "Saturation",
cp_value: "Value",
lang_text: "Language",
}
}
}

#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
/// Specifies the languages currently available for localization and is required by [`crate::Context::set_localization`] as the parameter type.
pub enum Language {
English,
BahasaMalaysia,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this list? Can't the user just call context.set_localization(Localization::malay()); instead? That way it is also easy for a user to add a new language without having to make a PR to egui.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Localization::lang could be changed to &'static str too (e.g. "english")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Localization::lang could be changed to &'static str too (e.g. "english")

Agree, some clarification: <language code>[_<territory code>] should be used (en, en_US and so on)

}

impl Default for Language {
fn default() -> Self {
Language::English
}
}

impl Localization {
/// Pattern matches on ['Language'] to call the function that'll set the fields within Localization accordingly.
pub fn set_localization(&mut self, lang: Language) {
*self = match lang {
Language::English => Localization::default(),
Language::BahasaMalaysia => Localization::malay(),
};
}

/// Returns the current language used for texts.
pub fn lang(&self) -> Language {
match self.lang {
Language::BahasaMalaysia => Language::BahasaMalaysia,
_ => Language::English,
}
}

/// Sets Bahasa Malaysia as the language for texts.
fn malay() -> Self {
Self {
lang: Language::BahasaMalaysia,
slider_tooltip: "Tarik untuk ubah atau klik untuk masukkan jumlah.\nTekan 'Shift' sambil tarik untuk pergerakan lebih terkawal.",
click_copy: "Klik untuk salin",
cp_edit: "Klik untuk ubah warna",
cp_blending: "Campuran",
cp_additive: "Tambahan",
cp_normal: "Biasa",
cp_selected_color: "Warna pilihan",
cp_hue: "Rona",
cp_saturation: "Ketepuan",
cp_value: "Nilai",
lang_text: "Bahasa",
}
}
}
8 changes: 7 additions & 1 deletion egui/src/memory.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::collections::{HashMap, HashSet};

use crate::{any, area, window, Id, InputState, LayerId, Pos2, Rect, Style};
use crate::{
any, area, localization::Language, window, Id, InputState, LayerId, Pos2, Rect, Style,
};

// ----------------------------------------------------------------------------

Expand All @@ -13,6 +15,7 @@ use crate::{any, area, window, Id, InputState, LayerId, Pos2, Rect, Style};
///
/// If you want to store data for your widgets, you should look at `data`/`data_temp` and
/// `id_data`/`id_data_temp` fields, and read the documentation of [`any`] module.

#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
Expand Down Expand Up @@ -53,6 +56,9 @@ pub struct Memory {
/// new fonts that will be applied at the start of the next frame
pub(crate) new_font_definitions: Option<epaint::text::FontDefinitions>,

/// new language that will be applied at the start of the next frame
pub(crate) new_language: Option<Language>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this - let's make it instant instead.


#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) interaction: Interaction,

Expand Down
38 changes: 26 additions & 12 deletions egui/src/widgets/color_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,15 @@ pub enum Alpha {

fn color_text_ui(ui: &mut Ui, color: impl Into<Color32>) {
let color = color.into();
let button_text = ui.ctx().localization().click_copy;
ui.horizontal(|ui| {
let [r, g, b, a] = color.to_array();

ui.label(format!(
"RGBA (premultiplied): rgba({}, {}, {}, {})",
r, g, b, a
));

if ui.button("📋").on_hover_text("Click to copy").clicked() {
if ui.button("📋").on_hover_text(button_text).clicked() {
ui.output().copied_text = format!("{}, {}, {}, {}", r, g, b, a);
}
});
Expand All @@ -225,14 +226,18 @@ fn color_text_ui(ui: &mut Ui, color: impl Into<Color32>) {
fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma, alpha: Alpha) {
color_text_ui(ui, *hsva);

let blending_text = ui.ctx().localization().cp_blending;
let additive_text = ui.ctx().localization().cp_additive;
let normal_text = ui.ctx().localization().cp_normal;

if alpha == Alpha::BlendOrAdditive {
// We signal additive blending by storing a negative alpha (a bit ironic).
let a = &mut hsva.a;
let mut additive = *a < 0.0;
ui.horizontal(|ui| {
ui.label("Blending:");
ui.radio_value(&mut additive, false, "Normal");
ui.radio_value(&mut additive, true, "Additive");
ui.label(blending_text);
ui.radio_value(&mut additive, false, additive_text);
ui.radio_value(&mut additive, true, normal_text);

if additive {
*a = -a.abs();
Expand Down Expand Up @@ -263,10 +268,12 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma, alpha: Alpha) {

let opaque = HsvaGamma { a: 1.0, ..*hsva };

let selected_color_text = ui.ctx().localization().cp_selected_color;

if alpha == Alpha::Opaque {
hsva.a = 1.0;
show_color(ui, *hsva, current_color_size);
ui.label("Selected color");
ui.label(selected_color_text);
ui.end_row();
} else {
let a = &mut hsva.a;
Expand All @@ -285,7 +292,7 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma, alpha: Alpha) {
}

show_color(ui, *hsva, current_color_size);
ui.label("Selected color");
ui.label(selected_color_text);
ui.end_row();
}

Expand All @@ -303,19 +310,24 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma, alpha: Alpha) {
}
.into()
});
ui.label("Hue");

let hue_text = ui.ctx().localization().cp_hue;
let saturation_text = ui.ctx().localization().cp_saturation;
let value_text = ui.ctx().localization().cp_value;

ui.label(hue_text);
ui.end_row();

color_slider_1d(ui, s, |s| HsvaGamma { s, ..opaque }.into());
ui.label("Saturation");
ui.label(saturation_text);
ui.end_row();

color_slider_1d(ui, v, |v| HsvaGamma { v, ..opaque }.into());
ui.label("Value");
ui.label(value_text);
ui.end_row();

color_slider_2d(ui, v, s, |v, s| HsvaGamma { s, v, ..opaque }.into());
ui.label("Value / Saturation");
ui.label(format!("{} / {}", value_text, saturation_text));
ui.end_row();
});
}
Expand All @@ -335,7 +347,9 @@ fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> bool {

pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response {
let pupup_id = ui.auto_id_with("popup");
let mut button_response = color_button(ui, (*hsva).into()).on_hover_text("Click to edit color");

let edit_text = ui.ctx().localization().cp_edit;
let mut button_response = color_button(ui, (*hsva).into()).on_hover_text(edit_text);

if button_response.clicked() {
ui.memory().toggle_popup(pupup_id);
Expand Down
6 changes: 4 additions & 2 deletions egui/src/widgets/drag_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,15 @@ impl<'a> Widget for DragValue<'a> {
.min_size(ui.spacing().interact_size); // TODO: find some more generic solution to this

let response = ui.add(button);
let slider_tooltip_text = &ui.ctx().localization().slider_tooltip;
let response = response
.on_hover_cursor(CursorIcon::ResizeHorizontal)
.on_hover_text(format!(
"{}{}{}\nDrag to edit or click to enter a value.\nPress 'Shift' while dragging for better control.",
"{}{}{}\n{}",
prefix,
value as f32, // Show full precision value on-hover. TODO: figure out f64 vs f32
suffix
suffix,
slider_tooltip_text
));

if response.clicked() {
Expand Down
Loading