diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml
new file mode 100644
index 0000000..644cdf0
--- /dev/null
+++ b/.idea/go.imports.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 16d9e98..dfb10d9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,29 +2,6 @@
# It is not intended for manual editing.
version = 4
-[[package]]
-name = "Tofu"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "chrono",
- "dirs",
- "dotenvy",
- "eyeball-im",
- "futures-util",
- "hex",
- "matrix-sdk",
- "matrix-sdk-ui",
- "rand 0.10.3",
- "serde",
- "serde_json",
- "slint",
- "slint-build",
- "tokio",
- "tracing",
- "tracing-subscriber",
-]
-
[[package]]
name = "ab_glyph"
version = "0.2.32"
@@ -6879,6 +6856,29 @@ version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
+[[package]]
+name = "tofu"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "chrono",
+ "dirs",
+ "dotenvy",
+ "eyeball-im",
+ "futures-util",
+ "hex",
+ "matrix-sdk",
+ "matrix-sdk-ui",
+ "rand 0.10.3",
+ "serde",
+ "serde_json",
+ "slint",
+ "slint-build",
+ "tokio",
+ "tracing",
+ "tracing-subscriber",
+]
+
[[package]]
name = "tokio"
version = "1.53.2"
diff --git a/Cargo.toml b/Cargo.toml
index c8f2f25..d95632b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
-name = "Tofu"
-authors = ["Inako"]
+name = "tofu"
+authors = ["Inako", "Thoq"]
version = "0.1.0"
edition = "2024"
diff --git a/assets/translations/en_US/LC_MESSAGES/tofu.po b/assets/translations/en_US/LC_MESSAGES/tofu.po
new file mode 100644
index 0000000..0698e21
--- /dev/null
+++ b/assets/translations/en_US/LC_MESSAGES/tofu.po
@@ -0,0 +1,151 @@
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Tofu 0.1.0\n"
+"POT-Creation-Date: 2026-10-09 21:50+0000\n"
+"PO-Revision-Date: 2026-10-09 20:26+0000\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: en_US\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. Reply preview: {0} is the sender, {1} the start of the replied-to text.
+#: ui/components/message-list.slint:112
+msgctxt "ChatMessage"
+msgid "↳ {0} {1}"
+msgstr "↳ {0} {1}"
+
+#: ui/components/message-list.slint:179
+msgctxt "ChatMessage"
+msgid "(edited)"
+msgstr "(edited)"
+
+#. {} is the channel name; the leading "#" is the channel prefix.
+#: ui/components/message-list.slint:274
+msgctxt "RoomStart"
+msgid "Welcome to #{}!"
+msgstr "Welcome to #{}!"
+
+#. {} is the channel name; the leading "#" is the channel prefix.
+#: ui/components/message-list.slint:283
+msgctxt "RoomStart"
+msgid "This is the start of the #{} channel."
+msgstr "This is the start of the #{} channel."
+
+#: ui/components/message-list.slint:395
+msgctxt "MessageList"
+msgid "Loading history…"
+msgstr "Loading history…"
+
+#: ui/login.slint:35
+msgctxt "LoginScreen"
+msgid "Welcome back!"
+msgstr "Welcome back!"
+
+#: ui/login.slint:43
+msgctxt "LoginScreen"
+msgid "Sign in with your Matrix account"
+msgstr "Sign in with your Matrix account"
+
+#: ui/login.slint:54
+msgctxt "LoginScreen"
+msgid "HOMESERVER"
+msgstr "HOMESERVER"
+
+#: ui/login.slint:61
+msgctxt "LoginScreen"
+msgid "matrix.org"
+msgstr "matrix.org"
+
+#: ui/login.slint:70
+msgctxt "LoginScreen"
+msgid "USERNAME"
+msgstr "USERNAME"
+
+#: ui/login.slint:77
+msgctxt "LoginScreen"
+msgid "user (or @user:matrix.org)"
+msgstr "user (or @user:matrix.org)"
+
+#: ui/login.slint:86
+msgctxt "LoginScreen"
+msgid "PASSWORD"
+msgstr "PASSWORD"
+
+#: ui/login.slint:115
+msgctxt "LoginScreen"
+msgid "Signing in…"
+msgstr "Signing in…"
+
+#: ui/login.slint:115
+msgctxt "LoginScreen"
+msgid "Log In"
+msgstr "Log In"
+
+#: ui/main.slint:18 ui/main.slint:84
+msgctxt "MainWindow"
+msgid "Tofu"
+msgstr "Tofu"
+
+#. Heading of the sidebar in the Home view, which lists direct messages.
+#: ui/main.slint:45 ui/main.slint:124
+msgctxt "MainWindow"
+msgid "Direct Messages"
+msgstr "Direct Messages"
+
+#: ui/main.slint:118
+msgctxt "MainWindow"
+msgid "Create / join a space"
+msgstr "Create / join a space"
+
+#: ui/main.slint:133
+msgctxt "MainWindow"
+msgid "Space settings"
+msgstr "Space settings"
+
+#: ui/main.slint:159
+msgctxt "MainWindow"
+msgid "Threads"
+msgstr "Threads"
+
+#: ui/main.slint:162
+msgctxt "MainWindow"
+msgid "Pinned messages"
+msgstr "Pinned messages"
+
+#: ui/main.slint:165
+msgctxt "MainWindow"
+msgid "Search"
+msgstr "Search"
+
+#. {} is the room name (or the person's name in a DM). Keep the "#" / "@".
+#: ui/main.slint:207
+msgctxt "MainWindow"
+msgid "Message @{}"
+msgstr "Message @{}"
+
+#. {} is the room name (or the person's name in a DM). Keep the "#" / "@".
+#: ui/main.slint:207
+msgctxt "MainWindow"
+msgid "Message #{}"
+msgstr "Message #{}"
+
+#. Banner above the message box. {} is the name of the person replied to.
+#: ui/main.slint:209
+msgctxt "MainWindow"
+msgid "Editing message"
+msgstr "Editing message"
+
+#. Banner above the message box. {} is the name of the person replied to.
+#: ui/main.slint:209
+msgctxt "MainWindow"
+msgid "Replying to {}"
+msgstr "Replying to {}"
+
+#: ui/main.slint:231
+msgctxt "MainWindow"
+msgid "File uploads"
+msgstr "File uploads"
diff --git a/assets/translations/tofu.pot b/assets/translations/tofu.pot
new file mode 100644
index 0000000..64e0dcf
--- /dev/null
+++ b/assets/translations/tofu.pot
@@ -0,0 +1,151 @@
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: tofu 0.1.0\n"
+"POT-Creation-Date: 2026-10-09 21:50+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. Reply preview: {0} is the sender, {1} the start of the replied-to text.
+#: ui/components/message-list.slint:112
+msgctxt "ChatMessage"
+msgid "↳ {0} {1}"
+msgstr ""
+
+#: ui/components/message-list.slint:179
+msgctxt "ChatMessage"
+msgid "(edited)"
+msgstr ""
+
+#. {} is the channel name; the leading "#" is the channel prefix.
+#: ui/components/message-list.slint:274
+msgctxt "RoomStart"
+msgid "Welcome to #{}!"
+msgstr ""
+
+#. {} is the channel name; the leading "#" is the channel prefix.
+#: ui/components/message-list.slint:283
+msgctxt "RoomStart"
+msgid "This is the start of the #{} channel."
+msgstr ""
+
+#: ui/components/message-list.slint:395
+msgctxt "MessageList"
+msgid "Loading history…"
+msgstr ""
+
+#: ui/login.slint:35
+msgctxt "LoginScreen"
+msgid "Welcome back!"
+msgstr ""
+
+#: ui/login.slint:43
+msgctxt "LoginScreen"
+msgid "Sign in with your Matrix account"
+msgstr ""
+
+#: ui/login.slint:54
+msgctxt "LoginScreen"
+msgid "HOMESERVER"
+msgstr ""
+
+#: ui/login.slint:61
+msgctxt "LoginScreen"
+msgid "matrix.org"
+msgstr ""
+
+#: ui/login.slint:70
+msgctxt "LoginScreen"
+msgid "USERNAME"
+msgstr ""
+
+#: ui/login.slint:77
+msgctxt "LoginScreen"
+msgid "user (or @user:matrix.org)"
+msgstr ""
+
+#: ui/login.slint:86
+msgctxt "LoginScreen"
+msgid "PASSWORD"
+msgstr ""
+
+#: ui/login.slint:115
+msgctxt "LoginScreen"
+msgid "Signing in…"
+msgstr ""
+
+#: ui/login.slint:115
+msgctxt "LoginScreen"
+msgid "Log In"
+msgstr ""
+
+#: ui/main.slint:18 ui/main.slint:84
+msgctxt "MainWindow"
+msgid "Tofu"
+msgstr ""
+
+#. Heading of the sidebar in the Home view, which lists direct messages.
+#: ui/main.slint:45 ui/main.slint:124
+msgctxt "MainWindow"
+msgid "Direct Messages"
+msgstr ""
+
+#: ui/main.slint:118
+msgctxt "MainWindow"
+msgid "Create / join a space"
+msgstr ""
+
+#: ui/main.slint:133
+msgctxt "MainWindow"
+msgid "Space settings"
+msgstr ""
+
+#: ui/main.slint:159
+msgctxt "MainWindow"
+msgid "Threads"
+msgstr ""
+
+#: ui/main.slint:162
+msgctxt "MainWindow"
+msgid "Pinned messages"
+msgstr ""
+
+#: ui/main.slint:165
+msgctxt "MainWindow"
+msgid "Search"
+msgstr ""
+
+#. {} is the room name (or the person's name in a DM). Keep the "#" / "@".
+#: ui/main.slint:207
+msgctxt "MainWindow"
+msgid "Message @{}"
+msgstr ""
+
+#. {} is the room name (or the person's name in a DM). Keep the "#" / "@".
+#: ui/main.slint:207
+msgctxt "MainWindow"
+msgid "Message #{}"
+msgstr ""
+
+#. Banner above the message box. {} is the name of the person replied to.
+#: ui/main.slint:209
+msgctxt "MainWindow"
+msgid "Editing message"
+msgstr ""
+
+#. Banner above the message box. {} is the name of the person replied to.
+#: ui/main.slint:209
+msgctxt "MainWindow"
+msgid "Replying to {}"
+msgstr ""
+
+#: ui/main.slint:231
+msgctxt "MainWindow"
+msgid "File uploads"
+msgstr ""
diff --git a/build.rs b/build.rs
index ef2013c..8032b86 100644
--- a/build.rs
+++ b/build.rs
@@ -1,6 +1,8 @@
use slint_build::CompilerConfiguration;
fn main() {
- let config = CompilerConfiguration::new().with_style("fluent-dark".to_owned());
+ let config = CompilerConfiguration::new()
+ .with_style("fluent-dark".to_owned())
+ .with_bundled_translations("assets/translations");
slint_build::compile_with_config("ui/main.slint", config).expect("Failed to compile Slint UI");
-}
\ No newline at end of file
+}
diff --git a/scripts/update-translations.sh b/scripts/update-translations.sh
new file mode 100755
index 0000000..332af8d
--- /dev/null
+++ b/scripts/update-translations.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+cd "$(dirname "${BASH_SOURCE[0]}")/.."
+
+# janky aaah
+domain=$(sed -n '/^\[package\]/,/^\[/s/^name = "\(.*\)"$/\1/p' Cargo.toml)
+version=$(sed -n '/^\[package\]/,/^\[/s/^version = "\(.*\)"$/\1/p' Cargo.toml)
+[ -n "$domain" ] || { echo "error: could not find the package name in Cargo.toml" >&2; exit 1; }
+dir=assets/translations
+pot=$dir/$domain.pot
+
+for tool in slint-tr-extractor msginit msgmerge msgcat; do
+ command -v "$tool" > /dev/null || { echo "error: '$tool' is not installed, see docs/TRANSLATIONS.md" >&2; exit 1; }
+done
+
+fresh=$(mktemp)
+trap 'rm -f "$fresh"' EXIT
+
+find ui -name '*.slint' | LC_ALL=C sort | xargs slint-tr-extractor --package-name "$domain" --package-version "$version" -o "$fresh"
+
+msgcat --no-wrap "$fresh" > /dev/null || { echo "error: the extracted template is not valid, is a translator comment longer than 76 characters?" >&2; exit 1; }
+
+if [ -f "$pot" ] && diff -q <(grep -v '^"POT-Creation-Date:' "$pot") <(grep -v '^"POT-Creation-Date:' "$fresh") > /dev/null; then
+ echo "$pot is up to date"
+else
+ cp "$fresh" "$pot"
+ echo "updated $pot"
+fi
+
+for lang in "$@"; do
+ po=$dir/$lang/LC_MESSAGES/$domain.po
+ if [ -e "$po" ]; then
+ echo "$po already exists" >&2
+ else
+ mkdir -p "$(dirname "$po")"
+ msginit --no-translator --locale="$lang" --input="$pot" --output-file="$po"
+ fi
+done
+
+for po in "$dir"/*/LC_MESSAGES/"$domain".po; do
+ [ -e "$po" ] || continue
+ msgmerge --update --backup=none --quiet "$po" "$pot"
+ echo "merged into $po"
+done
diff --git a/src/bridge.rs b/src/bridge.rs
index fb81c68..fb428b3 100644
--- a/src/bridge.rs
+++ b/src/bridge.rs
@@ -30,10 +30,11 @@ pub fn toast(ui: &MainWindow, text: &str) {
fn typing_text(names: &[String]) -> String {
match names {
+ //todo: translate
[] => String::new(),
[a] => format!("{a} is typing..."),
[a, b] => format!("{a} and {b} are typing..."),
- _ => "Several people are typing...".to_owned()
+ _ => "Several people are typing...".to_owned(),
}
}
@@ -46,7 +47,7 @@ pub fn apply(ui: &MainWindow, event: Event) {
}
Event::LoginBusy(busy) => {
- ui.set_login_busy(busy)
+ ui.set_login_busy(busy);
}
Event::LoggedIn(user) => {
@@ -58,16 +59,22 @@ pub fn apply(ui: &MainWindow, event: Event) {
ui.set_screen(Screen::Main);
}
- Event::Servers { servers, home_unread, home_mentions } => {
- let items: Vec = servers.into_iter()
+ Event::Servers {
+ servers,
+ home_unread,
+ home_mentions,
+ } => {
+ let items: Vec = servers
+ .into_iter()
.map(|server| ServerItem {
color: color_for(&server.id),
initial: initial_of(&server.name).into(),
id: server.id.into(),
name: server.name.into(),
unread: server.unread,
- mentions: i32::try_from(server.mentions).unwrap_or(i32::MAX)
- }).collect();
+ mentions: i32::try_from(server.mentions).unwrap_or(i32::MAX),
+ })
+ .collect();
ui.set_servers(model_of(items));
ui.set_home_unread(home_unread);
@@ -77,13 +84,18 @@ pub fn apply(ui: &MainWindow, event: Event) {
Event::Channels { title, channels } => {
let selected: String = ui.get_selected_room_id().to_string();
- let first_openable: Option = channels.iter()
- .find(|channel| channel.kind != model::ChannelKind::Category && channel.kind != model::ChannelKind::Voice)
+ let first_openable: Option = channels
+ .iter()
+ .find(|channel| {
+ channel.kind != model::ChannelKind::Category
+ && channel.kind != model::ChannelKind::Voice
+ })
.map(|channel| channel.id.clone());
let still_here: bool = channels.iter().any(|channel| channel.id == selected);
- let items: Vec = channels.into_iter()
+ let items: Vec = channels
+ .into_iter()
.map(|channel| ChannelItem {
color: color_for(&channel.id),
initial: initial_of(&channel.name).into(),
@@ -98,27 +110,24 @@ pub fn apply(ui: &MainWindow, event: Event) {
unread: channel.unread,
mentions: i32::try_from(channel.mentions).unwrap_or(i32::MAX),
joined: channel.joined,
- is_dm: channel.is_dm
- }).collect();
+ is_dm: channel.is_dm,
+ })
+ .collect();
ui.set_sidebar_title(title.into());
ui.set_channels(model_of(items));
if !still_here {
- match first_openable {
- Some(id) => {
- ui.set_selected_room_id(id.clone().into());
- ui.invoke_select_room(id.into());
- }
-
- None => {
- ui.set_selected_room_id(SharedString::new());
- ui.set_room_open(false);
- ui.set_room_name(SharedString::new());
- ui.set_room_topic(SharedString::new());
- ui.set_messages(model_of(Vec::new()));
- ui.set_members(model_of(Vec::new()));
- }
+ if let Some(id) = first_openable {
+ ui.set_selected_room_id(id.clone().into());
+ ui.invoke_select_room(id.into());
+ } else {
+ ui.set_selected_room_id(SharedString::new());
+ ui.set_room_open(false);
+ ui.set_room_name(SharedString::new());
+ ui.set_room_topic(SharedString::new());
+ ui.set_messages(model_of(Vec::new()));
+ ui.set_members(model_of(Vec::new()));
}
}
}
@@ -145,21 +154,23 @@ pub fn apply(ui: &MainWindow, event: Event) {
}
Event::Members(rows) => {
- let items: Vec = rows.into_iter()
+ let items: Vec = rows
+ .into_iter()
.map(|member| MemberItem {
color: color_for(&member.id),
initial: initial_of(&member.name).into(),
id: member.id.into(),
name: member.name.into(),
is_header: member.is_header,
- }).collect();
+ })
+ .collect();
ui.set_members(model_of(items));
}
Event::Typing(names) => ui.set_typing_text(typing_text(&names).into()),
Event::LoadingOlder(b) => ui.set_loading_older(b),
- Event::Toast(text) => toast(ui, &text)
+ Event::Toast(text) => toast(ui, &text),
}
}
@@ -191,12 +202,15 @@ fn message_item(message: model::MessageRow) -> MessageItem {
reply_sender: reply_sender.into(),
reply_body: reply_body.into(),
reactions: model_of(
- message.reactions.into_iter()
+ message
+ .reactions
+ .into_iter()
.map(|reaction| ReactionItem {
key: reaction.key.into(),
count: i32::try_from(reaction.count).unwrap_or(i32::MAX),
mine: reaction.mine,
- }).collect(),
+ })
+ .collect(),
),
}
}
@@ -210,11 +224,14 @@ pub fn wire(ui: &MainWindow, tx: &UnboundedSender) {
let tx = tx.clone();
move |homeserver, username, password| {
- send(&tx, Command::Login {
- homeserver: homeserver.into(),
- username: username.into(),
- password: password.into(),
- });
+ send(
+ &tx,
+ Command::Login {
+ homeserver: homeserver.into(),
+ username: username.into(),
+ password: password.into(),
+ },
+ );
}
});
@@ -225,7 +242,12 @@ pub fn wire(ui: &MainWindow, tx: &UnboundedSender) {
ui.on_select_server({
let tx = tx.clone();
- move |id| send(&tx, Command::SelectServer(Some(id.to_string()).filter(|s| !s.is_empty())))
+ move |id| {
+ send(
+ &tx,
+ Command::SelectServer(Some(id.to_string()).filter(|s| !s.is_empty())),
+ );
+ }
});
ui.on_select_room({
@@ -236,16 +258,27 @@ pub fn wire(ui: &MainWindow, tx: &UnboundedSender) {
ui.on_send_message({
let tx = tx.clone();
move |reply_to, text| {
- send(&tx, Command::Send {
- reply_to: Some(reply_to.to_string()).filter(|s| !s.is_empty()),
- text: text.into(),
- });
+ send(
+ &tx,
+ Command::Send {
+ reply_to: Some(reply_to.to_string()).filter(|s| !s.is_empty()),
+ text: text.into(),
+ },
+ );
}
});
ui.on_edit_message({
let tx = tx.clone();
- move |id, text| send(&tx, Command::Edit { id: id.into(), text: text.into() })
+ move |id, text| {
+ send(
+ &tx,
+ Command::Edit {
+ id: id.into(),
+ text: text.into(),
+ },
+ );
+ }
});
ui.on_delete_message({
@@ -255,7 +288,15 @@ pub fn wire(ui: &MainWindow, tx: &UnboundedSender) {
ui.on_toggle_reaction({
let tx = tx.clone();
- move |id, key| send(&tx, Command::ToggleReaction { id: id.into(), key: key.into() })
+ move |id, key| {
+ send(
+ &tx,
+ Command::ToggleReaction {
+ id: id.into(),
+ key: key.into(),
+ },
+ );
+ }
});
ui.on_load_older({
diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs
new file mode 100644
index 0000000..e69de29
diff --git a/src/main.rs b/src/main.rs
index e89c9c2..a7e5503 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,6 +3,7 @@
mod backend;
mod model;
pub mod bridge;
+mod helpers;
use std::sync::Arc;
use slint::Weak;
diff --git a/ui/components/message-list.slint b/ui/components/message-list.slint
index 706cbf6..2bfbce0 100644
--- a/ui/components/message-list.slint
+++ b/ui/components/message-list.slint
@@ -108,7 +108,8 @@ component ChatMessage inherits Rectangle {
x: Theme.gutter;
width: parent.width - Theme.gutter - 16px;
height: parent.height;
- text: "↳ \{root.m.reply-sender} \{root.m.reply-body}";
+ // Reply preview: {0} is the sender, {1} the start of the replied-to text.
+ text: @tr("↳ {0} {1}", root.m.reply-sender, root.m.reply-body);
color: Theme.text-muted;
font-size: 13px;
vertical-alignment: center;
@@ -175,7 +176,7 @@ component ChatMessage inherits Rectangle {
}
if root.m.edited: Text {
- text: "(edited)";
+ text: @tr("(edited)");
color: Theme.text-faint;
font-size: 11px;
}
@@ -269,7 +270,8 @@ component RoomStart inherits Rectangle {
padding-top: 32px;
spacing: 6px;
Text {
- text: "Welcome to #\{root.room-name}!";
+ // {} is the channel name; the leading "#" is the channel prefix.
+ text: @tr("Welcome to #{}!", root.room-name);
color: Theme.text-header;
font-size: 28px;
font-weight: 700;
@@ -277,7 +279,8 @@ component RoomStart inherits Rectangle {
}
Text {
- text: "This is the start of the #\{root.room-name} channel.";
+ // {} is the channel name; the leading "#" is the channel prefix.
+ text: @tr("This is the start of the #{} channel.", root.room-name);
color: Theme.text-muted;
font-size: 15px;
wrap: word-wrap;
@@ -389,7 +392,7 @@ export component MessageList inherits Rectangle {
border-radius: 12px;
background: Theme.popup-bg;
Text {
- text: "Loading history…";
+ text: @tr("Loading history…");
color: Theme.text-muted;
font-size: 12px;
horizontal-alignment: center;
diff --git a/ui/login.slint b/ui/login.slint
index 910d10f..3e0bdfb 100644
--- a/ui/login.slint
+++ b/ui/login.slint
@@ -32,7 +32,7 @@ export component LoginScreen inherits Rectangle {
spacing: 6px;
Text {
- text: "Welcome back!";
+ text: @tr("Welcome back!");
color: Theme.text-header;
font-size: 24px;
font-weight: 700;
@@ -40,7 +40,7 @@ export component LoginScreen inherits Rectangle {
}
Text {
- text: "Sign in with your Matrix account";
+ text: @tr("Sign in with your Matrix account");
color: Theme.text-muted;
font-size: 15px;
horizontal-alignment: center;
@@ -51,14 +51,14 @@ export component LoginScreen inherits Rectangle {
}
Text {
- text: "HOMESERVER";
+ text: @tr("HOMESERVER");
color: Theme.text-muted;
font-size: 12px;
font-weight: 700;
}
TextField {
- placeholder: "matrix.org";
+ placeholder: @tr("matrix.org");
text <=> root.homeserver;
}
@@ -67,14 +67,14 @@ export component LoginScreen inherits Rectangle {
}
Text {
- text: "USERNAME";
+ text: @tr("USERNAME");
color: Theme.text-muted;
font-size: 12px;
font-weight: 700;
}
TextField {
- placeholder: "user (or @user:matrix.org)";
+ placeholder: @tr("user (or @user:matrix.org)");
text <=> root.username;
}
@@ -83,7 +83,7 @@ export component LoginScreen inherits Rectangle {
}
Text {
- text: "PASSWORD";
+ text: @tr("PASSWORD");
color: Theme.text-muted;
font-size: 12px;
font-weight: 700;
@@ -112,7 +112,7 @@ export component LoginScreen inherits Rectangle {
border-radius: 4px;
background: root.busy ? Theme.accent-hover : (login-touch.has-hover ? Theme.accent-hover : Theme.accent);
Text {
- text: root.busy ? "Signing in…" : "Log In";
+ text: root.busy ? @tr("Signing in…") : @tr("Log In");
color: white;
font-size: 16px;
font-weight: 600;
diff --git a/ui/main.slint b/ui/main.slint
index 80793bb..ca14e83 100644
--- a/ui/main.slint
+++ b/ui/main.slint
@@ -15,7 +15,7 @@ import { MemberList } from "components/member-list.slint";
export { Screen, ServerItem, ChannelItem, MessageItem, MemberItem, ChannelKind, MessageKind, ReactionItem } from "types.slint";
export component MainWindow inherits Window {
- title: "Tofu";
+ title: @tr("Tofu");
min-width: 900px;
min-height: 520px;
preferred-width: 1280px;
@@ -42,7 +42,7 @@ export component MainWindow inherits Window {
in property <[MessageItem]> messages;
in property <[MemberItem]> members;
- in property sidebar-title: "Direct Messages";
+ in property sidebar-title: @tr("Direct Messages");
in property room-name;
in property room-topic;
in property room-is-dm;
@@ -81,7 +81,7 @@ export component MainWindow inherits Window {
if root.screen == Screen.loading: Rectangle {
background: Theme.rail-bg;
Text {
- text: "Tofu";
+ text: @tr("Tofu");
color: Theme.text-muted;
font-size: 32px;
font-weight: 700;
@@ -115,12 +115,13 @@ export component MainWindow inherits Window {
root.select-server(id);
}
add-server => {
- root.not-implemented("Create / join a space");
+ root.not-implemented(@tr("Create / join a space"));
}
}
ChannelList {
- title: root.sidebar-title;
+ // Heading of the sidebar in the Home view, which lists direct messages.
+ title: root.selected-server-id == "" ? @tr("Direct Messages") : root.sidebar-title;
is-home: root.selected-server-id == "";
channels: root.channels;
selected-id: root.selected-room-id;
@@ -129,7 +130,7 @@ export component MainWindow inherits Window {
root.select-room(id);
}
open-server-menu => {
- root.not-implemented("Space settings");
+ root.not-implemented(@tr("Space settings"));
}
}
}
@@ -147,6 +148,7 @@ export component MainWindow inherits Window {
// Right: chat
VerticalLayout {
+ width: root.width - Theme.rail-width - Theme.sidebar-width;
ChatHeader {
room-name: root.room-name;
topic: root.room-topic;
@@ -154,18 +156,21 @@ export component MainWindow inherits Window {
encrypted: root.room-encrypted;
show-members <=> root.show-members;
open-threads => {
- root.not-implemented("Threads");
+ root.not-implemented(@tr("Threads"));
}
open-pins => {
- root.not-implemented("Pinned messages");
+ root.not-implemented(@tr("Pinned messages"));
}
open-search => {
- root.not-implemented("Search");
+ root.not-implemented(@tr("Search"));
}
}
- HorizontalLayout {
+ Rectangle {
+ vertical-stretch: 1;
VerticalLayout {
+ width: parent.width - (root.show-members ? Theme.member-width : 0px);
+ height: parent.height;
MessageList {
messages: root.messages;
room-name: root.room-name;
@@ -198,8 +203,8 @@ export component MainWindow inherits Window {
text-to-set: root.composer-text;
text-nonce: root.composer-text-nonce;
focus-nonce: root.composer-focus-nonce;
- placeholder: root.room-is-dm ? "Message @\{root.room-name}" : "Message #\{root.room-name}";
- banner: root.editing-id != "" ? "Editing message" : (root.reply-to-id != "" ? "Replying to \{root.reply-to-name}" : "");
+ placeholder: root.room-is-dm ? @tr("Message @{}", root.room-name) : @tr("Message #{}", root.room-name);
+ banner: root.editing-id != "" ? @tr("Editing message") : (root.reply-to-id != "" ? @tr("Replying to {}", root.reply-to-name) : "");
editing: root.editing-id != "";
typing-text: root.typing-text;
submit(text) => {
@@ -221,19 +226,20 @@ export component MainWindow inherits Window {
root.typing();
}
attach => {
- root.not-implemented("File uploads");
+ root.not-implemented(@tr("File uploads"));
}
}
}
if root.show-members: MemberList {
+ x: parent.width - self.width;
+ height: parent.height;
members: root.members;
}
}
}
}
- // Transient banner for errors and "coming soon" notices.
if root.toast != "": Rectangle {
width: min(480px, parent.width - 32px);
height: 40px;