aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/dialogs.rs42
-rw-r--r--src/handlers/export.rs8
-rw-r--r--src/handlers/file.rs5
-rw-r--r--src/i18n.rs61
-rw-r--r--src/main.rs134
-rw-r--r--src/preferences/pages/editor.rs13
-rw-r--r--src/preferences/pages/general.rs30
-rw-r--r--src/preferences/pages/map.rs17
-rw-r--r--src/preferences/pages/stages.rs22
-rw-r--r--src/preferences/pages/templates.rs77
-rw-r--r--src/preferences/window.rs15
-rw-r--r--src/stages.rs159
12 files changed, 374 insertions, 209 deletions
diff --git a/src/dialogs.rs b/src/dialogs.rs
index f7fb9cf..8f5b951 100644
--- a/src/dialogs.rs
+++ b/src/dialogs.rs
@@ -25,13 +25,14 @@ use crate::AppModel;
use crate::actions::{Action, ImageFormat};
use crate::constants;
use crate::file_registry;
+use crate::tr;
/// Creates a file filter for .wmap files in file pickers.
pub fn create_file_filter() -> gtk::FileFilter {
let filter = gtk::FileFilter::new();
filter.add_pattern(&format!("*.{}", constants::FILE_EXTENSION));
filter.add_mime_type(constants::MIME_TYPE);
- filter.set_name(Some("Wardley Map Files"));
+ filter.set_name(Some(&tr!("dialog.file_type.wmap")));
filter
}
@@ -58,12 +59,12 @@ pub fn show_open_dialog(
sender: ComponentSender<AppModel>,
) {
let dialog = gtk::FileChooserDialog::new(
- Some("Open Map"),
+ Some(&tr!("dialog.open.title")),
Some(window),
gtk::FileChooserAction::Open,
&[
- ("Cancel", gtk::ResponseType::Cancel),
- ("Open", gtk::ResponseType::Accept),
+ (&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel),
+ (&tr!("dialog.action.open"), gtk::ResponseType::Accept),
],
);
dialog.add_filter(&create_file_filter());
@@ -98,26 +99,27 @@ pub fn show_save_dialog(
sender: ComponentSender<AppModel>,
) {
let dialog = gtk::FileChooserDialog::new(
- Some("Save Map As"),
+ Some(&tr!("dialog.save.title")),
Some(window),
gtk::FileChooserAction::Save,
&[
- ("Cancel", gtk::ResponseType::Cancel),
- ("Save", gtk::ResponseType::Accept),
+ (&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel),
+ (&tr!("dialog.action.save"), gtk::ResponseType::Accept),
],
);
dialog.add_filter(&create_file_filter());
+ let default_filename = "map.wmap";
if let Some(ref current_path) = current_file {
if let Some(parent) = current_path.parent() {
let folder = gtk::gio::File::for_path(parent);
dialog.set_current_folder(Some(&folder)).ok();
}
if let Some(filename) = current_path.file_name() {
- dialog.set_current_name(filename.to_str().unwrap_or("map.wmap"));
+ dialog.set_current_name(filename.to_str().unwrap_or(default_filename));
}
} else {
- dialog.set_current_name("map.wmap");
+ dialog.set_current_name(default_filename);
}
dialog.connect_response(move |dialog, response| {
@@ -143,12 +145,12 @@ pub fn show_unsaved_changes_dialog(
gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT,
gtk::MessageType::Warning,
gtk::ButtonsType::None,
- "Save changes before closing?",
+ tr!("dialog.unsaved_changes.message"),
);
- dialog.set_title(Some("Unsaved Changes"));
- dialog.add_button("Don't Save", gtk::ResponseType::Reject);
- dialog.add_button("Cancel", gtk::ResponseType::Cancel);
- dialog.add_button("Save", gtk::ResponseType::Accept);
+ dialog.set_title(Some(&tr!("dialog.unsaved_changes.title")));
+ dialog.add_button(&tr!("dialog.action.dont_save"), gtk::ResponseType::Reject);
+ dialog.add_button(&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel);
+ dialog.add_button(&tr!("dialog.action.save"), gtk::ResponseType::Accept);
let window = window.clone();
dialog.connect_response(move |dialog, response| {
@@ -179,10 +181,10 @@ fn create_image_filter(format: ImageFormat) -> gtk::FileFilter {
filter.add_pattern(&format!("*.{}", format.extension()));
filter.add_mime_type(format.mime_type());
let name = match format {
- ImageFormat::Png => "PNG Image",
- ImageFormat::Svg => "SVG Image",
+ ImageFormat::Png => tr!("dialog.file_type.png"),
+ ImageFormat::Svg => tr!("dialog.file_type.svg"),
};
- filter.set_name(Some(name));
+ filter.set_name(Some(&name));
filter
}
@@ -193,12 +195,12 @@ pub fn show_export_dialog(
sender: ComponentSender<AppModel>,
) {
let dialog = gtk::FileChooserDialog::new(
- Some("Export Map as Image"),
+ Some(&tr!("dialog.export.title")),
Some(window),
gtk::FileChooserAction::Save,
&[
- ("Cancel", gtk::ResponseType::Cancel),
- ("Export", gtk::ResponseType::Accept),
+ (&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel),
+ (&tr!("dialog.action.export"), gtk::ResponseType::Accept),
],
);
diff --git a/src/handlers/export.rs b/src/handlers/export.rs
index 0b64a73..a0a4451 100644
--- a/src/handlers/export.rs
+++ b/src/handlers/export.rs
@@ -18,24 +18,26 @@ use std::path::PathBuf;
use crate::AppModel;
use crate::actions::ImageFormat;
use crate::dialogs;
+use crate::tr;
+use crate::stages::LocalizedStageType;
use wmap_renderer::{render_to_png, render_to_svg};
/// Exports the map to an image file in the specified format.
pub fn export_to_file(model: &AppModel, path: PathBuf, format: ImageFormat) {
let result: Result<(), String> = match format {
ImageFormat::Png => {
- render_to_png(&model.map, &model.stage_type, &model.render_configuration)
+ render_to_png(&model.map, &model.stage_type.to_localized(), &model.render_configuration)
.map_err(|e| e.to_string())
.and_then(|data| std::fs::write(&path, data).map_err(|e| e.to_string()))
}
ImageFormat::Svg => {
- render_to_svg(&model.map, &model.stage_type, &model.render_configuration)
+ render_to_svg(&model.map, &model.stage_type.to_localized(), &model.render_configuration)
.map_err(|e| e.to_string())
.and_then(|data| std::fs::write(&path, data).map_err(|e| e.to_string()))
}
};
if let Err(error) = result {
- dialogs::show_error(&model.window, "Error Exporting Image", &error);
+ dialogs::show_error(&model.window, &tr!("error.file.export"), &error);
}
}
diff --git a/src/handlers/file.rs b/src/handlers/file.rs
index 615a1f7..bd501e8 100644
--- a/src/handlers/file.rs
+++ b/src/handlers/file.rs
@@ -23,6 +23,7 @@ use crate::actions::Action;
use crate::constants;
use crate::dialogs;
use crate::file_registry;
+use crate::tr;
/// Loads a file into the current document, updating the file registry.
pub fn load_file(model: &mut AppModel, path: PathBuf) {
@@ -35,7 +36,7 @@ pub fn load_file(model: &mut AppModel, path: PathBuf) {
model.modified = false;
}
Err(error) => {
- dialogs::show_error(&model.window, "Error Opening File", &error.to_string());
+ dialogs::show_error(&model.window, &tr!("errors.file.open"), &error.to_string());
}
}
}
@@ -58,7 +59,7 @@ pub fn save_to_file(model: &mut AppModel, path: PathBuf, close_after: bool) {
}
}
Err(error) => {
- dialogs::show_error(&model.window, "Error Saving File", &error.to_string());
+ dialogs::show_error(&model.window, &tr!("errors.file.save"), &error.to_string());
}
}
}
diff --git a/src/i18n.rs b/src/i18n.rs
new file mode 100644
index 0000000..1c574e0
--- /dev/null
+++ b/src/i18n.rs
@@ -0,0 +1,61 @@
+// Map, wardley map editor for linux
+// Copyright (C) 2026 Rubén Beltrán del Río
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+//! Internationalization support using gettext.
+
+use gettextrs::{bindtextdomain, setlocale, textdomain, LocaleCategory};
+
+/// The gettext domain for the application.
+pub const GETTEXT_DOMAIN: &str = "systems.tranquil.Map";
+
+/// Initializes the localization system.
+pub fn init() {
+ setlocale(LocaleCategory::LcAll, "");
+
+ // Try standard locations for locale files
+ let locale_dirs = [
+ // Development: local po directory
+ concat!(env!("CARGO_MANIFEST_DIR"), "/po"),
+ // Flatpak
+ "/app/share/locale",
+ // System-wide installation
+ "/usr/share/locale",
+ "/usr/local/share/locale",
+ ];
+
+ for dir in &locale_dirs {
+ if std::path::Path::new(dir).exists() {
+ let _ = bindtextdomain(GETTEXT_DOMAIN, *dir);
+ break;
+ }
+ }
+
+ textdomain(GETTEXT_DOMAIN).expect("Failed to set gettext domain");
+}
+
+/// Macro for translating strings at runtime.
+///
+/// Usage:
+/// ```ignore
+/// use crate::i18n::tr;
+/// let translated = tr!("Hello, world!");
+/// ```
+#[macro_export]
+macro_rules! tr {
+ ($msgid:expr) => {
+ gettextrs::gettext($msgid)
+ };
+}
diff --git a/src/main.rs b/src/main.rs
index 60be618..c15ffc2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -22,6 +22,7 @@ mod constants;
mod dialogs;
mod file_registry;
mod handlers;
+mod i18n;
mod preferences;
mod stages;
mod ui_helpers;
@@ -50,7 +51,7 @@ use relm4::{
};
use sourceview5::LanguageManager;
use sourceview5::prelude::{BufferExt, TextBufferExt, TextViewExt};
-use stages::ALL_STAGE_TYPES;
+use stages::{ALL_STAGE_TYPES, LocalizedStageType};
use wmap_parser::{Map, parse};
use wmap_renderer::{Configuration, StageType, render_to_surface};
@@ -71,6 +72,15 @@ relm4::new_stateless_action!(ZoomInAction, WindowActionGroup, "zoom-in");
relm4::new_stateless_action!(ZoomOutAction, WindowActionGroup, "zoom-out");
relm4::new_stateless_action!(PreferencesAction, WindowActionGroup, "preferences");
+struct NewFromTemplateAction;
+impl relm4::actions::ActionName for NewFromTemplateAction {
+ type Group = WindowActionGroup;
+ type Target = String;
+ type State = ();
+
+ const NAME: &'static str = "new-from-template";
+}
+
/// Macro to reduce boilerplate when registering actions with accelerators.
macro_rules! register_action {
($group:expr, $app:expr, $sender:expr, $action_type:ty, $message:expr, $accelerators:expr) => {{
@@ -168,29 +178,12 @@ fn setup_actions(window: gtk::Window, sender: ComponentSender<AppModel>) {
&["<Primary>comma"]
);
+ let sender = sender.clone();
+ let action: RelmAction<NewFromTemplateAction> = RelmAction::new_with_target_value(move |_, template_id| {
+ sender.input(Action::NewFromTemplateById(template_id))
+ });
+ action_group.add_action(action);
action_group.register_for_widget(&window);
-
- // Register new-from-template action with string parameter (template ID)
- let template_action =
- gio::SimpleAction::new("new-from-template", Some(glib::VariantTy::STRING));
- {
- let sender = sender.clone();
- template_action.connect_activate(move |_, param| {
- if let Some(variant) = param
- && let Some(template_id) = variant.get::<String>()
- {
- sender.input(Action::NewFromTemplateById(template_id));
- }
- });
- }
- window.insert_action_group(
- "win",
- Some(&{
- let group = gio::SimpleActionGroup::new();
- group.add_action(&template_action);
- group
- }),
- );
}
struct AppInit {
@@ -228,7 +221,7 @@ struct AppModel {
impl AppModel {
fn update_image(&mut self) {
if let Ok(surface) =
- render_to_surface(&self.map, &self.stage_type, &self.render_configuration)
+ render_to_surface(&self.map, &self.stage_type.to_localized(), &self.render_configuration)
{
self.surface_width = (surface.width() as f64 * self.zoom).round() as i32;
self.surface_height = (surface.height() as f64 * self.zoom).round() as i32;
@@ -250,12 +243,13 @@ impl AppModel {
}
fn window_title(&self) -> String {
+ let default_name = tr!("document.untitled");
let filename = self
.current_file
.as_ref()
.and_then(|path| path.file_name())
.and_then(|name| name.to_str())
- .unwrap_or("Untitled");
+ .unwrap_or(&default_name);
if self.modified {
format!("{}* - {}", filename, constants::APP_NAME)
@@ -279,7 +273,6 @@ impl AppModel {
}
fn apply_preferences(&mut self) {
- // Apply editor preferences
self.source_view
.set_wrap_mode(if self.preferences.soft_wrap_lines {
gtk::WrapMode::Word
@@ -287,7 +280,6 @@ impl AppModel {
gtk::WrapMode::None
});
- // Build CSS for editor font
let css = if self.preferences.use_custom_editor_font {
self.source_view.set_monospace(false);
format!(
@@ -295,7 +287,6 @@ impl AppModel {
self.preferences.custom_editor_font_name, self.preferences.editor_font_size as i32
)
} else {
- // Use monospace but still apply font size
self.source_view.set_monospace(true);
format!(
"textview {{ font-size: {}pt; }}",
@@ -303,28 +294,22 @@ impl AppModel {
)
};
- // Update the CSS provider (reusing the same one avoids accumulation)
self.editor_css_provider.load_from_data(&css);
- // Apply render configuration
self.render_configuration.options.smart_label_positioning =
self.preferences.use_smart_label_positioning;
self.render_configuration.options.show_background = self.preferences.show_map_background;
- // Apply map font
if self.preferences.use_custom_font {
self.render_configuration.theme.fonts.face = self.preferences.custom_font_name.clone();
} else {
- // Reset to default font
self.render_configuration.theme.fonts.face = String::from("sans-serif");
}
}
fn rebuild_stage_type_list(&mut self) {
- // Build list: built-in stage types + custom stages from preferences
let mut stage_types: Vec<StageType> = ALL_STAGE_TYPES.to_vec();
- // Add custom stages from preferences
for custom in &self.preferences.custom_stages {
stage_types.push(StageType::Custom {
name: custom.name.clone(),
@@ -335,15 +320,13 @@ impl AppModel {
});
}
- // Update the StringList for the dropdown
- // Clear existing items
while self.stage_type_list.n_items() > 0 {
self.stage_type_list.remove(0);
}
- // Add all stage type names
for stage_type in &stage_types {
- self.stage_type_list.append(stage_type.name());
+ self.stage_type_list
+ .append(&stage_type.localized_name());
}
self.available_stage_types = stage_types;
@@ -352,11 +335,10 @@ impl AppModel {
fn rebuild_templates_menu(&self) {
self.templates_menu.remove_all();
for template in &self.preferences.map_templates {
- let item = gio::MenuItem::new(
+ self.templates_menu.append(
Some(&template.name),
- Some(&format!("win.new-from-template::{}", template.id)),
+ Some(&format!("window.new-from-template::{}", template.id))
);
- self.templates_menu.append_item(&item);
}
}
@@ -400,7 +382,10 @@ impl SimpleComponent for AppModel {
pack_start = &gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
- gtk::Button::with_label("Change Orientation") {
+ gtk::Button::with_label(&match model.orientation {
+ gtk::Orientation::Horizontal => tr!("command.view.use_vertical_layout"),
+ _ => tr!("command.view.layout.use_horizontal_layout")
+ }) {
#[watch]
set_icon_name: match model.orientation {
gtk::Orientation::Horizontal => SPLIT_VERTICAL_REGULAR,
@@ -412,7 +397,7 @@ impl SimpleComponent for AppModel {
pack_end = &gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
- gtk::Button::with_label("Export as Image") {
+ gtk::Button::with_label(&tr!("command.file.export")) {
set_icon_name: IMAGE_REGULAR,
ActionablePlus::set_stateless_action::<ExportImageAction>: &(),
},
@@ -470,7 +455,7 @@ impl SimpleComponent for AppModel {
set_margin_end: 5
},
- gtk::Button::with_label("Zoom Out") {
+ gtk::Button::with_label(&tr!("command.view.zoom_out")) {
set_icon_name: ZOOM_OUT_REGULAR,
set_margin_end: 5,
add_css_class: "flat",
@@ -493,7 +478,7 @@ impl SimpleComponent for AppModel {
}
},
- gtk::Button::with_label("Zoom In") {
+ gtk::Button::with_label(&tr!("command.view.zoom_in")) {
set_icon_name: ZOOM_IN_REGULAR,
add_css_class: "flat",
add_css_class: "zoom",
@@ -511,11 +496,12 @@ impl SimpleComponent for AppModel {
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
- let stage_types: Vec<&str> = ALL_STAGE_TYPES
+ let stage_types: Vec<String> = ALL_STAGE_TYPES
.iter()
- .map(|stage_type| stage_type.name())
+ .map(|stage_type| stage_type.localized_name())
.collect();
- let stage_type_list = gtk::StringList::new(&stage_types);
+ let stage_type_refs: Vec<&str> = stage_types.iter().map(|s| s.as_str()).collect();
+ let stage_type_list = gtk::StringList::new(&stage_type_refs);
let source = sourceview5::Buffer::new(None);
source.set_highlight_syntax(true);
@@ -545,11 +531,9 @@ impl SimpleComponent for AppModel {
setup_actions(root.clone(), sender.clone());
- // Create source view
let source_view = sourceview5::View::with_buffer(&source);
source_view.set_monospace(true);
- // Create CSS provider for editor font styling
let editor_css_provider = gtk::CssProvider::new();
source_view.style_context().add_provider(
&editor_css_provider,
@@ -562,53 +546,48 @@ impl SimpleComponent for AppModel {
let stage_type = StageType::Activities;
render_configuration.options.smart_label_positioning = true;
let surface =
- render_to_surface(&map, &stage_type, &render_configuration).unwrap_or_else(|_| {
+ render_to_surface(&map, &stage_type.to_localized(), &render_configuration).unwrap_or_else(|_| {
ImageSurface::create(cairo::Format::ARgb32, 1, 1)
- .expect("Failed to create fallback surface")
+ .expect("errors.render.failed_to_create_surface")
});
if let Some(ref path) = current_file {
file_registry::register_file(path, &root);
}
- // Load preferences
let prefs = preferences::storage::load();
let templates_menu = gio::Menu::new();
-
- // Populate templates menu from preferences
for template in &prefs.map_templates {
- let item = gio::MenuItem::new(
+ templates_menu.append(
Some(&template.name),
- Some(&format!("win.new-from-template::{}", template.id)),
+ Some(&format!("window.new-from-template::{}", template.id))
);
- templates_menu.append_item(&item);
}
- // Build main menu
let main_menu = gio::Menu::new();
- main_menu.append(Some("New Map"), Some("window.new"));
- main_menu.append_submenu(Some("New from Template"), &templates_menu);
- main_menu.append(Some("Open..."), Some("window.open"));
- main_menu.append(Some("Save"), Some("window.save"));
- main_menu.append(Some("Save As..."), Some("window.save-as"));
- main_menu.append(Some("Close"), Some("window.close"));
- main_menu.append(Some("Export Map as Image"), Some("window.export-image"));
+ main_menu.append(Some(&tr!("command.file.new")), Some("window.new"));
+ main_menu.append_submenu(Some(&tr!("command.file.new_from_template")), &templates_menu);
+ main_menu.append(Some(&tr!("command.file.open")), Some("window.open"));
+ main_menu.append(Some(&tr!("command.file.save")), Some("window.save"));
+ main_menu.append(Some(&tr!("command.file.save_as")), Some("window.save-as"));
+ main_menu.append(Some(&tr!("command.file.close")), Some("window.close"));
+ main_menu.append(Some(&tr!("command.file.export")), Some("window.export-image"));
let layout_section = gio::Menu::new();
layout_section.append(
- Some("Use Vertical Layout"),
+ Some(&tr!("command.view.use_vertical_layout")),
Some("window.change-orientation"),
);
main_menu.append_section(None, &layout_section);
let zoom_section = gio::Menu::new();
- zoom_section.append(Some("Zoom In"), Some("window.zoom-in"));
- zoom_section.append(Some("Zoom Out"), Some("window.zoom-out"));
+ zoom_section.append(Some(&tr!("command.view.zoom_in")), Some("window.zoom-in"));
+ zoom_section.append(Some(&tr!("command.view.zoom_out")), Some("window.zoom-out"));
main_menu.append_section(None, &zoom_section);
let prefs_section = gio::Menu::new();
- prefs_section.append(Some("Preferences"), Some("window.preferences"));
+ prefs_section.append(Some(&tr!("command.application.preferences")), Some("window.preferences"));
main_menu.append_section(None, &prefs_section);
let mut model = AppModel {
@@ -637,12 +616,10 @@ impl SimpleComponent for AppModel {
main_menu,
};
- // Apply preferences and rebuild stage type list
model.apply_preferences();
model.rebuild_stage_type_list();
model.update_image();
- // Register this window's sender for broadcasting
file_registry::register_sender(sender.input_sender().clone());
{
@@ -667,11 +644,9 @@ impl SimpleComponent for AppModel {
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
match message {
- // View actions
Action::ChangeOrientation => handlers::view::change_orientation(self),
Action::StageTypeSelected(index) => handlers::view::stage_type_selected(self, index),
- // Source changes
Action::SourceChanged => {
let pending = *self.pending_load_events.borrow();
if pending > 0 {
@@ -683,12 +658,10 @@ impl SimpleComponent for AppModel {
self.update_image();
}
- // Zoom actions
Action::Zoom(level) => handlers::zoom::zoom(self, level),
Action::ZoomIn => handlers::zoom::zoom_in(self),
Action::ZoomOut => handlers::zoom::zoom_out(self),
- // Export
Action::ExportImage => {
dialogs::show_export_dialog(&self.window, self.current_file.clone(), sender)
}
@@ -696,9 +669,7 @@ impl SimpleComponent for AppModel {
handlers::export::export_to_file(self, path, format)
}
- // File actions
Action::New => {
- // Use default template if one exists
let content = self
.preferences
.map_templates
@@ -731,10 +702,8 @@ impl SimpleComponent for AppModel {
}
Action::CloseWindow => handlers::file::close_window(self, &sender),
- // Internal actions
Action::MarkInitialized => self.initialized = true,
- // Preferences
Action::ShowPreferences => {
if self.preferences_window.is_none() {
let controller = preferences::PreferencesWindow::builder()
@@ -760,14 +729,12 @@ impl SimpleComponent for AppModel {
self.rebuild_stage_type_list();
self.rebuild_templates_menu();
self.update_image();
- // Broadcast to all other windows to reload preferences
file_registry::broadcast_to_all_windows(Action::ReloadPreferences);
}
Action::PreferencesWindowClosed => {
self.preferences_window = None;
}
Action::ReloadPreferences => {
- // Reload preferences from disk (another window changed them)
self.preferences = preferences::storage::load();
self.apply_preferences();
self.rebuild_stage_type_list();
@@ -779,9 +746,10 @@ impl SimpleComponent for AppModel {
}
fn main() {
+ i18n::init();
+
relm4_icons::initialize_icons(icon_names::GRESOURCE_BYTES, icon_names::RESOURCE_PREFIX);
- // Load preferences to get default template for initial window
let prefs = preferences::storage::load();
let initial_content = prefs
.map_templates
diff --git a/src/preferences/pages/editor.rs b/src/preferences/pages/editor.rs
index 9f10d43..d4acb94 100644
--- a/src/preferences/pages/editor.rs
+++ b/src/preferences/pages/editor.rs
@@ -18,6 +18,7 @@ use gtk::prelude::*;
use relm4::Sender;
use relm4::gtk;
+use crate::tr;
use crate::ui_helpers::create_switch_row;
use super::super::models::UserPreferences;
@@ -41,7 +42,7 @@ impl EditorPage {
container.set_valign(gtk::Align::Start);
// Font Size section
- let font_size_label = gtk::Label::new(Some("Font Size"));
+ let font_size_label = gtk::Label::new(Some(&tr!("preferences.editor.font_size.title")));
font_size_label.add_css_class("heading");
font_size_label.set_halign(gtk::Align::Start);
container.append(&font_size_label);
@@ -56,18 +57,18 @@ impl EditorPage {
});
}
font_size_box.append(&font_size_spin);
- font_size_box.append(&gtk::Label::new(Some("pt")));
+ font_size_box.append(&gtk::Label::new(Some(&tr!("preferences.editor.font_size.unit"))));
container.append(&font_size_box);
// Editor Style section
- let style_label = gtk::Label::new(Some("Editor Style"));
+ let style_label = gtk::Label::new(Some(&tr!("preferences.editor.editor_style.title")));
style_label.add_css_class("heading");
style_label.set_halign(gtk::Align::Start);
style_label.set_margin_top(10);
container.append(&style_label);
// Soft Wrap Lines
- let soft_wrap_row = create_switch_row("Soft Wrap Lines", preferences.soft_wrap_lines);
+ let soft_wrap_row = create_switch_row(&tr!("preferences.editor.editor_style.soft_wrap_lines"), preferences.soft_wrap_lines);
let soft_wrap_switch = soft_wrap_row.1.clone();
{
let sender = sender.clone();
@@ -80,13 +81,13 @@ impl EditorPage {
// Use Custom Font
let custom_font_row =
- create_switch_row("Use Custom Editor Font", preferences.use_custom_editor_font);
+ create_switch_row(&tr!("preferences.editor.editor_style.use_custom_font"), preferences.use_custom_editor_font);
let custom_font_switch = custom_font_row.1.clone();
container.append(&custom_font_row.0);
// Font selection
let font_row = gtk::Box::new(gtk::Orientation::Horizontal, 10);
- font_row.append(&gtk::Label::new(Some("Font")));
+ font_row.append(&gtk::Label::new(Some(&tr!("preferences.editor.font"))));
let font_button = gtk::FontButton::new();
font_button.set_font(&format!(
diff --git a/src/preferences/pages/general.rs b/src/preferences/pages/general.rs
index 43cd784..d70e9e6 100644
--- a/src/preferences/pages/general.rs
+++ b/src/preferences/pages/general.rs
@@ -19,6 +19,8 @@ use gtk::prelude::*;
use relm4::Sender;
use relm4::gtk;
+use crate::tr;
+
use super::super::window::PreferencesInput;
pub struct GeneralPage {
@@ -35,7 +37,7 @@ impl GeneralPage {
container.set_valign(gtk::Align::Start);
// Preferences section
- let prefs_label = gtk::Label::new(Some("Preferences"));
+ let prefs_label = gtk::Label::new(Some(&tr!("preferences.general.preferences.title")));
prefs_label.add_css_class("heading");
prefs_label.set_halign(gtk::Align::Start);
container.append(&prefs_label);
@@ -43,7 +45,7 @@ impl GeneralPage {
let button_box = gtk::Box::new(gtk::Orientation::Horizontal, 10);
// Export button
- let export_button = gtk::Button::with_label("Export...");
+ let export_button = gtk::Button::with_label(&tr!("preferences.general.preferences.export"));
{
let sender = sender.clone();
export_button.connect_clicked(move |_| {
@@ -53,7 +55,7 @@ impl GeneralPage {
button_box.append(&export_button);
// Import button
- let import_button = gtk::Button::with_label("Import...");
+ let import_button = gtk::Button::with_label(&tr!("preferences.general.preferences.import"));
{
let sender = sender.clone();
import_button.connect_clicked(move |_| {
@@ -63,7 +65,7 @@ impl GeneralPage {
button_box.append(&import_button);
// Reset button
- let reset_button = gtk::Button::with_label("Reset to Defaults");
+ let reset_button = gtk::Button::with_label(&tr!("preferences.general.preferences.reset"));
reset_button.add_css_class("destructive-action");
{
let sender = sender.clone();
@@ -76,9 +78,7 @@ impl GeneralPage {
container.append(&button_box);
// Help text
- let help_label = gtk::Label::new(Some(
- "Export your preferences to share with others or back up your settings.\nImport preferences from a file to restore settings.",
- ));
+ let help_label = gtk::Label::new(Some(&tr!("preferences.general.explanation")));
help_label.add_css_class("dim-label");
help_label.set_halign(gtk::Align::Start);
help_label.set_wrap(true);
@@ -95,15 +95,15 @@ impl GeneralPage {
pub fn show_export_dialog(sender: &Sender<PreferencesInput>, window: &gtk::Window) {
let filter = FileFilter::new();
filter.add_pattern("*.json");
- filter.set_name(Some("JSON files"));
+ filter.set_name(Some(&tr!("dialog.file_type.json")));
let dialog = gtk::FileChooserDialog::new(
- Some("Export Preferences"),
+ Some(&tr!("dialog.export_preferences.title")),
Some(window),
gtk::FileChooserAction::Save,
&[
- ("Cancel", gtk::ResponseType::Cancel),
- ("Save", gtk::ResponseType::Accept),
+ (&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel),
+ (&tr!("dialog.action.save"), gtk::ResponseType::Accept),
],
);
dialog.set_current_name("preferences.json");
@@ -127,15 +127,15 @@ pub fn show_export_dialog(sender: &Sender<PreferencesInput>, window: &gtk::Windo
pub fn show_import_dialog(sender: &Sender<PreferencesInput>, window: &gtk::Window) {
let filter = FileFilter::new();
filter.add_pattern("*.json");
- filter.set_name(Some("JSON files"));
+ filter.set_name(Some(&tr!("dialog.file_type.json")));
let dialog = gtk::FileChooserDialog::new(
- Some("Import Preferences"),
+ Some(&tr!("dialog.import_preferences.title")),
Some(window),
gtk::FileChooserAction::Open,
&[
- ("Cancel", gtk::ResponseType::Cancel),
- ("Open", gtk::ResponseType::Accept),
+ (&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel),
+ (&tr!("dialog.action.open"), gtk::ResponseType::Accept),
],
);
dialog.add_filter(&filter);
diff --git a/src/preferences/pages/map.rs b/src/preferences/pages/map.rs
index 8deae20..5ffd04a 100644
--- a/src/preferences/pages/map.rs
+++ b/src/preferences/pages/map.rs
@@ -18,6 +18,7 @@ use gtk::prelude::*;
use relm4::Sender;
use relm4::gtk;
+use crate::tr;
use crate::ui_helpers::create_switch_row;
use super::super::models::UserPreferences;
@@ -42,14 +43,14 @@ impl MapPage {
container.set_valign(gtk::Align::Start);
// Map Style section
- let style_label = gtk::Label::new(Some("Map Style"));
+ let style_label = gtk::Label::new(Some(&tr!("preferences.map.map_style.title")));
style_label.add_css_class("heading");
style_label.set_halign(gtk::Align::Start);
container.append(&style_label);
// Show Background
let background_row =
- create_switch_row("Show Map Background", preferences.show_map_background);
+ create_switch_row(&tr!("preferences.map.map_style.show_background"), preferences.show_map_background);
let show_background_switch = background_row.1.clone();
{
let sender = sender.clone();
@@ -62,7 +63,7 @@ impl MapPage {
// Smart Label Positioning
let smart_row = create_switch_row(
- "Use Smart Label Positioning",
+ &tr!("preferences.map.map_style.use_smart_label_positioning"),
preferences.use_smart_label_positioning,
);
let smart_positioning_switch = smart_row.1.clone();
@@ -76,13 +77,13 @@ impl MapPage {
container.append(&smart_row.0);
// Use Custom Font
- let custom_font_row = create_switch_row("Use Custom Font", preferences.use_custom_font);
+ let custom_font_row = create_switch_row(&tr!("preferences.map.map_style.use_custom_font"), preferences.use_custom_font);
let custom_font_switch = custom_font_row.1.clone();
container.append(&custom_font_row.0);
// Font selection
let font_row = gtk::Box::new(gtk::Orientation::Horizontal, 10);
- font_row.append(&gtk::Label::new(Some("Font")));
+ font_row.append(&gtk::Label::new(Some(&tr!("preferences.map.font"))));
let font_button = gtk::FontButton::new();
font_button.set_font(&preferences.custom_font_name);
@@ -115,16 +116,16 @@ impl MapPage {
}
// Export section
- let export_label = gtk::Label::new(Some("Export"));
+ let export_label = gtk::Label::new(Some(&tr!("preferences.map.export.title")));
export_label.add_css_class("heading");
export_label.set_halign(gtk::Align::Start);
export_label.set_margin_top(10);
container.append(&export_label);
let export_row = gtk::Box::new(gtk::Orientation::Horizontal, 10);
- export_row.append(&gtk::Label::new(Some("Default Export Format")));
+ export_row.append(&gtk::Label::new(Some(&tr!("preferences.map.export.default_format"))));
- let formats = gtk::StringList::new(&["PNG", "SVG"]);
+ let formats = gtk::StringList::new(&[&tr!("export_formats.png"), &tr!("export_formats.svg")]);
let export_format_dropdown = gtk::DropDown::new(Some(formats), None::<gtk::Expression>);
let selected = match preferences.default_export_format.as_str() {
"svg" => 1,
diff --git a/src/preferences/pages/stages.rs b/src/preferences/pages/stages.rs
index 6f4121e..9f0685d 100644
--- a/src/preferences/pages/stages.rs
+++ b/src/preferences/pages/stages.rs
@@ -19,6 +19,8 @@ use relm4::Sender;
use relm4::gtk;
use uuid::Uuid;
+use crate::tr;
+
use super::super::models::UserPreferences;
use super::super::window::{PreferencesInput, StageLabel};
@@ -37,14 +39,12 @@ impl StagesPage {
container.set_margin_end(20);
// Header
- let header_label = gtk::Label::new(Some("Custom Stages"));
+ let header_label = gtk::Label::new(Some(&tr!("preferences.stages.title")));
header_label.add_css_class("heading");
header_label.set_halign(gtk::Align::Start);
container.append(&header_label);
- let help_label = gtk::Label::new(Some(
- "Custom stages allow you to define your own evolution axis labels. These will be available for rendering when the renderer supports custom stages.",
- ));
+ let help_label = gtk::Label::new(Some(&tr!("preferences.stages.explanation")));
help_label.add_css_class("dim-label");
help_label.set_halign(gtk::Align::Start);
help_label.set_wrap(true);
@@ -55,27 +55,27 @@ impl StagesPage {
let header_row = gtk::Box::new(gtk::Orientation::Horizontal, 5);
header_row.add_css_class("dim-label");
- let name_header = gtk::Label::new(Some("Name"));
+ let name_header = gtk::Label::new(Some(&tr!("preferences.stages.column.name")));
name_header.set_width_chars(15);
name_header.set_xalign(0.0);
header_row.append(&name_header);
- let i_header = gtk::Label::new(Some("Stage I"));
+ let i_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_i")));
i_header.set_width_chars(12);
i_header.set_xalign(0.0);
header_row.append(&i_header);
- let ii_header = gtk::Label::new(Some("Stage II"));
+ let ii_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_ii")));
ii_header.set_width_chars(12);
ii_header.set_xalign(0.0);
header_row.append(&ii_header);
- let iii_header = gtk::Label::new(Some("Stage III"));
+ let iii_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_iii")));
iii_header.set_width_chars(12);
iii_header.set_xalign(0.0);
header_row.append(&iii_header);
- let iv_header = gtk::Label::new(Some("Stage IV"));
+ let iv_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_iv")));
iv_header.set_width_chars(12);
iv_header.set_xalign(0.0);
header_row.append(&iv_header);
@@ -104,7 +104,7 @@ impl StagesPage {
button_box.set_halign(gtk::Align::End);
let add_button = gtk::Button::from_icon_name("list-add-symbolic");
- add_button.set_tooltip_text(Some("Add custom stage"));
+ add_button.set_tooltip_text(Some(&tr!("preferences.stages.help.add")));
{
let sender = sender.clone();
add_button.connect_clicked(move |_| {
@@ -238,7 +238,7 @@ impl StagesPage {
// Delete button
let delete_button = gtk::Button::from_icon_name("list-remove-symbolic");
delete_button.add_css_class("flat");
- delete_button.set_tooltip_text(Some("Remove stage"));
+ delete_button.set_tooltip_text(Some(&tr!("preferences.stages.help.remove")));
{
let sender = self.sender.clone();
delete_button.connect_clicked(move |_| {
diff --git a/src/preferences/pages/templates.rs b/src/preferences/pages/templates.rs
index da3cafc..acc679f 100644
--- a/src/preferences/pages/templates.rs
+++ b/src/preferences/pages/templates.rs
@@ -22,6 +22,8 @@ use relm4::gtk;
use sourceview5::prelude::*;
use uuid::Uuid;
+use crate::tr;
+
use super::super::models::{Template, UserPreferences};
use super::super::window::PreferencesInput;
@@ -49,7 +51,7 @@ impl TemplatesPage {
let sidebar = gtk::Box::new(gtk::Orientation::Vertical, 5);
sidebar.set_width_request(200);
- let sidebar_label = gtk::Label::new(Some("Templates"));
+ let sidebar_label = gtk::Label::new(Some(&tr!("preferences.templates.title")));
sidebar_label.add_css_class("heading");
sidebar_label.set_halign(gtk::Align::Start);
sidebar_label.set_margin_start(5);
@@ -72,18 +74,19 @@ impl TemplatesPage {
button_box.set_margin_top(5);
let add_button = gtk::Button::from_icon_name("list-add-symbolic");
- add_button.set_tooltip_text(Some("Add template"));
+ add_button.set_tooltip_text(Some(&tr!("preferences.templates.help.add")));
{
let sender = sender.clone();
add_button.connect_clicked(move |_| {
- // Show a simple dialog to get the template name
- show_add_template_dialog(sender.clone());
+ sender.emit(PreferencesInput::AddTemplate(
+ tr!("dialog.template.default_label"),
+ ));
});
}
button_box.append(&add_button);
let remove_button = gtk::Button::from_icon_name("list-remove-symbolic");
- remove_button.set_tooltip_text(Some("Remove template"));
+ remove_button.set_tooltip_text(Some(&tr!("preferences.templates.help.remove")));
{
let sender = sender.clone();
let selected_id = selected_id.clone();
@@ -108,7 +111,7 @@ impl TemplatesPage {
let editor_box = gtk::Box::new(gtk::Orientation::Vertical, 5);
editor_box.set_hexpand(true);
- let editor_label = gtk::Label::new(Some("Template Content"));
+ let editor_label = gtk::Label::new(Some(&tr!("preferences.templates.content")));
editor_label.add_css_class("heading");
editor_label.set_halign(gtk::Align::Start);
editor_box.append(&editor_label);
@@ -245,10 +248,10 @@ impl TemplatesPage {
let default_button = gtk::Button::new();
if template.is_default {
default_button.set_icon_name("emblem-default-symbolic");
- default_button.set_tooltip_text(Some("Default template"));
+ default_button.set_tooltip_text(Some(&tr!("preferences.templates.help.default")));
} else {
default_button.set_icon_name("radio-symbolic");
- default_button.set_tooltip_text(Some("Set as default"));
+ default_button.set_tooltip_text(Some(&tr!("preferences.templates.help.set_as_default")));
}
default_button.add_css_class("flat");
{
@@ -260,54 +263,20 @@ impl TemplatesPage {
}
hbox.append(&default_button);
- // Template name
- let name_label = gtk::Label::new(Some(&template.name));
- name_label.set_hexpand(true);
- name_label.set_xalign(0.0);
- hbox.append(&name_label);
+ // Template name (editable)
+ let name_entry = gtk::Entry::new();
+ name_entry.set_text(&template.name);
+ name_entry.set_hexpand(true);
+ {
+ let sender = self.sender.clone();
+ let id = template.id;
+ name_entry.connect_changed(move |entry| {
+ sender.emit(PreferencesInput::SetTemplateName(id, entry.text().to_string()));
+ });
+ }
+ hbox.append(&name_entry);
row.set_child(Some(&hbox));
row
}
}
-
-fn show_add_template_dialog(sender: Sender<PreferencesInput>) {
- let dialog = gtk::Dialog::with_buttons(
- Some("New Template"),
- None::<&gtk::Window>,
- gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT,
- &[
- ("Cancel", gtk::ResponseType::Cancel),
- ("Add", gtk::ResponseType::Accept),
- ],
- );
- dialog.set_default_response(gtk::ResponseType::Accept);
-
- let content = dialog.content_area();
- content.set_margin_top(10);
- content.set_margin_bottom(10);
- content.set_margin_start(10);
- content.set_margin_end(10);
- content.set_spacing(10);
-
- let label = gtk::Label::new(Some("Template name:"));
- label.set_halign(gtk::Align::Start);
- content.append(&label);
-
- let entry = gtk::Entry::new();
- entry.set_placeholder_text(Some("My Template"));
- entry.set_activates_default(true);
- content.append(&entry);
-
- dialog.connect_response(move |dialog, response| {
- if response == gtk::ResponseType::Accept {
- let name = entry.text().to_string();
- if !name.is_empty() {
- sender.emit(PreferencesInput::AddTemplate(name));
- }
- }
- dialog.close();
- });
-
- dialog.present();
-}
diff --git a/src/preferences/window.rs b/src/preferences/window.rs
index 8b38c29..2118056 100644
--- a/src/preferences/window.rs
+++ b/src/preferences/window.rs
@@ -22,6 +22,8 @@ use std::path::PathBuf;
use gtk::prelude::*;
use relm4::{ComponentParts, ComponentSender, SimpleComponent, gtk};
+use crate::tr;
+
use super::models::UserPreferences;
use super::pages::{editor, general, map, stages, templates};
use super::storage;
@@ -52,7 +54,6 @@ pub enum PreferencesInput {
AddTemplate(String),
RemoveTemplate(uuid::Uuid),
SetTemplateContent(uuid::Uuid, String),
- #[allow(dead_code)]
SetTemplateName(uuid::Uuid, String),
SetDefaultTemplate(uuid::Uuid),
@@ -101,7 +102,7 @@ impl SimpleComponent for PreferencesWindow {
view! {
gtk::Window {
- set_title: Some("Preferences"),
+ set_title: Some(&tr!("preferences.title")),
set_default_width: 700,
set_default_height: 500,
set_modal: true,
@@ -127,11 +128,11 @@ impl SimpleComponent for PreferencesWindow {
set_transition_type: gtk::StackTransitionType::SlideLeftRight,
set_vexpand: true,
- add_titled[Some("general"), "General"] = &model.general_page.widget() -> gtk::Box {},
- add_titled[Some("editor"), "Editor"] = &model.editor_page.widget() -> gtk::Box {},
- add_titled[Some("map"), "Map"] = &model.map_page.widget() -> gtk::Box {},
- add_titled[Some("stages"), "Stages"] = &model.stages_page.widget() -> gtk::Box {},
- add_titled[Some("templates"), "Templates"] = &model.templates_page.widget() -> gtk::Box {},
+ add_titled[Some("general"), &tr!("preferences.menu.general")] = &model.general_page.widget() -> gtk::Box {},
+ add_titled[Some("editor"), &tr!("preferences.menu.editor")] = &model.editor_page.widget() -> gtk::Box {},
+ add_titled[Some("map"), &tr!("preferences.menu.map")] = &model.map_page.widget() -> gtk::Box {},
+ add_titled[Some("stages"), &tr!("preferences.menu.stages")] = &model.stages_page.widget() -> gtk::Box {},
+ add_titled[Some("templates"), &tr!("preferences.menu.templates")] = &model.templates_page.widget() -> gtk::Box {},
},
},
}
diff --git a/src/stages.rs b/src/stages.rs
index fba86b3..8de8f89 100644
--- a/src/stages.rs
+++ b/src/stages.rs
@@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use wmap_renderer::StageType;
+use crate::tr;
/// All available stage types for rendering Wardley Maps.
/// Order determines dropdown menu order.
@@ -40,3 +41,161 @@ pub const ALL_STAGE_TYPES: &[StageType] = &[
StageType::DecisionDrivers,
StageType::Behavior,
];
+
+pub trait LocalizedStageType {
+ fn localized_name(&self) -> String;
+ fn localized_i(&self) -> String;
+ fn localized_ii(&self) -> String;
+ fn localized_iii(&self) -> String;
+ fn localized_iv(&self) -> String;
+
+ fn to_localized(&self) -> StageType;
+}
+
+impl LocalizedStageType for StageType {
+ fn localized_name(&self) -> String {
+ match self {
+ StageType::Activities => tr!("stage.activities.name"),
+ StageType::Practice => tr!("stage.practice.name"),
+ StageType::Data => tr!("stage.data.name"),
+ StageType::Knowledge => tr!("stages.knowledge.name"),
+ StageType::Ubiquity => tr!("stages.ubiquity.name"),
+ StageType::Certainty => tr!("stages.certainty.name"),
+ StageType::PublicationTypes => tr!("stages.publication_types.name"),
+ StageType::Market => tr!("stages.market.name"),
+ StageType::KnowledgeManagement => tr!("stages.knowledge_management.name"),
+ StageType::MarketPerception => tr!("stages.market_perception.name"),
+ StageType::UserPerception => tr!("stages.user_perception.name"),
+ StageType::PerceptionInIndustry => tr!("stages.perception_in_industry.name"),
+ StageType::FocusOfValue => tr!("stages.focus_of_value.name"),
+ StageType::Understanding => tr!("stages.understanding.name"),
+ StageType::Comparison => tr!("stages.comparison.name"),
+ StageType::Failure => tr!("stages.failure.name"),
+ StageType::MarketAction => tr!("stages.market_action.name"),
+ StageType::Efficiency => tr!("stages.efficiency.name"),
+ StageType::DecisionDrivers => tr!("stages.decision_drivers.name"),
+ StageType::Behavior => tr!("stages.behavior.name"),
+ StageType::Cynefin => tr!("stages.cynefin.name"),
+ StageType::EvolutionStage => tr!("stages.evolution_stage.name"),
+ StageType::Custom{name, i: _, ii: _, iii: _, iv: _} => name.to_string()
+ }
+ }
+ fn localized_i(&self) -> String {
+ match self {
+ StageType::Activities => tr!("stage.activities.i"),
+ StageType::Practice => tr!("stage.practice.i"),
+ StageType::Data => tr!("stage.data.i"),
+ StageType::Knowledge => tr!("stages.knowledge.i"),
+ StageType::Ubiquity => tr!("stages.ubiquity.i"),
+ StageType::Certainty => tr!("stages.certainty.i"),
+ StageType::PublicationTypes => tr!("stages.publication_types.i"),
+ StageType::Market => tr!("stages.market.i"),
+ StageType::KnowledgeManagement => tr!("stages.knowledge_management.i"),
+ StageType::MarketPerception => tr!("stages.market_perception.i"),
+ StageType::UserPerception => tr!("stages.user_perception.i"),
+ StageType::PerceptionInIndustry => tr!("stages.perception_in_industry.i"),
+ StageType::FocusOfValue => tr!("stages.focus_of_value.i"),
+ StageType::Understanding => tr!("stages.understanding.i"),
+ StageType::Comparison => tr!("stages.comparison.i"),
+ StageType::Failure => tr!("stages.failure.i"),
+ StageType::MarketAction => tr!("stages.market_action.i"),
+ StageType::Efficiency => tr!("stages.efficiency.i"),
+ StageType::DecisionDrivers => tr!("stages.decision_drivers.i"),
+ StageType::Behavior => tr!("stages.behavior.i"),
+ StageType::Cynefin => tr!("stages.cynefin.i"),
+ StageType::EvolutionStage => tr!("stages.evolution_stage.i"),
+ StageType::Custom{name: _, i, ii: _, iii: _, iv: _} => i.to_string()
+ }
+ }
+ fn localized_ii(&self) -> String {
+ match self {
+ StageType::Activities => tr!("stage.activities.ii"),
+ StageType::Practice => tr!("stage.practice.ii"),
+ StageType::Data => tr!("stage.data.ii"),
+ StageType::Knowledge => tr!("stages.knowledge.ii"),
+ StageType::Ubiquity => tr!("stages.ubiquity.ii"),
+ StageType::Certainty => tr!("stages.certainty.ii"),
+ StageType::PublicationTypes => tr!("stages.publication_types.ii"),
+ StageType::Market => tr!("stages.market.ii"),
+ StageType::KnowledgeManagement => tr!("stages.knowledge_management.ii"),
+ StageType::MarketPerception => tr!("stages.market_perception.ii"),
+ StageType::UserPerception => tr!("stages.user_perception.ii"),
+ StageType::PerceptionInIndustry => tr!("stages.perception_in_industry.ii"),
+ StageType::FocusOfValue => tr!("stages.focus_of_value.ii"),
+ StageType::Understanding => tr!("stages.understanding.ii"),
+ StageType::Comparison => tr!("stages.comparison.ii"),
+ StageType::Failure => tr!("stages.failure.ii"),
+ StageType::MarketAction => tr!("stages.market_action.ii"),
+ StageType::Efficiency => tr!("stages.efficiency.ii"),
+ StageType::DecisionDrivers => tr!("stages.decision_drivers.ii"),
+ StageType::Behavior => tr!("stages.behavior.ii"),
+ StageType::Cynefin => tr!("stages.cynefin.ii"),
+ StageType::EvolutionStage => tr!("stages.evolution_stage.ii"),
+ StageType::Custom{name: _, i: _, ii, iii: _, iv: _} => ii.to_string()
+ }
+ }
+ fn localized_iii(&self) -> String {
+ match self {
+ StageType::Activities => tr!("stage.activities.iii"),
+ StageType::Practice => tr!("stage.practice.iii"),
+ StageType::Data => tr!("stage.data.iii"),
+ StageType::Knowledge => tr!("stages.knowledge.iii"),
+ StageType::Ubiquity => tr!("stages.ubiquity.iii"),
+ StageType::Certainty => tr!("stages.certainty.iii"),
+ StageType::PublicationTypes => tr!("stages.publication_types.iii"),
+ StageType::Market => tr!("stages.market.iii"),
+ StageType::KnowledgeManagement => tr!("stages.knowledge_management.iii"),
+ StageType::MarketPerception => tr!("stages.market_perception.iii"),
+ StageType::UserPerception => tr!("stages.user_perception.iii"),
+ StageType::PerceptionInIndustry => tr!("stages.perception_in_industry.iii"),
+ StageType::FocusOfValue => tr!("stages.focus_of_value.iii"),
+ StageType::Understanding => tr!("stages.understanding.iii"),
+ StageType::Comparison => tr!("stages.comparison.iii"),
+ StageType::Failure => tr!("stages.failure.iii"),
+ StageType::MarketAction => tr!("stages.market_action.iii"),
+ StageType::Efficiency => tr!("stages.efficiency.iii"),
+ StageType::DecisionDrivers => tr!("stages.decision_drivers.iii"),
+ StageType::Behavior => tr!("stages.behavior.iii"),
+ StageType::Cynefin => tr!("stages.cynefin.iii"),
+ StageType::EvolutionStage => tr!("stages.evolution_stage.iii"),
+ StageType::Custom{name: _, i: _, ii: _, iii, iv: _} => iii.to_string()
+ }
+ }
+ fn localized_iv(&self) -> String {
+ match self {
+ StageType::Activities => tr!("stage.activities.iv"),
+ StageType::Practice => tr!("stage.practice.iv"),
+ StageType::Data => tr!("stage.data.iv"),
+ StageType::Knowledge => tr!("stages.knowledge.iv"),
+ StageType::Ubiquity => tr!("stages.ubiquity.iv"),
+ StageType::Certainty => tr!("stages.certainty.iv"),
+ StageType::PublicationTypes => tr!("stages.publication_types.iv"),
+ StageType::Market => tr!("stages.market.iv"),
+ StageType::KnowledgeManagement => tr!("stages.knowledge_management.iv"),
+ StageType::MarketPerception => tr!("stages.market_perception.iv"),
+ StageType::UserPerception => tr!("stages.user_perception.iv"),
+ StageType::PerceptionInIndustry => tr!("stages.perception_in_industry.iv"),
+ StageType::FocusOfValue => tr!("stages.focus_of_value.iv"),
+ StageType::Understanding => tr!("stages.understanding.iv"),
+ StageType::Comparison => tr!("stages.comparison.iv"),
+ StageType::Failure => tr!("stages.failure.iv"),
+ StageType::MarketAction => tr!("stages.market_action.iv"),
+ StageType::Efficiency => tr!("stages.efficiency.iv"),
+ StageType::DecisionDrivers => tr!("stages.decision_drivers.iv"),
+ StageType::Behavior => tr!("stages.behavior.iv"),
+ StageType::Cynefin => tr!("stages.cynefin.iv"),
+ StageType::EvolutionStage => tr!("stages.evolution_stage.iv"),
+ StageType::Custom{name: _, i: _, ii: _, iii: _, iv} => iv.to_string()
+ }
+ }
+
+ fn to_localized(&self) -> StageType {
+ StageType::Custom{
+ name: self.localized_name(),
+ i: self.localized_i(),
+ ii: self.localized_ii(),
+ iii: self.localized_iii(),
+ iv: self.localized_iv()
+ }
+ }
+}