impr: Fix various issues with runtime-generated language strings
This commit is contained in:
parent
efee128c1c
commit
b2fc80f970
@ -1273,7 +1273,7 @@ namespace hex {
|
||||
void stopServices();
|
||||
}
|
||||
|
||||
void registerService(Lang name, const impl::Callback &callback);
|
||||
void registerService(const UnlocalizedString &unlocalizedString, const impl::Callback &callback);
|
||||
}
|
||||
|
||||
/* Network Communication Interface Registry. Allows adding new communication interface endpoints */
|
||||
|
@ -42,11 +42,13 @@ namespace hex {
|
||||
|
||||
struct UnlocalizedString;
|
||||
|
||||
class LangConst;
|
||||
|
||||
class Lang {
|
||||
public:
|
||||
Lang() = default;
|
||||
explicit Lang(const char *unlocalizedString);
|
||||
explicit Lang(const std::string &unlocalizedString);
|
||||
explicit(false) Lang(const LangConst &localizedString);
|
||||
explicit Lang(const UnlocalizedString &unlocalizedString);
|
||||
explicit Lang(std::string_view unlocalizedString);
|
||||
|
||||
@ -56,9 +58,22 @@ namespace hex {
|
||||
|
||||
const char* get() const;
|
||||
|
||||
constexpr static size_t hash(std::string_view string){
|
||||
private:
|
||||
std::size_t m_entryHash;
|
||||
std::string m_unlocalizedString;
|
||||
};
|
||||
|
||||
class LangConst {
|
||||
public:
|
||||
[[nodiscard]] operator std::string() const;
|
||||
[[nodiscard]] operator std::string_view() const;
|
||||
[[nodiscard]] operator const char *() const;
|
||||
|
||||
const char* get() const;
|
||||
|
||||
constexpr static size_t hash(std::string_view string) {
|
||||
constexpr u64 p = 131;
|
||||
constexpr u64 m = std::numeric_limits<std::uint32_t>::max() - 4; // Largest 32 bit prime
|
||||
constexpr u64 m = std::numeric_limits<std::uint32_t>::max() - 4;
|
||||
u64 total = 0;
|
||||
u64 currentMultiplier = 1;
|
||||
|
||||
@ -71,36 +86,24 @@ namespace hex {
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr explicit Lang(std::size_t hash, const char *unlocalizedString) : m_entryHash(hash), m_unlocalizedString(unlocalizedString) {}
|
||||
constexpr explicit LangConst(std::size_t hash, const char *unlocalizedString) : m_entryHash(hash), m_unlocalizedString(unlocalizedString) {}
|
||||
|
||||
template<wolv::type::StaticString>
|
||||
friend consteval Lang operator""_lang();
|
||||
friend consteval LangConst operator""_lang();
|
||||
friend class Lang;
|
||||
|
||||
private:
|
||||
std::size_t m_entryHash;
|
||||
const char *m_unlocalizedString = nullptr;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::string operator+(const std::string &&left, const Lang &&right);
|
||||
[[nodiscard]] std::string operator+(const Lang &&left, const std::string &&right);
|
||||
[[nodiscard]] std::string operator+(const std::string_view &&left, const Lang &&right);
|
||||
[[nodiscard]] std::string operator+(const Lang &&left, const std::string_view &&right);
|
||||
[[nodiscard]] std::string operator+(const char *left, const Lang &&right);
|
||||
[[nodiscard]] std::string operator+(const Lang &&left, const char *right);
|
||||
[[nodiscard]] std::string operator+(const Lang &&left, const Lang &&right);
|
||||
|
||||
template<wolv::type::StaticString String>
|
||||
[[nodiscard]] consteval Lang operator""_lang() {
|
||||
return Lang(Lang::hash(String.value.data()), String.value.data());
|
||||
}
|
||||
|
||||
|
||||
struct UnlocalizedString {
|
||||
public:
|
||||
UnlocalizedString() = default;
|
||||
|
||||
UnlocalizedString(auto && arg) : m_unlocalizedString(std::forward<decltype(arg)>(arg)) {
|
||||
static_assert(!std::same_as<std::remove_cvref_t<decltype(arg)>, Lang>, "Expected a unlocalized name, got a localized one!");
|
||||
template<typename T>
|
||||
UnlocalizedString(T &&arg) : m_unlocalizedString(std::forward<T>(arg)) {
|
||||
static_assert(!std::same_as<std::remove_cvref_t<T>, Lang>, "Expected a unlocalized name, got a localized one!");
|
||||
}
|
||||
|
||||
[[nodiscard]] operator std::string() const {
|
||||
@ -132,9 +135,17 @@ namespace hex {
|
||||
std::string m_unlocalizedString;
|
||||
};
|
||||
|
||||
// {fmt} formatter for hex::Lang
|
||||
template<wolv::type::StaticString String>
|
||||
[[nodiscard]] consteval LangConst operator""_lang() {
|
||||
return LangConst(LangConst::hash(String.value.data()), String.value.data());
|
||||
}
|
||||
|
||||
// {fmt} formatter for hex::Lang and hex::LangConst
|
||||
inline auto format_as(const hex::Lang &entry) {
|
||||
return entry.get();
|
||||
}
|
||||
inline auto format_as(const hex::LangConst &entry) {
|
||||
return entry.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ namespace hex {
|
||||
class Task {
|
||||
public:
|
||||
Task() = default;
|
||||
Task(Lang name, u64 maxValue, bool background, std::function<void(Task &)> function);
|
||||
Task(const UnlocalizedString &unlocalizedName, u64 maxValue, bool background, std::function<void(Task &)> function);
|
||||
|
||||
Task(const Task&) = delete;
|
||||
Task(Task &&other) noexcept;
|
||||
@ -65,7 +65,7 @@ namespace hex {
|
||||
void clearException();
|
||||
[[nodiscard]] std::string getExceptionMessage() const;
|
||||
|
||||
[[nodiscard]] const Lang &getName();
|
||||
[[nodiscard]] const UnlocalizedString &getUnlocalizedName();
|
||||
[[nodiscard]] u64 getValue() const;
|
||||
[[nodiscard]] u64 getMaxValue() const;
|
||||
|
||||
@ -77,7 +77,7 @@ namespace hex {
|
||||
private:
|
||||
mutable std::mutex m_mutex;
|
||||
|
||||
Lang m_name;
|
||||
UnlocalizedString m_unlocalizedName;
|
||||
std::atomic<u64> m_currValue = 0, m_maxValue = 0;
|
||||
std::function<void()> m_interruptCallback;
|
||||
std::function<void(Task &)> m_function;
|
||||
@ -130,20 +130,20 @@ namespace hex {
|
||||
|
||||
/**
|
||||
* @brief Creates a new asynchronous task that gets displayed in the Task Manager in the footer
|
||||
* @param name Name of the task
|
||||
* @param unlocalizedName Name of the task
|
||||
* @param maxValue Maximum value of the task
|
||||
* @param function Function to be executed
|
||||
* @return A TaskHolder holding a weak reference to the task
|
||||
*/
|
||||
static TaskHolder createTask(Lang name, u64 maxValue, std::function<void(Task &)> function);
|
||||
static TaskHolder createTask(const UnlocalizedString &unlocalizedName, u64 maxValue, std::function<void(Task &)> function);
|
||||
|
||||
/**
|
||||
* @brief Creates a new asynchronous task that does not get displayed in the Task Manager
|
||||
* @param name Name of the task
|
||||
* @param unlocalizedName Name of the task
|
||||
* @param function Function to be executed
|
||||
* @return A TaskHolder holding a weak reference to the task
|
||||
*/
|
||||
static TaskHolder createBackgroundTask(Lang name, std::function<void(Task &)> function);
|
||||
static TaskHolder createBackgroundTask(const UnlocalizedString &unlocalizedName, std::function<void(Task &)> function);
|
||||
|
||||
/**
|
||||
* @brief Creates a new synchronous task that will execute the given function at the start of the next frame
|
||||
@ -190,7 +190,7 @@ namespace hex {
|
||||
static void runDeferredCalls();
|
||||
|
||||
private:
|
||||
static TaskHolder createTask(Lang name, u64 maxValue, bool background, std::function<void(Task &)> function);
|
||||
static TaskHolder createTask(const UnlocalizedString &unlocalizedName, u64 maxValue, bool background, std::function<void(Task &)> function);
|
||||
};
|
||||
|
||||
}
|
@ -35,7 +35,7 @@ namespace hex::ui {
|
||||
m_filteredEntries.clear();
|
||||
m_filteredEntries.reserve(entries.size());
|
||||
|
||||
m_updateTask = TaskManager::createBackgroundTask(Lang("Searching"), [this, &entries, searchBuffer = m_searchBuffer](Task&) {
|
||||
m_updateTask = TaskManager::createBackgroundTask("Searching", [this, &entries, searchBuffer = m_searchBuffer](Task&) {
|
||||
for (auto &entry : entries) {
|
||||
if (searchBuffer.empty() || m_comparator(searchBuffer, entry))
|
||||
m_filteredEntries.push_back(&entry);
|
||||
|
@ -1199,7 +1199,7 @@ namespace hex {
|
||||
|
||||
class Service {
|
||||
public:
|
||||
Service(std::string name, std::jthread thread) : m_name(std::move(name)), m_thread(std::move(thread)) { }
|
||||
Service(const UnlocalizedString &unlocalizedName, std::jthread thread) : m_unlocalizedName(std::move(unlocalizedName)), m_thread(std::move(thread)) { }
|
||||
Service(const Service&) = delete;
|
||||
Service(Service &&) = default;
|
||||
~Service() {
|
||||
@ -1211,8 +1211,8 @@ namespace hex {
|
||||
Service& operator=(const Service&) = delete;
|
||||
Service& operator=(Service &&) = default;
|
||||
|
||||
[[nodiscard]] const std::string& getName() const {
|
||||
return m_name;
|
||||
[[nodiscard]] const UnlocalizedString& getUnlocalizedName() const {
|
||||
return m_unlocalizedName;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::jthread& getThread() const {
|
||||
@ -1220,7 +1220,7 @@ namespace hex {
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
UnlocalizedString m_unlocalizedName;
|
||||
std::jthread m_thread;
|
||||
};
|
||||
|
||||
@ -1235,13 +1235,13 @@ namespace hex {
|
||||
|
||||
}
|
||||
|
||||
void registerService(Lang name, const impl::Callback &callback) {
|
||||
log::debug("Registered new background service: {}", name.get());
|
||||
void registerService(const UnlocalizedString &unlocalizedName, const impl::Callback &callback) {
|
||||
log::debug("Registered new background service: {}", unlocalizedName.get());
|
||||
|
||||
impl::s_services->emplace_back(
|
||||
name,
|
||||
unlocalizedName,
|
||||
std::jthread([=](const std::stop_token &stopToken){
|
||||
TaskManager::setCurrentThreadName(name);
|
||||
TaskManager::setCurrentThreadName(Lang(unlocalizedName));
|
||||
while (!stopToken.stop_requested()) {
|
||||
callback();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
@ -450,7 +450,7 @@ namespace hex {
|
||||
// Do the destruction of the provider in the background once all tasks have finished
|
||||
TaskManager::runWhenTasksFinished([providerToRemove] {
|
||||
EventProviderDeleted::post(providerToRemove);
|
||||
TaskManager::createBackgroundTask(Lang("Closing Provider"), [providerToRemove](Task &) {
|
||||
TaskManager::createBackgroundTask("Closing Provider", [providerToRemove](Task &) {
|
||||
eraseMutex.lock();
|
||||
auto provider = std::move((*s_providersToRemove)[providerToRemove]);
|
||||
s_providersToRemove->erase(providerToRemove);
|
||||
|
@ -51,7 +51,7 @@ namespace hex {
|
||||
if (value.empty())
|
||||
continue;
|
||||
|
||||
s_currStrings->emplace(Lang::hash(key), value);
|
||||
s_currStrings->emplace(LangConst::hash(key), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,10 +109,11 @@ namespace hex {
|
||||
|
||||
}
|
||||
|
||||
Lang::Lang(const char *unlocalizedString) : m_entryHash(hash(unlocalizedString)) { }
|
||||
Lang::Lang(const std::string &unlocalizedString) : m_entryHash(hash(unlocalizedString)) { }
|
||||
Lang::Lang(const UnlocalizedString &unlocalizedString) : m_entryHash(hash(unlocalizedString.get())) { }
|
||||
Lang::Lang(std::string_view unlocalizedString) : m_entryHash(hash(unlocalizedString)) { }
|
||||
Lang::Lang(const char *unlocalizedString) : m_entryHash(LangConst::hash(unlocalizedString)), m_unlocalizedString(unlocalizedString) { }
|
||||
Lang::Lang(const std::string &unlocalizedString) : m_entryHash(LangConst::hash(unlocalizedString)), m_unlocalizedString(unlocalizedString) { }
|
||||
Lang::Lang(const LangConst &localizedString) : m_entryHash(localizedString.m_entryHash), m_unlocalizedString(localizedString.m_unlocalizedString) { }
|
||||
Lang::Lang(const UnlocalizedString &unlocalizedString) : m_entryHash(LangConst::hash(unlocalizedString.get())), m_unlocalizedString(unlocalizedString.get()) { }
|
||||
Lang::Lang(std::string_view unlocalizedString) : m_entryHash(LangConst::hash(unlocalizedString)), m_unlocalizedString(unlocalizedString) { }
|
||||
|
||||
Lang::operator std::string() const {
|
||||
return get();
|
||||
@ -129,9 +130,32 @@ namespace hex {
|
||||
const char *Lang::get() const {
|
||||
const auto &lang = *LocalizationManager::s_currStrings;
|
||||
|
||||
auto it = lang.find(m_entryHash);
|
||||
const auto it = lang.find(m_entryHash);
|
||||
if (it == lang.end()) {
|
||||
return m_unlocalizedString == nullptr ? "[ !!! INVALID LANGUAGE STRING !!! ]" : m_unlocalizedString;
|
||||
return m_unlocalizedString.c_str();
|
||||
} else {
|
||||
return it->second.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
LangConst::operator std::string() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
LangConst::operator std::string_view() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
LangConst::operator const char *() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
const char *LangConst::get() const {
|
||||
const auto &lang = *LocalizationManager::s_currStrings;
|
||||
|
||||
const auto it = lang.find(m_entryHash);
|
||||
if (it == lang.end()) {
|
||||
return m_unlocalizedString;
|
||||
} else {
|
||||
return it->second.c_str();
|
||||
}
|
||||
|
@ -63,8 +63,8 @@ namespace hex {
|
||||
}
|
||||
|
||||
|
||||
Task::Task(Lang name, u64 maxValue, bool background, std::function<void(Task &)> function)
|
||||
: m_name(std::move(name)), m_maxValue(maxValue), m_function(std::move(function)), m_background(background) { }
|
||||
Task::Task(const UnlocalizedString &unlocalizedName, u64 maxValue, bool background, std::function<void(Task &)> function)
|
||||
: m_unlocalizedName(std::move(unlocalizedName)), m_maxValue(maxValue), m_function(std::move(function)), m_background(background) { }
|
||||
|
||||
Task::Task(hex::Task &&other) noexcept {
|
||||
{
|
||||
@ -72,7 +72,7 @@ namespace hex {
|
||||
std::scoped_lock otherLock(other.m_mutex);
|
||||
|
||||
m_function = std::move(other.m_function);
|
||||
m_name = std::move(other.m_name);
|
||||
m_unlocalizedName = std::move(other.m_unlocalizedName);
|
||||
}
|
||||
|
||||
m_maxValue = u64(other.m_maxValue);
|
||||
@ -159,8 +159,8 @@ namespace hex {
|
||||
return m_exceptionMessage;
|
||||
}
|
||||
|
||||
const Lang &Task::getName() {
|
||||
return m_name;
|
||||
const UnlocalizedString &Task::getUnlocalizedName() {
|
||||
return m_unlocalizedName;
|
||||
}
|
||||
|
||||
u64 Task::getValue() const {
|
||||
@ -275,22 +275,22 @@ namespace hex {
|
||||
|
||||
try {
|
||||
// Set the thread name to the name of the task
|
||||
TaskManager::setCurrentThreadName(task->m_name);
|
||||
TaskManager::setCurrentThreadName(Lang(task->m_unlocalizedName));
|
||||
|
||||
// Execute the task
|
||||
task->m_function(*task);
|
||||
|
||||
log::debug("Task '{}' finished", task->m_name.get());
|
||||
log::debug("Task '{}' finished", task->m_unlocalizedName.get());
|
||||
} catch (const Task::TaskInterruptor &) {
|
||||
// Handle the task being interrupted by user request
|
||||
task->interruption();
|
||||
} catch (const std::exception &e) {
|
||||
log::error("Exception in task '{}': {}", task->m_name.get(), e.what());
|
||||
log::error("Exception in task '{}': {}", task->m_unlocalizedName.get(), e.what());
|
||||
|
||||
// Handle the task throwing an uncaught exception
|
||||
task->exception(e.what());
|
||||
} catch (...) {
|
||||
log::error("Exception in task '{}'", task->m_name.get());
|
||||
log::error("Exception in task '{}'", task->m_unlocalizedName.get());
|
||||
|
||||
// Handle the task throwing an uncaught exception of unknown type
|
||||
task->exception("Unknown Exception");
|
||||
@ -327,11 +327,11 @@ namespace hex {
|
||||
s_tasksFinishedCallbacks.clear();
|
||||
}
|
||||
|
||||
TaskHolder TaskManager::createTask(Lang name, u64 maxValue, bool background, std::function<void(Task&)> function) {
|
||||
TaskHolder TaskManager::createTask(const UnlocalizedString &unlocalizedName, u64 maxValue, bool background, std::function<void(Task&)> function) {
|
||||
std::scoped_lock lock(s_queueMutex);
|
||||
|
||||
// Construct new task
|
||||
auto task = std::make_shared<Task>(std::move(name), maxValue, background, std::move(function));
|
||||
auto task = std::make_shared<Task>(std::move(unlocalizedName), maxValue, background, std::move(function));
|
||||
|
||||
s_tasks.emplace_back(task);
|
||||
|
||||
@ -344,14 +344,14 @@ namespace hex {
|
||||
}
|
||||
|
||||
|
||||
TaskHolder TaskManager::createTask(Lang name, u64 maxValue, std::function<void(Task &)> function) {
|
||||
log::debug("Creating task {}", name);
|
||||
return createTask(std::move(name), maxValue, false, std::move(function));
|
||||
TaskHolder TaskManager::createTask(const UnlocalizedString &unlocalizedName, u64 maxValue, std::function<void(Task &)> function) {
|
||||
log::debug("Creating task {}", unlocalizedName.get());
|
||||
return createTask(std::move(unlocalizedName), maxValue, false, std::move(function));
|
||||
}
|
||||
|
||||
TaskHolder TaskManager::createBackgroundTask(Lang name, std::function<void(Task &)> function) {
|
||||
log::debug("Creating background task {}", name);
|
||||
return createTask(std::move(name), 0, true, std::move(function));
|
||||
TaskHolder TaskManager::createBackgroundTask(const UnlocalizedString &unlocalizedName, std::function<void(Task &)> function) {
|
||||
log::debug("Creating background task {}", unlocalizedName.get());
|
||||
return createTask(std::move(unlocalizedName), 0, true, std::move(function));
|
||||
}
|
||||
|
||||
void TaskManager::collectGarbage() {
|
||||
|
@ -111,8 +111,8 @@ namespace hex::plugin::builtin {
|
||||
s_autoBackupTime = value.get<int>(0) * 30;
|
||||
});
|
||||
|
||||
ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.network_interface"_lang, handleNetworkInterfaceService);
|
||||
ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.auto_backup"_lang, handleAutoBackup);
|
||||
ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.network_interface", handleNetworkInterfaceService);
|
||||
ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.auto_backup", handleAutoBackup);
|
||||
|
||||
EventProviderDirtied::subscribe([](prv::Provider *) {
|
||||
s_dataDirty = true;
|
||||
|
@ -174,7 +174,7 @@ namespace hex::plugin::builtin {
|
||||
bool draw(const std::string &name) override {
|
||||
auto format = [this] -> std::string {
|
||||
if (m_value == 0)
|
||||
return "hex.builtin.setting.interface.scaling.native"_lang + hex::format(" (x{:.1f})", ImHexApi::System::getNativeScale());
|
||||
return hex::format("{} (x{:.1f})", "hex.builtin.setting.interface.scaling.native"_lang, ImHexApi::System::getNativeScale());
|
||||
else
|
||||
return "x%.1f";
|
||||
}();
|
||||
|
@ -42,7 +42,7 @@ namespace hex::plugin::builtin {
|
||||
// Task exception toast
|
||||
for (const auto &task : TaskManager::getRunningTasks()) {
|
||||
if (task->hadException()) {
|
||||
ui::ToastError::open(hex::format("hex.builtin.popup.error.task_exception"_lang, task->getName(), task->getExceptionMessage()));
|
||||
ui::ToastError::open(hex::format("hex.builtin.popup.error.task_exception"_lang, Lang(task->getUnlocalizedName()), task->getExceptionMessage()));
|
||||
task->clearException();
|
||||
break;
|
||||
}
|
||||
@ -268,7 +268,7 @@ namespace hex::plugin::builtin {
|
||||
else
|
||||
progressString = hex::format("[ {}/{} ({:.1f}%) ] ", frontTask->getValue(), frontTask->getMaxValue(), std::min(progress, 1.0F) * 100.0F);
|
||||
|
||||
ImGuiExt::InfoTooltip(hex::format("{}{}", progressString, frontTask->getName()).c_str());
|
||||
ImGuiExt::InfoTooltip(hex::format("{}{}", progressString, Lang(frontTask->getUnlocalizedName())).c_str());
|
||||
|
||||
if (ImGui::BeginPopupContextItem("RestTasks", ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
for (const auto &task : tasks) {
|
||||
@ -276,7 +276,7 @@ namespace hex::plugin::builtin {
|
||||
continue;
|
||||
|
||||
ImGui::PushID(&task);
|
||||
ImGuiExt::TextFormatted("{}", task->getName());
|
||||
ImGuiExt::TextFormatted("{}", Lang(task->getUnlocalizedName()));
|
||||
ImGui::SameLine();
|
||||
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||
ImGui::SameLine();
|
||||
|
@ -166,7 +166,7 @@ namespace hex::plugin::builtin {
|
||||
|
||||
if (auto [match, value] = MatchCommand(input, command); match != MatchType::NoMatch) {
|
||||
if (match != MatchType::PerfectMatch) {
|
||||
results.push_back({ command + " (" + Lang(unlocalizedDescription) + ")", "", AutoComplete });
|
||||
results.push_back({ hex::format("{} ({})", command, Lang(unlocalizedDescription)), "", AutoComplete });
|
||||
} else {
|
||||
auto matchedCommand = wolv::util::trim(input.substr(command.length()));
|
||||
results.push_back({ displayCallback(matchedCommand), matchedCommand, executeCallback });
|
||||
@ -178,7 +178,7 @@ namespace hex::plugin::builtin {
|
||||
|
||||
if (auto [match, value] = MatchCommand(input, command + " "); match != MatchType::NoMatch) {
|
||||
if (match != MatchType::PerfectMatch) {
|
||||
results.push_back({ command + " (" + Lang(unlocalizedDescription) + ")", "", AutoComplete });
|
||||
results.push_back({ hex::format("{} ({})", command, Lang(unlocalizedDescription)), "", AutoComplete });
|
||||
} else {
|
||||
auto matchedCommand = wolv::util::trim(input.substr(command.length() + 1));
|
||||
results.push_back({ displayCallback(matchedCommand), matchedCommand, executeCallback });
|
||||
|
Loading…
x
Reference in New Issue
Block a user