i18n: init localization for ANR, Permissions and Notifications (#12316)

Adds localization support for en, it, pl and jp

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
Co-authored-by: Aaron Blasko <blaskoazzolaaaron@gmail.com>
This commit is contained in:
Vaxry 2025-11-16 14:51:14 +00:00 committed by GitHub
parent cb47eb1d11
commit e616e595ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 302 additions and 67 deletions

View file

@ -108,7 +108,7 @@ find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.9.3)
pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=0.3.2)
pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=0.1.7)
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.8.2)
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.10.2)
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.6)
string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION})

6
flake.lock generated
View file

@ -261,11 +261,11 @@
]
},
"locked": {
"lastModified": 1762387740,
"narHash": "sha256-gQ9zJ+pUI4o+Gh4Z6jhJll7jjCSwi8ZqJIhCE2oqwhQ=",
"lastModified": 1762812168,
"narHash": "sha256-pY+dUqi2AYpH0HHT2JFzt1qWoJQBWtBdzzcL1ZK5Mwo=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "926689ddb9c0a8787e58c02c765a62e32d63d1f7",
"rev": "cb3e797fde5c748164eb70d9859336141136a166",
"type": "github"
},
"original": {

View file

@ -65,6 +65,7 @@
#include "debug/HyprNotificationOverlay.hpp"
#include "debug/HyprDebugOverlay.hpp"
#include "helpers/MonitorFrameScheduler.hpp"
#include "i18n/Engine.hpp"
#include <hyprutils/string/String.hpp>
#include <aquamarine/input/Input.hpp>
@ -2765,22 +2766,19 @@ void CCompositor::performUserChecks() {
const auto CURRENT_DESKTOP_ENV = getenv("XDG_CURRENT_DESKTOP");
if (!CURRENT_DESKTOP_ENV || std::string{CURRENT_DESKTOP_ENV} != "Hyprland") {
g_pHyprNotificationOverlay->addNotification(
std::format("Your XDG_CURRENT_DESKTOP environment seems to be managed externally, and the current value is {}.\nThis might cause issues unless it's intentional.",
CURRENT_DESKTOP_ENV ? CURRENT_DESKTOP_ENV : "unset"),
CHyprColor{}, 15000, ICON_WARNING);
I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, {{"value", CURRENT_DESKTOP_ENV ? CURRENT_DESKTOP_ENV : "unset"}}), CHyprColor{}, 15000,
ICON_WARNING);
}
}
if (!*PNOCHECKGUIUTILS) {
if (!NFsUtils::executableExistsInPath("hyprland-dialog")) {
g_pHyprNotificationOverlay->addNotification(
"Your system does not have hyprland-guiutils installed. This is a runtime dependency for some dialogs. Consider installing it.", CHyprColor{}, 15000, ICON_WARNING);
g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_GUIUTILS), CHyprColor{}, 15000, ICON_WARNING);
}
}
if (g_pHyprOpenGL->m_failedAssetsNo > 0) {
g_pHyprNotificationOverlay->addNotification(std::format("Hyprland failed to load {} essential asset{}, blame your distro's packager for doing a bad job at packaging!",
g_pHyprOpenGL->m_failedAssetsNo, g_pHyprOpenGL->m_failedAssetsNo > 1 ? "s" : ""),
g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_ASSETS, {{"count", std::to_string(g_pHyprOpenGL->m_failedAssetsNo)}}),
CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_ERROR);
}
}
@ -2905,10 +2903,8 @@ void CCompositor::checkMonitorOverlaps() {
for (const auto& m : m_monitors) {
if (!monitorRegion.copy().intersect(m->logicalBox()).empty()) {
Debug::log(ERR, "Monitor {}: detected overlap with layout", m->m_name);
g_pHyprNotificationOverlay->addNotification(std::format("Your monitor layout is set up incorrectly. Monitor {} overlaps with other monitor(s) in the "
"layout.\nPlease see the wiki (Monitors page) for more. This will cause issues.",
m->m_name),
CHyprColor{}, 15000, ICON_WARNING);
g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, {{"name", m->m_name}}), CHyprColor{}, 15000,
ICON_WARNING);
break;
}

View file

@ -27,6 +27,7 @@
#include "../managers/animation/DesktopAnimationManager.hpp"
#include "../managers/input/InputManager.hpp"
#include "../hyprerror/HyprError.hpp"
#include "../i18n/Engine.hpp"
#include "sync/SyncTimeline.hpp"
#include "time/Time.hpp"
#include "../desktop/LayerSurface.hpp"
@ -811,8 +812,8 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
if (!m_state.test())
continue;
auto errorMessage =
std::format("Monitor {} failed to set any requested modes, falling back to mode {:X0}@{:.2f}Hz", m_name, mode->pixelSize, mode->refreshRate / 1000.f);
auto errorMessage = I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_MODE_FAIL,
{{"name", m_name}, {"mode", std::format("{:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f)}});
Debug::log(WARN, errorMessage);
g_pHyprNotificationOverlay->addNotification(errorMessage, CHyprColor(0xff0000ff), 5000, ICON_WARNING);
@ -939,8 +940,10 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
Debug::log(ERR, "Invalid scale passed to monitor, {} found suggestion {}", m_scale, searchScale);
static auto PDISABLENOTIFICATION = CConfigValue<Hyprlang::INT>("misc:disable_scale_notification");
if (!*PDISABLENOTIFICATION)
g_pHyprNotificationOverlay->addNotification(std::format("Invalid scale passed to monitor: {}, using suggested scale: {}", m_scale, searchScale),
CHyprColor(1.0, 0.0, 0.0, 1.0), 5000, ICON_WARNING);
g_pHyprNotificationOverlay->addNotification(
I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,
{{"name", m_name}, {"scale", std::format("{:.2f}", m_scale)}, {"fixed_scale", std::format("{:.2f}", searchScale)}}),
CHyprColor(1.0, 0.0, 0.0, 1.0), 5000, ICON_WARNING);
}
m_scale = searchScale;
}

175
src/i18n/Engine.cpp Normal file
View file

@ -0,0 +1,175 @@
#include "Engine.hpp"
#include <hyprutils/i18n/I18nEngine.hpp>
using namespace I18n;
using namespace Hyprutils::I18n;
static SP<Hyprutils::I18n::CI18nEngine> huEngine;
static std::string localeStr;
//
SP<I18n::CI18nEngine> I18n::i18nEngine() {
static SP<I18n::CI18nEngine> engine = makeShared<I18n::CI18nEngine>();
return engine;
}
I18n::CI18nEngine::CI18nEngine() {
huEngine = makeShared<Hyprutils::I18n::CI18nEngine>();
huEngine->setFallbackLocale("en_US");
localeStr = huEngine->getSystemLocale().locale();
// en_US (English)
huEngine->registerEntry("en_US", TXT_KEY_ANR_TITLE, "Application Not Responding");
huEngine->registerEntry("en_US", TXT_KEY_ANR_CONTENT, "An application {title} - {class} is not responding.\nWhat do you want to do with it?");
huEngine->registerEntry("en_US", TXT_KEY_ANR_OPTION_TERMINATE, "Terminate");
huEngine->registerEntry("en_US", TXT_KEY_ANR_OPTION_WAIT, "Wait");
huEngine->registerEntry("en_US", TXT_KEY_ANR_PROP_UNKNOWN, "(unknown)");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "An application <b>{app}</b> is requesting an unknown permission.");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "An application <b>{app}</b> is trying to capture your screen.\n\nDo you want to allow it to?");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "An application <b>{app}</b> is trying to load a plugin: <b>{plugin}</b>.\n\nDo you want to allow it to?");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "A new keyboard has been detected: <b>{keyboard}</b>.\n\nDo you want to allow it to operate?");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(unknown)");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_TITLE, "Permission request");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Hint: you can set persistent rules for these in the Hyprland config file.");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_ALLOW, "Allow");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Allow and remember");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_ALLOW_ONCE, "Allow once");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_DENY, "Deny");
huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Unknown application (wayland client ID {wayland_id})");
huEngine->registerEntry(
"en_US", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,
"Your XDG_CURRENT_DESKTOP environment seems to be managed externally, and the current value is {value}.\nThis might cause issues unless it's intentional.");
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_NO_GUIUTILS,
"Your system does not have hyprland-guiutils installed. This is a runtime dependency for some dialogs. Consider installing it.");
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {
int assetsNo = std::stoi(vars.at("count"));
if (assetsNo <= 1)
return "Hyprland failed to load {count} essential asset, blame your distro's packager for doing a bad job at packaging!";
return "Hyprland failed to load {count} essential assets, blame your distro's packager for doing a bad job at packaging!";
});
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,
"Your monitor layout is set up incorrectly. Monitor {name} overlaps with other monitor(s) in the layout.\nPlease see the wiki (Monitors page) for "
"more. This <b>will</b> cause issues.");
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} failed to set any requested modes, falling back to mode {mode}.");
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Invalid scale passed to monitor {name}: {scale}, using suggested scale: {fixed_scale}");
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Failed to load plugin {name}: {error}");
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader reload failed, falling back to rgba/rgbx.");
huEngine->registerEntry("en_US", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: wide color gamut is enabled but the display is not in 10-bit mode.");
// it_IT (Italian)
huEngine->registerEntry("it_IT", TXT_KEY_ANR_TITLE, "L'applicazione non risponde");
huEngine->registerEntry("it_IT", TXT_KEY_ANR_CONTENT, "Un'applicazione {title} - {class} non risponde.\nCosa vuoi fare?");
huEngine->registerEntry("it_IT", TXT_KEY_ANR_OPTION_TERMINATE, "Termina");
huEngine->registerEntry("it_IT", TXT_KEY_ANR_OPTION_WAIT, "Attendi");
huEngine->registerEntry("it_IT", TXT_KEY_ANR_PROP_UNKNOWN, "(sconosciuto)");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Un'applicazione <b>{app}</b> richiede un'autorizzazione sconosciuta.");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Un'applicazione <b>{app}</b> sta provando a catturare il tuo schermo.\n\nGlie lo vuoi permettere?");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_PLUGIN,
"Un'applicazione <b>{app}</b> sta provando a caricare un plugin: <b>{plugin}</b>.\n\nGlie lo vuoi permettere?");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "È stata rilevata una nuova tastiera: <b>{keyboard}</b>.\n\nLe vuoi permettere di operare?");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(sconosciuto)");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_TITLE, "Richiesta di autorizzazione");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Consiglio: Puoi impostare una regola persistente nel tuo file di configurazione di Hyprland.");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_ALLOW, "Permetti");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Permetti e ricorda");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_ALLOW_ONCE, "Permetti una volta");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_DENY, "Nega");
huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Applicazione sconosciuta (wayland client ID {wayland_id})");
huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,
"L'ambiente XDG_CURRENT_DESKTOP sembra essere gestito esternamente, il valore attuale è {value}.\nSe non è voluto, potrebbe causare problemi.");
huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_NO_GUIUTILS,
"Sembra che hyprland-guiutils non sia installato. È una dipendenza richiesta per alcuni dialoghi che potresti voler installare.");
huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_FAILED_ASSETS,
"Hyprland non ha potuto caricare {count} asset, dai la colpa al packager della tua distribuzione per il suo cattivo lavoro!");
huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,
"I tuoi schermi sono configurati incorrettamente. Lo schermo {name} si sovrappone con altri nel layout.\nConsulta la wiki (voce Schermi) per "
"altre informazioni. Questo <b>causerà</b> problemi.");
huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Lo schermo {name} non ha potuto impostare alcuna modalità richiesta, sarà usata la modalità {mode}.");
huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,
"Fattore di scala non valido per lo schermo {name}: {scale}, utilizzando il fattore suggerito: {fixed_scale}");
huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Impossibile caricare il plugin {name}: {error}");
huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Impossibile ricaricare gli shader CM, sarà usato rgba/rgbx.");
huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Schermo {name}: la gamma di colori ampia è abilitata ma lo schermo non è in modalità 10-bit.");
// ja_JP (Japanese)
huEngine->registerEntry("ja_JP", TXT_KEY_ANR_TITLE, "アプリは応答しません");
huEngine->registerEntry("ja_JP", TXT_KEY_ANR_CONTENT, "アプリ {title} ー {class}は応答しません。\n何をしたいですか?");
huEngine->registerEntry("ja_JP", TXT_KEY_ANR_OPTION_TERMINATE, "強制終了");
huEngine->registerEntry("ja_JP", TXT_KEY_ANR_OPTION_WAIT, "待機");
huEngine->registerEntry("ja_JP", TXT_KEY_ANR_PROP_UNKNOWN, "(不明)");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "アプリ<b>{app}</b>は不明な許可を要求します。");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "アプリ<b>{app}</b>は画面へのアクセスを要求します。\n\n許可したいですか?");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "アプリ<b>{app}</b>は以下のプラグインをロード許可を要求します:<b>{plugin}</b>。\n\n許可したいですか?");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "新しいキーボードを見つけた:<b>{keyboard}</b>。\n\n稼働を許可したいですか?");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(不明)");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_TITLE, "許可要求");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "ヒントHyprlandのコンフィグで通常の許可や却下を設定できます。");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW, "許可");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "保存して許可");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW_ONCE, "一度許可");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_DENY, "却下");
huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "不明なアプリ (waylandクライアントID {wayland_id})");
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,
"エンバイアロンメント変数「XDG_CURRENT_DESKTOP」は外部から「{value}」に設定しました。\n意図的ではなければ、問題は発生可能性があります。");
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_NO_GUIUTILS, "システムにhyprland-guiutilsはインストールしていません。このパッケージをインストールしてください。");
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_FAILED_ASSETS,
"{count}つの根本的なアセットをロードできませんでした。これはパッケージャーのせいだから、パッケージャーに文句してください。");
huEngine->registerEntry(
"ja_JP", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,
"画面の位置設定は誤用です。画面{name}は他の画面の区域と重ね合わせます。\nウィキのモニターページで詳細を確認してください。これは<b>絶対に</b>問題になります。");
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "画面{name}は設定したモードを正常に受け入れませんでした。{mode}を使いました。");
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "画面{name}のスケールは無効:{scale}、代わりにおすすめのスケール{fixed_scale}を使いました。");
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "プラグイン{name}のロード失敗: {error}");
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CMシェーダーのリロード失敗、rgba/rgbxを使いました。");
huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "画面{name}広い色域は設定していますけど、画面は10ビットモードに設定されていません。");
// pl_PL (Polish)
huEngine->registerEntry("pl_PL", TXT_KEY_ANR_TITLE, "Aplikacja Nie Odpowiada");
huEngine->registerEntry("pl_PL", TXT_KEY_ANR_CONTENT, "Aplikacja {title} - {class} nie odpowiada.\nCo chcesz z nią zrobić?");
huEngine->registerEntry("pl_PL", TXT_KEY_ANR_OPTION_TERMINATE, "Zakończ proces");
huEngine->registerEntry("pl_PL", TXT_KEY_ANR_OPTION_WAIT, "Czekaj");
huEngine->registerEntry("pl_PL", TXT_KEY_ANR_PROP_UNKNOWN, "(nieznane)");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Aplikacja <b>{app}</b> prosi o pozwolenie na nieznany typ operacji.");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Aplikacja <b>{app}</b> prosi o dostęp do twojego ekranu.\n\nCzy chcesz jej na to pozwolić?");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Aplikacja <b>{app}</b> próbuje załadować plugin: <b>{plugin}</b>.\n\nCzy chcesz jej na to pozwolić?");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Wykryto nową klawiaturę: <b>{keyboard}</b>.\n\nCzy chcesz jej pozwolić operować?");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(nieznane)");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_TITLE, "Prośba o pozwolenie");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Podpowiedź: możesz ustawić stałe zasady w konfiguracji Hyprland'a.");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_ALLOW, "Zezwól");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Zezwól i zapamiętaj");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_ALLOW_ONCE, "Zezwól raz");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_DENY, "Odmów");
huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Nieznana aplikacja (ID klienta wayland {wayland_id})");
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,
"Zmienna środowiska XDG_CURRENT_DESKTOP została ustawiona zewnętrznie na {value}.\nTo może sprawić problemy, chyba, że jest celowe.");
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_NO_GUIUTILS, "Twój system nie ma hyprland-guiutils zainstalowanych, co może sprawić problemy. Zainstaluj pakiet.");
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) {
int assetsNo = std::stoi(vars.at("count"));
if (assetsNo == 1)
return "Nie udało się załadować {count} kluczowego zasobu, wiń swojego packager'a za robienie słabej roboty!";
return "Nie udało się załadować {count} kluczowych zasobów, wiń swojego packager'a za robienie słabej roboty!";
});
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,
"Pozycje twoich monitorów nie są ustawione poprawnie. Monitor {name} wchodzi na inne monitory.\nWejdź na wiki (stronę Monitory) "
"po więcej. To <b>będzie</b> sprawiać problemy.");
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} nie zaakceptował żadnego wybranego programu. Użyto {mode}.");
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Nieprawidłowa skala dla monitora {name}: {scale}, użyto proponowanej skali: {fixed_scale}");
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Nie udało się załadować plugin'a {name}: {error}");
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Nie udało się przeładować shader'a CM, użyto rgba/rgbx.");
huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: skonfigurowano szeroką głębię barw, ale monitor nie jest w trybie 10-bit.");
}
std::string I18n::CI18nEngine::localize(eI18nKeys key, const Hyprutils::I18n::translationVarMap& vars) {
return huEngine->localizeEntry(localeStr, key, vars);
}

50
src/i18n/Engine.hpp Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include "../helpers/memory/Memory.hpp"
#include <unordered_map>
#include <cstdint>
#include <string>
namespace I18n {
enum eI18nKeys : uint8_t {
TXT_KEY_ANR_TITLE = 0,
TXT_KEY_ANR_CONTENT,
TXT_KEY_ANR_OPTION_TERMINATE,
TXT_KEY_ANR_OPTION_WAIT,
TXT_KEY_ANR_PROP_UNKNOWN,
TXT_KEY_PERMISSION_REQUEST_UNKNOWN,
TXT_KEY_PERMISSION_REQUEST_SCREENCOPY,
TXT_KEY_PERMISSION_REQUEST_PLUGIN,
TXT_KEY_PERMISSION_REQUEST_KEYBOARD,
TXT_KEY_PERMISSION_UNKNOWN_NAME,
TXT_KEY_PERMISSION_TITLE,
TXT_KEY_PERMISSION_PERSISTENCE_HINT,
TXT_KEY_PERMISSION_ALLOW,
TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER,
TXT_KEY_PERMISSION_ALLOW_ONCE,
TXT_KEY_PERMISSION_DENY,
TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP,
TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP,
TXT_KEY_NOTIF_NO_GUIUTILS,
TXT_KEY_NOTIF_FAILED_ASSETS,
TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT,
TXT_KEY_NOTIF_MONITOR_MODE_FAIL,
TXT_KEY_NOTIF_MONITOR_AUTO_SCALE,
TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN,
TXT_KEY_NOTIF_CM_RELOAD_FAILED,
TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B,
};
class CI18nEngine {
public:
CI18nEngine();
~CI18nEngine() = default;
std::string localize(eI18nKeys key, const std::unordered_map<std::string, std::string>& vars = {});
};
SP<CI18nEngine> i18nEngine();
};

View file

@ -8,6 +8,7 @@
#include "./eventLoop/EventLoopManager.hpp"
#include "../config/ConfigValue.hpp"
#include "../xwayland/XSurface.hpp"
#include "../i18n/Engine.hpp"
using namespace Hyprutils::OS;
@ -83,7 +84,7 @@ void CANRManager::onTick() {
if (data->missedResponses >= *PANRTHRESHOLD) {
if (!data->isRunning() && !data->dialogSaidWait) {
data->runDialog("Application Not Responding", firstWindow->m_title, firstWindow->m_class, data->getPid());
data->runDialog(firstWindow->m_title, firstWindow->m_class, data->getPid());
for (const auto& w : g_pCompositor->m_windows) {
if (!w->m_isMapped)
@ -176,16 +177,29 @@ CANRManager::SANRData::~SANRData() {
killDialog();
}
void CANRManager::SANRData::runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID) {
void CANRManager::SANRData::runDialog(const std::string& appName, const std::string appClass, pid_t dialogWmPID) {
if (dialogBox && dialogBox->isRunning())
killDialog();
dialogBox = CAsyncDialogBox::create(title,
std::format("Application {} with class of {} is not responding.\nWhat do you want to do with it?", appName.empty() ? "unknown" : appName,
appClass.empty() ? "unknown" : appClass),
std::vector<std::string>{"Terminate", "Wait"});
const auto OPTION_TERMINATE_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_TERMINATE, {});
const auto OPTION_WAIT_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_WAIT, {});
dialogBox->open()->then([dialogWmPID, this](SP<CPromiseResult<std::string>> r) {
dialogBox =
CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_TITLE, {}),
I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_CONTENT,
{
//
{"class", appClass.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appClass}, //
{"title", appName.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appName} //
}),
std::vector<std::string>{
//
OPTION_TERMINATE_STR, //
OPTION_WAIT_STR //
} //
);
dialogBox->open()->then([dialogWmPID, this, OPTION_TERMINATE_STR, OPTION_WAIT_STR](SP<CPromiseResult<std::string>> r) {
if (r->hasError()) {
Debug::log(ERR, "CANRManager::SANRData::runDialog: error spawning dialog");
return;
@ -193,9 +207,9 @@ void CANRManager::SANRData::runDialog(const std::string& title, const std::strin
const auto& result = r->result();
if (result.starts_with("Terminate"))
if (result.starts_with(OPTION_TERMINATE_STR))
::kill(dialogWmPID, SIGKILL);
else if (result.starts_with("Wait"))
else if (result.starts_with(OPTION_WAIT_STR))
dialogSaidWait = true;
else
Debug::log(ERR, "CANRManager::SANRData::runDialog: lambda: unrecognized result: {}", result);

View file

@ -39,7 +39,7 @@ class CANRManager {
bool dialogSaidWait = false;
SP<CAsyncDialogBox> dialogBox;
void runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID);
void runDialog(const std::string& appName, const std::string appClass, pid_t dialogWmPID);
bool isRunning();
void killDialog();
bool isDefunct() const;

View file

@ -5,6 +5,7 @@
#include "../../Compositor.hpp"
#include "../../config/ConfigValue.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include "../../i18n/Engine.hpp"
#include <hyprutils/string/String.hpp>
using namespace Hyprutils::String;
@ -57,17 +58,6 @@ static const char* permissionToString(eDynamicPermissionType type) {
return "error";
}
static const char* permissionToHumanString(eDynamicPermissionType type) {
switch (type) {
case PERMISSION_TYPE_UNKNOWN: return "An application <b>{}</b> is requesting an unknown permission.";
case PERMISSION_TYPE_SCREENCOPY: return "An application <b>{}</b> is trying to capture your screen.<br/><br/>Do you want to allow it to do so?";
case PERMISSION_TYPE_PLUGIN: return "An application <b>{}</b> is trying to load a plugin: <b>{}</b>.<br/><br/>Do you want to load it?";
case PERMISSION_TYPE_KEYBOARD: return "A new keyboard has been plugged in: {}.<br/><br/>Do you want to allow it to operate?";
}
return "error";
}
static const char* specialPidToString(eSpecialPidTypes type) {
switch (type) {
case SPECIAL_PID_TYPE_CONFIG: return "config";
@ -244,39 +234,41 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s
rule->m_pid = pid;
std::string description = "";
std::string appName = "";
if (binaryPath.empty())
description = std::format(std::runtime_format(permissionToHumanString(type)), std::format("unknown application (wayland client ID 0x{:x})", rc<uintptr_t>(client)));
appName = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, {{"wayland_id", std::format("{:x}", rc<uintptr_t>(client))}});
else if (client) {
std::string binaryName = binaryPath.contains("/") ? binaryPath.substr(binaryPath.find_last_of('/') + 1) : binaryPath;
description = std::format(std::runtime_format(permissionToHumanString(type)), std::format("{}</b> ({})", binaryName, binaryPath));
appName = binaryPath.contains("/") ? binaryPath.substr(binaryPath.find_last_of('/') + 1) : binaryPath;
} else {
std::string lookup = "";
if (pid < 0)
lookup = specialPidToString(sc<eSpecialPidTypes>(pid));
appName = specialPidToString(sc<eSpecialPidTypes>(pid));
else {
const auto LOOKUP = binaryNameForPid(pid);
lookup = LOOKUP.value_or("Unknown");
}
if (type == PERMISSION_TYPE_PLUGIN) {
const auto LOOKUP = binaryNameForPid(pid);
description = std::format(std::runtime_format(permissionToHumanString(type)), lookup, binaryPath);
} else {
const auto LOOKUP = binaryNameForPid(pid);
description = std::format(std::runtime_format(permissionToHumanString(type)), lookup, binaryPath);
appName = LOOKUP.value_or(I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_UNKNOWN_NAME));
}
}
std::string description = "";
switch (rule->m_type) {
case PERMISSION_TYPE_SCREENCOPY: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}}); break;
case PERMISSION_TYPE_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}, {"plugin", binaryPath}}); break;
case PERMISSION_TYPE_KEYBOARD: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_KEYBOARD, {{"keyboard", binaryPath}}); break;
case PERMISSION_TYPE_UNKNOWN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_UNKNOWN, {{"app", appName}}); break;
}
std::vector<std::string> options;
const auto ALLOW = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW);
const auto ALLOW_AND_REMEMBER = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER);
const auto ALLOW_ONCE = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW_ONCE);
const auto DENY = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_DENY);
if (!binaryPath.empty() && client) {
description += "<br/><br/><i>Hint: you can set persistent rules for these in the Hyprland config file.</i>";
options = {"Deny", "Allow and remember app", "Allow once"};
description += std::format("<br/><br/><i>{}</i>", I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_PERSISTENCE_HINT));
options = {DENY, ALLOW_AND_REMEMBER, ALLOW_ONCE};
} else
options = {"Deny", "Allow"};
options = {DENY, ALLOW};
rule->m_dialogBox = CAsyncDialogBox::create("Permission request", description, options);
rule->m_dialogBox = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_TITLE), description, options);
rule->m_dialogBox->m_priority = true;
if (!rule->m_dialogBox) {
@ -286,7 +278,7 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s
}
rule->m_promise = rule->m_dialogBox->open();
rule->m_promise->then([r = WP<CDynamicPermissionRule>(rule), binaryPath](SP<CPromiseResult<std::string>> pr) {
rule->m_promise->then([r = WP<CDynamicPermissionRule>(rule), binaryPath, ALLOW, ALLOW_AND_REMEMBER, ALLOW_ONCE, DENY](SP<CPromiseResult<std::string>> pr) {
if (!r)
return;
@ -303,15 +295,15 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s
Debug::log(TRACE, "CDynamicPermissionRule: user returned {}", result);
if (result.starts_with("Allow once"))
if (result.starts_with(ALLOW_ONCE))
r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW;
else if (result.starts_with("Deny")) {
else if (result.starts_with(DENY)) {
r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_DENY;
r->m_binaryPath = binaryPath;
} else if (result.starts_with("Allow and remember")) {
} else if (result.starts_with(ALLOW_AND_REMEMBER)) {
r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW;
r->m_binaryPath = binaryPath;
} else if (result.starts_with("Allow"))
} else if (result.starts_with(ALLOW))
r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW;
if (r->m_promiseResolverForExternal)

View file

@ -9,6 +9,7 @@
#include "../managers/eventLoop/EventLoopManager.hpp"
#include "../managers/permissions/DynamicPermissionManager.hpp"
#include "../debug/HyprNotificationOverlay.hpp"
#include "../i18n/Engine.hpp"
CPluginSystem::CPluginSystem() {
g_pFunctionHookSystem = makeUnique<CHookSystem>();
@ -224,7 +225,8 @@ void CPluginSystem::updateConfigPlugins(const std::vector<std::string>& plugins,
if (result->hasError()) {
const auto NAME = path.contains('/') ? path.substr(path.find_last_of('/') + 1) : path;
Debug::log(ERR, "CPluginSystem::updateConfigPlugins: failed to load plugin {}: {}", NAME, result->error());
g_pHyprNotificationOverlay->addNotification(std::format("Failed to load plugin {}: {}", NAME, result->error()), CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR);
g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, {{"name", NAME}, {"error", result->error()}}),
CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR);
return;
}

View file

@ -24,6 +24,7 @@
#include "../managers/CursorManager.hpp"
#include "../helpers/fs/FsUtils.hpp"
#include "../helpers/MainLoopExecutor.hpp"
#include "../i18n/Engine.hpp"
#include "debug/HyprNotificationOverlay.hpp"
#include "hyprerror/HyprError.hpp"
#include "pass/TexPassElement.hpp"
@ -1006,7 +1007,7 @@ bool CHyprOpenGLImpl::initShaders() {
prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCCM, true, true);
if (m_shadersInitialized && m_cmSupported && prog == 0)
g_pHyprNotificationOverlay->addNotification("CM shader reload failed, falling back to rgba/rgbx", CHyprColor{}, 15000, ICON_WARNING);
g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_CM_RELOAD_FAILED), CHyprColor{}, 15000, ICON_WARNING);
m_cmSupported = prog > 0;
if (m_cmSupported) {

View file

@ -26,6 +26,7 @@
#include "../hyprerror/HyprError.hpp"
#include "../debug/HyprDebugOverlay.hpp"
#include "../debug/HyprNotificationOverlay.hpp"
#include "../i18n/Engine.hpp"
#include "helpers/CursorShapes.hpp"
#include "helpers/Monitor.hpp"
#include "pass/TexPassElement.hpp"
@ -1576,7 +1577,8 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {
Debug::log(WARN, "Wide color gamut is enabled but the display is not in 10bit mode");
static bool shown = false;
if (!shown) {
g_pHyprNotificationOverlay->addNotification("Wide color gamut is enabled but the display is not in 10bit mode", CHyprColor{}, 15000, ICON_WARNING);
g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, {{"name", pMonitor->m_name}}), CHyprColor{}, 15000,
ICON_WARNING);
shown = true;
}
}