From 237c09966cc05f5aeeedfd93ef342dc8ff52eba2 Mon Sep 17 00:00:00 2001 From: Vika Date: Sun, 1 Sep 2024 19:04:08 +0300 Subject: Gettextize and add Russian translation This is a very shitty translation, but it can be improved later. I added it mostly as a test for translations working correctly, since I know Russian and might as well translate the app into the language. --- default.nix | 2 +- po/LINGUAS | 1 + po/bowl.pot | 132 ++++++++++++++++++++++++++++++++++++++ po/ru.po | 137 ++++++++++++++++++++++++++++++++++++++++ src/components/post_editor.rs | 21 +++--- src/components/signin.rs | 19 +++--- src/components/smart_summary.rs | 4 +- src/lib.rs | 21 +++--- src/main.rs | 7 ++ 9 files changed, 313 insertions(+), 31 deletions(-) create mode 100644 po/bowl.pot create mode 100644 po/ru.po diff --git a/default.nix b/default.nix index c79ba7c..3ea5c00 100644 --- a/default.nix +++ b/default.nix @@ -6,7 +6,7 @@ let src = let - suffixes = [ "meson.options" "meson.build" ".sh" ".po" ".pot" ".in" ]; + suffixes = [ "meson.options" "meson.build" ".sh" ".po" ".pot" ".in" "LINGUAS" ]; suffixFilter = name: type: let base = baseNameOf (toString name); in type == "directory" || lib.any (ext: lib.hasSuffix ext base) suffixes; diff --git a/po/LINGUAS b/po/LINGUAS index e69de29..adc719b 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -0,0 +1 @@ +ru \ No newline at end of file diff --git a/po/bowl.pot b/po/bowl.pot new file mode 100644 index 0000000..24e87b5 --- /dev/null +++ b/po/bowl.pot @@ -0,0 +1,132 @@ +# Bowl for Kittybox. +# Copyright (C) 2024 Vika Shleina +# This file is distributed under the same license as the bowl package. +# Vika , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: bowl\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-09-01 18:02+0300\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=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: data/xyz.vikanezrimaya.kittybox.Bowl.desktop.in.in:3 src/lib.rs:187 +msgid "Bowl" +msgstr "" + +#: data/xyz.vikanezrimaya.kittybox.Bowl.desktop.in.in:4 +msgid "Minimalist Micropub post creator" +msgstr "" + +#. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +#: data/xyz.vikanezrimaya.kittybox.Bowl.desktop.in.in:10 +msgid "Micropub;IndieWeb;Kittybox;" +msgstr "" + +#. TRANSLATORS: please keep the newline and `` tags +#: src/components/smart_summary.rs:47 +msgid "" +"Smart Summary\n" +"Ask a language model for a single-sentence summary." +msgstr "" + +#: src/components/post_editor.rs:142 +msgid "Name" +msgstr "" + +#: src/components/post_editor.rs:157 +msgid "Summary" +msgstr "" + +#: src/components/post_editor.rs:179 +msgid "Tags" +msgstr "" + +#: src/components/post_editor.rs:227 +msgid "Content" +msgstr "" + +#: src/components/post_editor.rs:280 +msgid "Visibility" +msgstr "" + +#: src/components/post_editor.rs:493 +msgid "Smart Summary error: {}" +msgstr "" + +#: src/components/post_editor.rs:540 +msgid "Post submitted" +msgstr "" + +#: src/components/post_editor.rs:541 +msgid "Open" +msgstr "" + +#: src/components/post_editor.rs:559 +msgid "Error sending post: {}" +msgstr "" + +#: src/components/signin.rs:91 +msgid "Thank you! This window can now be closed." +msgstr "" + +#: src/components/signin.rs:210 src/components/signin.rs:249 +msgid "Sign in" +msgstr "" + +#: src/components/signin.rs:215 +msgid "" +"Please sign in with your website to use Bowl.\n" +"Your website needs to support IndieAuth and Micropub for this app to work." +msgstr "" + +#: src/components/signin.rs:245 +msgid "Talking to your website..." +msgstr "" + +#: src/components/signin.rs:247 +msgid "Waiting for authorization..." +msgstr "" + +#: src/components/signin.rs:455 +msgid "state doesn't match what we remember, ceremony aborted" +msgstr "" + +#: src/components/signin.rs:463 +msgid "issuer doesn't match what we remember, ceremony aborted" +msgstr "" + +#: src/lib.rs:131 +msgid "Bowl for Kittybox" +msgstr "" + +#: src/lib.rs:169 +msgid "Sign out" +msgstr "" + +#: src/lib.rs:170 +msgid "Preferences" +msgstr "" + +#: src/lib.rs:171 +msgid "About" +msgstr "" + +#: src/lib.rs:185 +msgid "Bowl - Sign in with your website" +msgstr "" + +#: src/lib.rs:201 +msgid "Publish" +msgstr "" + +#: src/lib.rs:331 +msgid "Micropub access token for {}" +msgstr "" diff --git a/po/ru.po b/po/ru.po new file mode 100644 index 0000000..29af65e --- /dev/null +++ b/po/ru.po @@ -0,0 +1,137 @@ +# Russian translations for PACKAGE package. +# Copyright (C) 2024 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Vika , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-09-01 18:35+0300\n" +"PO-Revision-Date: 2024-09-01 18:36+0300\n" +"Last-Translator: Vika \n" +"Language-Team: Russian \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: data/xyz.vikanezrimaya.kittybox.Bowl.desktop.in.in:3 src/lib.rs:187 +msgid "Bowl" +msgstr "Bowl" + +#: data/xyz.vikanezrimaya.kittybox.Bowl.desktop.in.in:4 +msgid "Minimalist Micropub post creator" +msgstr "Минималистичная Micropub-утилита для написания постов в блог" + +#. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +#: data/xyz.vikanezrimaya.kittybox.Bowl.desktop.in.in:10 +msgid "Micropub;IndieWeb;Kittybox;" +msgstr "Miropub;IndieWeb;Kittybox" + +#. TRANSLATORS: please keep the newline and `` tags +#: src/components/smart_summary.rs:47 +msgid "" +"Smart Summary\n" +"Ask a language model for a single-sentence summary." +msgstr "" +"Умная Выжимка\n" +"Попросит языковую модель описать содержимое статьи в одном предложении." + +#: src/components/post_editor.rs:142 +msgid "Name" +msgstr "Название" + +#: src/components/post_editor.rs:157 +msgid "Summary" +msgstr "Содержание" + +#: src/components/post_editor.rs:179 +msgid "Tags" +msgstr "Тэги" + +#: src/components/post_editor.rs:227 +msgid "Content" +msgstr "Текст" + +#: src/components/post_editor.rs:280 +msgid "Visibility" +msgstr "" + +#: src/components/post_editor.rs:493 +msgid "Smart Summary error: {}" +msgstr "Ошибка Умной Выжимки: {}" + +#: src/components/post_editor.rs:540 +msgid "Post submitted" +msgstr "Статья отправлена" + +#: src/components/post_editor.rs:541 +msgid "Open" +msgstr "Открыть" + +#: src/components/post_editor.rs:559 +msgid "Error sending post: {}" +msgstr "Ошибка отправки статьи: {}" + +#: src/components/signin.rs:91 +msgid "Thank you! This window can now be closed." +msgstr "Благодарим Вас! Это окно можно закрыть." + +#: src/components/signin.rs:210 src/components/signin.rs:249 +msgid "Sign in" +msgstr "Войти" + +#: src/components/signin.rs:215 +msgid "" +"Please sign in with your website to use Bowl.\n" +"Your website needs to support IndieAuth and Micropub for this app to work." +msgstr "" +"Пожалуйста, войдите со своим веб-сайтом, чтобы использовать Боул.\n" +"Ваш веб-сайт должен поддерживать протоколы IndieAuth и Micropub для корректной работы приложения." + +#: src/components/signin.rs:245 +msgid "Talking to your website..." +msgstr "Общаемся с Вашим веб-сайтом..." + +#: src/components/signin.rs:247 +msgid "Waiting for authorization..." +msgstr "Ждём авторизации..." + +#: src/components/signin.rs:455 +msgid "state doesn't match what we remember, ceremony aborted" +msgstr "поле state не совпадает с тем, что мы помним, церемония отменена" + +#: src/components/signin.rs:463 +msgid "issuer doesn't match what we remember, ceremony aborted" +msgstr "поле issuer не совпадает с тем, что мы помним, церемония отменена" + +#: src/lib.rs:131 +msgid "Bowl for Kittybox" +msgstr "Bowl для Kittybox" + +#: src/lib.rs:169 +msgid "Sign out" +msgstr "Выйти" + +#: src/lib.rs:170 +msgid "Preferences" +msgstr "Настройки" + +#: src/lib.rs:171 +msgid "About" +msgstr "О приложении" + +#: src/lib.rs:185 +msgid "Bowl - Sign in with your website" +msgstr "Bowl - Войдите со своим веб-сайтом" + +#: src/lib.rs:201 +msgid "Publish" +msgstr "Опубликовать" + +#: src/lib.rs:331 +msgid "Micropub access token for {}" +msgstr "Токен доступа Micropub для {}" diff --git a/src/components/post_editor.rs b/src/components/post_editor.rs index 534b0dd..d1a6cf0 100644 --- a/src/components/post_editor.rs +++ b/src/components/post_editor.rs @@ -1,3 +1,4 @@ +use gettextrs::*; use crate::components; use crate::components::tag_pill::*; use adw::prelude::*; @@ -138,7 +139,7 @@ impl Component for Post #[name = "name_label"] gtk::Label { - set_markup: "Name", + set_markup: &gettext("Name"), set_margin_horizontal: 10, set_halign: gtk::Align::Start, set_valign: gtk::Align::Center, @@ -153,7 +154,7 @@ impl Component for Post #[name = "summary_label"] gtk::Label { - set_markup: "Summary", + set_markup: &gettext("Summary"), set_margin_horizontal: 10, set_halign: gtk::Align::Start, set_valign: gtk::Align::Center, @@ -175,7 +176,7 @@ impl Component for Post #[name = "tag_label"] gtk::Label { - set_markup: "Tags", + set_markup: &gettext("Tags"), set_margin_horizontal: 10, set_halign: gtk::Align::Start, set_valign: gtk::Align::Center, @@ -223,7 +224,7 @@ impl Component for Post #[name = "content_label"] gtk::Label { - set_markup: "Content", + set_markup: &gettext("Content"), set_halign: gtk::Align::Start, set_valign: gtk::Align::Start, set_margin_vertical: 10, @@ -276,7 +277,7 @@ impl Component for Post #[name = "visibility_label"] gtk::Label { - set_markup: "Visibility", + set_markup: &gettext("Visibility"), set_halign: gtk::Align::Start, set_valign: gtk::Align::Start, set_margin_vertical: 10, @@ -373,7 +374,7 @@ impl Component for Post [] as [gtk::Expression; 0], glib::closure::RustClosure::new(|v| { let list_item = v[0].get::().unwrap(); - Some(list_item.name().into()) + Some(gettext(list_item.name().as_str()).into()) }) ) )); @@ -489,7 +490,7 @@ impl Component for Post Input::SmartSummary(components::SmartSummaryOutput::Error(err)) => { self.set_smart_summary_busy_guard(None); - let toast = adw::Toast::new(&format!("Smart Summary error: {}", err)); + let toast = adw::Toast::new(&gettext!("Smart Summary error: {}", err)); toast.set_timeout(0); toast.set_priority(adw::ToastPriority::High); root.add_toast(toast); @@ -536,8 +537,8 @@ impl Component for Post self.summary_buffer.set_text(""); self.tags.guard().clear(); self.content_buffer.set_text(""); - let toast = adw::Toast::new("Post submitted"); - toast.set_button_label(Some("Open")); + let toast = adw::Toast::new(&gettext("Post submitted")); + toast.set_button_label(Some(&gettext("Open"))); toast.connect_button_clicked(move |toast| { gtk::UriLauncher::new(&location.to_string()).launch( None::<&adw::ApplicationWindow>, @@ -555,7 +556,7 @@ impl Component for Post root.add_toast(toast); }, Input::SubmitError(err) => { - let toast = adw::Toast::new(&format!("Error sending post: {}", err)); + let toast = adw::Toast::new(&gettext!("Error sending post: {}", err)); toast.set_timeout(0); toast.set_priority(adw::ToastPriority::High); diff --git a/src/components/signin.rs b/src/components/signin.rs index 08e850a..156686e 100644 --- a/src/components/signin.rs +++ b/src/components/signin.rs @@ -1,3 +1,4 @@ +use gettextrs::*; use std::cell::RefCell; use adw::prelude::*; @@ -87,7 +88,7 @@ fn callback_handler(sender: AsyncComponentSender) -> impl Fn(&soup::Serv msg.set_response( Some("text/plain; charset=\"utf-8\""), soup::MemoryUse::Static, - "Thank you! This window can now be closed.".as_bytes() + gettext("Thank you! This window can now be closed.").as_bytes() ); msg.connect_finished(move |_| { sender.input(Input::Callback(Ok(response.take().unwrap()))); @@ -206,12 +207,12 @@ impl AsyncComponent for SignIn { gtk::Label { add_css_class: "title-1", - set_text: "Sign in", + set_text: &gettext("Sign in"), set_justify: gtk::Justification::Center, }, gtk::Label { - set_text: "Please sign in with your website to use Bowl.\nYour website needs to support IndieAuth and Micropub for this app to work.", + set_text: &gettext("Please sign in with your website to use Bowl.\nYour website needs to support IndieAuth and Micropub for this app to work."), set_wrap: true, set_halign: gtk::Align::BaselineCenter, set_valign: gtk::Align::BaselineCenter, @@ -240,12 +241,12 @@ impl AsyncComponent for SignIn { }, gtk::Label { #[watch] - set_text: if model.busy_guard.is_some() { - "Talking to your website..." + set_text: &if model.busy_guard.is_some() { + gettext("Talking to your website...") } else if model.callback_server.is_some() { - "Waiting for authorization..." + gettext("Waiting for authorization...") } else { - "Sign in" + gettext("Sign in") }, } }, @@ -451,7 +452,7 @@ impl AsyncComponent for SignIn { if res.state != self.state { return self.bail_out(widgets, sender, IndieauthError { kind: kittybox_indieauth::ErrorKind::InvalidRequest, - msg: Some("state doesn't match what we remember, ceremony aborted".to_owned()), + msg: Some(gettext("state doesn't match what we remember, ceremony aborted")), error_uri: None, }.into()) } @@ -459,7 +460,7 @@ impl AsyncComponent for SignIn { if res.iss != metadata.issuer { return self.bail_out(widgets, sender, IndieauthError { kind: kittybox_indieauth::ErrorKind::InvalidRequest, - msg: Some("issuer doesn't match what we remember, ceremony aborted".to_owned()), + msg: Some(gettext("issuer doesn't match what we remember, ceremony aborted")), error_uri: None, }.into()) } diff --git a/src/components/smart_summary.rs b/src/components/smart_summary.rs index fbfc80b..9da67af 100644 --- a/src/components/smart_summary.rs +++ b/src/components/smart_summary.rs @@ -1,4 +1,5 @@ use adw::prelude::*; +use gettextrs::*; use relm4::{gtk, prelude::{Component, ComponentParts}, ComponentSender}; #[derive(Debug, Default)] @@ -42,7 +43,8 @@ impl Component for SmartSummaryButton { connect_clicked => Input::ButtonPressed, #[watch] set_sensitive: !model.busy, - set_tooltip_markup: Some("Smart Summary\nAsk a language model for a single-sentence summary."), + // TRANSLATORS: please keep the newline and `` tags + set_tooltip_markup: Some(gettext("Smart Summary\nAsk a language model for a single-sentence summary.")).as_deref(), if model.busy { gtk::Spinner { set_spinning: true } diff --git a/src/lib.rs b/src/lib.rs index 530eab6..ee92350 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +use gettextrs::*; use adw::prelude::*; use libsecret::prelude::{RetrievableExtManual, RetrievableExt}; use relm4::{actions::{RelmAction, RelmActionGroup}, gtk, loading_widgets::LoadingWidgets, prelude::{AsyncComponent, AsyncComponentController, AsyncComponentParts, AsyncController, ComponentController, Controller}, AsyncComponentSender, Component, RelmWidgetExt}; @@ -14,7 +15,7 @@ pub mod components { }; pub(crate) mod tag_pill; - // pub(crate) use tag_pill::{TagPill, TagPillDelete}; + // pub(crate) use tag_pill::{TagPill, TagPillDelete} pub mod signin; pub use signin::{SignIn, Output as SignInOutput}; @@ -127,7 +128,7 @@ impl App { fn about() -> adw::AboutDialog { adw::AboutDialog::builder() - .application_name("Bowl for Kittybox") + .application_name(gettext("Bowl for Kittybox")) .developer_name("Vika Shleina") .version(env!("CARGO_PKG_VERSION")) .website("https://kittybox.fireburn.ru/bowl/") @@ -165,9 +166,9 @@ impl AsyncComponent for App { menu! { main_menu: { - "Sign out" => SignOutAction, - "Preferences" => PreferencesAction, - "About" => AboutAction, + &gettext("Sign out") => SignOutAction, + &gettext("Preferences") => PreferencesAction, + &gettext("About") => AboutAction, } } @@ -181,10 +182,10 @@ impl AsyncComponent for App { #[watch] set_title: if model.micropub.is_none() { - Some("Bowl – Sign in with your website") + Some(gettext("Bowl - Sign in with your website")) } else { - Some("Bowl") - }, + Some(gettext("Bowl")) + }.as_deref(), adw::ToolbarView { add_top_bar = &adw::HeaderBar { @@ -197,7 +198,7 @@ impl AsyncComponent for App { }, pack_end = >k::Button { set_icon_name: "document-send-symbolic", - set_tooltip: "Send post", + set_tooltip: &gettext("Publish"), #[watch] set_visible: model.micropub.is_some(), #[watch] @@ -327,7 +328,7 @@ impl AsyncComponent for App { Some(&self.secret_schema), attributes.clone(), Some(libsecret::COLLECTION_DEFAULT), - &format!("Micropub access token for {}", &data.me), + &gettext!("Micropub access token for {}", &data.me), &data.access_token ).await { Ok(()) => {}, diff --git a/src/main.rs b/src/main.rs index d906a66..d7dd0a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,13 @@ static GLIB_LOGGER: glib::GlibLogger = glib::GlibLogger::new( ); fn main() { + gettextrs::bindtextdomain( + env!("CARGO_PKG_NAME"), + env!("LOCALEDIR") + ).expect("failed to bind text domain"); + gettextrs::bind_textdomain_codeset(env!("CARGO_PKG_NAME"), "UTF-8").unwrap(); + gettextrs::textdomain(env!("CARGO_PKG_NAME")).unwrap(); + log::set_logger(&GLIB_LOGGER).unwrap(); log::set_max_level(log::LevelFilter::Debug); -- cgit 1.4.1