diff --git a/config/bandastation/bandastation_config.txt b/config/bandastation/bandastation_config.txt
index 2925f6c37cb00..e5bab371b690c 100644
--- a/config/bandastation/bandastation_config.txt
+++ b/config/bandastation/bandastation_config.txt
@@ -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
diff --git a/modular_bandastation/translations/code/translate_suggest_ru_names.dm b/modular_bandastation/translations/code/translate_suggest_ru_names.dm
index e446fb3860901..a9bc6254319db 100644
--- a/modular_bandastation/translations/code/translate_suggest_ru_names.dm
+++ b/modular_bandastation/translations/code/translate_suggest_ru_names.dm
@@ -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 = "Предложить перевод"
@@ -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
diff --git a/tgui/packages/tgui/interfaces/RuNamesReviewPanel.jsx b/tgui/packages/tgui/interfaces/RuNamesReviewPanel.jsx
new file mode 100644
index 0000000000000..1e4884a7a40ba
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RuNamesReviewPanel.jsx
@@ -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 (
+
+
+ {json_data.map((entry_id) => (
+
+
+ {entry_id.ckey}
+
+ {entry_id.atom_path}
+
+
+ {entry_id.suggested_list['base']}
+
+
+ {entry_id.suggested_list['именительный']}
+
+
+ {entry_id.suggested_list['родительный']}
+
+
+ {entry_id.suggested_list['дательный']}
+
+
+ {entry_id.suggested_list['винительный']}
+
+
+ {entry_id.suggested_list['творительный']}
+
+
+ {entry_id.suggested_list['предложный']}
+
+
+
+
+
+ act('approve', {
+ entry_id: entry_id.ckey + '-' + entry_id.atom_path,
+ })
+ }
+ >
+ Принять
+
+
+
+
+ act('deny', {
+ entry_id: entry_id.ckey + '-' + entry_id.atom_path,
+ })
+ }
+ >
+ Отклонить
+
+
+
+
+ ))}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/RuNamesSuggestPanel.jsx b/tgui/packages/tgui/interfaces/RuNamesSuggestPanel.jsx
new file mode 100644
index 0000000000000..08f816f9a63e4
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RuNamesSuggestPanel.jsx
@@ -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 (
+
+
+
+
+ );
+};