generated from lucxjo/template
Compare commits
3 commits
94d5f5afe7
...
6cbcb66cf3
Author | SHA1 | Date | |
---|---|---|---|
Louis Hollingworth | 6cbcb66cf3 | ||
Louis Hollingworth | 9882a127bc | ||
Louis Hollingworth | 46c6a25164 |
845
Cargo.lock
generated
845
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -17,9 +17,14 @@ name = "er"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
|
fluent = "0.16.0"
|
||||||
|
fluent-syntax = "0.11.0"
|
||||||
|
intl-memoizer = "0.5.1"
|
||||||
poise = "0.5.6"
|
poise = "0.5.6"
|
||||||
reqwest = "0.11.22"
|
reqwest = "0.11.22"
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
|
sqlx = { version = "0.7.2", features = ["postgres", "macros", "uuid", "chrono", "json", "runtime-tokio"] }
|
||||||
thiserror = "1.0.50"
|
thiserror = "1.0.50"
|
||||||
tokio = { version = "1.32.0", features = ["full"] }
|
tokio = { version = "1.32.0", features = ["full"] }
|
||||||
|
uuid = { version = "1.5.0", features = ["v4"] }
|
||||||
|
|
9
build.rs
Normal file
9
build.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Louis Hollingworth <louis@hollingworth.nl>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// generated by `sqlx migrate build-script`
|
||||||
|
fn main() {
|
||||||
|
// trigger recompilation when a new migration is added
|
||||||
|
println!("cargo:rerun-if-changed=migrations");
|
||||||
|
}
|
6
migrations/20231028165341_init.down.sql
Normal file
6
migrations/20231028165341_init.down.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
-- SPDX-FileCopyrightText: 2023 Louis Hollingworth <louis@hollingworth.nl>
|
||||||
|
--
|
||||||
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
-- Add down migration script here
|
||||||
|
DROP TABLE "guild";
|
10
migrations/20231028165341_init.up.sql
Normal file
10
migrations/20231028165341_init.up.sql
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
-- SPDX-FileCopyrightText: 2023 Louis Hollingworth <louis@hollingworth.nl>
|
||||||
|
--
|
||||||
|
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
-- Add up migration script here
|
||||||
|
CREATE TABLE "guild" (
|
||||||
|
"id" TEXT PRIMARY KEY NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"reports_channel_id" TEXT
|
||||||
|
);
|
70
src/commands/admin.rs
Normal file
70
src/commands/admin.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Louis Hollingworth <louis@hollingworth.nl>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
use poise::serenity_prelude as serenity;
|
||||||
|
|
||||||
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
subcommands("admin_reports", "admin_setup"),
|
||||||
|
subcommand_required,
|
||||||
|
guild_only,
|
||||||
|
default_member_permissions = "ADMINISTRATOR"
|
||||||
|
)]
|
||||||
|
pub async fn admin(_: crate::Context<'_>) -> Result<(), crate::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set or get the configured channel for reports
|
||||||
|
#[poise::command(slash_command, rename = "reports")]
|
||||||
|
pub async fn admin_reports(
|
||||||
|
ctx: crate::Context<'_>,
|
||||||
|
#[description = "Set where the reports should be sent"] reports_channel: Option<
|
||||||
|
serenity::Channel,
|
||||||
|
>,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
ctx.defer_ephemeral().await.unwrap();
|
||||||
|
let pool = &ctx.framework().user_data.db_pool;
|
||||||
|
match reports_channel {
|
||||||
|
None => {
|
||||||
|
let dbg = crate::models::db::guild::Guild::get_by_id(
|
||||||
|
ctx.guild_id().unwrap().to_string(),
|
||||||
|
pool.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
ctx.reply(format!(
|
||||||
|
"<#{}> is currently receiving reports for this guild.",
|
||||||
|
dbg.reports_channel_id.unwrap_or("0000000".to_string())
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
Some(cnl) => {
|
||||||
|
crate::models::db::guild::Guild::update_reports_channel(
|
||||||
|
ctx.guild_id().unwrap().to_string(),
|
||||||
|
Some(cnl.id().to_string()),
|
||||||
|
pool.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
ctx.reply(format!(
|
||||||
|
"<#{}> is now setup to receive reports.",
|
||||||
|
cnl.id().to_string()
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This command shouldn't be needed for anything other than development
|
||||||
|
#[poise::command(slash_command, rename = "setup")]
|
||||||
|
pub async fn admin_setup(ctx: crate::Context<'_>) -> Result<(), crate::Error> {
|
||||||
|
let pool = &ctx.framework().user_data.db_pool;
|
||||||
|
let g = ctx.guild().unwrap();
|
||||||
|
crate::models::db::guild::Guild::create(g.id.to_string(), g.name, pool.clone()).await?;
|
||||||
|
ctx.reply("Setup complete, never run this again. See the [issue tracker](https://git.ludoviko.ch/lucxjo/er/issues) if you are still having issues").await.unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -2,4 +2,6 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
pub mod admin;
|
||||||
|
pub mod report;
|
||||||
pub mod threads;
|
pub mod threads;
|
||||||
|
|
44
src/commands/report.rs
Normal file
44
src/commands/report.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Louis Hollingworth <louis@hollingworth.nl>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
use poise::serenity_prelude as serenity;
|
||||||
|
|
||||||
|
/// Report a user to the guild staff
|
||||||
|
#[poise::command(slash_command, guild_only)]
|
||||||
|
pub async fn report(
|
||||||
|
ctx: crate::Context<'_>,
|
||||||
|
#[description = "The user to report"] user: serenity::Member,
|
||||||
|
#[description = "Why you are reporting the user"] reason: String,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
ctx.defer_ephemeral().await?;
|
||||||
|
let pool = &ctx.framework().user_data.db_pool;
|
||||||
|
let g = crate::models::db::guild::Guild::get_by_id(
|
||||||
|
ctx.guild_id().unwrap().to_string(),
|
||||||
|
pool.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
match g.reports_channel_id {
|
||||||
|
None => {
|
||||||
|
ctx.reply("It looks like your guild staff haven't enabled this feature")
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Some(rcid) => {
|
||||||
|
let cid = serenity::ChannelId::from(rcid.parse::<u64>().unwrap());
|
||||||
|
cid.send_message(ctx.http(), |m| {
|
||||||
|
m.embed(|embed| {
|
||||||
|
embed.title("Member Report");
|
||||||
|
embed.author(|a| {
|
||||||
|
a.icon_url(ctx.author().avatar_url().unwrap());
|
||||||
|
a.name(ctx.author().name.clone())
|
||||||
|
});
|
||||||
|
embed.colour(0xd1021a);
|
||||||
|
embed.description(format!("{}: {}", user, reason))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
ctx.reply("Report sent").await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -3,9 +3,8 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use crate::models::discord::{DiscordThreadsResp, DiscordThreadsRespThread, EDiscordThreadsResp};
|
use crate::models::discord::{DiscordThreadsResp, DiscordThreadsRespThread, EDiscordThreadsResp};
|
||||||
use poise::serenity_prelude as serenity;
|
use crate::translation::tr;
|
||||||
|
|
||||||
/// Displays current active threads
|
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn threads(ctx: crate::Context<'_>) -> Result<(), crate::Error> {
|
pub async fn threads(ctx: crate::Context<'_>) -> Result<(), crate::Error> {
|
||||||
let mut threadsr: DiscordThreadsResp = DiscordThreadsResp { threads: vec![] };
|
let mut threadsr: DiscordThreadsResp = DiscordThreadsResp { threads: vec![] };
|
||||||
|
@ -35,7 +34,8 @@ pub async fn threads(ctx: crate::Context<'_>) -> Result<(), crate::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"The current active threads are:{}",
|
"{}{}",
|
||||||
|
tr!(ctx, "threads-response"),
|
||||||
build_thread_response_str(threadsr.threads)
|
build_thread_response_str(threadsr.threads)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
26
src/lib.rs
26
src/lib.rs
|
@ -4,8 +4,30 @@
|
||||||
|
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
pub mod translation;
|
||||||
|
|
||||||
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||||
pub type Context<'a> = poise::Context<'a, Data, Error>;
|
pub type Context<'a> = poise::Context<'a, AppState, Error>;
|
||||||
|
|
||||||
pub struct Data {}
|
pub struct AppState {
|
||||||
|
pub db_pool: sqlx::Pool<sqlx::Postgres>,
|
||||||
|
pub translations: translation::Translations,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub async fn new(translations: translation::Translations) -> Result<Self, Error> {
|
||||||
|
let pool = establish_db_pool().await?;
|
||||||
|
sqlx::migrate!().run(&pool).await?;
|
||||||
|
Ok(AppState { db_pool: pool, translations })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn establish_db_pool() -> Result<sqlx::Pool<sqlx::Postgres>, sqlx::Error> {
|
||||||
|
dotenvy::dotenv().ok();
|
||||||
|
|
||||||
|
let url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||||
|
Ok(sqlx::postgres::PgPoolOptions::new()
|
||||||
|
.max_connections(5)
|
||||||
|
.connect(&url)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -3,16 +3,21 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
use er::{commands, Data};
|
use er::commands;
|
||||||
use poise::serenity_prelude as serenity;
|
use poise::serenity_prelude as serenity;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
|
let translations = er::translation::read_ftl().expect("Failed to read translation files");
|
||||||
|
let mut cmds = vec![commands::admin::admin(), commands::threads::threads(), commands::report::report()];
|
||||||
|
er::translation::apply_translations(&translations, &mut cmds);
|
||||||
|
|
||||||
|
|
||||||
let framework = poise::Framework::builder()
|
let framework = poise::Framework::builder()
|
||||||
.options(poise::FrameworkOptions {
|
.options(poise::FrameworkOptions {
|
||||||
commands: vec![commands::threads::threads()],
|
commands: cmds,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.token(std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"))
|
.token(std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"))
|
||||||
|
@ -26,7 +31,7 @@ async fn main() {
|
||||||
.setup(|ctx, _ready, fw| {
|
.setup(|ctx, _ready, fw| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
poise::builtins::register_globally(ctx, &fw.options().commands).await?;
|
poise::builtins::register_globally(ctx, &fw.options().commands).await?;
|
||||||
Ok(Data {})
|
Ok(er::AppState::new(translations).await?)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
65
src/models/db/guild.rs
Normal file
65
src/models/db/guild.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Louis Hollingworth <louis@hollingworth.nl>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
pub struct NewGuild {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
pub struct Guild {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub reports_channel_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Guild {
|
||||||
|
pub async fn create(
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
pool: sqlx::Pool<sqlx::Postgres>,
|
||||||
|
) -> Result<(), sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
NewGuild,
|
||||||
|
"INSERT INTO guild (id, name) VALUES ($1, $2)",
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.execute(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub async fn get_by_id(
|
||||||
|
id: String,
|
||||||
|
pool: sqlx::Pool<sqlx::Postgres>,
|
||||||
|
) -> Result<Self, sqlx::Error> {
|
||||||
|
let guild = sqlx::query_as!(
|
||||||
|
Self,
|
||||||
|
"SELECT id, name, reports_channel_id FROM guild WHERE id = $1",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
Ok(guild)
|
||||||
|
}
|
||||||
|
pub async fn update_reports_channel(
|
||||||
|
gid: String,
|
||||||
|
rcid: Option<String>,
|
||||||
|
pool: sqlx::Pool<sqlx::Postgres>,
|
||||||
|
) -> Result<(), sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE guild SET reports_channel_id = $1 WHERE id = $2",
|
||||||
|
rcid,
|
||||||
|
gid
|
||||||
|
)
|
||||||
|
.execute(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
5
src/models/db/mod.rs
Normal file
5
src/models/db/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Louis Hollingworth <louis@hollingworth.nl>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
pub mod guild;
|
|
@ -2,4 +2,5 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
pub mod db;
|
||||||
pub mod discord;
|
pub mod discord;
|
||||||
|
|
164
src/translation.rs
Normal file
164
src/translation.rs
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Louis Hollingworth <louis@hollingworth.nl>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
type FluentBundle = fluent::bundle::FluentBundle<
|
||||||
|
fluent::FluentResource,
|
||||||
|
intl_memoizer::concurrent::IntlLangMemoizer,
|
||||||
|
>;
|
||||||
|
|
||||||
|
pub struct Translations {
|
||||||
|
pub main: FluentBundle,
|
||||||
|
pub other: std::collections::HashMap<String, FluentBundle>
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! tr {
|
||||||
|
($ctx:ident, $id:expr $(, $argname:ident: $argvalue:expr)* $(,)? ) => {{
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut args = fluent::FluentArgs::new();
|
||||||
|
$( args.set(stringify!($argname), $argvalue); )*
|
||||||
|
|
||||||
|
$crate::translation::get($ctx, $id, None, Some(&args))
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) use tr;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn format(
|
||||||
|
bundle: &FluentBundle,
|
||||||
|
id: &str,
|
||||||
|
attr: Option<&str>,
|
||||||
|
args: Option<&fluent::FluentArgs<'_>>,
|
||||||
|
) -> Option<String> {
|
||||||
|
let msg = bundle.get_message(id)?;
|
||||||
|
let ptrn = match attr {
|
||||||
|
None => msg.value()?,
|
||||||
|
Some(a) => msg.get_attribute(a)?.value()
|
||||||
|
};
|
||||||
|
let fmt = bundle.format_pattern(ptrn, args, &mut vec![]);
|
||||||
|
Some(fmt.into_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get<'a>(
|
||||||
|
ctx: crate::Context<'a>,
|
||||||
|
id: &str,
|
||||||
|
attr: Option<&str>,
|
||||||
|
args: Option<&fluent::FluentArgs<'_>>,
|
||||||
|
) -> String {
|
||||||
|
let t = &ctx.data().translations;
|
||||||
|
println!("The current locale is: {}", ctx.locale().unwrap());
|
||||||
|
ctx.locale()
|
||||||
|
.and_then(|lcl| format(t.other.get(lcl)?, id, attr, args))
|
||||||
|
.or_else(|| format(&t.main, id, attr, args))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
println!("unknown fluent message identifier `{}`", id);
|
||||||
|
id.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_ftl() -> Result<Translations, crate::Error> {
|
||||||
|
fn read_one_ftl(path: &std::path::Path) -> Result<(String, FluentBundle), crate::Error> {
|
||||||
|
let loc = path.file_stem().ok_or("Invalid .ftl filename")?;
|
||||||
|
let loc = loc.to_str().ok_or("Invalid UTF-8 filename")?;
|
||||||
|
|
||||||
|
let fc = std::fs::read_to_string(path)?;
|
||||||
|
let res = fluent::FluentResource::try_new(fc)
|
||||||
|
.map_err(|(_, e)| format!("failed to parse {:?}: {:?}", path, e))?;
|
||||||
|
|
||||||
|
let mut bun = FluentBundle::new_concurrent(vec![loc.parse().map_err(|e| format!("invalid locale `{}`: {}", loc, e))?]);
|
||||||
|
bun.add_resource(res)
|
||||||
|
.map_err(|e| format!("failed to add resource to bundle: {:?}", e))?;
|
||||||
|
Ok((loc.to_string(), bun))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Translations {
|
||||||
|
main: read_one_ftl("translations/en-US.ftl".as_ref())?.1,
|
||||||
|
other: std::fs::read_dir("translations")?
|
||||||
|
.map(|f| read_one_ftl(&f?.path()))
|
||||||
|
.collect::<Result<_, _>>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Given a set of language files, fills in command strings and their localizations accordingly
|
||||||
|
pub fn apply_translations(
|
||||||
|
translations: &Translations,
|
||||||
|
commands: &mut [poise::Command<crate::AppState, crate::Error>],
|
||||||
|
) {
|
||||||
|
for command in &mut *commands {
|
||||||
|
// Add localizations
|
||||||
|
for (locale, bundle) in &translations.other {
|
||||||
|
// Insert localized command name and description
|
||||||
|
let localized_command_name = match format(bundle, &command.name, None, None) {
|
||||||
|
Some(x) => x,
|
||||||
|
None => continue, // no localization entry => skip localization
|
||||||
|
};
|
||||||
|
command
|
||||||
|
.name_localizations
|
||||||
|
.insert(locale.clone(), localized_command_name);
|
||||||
|
command.description_localizations.insert(
|
||||||
|
locale.clone(),
|
||||||
|
format(bundle, &command.name, Some("description"), None).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
for parameter in &mut command.parameters {
|
||||||
|
// Insert localized parameter name and description
|
||||||
|
parameter.name_localizations.insert(
|
||||||
|
locale.clone(),
|
||||||
|
format(bundle, &command.name, Some(¶meter.name), None).unwrap(),
|
||||||
|
);
|
||||||
|
parameter.description_localizations.insert(
|
||||||
|
locale.clone(),
|
||||||
|
format(
|
||||||
|
bundle,
|
||||||
|
&command.name,
|
||||||
|
Some(&format!("{}-description", parameter.name)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If this is a choice parameter, insert its localized variants
|
||||||
|
for choice in &mut parameter.choices {
|
||||||
|
choice.localizations.insert(
|
||||||
|
locale.clone(),
|
||||||
|
format(bundle, &choice.name, None, None).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, all translation files have been applied. However, if a user uses a locale
|
||||||
|
// we haven't explicitly inserted, there would be no translations at all -> blank texts. So,
|
||||||
|
// we use the "main" translation file (en-US) as the non-localized strings.
|
||||||
|
|
||||||
|
// Set fallback command name and description to en-US
|
||||||
|
let bundle = &translations.main;
|
||||||
|
match format(bundle, &command.name, None, None) {
|
||||||
|
Some(x) => command.name = x,
|
||||||
|
None => continue, // no localization entry => keep hardcoded names
|
||||||
|
}
|
||||||
|
command.description =
|
||||||
|
Some(format(bundle, &command.name, Some("description"), None).unwrap());
|
||||||
|
|
||||||
|
for parameter in &mut command.parameters {
|
||||||
|
// Set fallback parameter name and description to en-US
|
||||||
|
parameter.name = format(bundle, &command.name, Some(¶meter.name), None).unwrap();
|
||||||
|
parameter.description = Some(
|
||||||
|
format(
|
||||||
|
bundle,
|
||||||
|
&command.name,
|
||||||
|
Some(&format!("{}-description", parameter.name)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If this is a choice parameter, set the choice names to en-US
|
||||||
|
for choice in &mut parameter.choices {
|
||||||
|
choice.name = format(bundle, &choice.name, None, None).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
translations/en-US.ftl
Normal file
10
translations/en-US.ftl
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# SPDX-FileCopyrightText: 2023 Louis Hollingworth <louis@hollingworth.nl>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
# Command metadata
|
||||||
|
threads = threads
|
||||||
|
.description = Displays current active threads
|
||||||
|
|
||||||
|
# Responses
|
||||||
|
threads-response = The current threads are:
|
Loading…
Reference in a new issue