feat: set lang
All checks were successful
Build / Build (push) Successful in 2m8s
Build / docker (push) Successful in 31s

This commit is contained in:
파링 2026-02-02 03:04:42 +09:00
parent 11f6f1edbf
commit e633c61820
Signed by: paring
SSH key fingerprint: SHA256:8uCHhCpn/gVOLEaTolmbub9kfM6XBxWkIWmHxUZoWWk
6 changed files with 174 additions and 31 deletions

View file

@ -0,0 +1 @@
alter table guilds add column lang text not null default 'ko';

View file

@ -1,24 +1,46 @@
use std::collections::HashMap;
use std::{collections::HashMap, str::FromStr};
use anyhow::anyhow;
use anyhow::{Context as _, anyhow, bail};
use serenity::all::{
CommandInteraction, CommandOptionType, CommandType, Context, CreateCommand,
CreateCommandOption, CreateComponent, CreateContainer, CreateContainerComponent,
CreateInputText, CreateInteractionResponseMessage, CreateLabel, CreateModal,
CreateModalComponent, CreateTextDisplay, InputTextStyle, MessageFlags, Permissions,
ResolvedTarget,
ResolvedTarget, ResolvedValue,
};
use crate::handler::{Handler, get_guild};
use crate::{
handler::{Handler, get_guild},
lang::{Lang, ParseLocale},
};
pub fn config_command() -> CreateCommand<'static> {
CreateCommand::new("starboard")
.description("스타보드 설정")
.add_option(CreateCommandOption::new(
.description("Configure starboard")
.description_localized("ko", "스타보드 설정")
.add_option(
CreateCommandOption::new(
CommandOptionType::SubCommand,
"script",
"스크립트 수정",
))
"Edit starboard script",
)
.description_localized("ko", "스크립트 설정"),
)
.add_option(
CreateCommandOption::new(CommandOptionType::SubCommand, "lang", "Change language")
.description_localized("ko", "이 서버에 대한 언어 변경")
.add_sub_option(
CreateCommandOption::new(
CommandOptionType::String,
"lang",
"Set language for this server",
)
.description_localized("ko", "사용할 언어 선택")
.add_string_choice("한국어", "ko")
.add_string_choice("English", "en")
.required(true),
),
)
.default_member_permissions(Permissions::ADMINISTRATOR)
}
@ -51,7 +73,7 @@ impl Handler {
&ctx.http,
serenity::all::CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("관리자 권한이 필요합니다")
.content(interaction.locale().admin_required())
.ephemeral(true),
),
)
@ -59,7 +81,9 @@ impl Handler {
return Ok(());
}
for option in interaction.data.options() {
debug!("got options: {:?}", &interaction.data.options());
if let Some(option) = interaction.data.options().first() {
if option.name == "script" {
let script = get_guild(&self.db, guild_id)
.await?
@ -84,8 +108,9 @@ impl Handler {
:
```rs
if reactions[""] >= 3 || removed {
return result("0000000000000000000", reactions[""], "")
let emoji = "";
if reactions[emoji] >= 3 || removed {
return result("0000000000000000000", reactions[emoji], emoji)
}
```"#,
)),
@ -99,6 +124,41 @@ if reactions["⭐"] >= 3 || removed {
),
)
.await?;
return Ok(());
} else if option.name == "lang" {
let lang = Some(&option.value)
.and_then(|x| match x {
ResolvedValue::SubCommand(value) => Some(value),
_ => None,
})
.and_then(|x| x.first())
.filter(|x| x.name == "lang")
.and_then(|x| match x.value {
ResolvedValue::String(x) => Some(x),
_ => None,
})
.context("lang option not found")?;
let Ok(lang) = Lang::from_str(lang) else {
bail!("unknown lang value");
};
sqlx::query(
"insert into guilds (id, lang) values ($1, $2) on conflict (id) do update set lang = excluded.lang",
).bind(guild_id.to_string())
.bind(lang)
.execute(&self.db).await?;
interaction
.create_response(
&ctx.http,
serenity::all::CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content(interaction.locale().lang_changed(lang))
.ephemeral(true),
),
)
.await?;
}
}

View file

@ -1,9 +1,12 @@
use sqlx::prelude::FromRow;
use crate::lang::Lang;
#[derive(Debug, FromRow)]
pub struct GuildRow {
// pub id: String,
pub script: Option<String>,
pub lang: Lang,
}
#[derive(Debug, FromRow)]

View file

@ -235,7 +235,7 @@ impl Handler {
ctx.cache
.guild(guild_id)
.as_ref()
.and_then(|x| x.channel(channel_id).clone())
.and_then(|x| x.channel(channel_id))
);
let channel = channel_id
@ -379,10 +379,11 @@ impl Handler {
components.push(CreateComponent::TextDisplay(CreateTextDisplay::new(
format!(
"-# {} {} · [원본 메시지]({})",
"-# {} {} · [{original_message}]({})",
result.icon,
result.count,
msg.link().to_string()
msg.link(),
original_message = guild.lang.to_locale().original_message()
),
)));
@ -434,26 +435,22 @@ pub async fn get_guild(
executor: impl PgExecutor<'_>,
id: GuildId,
) -> anyhow::Result<Option<GuildRow>> {
Ok(
sqlx::query_as::<_, GuildRow>("select * from guilds where id = $1")
.bind(id.to_string())
.fetch_optional(executor)
.await
.context("failed to get guild data from db")?,
)
.context("failed to get guild data from db")
}
async fn get_message(
executor: impl PgExecutor<'_>,
message_id: MessageId,
) -> anyhow::Result<Option<MessageRow>> {
Ok(
sqlx::query_as::<_, MessageRow>("select * from messages where message_id = $1")
.bind(message_id.to_string())
.fetch_optional(executor)
.await
.context("failed to get message data from db")?,
)
.context("failed to get message data from db")
}
async fn get_or_create_webhook(

81
src/bot/lang.rs Normal file
View file

@ -0,0 +1,81 @@
use std::str::FromStr;
use serenity::all::CommandInteraction;
#[derive(sqlx::Type, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[sqlx(rename_all = "snake_case", type_name = "TEXT")]
pub enum Lang {
#[default]
Ko,
En,
}
impl Lang {
pub fn to_locale(self) -> Box<dyn Locale> {
match self {
Lang::Ko => Box::new(Korean),
Lang::En => Box::new(English),
}
}
}
pub trait ParseLocale {
fn locale(&self) -> Box<dyn Locale>;
}
impl ParseLocale for CommandInteraction {
fn locale(&self) -> Box<dyn Locale> {
Lang::from_str(&self.locale).unwrap_or_default().to_locale()
}
}
impl FromStr for Lang {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ko" => Ok(Lang::Ko),
"en" => Ok(Lang::En),
_ => Err(()),
}
}
}
pub trait Locale: Send + Sync {
fn original_message(&self) -> &'static str;
fn admin_required(&self) -> &'static str;
fn lang_changed(&self, new_lang: Lang) -> String;
}
struct Korean;
impl Locale for Korean {
fn original_message(&self) -> &'static str {
"원본 메시지"
}
fn lang_changed(&self, new_lang: Lang) -> String {
format!("서버 언어가 {new_lang:?}(으)로 변경되었습니다")
}
fn admin_required(&self) -> &'static str {
"관리자 권한이 필요합니다"
}
}
struct English;
impl Locale for English {
fn original_message(&self) -> &'static str {
"Original Message"
}
fn lang_changed(&self, new_lang: Lang) -> String {
format!("Server language changed to {new_lang:?}")
}
fn admin_required(&self) -> &'static str {
"Administrator permission is required"
}
}

View file

@ -20,6 +20,7 @@ mod commands;
mod config;
mod db;
mod handler;
mod lang;
mod modal;
#[macro_use]