diff --git a/Cargo.lock b/Cargo.lock
index ba1f581..25f0083 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -97,6 +97,12 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
+[[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
[[package]]
name = "arg_enum_proc_macro"
version = "0.3.4"
@@ -403,9 +409,11 @@ dependencies = [
"common",
"global",
"gpui",
+ "i18n",
"log",
"nostr-sdk",
"reqwest 0.12.22",
+ "rust-i18n",
"smol",
"tempfile",
]
@@ -477,6 +485,15 @@ dependencies = [
"windows-targets 0.52.6",
]
+[[package]]
+name = "base62"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10e52a7bcb1d6beebee21fb5053af9e3cbb7a7ed1a4909e534040e676437ab1f"
+dependencies = [
+ "rustversion",
+]
+
[[package]]
name = "base64"
version = "0.22.1"
@@ -928,12 +945,14 @@ dependencies = [
"fuzzy-matcher",
"global",
"gpui",
+ "i18n",
"identity",
"itertools 0.13.0",
"log",
"nostr",
"nostr-sdk",
"oneshot",
+ "rust-i18n",
"settings",
"smallvec",
"smol",
@@ -982,8 +1001,10 @@ dependencies = [
"anyhow",
"global",
"gpui",
+ "i18n",
"log",
"nostr-sdk",
+ "rust-i18n",
"smallvec",
]
@@ -1181,6 +1202,7 @@ dependencies = [
"futures",
"global",
"gpui",
+ "i18n",
"identity",
"itertools 0.13.0",
"log",
@@ -1190,6 +1212,7 @@ dependencies = [
"oneshot",
"reqwest_client",
"rust-embed",
+ "rust-i18n",
"serde",
"serde_json",
"settings",
@@ -2261,6 +2284,17 @@ dependencies = [
"regex-syntax",
]
+[[package]]
+name = "globwalk"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
+dependencies = [
+ "bitflags 1.3.2",
+ "ignore",
+ "walkdir",
+]
+
[[package]]
name = "gloo-timers"
version = "0.3.0"
@@ -2743,6 +2777,13 @@ dependencies = [
"windows-registry 0.5.3",
]
+[[package]]
+name = "i18n"
+version = "1.0.0"
+dependencies = [
+ "rust-i18n",
+]
+
[[package]]
name = "iana-time-zone"
version = "0.1.63"
@@ -2862,10 +2903,12 @@ dependencies = [
"common",
"global",
"gpui",
+ "i18n",
"log",
"nostr-connect",
"nostr-sdk",
"oneshot",
+ "rust-i18n",
"settings",
"smallvec",
"ui",
@@ -2892,6 +2935,22 @@ dependencies = [
"icu_properties",
]
+[[package]]
+name = "ignore"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
[[package]]
name = "image"
version = "0.25.6"
@@ -3048,6 +3107,15 @@ dependencies = [
"once_cell",
]
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itertools"
version = "0.12.1"
@@ -3607,6 +3675,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
+[[package]]
+name = "normpath"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "nostr"
version = "0.42.1"
@@ -4964,6 +5041,60 @@ dependencies = [
"walkdir",
]
+[[package]]
+name = "rust-i18n"
+version = "3.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda2551fdfaf6cc5ee283adc15e157047b92ae6535cf80f6d4962d05717dc332"
+dependencies = [
+ "globwalk",
+ "once_cell",
+ "regex",
+ "rust-i18n-macro",
+ "rust-i18n-support",
+ "smallvec",
+]
+
+[[package]]
+name = "rust-i18n-macro"
+version = "3.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22baf7d7f56656d23ebe24f6bb57a5d40d2bce2a5f1c503e692b5b2fa450f965"
+dependencies = [
+ "glob",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "rust-i18n-support",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "rust-i18n-support"
+version = "3.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940ed4f52bba4c0152056d771e563b7133ad9607d4384af016a134b58d758f19"
+dependencies = [
+ "arc-swap",
+ "base62",
+ "globwalk",
+ "itertools 0.11.0",
+ "lazy_static",
+ "normpath",
+ "once_cell",
+ "proc-macro2",
+ "regex",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "siphasher",
+ "toml",
+ "triomphe",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.25"
@@ -5453,6 +5584,19 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_yaml"
+version = "0.9.34+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
[[package]]
name = "settings"
version = "1.0.0"
@@ -5460,8 +5604,10 @@ dependencies = [
"anyhow",
"global",
"gpui",
+ "i18n",
"log",
"nostr-sdk",
+ "rust-i18n",
"serde",
"serde_json",
"smallvec",
@@ -6358,6 +6504,17 @@ dependencies = [
"tracing-log",
]
+[[package]]
+name = "triomphe"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85"
+dependencies = [
+ "arc-swap",
+ "serde",
+ "stable_deref_trait",
+]
+
[[package]]
name = "try-lock"
version = "0.2.5"
@@ -6436,6 +6593,7 @@ dependencies = [
"common",
"emojis",
"gpui",
+ "i18n",
"image",
"itertools 0.13.0",
"linkify",
@@ -6443,6 +6601,7 @@ dependencies = [
"once_cell",
"paste",
"regex",
+ "rust-i18n",
"serde",
"serde_json",
"smallvec",
@@ -6555,6 +6714,12 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
+
[[package]]
name = "untrusted"
version = "0.9.0"
diff --git a/Cargo.toml b/Cargo.toml
index 5a6e0c5..001ffe1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,51 +1,57 @@
-[workspace]
-resolver = "2"
-members = ["crates/*"]
-default-members = ["crates/coop"]
-
-[workspace.package]
-version = "1.0.0"
-edition = "2021"
-publish = false
-
-[workspace.dependencies]
-coop = { path = "crates/*" }
-
-# GPUI
-gpui = { git = "https://github.com/zed-industries/zed" }
-reqwest_client = { git = "https://github.com/zed-industries/zed" }
-
-# Nostr
-nostr = { git = "https://github.com/rust-nostr/nostr" }
-nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = [
- "lmdb",
- "nip96",
- "nip59",
- "nip49",
- "nip44",
-] }
-nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
-
-# Others
-reqwest = { version = "0.12", features = ["multipart", "stream", "json"] }
-emojis = "0.6.4"
-smol = "2"
-futures = "0.3"
-oneshot = "0.1.10"
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-dirs = "5.0"
-itertools = "0.13.0"
-chrono = "0.4.38"
-tracing = "0.1.40"
-anyhow = "1.0.44"
-smallvec = "1.14.0"
-rust-embed = "8.5.0"
-log = "0.4"
-
-[profile.release]
-strip = true
-opt-level = "z"
-lto = true
-codegen-units = 1
-panic = "abort"
+[workspace]
+resolver = "2"
+members = ["crates/*"]
+default-members = ["crates/coop"]
+
+[workspace.package]
+version = "1.0.0"
+edition = "2021"
+publish = false
+
+[workspace.metadata.i18n]
+available-locales = ["en", "zh-CN", "zh-TW", "ru", "vi", "ja", "es", "pt", "ko"]
+default-locale = "en"
+load-path = "locales"
+
+[workspace.dependencies]
+i18n = { path = "crates/i18n" }
+
+# GPUI
+gpui = { git = "https://github.com/zed-industries/zed" }
+reqwest_client = { git = "https://github.com/zed-industries/zed" }
+
+# Nostr
+nostr = { git = "https://github.com/rust-nostr/nostr" }
+nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
+nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = [
+ "lmdb",
+ "nip96",
+ "nip59",
+ "nip49",
+ "nip44",
+] }
+
+# Others
+anyhow = "1.0.44"
+chrono = "0.4.38"
+dirs = "5.0"
+emojis = "0.6.4"
+futures = "0.3"
+itertools = "0.13.0"
+log = "0.4"
+oneshot = "0.1.10"
+reqwest = { version = "0.12", features = ["multipart", "stream", "json"] }
+rust-embed = "8.5.0"
+rust-i18n = "3"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+smallvec = "1.14.0"
+smol = "2"
+tracing = "0.1.40"
+
+[profile.release]
+strip = true
+opt-level = "z"
+lto = true
+codegen-units = 1
+panic = "abort"
diff --git a/assets/icons/language.svg b/assets/icons/language.svg
new file mode 100644
index 0000000..23a400e
--- /dev/null
+++ b/assets/icons/language.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml
index 0337536..8b567e7 100644
--- a/crates/auto_update/Cargo.toml
+++ b/crates/auto_update/Cargo.toml
@@ -1,18 +1,20 @@
-[package]
-name = "auto_update"
-version.workspace = true
-edition.workspace = true
-publish.workspace = true
-
-[dependencies]
-common = { path = "../common" }
-global = { path = "../global" }
-
-gpui.workspace = true
-nostr-sdk.workspace = true
-anyhow.workspace = true
-smol.workspace = true
-reqwest.workspace = true
-log.workspace = true
-
-tempfile = "3.19.1"
+[package]
+name = "auto_update"
+version.workspace = true
+edition.workspace = true
+publish.workspace = true
+
+[dependencies]
+common = { path = "../common" }
+global = { path = "../global" }
+
+rust-i18n.workspace = true
+i18n.workspace = true
+gpui.workspace = true
+nostr-sdk.workspace = true
+anyhow.workspace = true
+smol.workspace = true
+reqwest.workspace = true
+log.workspace = true
+
+tempfile = "3.19.1"
diff --git a/crates/auto_update/src/lib.rs b/crates/auto_update/src/lib.rs
index 0bf838b..430b0be 100644
--- a/crates/auto_update/src/lib.rs
+++ b/crates/auto_update/src/lib.rs
@@ -12,6 +12,8 @@ use smol::io::AsyncWriteExt;
use smol::process::Command;
use tempfile::TempDir;
+i18n::init!();
+
struct GlobalAutoUpdate(Entity);
impl Global for GlobalAutoUpdate {}
diff --git a/crates/chats/Cargo.toml b/crates/chats/Cargo.toml
index f70ef63..a779d2f 100644
--- a/crates/chats/Cargo.toml
+++ b/crates/chats/Cargo.toml
@@ -1,24 +1,26 @@
-[package]
-name = "chats"
-version.workspace = true
-edition.workspace = true
-publish.workspace = true
-
-[dependencies]
-common = { path = "../common" }
-global = { path = "../global" }
-identity = { path = "../identity" }
-settings = { path = "../settings" }
-
-gpui.workspace = true
-nostr.workspace = true
-nostr-sdk.workspace = true
-anyhow.workspace = true
-itertools.workspace = true
-chrono.workspace = true
-smallvec.workspace = true
-smol.workspace = true
-oneshot.workspace = true
-log.workspace = true
-
-fuzzy-matcher = "0.3.7"
+[package]
+name = "chats"
+version.workspace = true
+edition.workspace = true
+publish.workspace = true
+
+[dependencies]
+common = { path = "../common" }
+global = { path = "../global" }
+identity = { path = "../identity" }
+settings = { path = "../settings" }
+
+rust-i18n.workspace = true
+i18n.workspace = true
+gpui.workspace = true
+nostr.workspace = true
+nostr-sdk.workspace = true
+anyhow.workspace = true
+itertools.workspace = true
+chrono.workspace = true
+smallvec.workspace = true
+smol.workspace = true
+oneshot.workspace = true
+log.workspace = true
+
+fuzzy-matcher = "0.3.7"
diff --git a/crates/chats/src/lib.rs b/crates/chats/src/lib.rs
index f82c1ec..4afa570 100644
--- a/crates/chats/src/lib.rs
+++ b/crates/chats/src/lib.rs
@@ -22,6 +22,8 @@ pub mod room;
mod constants;
+i18n::init!();
+
pub fn init(cx: &mut App) {
ChatRegistry::set_global(cx.new(ChatRegistry::new), cx);
}
diff --git a/crates/client_keys/Cargo.toml b/crates/client_keys/Cargo.toml
index 8d6f50e..ac797f4 100644
--- a/crates/client_keys/Cargo.toml
+++ b/crates/client_keys/Cargo.toml
@@ -1,14 +1,16 @@
-[package]
-name = "client_keys"
-version.workspace = true
-edition.workspace = true
-publish.workspace = true
-
-[dependencies]
-global = { path = "../global" }
-
-nostr-sdk.workspace = true
-gpui.workspace = true
-anyhow.workspace = true
-log.workspace = true
-smallvec.workspace = true
+[package]
+name = "client_keys"
+version.workspace = true
+edition.workspace = true
+publish.workspace = true
+
+[dependencies]
+global = { path = "../global" }
+
+rust-i18n.workspace = true
+i18n.workspace = true
+nostr-sdk.workspace = true
+gpui.workspace = true
+anyhow.workspace = true
+log.workspace = true
+smallvec.workspace = true
diff --git a/crates/client_keys/src/lib.rs b/crates/client_keys/src/lib.rs
index 7cdd6f5..4feb7ff 100644
--- a/crates/client_keys/src/lib.rs
+++ b/crates/client_keys/src/lib.rs
@@ -4,6 +4,8 @@ use gpui::{App, AppContext, Context, Entity, Global, Subscription, Window};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
+i18n::init!();
+
pub fn init(cx: &mut App) {
ClientKeys::set_global(cx.new(ClientKeys::new), cx);
}
diff --git a/crates/coop/Cargo.toml b/crates/coop/Cargo.toml
index 7536e7f..bb39831 100644
--- a/crates/coop/Cargo.toml
+++ b/crates/coop/Cargo.toml
@@ -1,41 +1,43 @@
-[package]
-name = "coop"
-version.workspace = true
-edition.workspace = true
-publish.workspace = true
-
-[[bin]]
-name = "coop"
-path = "src/main.rs"
-
-[dependencies]
-ui = { path = "../ui" }
-identity = { path = "../identity" }
-theme = { path = "../theme" }
-common = { path = "../common" }
-global = { path = "../global" }
-chats = { path = "../chats" }
-settings = { path = "../settings" }
-client_keys = { path = "../client_keys" }
-auto_update = { path = "../auto_update" }
-
-gpui.workspace = true
-reqwest_client.workspace = true
-
-nostr-connect.workspace = true
-nostr-sdk.workspace = true
-nostr.workspace = true
-
-anyhow.workspace = true
-serde.workspace = true
-serde_json.workspace = true
-itertools.workspace = true
-dirs.workspace = true
-rust-embed.workspace = true
-log.workspace = true
-smallvec.workspace = true
-smol.workspace = true
-futures.workspace = true
-oneshot.workspace = true
-
-tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
+[package]
+name = "coop"
+version.workspace = true
+edition.workspace = true
+publish.workspace = true
+
+[[bin]]
+name = "coop"
+path = "src/main.rs"
+
+[dependencies]
+ui = { path = "../ui" }
+identity = { path = "../identity" }
+theme = { path = "../theme" }
+common = { path = "../common" }
+global = { path = "../global" }
+chats = { path = "../chats" }
+settings = { path = "../settings" }
+client_keys = { path = "../client_keys" }
+auto_update = { path = "../auto_update" }
+
+rust-i18n.workspace = true
+i18n.workspace = true
+gpui.workspace = true
+reqwest_client.workspace = true
+
+nostr-connect.workspace = true
+nostr-sdk.workspace = true
+nostr.workspace = true
+
+anyhow.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+itertools.workspace = true
+dirs.workspace = true
+rust-embed.workspace = true
+log.workspace = true
+smallvec.workspace = true
+smol.workspace = true
+futures.workspace = true
+oneshot.workspace = true
+
+tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs
index cbc4cba..67f0dc8 100644
--- a/crates/coop/src/chatspace.rs
+++ b/crates/coop/src/chatspace.rs
@@ -8,8 +8,9 @@ use global::shared_state;
use gpui::prelude::FluentBuilder;
use gpui::{
div, px, relative, Action, App, AppContext, Axis, Context, Entity, IntoElement, ParentElement,
- Render, Styled, Subscription, Task, Window,
+ Render, SharedString, Styled, Subscription, Task, Window,
};
+use i18n::t;
use identity::Identity;
use nostr_connect::prelude::*;
use serde::Deserialize;
@@ -54,6 +55,10 @@ pub enum ModalKind {
SetupRelay,
}
+#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
+#[action(namespace = story, no_json)]
+pub struct SelectLocale(SharedString);
+
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
#[action(namespace = modal, no_json)]
pub struct ToggleModal {
@@ -91,17 +96,14 @@ impl ChatSpace {
|_this: &mut Self, state, window, cx| {
if !state.read(cx).has_keys() {
window.open_modal(cx, |this, _window, cx| {
- const DESCRIPTION: &str =
- "Allow Coop to read the client keys stored in Keychain to continue";
-
this.overlay_closable(false)
.show_close(false)
.keyboard(false)
.confirm()
.button_props(
ModalButtonProps::default()
- .cancel_text("Create New Keys")
- .ok_text("Allow"),
+ .cancel_text(t!("chatspace.create_new_keys"))
+ .ok_text(t!("common.allow")),
)
.child(
div()
@@ -119,9 +121,13 @@ impl ChatSpace {
div()
.font_semibold()
.text_color(cx.theme().text_muted)
- .child("Warning"),
+ .child(SharedString::new(t!("chatspace.warning"))),
)
- .child(div().line_height(relative(1.4)).child(DESCRIPTION)),
+ .child(div().line_height(relative(1.4)).child(
+ SharedString::new(t!(
+ "chatspace.allow_keychain_access"
+ )),
+ )),
)
.on_cancel(|_, _window, cx| {
ClientKeys::global(cx).update(cx, |this, cx| {
@@ -182,7 +188,7 @@ impl ChatSpace {
});
} else {
window.push_notification(
- "Failed to open room. Please try again later.",
+ SharedString::new(t!("chatspace.failed_to_open_room")),
cx,
);
}
@@ -264,7 +270,7 @@ impl ChatSpace {
window.open_modal(cx, move |modal, _, _| {
modal
- .title("Preferences")
+ .title(SharedString::new(t!("chatspace.preferences_title")))
.width(px(DEFAULT_MODAL_WIDTH))
.child(settings.clone())
});
@@ -349,7 +355,7 @@ impl Render for ChatSpace {
.px_2()
.child(
Button::new("appearance")
- .tooltip("Change the app's appearance")
+ .tooltip(t!("chatspace.appearance_tooltip"))
.small()
.ghost()
.map(|this| {
@@ -365,7 +371,7 @@ impl Render for ChatSpace {
)
.child(
Button::new("preferences")
- .tooltip("Open Preferences")
+ .tooltip(t!("chatspace.preferences_tooltip"))
.small()
.ghost()
.icon(IconName::Settings)
@@ -375,7 +381,7 @@ impl Render for ChatSpace {
)
.child(
Button::new("logout")
- .tooltip("Log Out")
+ .tooltip(t!("common.logout"))
.small()
.ghost()
.icon(IconName::Logout)
diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs
index 63e2aa1..eb7f5aa 100644
--- a/crates/coop/src/main.rs
+++ b/crates/coop/src/main.rs
@@ -23,6 +23,8 @@ pub(crate) mod asset;
pub(crate) mod chatspace;
pub(crate) mod views;
+i18n::init!();
+
actions!(coop, [Quit]);
fn main() {
diff --git a/crates/coop/src/views/chat.rs b/crates/coop/src/views/chat.rs
index 313caa0..9382c14 100644
--- a/crates/coop/src/views/chat.rs
+++ b/crates/coop/src/views/chat.rs
@@ -16,6 +16,7 @@ use gpui::{
PathPromptOptions, Render, RetainAllImageCache, SharedString, StatefulInteractiveElement,
Styled, StyledImage, Subscription, Window,
};
+use i18n::t;
use identity::Identity;
use itertools::Itertools;
use nostr_sdk::prelude::*;
@@ -73,10 +74,7 @@ impl Chat {
let messages = cx.new(|_| {
let message = Message::builder()
- .content(
- "This conversation is private. Only members can see each other's messages."
- .into(),
- )
+ .content(t!("chat.private_conversation_notice").into())
.build_rc()
.unwrap();
@@ -85,7 +83,7 @@ impl Chat {
let input = cx.new(|cx| {
InputState::new(window, cx)
- .placeholder("Message...")
+ .placeholder(t!("chat.placeholder"))
.multi_line()
.prevent_new_line_on_enter()
.rows(1)
@@ -103,7 +101,10 @@ impl Chat {
move |this: &mut Self, input, event, window, cx| {
if let InputEvent::PressEnter { .. } = event {
if input.read(cx).value().trim().is_empty() {
- window.push_notification("Cannot send an empty message", cx);
+ window.push_notification(
+ Notification::new(t!("chat.empty_message_error")),
+ cx,
+ );
} else {
this.send_message(window, cx);
}
@@ -498,7 +499,7 @@ impl Chat {
.gap_1()
.text_xs()
.text_color(cx.theme().text_muted)
- .child("Replying to:")
+ .child(SharedString::new(t!("chat.replying_to_label")))
.child(
div()
.text_color(cx.theme().text_accent)
@@ -687,12 +688,12 @@ impl Chat {
.text_xs()
.italic()
.child(Icon::new(IconName::Info).small())
- .child("Failed to send message. Click to see details.")
+ .child(SharedString::new(t!("chat.send_fail")))
.on_click(move |_, window, cx| {
let errors = errors.clone();
window.open_modal(cx, move |this, _window, cx| {
- this.title("Error Logs")
+ this.title(SharedString::new(t!("chat.logs_title")))
.child(message_errors(errors.clone(), cx))
});
}),
@@ -705,7 +706,7 @@ impl Chat {
vec![
Button::new("reply")
.icon(IconName::Reply)
- .tooltip("Reply")
+ .tooltip(t!("chat.reply_button"))
.small()
.ghost()
.on_click({
@@ -716,7 +717,7 @@ impl Chat {
}),
Button::new("copy")
.icon(IconName::Copy)
- .tooltip("Copy Message")
+ .tooltip(t!("chat.copy_message_button"))
.small()
.ghost()
.on_click({
@@ -779,12 +780,12 @@ impl Panel for Chat {
let button = Button::new("subject")
.icon(IconName::EditFill)
- .tooltip("Change Subject")
+ .tooltip(t!("chat.change_subject_button"))
.on_click(move |_, window, cx| {
let subject = subject::init(id, subject.clone(), window, cx);
window.open_modal(cx, move |this, _window, _cx| {
- this.title("Change the subject of the conversation")
+ this.title(SharedString::new(t!("chat.change_subject_modal_title")))
.child(subject.clone())
});
});
@@ -896,7 +897,7 @@ fn message_errors(errors: Vec, cx: &App) -> Div {
.items_baseline()
.gap_1()
.text_color(cx.theme().text_muted)
- .child("Send to:")
+ .child(SharedString::new(t!("chat.send_to_label")))
.child(error.profile.render_name()),
)
.child(error.message)
diff --git a/crates/coop/src/views/compose.rs b/crates/coop/src/views/compose.rs
index 595c3b4..d4e2efb 100644
--- a/crates/coop/src/views/compose.rs
+++ b/crates/coop/src/views/compose.rs
@@ -13,16 +13,19 @@ use gpui::{
InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled, Subscription, Task, TextAlign, Window,
};
+use i18n::t;
use itertools::Itertools;
use nostr_sdk::prelude::*;
use settings::AppSettings;
use smallvec::{smallvec, SmallVec};
use smol::Timer;
use theme::ActiveTheme;
-use ui::button::{Button, ButtonVariants};
-use ui::input::{InputEvent, InputState, TextInput};
-use ui::notification::Notification;
-use ui::{ContextModal, Disableable, Icon, IconName, Sizable, StyledExt};
+use ui::{
+ button::{Button, ButtonVariants},
+ input::{InputEvent, InputState, TextInput},
+ notification::Notification,
+ ContextModal, Disableable, Icon, IconName, Sizable, StyledExt,
+};
pub fn init(window: &mut Window, cx: &mut App) -> Entity {
cx.new(|cx| Compose::new(window, cx))
@@ -72,10 +75,10 @@ pub struct Compose {
impl Compose {
pub fn new(window: &mut Window, cx: &mut Context<'_, Self>) -> Self {
let user_input =
- cx.new(|cx| InputState::new(window, cx).placeholder("npub or nprofile..."));
+ cx.new(|cx| InputState::new(window, cx).placeholder(t!("compose.placeholder_npub")));
let title_input =
- cx.new(|cx| InputState::new(window, cx).placeholder("Family...(Optional)"));
+ cx.new(|cx| InputState::new(window, cx).placeholder(t!("compose.placeholder_title")));
let error_message = cx.new(|_| None);
let mut subscriptions = smallvec![];
@@ -113,10 +116,7 @@ impl Compose {
}
Err(e) => {
cx.update(|window, cx| {
- window.push_notification(
- Notification::error(e.to_string()).title("Contacts"),
- cx,
- );
+ window.push_notification(Notification::error(e.to_string()), cx);
})
.ok();
}
@@ -139,7 +139,7 @@ impl Compose {
let public_keys: Vec = self.selected(cx);
if public_keys.is_empty() {
- self.set_error(Some("You need to add at least 1 receiver".into()), cx);
+ self.set_error(Some(t!("compose.receiver_required").into()), cx);
return;
}
@@ -171,28 +171,30 @@ impl Compose {
Ok(room)
});
- cx.spawn_in(window, async move |this, cx| match event.await {
- Ok(room) => {
- cx.update(|window, cx| {
- this.update(cx, |this, cx| {
- this.set_submitting(false, cx);
+ cx.spawn_in(window, async move |this, cx| {
+ match event.await {
+ Ok(room) => {
+ cx.update(|window, cx| {
+ this.update(cx, |this, cx| {
+ this.set_submitting(false, cx);
+ })
+ .ok();
+
+ ChatRegistry::global(cx).update(cx, |this, cx| {
+ this.push_room(cx.new(|_| room), cx);
+ });
+
+ window.close_modal(cx);
})
.ok();
-
- ChatRegistry::global(cx).update(cx, |this, cx| {
- this.push_room(cx.new(|_| room), cx);
- });
-
- window.close_modal(cx);
- })
- .ok();
- }
- Err(e) => {
- this.update(cx, |this, cx| {
- this.set_error(Some(e.to_string().into()), cx);
- })
- .ok();
- }
+ }
+ Err(e) => {
+ this.update(cx, |this, cx| {
+ this.set_error(Some(e.to_string().into()), cx);
+ })
+ .ok();
+ }
+ };
})
.detach();
}
@@ -211,6 +213,11 @@ impl Compose {
{
self.contacts.insert(0, cx.new(|_| contact));
cx.notify();
+ } else {
+ self.set_error(
+ Some(t!("compose.contact_existed", name = contact.profile.name()).into()),
+ cx,
+ );
}
}
@@ -259,7 +266,7 @@ impl Compose {
Ok(contact)
} else {
- Err(anyhow!("Profile not found"))
+ Err(anyhow!(t!("common.not_found")))
}
})
} else if content.starts_with("nprofile1") {
@@ -267,7 +274,7 @@ impl Compose {
.map(|nip19| nip19.public_key)
.ok()
else {
- self.set_error(Some("Public Key is not valid".into()), cx);
+ self.set_error(Some(t!("common.pubkey_invalid").into()), cx);
return;
};
@@ -285,7 +292,7 @@ impl Compose {
})
} else {
let Ok(public_key) = PublicKey::parse(&content) else {
- self.set_error(Some("Public Key is not valid".into()), cx);
+ self.set_error(Some(t!("common.pubkey_invalid").into()), cx);
return;
};
@@ -328,7 +335,7 @@ impl Compose {
.detach();
}
- fn set_error(&mut self, error: Option, cx: &mut Context) {
+ fn set_error(&mut self, error: impl Into