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
24 changes: 23 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 @@ -320,6 +321,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 @@ -348,6 +350,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 @@ -553,9 +556,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 @@ -928,3 +933,20 @@ impl Context {
self.set_style(style);
}
}

/// ## Localization
impl Context {
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.clone());
}

pub fn localization(&self) -> MutexGuard<'_, Localization> {
self.localization.lock()
}

pub fn lang(&self) -> Language {
let current_lang = self.memory().new_language.clone().unwrap_or_default();
current_lang
}
}
1 change: 1 addition & 0 deletions egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,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
84 changes: 84 additions & 0 deletions egui/src/localization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::default::Default;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Localization {
pub lang: &'static str,
pub slider_tooltip: &'static str,
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 {
fn default() -> Self {
Self {
lang: "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))]
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 {
pub fn set_localization(&mut self, lang: Language) {
*self = match lang {
Language::English => Localization::default(),
Language::BahasaMalaysia => Localization::malay(),
};
}

pub fn lang(&self) -> Language {
match self.lang {
"English" => Language::English,
"Bahasa Malaysia" => Language::BahasaMalaysia,
_ => Language::English,
}
}

pub fn malay() -> Self {
Self {
lang: "Bahasa Malaysia",
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 @@ -206,14 +206,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.clone();
emilk marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -222,14 +223,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.clone();
let additive_text = ui.ctx().localization().cp_additive.clone();
let normal_text = ui.ctx().localization().cp_normal.clone();

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 @@ -260,10 +265,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 @@ -282,7 +289,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 @@ -300,19 +307,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 @@ -332,7 +344,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
21 changes: 20 additions & 1 deletion egui_demo_lib/src/apps/demo/demo_app_windows.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::Demo;
use egui::{CtxRef, ScrollArea, Ui, Window};
use egui::{localization::Language, CtxRef, ScrollArea, Ui, Window};
use std::collections::BTreeSet;

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -140,6 +140,7 @@ pub struct DemoWindows {
demos: Demos,
tests: Tests,
egui_windows: EguiWindows,
lang: Language,
}

impl DemoWindows {
Expand All @@ -150,6 +151,7 @@ impl DemoWindows {
demos,
tests,
egui_windows,
lang,
} = self;

egui::SidePanel::left("side_panel", 190.0).show(ctx, |ui| {
Expand All @@ -176,6 +178,22 @@ impl DemoWindows {
);
});

let lang_text = ctx.localization().lang_text;
let previous_lang = lang.clone();

egui::ComboBox::from_label(lang_text)
.selected_text(format!("{:?}", lang))
.show_ui(ui, |ui| {
ui.selectable_value(lang, Language::English, "English");
ui.selectable_value(lang, Language::BahasaMalaysia, "BahasaMalaysia");
});

let current_lang = lang.clone();

if previous_lang != current_lang {
ctx.set_localization(current_lang);
}

ui.separator();
demos.checkboxes(ui);
ui.separator();
Expand Down Expand Up @@ -217,6 +235,7 @@ impl DemoWindows {
demos,
tests,
egui_windows,
..
} = self;

demos.windows(ctx);
Expand Down