feat: set lang
This commit is contained in:
parent
11f6f1edbf
commit
e633c61820
6 changed files with 174 additions and 31 deletions
1
migrations/00006_guild_lang.sql
Normal file
1
migrations/00006_guild_lang.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
alter table guilds add column lang text not null default 'ko';
|
||||||
|
|
@ -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::{
|
use serenity::all::{
|
||||||
CommandInteraction, CommandOptionType, CommandType, Context, CreateCommand,
|
CommandInteraction, CommandOptionType, CommandType, Context, CreateCommand,
|
||||||
CreateCommandOption, CreateComponent, CreateContainer, CreateContainerComponent,
|
CreateCommandOption, CreateComponent, CreateContainer, CreateContainerComponent,
|
||||||
CreateInputText, CreateInteractionResponseMessage, CreateLabel, CreateModal,
|
CreateInputText, CreateInteractionResponseMessage, CreateLabel, CreateModal,
|
||||||
CreateModalComponent, CreateTextDisplay, InputTextStyle, MessageFlags, Permissions,
|
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> {
|
pub fn config_command() -> CreateCommand<'static> {
|
||||||
CreateCommand::new("starboard")
|
CreateCommand::new("starboard")
|
||||||
.description("스타보드 설정")
|
.description("Configure starboard")
|
||||||
.add_option(CreateCommandOption::new(
|
.description_localized("ko", "스타보드 설정")
|
||||||
CommandOptionType::SubCommand,
|
.add_option(
|
||||||
"script",
|
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)
|
.default_member_permissions(Permissions::ADMINISTRATOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +73,7 @@ impl Handler {
|
||||||
&ctx.http,
|
&ctx.http,
|
||||||
serenity::all::CreateInteractionResponse::Message(
|
serenity::all::CreateInteractionResponse::Message(
|
||||||
CreateInteractionResponseMessage::new()
|
CreateInteractionResponseMessage::new()
|
||||||
.content("관리자 권한이 필요합니다")
|
.content(interaction.locale().admin_required())
|
||||||
.ephemeral(true),
|
.ephemeral(true),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -59,7 +81,9 @@ impl Handler {
|
||||||
return Ok(());
|
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" {
|
if option.name == "script" {
|
||||||
let script = get_guild(&self.db, guild_id)
|
let script = get_guild(&self.db, guild_id)
|
||||||
.await?
|
.await?
|
||||||
|
|
@ -84,8 +108,9 @@ impl Handler {
|
||||||
|
|
||||||
예시 스크립트:
|
예시 스크립트:
|
||||||
```rs
|
```rs
|
||||||
if reactions["⭐"] >= 3 || removed {
|
let emoji = "⭐";
|
||||||
return result("0000000000000000000", reactions["⭐"], "⭐")
|
if reactions[emoji] >= 3 || removed {
|
||||||
|
return result("0000000000000000000", reactions[emoji], emoji)
|
||||||
}
|
}
|
||||||
```"#,
|
```"#,
|
||||||
)),
|
)),
|
||||||
|
|
@ -99,6 +124,41 @@ if reactions["⭐"] >= 3 || removed {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.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?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
use sqlx::prelude::FromRow;
|
use sqlx::prelude::FromRow;
|
||||||
|
|
||||||
|
use crate::lang::Lang;
|
||||||
|
|
||||||
#[derive(Debug, FromRow)]
|
#[derive(Debug, FromRow)]
|
||||||
pub struct GuildRow {
|
pub struct GuildRow {
|
||||||
// pub id: String,
|
// pub id: String,
|
||||||
pub script: Option<String>,
|
pub script: Option<String>,
|
||||||
|
pub lang: Lang,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, FromRow)]
|
#[derive(Debug, FromRow)]
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ impl Handler {
|
||||||
ctx.cache
|
ctx.cache
|
||||||
.guild(guild_id)
|
.guild(guild_id)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|x| x.channel(channel_id).clone())
|
.and_then(|x| x.channel(channel_id))
|
||||||
);
|
);
|
||||||
|
|
||||||
let channel = channel_id
|
let channel = channel_id
|
||||||
|
|
@ -379,10 +379,11 @@ impl Handler {
|
||||||
|
|
||||||
components.push(CreateComponent::TextDisplay(CreateTextDisplay::new(
|
components.push(CreateComponent::TextDisplay(CreateTextDisplay::new(
|
||||||
format!(
|
format!(
|
||||||
"-# {} {} · [원본 메시지]({})",
|
"-# {} {} · [{original_message}]({})",
|
||||||
result.icon,
|
result.icon,
|
||||||
result.count,
|
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<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
id: GuildId,
|
id: GuildId,
|
||||||
) -> anyhow::Result<Option<GuildRow>> {
|
) -> anyhow::Result<Option<GuildRow>> {
|
||||||
Ok(
|
sqlx::query_as::<_, GuildRow>("select * from guilds where id = $1")
|
||||||
sqlx::query_as::<_, GuildRow>("select * from guilds where id = $1")
|
.bind(id.to_string())
|
||||||
.bind(id.to_string())
|
.fetch_optional(executor)
|
||||||
.fetch_optional(executor)
|
.await
|
||||||
.await
|
.context("failed to get guild data from db")
|
||||||
.context("failed to get guild data from db")?,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_message(
|
async fn get_message(
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
message_id: MessageId,
|
message_id: MessageId,
|
||||||
) -> anyhow::Result<Option<MessageRow>> {
|
) -> anyhow::Result<Option<MessageRow>> {
|
||||||
Ok(
|
sqlx::query_as::<_, MessageRow>("select * from messages where message_id = $1")
|
||||||
sqlx::query_as::<_, MessageRow>("select * from messages where message_id = $1")
|
.bind(message_id.to_string())
|
||||||
.bind(message_id.to_string())
|
.fetch_optional(executor)
|
||||||
.fetch_optional(executor)
|
.await
|
||||||
.await
|
.context("failed to get message data from db")
|
||||||
.context("failed to get message data from db")?,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_or_create_webhook(
|
async fn get_or_create_webhook(
|
||||||
|
|
|
||||||
81
src/bot/lang.rs
Normal file
81
src/bot/lang.rs
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ mod commands;
|
||||||
mod config;
|
mod config;
|
||||||
mod db;
|
mod db;
|
||||||
mod handler;
|
mod handler;
|
||||||
|
mod lang;
|
||||||
mod modal;
|
mod modal;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue