From d4dbf9b8142cf93a973d1613c845d1fa7679170b Mon Sep 17 00:00:00 2001 From: AnnsAnn Date: Sat, 16 Mar 2024 14:50:01 +0100 Subject: [PATCH] Fully working i18n + Configuration --- Cargo.lock | 9 +++--- Cargo.toml | 2 +- locales/README.md | 35 +++++++++++++++++++++++ locales/de-DE.yml | 6 ++-- locales/en.yml | 11 ++++--- locales/nl-NL.yml | 32 +++++++++++++++++++++ sphene/src/main.rs | 16 +++++++---- sphene/src/options.rs | 28 ++++++++++++++---- sphene_config/Cargo.toml | 3 +- sphene_config/src/main.rs | 60 +++++++++++++++++++++++++++++++++++++-- thorium/src/db.rs | 30 +++++++++++++++++--- 11 files changed, 200 insertions(+), 32 deletions(-) create mode 100644 locales/README.md create mode 100644 locales/nl-NL.yml diff --git a/Cargo.lock b/Cargo.lock index de10f2b..e8fd2cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1864,7 +1864,7 @@ dependencies = [ [[package]] name = "sphene" -version = "8.3.0" +version = "9.0.0" dependencies = [ "dotenv", "regex", @@ -1876,10 +1876,11 @@ dependencies = [ [[package]] name = "sphene_config" -version = "8.3.0" +version = "9.0.0" dependencies = [ "dotenv", "poise", + "rust-i18n", "thorium", "tokio", ] @@ -2023,7 +2024,7 @@ dependencies = [ [[package]] name = "thorium" -version = "8.3.0" +version = "9.0.0" dependencies = [ "reqwest", "rusqlite", @@ -2078,7 +2079,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "titanite" -version = "8.3.0" +version = "9.0.0" dependencies = [ "dotenv", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 8725324..f9f69c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,5 +8,5 @@ members = [ resolver = "2" [workspace.package] -version = "8.3.0" +version = "9.0.0" authors = ["AnnsAnn"] \ No newline at end of file diff --git a/locales/README.md b/locales/README.md new file mode 100644 index 0000000..b65e90a --- /dev/null +++ b/locales/README.md @@ -0,0 +1,35 @@ +# i18n ReadME + +This folder contains internationalization (i18n) files for translating the bot messages into different languages. When translating, follow these instructions: + +1. **File Naming:** Create a new file named `.yml` for your language within this folder. + +2. **Base File:** Start by copying the contents of the `en.yml` file into your newly created file. + +3. **Translation:** Replace the English strings with their translated counterparts in your language. Ensure that the structure of the file remains intact, including placeholders and formatting. + +4. **Testing:** Test the translations thoroughly to ensure accuracy and readability. + - I'd recommend [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) for VSCode to help you with the translations. + +5. **Contributing:** If you've made significant improvements or translations for a language that doesn't exist yet, consider submitting a pull request to the repository. + +## Example Translation File Structure + +```yaml +_version: 1 +referenced: "๐Ÿ”— Your message was referenced by <@%{USER_ID}< (%{AUTHOR_NICKNAME}) in: %{MESSAGE_URL}" +error_sending_message: "๐Ÿšจ Error sending message: %{WHY}" +nothing_selected: "๐Ÿšจ Nothing selected" +``` + +## Notes + +- **Placeholder Usage:** Do not alter the placeholders (`%{...}`). They are used for dynamic content insertion and should be retained in your translations. + +- **URLs and Links:** Be cautious when translating URLs or links. Ensure that they remain functional and relevant in your language context. + +- **Consistency:** Maintain consistency in tone, style, and formatting throughout the translation for a seamless user experience. + +- **Fallback Language:** If a user's language is not supported or the specific string hasn't been translated, the bot will default to English. + +Thank you for your contribution to making the bot accessible to users worldwide! ๐ŸŒโœจ diff --git a/locales/de-DE.yml b/locales/de-DE.yml index bd1b580..d49b447 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1,8 +1,8 @@ _version: 1 referenced: "๐Ÿ”— Deine Nachricht wurde von <@%{USER_ID}< (%{AUTHOR_NICKNAME}) in: %{MESSAGE_URL} referenziert" -error_sending_message: "๐Ÿšจ Fehler beim Senden der Nachricht: ${WHY}" +error_sending_message: "๐Ÿšจ Fehler beim Senden der Nachricht: %{WHY}" nothing_selected: "๐Ÿšจ Nichts ausgewรคhlt" -error_delete_message: "๐Ÿšจ Fehler beim Lรถschen der Nachricht: ${WHY}" +error_delete_message: "๐Ÿšจ Fehler beim Lรถschen der Nachricht: %{WHY}" source_code: "โ˜๏ธ Der Quellcode ist hier zu finden: %{URL}" menu_meme: "๐Ÿ•บ https://www.youtube.com/watch?v=dQw4w9WgXcQ" # Bitte harmlos halten, wenn geรคndert disable: "โ›” Deaktiviere diesen Bot fรผr diese Seite mit dem Befehl /change!" @@ -14,7 +14,7 @@ remove: "โŒ Entferne diese Nachricht" change_info: "โ›” Verwende /change, um den Bot fรผr diese Seite zu deaktivieren!" menu: "Menรผ" download: "โฌ Medien herunterladen" -version: "๐Ÿณ๏ธโ€โšง๏ธ Lรคuft v${VERSION} von Sphene unter Verwendung von Thorium" +version: "๐Ÿณ๏ธโ€โšง๏ธ Lรคuft v%{VERSION} von Sphene unter Verwendung von Thorium" psky: "๐Ÿ”„๏ธ ร„ndern zu: Psky" fixbluesky: "๐Ÿ”„๏ธ ร„ndern zu: FixBluesky" media_only: "๐Ÿ–ผ๏ธ Nur Medien" diff --git a/locales/en.yml b/locales/en.yml index 1103dd7..3928615 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -1,8 +1,8 @@ _version: 1 referenced: "๐Ÿ”— Your message has been referenced by <@%{USER_ID}< (%{AUTHOR_NICKNAME}) in: %{MESSAGE_URL}" -error_sending_message: "๐Ÿšจ Error sending message: ${WHY}" +error_sending_message: "๐Ÿšจ Error sending message: %{WHY}" nothing_selected: "๐Ÿšจ Nothing selected" -error_delete_message: "๐Ÿšจ Error deleting message: ${WHY}" +error_delete_message: "๐Ÿšจ Error deleting message: %{WHY}" source_code: "โ˜๏ธ The Source Code can be found at: %{URL}" menu_meme: "๐Ÿ•บ https://www.youtube.com/watch?v=dQw4w9WgXcQ" # Please keep it harmless when changing disable: "โ›” Disable this bot for this site using the /change slash command!" @@ -14,7 +14,7 @@ remove: "โŒ Remove this Message" change_info: "โ›” Use /change to disable bot for this site!" menu: "Menu" download: "โฌ Download Media" -version: "๐Ÿณ๏ธโ€โšง๏ธ Running v${VERSION} of Sphene using Thorium" +version: "๐Ÿณ๏ธโ€โšง๏ธ Running v%{VERSION} of Sphene using Thorium" psky: "๐Ÿ”„๏ธ Change to: Psky" fixbluesky: "๐Ÿ”„๏ธ Change to: FixBluesky" media_only: "๐Ÿ–ผ๏ธ Media Only" @@ -29,4 +29,7 @@ show_original_instagram: "๐Ÿ“ธ Show original Instagram URL" tiktxk: "๐Ÿ”„๏ธ Change to: TikTxk" tnktok: "๐Ÿ”„๏ธ Change to: Tnktok" show_media_only_tiktok: "๐Ÿ–ผ๏ธ Media Only" -show_original_tiktok: "๐Ÿ‘ถ Show original TikTok URL" \ No newline at end of file +show_original_tiktok: "๐Ÿ‘ถ Show original TikTok URL" +change_language: "๐ŸŒ Change language with /set_guild_language!" +contribute_language: "๐ŸŒ Contribute to the translations at: %{URL} - Any contributions are welcome!" +contribute_languages_option: "๐ŸŒ Contribute to the translations" \ No newline at end of file diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml new file mode 100644 index 0000000..cbc1e0e --- /dev/null +++ b/locales/nl-NL.yml @@ -0,0 +1,32 @@ +_version: 1 +referenced: "๐Ÿ”— Je bericht is genoemd door <@%{USER_ID}< (%{AUTHOR_NICKNAME}) in: %{MESSAGE_URL}" +error_sending_message: "๐Ÿšจ Fout bij het verzenden van bericht: %{WHY}" +nothing_selected: "๐Ÿšจ Niets geselecteerd" +error_delete_message: "๐Ÿšจ Fout bij het verwijderen van bericht: %{WHY}" +source_code: "โ˜๏ธ De broncode is te vinden op: %{URL}" +menu_meme: "๐Ÿ•บ https://www.youtube.com/watch?v=dQw4w9WgXcQ" # Houd het alsjeblieft onschadelijk bij het wijzigen +disable: "โ›” Schakel deze bot uit voor deze site met behulp van de /change slash-opdracht!" +download_url: "โฌ Jouw download-URL is: <%{URL}>" +no_download: "โš ๏ธ Geen download-URL gevonden!" +not_author: "โš ๏ธ Jij bent niet de auteur van dit bericht!" +deleted_message: "๐Ÿ’ฃ Verwijderd bericht" +remove: "โŒ Verwijder dit bericht" +change_info: "โ›” Gebruik /change om de bot voor deze site uit te schakelen!" +menu: "Menu" +download: "โฌ Media downloaden" +version: "๐Ÿณ๏ธโ€โšง๏ธ Draait v%{VERSION} van Sphene met behulp van Thorium" +psky: "๐Ÿ”„๏ธ Verander naar: Psky" +fixbluesky: "๐Ÿ”„๏ธ Verander naar: FixBluesky" +media_only: "๐Ÿ–ผ๏ธ Alleen media" +show_original_bluesky: "โ˜๏ธ Toon originele Bluesky URL" +vxtwitter: "๐Ÿ”„๏ธ Verander naar: VXTwitter" +fxtwitter: "๐Ÿ”„๏ธ Verander naar: FXTwitter" +media_only_vxtwitter: "๐Ÿ–ผ๏ธ Alleen media (VXTwitter)" +media_only_fxtwitter: "๐Ÿ–ผ๏ธ Alleen media (FXTwitter)" +show_original_twitter: "๐Ÿคจ Toon originele Twitter URL" +ddinstagram: "๐Ÿ”„๏ธ Verander naar: DDInstagram" +show_original_instagram: "๐Ÿ“ธ Toon originele Instagram URL" +tiktxk: "๐Ÿ”„๏ธ Verander naar: TikTxk" +tnktok: "๐Ÿ”„๏ธ Verander naar: Tnktok" +show_media_only_tiktok: "๐Ÿ–ผ๏ธ Alleen media" +show_original_tiktok: "๐Ÿ‘ถ Toon originele TikTok URL" diff --git a/sphene/src/main.rs b/sphene/src/main.rs index f1ab471..1a43e7f 100644 --- a/sphene/src/main.rs +++ b/sphene/src/main.rs @@ -9,6 +9,7 @@ use options::get_instagram_options; use options::get_tik_tok_options; use options::get_twitter_options; use regex::Regex; +use rust_i18n::available_locales; use serenity::async_trait; use serenity::builder::CreateSelectMenuOption; @@ -25,7 +26,7 @@ use thorium::*; use rust_i18n::t; -rust_i18n::i18n!("locales", fallback = "en"); +rust_i18n::i18n!("../locales", fallback = "en"); mod options; @@ -184,11 +185,8 @@ impl EventHandler for Handler { return; } - let id = if msg.guild_id.is_some() { - msg.guild_id.unwrap().0 - } else { - msg.author.id.0 - }; + // Get guild ID + let id = msg.author.id.0; let get_lang = match self.dbconn.lock().await.get_server(id, false).language { Some(lang) => lang, None => "en".to_string(), @@ -203,6 +201,8 @@ impl EventHandler for Handler { || command == "download" || command == "menu" || command == "disable" + || command == "set_language" + || command == "contribute_language" { let content = if command == "version" { t!("source_code", locale=lang, URL = "https://github.com/AnnsAnns/sphene").to_string() @@ -210,6 +210,8 @@ impl EventHandler for Handler { t!("menu_meme").to_string() } else if command == "disable" { t!("disable").to_string() + } else if command == "set_language" || command == "contribute_language"{ + t!("contribute_language", locale=lang, URL="https://github.com/AnnsAnns/sphene/locales").to_string() } else if command == "download" { let extracted_url = self .regex_pattern @@ -384,6 +386,8 @@ async fn main() { let dbconn = Mutex::new(DBConn::new().unwrap()); + println!("Available Languages: {:?}", available_locales!()); + let mut client = Client::builder(&token, intents) .event_handler(Handler { regex_pattern, diff --git a/sphene/src/options.rs b/sphene/src/options.rs index 61b633c..a95c5ef 100644 --- a/sphene/src/options.rs +++ b/sphene/src/options.rs @@ -15,12 +15,20 @@ pub fn get_disable_option(lang: &str) -> CreateSelectMenuOption { CreateSelectMenuOption::new(t!("change_info", locale = lang), "disable") } -pub fn get_defaul_option(lang: &str) -> CreateSelectMenuOption { +pub fn get_default_option(lang: &str) -> CreateSelectMenuOption { CreateSelectMenuOption::new(t!("menu", locale = lang), "menu") .default_selection(true) .to_owned() } +pub fn get_set_language_option(lang: &str) -> CreateSelectMenuOption { + CreateSelectMenuOption::new(t!("change_language", locale = lang), "set_language") +} + +pub fn get_contribute_language_option(lang: &str) -> CreateSelectMenuOption { + CreateSelectMenuOption::new(t!("contribute_languages_option", locale = lang), "contribute_language") +} + pub fn get_download_option(lang: &str) -> CreateSelectMenuOption { CreateSelectMenuOption::new(t!("download", locale = lang), "download") } @@ -34,15 +42,17 @@ pub fn get_blueksy_options(lang: &str) -> Vec { get_download_option(lang), CreateSelectMenuOption::new(t!("psky", locale = lang), bluesky::PSKY_URL), CreateSelectMenuOption::new(t!("fixbluesky", locale = lang), bluesky::FIXBLUESKY_URL), - CreateSelectMenuOption::new(t!("mediaonly", locale = lang), "direct_fxbsky"), + CreateSelectMenuOption::new(t!("media_only", locale = lang), "direct_fxbsky"), CreateSelectMenuOption::new( t!("show_original_bluesky", locale = lang), bluesky::BLUESKY_URL, ), get_remove_option(lang), + get_set_language_option(lang), + get_contribute_language_option(lang), get_disable_option(lang), get_version_option(lang), - get_defaul_option(lang), + get_default_option(lang), ] } @@ -58,9 +68,11 @@ pub fn get_twitter_options(lang: &str) -> Vec { twitter::TWITTER_URL, ), get_remove_option(lang), + get_set_language_option(lang), + get_contribute_language_option(lang), get_disable_option(lang), get_version_option(lang), - get_defaul_option(lang), + get_default_option(lang), ] } @@ -72,9 +84,11 @@ pub fn get_instagram_options(lang: &str) -> Vec { instagram::INSTAGRAM_URL, ), get_remove_option(lang), + get_set_language_option(lang), + get_contribute_language_option(lang), get_disable_option(lang), get_version_option(lang), - get_defaul_option(lang), + get_default_option(lang), ] } @@ -89,8 +103,10 @@ pub fn get_tik_tok_options(lang: &str) -> Vec { tiktok::TIKTOK_URL, ), get_remove_option(lang), + get_set_language_option(lang), + get_contribute_language_option(lang), get_disable_option(lang), get_version_option(lang), - get_defaul_option(lang), + get_default_option(lang), ] } diff --git a/sphene_config/Cargo.toml b/sphene_config/Cargo.toml index 940ed17..9b4054a 100644 --- a/sphene_config/Cargo.toml +++ b/sphene_config/Cargo.toml @@ -10,4 +10,5 @@ authors.workspace = true dotenv = "0.15.0" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } poise = "0.5.6" -thorium = { path = "../thorium" } \ No newline at end of file +thorium = { path = "../thorium" } +rust-i18n = "3.0.1" \ No newline at end of file diff --git a/sphene_config/src/main.rs b/sphene_config/src/main.rs index f0f4dd8..8391bcf 100644 --- a/sphene_config/src/main.rs +++ b/sphene_config/src/main.rs @@ -22,6 +22,28 @@ pub enum EnableOrDisable { Disable = 0, } +#[derive(Debug, Clone, poise::ChoiceParameter)] +pub enum Languages { + English, + German, + Dutch, +} + +// Allow enum to string conversion for Languages +trait ToLanguageString { + fn to_language_string(&self) -> String; +} + +impl ToLanguageString for Languages { + fn to_language_string(&self) -> String { + match self { + Languages::English => "en".to_string(), + Languages::German => "de-DE".to_string(), + Languages::Dutch => "nl-NL".to_string(), + } + } +} + fn parse_choice(choice: &Choices, mut server: Server, change_to: bool) -> Server { match choice { Choices::Twitter => { @@ -40,6 +62,38 @@ fn parse_choice(choice: &Choices, mut server: Server, change_to: bool) -> Server server } +#[poise::command(slash_command, prefix_command)] +async fn set_own_language( + ctx: Context<'_>, + #[description = "Which language should the bot use for you personally?"] language: Languages, +) -> Result<(), Error> { + let db = ctx.data().db.lock().await; + let id = ctx.author().id.0; + let mut server = db.get_server(id, true); + server.language = Some(language.to_language_string()); + db.update_server(server); + ctx.say(format!("Changed language to {:#?} ๐Ÿ‘", language)).await?; + Ok(()) +} + +#[poise::command(slash_command, prefix_command, required_permissions = "ADMINISTRATOR")] +async fn set_guild_language( + ctx: Context<'_>, + #[description = "Which language should the bot use for this Guild?"] language: Languages, +) -> Result<(), Error> { + let db = ctx.data().db.lock().await; + let id = if ctx.guild_id().is_some() { + ctx.guild_id().unwrap().0 + } else { + ctx.author().id.0 + }; + let mut server = db.get_server(id, true); + server.language = Some(language.to_language_string()); + db.update_server(server); + ctx.say(format!("Changed language to {:#?} ๐Ÿ‘", language)).await?; + Ok(()) +} + /// Displays your or another user's account creation date #[poise::command(slash_command, prefix_command, required_permissions = "ADMINISTRATOR")] async fn change( @@ -69,14 +123,14 @@ async fn main() { let framework = poise::Framework::builder() .options(poise::FrameworkOptions { - commands: vec![change()], - ..Default::default() + commands: vec![change(), set_own_language(), set_guild_language()], + ..Default::default() }) .token(std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN")) .intents(serenity::GatewayIntents::non_privileged()) .setup(|ctx, _ready, framework| { Box::pin(async move { - poise::builtins::register_globally(ctx, &framework.options().commands).await?; + poise::builtins::register_in_guild(ctx, &framework.options().commands, poise::serenity_prelude::GuildId(644875066982793216)).await?; Ok(Data { db: dbconn }) }) }); diff --git a/thorium/src/db.rs b/thorium/src/db.rs index 9b8fd98..df02f6b 100644 --- a/thorium/src/db.rs +++ b/thorium/src/db.rs @@ -39,7 +39,7 @@ impl DBConn { twitter boolean not null, bluesky boolean not null, instagram boolean not null, - tiktok boolean not null + tiktok boolean not null, language text )", [], @@ -71,7 +71,10 @@ impl DBConn { server.unwrap() } else { if init { - let mut insert_statement = self.conn.prepare("INSERT INTO server (id, twitter, bluesky, instagram, tiktok, language) VALUES (?1, ?2, ?3, ?4, ?5, ?6)").unwrap(); + let mut insert_statement = self.conn.prepare( + + "INSERT INTO server (id, twitter, bluesky, instagram, tiktok, language) + VALUES (?1, ?2, ?3, ?4, ?5, ?6)").unwrap(); insert_statement .execute(rusqlite::params![ id, @@ -87,11 +90,30 @@ impl DBConn { } } + pub fn migrate_db(&self) { + self.conn.execute( + " + ALTER TABLE server + ADD COLUMN language TEXT; + )", + [], + ) + .unwrap(); + } + pub fn update_server(&self, server: Server) { print!("{:?}", server); let mut stmt = self .conn - .prepare("UPDATE server SET twitter = ?1, bluesky = ?2, instagram = ?3, tiktok = ?4, language = ?5 WHERE id = ?5") + .prepare( + "UPDATE server + SET twitter = ?1, + bluesky = ?2, + instagram = ?3, + tiktok = ?4, + language = ?5 + WHERE id = ?6", + ) .unwrap(); stmt.execute(rusqlite::params![ server.twitter, @@ -103,4 +125,4 @@ impl DBConn { ]) .unwrap(); } -} +} \ No newline at end of file