Skip to content

Commit

Permalink
Better translate suggest (#579)
Browse files Browse the repository at this point in the history
## About The Pull Request

Добавляет ТГУИ-шку в панель Admin под названием Ru Names Suggestions,
где можно посмотреть на имеющиеся предложения переводов от игроков.
Аппрувнутые предложения уходят в дискорд через вебхук, денай уходит в
небытие (оба логируются).

Предложение игрока отправляется в `data/ru_names_suggest.json`.

Также делает более красивым меню предложения перевода.


![image](https://github.com/user-attachments/assets/f1a8e699-214b-4685-bc3d-5fe991dcf7ce)


![image](https://github.com/user-attachments/assets/a0f6db3c-fcf0-46cb-b549-df121889f2e2)


![image](https://github.com/user-attachments/assets/a64cf65f-0849-45b8-8fec-474ae83142a1)

# ДЛЯ ХОСТА
Не забудь проверить существование файла `data/ru_names_suggest.json`
Не забудь выставить в конфиге
`config/bandastation/bandastation_config.txt` вебхук

---------

Co-authored-by: Aylong <69762909+AyIong@users.noreply.github.com>
  • Loading branch information
larentoun and AyIong authored Oct 15, 2024
1 parent c15c045 commit a8efa6b
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 14 deletions.
4 changes: 4 additions & 0 deletions config/bandastation/bandastation_config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@
## If there is less security than this value, a percent of roundstart threat will be pushed to midround
## Example: with value of 5, if there is 2 security members out of 5, then 3/5 of roundstart threat will be moved to midround
#ROUNDSTART_SECURITY_FOR_THREAT 5

#TRANSLATE_SUGGEST_WEBHOOK_URL
#TRANSLATE_SUGGEST_WEBHOOK_PFP
#TRANSLATE_SUGGEST_WEBHOOK_NAME
204 changes: 190 additions & 14 deletions modular_bandastation/translations/code/translate_suggest_ru_names.dm
Original file line number Diff line number Diff line change
@@ -1,8 +1,190 @@
#define LOG_CATEGORY_RU_NAMES_SUGGEST "ru_names_suggest"
#define FILE_NAME "ru_names_suggest.json"
#define FILE_PATH_TO_RU_NAMES_SUGGEST "data/[FILE_NAME]"

GLOBAL_DATUM_INIT(ru_names_review_panel, /datum/ru_names_review_panel, new)

ADMIN_VERB(ru_names_review_panel, R_ADMIN, "Ru Names Review", "Shows player-suggested values for ru-names", ADMIN_CATEGORY_MAIN)
GLOB.ru_names_review_panel.ui_interact(user.mob)

/datum/log_category/ru_names_suggest
category = LOG_CATEGORY_RU_NAMES_SUGGEST

// MARK: Review
/datum/ru_names_review_panel
var/list/json_data = list()

/datum/ru_names_review_panel/New()
load_data()

/datum/ru_names_review_panel/ui_state(mob/user)
return GLOB.admin_state

/datum/ru_names_review_panel/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "RuNamesReviewPanel")
ui.open()

/datum/ru_names_review_panel/ui_data(mob/user)
. = list()
.["json_data"] = list()
for(var/entry_id in json_data)
.["json_data"] += list(json_data["[entry_id]"])

/datum/ru_names_review_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("approve")
approve_entry(params["entry_id"])
if("deny")
deny_entry(params["entry_id"])
if("update")
load_data()
. = TRUE

/datum/ru_names_review_panel/proc/load_data()
var/json_file = file(FILE_PATH_TO_RU_NAMES_SUGGEST)
if(!fexists(json_file))
return
json_data = json_decode(file2text(json_file))

/datum/ru_names_review_panel/proc/write_data()
rustg_file_write(json_encode(json_data, JSON_PRETTY_PRINT), FILE_PATH_TO_RU_NAMES_SUGGEST)

/datum/ru_names_review_panel/proc/approve_entry(entry_id)
load_data()
if(!length(json_data))
return
if(!json_data[entry_id])
to_chat(usr, span_notice("Couldn't find entry [entry_id]. Perhaps it was already approved or disapproved"))
return
var/list/data = json_data[entry_id]
var/suggested_list = "RU_NAMES_LIST_INIT(\"[data["suggested_list"]["base"]]\", \"[data["suggested_list"][NOMINATIVE]]\", \"[data["suggested_list"][GENITIVE]]\", \"[data["suggested_list"][DATIVE]]\", \"[data["suggested_list"][ACCUSATIVE]]\", \"[data["suggested_list"][INSTRUMENTAL]]\", \"[data["suggested_list"][PREPOSITIONAL]]\")"
var/message = "approves [suggested_list] for [data["atom_path"]]"
// Here we send message to discord
var/webhook_message = "[usr.ckey] [message] by [data["ckey"]]"
send2translate_webhook(webhook_message)
json_data.Remove(entry_id)
// Logging
write_data()
var/log_text = "[key_name_and_tag(usr)] [message]"
logger.Log(LOG_CATEGORY_RU_NAMES_SUGGEST, log_text)
to_chat(usr, span_notice("Entry [entry_id] approved."))

/datum/ru_names_review_panel/proc/deny_entry(entry_id)
load_data()
if(!length(json_data))
return
if(!json_data[entry_id])
to_chat(usr, "Couldn't find entry [entry_id]. Perhaps it was already approved or disapproved")
return
var/list/data = json_data[entry_id]
var/suggested_list = "RU_NAMES_LIST_INIT(\"[data["suggested_list"]["base"]]\", \"[data["suggested_list"][NOMINATIVE]]\", \"[data["suggested_list"][GENITIVE]]\", \"[data["suggested_list"][DATIVE]]\", \"[data["suggested_list"][ACCUSATIVE]]\", \"[data["suggested_list"][INSTRUMENTAL]]\", \"[data["suggested_list"][PREPOSITIONAL]]\")"
var/message = "denies [suggested_list] for [data["atom_path"]]"
json_data.Remove(entry_id)
write_data()
var/log_text = "[key_name_and_tag(usr)] [message]"
logger.Log(LOG_CATEGORY_RU_NAMES_SUGGEST, log_text)
to_chat(usr, span_notice("Entry [entry_id] denied."))

/datum/ru_names_review_panel/proc/add_entry(data)
json_data["[data["ckey"]]-[data["atom_path"]]"] = data
rustg_file_write(json_encode(json_data, JSON_PRETTY_PRINT), FILE_PATH_TO_RU_NAMES_SUGGEST)

var/suggested_list = "RU_NAMES_LIST_INIT(\"[data["suggested_list"]["base"]]\", \"[data["suggested_list"][NOMINATIVE]]\", \"[data["suggested_list"][GENITIVE]]\", \"[data["suggested_list"][DATIVE]]\", \"[data["suggested_list"][ACCUSATIVE]]\", \"[data["suggested_list"][INSTRUMENTAL]]\", \"[data["suggested_list"][PREPOSITIONAL]]\")"
var/message = "suggests [suggested_list] for [data["atom_path"]]"
var/log_text = "[key_name_and_tag(usr)] [message]"
logger.Log(LOG_CATEGORY_RU_NAMES_SUGGEST, log_text)

to_chat(usr, span_notice("Ваше предложение перевода успешно записано."))

// MARK: Webhook
/datum/config_entry/string/translate_suggest_webhook_url

/datum/config_entry/string/translate_suggest_webhook_pfp

/datum/config_entry/string/translate_suggest_webhook_name

/proc/send2translate_webhook(message)
var/webhook = CONFIG_GET(string/translate_suggest_webhook_url)
if(!webhook || !message)
return
var/list/webhook_info = list()
message = GLOB.has_discord_embeddable_links.Replace_char(replacetext_char(message, "`", ""), " ```$1``` ")
webhook_info["content"] = message
if(CONFIG_GET(string/translate_suggest_webhook_name))
webhook_info["username"] = CONFIG_GET(string/translate_suggest_webhook_name)
if(CONFIG_GET(string/translate_suggest_webhook_pfp))
webhook_info["avatar_url"] = CONFIG_GET(string/translate_suggest_webhook_pfp)
var/list/headers = list()
headers["Content-Type"] = "application/json"
var/datum/http_request/request = new()
request.prepare(RUSTG_HTTP_METHOD_POST, webhook, json_encode(webhook_info), headers, "tmp/response.json")
request.begin_async()

// MARK: Suggest
/datum/ru_name_suggest_panel
var/list/ru_name_data = list()

/datum/ru_name_suggest_panel/New(new_data)
if(!length(new_data))
CRASH("Ru Name Suggest panel was created with no data!")
ru_name_data = list(
"ckey" = new_data["ckey"],
"atom_path" = new_data["atom_path"],
"visible_name" = new_data["visible_name"],
"suggested_list" = list(
"base" = new_data["suggested_list"]["base"],
NOMINATIVE = "",
GENITIVE = "",
DATIVE = "",
ACCUSATIVE = "",
INSTRUMENTAL = "",
PREPOSITIONAL = "",
)
)

/datum/ru_name_suggest_panel/ui_state(mob/user)
return GLOB.always_state

/datum/ru_name_suggest_panel/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "RuNamesSuggestPanel")
ui.open()

/datum/ru_name_suggest_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("send")
send_suggestion(params["entries"])
. = TRUE

/datum/ru_name_suggest_panel/ui_data(mob/user)
. = list()
.["visible_name"] = ru_name_data["visible_name"]

/datum/ru_name_suggest_panel/ui_close(mob/user)
. = ..()
qdel(src)

/datum/ru_name_suggest_panel/proc/send_suggestion(list/entries)
var/list/declents = list(NOMINATIVE, GENITIVE, DATIVE, ACCUSATIVE, INSTRUMENTAL, PREPOSITIONAL)
if(length(entries) != length(declents))
to_chat(usr, span_warning("Ошибка! Пожалуйста, заполните все строки перед отправкой."))
return
for(var/declent in declents)
var/sanitized_input = trim(copytext_char(sanitize(entries[1]), 1, MAX_MESSAGE_LEN))
ru_name_data["suggested_list"]["[declent]"] = sanitized_input
entries -= entries[1]
GLOB.ru_names_review_panel.add_entry(ru_name_data)
qdel(src)

/mob/verb/suggest_ru_name(atom/A as mob|obj|turf in view())
set name = "Предложить перевод"

Expand All @@ -11,20 +193,14 @@
/mob/proc/_suggested_ru_name(atom/suggested_atom)
if(!client)
return FALSE
var/atom_name = suggested_atom.name
var/atom/atom_type = suggested_atom.type

var/static/list/declents = list(NOMINATIVE, GENITIVE, DATIVE, ACCUSATIVE, INSTRUMENTAL, PREPOSITIONAL)
var/list/ru_name_suggest = list()
for(var/declent in declents)
ru_name_suggest[declent] = tgui_input_text(src, "Введите [declent] падеж", "Предложение перевода для [atom_name]", atom_name)
if(!ru_name_suggest[declent])
to_chat(src, span_notice("Вы отменили предложение перевода."))
return TRUE
var/message = "suggests RU_NAMES_LIST_INIT(\"[atom_type::name]\", \"[ru_name_suggest[NOMINATIVE]]\", \"[ru_name_suggest[GENITIVE]]\", \"[ru_name_suggest[DATIVE]]\", \"[ru_name_suggest[ACCUSATIVE]]\", \"[ru_name_suggest[INSTRUMENTAL]]\", \"[ru_name_suggest[PREPOSITIONAL]]\") for [atom_type::type]"
var/log_text = "[key_name_and_tag(src)] [message]"
logger.Log(LOG_CATEGORY_RU_NAMES_SUGGEST, log_text)
to_chat(src, span_notice("Ваше предложение перевода успешно записано."))
var/list/data = list()
data["ckey"] = usr.ckey
data["suggested_list"] += list("base" = suggested_atom::name)
data["atom_path"] = suggested_atom::type
data["visible_name"] = suggested_atom.name
var/datum/ru_name_suggest_panel/ru_name_suggest_panel = new(data)
ru_name_suggest_panel.ui_interact(src)
return TRUE

#undef LOG_CATEGORY_RU_NAMES_SUGGEST
#undef FILE_PATH_TO_RU_NAMES_SUGGEST
84 changes: 84 additions & 0 deletions tgui/packages/tgui/interfaces/RuNamesReviewPanel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Button, Collapsible, LabeledList, Stack } from 'tgui-core/components';

import { useBackend } from '../backend';
import { Window } from '../layouts';

export const RuNamesReviewPanel = (props) => {
const { act, data } = useBackend();
const json_data = data.json_data || [];
return (
<Window
title="Предложения переводов"
theme="admin"
width={550}
height={400}
>
<Window.Content scrollable>
{json_data.map((entry_id) => (
<Collapsible
key={entry_id}
title={entry_id.ckey + ' предлагает для ' + entry_id.atom_path}
>
<LabeledList>
<LabeledList.Item label="ckey">{entry_id.ckey}</LabeledList.Item>
<LabeledList.Item label="Путь к объекту">
{entry_id.atom_path}
</LabeledList.Item>
<LabeledList.Item label="Стандартное имя">
{entry_id.suggested_list['base']}
</LabeledList.Item>
<LabeledList.Item label="Именительный">
{entry_id.suggested_list['именительный']}
</LabeledList.Item>
<LabeledList.Item label="Родительный">
{entry_id.suggested_list['родительный']}
</LabeledList.Item>
<LabeledList.Item label="Дательный">
{entry_id.suggested_list['дательный']}
</LabeledList.Item>
<LabeledList.Item label="Винительный">
{entry_id.suggested_list['винительный']}
</LabeledList.Item>
<LabeledList.Item label="Творительный">
{entry_id.suggested_list['творительный']}
</LabeledList.Item>
<LabeledList.Item label="Предложный">
{entry_id.suggested_list['предложный']}
</LabeledList.Item>
</LabeledList>
<Stack mt={0.75}>
<Stack.Item grow>
<Button.Confirm
fluid
confirmContent="Вы уверены?"
color="green"
onClick={() =>
act('approve', {
entry_id: entry_id.ckey + '-' + entry_id.atom_path,
})
}
>
Принять
</Button.Confirm>
</Stack.Item>
<Stack.Item grow>
<Button.Confirm
fluid
confirmContent="Вы уверены?"
color="red"
onClick={() =>
act('deny', {
entry_id: entry_id.ckey + '-' + entry_id.atom_path,
})
}
>
Отклонить
</Button.Confirm>
</Stack.Item>
</Stack>
</Collapsible>
))}
</Window.Content>
</Window>
);
};
78 changes: 78 additions & 0 deletions tgui/packages/tgui/interfaces/RuNamesSuggestPanel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useState } from 'react';
import { Button, Input, LabeledList, Section } from 'tgui-core/components';

import { useBackend } from '../backend';
import { Window } from '../layouts';

export const RuNamesSuggestPanel = (props) => {
const { act, data } = useBackend();
const visible_name = data.visible_name;
const [nominative, setNominative] = useState('');
const [genitive, setGenitive] = useState('');
const [dative, setDative] = useState('');
const [accusative, setAccusative] = useState('');
const [instrumental, setInstrumental] = useState('');
const [prepositional, setPrepositional] = useState('');
return (
<Window theme="admin" title="Предложение перевода" width={450} height={250}>
<Window.Content />
<Section title={'Оригинал: ' + visible_name}>
<LabeledList>
<LabeledList.Item label="Именительный (Кто? Что?)">
<Input width="100%" onChange={(e, value) => setNominative(value)} />
</LabeledList.Item>
<LabeledList.Item label="Родительный (Кого? Чего?)">
<Input width="100%" onChange={(e, value) => setGenitive(value)} />
</LabeledList.Item>
<LabeledList.Item label="Дательный (Кому? Чему?)">
<Input width="100%" onChange={(e, value) => setDative(value)} />
</LabeledList.Item>
<LabeledList.Item label="Винительный (Кого? Что?)">
<Input width="100%" onChange={(e, value) => setAccusative(value)} />
</LabeledList.Item>
<LabeledList.Item label="Творительный (Кем? Чем?)">
<Input
width="100%"
onChange={(e, value) => setInstrumental(value)}
/>
</LabeledList.Item>
<LabeledList.Item label="Предложный (О/В ком/чём?)">
<Input
width="100%"
onChange={(e, value) => setPrepositional(value)}
/>
</LabeledList.Item>
</LabeledList>
<Button.Confirm
fluid
textAlign="center"
mt={1.5}
confirmColor="green"
confirmContent="Вы уверены?"
disabled={
!nominative ||
!genitive ||
!dative ||
!accusative ||
!instrumental ||
!prepositional
}
onClick={() =>
act('send', {
entries: [
nominative,
genitive,
dative,
accusative,
instrumental,
prepositional,
],
})
}
>
Отправить
</Button.Confirm>
</Section>
</Window>
);
};

0 comments on commit a8efa6b

Please sign in to comment.