diff options
| -rw-r--r-- | Cargo.lock | 172 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | Makefile | 19 | ||||
| -rw-r--r-- | README.md | 9 | ||||
| -rw-r--r-- | po/POTFILES | 12 | ||||
| -rw-r--r-- | po/en_US.mo | bin | 0 -> 13775 bytes | |||
| -rw-r--r-- | po/en_US.po | 788 | ||||
| -rw-r--r-- | po/en_US/LC_MESSAGES/systems.tranquil.Map.mo | bin | 0 -> 13775 bytes | |||
| -rw-r--r-- | po/systems.tranquil.Map.pot | 767 | ||||
| -rw-r--r-- | resources/wmap.lang | 8 | ||||
| -rw-r--r-- | src/dialogs.rs | 42 | ||||
| -rw-r--r-- | src/handlers/export.rs | 8 | ||||
| -rw-r--r-- | src/handlers/file.rs | 5 | ||||
| -rw-r--r-- | src/i18n.rs | 61 | ||||
| -rw-r--r-- | src/main.rs | 134 | ||||
| -rw-r--r-- | src/preferences/pages/editor.rs | 13 | ||||
| -rw-r--r-- | src/preferences/pages/general.rs | 30 | ||||
| -rw-r--r-- | src/preferences/pages/map.rs | 17 | ||||
| -rw-r--r-- | src/preferences/pages/stages.rs | 22 | ||||
| -rw-r--r-- | src/preferences/pages/templates.rs | 77 | ||||
| -rw-r--r-- | src/preferences/window.rs | 15 | ||||
| -rw-r--r-- | src/stages.rs | 159 |
22 files changed, 2145 insertions, 214 deletions
@@ -9,6 +9,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -36,6 +45,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -71,6 +86,16 @@ dependencies = [ ] [[package]] +name = "cc" +version = "1.2.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] name = "cfg-expr" version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -196,6 +221,12 @@ dependencies = [ ] [[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] name = "flate2" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -449,6 +480,26 @@ dependencies = [ ] [[package]] +name = "gettext-rs" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5857dc1b7f0fee86961de833f434e29494d72af102ce5355738c0664222bdf" +dependencies = [ + "gettext-sys", + "locale_config", +] + +[[package]] +name = "gettext-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea859ab0dd7e70ff823032b3e077d03d39c965d68c6c10775add60e999d8ee9" +dependencies = [ + "cc", + "temp-dir", +] + +[[package]] name = "gio" version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -775,6 +826,12 @@ dependencies = [ ] [[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] name = "libc" version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -803,6 +860,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] +name = "locale_config" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934" +dependencies = [ + "lazy_static", + "objc", + "objc-foundation", + "regex", + "winapi", +] + +[[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -818,11 +888,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] name = "map" version = "1.0.0" dependencies = [ "cairo-rs", "dirs", + "gettext-rs", "relm4", "relm4-components", "relm4-icons", @@ -881,6 +961,35 @@ dependencies = [ ] [[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1048,6 +1157,35 @@ dependencies = [ ] [[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] name = "relm4" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1238,6 +1376,12 @@ dependencies = [ ] [[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] name = "sif-itree" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1354,6 +1498,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] +name = "temp-dir" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964" + +[[package]] name = "tempfile" version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1612,6 +1762,22 @@ dependencies = [ ] [[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1621,6 +1787,12 @@ dependencies = [ ] [[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -6,6 +6,7 @@ edition = "2024" [dependencies] cairo-rs = "0.21.5" dirs = "5.0" +gettext-rs = "0.7" relm4 = "0.10.0" relm4-components = "0.10.0" relm4-icons = "0.10.0" @@ -42,6 +42,23 @@ format: lint: cargo fmt -- --check && cargo clippy +# Internationalization / Localization ########################################## + +find_translatable_files: + @find ./ -not -path '*/.*' -type f -name "*.rs" -exec grep -l "tr!(\"\|gettext" {} \; | sort > po/POTFILES + +extract_strings: find_translatable_files + @xgettext --files-from=po/POTFILES --output=po/systems.tranquil.Map.pot --from-code=UTF-8 --add-comments --keyword='tr!' + +po_files := $(wildcard po/*.po) +mo_files := $(patsubst po/%.po,po/%/LC_MESSAGES/systems.tranquil.Map.mo,$(po_files)) + +compile_translations: $(mo_files) + +po/%/LC_MESSAGES/systems.tranquil.Map.mo: po/%.po + @mkdir -p $(dir $@) + msgfmt $< -o $@ + # Packaging #################################################################### package: $(architectures) @@ -95,4 +112,4 @@ else endif -.PHONY: default build $(architectures) rpm package prepare set_rust ci release test coverage format flatpak +.PHONY: default build $(architectures) rpm package prepare set_rust ci release test coverage format flatpak extract_strings find_translatable_files compile_translations @@ -26,6 +26,15 @@ flatpak. Since flatpak expects offline building, we use `flatpak-cargo-generator` to generate the sources. +## Localization / Internationalization + +This project uses gettext for translations. You can update the template files +with: + +``` +make extract_strings +``` + ## The wmap language Learn more about the language at [map.tranquil.systems][map]. diff --git a/po/POTFILES b/po/POTFILES new file mode 100644 index 0000000..7673083 --- /dev/null +++ b/po/POTFILES @@ -0,0 +1,12 @@ +./src/dialogs.rs +./src/handlers/export.rs +./src/handlers/file.rs +./src/i18n.rs +./src/main.rs +./src/preferences/pages/editor.rs +./src/preferences/pages/general.rs +./src/preferences/pages/map.rs +./src/preferences/pages/stages.rs +./src/preferences/pages/templates.rs +./src/preferences/window.rs +./src/stages.rs diff --git a/po/en_US.mo b/po/en_US.mo Binary files differnew file mode 100644 index 0000000..9daf8c8 --- /dev/null +++ b/po/en_US.mo diff --git a/po/en_US.po b/po/en_US.po new file mode 100644 index 0000000..daa9615 --- /dev/null +++ b/po/en_US.po @@ -0,0 +1,788 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-01-17 02:37+0100\n" +"PO-Revision-Date: 2026-01-17 02:37+0100\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.8\n" + +#: src/dialogs.rs:35 +msgid "dialog.file_type.wmap" +msgstr "Wardley Map Files" + +#: src/dialogs.rs:62 +msgid "dialog.open.title" +msgstr "Open Map" + +#: src/dialogs.rs:66 src/dialogs.rs:106 src/dialogs.rs:152 src/dialogs.rs:202 +#: src/preferences/pages/general.rs:105 src/preferences/pages/general.rs:137 +msgid "dialog.action.cancel" +msgstr "Cancel" + +#: src/dialogs.rs:67 src/preferences/pages/general.rs:138 +msgid "dialog.action.open" +msgstr "Open" + +#: src/dialogs.rs:102 +msgid "dialog.save.title" +msgstr "Save Map As" + +#: src/dialogs.rs:107 src/dialogs.rs:153 src/preferences/pages/general.rs:106 +msgid "dialog.action.save" +msgstr "Save" + +#: src/dialogs.rs:148 +msgid "dialog.unsaved_changes.message" +msgstr "Save changes before closing?" + +#: src/dialogs.rs:150 +msgid "dialog.unsaved_changes.title" +msgstr "Unsaved Changes" + +#: src/dialogs.rs:151 +msgid "dialog.action.dont_save" +msgstr "Don't Save" + +#: src/dialogs.rs:184 +msgid "dialog.file_type.png" +msgstr "PNG Image" + +#: src/dialogs.rs:185 +msgid "dialog.file_type.svg" +msgstr "SVG Image" + +#: src/dialogs.rs:198 +msgid "dialog.export.title" +msgstr "Export Map as Image" + +#: src/dialogs.rs:203 +msgid "dialog.action.export" +msgstr "Export" + +#: src/handlers/export.rs:41 +msgid "error.file.export" +msgstr "Could not export image. Please make sure the destination is writable." + +#: src/handlers/file.rs:39 +msgid "errors.file.open" +msgstr "Could not open file. Please make sure the file is readable." + +#: src/handlers/file.rs:62 +msgid "errors.file.save" +msgstr "Could not save file. Please make sure the destination is writable." + +#: src/main.rs:246 +msgid "document.untitled" +msgstr "Untitled" + +#: src/main.rs:386 src/main.rs:579 +msgid "command.view.use_vertical_layout" +msgstr "Use Vertical Layout" + +#: src/main.rs:387 +msgid "command.view.layout.use_horizontal_layout" +msgstr "Use Horizontal Layout" + +#: src/main.rs:400 src/main.rs:575 +msgid "command.file.export" +msgstr "Export as Image" + +#: src/main.rs:458 src/main.rs:586 +msgid "command.view.zoom_out" +msgstr "Zoom Out" + +#: src/main.rs:481 src/main.rs:585 +msgid "command.view.zoom_in" +msgstr "Zoom In" + +#: src/main.rs:569 +msgid "command.file.new" +msgstr "New Map" + +#: src/main.rs:570 +msgid "command.file.new_from_template" +msgstr "New from Template" + +#: src/main.rs:571 +msgid "command.file.open" +msgstr "Open..." + +#: src/main.rs:572 +msgid "command.file.save" +msgstr "Save" + +#: src/main.rs:573 +msgid "command.file.save_as" +msgstr "Save As..." + +#: src/main.rs:574 +msgid "command.file.close" +msgstr "Close" + +#: src/main.rs:590 +msgid "command.application.preferences" +msgstr "Preferences" + +#. Font Size section +#: src/preferences/pages/editor.rs:45 +msgid "preferences.editor.font_size.title" +msgstr "Font Size" + +#: src/preferences/pages/editor.rs:60 +msgid "preferences.editor.font_size.unit" +msgstr "pt" + +#. Editor Style section +#: src/preferences/pages/editor.rs:64 +msgid "preferences.editor.editor_style.title" +msgstr "Editor Style" + +#. Soft Wrap Lines +#: src/preferences/pages/editor.rs:71 +msgid "preferences.editor.editor_style.soft_wrap_lines" +msgstr "Soft wrap lines" + +#: src/preferences/pages/editor.rs:84 +msgid "preferences.editor.editor_style.use_custom_font" +msgstr "Use custom font" + +#: src/preferences/pages/editor.rs:90 +msgid "preferences.editor.font" +msgstr "Font" + +#. Preferences section +#: src/preferences/pages/general.rs:40 +msgid "preferences.general.preferences.title" +msgstr "Preferences" + +#. Export button +#: src/preferences/pages/general.rs:48 +msgid "preferences.general.preferences.export" +msgstr "Export" + +#. Import button +#: src/preferences/pages/general.rs:58 +msgid "preferences.general.preferences.import" +msgstr "Import" + +#. Reset button +#: src/preferences/pages/general.rs:68 +msgid "preferences.general.preferences.reset" +msgstr "Reset to defaults" + +#. Help text +#: src/preferences/pages/general.rs:81 +msgid "preferences.general.explanation" +msgstr "" +"Export your preferences to share with others or back up your settings.\n" +"Import preferences from a file to restore settings." + +#: src/preferences/pages/general.rs:98 src/preferences/pages/general.rs:130 +msgid "dialog.file_type.json" +msgstr "JSON File" + +#: src/preferences/pages/general.rs:101 +msgid "dialog.export_preferences.title" +msgstr "Export Preferences" + +#: src/preferences/pages/general.rs:133 +msgid "dialog.import_preferences.title" +msgstr "Import Preferences" + +#. Map Style section +#: src/preferences/pages/map.rs:46 +msgid "preferences.map.map_style.title" +msgstr "Map Style" + +#: src/preferences/pages/map.rs:53 +msgid "preferences.map.map_style.show_background" +msgstr "Show map background" + +#: src/preferences/pages/map.rs:66 +msgid "preferences.map.map_style.use_smart_label_positioning" +msgstr "Use smart label positioning" + +#. Use Custom Font +#: src/preferences/pages/map.rs:80 +msgid "preferences.map.map_style.use_custom_font" +msgstr "Use custom font" + +#: src/preferences/pages/map.rs:86 +msgid "preferences.map.font" +msgstr "Font" + +#. Export section +#: src/preferences/pages/map.rs:119 +msgid "preferences.map.export.title" +msgstr "Export" + +#: src/preferences/pages/map.rs:126 +msgid "preferences.map.export.default_format" +msgstr "Default export format" + +#: src/preferences/pages/map.rs:128 +msgid "export_formats.png" +msgstr "PNG (Raster)" + +#: src/preferences/pages/map.rs:128 +msgid "export_formats.svg" +msgstr "SVG (Vector)" + +#. Header +#: src/preferences/pages/stages.rs:42 +msgid "preferences.stages.title" +msgstr "Custom Stages" + +#: src/preferences/pages/stages.rs:47 +msgid "preferences.stages.explanation" +msgstr "Custom stages allow you to define your own evolution axis label." + +#: src/preferences/pages/stages.rs:58 +msgid "preferences.stages.column.name" +msgstr "Name" + +#: src/preferences/pages/stages.rs:63 +msgid "preferences.stages.column.stage_i" +msgstr "Stage I" + +#: src/preferences/pages/stages.rs:68 +msgid "preferences.stages.column.stage_ii" +msgstr "Stage II" + +#: src/preferences/pages/stages.rs:73 +msgid "preferences.stages.column.stage_iii" +msgstr "Stage III" + +#: src/preferences/pages/stages.rs:78 +msgid "preferences.stages.column.stage_iv" +msgstr "Stage IV" + +#: src/preferences/pages/stages.rs:107 +msgid "preferences.stages.help.add" +msgstr "Add custom stage" + +#: src/preferences/pages/stages.rs:241 +msgid "preferences.stages.help.remove" +msgstr "Remove stage" + +#: src/preferences/pages/templates.rs:54 +msgid "preferences.templates.title" +msgstr "Title" + +#: src/preferences/pages/templates.rs:77 +msgid "preferences.templates.help.add" +msgstr "Add template" + +#: src/preferences/pages/templates.rs:82 +msgid "dialog.template.default_label" +msgstr "My Template" + +#: src/preferences/pages/templates.rs:89 +msgid "preferences.templates.help.remove" +msgstr "Remove template" + +#: src/preferences/pages/templates.rs:114 +msgid "preferences.templates.content" +msgstr "Template Content" + +#: src/preferences/pages/templates.rs:251 +msgid "preferences.templates.help.default" +msgstr "This is the default template." + +#: src/preferences/pages/templates.rs:254 +msgid "preferences.templates.help.set_as_default" +msgstr "Set as default template." + +#: src/preferences/window.rs:105 +msgid "preferences.title" +msgstr "Preferences" + +#: src/preferences/window.rs:131 +msgid "preferences.menu.general" +msgstr "General" + +#: src/preferences/window.rs:132 +msgid "preferences.menu.editor" +msgstr "Editor" + +#: src/preferences/window.rs:133 +msgid "preferences.menu.map" +msgstr "Map" + +#: src/preferences/window.rs:134 +msgid "preferences.menu.stages" +msgstr "Stages" + +#: src/preferences/window.rs:135 +msgid "preferences.menu.templates" +msgstr "Templates" + +#: src/stages.rs:58 +msgid "stage.activities.name" +msgstr "Activities" + +#: src/stages.rs:59 +msgid "stage.practice.name" +msgstr "Practice" + +#: src/stages.rs:60 +msgid "stage.data.name" +msgstr "Data" + +#: src/stages.rs:61 +msgid "stages.knowledge.name" +msgstr "Knowledge" + +#: src/stages.rs:62 +msgid "stages.ubiquity.name" +msgstr "Ubiquity" + +#: src/stages.rs:63 +msgid "stages.certainty.name" +msgstr "Certainty" + +#: src/stages.rs:64 +msgid "stages.publication_types.name" +msgstr "Publication Types" + +#: src/stages.rs:65 +msgid "stages.market.name" +msgstr "Market" + +#: src/stages.rs:66 +msgid "stages.knowledge_management.name" +msgstr "Knowledge Management" + +#: src/stages.rs:67 +msgid "stages.market_perception.name" +msgstr "Market Perception" + +#: src/stages.rs:68 +msgid "stages.user_perception.name" +msgstr "User Perception" + +#: src/stages.rs:69 +msgid "stages.perception_in_industry.name" +msgstr "Perception in Industry" + +#: src/stages.rs:70 +msgid "stages.focus_of_value.name" +msgstr "Focus of Value" + +#: src/stages.rs:71 +msgid "stages.understanding.name" +msgstr "Understanding" + +#: src/stages.rs:72 +msgid "stages.comparison.name" +msgstr "Comparison" + +#: src/stages.rs:73 +msgid "stages.failure.name" +msgstr "Failure" + +#: src/stages.rs:74 +msgid "stages.market_action.name" +msgstr "Market Action" + +#: src/stages.rs:75 +msgid "stages.efficiency.name" +msgstr "Efficiency" + +#: src/stages.rs:76 +msgid "stages.decision_drivers.name" +msgstr "Decision Drivers" + +#: src/stages.rs:77 +msgid "stages.behavior.name" +msgstr "Behavior" + +#: src/stages.rs:78 +msgid "stages.cynefin.name" +msgstr "Cynefin" + +#: src/stages.rs:79 +msgid "stages.evolution_stage.name" +msgstr "Evolution Stage" + +#: src/stages.rs:85 +msgid "stage.activities.i" +msgstr "Genesis" + +#: src/stages.rs:86 +msgid "stage.practice.i" +msgstr "Novel" + +#: src/stages.rs:87 +msgid "stage.data.i" +msgstr "Unmodelled" + +#: src/stages.rs:88 +msgid "stages.knowledge.i" +msgstr "Concept" + +#: src/stages.rs:89 +msgid "stages.ubiquity.i" +msgstr "Rare" + +#: src/stages.rs:90 +msgid "stages.certainty.i" +msgstr "Poorly Understood / exploring the unknown" + +#: src/stages.rs:91 +msgid "stages.publication_types.i" +msgstr "" +"Describe the wonder of the thing / the discovery of some marvel / a new " +"land / an unknown frontier" + +#: src/stages.rs:92 +msgid "stages.market.i" +msgstr "Undefined Market" + +#: src/stages.rs:93 +msgid "stages.knowledge_management.i" +msgstr "Uncertain" + +#: src/stages.rs:94 +msgid "stages.market_perception.i" +msgstr "Chaotic (non-linear) / domain of the \"crazy\"" + +#: src/stages.rs:95 +msgid "stages.user_perception.i" +msgstr "Different / confusing / exciting / surprising / dangerous" + +#: src/stages.rs:96 +msgid "stages.perception_in_industry.i" +msgstr "Future source of competitive advantage / unpredictable / unknown" + +#: src/stages.rs:97 +msgid "stages.focus_of_value.i" +msgstr "High future worth but immediate investment" + +#: src/stages.rs:98 +msgid "stages.understanding.i" +msgstr "Poorly Understood / unpredictable" + +#: src/stages.rs:99 +msgid "stages.comparison.i" +msgstr "Constantly changing / a differential / unstable" + +#: src/stages.rs:100 +msgid "stages.failure.i" +msgstr "High / tolerated / assumed to be wrong" + +#: src/stages.rs:101 +msgid "stages.market_action.i" +msgstr "Gambling / driven by gut" + +#: src/stages.rs:102 +msgid "stages.efficiency.i" +msgstr "Reducing the cost of change (experimentation)" + +#: src/stages.rs:103 +msgid "stages.decision_drivers.i" +msgstr "Heritage / culture" + +#: src/stages.rs:104 +msgid "stages.behavior.i" +msgstr "Uncertain when to use" + +#: src/stages.rs:105 +msgid "stages.cynefin.i" +msgstr "Chaotic" + +#: src/stages.rs:106 +msgid "stages.evolution_stage.i" +msgstr "Stage I" + +#: src/stages.rs:112 +msgid "stage.activities.ii" +msgstr "Custom" + +#: src/stages.rs:113 +msgid "stage.practice.ii" +msgstr "Emerging" + +#: src/stages.rs:114 +msgid "stage.data.ii" +msgstr "Divergent" + +#: src/stages.rs:115 +msgid "stages.knowledge.ii" +msgstr "Hypothesis" + +#: src/stages.rs:116 +msgid "stages.ubiquity.ii" +msgstr "Slowly Increasing" + +#: src/stages.rs:117 +msgid "stages.certainty.ii" +msgstr "Rapid Increase In Learning / discovery becomes refining" + +#: src/stages.rs:118 +msgid "stages.publication_types.ii" +msgstr "" +"Focused on build / construct / awareness and learning / many models of " +"explanation / no accepted forms / a wild west" + +#: src/stages.rs:119 +msgid "stages.market.ii" +msgstr "" +"Forming Market / an array of competing forms and models of understanding" + +#: src/stages.rs:120 +msgid "stages.knowledge_management.ii" +msgstr "Learning on use / focused on testing prediction" + +#: src/stages.rs:121 +msgid "stages.market_perception.ii" +msgstr "Domain of \"experts\"" + +#: src/stages.rs:122 +msgid "stages.user_perception.ii" +msgstr "Leading edge / emerging / unceirtanty over results" + +#: src/stages.rs:123 +msgid "stages.perception_in_industry.ii" +msgstr "" +"Seen as a scompetitive advantage / a differential / ROI / case examples" + +#: src/stages.rs:124 +msgid "stages.focus_of_value.ii" +msgstr "Seeking ways to profit and a ROI / seeking confirmation of value" + +#: src/stages.rs:125 +msgid "stages.understanding.ii" +msgstr "Increasing understanding / development of measures" + +#: src/stages.rs:126 +msgid "stages.comparison.ii" +msgstr "Learning from others / testing the water / some evidential support" + +#: src/stages.rs:127 +msgid "stages.failure.ii" +msgstr "Moderate / unsurprising if wrong but disappointed" + +#: src/stages.rs:128 +msgid "stages.market_action.ii" +msgstr "Exploring a \"found\" value" + +#: src/stages.rs:129 +msgid "stages.efficiency.ii" +msgstr "Reducing cost of waste (Learning)" + +#: src/stages.rs:130 +msgid "stages.decision_drivers.ii" +msgstr "Analyses & synthesis" + +#: src/stages.rs:131 +msgid "stages.behavior.ii" +msgstr "Learning when to use" + +#: src/stages.rs:132 +msgid "stages.cynefin.ii" +msgstr "Complex" + +#: src/stages.rs:133 +msgid "stages.evolution_stage.ii" +msgstr "Stage II" + +#: src/stages.rs:139 +msgid "stage.activities.iii" +msgstr "Product (+rental)" + +#: src/stages.rs:140 +msgid "stage.practice.iii" +msgstr "Good" + +#: src/stages.rs:141 +msgid "stage.data.iii" +msgstr "Convergent" + +#: src/stages.rs:142 +msgid "stages.knowledge.iii" +msgstr "Theory" + +#: src/stages.rs:143 +msgid "stages.ubiquity.iii" +msgstr "Rapidly Increasing" + +#: src/stages.rs:144 +msgid "stages.certainty.iii" +msgstr "Rapid increase in use / increasing fit for purpose" + +#: src/stages.rs:145 +msgid "stages.publication_types.iii" +msgstr "" +"Maintenance / operations / installation / comparison between competing " +"forms / feature analysis" + +#: src/stages.rs:146 +msgid "stages.market.iii" +msgstr "" +"Growing Market / consolidation to a few competing but more accepted forms" + +#: src/stages.rs:147 +msgid "stages.knowledge_management.iii" +msgstr "Learning on operation / using prediction / verification" + +#: src/stages.rs:148 +msgid "stages.market_perception.iii" +msgstr "Increasing expectation of use / domain of \"professionals\"" + +#: src/stages.rs:149 +msgid "stages.user_perception.iii" +msgstr "" +"Increasingly common / disappointed if not used or available / feeling left " +"behind" + +#: src/stages.rs:150 +msgid "stages.perception_in_industry.iii" +msgstr "" +"Advantage through implementation / features / this model is better than that" + +#: src/stages.rs:151 +msgid "stages.focus_of_value.iii" +msgstr "" +"High profitability per unit / a valuable model / a feeling of " +"understanding / focus on exploitation" + +#: src/stages.rs:152 +msgid "stages.understanding.iii" +msgstr "Increasing education / constant refinement of needs / measures" + +#: src/stages.rs:153 +msgid "stages.comparison.iii" +msgstr "Competing models / feature difference / evidential support" + +#: src/stages.rs:154 +msgid "stages.failure.iii" +msgstr "" +"Not tolerated / focus on constant improvement / assumed to be in the right " +"direction / resistance to changing the model" + +#: src/stages.rs:155 +msgid "stages.market_action.iii" +msgstr "Market analysis / listening to customers" + +#: src/stages.rs:156 +msgid "stages.efficiency.iii" +msgstr "Reducing cost of waste (Learning)" + +#: src/stages.rs:157 +msgid "stages.decision_drivers.iii" +msgstr "Analyses & synthesis" + +#: src/stages.rs:158 +msgid "stages.behavior.iii" +msgstr "Learning through use" + +#: src/stages.rs:159 +msgid "stages.cynefin.iii" +msgstr "Complicated" + +#: src/stages.rs:160 +msgid "stages.evolution_stage.iii" +msgstr "Stage III" + +#: src/stages.rs:166 +msgid "stage.activities.iv" +msgstr "Commodity (+utility)" + +#: src/stages.rs:167 +msgid "stage.practice.iv" +msgstr "Best" + +#: src/stages.rs:168 +msgid "stage.data.iv" +msgstr "Modelled" + +#: src/stages.rs:169 +msgid "stages.knowledge.iv" +msgstr "Accepted" + +#: src/stages.rs:170 +msgid "stages.ubiquity.iv" +msgstr "Widespread in the applicable market / ecosystem" + +#: src/stages.rs:171 +msgid "stages.certainty.iv" +msgstr "Commonly understood (in terms of use)" + +#: src/stages.rs:172 +msgid "stages.publication_types.iv" +msgstr "Focused on use / increasingly an accepted, almost invisible component" + +#: src/stages.rs:173 +msgid "stages.market.iv" +msgstr "Mature Market / stabilised to an accepted form" + +#: src/stages.rs:174 +msgid "stages.knowledge_management.iv" +msgstr "Known / accepted" + +#: src/stages.rs:175 +msgid "stages.market_perception.iv" +msgstr "Ordered (appearance of being linear) / trivial / formula to be applied" + +#: src/stages.rs:176 +msgid "stages.user_perception.iv" +msgstr "Standard / expected / feeling of shock if not used" + +#: src/stages.rs:177 +msgid "stages.perception_in_industry.iv" +msgstr "Cost of doing business / accepted / specific defined models" + +#: src/stages.rs:178 +msgid "stages.focus_of_value.iv" +msgstr "" +"High volume / reducing margin / important but invisible / an essential " +"component of something more complex" + +#: src/stages.rs:179 +msgid "stages.understanding.iv" +msgstr "Believed to be well defined / stable / measurable" + +#: src/stages.rs:180 +msgid "stages.comparison.iv" +msgstr "Essential / any advantage is operational / accepted norm" + +#: src/stages.rs:181 +msgid "stages.failure.iv" +msgstr "Surprised by failure / focus on operational efficiency" + +#: src/stages.rs:182 +msgid "stages.market_action.iv" +msgstr "Metric driven / build what is needed" + +#: src/stages.rs:183 +msgid "stages.efficiency.iv" +msgstr "Reducing cost of deviation (Volume)" + +#: src/stages.rs:184 +msgid "stages.decision_drivers.iv" +msgstr "Previous Experience" + +#: src/stages.rs:185 +msgid "stages.behavior.iv" +msgstr "Known / common usage" + +#: src/stages.rs:186 +msgid "stages.cynefin.iv" +msgstr "Clear" + +#: src/stages.rs:187 +msgid "stages.evolution_stage.iv" +msgstr "Stage IV" diff --git a/po/en_US/LC_MESSAGES/systems.tranquil.Map.mo b/po/en_US/LC_MESSAGES/systems.tranquil.Map.mo Binary files differnew file mode 100644 index 0000000..9daf8c8 --- /dev/null +++ b/po/en_US/LC_MESSAGES/systems.tranquil.Map.mo diff --git a/po/systems.tranquil.Map.pot b/po/systems.tranquil.Map.pot new file mode 100644 index 0000000..bac0955 --- /dev/null +++ b/po/systems.tranquil.Map.pot @@ -0,0 +1,767 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-01-17 02:37+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/dialogs.rs:35 +msgid "dialog.file_type.wmap" +msgstr "" + +#: src/dialogs.rs:62 +msgid "dialog.open.title" +msgstr "" + +#: src/dialogs.rs:66 src/dialogs.rs:106 src/dialogs.rs:152 src/dialogs.rs:202 +#: src/preferences/pages/general.rs:105 src/preferences/pages/general.rs:137 +msgid "dialog.action.cancel" +msgstr "" + +#: src/dialogs.rs:67 src/preferences/pages/general.rs:138 +msgid "dialog.action.open" +msgstr "" + +#: src/dialogs.rs:102 +msgid "dialog.save.title" +msgstr "" + +#: src/dialogs.rs:107 src/dialogs.rs:153 src/preferences/pages/general.rs:106 +msgid "dialog.action.save" +msgstr "" + +#: src/dialogs.rs:148 +msgid "dialog.unsaved_changes.message" +msgstr "" + +#: src/dialogs.rs:150 +msgid "dialog.unsaved_changes.title" +msgstr "" + +#: src/dialogs.rs:151 +msgid "dialog.action.dont_save" +msgstr "" + +#: src/dialogs.rs:184 +msgid "dialog.file_type.png" +msgstr "" + +#: src/dialogs.rs:185 +msgid "dialog.file_type.svg" +msgstr "" + +#: src/dialogs.rs:198 +msgid "dialog.export.title" +msgstr "" + +#: src/dialogs.rs:203 +msgid "dialog.action.export" +msgstr "" + +#: src/handlers/export.rs:41 +msgid "error.file.export" +msgstr "" + +#: src/handlers/file.rs:39 +msgid "errors.file.open" +msgstr "" + +#: src/handlers/file.rs:62 +msgid "errors.file.save" +msgstr "" + +#: src/main.rs:246 +msgid "document.untitled" +msgstr "" + +#: src/main.rs:386 src/main.rs:579 +msgid "command.view.use_vertical_layout" +msgstr "" + +#: src/main.rs:387 +msgid "command.view.layout.use_horizontal_layout" +msgstr "" + +#: src/main.rs:400 src/main.rs:575 +msgid "command.file.export" +msgstr "" + +#: src/main.rs:458 src/main.rs:586 +msgid "command.view.zoom_out" +msgstr "" + +#: src/main.rs:481 src/main.rs:585 +msgid "command.view.zoom_in" +msgstr "" + +#: src/main.rs:569 +msgid "command.file.new" +msgstr "" + +#: src/main.rs:570 +msgid "command.file.new_from_template" +msgstr "" + +#: src/main.rs:571 +msgid "command.file.open" +msgstr "" + +#: src/main.rs:572 +msgid "command.file.save" +msgstr "" + +#: src/main.rs:573 +msgid "command.file.save_as" +msgstr "" + +#: src/main.rs:574 +msgid "command.file.close" +msgstr "" + +#: src/main.rs:590 +msgid "command.application.preferences" +msgstr "" + +#. Font Size section +#: src/preferences/pages/editor.rs:45 +msgid "preferences.editor.font_size.title" +msgstr "" + +#: src/preferences/pages/editor.rs:60 +msgid "preferences.editor.font_size.unit" +msgstr "" + +#. Editor Style section +#: src/preferences/pages/editor.rs:64 +msgid "preferences.editor.editor_style.title" +msgstr "" + +#. Soft Wrap Lines +#: src/preferences/pages/editor.rs:71 +msgid "preferences.editor.editor_style.soft_wrap_lines" +msgstr "" + +#: src/preferences/pages/editor.rs:84 +msgid "preferences.editor.editor_style.use_custom_font" +msgstr "" + +#: src/preferences/pages/editor.rs:90 +msgid "preferences.editor.font" +msgstr "" + +#. Preferences section +#: src/preferences/pages/general.rs:40 +msgid "preferences.general.preferences.title" +msgstr "" + +#. Export button +#: src/preferences/pages/general.rs:48 +msgid "preferences.general.preferences.export" +msgstr "" + +#. Import button +#: src/preferences/pages/general.rs:58 +msgid "preferences.general.preferences.import" +msgstr "" + +#. Reset button +#: src/preferences/pages/general.rs:68 +msgid "preferences.general.preferences.reset" +msgstr "" + +#. Help text +#: src/preferences/pages/general.rs:81 +msgid "preferences.general.explanation" +msgstr "" + +#: src/preferences/pages/general.rs:98 src/preferences/pages/general.rs:130 +msgid "dialog.file_type.json" +msgstr "" + +#: src/preferences/pages/general.rs:101 +msgid "dialog.export_preferences.title" +msgstr "" + +#: src/preferences/pages/general.rs:133 +msgid "dialog.import_preferences.title" +msgstr "" + +#. Map Style section +#: src/preferences/pages/map.rs:46 +msgid "preferences.map.map_style.title" +msgstr "" + +#: src/preferences/pages/map.rs:53 +msgid "preferences.map.map_style.show_background" +msgstr "" + +#: src/preferences/pages/map.rs:66 +msgid "preferences.map.map_style.use_smart_label_positioning" +msgstr "" + +#. Use Custom Font +#: src/preferences/pages/map.rs:80 +msgid "preferences.map.map_style.use_custom_font" +msgstr "" + +#: src/preferences/pages/map.rs:86 +msgid "preferences.map.font" +msgstr "" + +#. Export section +#: src/preferences/pages/map.rs:119 +msgid "preferences.map.export.title" +msgstr "" + +#: src/preferences/pages/map.rs:126 +msgid "preferences.map.export.default_format" +msgstr "" + +#: src/preferences/pages/map.rs:128 +msgid "export_formats.png" +msgstr "" + +#: src/preferences/pages/map.rs:128 +msgid "export_formats.svg" +msgstr "" + +#. Header +#: src/preferences/pages/stages.rs:42 +msgid "preferences.stages.title" +msgstr "" + +#: src/preferences/pages/stages.rs:47 +msgid "preferences.stages.explanation" +msgstr "" + +#: src/preferences/pages/stages.rs:58 +msgid "preferences.stages.column.name" +msgstr "" + +#: src/preferences/pages/stages.rs:63 +msgid "preferences.stages.column.stage_i" +msgstr "" + +#: src/preferences/pages/stages.rs:68 +msgid "preferences.stages.column.stage_ii" +msgstr "" + +#: src/preferences/pages/stages.rs:73 +msgid "preferences.stages.column.stage_iii" +msgstr "" + +#: src/preferences/pages/stages.rs:78 +msgid "preferences.stages.column.stage_iv" +msgstr "" + +#: src/preferences/pages/stages.rs:107 +msgid "preferences.stages.help.add" +msgstr "" + +#: src/preferences/pages/stages.rs:241 +msgid "preferences.stages.help.remove" +msgstr "" + +#: src/preferences/pages/templates.rs:54 +msgid "preferences.templates.title" +msgstr "" + +#: src/preferences/pages/templates.rs:77 +msgid "preferences.templates.help.add" +msgstr "" + +#: src/preferences/pages/templates.rs:82 +msgid "dialog.template.default_label" +msgstr "" + +#: src/preferences/pages/templates.rs:89 +msgid "preferences.templates.help.remove" +msgstr "" + +#: src/preferences/pages/templates.rs:114 +msgid "preferences.templates.content" +msgstr "" + +#: src/preferences/pages/templates.rs:251 +msgid "preferences.templates.help.default" +msgstr "" + +#: src/preferences/pages/templates.rs:254 +msgid "preferences.templates.help.set_as_default" +msgstr "" + +#: src/preferences/window.rs:105 +msgid "preferences.title" +msgstr "" + +#: src/preferences/window.rs:131 +msgid "preferences.menu.general" +msgstr "" + +#: src/preferences/window.rs:132 +msgid "preferences.menu.editor" +msgstr "" + +#: src/preferences/window.rs:133 +msgid "preferences.menu.map" +msgstr "" + +#: src/preferences/window.rs:134 +msgid "preferences.menu.stages" +msgstr "" + +#: src/preferences/window.rs:135 +msgid "preferences.menu.templates" +msgstr "" + +#: src/stages.rs:58 +msgid "stage.activities.name" +msgstr "" + +#: src/stages.rs:59 +msgid "stage.practice.name" +msgstr "" + +#: src/stages.rs:60 +msgid "stage.data.name" +msgstr "" + +#: src/stages.rs:61 +msgid "stages.knowledge.name" +msgstr "" + +#: src/stages.rs:62 +msgid "stages.ubiquity.name" +msgstr "" + +#: src/stages.rs:63 +msgid "stages.certainty.name" +msgstr "" + +#: src/stages.rs:64 +msgid "stages.publication_types.name" +msgstr "" + +#: src/stages.rs:65 +msgid "stages.market.name" +msgstr "" + +#: src/stages.rs:66 +msgid "stages.knowledge_management.name" +msgstr "" + +#: src/stages.rs:67 +msgid "stages.market_perception.name" +msgstr "" + +#: src/stages.rs:68 +msgid "stages.user_perception.name" +msgstr "" + +#: src/stages.rs:69 +msgid "stages.perception_in_industry.name" +msgstr "" + +#: src/stages.rs:70 +msgid "stages.focus_of_value.name" +msgstr "" + +#: src/stages.rs:71 +msgid "stages.understanding.name" +msgstr "" + +#: src/stages.rs:72 +msgid "stages.comparison.name" +msgstr "" + +#: src/stages.rs:73 +msgid "stages.failure.name" +msgstr "" + +#: src/stages.rs:74 +msgid "stages.market_action.name" +msgstr "" + +#: src/stages.rs:75 +msgid "stages.efficiency.name" +msgstr "" + +#: src/stages.rs:76 +msgid "stages.decision_drivers.name" +msgstr "" + +#: src/stages.rs:77 +msgid "stages.behavior.name" +msgstr "" + +#: src/stages.rs:78 +msgid "stages.cynefin.name" +msgstr "" + +#: src/stages.rs:79 +msgid "stages.evolution_stage.name" +msgstr "" + +#: src/stages.rs:85 +msgid "stage.activities.i" +msgstr "" + +#: src/stages.rs:86 +msgid "stage.practice.i" +msgstr "" + +#: src/stages.rs:87 +msgid "stage.data.i" +msgstr "" + +#: src/stages.rs:88 +msgid "stages.knowledge.i" +msgstr "" + +#: src/stages.rs:89 +msgid "stages.ubiquity.i" +msgstr "" + +#: src/stages.rs:90 +msgid "stages.certainty.i" +msgstr "" + +#: src/stages.rs:91 +msgid "stages.publication_types.i" +msgstr "" + +#: src/stages.rs:92 +msgid "stages.market.i" +msgstr "" + +#: src/stages.rs:93 +msgid "stages.knowledge_management.i" +msgstr "" + +#: src/stages.rs:94 +msgid "stages.market_perception.i" +msgstr "" + +#: src/stages.rs:95 +msgid "stages.user_perception.i" +msgstr "" + +#: src/stages.rs:96 +msgid "stages.perception_in_industry.i" +msgstr "" + +#: src/stages.rs:97 +msgid "stages.focus_of_value.i" +msgstr "" + +#: src/stages.rs:98 +msgid "stages.understanding.i" +msgstr "" + +#: src/stages.rs:99 +msgid "stages.comparison.i" +msgstr "" + +#: src/stages.rs:100 +msgid "stages.failure.i" +msgstr "" + +#: src/stages.rs:101 +msgid "stages.market_action.i" +msgstr "" + +#: src/stages.rs:102 +msgid "stages.efficiency.i" +msgstr "" + +#: src/stages.rs:103 +msgid "stages.decision_drivers.i" +msgstr "" + +#: src/stages.rs:104 +msgid "stages.behavior.i" +msgstr "" + +#: src/stages.rs:105 +msgid "stages.cynefin.i" +msgstr "" + +#: src/stages.rs:106 +msgid "stages.evolution_stage.i" +msgstr "" + +#: src/stages.rs:112 +msgid "stage.activities.ii" +msgstr "" + +#: src/stages.rs:113 +msgid "stage.practice.ii" +msgstr "" + +#: src/stages.rs:114 +msgid "stage.data.ii" +msgstr "" + +#: src/stages.rs:115 +msgid "stages.knowledge.ii" +msgstr "" + +#: src/stages.rs:116 +msgid "stages.ubiquity.ii" +msgstr "" + +#: src/stages.rs:117 +msgid "stages.certainty.ii" +msgstr "" + +#: src/stages.rs:118 +msgid "stages.publication_types.ii" +msgstr "" + +#: src/stages.rs:119 +msgid "stages.market.ii" +msgstr "" + +#: src/stages.rs:120 +msgid "stages.knowledge_management.ii" +msgstr "" + +#: src/stages.rs:121 +msgid "stages.market_perception.ii" +msgstr "" + +#: src/stages.rs:122 +msgid "stages.user_perception.ii" +msgstr "" + +#: src/stages.rs:123 +msgid "stages.perception_in_industry.ii" +msgstr "" + +#: src/stages.rs:124 +msgid "stages.focus_of_value.ii" +msgstr "" + +#: src/stages.rs:125 +msgid "stages.understanding.ii" +msgstr "" + +#: src/stages.rs:126 +msgid "stages.comparison.ii" +msgstr "" + +#: src/stages.rs:127 +msgid "stages.failure.ii" +msgstr "" + +#: src/stages.rs:128 +msgid "stages.market_action.ii" +msgstr "" + +#: src/stages.rs:129 +msgid "stages.efficiency.ii" +msgstr "" + +#: src/stages.rs:130 +msgid "stages.decision_drivers.ii" +msgstr "" + +#: src/stages.rs:131 +msgid "stages.behavior.ii" +msgstr "" + +#: src/stages.rs:132 +msgid "stages.cynefin.ii" +msgstr "" + +#: src/stages.rs:133 +msgid "stages.evolution_stage.ii" +msgstr "" + +#: src/stages.rs:139 +msgid "stage.activities.iii" +msgstr "" + +#: src/stages.rs:140 +msgid "stage.practice.iii" +msgstr "" + +#: src/stages.rs:141 +msgid "stage.data.iii" +msgstr "" + +#: src/stages.rs:142 +msgid "stages.knowledge.iii" +msgstr "" + +#: src/stages.rs:143 +msgid "stages.ubiquity.iii" +msgstr "" + +#: src/stages.rs:144 +msgid "stages.certainty.iii" +msgstr "" + +#: src/stages.rs:145 +msgid "stages.publication_types.iii" +msgstr "" + +#: src/stages.rs:146 +msgid "stages.market.iii" +msgstr "" + +#: src/stages.rs:147 +msgid "stages.knowledge_management.iii" +msgstr "" + +#: src/stages.rs:148 +msgid "stages.market_perception.iii" +msgstr "" + +#: src/stages.rs:149 +msgid "stages.user_perception.iii" +msgstr "" + +#: src/stages.rs:150 +msgid "stages.perception_in_industry.iii" +msgstr "" + +#: src/stages.rs:151 +msgid "stages.focus_of_value.iii" +msgstr "" + +#: src/stages.rs:152 +msgid "stages.understanding.iii" +msgstr "" + +#: src/stages.rs:153 +msgid "stages.comparison.iii" +msgstr "" + +#: src/stages.rs:154 +msgid "stages.failure.iii" +msgstr "" + +#: src/stages.rs:155 +msgid "stages.market_action.iii" +msgstr "" + +#: src/stages.rs:156 +msgid "stages.efficiency.iii" +msgstr "" + +#: src/stages.rs:157 +msgid "stages.decision_drivers.iii" +msgstr "" + +#: src/stages.rs:158 +msgid "stages.behavior.iii" +msgstr "" + +#: src/stages.rs:159 +msgid "stages.cynefin.iii" +msgstr "" + +#: src/stages.rs:160 +msgid "stages.evolution_stage.iii" +msgstr "" + +#: src/stages.rs:166 +msgid "stage.activities.iv" +msgstr "" + +#: src/stages.rs:167 +msgid "stage.practice.iv" +msgstr "" + +#: src/stages.rs:168 +msgid "stage.data.iv" +msgstr "" + +#: src/stages.rs:169 +msgid "stages.knowledge.iv" +msgstr "" + +#: src/stages.rs:170 +msgid "stages.ubiquity.iv" +msgstr "" + +#: src/stages.rs:171 +msgid "stages.certainty.iv" +msgstr "" + +#: src/stages.rs:172 +msgid "stages.publication_types.iv" +msgstr "" + +#: src/stages.rs:173 +msgid "stages.market.iv" +msgstr "" + +#: src/stages.rs:174 +msgid "stages.knowledge_management.iv" +msgstr "" + +#: src/stages.rs:175 +msgid "stages.market_perception.iv" +msgstr "" + +#: src/stages.rs:176 +msgid "stages.user_perception.iv" +msgstr "" + +#: src/stages.rs:177 +msgid "stages.perception_in_industry.iv" +msgstr "" + +#: src/stages.rs:178 +msgid "stages.focus_of_value.iv" +msgstr "" + +#: src/stages.rs:179 +msgid "stages.understanding.iv" +msgstr "" + +#: src/stages.rs:180 +msgid "stages.comparison.iv" +msgstr "" + +#: src/stages.rs:181 +msgid "stages.failure.iv" +msgstr "" + +#: src/stages.rs:182 +msgid "stages.market_action.iv" +msgstr "" + +#: src/stages.rs:183 +msgid "stages.efficiency.iv" +msgstr "" + +#: src/stages.rs:184 +msgid "stages.decision_drivers.iv" +msgstr "" + +#: src/stages.rs:185 +msgid "stages.behavior.iv" +msgstr "" + +#: src/stages.rs:186 +msgid "stages.cynefin.iv" +msgstr "" + +#: src/stages.rs:187 +msgid "stages.evolution_stage.iv" +msgstr "" diff --git a/resources/wmap.lang b/resources/wmap.lang index c00d29e..e05c8d3 100644 --- a/resources/wmap.lang +++ b/resources/wmap.lang @@ -46,10 +46,10 @@ </include> </context> - <!-- Blocker pattern: [Blocker] Node --> - <context id="blocker"> + <!-- Inertia pattern: [Inertia] Node --> + <context id="inertia"> <match extended="true" case-sensitive="false"> - ^\[(Blocker)\]\s*(.+)$ + ^\[(inertia)\]\s*(.+)$ </match> <include> <context sub-pattern="1" style-ref="keyword"/> @@ -110,7 +110,7 @@ <!-- Main context --> <context id="wmap"> <include> - <context ref="blocker"/> + <context ref="inertia"/> <context ref="evolution"/> <context ref="note"/> <context ref="stage"/> 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 = >k::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 = >k::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(>k::Label::new(Some("pt"))); + font_size_box.append(>k::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(>k::Label::new(Some("Font"))); + font_row.append(>k::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: >k::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: >k::Windo pub fn show_import_dialog(sender: &Sender<PreferencesInput>, window: >k::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(>k::Label::new(Some("Font"))); + font_row.append(>k::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(>k::Label::new(Some("Default Export Format"))); + export_row.append(>k::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::<>k::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() + } + } +} |