diff --git a/CursorLang/Interop/CaretNative.cs b/CursorLang/Interop/CaretNative.cs
index 5310d5d..d2188fa 100644
--- a/CursorLang/Interop/CaretNative.cs
+++ b/CursorLang/Interop/CaretNative.cs
@@ -6,12 +6,12 @@ using Accessibility;
namespace CursorLang.Interop;
///
-/// Определяет положение каретки в активном поле ввода — в том числе в чужом приложении.
+/// Locates the caret in the active input field — including one in another application.
///
///
-/// Единого способа нет: классические Win32-приложения заводят системную каретку,
-/// а Chrome, Electron и прочие рисуют её сами и сообщают положение только через
-/// средства доступности. Поэтому сначала спрашиваем систему, затем — приложение.
+/// There is no single way to do it: classic Win32 applications create a system caret,
+/// while Chrome, Electron and others draw it themselves and report its position only
+/// through accessibility interfaces. So we ask the system first, then the application.
///
internal static class CaretNative
{
@@ -32,8 +32,8 @@ internal static class CaretNative
private const int CHILDID_SELF = 0;
///
- /// Прямоугольник каретки в пикселях экрана или null, если активное
- /// приложение не сообщает её положение.
+ /// The caret rectangle in screen pixels, or null when the active
+ /// application does not report its position.
///
internal static PopupWindowNative.Rect? TryGetCaretRect()
{
@@ -50,13 +50,13 @@ internal static class CaretNative
}
///
- /// Отсеивает заведомо неверные координаты.
+ /// Filters out obviously wrong coordinates.
///
///
- /// Часть приложений отдаёт положение каретки в своей системе координат либо
- /// без учёта масштаба экрана, и подсказка уезжает далеко от поля ввода.
- /// Каретка обязана находиться внутри окна ввода — это и проверяем, а перед
- /// отказом пробуем истолковать координаты как немасштабированные.
+ /// Some applications report the caret position in their own coordinate system or
+ /// without accounting for display scaling, and the popup ends up far from the input
+ /// field. The caret must be inside the input window — that is what we check, and
+ /// before giving up we try to read the coordinates as unscaled ones.
///
private static PopupWindowNative.Rect? Validate(PopupWindowNative.Rect caret, IntPtr hwndFocus)
{
@@ -86,16 +86,16 @@ internal static class CaretNative
inner.Left >= outer.Left && inner.Right <= outer.Right &&
inner.Top >= outer.Top && inner.Bottom <= outer.Bottom;
- /// Сколько ждём ответа от чужого приложения по UI Automation.
+ /// How long we wait for another application to answer over UI Automation.
private static readonly TimeSpan AutomationTimeout = TimeSpan.FromMilliseconds(150);
- // Браузеры и другие приложения на своих движках рисуют каретку сами и
- // сообщают её положение только через UI Automation. Запрос идёт в чужой
- // процесс, поэтому он самый медленный и стоит последним
+ // Browsers and other applications with their own rendering engines draw the caret
+ // themselves and report its position only through UI Automation. The request goes
+ // into another process, so it is the slowest one and comes last
private static PopupWindowNative.Rect? TryGetAutomationCaret()
{
- // Зависшее приложение не должно подвешивать подсказку вместе с собой:
- // ждём ответ ограниченное время, иначе показываем у курсора
+ // A hung application must not hang the popup along with it: we wait for the
+ // answer for a limited time, otherwise we show the popup at the cursor
Task query = Task.Run(QueryAutomationCaret);
return query.Wait(AutomationTimeout) ? query.Result : null;
}
@@ -117,8 +117,8 @@ internal static class CaretNative
return null;
}
- // У каретки выделение пустое и прямоугольника не имеет,
- // поэтому расширяем его до ближайшего символа
+ // The caret has an empty selection and therefore no rectangle,
+ // so we expand it to the nearest character
TextPatternRange range = selection[0].Clone();
range.ExpandToEnclosingUnit(TextUnit.Character);
@@ -141,12 +141,12 @@ internal static class CaretNative
or InvalidOperationException
or COMException)
{
- // Приложение закрылось или не отвечает — подсказку это ронять не должно
+ // The application closed or stopped responding — that must not take the popup down
return null;
}
}
- // Системная каретка: координаты приходят относительно окна, которому она принадлежит
+ // The system caret: its coordinates come relative to the window that owns it
private static PopupWindowNative.Rect? TryGetSystemCaret(ForegroundInputNative.GuiThreadInfo info)
{
if (info.hwndCaret == IntPtr.Zero || IsEmpty(info.rcCaret))
@@ -170,7 +170,7 @@ internal static class CaretNative
};
}
- // Каретка через средства доступности: сюда попадают браузеры и Electron
+ // The caret through accessibility interfaces: this is where browsers and Electron land
private static PopupWindowNative.Rect? TryGetAccessibleCaret(IntPtr hwndFocus)
{
if (hwndFocus == IntPtr.Zero)
@@ -199,7 +199,7 @@ internal static class CaretNative
}
catch (COMException)
{
- // Приложение объявило поддержку, но положение не отдало
+ // The application declared support but did not report the position
return null;
}
finally
@@ -208,7 +208,7 @@ internal static class CaretNative
}
}
- // Когда каретки нет, её прямоугольник приходит нулевой высоты.
- // Судим только по высоте: нулевые координаты — это обычное начало пустого поля
- private static bool IsEmpty(PopupWindowNative.Rect rect) => rect.Bottom - rect.Top <= 0;
+ // When there is no caret, its rectangle comes back with zero height.
+ // We judge by height alone: zero coordinates are a normal start of an empty field
+ internal static bool IsEmpty(PopupWindowNative.Rect rect) => rect.Bottom - rect.Top <= 0;
}
diff --git a/CursorLang/Interop/ForegroundInputNative.cs b/CursorLang/Interop/ForegroundInputNative.cs
index 574a518..002ac64 100644
--- a/CursorLang/Interop/ForegroundInputNative.cs
+++ b/CursorLang/Interop/ForegroundInputNative.cs
@@ -3,8 +3,8 @@ using System.Runtime.InteropServices;
namespace CursorLang.Interop;
///
-/// Сведения о вводе в активном приложении: какое окно держит фокус клавиатуры
-/// и где находится каретка.
+/// Input details of the active application: which window holds keyboard focus
+/// and where the caret is.
///
internal static class ForegroundInputNative
{
@@ -26,12 +26,12 @@ internal static class ForegroundInputNative
private static extern bool GetGUIThreadInfo(uint idThread, ref GuiThreadInfo lpgui);
///
- /// Состояние ввода потока переднего плана.
+ /// The input state of the foreground thread.
///
///
- /// Нулевой идентификатор потока не случаен: у современных приложений окно
- /// верхнего уровня и окно ввода живут в разных потоках, и спрашивать нужно
- /// именно про передний план целиком.
+ /// The zero thread identifier is not accidental: in modern applications the
+ /// top-level window and the input window live in different threads, and the
+ /// question has to be about the foreground as a whole.
///
internal static bool TryGetInfo(out GuiThreadInfo info)
{
diff --git a/CursorLang/Interop/ForegroundPermissionNative.cs b/CursorLang/Interop/ForegroundPermissionNative.cs
index a3a33b7..ff169eb 100644
--- a/CursorLang/Interop/ForegroundPermissionNative.cs
+++ b/CursorLang/Interop/ForegroundPermissionNative.cs
@@ -3,21 +3,21 @@ using System.Runtime.InteropServices;
namespace CursorLang.Interop;
///
-/// Право выводить окно на передний план.
+/// The right to bring a window to the foreground.
///
internal static class ForegroundPermissionNative
{
- /// ASFW_ANY — право получает любой процесс.
+ /// ASFW_ANY — any process gets the right.
private const uint AnyProcess = 0xFFFFFFFF;
///
- /// Уступает своё право вывести окно на передний план другим процессам.
+ /// Gives up our right to bring a window to the foreground in favour of other processes.
///
///
- /// Поменять окно переднего плана Windows разрешает не всякому: нужно быть
- /// процессом, с которым пользователь работал последним. Давно запущенный
- /// экземпляр приложения к таким не относится, и его окно всплывёт только
- /// если правом поделится процесс, который пользователь запустил только что.
+ /// Windows does not let just anyone change the foreground window: you have to be
+ /// the process the user interacted with last. An instance started long ago is not
+ /// one of those, and its window will come up only if the right is shared by the
+ /// process the user has just launched.
///
internal static void GrantToAnyProcess() => AllowSetForegroundWindow(AnyProcess);
diff --git a/CursorLang/Interop/KeyboardLayoutNative.cs b/CursorLang/Interop/KeyboardLayoutNative.cs
index cd90b32..d425887 100644
--- a/CursorLang/Interop/KeyboardLayoutNative.cs
+++ b/CursorLang/Interop/KeyboardLayoutNative.cs
@@ -3,16 +3,16 @@ using System.Runtime.InteropServices;
namespace CursorLang.Interop;
///
-/// Win32 API для определения раскладки активного приложения.
+/// Win32 API for reading the layout of the active application.
///
internal static class KeyboardLayoutNative
{
private const uint WmInputLangChangeRequest = 0x0050;
- /// Взять следующую раскладку из системного списка.
+ /// Take the next layout from the system list.
private static readonly IntPtr InputLangChangeForward = new(0x0002);
- /// HKL_NEXT — та же просьба на языке старых версий Windows.
+ /// HKL_NEXT — the same request in the language of older Windows versions.
private static readonly IntPtr HklNext = new(1);
[DllImport("user32.dll")]
@@ -28,17 +28,17 @@ internal static class KeyboardLayoutNative
private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
///
- /// Раскладка, которой сейчас печатает пользователь.
+ /// The layout the user is currently typing with.
///
internal static int GetActiveLocaleId() => GetLocaleIdOf(GetInputWindow());
///
- /// Просит активное приложение перейти на следующую раскладку из системного списка.
+ /// Asks the active application to switch to the next layout from the system list.
///
///
- /// Синтезировать системное сочетание вроде Alt+Shift не годится: его назначение
- /// пользователь меняет в настройках Windows и может отключить вовсе. Просьба
- /// сообщением от таких настроек не зависит и работает в чужом процессе.
+ /// Synthesizing a system shortcut such as Alt+Shift will not do: the user can
+ /// reassign it in the Windows settings or turn it off entirely. A request sent as
+ /// a message does not depend on those settings and works in another process.
///
internal static void RequestNextLayout()
{
@@ -48,19 +48,19 @@ internal static class KeyboardLayoutNative
return;
}
- // Оба параметра означают одно и то же: разные версии Windows и разные
- // библиотеки интерфейса смотрят то на флаг, то на lParam
+ // Both parameters mean the same thing: different Windows versions and different
+ // UI frameworks look either at the flag or at lParam
PostMessage(target, WmInputLangChangeRequest, InputLangChangeForward, HklNext);
}
///
- /// Окно, которому принадлежит ввод с клавиатуры.
+ /// The window that owns keyboard input.
///
///
- /// Спрашиваем окно с фокусом клавиатуры, а не окно верхнего плана: у Блокнота
- /// Windows 11, меню «Пуск» и прочих приложений на WinUI поле ввода живёт в
- /// отдельном потоке, и раскладка меняется только у него. У потока главного
- /// окна она остаётся прежней, и переключение проходит незамеченным.
+ /// We ask the window with keyboard focus rather than the foreground window: in
+ /// Windows 11 Notepad, the Start menu and other WinUI applications the input field
+ /// lives in a separate thread, and the layout changes only for that thread. For the
+ /// main window's thread it stays the same, and the switch goes unnoticed.
///
private static IntPtr GetInputWindow()
{
@@ -74,8 +74,8 @@ internal static class KeyboardLayoutNative
}
///
- /// Идентификатор локали для окна. Раскладка в Windows привязана к потоку,
- /// поэтому так её видно у любого приложения, а не только у своего.
+ /// The locale identifier for a window. In Windows the layout is bound to a thread,
+ /// so this reveals it for any application, not only for our own.
///
internal static int GetLocaleIdOf(IntPtr hWnd)
{
diff --git a/CursorLang/Interop/LowLevelKeyboardHook.cs b/CursorLang/Interop/LowLevelKeyboardHook.cs
index 1fc3ae8..b0dc779 100644
--- a/CursorLang/Interop/LowLevelKeyboardHook.cs
+++ b/CursorLang/Interop/LowLevelKeyboardHook.cs
@@ -3,22 +3,22 @@ using System.Runtime.InteropServices;
namespace CursorLang.Interop;
///
-/// Системный перехватчик клавиатуры (WH_KEYBOARD_LL): видит нажатия во всех
-/// приложениях и умеет не пропускать их дальше.
+/// A system keyboard hook (WH_KEYBOARD_LL): sees key presses in every application
+/// and can keep them from going any further.
///
///
-/// Ставится только низкоуровневый хук: обычный WH_KEYBOARD требует внедрения
-/// DLL в чужие процессы, что для managed-кода невозможно. Обратный вызов
-/// приходит на тот поток, который поставил хук, и поток обязан крутить цикл
-/// сообщений — отсюда требование ставить хук из потока пользовательского
-/// интерфейса. Возврат управления затягивать нельзя: по истечении системного
-/// тайм-аута Windows молча снимает хук.
+/// Only the low-level hook is installed: a regular WH_KEYBOARD requires injecting a
+/// DLL into other processes, which is impossible for managed code. The callback
+/// arrives on the thread that installed the hook, and that thread must pump a message
+/// loop — hence the requirement to install the hook from the user interface thread.
+/// Returning control must not be delayed: once the system timeout expires, Windows
+/// silently removes the hook.
///
internal sealed class LowLevelKeyboardHook : IDisposable
{
///
- /// Обработчик события клавиши. Возвращает true, если событие нужно
- /// проглотить — тогда приложение переднего плана его не увидит.
+ /// A key event handler. Returns true when the event must be swallowed —
+ /// then the foreground application will not see it.
///
internal delegate bool KeyFilter(int virtualKey, bool isKeyDown);
@@ -30,14 +30,14 @@ internal sealed class LowLevelKeyboardHook : IDisposable
private const int WmSysKeyDown = 0x0104;
private const int WmSysKeyUp = 0x0105;
- /// Событие пришло не от живого нажатия, а из SendInput.
+ /// The event came from SendInput rather than from a real key press.
private const uint LowLevelKeyHookFlagInjected = 0x10;
private readonly KeyFilter _filter;
- // Делегат живёт в поле не для удобства: ссылку на него держит только Win32,
- // о которой сборщик мусора не знает, и без поля хук перестаёт работать
- // через случайное время
+ // The delegate lives in a field not for convenience: the only reference to it is
+ // held by Win32, which the garbage collector knows nothing about, and without the
+ // field the hook stops working after a random amount of time
private readonly HookProc _callback;
private IntPtr _handle;
@@ -53,7 +53,7 @@ internal sealed class LowLevelKeyboardHook : IDisposable
internal bool IsInstalled => _handle != IntPtr.Zero;
///
- /// Ставит хук. Возвращает false, если система отказала.
+ /// Installs the hook. Returns false when the system refuses.
///
internal bool Install()
{
@@ -104,14 +104,14 @@ internal sealed class LowLevelKeyboardHook : IDisposable
var data = Marshal.PtrToStructure(lParam);
- // Синтетический ввод присылают экранные клавиатуры, программы
- // автозамены и средства автоматизации: подменять их работу не наше дело
+ // Synthetic input comes from on-screen keyboards, text expanders and
+ // automation tools: overriding what they do is none of our business
bool injected = (data.flags & LowLevelKeyHookFlagInjected) != 0;
if ((isKeyDown || isKeyUp) && !injected && _filter((int)data.vkCode, isKeyDown))
{
- // Ненулевой результат вместо CallNextHookEx обрывает цепочку: событие
- // не дойдёт ни до приложения, ни до обработчика регистра в Windows
+ // A non-zero result instead of CallNextHookEx breaks the chain: the event
+ // will reach neither the application nor the Windows caps-lock handler
return 1;
}
diff --git a/CursorLang/Interop/PopupWindowNative.cs b/CursorLang/Interop/PopupWindowNative.cs
index 955b350..8d22efb 100644
--- a/CursorLang/Interop/PopupWindowNative.cs
+++ b/CursorLang/Interop/PopupWindowNative.cs
@@ -3,8 +3,8 @@ using System.Runtime.InteropServices;
namespace CursorLang.Interop;
///
-/// Win32 API для окна-подсказки: стили, позиционирование у курсора
-/// и масштаб монитора, на котором курсор находится.
+/// Win32 API for the popup window: styles, positioning near the cursor
+/// and the scale of the monitor the cursor is on.
///
internal static class PopupWindowNative
{
@@ -62,9 +62,9 @@ internal static class PopupWindowNative
}
private const int GWL_EXSTYLE = -20;
- // Окно не забирает фокус у активного приложения
+ // The window does not take focus away from the active application
private const int WS_EX_NOACTIVATE = 0x08000000;
- // И не попадает в Alt+Tab
+ // And does not show up in Alt+Tab
private const int WS_EX_TOOLWINDOW = 0x00000080;
private const uint SWP_NOSIZE = 0x0001;
@@ -81,8 +81,8 @@ internal static class PopupWindowNative
}
///
- /// Подсказка всплывает поверх чужих приложений, поэтому она не должна
- /// ни активироваться сама, ни отбирать фокус ввода у активного окна.
+ /// The popup shows up on top of other applications, so it must neither
+ /// activate itself nor steal input focus from the active window.
///
internal static void MakePassive(IntPtr hWnd)
{
@@ -91,10 +91,10 @@ internal static class PopupWindowNative
}
///
- /// Двигает окно в точку экрана, не меняя размер и порядок окон.
- /// Координаты — физические пиксели: у мониторов разный масштаб, а
- /// Window.Left/Top пересчитываются по DPI того монитора, где окно сейчас,
- /// и на соседнем мониторе дают промах.
+ /// Moves the window to a screen point without changing its size or z-order.
+ /// The coordinates are physical pixels: monitors have different scaling, while
+ /// Window.Left/Top are converted using the DPI of the monitor the window is on
+ /// right now, which misses the target on a neighbouring monitor.
///
internal static void MoveTo(IntPtr hWnd, int x, int y)
{
@@ -102,26 +102,27 @@ internal static class PopupWindowNative
}
///
- /// Задаёт положение и размер окна в физических пикселях.
+ /// Sets the window position and size in physical pixels.
///
///
- /// Размер выставляется именно так, а не через Width/Height: при первом показе
- /// окно ещё подчиняется системному минимальному размеру окна (SM_CXMIN×SM_CYMIN)
- /// и подсказка выходит заметно крупнее текста. К моменту вызова окно уже
- /// показано и стало popup-окном, на которое это ограничение не действует.
+ /// The size is set this way rather than through Width/Height: on the first show the
+ /// window is still subject to the system minimum window size (SM_CXMIN×SM_CYMIN)
+ /// and the popup comes out noticeably larger than its text. By the time this is
+ /// called the window is already shown and has become a popup window, which that
+ /// restriction does not apply to.
///
internal static void SetBounds(IntPtr hWnd, int x, int y, int width, int height)
{
SetWindowPos(hWnd, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
}
- /// Масштаб монитора, на котором находится точка (1.0 при 96 DPI).
+ /// The scale of the monitor the point is on (1.0 at 96 DPI).
internal static double GetScaleAt(Point point) =>
GetScaleOf(MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST));
///
- /// Рабочая область монитора с активным окном — без панели задач — и его масштаб.
- /// Именно на этом мониторе пользователь сейчас работает.
+ /// The work area of the monitor holding the active window — without the taskbar —
+ /// and its scale. That is the monitor the user is working on right now.
///
internal static (Rect WorkArea, double Scale) GetActiveMonitorWorkArea()
{
diff --git a/CursorLang/Interop/WindowPlacementNative.cs b/CursorLang/Interop/WindowPlacementNative.cs
index 954b751..d4fff30 100644
--- a/CursorLang/Interop/WindowPlacementNative.cs
+++ b/CursorLang/Interop/WindowPlacementNative.cs
@@ -3,14 +3,14 @@ using System.Runtime.InteropServices;
namespace CursorLang.Interop;
///
-/// Win32 API для размещения окна настроек: его собственные границы и рабочая
-/// область монитора, на который окно просят поставить.
+/// Win32 API for placing the settings window: its own bounds and the work area
+/// of the monitor the window is asked to be put on.
///
///
-/// Границы берутся у системы, а не из Window.Left/Top/Width/Height:
-/// высота окна подстраивается под содержимое, и WPF пересчитывает эти свойства
-/// по DPI монитора, а рабочая область монитора приходит в пикселях. Считать
-/// центр в одних единицах проще, чем переводить туда-обратно.
+/// The bounds are taken from the system rather than from Window.Left/Top/Width/Height:
+/// the window height adapts to its content, and WPF converts those properties using the
+/// monitor DPI, while the monitor work area comes in pixels. Computing the centre in a
+/// single unit is simpler than converting back and forth.
///
internal static class WindowPlacementNative
{
@@ -34,13 +34,13 @@ internal static class WindowPlacementNative
private const uint MONITOR_DEFAULTTONEAREST = 2;
- /// Границы окна в пикселях экрана — вместе с рамкой и заголовком.
+ /// The window bounds in screen pixels — including the frame and the title bar.
internal static PopupWindowNative.Rect? TryGetBounds(IntPtr hWnd) =>
GetWindowRect(hWnd, out PopupWindowNative.Rect bounds) ? bounds : null;
///
- /// Рабочая область — без панели задач — того монитора, на котором
- /// прямоугольник находится целиком или хотя бы большей частью.
+ /// The work area — without the taskbar — of the monitor that holds the
+ /// rectangle entirely, or at least most of it.
///
internal static PopupWindowNative.Rect? TryGetWorkAreaNear(PopupWindowNative.Rect rect)
{
diff --git a/CursorLang/Interop/WindowThemeNative.cs b/CursorLang/Interop/WindowThemeNative.cs
index ff457fd..2ed1f71 100644
--- a/CursorLang/Interop/WindowThemeNative.cs
+++ b/CursorLang/Interop/WindowThemeNative.cs
@@ -3,8 +3,8 @@ using System.Runtime.InteropServices;
namespace CursorLang.Interop;
///
-/// Оформление рамки окна средствами системы: заголовок рисует Windows,
-/// и в тёмной теме его нужно переключать отдельно от содержимого окна.
+/// Window frame styling by the system: the title bar is drawn by Windows,
+/// and in the dark theme it has to be switched separately from the window content.
///
internal static class WindowThemeNative
{
@@ -14,8 +14,8 @@ internal static class WindowThemeNative
private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
///
- /// Перекрашивает заголовок окна. На сборках Windows 10 до 2004 атрибут
- /// не поддерживается — заголовок просто останется светлым.
+ /// Recolours the window title bar. On Windows 10 builds before 2004 the attribute
+ /// is not supported — the title bar simply stays light.
///
internal static void SetDarkTitleBar(IntPtr hWnd, bool isDark)
{
diff --git a/CursorLang/Models/AppSettings.cs b/CursorLang/Models/AppSettings.cs
index 94f8bb4..03a1031 100644
--- a/CursorLang/Models/AppSettings.cs
+++ b/CursorLang/Models/AppSettings.cs
@@ -5,67 +5,68 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace CursorLang.Models;
///
-/// Настройки приложения. Все изменения применяются на лету: подсказка и окно
-/// настроек привязаны к этим свойствам, а SettingsService сохраняет их на диск.
+/// The application settings. Every change applies on the fly: the popup and the
+/// settings window are bound to these properties, and SettingsService saves
+/// them to disk.
///
public sealed partial class AppSettings : ObservableObject
{
- /// Язык интерфейса в виде кода культуры: «ru», «en».
+ /// The interface language as a culture code: "ru", "en".
[ObservableProperty]
private string _language = "en";
- /// Оформление окна настроек.
+ /// The look of the settings window.
[ObservableProperty]
private AppTheme _theme = AppTheme.System;
[ObservableProperty]
private PopupPlacementMode _placementMode = PopupPlacementMode.AtCursor;
- // Сторона и отступ хранятся отдельно для каждого режима: у курсора и у
- // каретки удобны разные настройки, и переключение режима их не сбрасывает
+ // The side and the offset are stored per mode: the cursor and the caret call for
+ // different settings, and switching the mode does not reset them
[ObservableProperty]
private AnchorSide _cursorSide = AnchorSide.BottomRight;
- /// Отступ от курсора в единицах WPF.
+ /// The offset from the cursor in WPF units.
[ObservableProperty]
private double _cursorOffset = 16;
[ObservableProperty]
private AnchorSide _caretSide = AnchorSide.BottomRight;
- /// Отступ от каретки в единицах WPF.
+ /// The offset from the caret in WPF units.
[ObservableProperty]
private double _caretOffset = 16;
[ObservableProperty]
private ScreenPosition _screenPosition = ScreenPosition.BottomRight;
- /// Отступ от края монитора в единицах WPF.
+ /// The offset from the monitor edge in WPF units.
[ObservableProperty]
private double _screenMargin = 24;
[ObservableProperty]
private double _fontSize = 20;
- /// Непрозрачность подсказки: 1.0 — полностью непрозрачная.
+ /// The popup opacity: 1.0 is fully opaque.
[ObservableProperty]
private double _opacity = 0.9;
- /// Сколько подсказка держится на экране, в миллисекундах.
+ /// How long the popup stays on screen, in milliseconds.
[ObservableProperty]
private double _durationMilliseconds = 500;
///
- /// Перехватывать Caps Lock и переключать по ней раскладку вместо смены регистра.
- /// По умолчанию выключено: приложение не должно менять поведение системы,
- /// пока его об этом не попросили.
+ /// Intercept Caps Lock and switch the layout with it instead of changing the case.
+ /// Off by default: the application must not change the behaviour of the system
+ /// until it is asked to.
///
[ObservableProperty]
private bool _useCapsLockHotkey;
///
- /// С какого времени удержания Caps Lock переключение отменяется, в миллисекундах.
+ /// After how long a Caps Lock hold cancels the switch, in milliseconds.
///
[ObservableProperty]
private double _capsLockHoldMilliseconds = 300;
diff --git a/CursorLang/Models/AppTheme.cs b/CursorLang/Models/AppTheme.cs
index 6710f6d..0988653 100644
--- a/CursorLang/Models/AppTheme.cs
+++ b/CursorLang/Models/AppTheme.cs
@@ -1,8 +1,8 @@
namespace CursorLang.Models;
///
-/// Оформление окна настроек. По умолчанию приложение следует теме Windows,
-/// но пользователь может закрепить светлую или тёмную.
+/// The look of the settings window. By default the application follows the Windows
+/// theme, but the user can pin the light or the dark one.
///
public enum AppTheme
{
diff --git a/CursorLang/Models/KeyboardLayout.cs b/CursorLang/Models/KeyboardLayout.cs
index e114260..a778d1f 100644
--- a/CursorLang/Models/KeyboardLayout.cs
+++ b/CursorLang/Models/KeyboardLayout.cs
@@ -3,16 +3,16 @@ using System.Globalization;
namespace CursorLang.Models;
///
-/// Раскладка клавиатуры в удобном для отображения виде.
+/// A keyboard layout in a form convenient for display.
///
-/// Идентификатор локали (младшее слово HKL).
-/// Короткое имя для подсказки у курсора, например «RU».
-/// Полное имя, например «RU — русский (Россия)».
+/// The locale identifier (the low word of HKL).
+/// A short name for the popup at the cursor, "RU" for instance.
+/// The full name, "RU — русский (Россия)" for instance.
public sealed record KeyboardLayout(int LocaleId, string ShortName, string DisplayName)
{
///
- /// Строит модель по идентификатору локали. Неизвестные локали не являются
- /// ошибкой: для них показываем сам идентификатор.
+ /// Builds the model from a locale identifier. Unknown locales are not an
+ /// error: for them we show the identifier itself.
///
public static KeyboardLayout FromLocaleId(int localeId)
{
diff --git a/CursorLang/Models/LayoutChangedEventArgs.cs b/CursorLang/Models/LayoutChangedEventArgs.cs
index cbe08a2..545f304 100644
--- a/CursorLang/Models/LayoutChangedEventArgs.cs
+++ b/CursorLang/Models/LayoutChangedEventArgs.cs
@@ -1,19 +1,19 @@
namespace CursorLang.Models;
///
-/// Почему изменилась текущая раскладка.
+/// Why the current layout has changed.
///
public enum LayoutChangeReason
{
- /// Пользователь переключил раскладку в активном приложении.
+ /// The user switched the layout in the active application.
UserSwitched,
- /// Пользователь перешёл в другое приложение, где своя раскладка.
+ /// The user moved to another application that has a layout of its own.
ApplicationSwitched,
}
///
-/// Данные события смены раскладки.
+/// The data of a layout change event.
///
public sealed class LayoutChangedEventArgs(KeyboardLayout layout, LayoutChangeReason reason) : EventArgs
{
diff --git a/CursorLang/Models/PopupPlacement.cs b/CursorLang/Models/PopupPlacement.cs
index 826a7b9..f9afdd8 100644
--- a/CursorLang/Models/PopupPlacement.cs
+++ b/CursorLang/Models/PopupPlacement.cs
@@ -1,25 +1,25 @@
namespace CursorLang.Models;
///
-/// Способ выбора места для подсказки.
+/// How the place for the popup is chosen.
///
public enum PopupPlacementMode
{
- /// Рядом с курсором мыши.
+ /// Next to the mouse cursor.
AtCursor,
///
- /// Рядом с кареткой в активном поле ввода. Если приложение не сообщает
- /// её положение, подсказка показывается у курсора мыши.
+ /// Next to the caret in the active input field. When the application does not
+ /// report its position, the popup is shown at the mouse cursor.
///
AtCaret,
- /// В заданной точке монитора с активным окном.
+ /// At a fixed point of the monitor holding the active window.
FixedPoint,
}
///
-/// С какой стороны от курсора или каретки показывать подсказку.
+/// Which side of the cursor or the caret to show the popup on.
///
public enum AnchorSide
{
@@ -32,7 +32,7 @@ public enum AnchorSide
}
///
-/// Место на мониторе для режима .
+/// The place on the monitor for the mode.
///
public enum ScreenPosition
{
diff --git a/CursorLang/Services/CapsLockHotkeyService.cs b/CursorLang/Services/CapsLockHotkeyService.cs
index 8a1a9ed..f0748fc 100644
--- a/CursorLang/Services/CapsLockHotkeyService.cs
+++ b/CursorLang/Services/CapsLockHotkeyService.cs
@@ -5,13 +5,13 @@ using CursorLang.Models;
namespace CursorLang.Services;
///
-/// Держит системный перехват Caps Lock и отличает короткое нажатие от удержания.
+/// Holds the system Caps Lock hook and tells a short tap from a hold.
///
///
-/// Различить их можно только по факту отпускания клавиши, поэтому перехватываются
-/// оба события — и нажатие, и отпускание. Заодно это единственный способ отменить
-/// смену регистра: Windows переключает Caps Lock по событию нажатия, и пропустить
-/// его «на всякий случай» нельзя.
+/// They can be told apart only by the key being released, so both events are
+/// intercepted — the press and the release. That is also the only way to cancel the
+/// case change: Windows toggles Caps Lock on the press event, and letting it through
+/// "just in case" is not an option.
///
public sealed class CapsLockHotkeyService : ICapsLockHotkeyService, IDisposable
{
@@ -28,7 +28,7 @@ public sealed class CapsLockHotkeyService : ICapsLockHotkeyService, IDisposable
public CapsLockHotkeyService(AppSettings settings)
{
_settings = settings;
- _hook = new LowLevelKeyboardHook(OnKeyEvent);
+ _hook = new LowLevelKeyboardHook(HandleKeyEvent);
_holdTimer.Tick += OnHoldTimerTick;
}
@@ -48,9 +48,9 @@ public sealed class CapsLockHotkeyService : ICapsLockHotkeyService, IDisposable
ResetPress();
}
- // При закрытии приложения событий уже никто не ждёт, поэтому в отличие
- // от Stop состояние сбрасывается молча: очередь диспетчера в этот момент
- // может быть закрыта
+ // When the application is closing, nobody is waiting for events any more, so
+ // unlike in Stop the state is reset quietly: the dispatcher queue may be shut
+ // down by that moment
public void Dispose()
{
_holdTimer.Tick -= OnHoldTimerTick;
@@ -60,10 +60,12 @@ public sealed class CapsLockHotkeyService : ICapsLockHotkeyService, IDisposable
_hook.Dispose();
}
- // Вызывается системным хуком, то есть внутри разбора очереди сообщений.
- // Здесь только учёт состояния: показывать окна и рассылать события отсюда
- // нельзя — обработчик обязан вернуть управление за считанные миллисекунды
- private bool OnKeyEvent(int virtualKey, bool isKeyDown)
+ // Called by the system hook, that is, inside message queue processing. Only state
+ // tracking belongs here: showing windows and raising events from here is not
+ // allowed — the handler must return control within a few milliseconds.
+ // In tests the key presses are fed here as well: there is no need to install a
+ // real keyboard hook just to check how presses are interpreted
+ internal bool HandleKeyEvent(int virtualKey, bool isKeyDown)
{
if (virtualKey != VirtualKeyCapsLock)
{
@@ -72,13 +74,13 @@ public sealed class CapsLockHotkeyService : ICapsLockHotkeyService, IDisposable
if (isKeyDown)
{
- // Пока клавишу держат, Windows повторяет нажатие: отсчёт удержания
- // ведём от первого события и на повторы не реагируем
+ // While the key is held, Windows repeats the press: the hold is counted
+ // from the first event and the repeats are ignored
if (!_isPressed)
{
_isPressed = true;
- // Порог читаем при каждом нажатии: его меняют в настройках на лету
+ // The threshold is read on every press: it is changed in the settings on the fly
_holdTimer.Interval = _settings.CapsLockHoldDelay;
_holdTimer.Start();
}
@@ -109,8 +111,8 @@ public sealed class CapsLockHotkeyService : ICapsLockHotkeyService, IDisposable
HoldStarted?.Invoke(this, EventArgs.Empty);
}
- // Событие уходит подписчикам после возврата из хука: они вправе показывать
- // окна и делать что угодно ещё, не задерживая обработку нажатия
+ // The event reaches the subscribers after the hook returns: they are free to show
+ // windows and do anything else without holding up the handling of the key press
private void Notify(EventHandler? handler)
{
if (handler is not null)
@@ -119,8 +121,8 @@ public sealed class CapsLockHotkeyService : ICapsLockHotkeyService, IDisposable
}
}
- // Перехват могли снять с зажатой клавишей — например, сняв галочку
- // в настройках. Подсказку в этом случае нужно убрать
+ // The hook may have been removed with the key held down — by clearing the
+ // checkbox in the settings, for instance. The popup has to be taken down then
private void ResetPress()
{
_isPressed = false;
diff --git a/CursorLang/Services/CapsLockSwitchCoordinator.cs b/CursorLang/Services/CapsLockSwitchCoordinator.cs
index a7b7a50..10af38c 100644
--- a/CursorLang/Services/CapsLockSwitchCoordinator.cs
+++ b/CursorLang/Services/CapsLockSwitchCoordinator.cs
@@ -4,9 +4,9 @@ using CursorLang.Models;
namespace CursorLang.Services;
///
-/// Превращает нажатия Caps Lock в действия: короткое — переключает раскладку,
-/// длинное — только показывает подсказку. Он же включает и выключает перехват
-/// вслед за галочкой в настройках.
+/// Turns Caps Lock presses into actions: a short one switches the layout, a long one
+/// only shows the popup. It also turns the hook on and off following the checkbox in
+/// the settings.
///
public sealed class CapsLockSwitchCoordinator : IDisposable
{
@@ -68,8 +68,8 @@ public sealed class CapsLockSwitchCoordinator : IDisposable
private void OnTapped(object? sender, EventArgs e) => _layoutService.SwitchToNext();
- // Раскладку не меняем, но и молчать нельзя: без подсказки долгое нажатие
- // выглядит как то, что клавиша просто не сработала
+ // We do not change the layout, but staying silent will not do either: without the
+ // popup a long press looks as if the key simply did not work
private void OnHoldStarted(object? sender, EventArgs e) =>
_popupService.ShowUntilHidden(_layoutService.Current);
diff --git a/CursorLang/Services/ICapsLockHotkeyService.cs b/CursorLang/Services/ICapsLockHotkeyService.cs
index c0fd0e6..32f0152 100644
--- a/CursorLang/Services/ICapsLockHotkeyService.cs
+++ b/CursorLang/Services/ICapsLockHotkeyService.cs
@@ -1,29 +1,29 @@
namespace CursorLang.Services;
///
-/// Перехватывает Caps Lock на уровне системы и делит нажатия на короткие
-/// и длинные. Что делать с ними — решают подписчики.
+/// Intercepts Caps Lock at the system level and splits the presses into short and
+/// long ones. What to do with them is up to the subscribers.
///
public interface ICapsLockHotkeyService
{
- /// Короткое нажатие: клавишу отпустили до порога удержания.
+ /// A short press: the key was released before the hold threshold.
event EventHandler? Tapped;
- /// Порог удержания пройден, клавишу всё ещё держат.
+ /// The hold threshold has passed, the key is still held.
event EventHandler? HoldStarted;
- /// Удержание закончилось: клавишу отпустили.
+ /// The hold is over: the key was released.
event EventHandler? HoldEnded;
- /// Стоит ли перехват прямо сейчас.
+ /// Whether the hook is installed right now.
bool IsRunning { get; }
///
- /// Начинает перехват. Вызывать нужно из потока пользовательского интерфейса:
- /// системный хук клавиатуры работает только на потоке с циклом сообщений.
+ /// Starts intercepting. Must be called from the user interface thread:
+ /// a system keyboard hook works only on a thread with a message loop.
///
void Start();
- /// Снимает перехват, возвращая клавише её обычное поведение.
+ /// Removes the hook, giving the key its usual behaviour back.
void Stop();
}
diff --git a/CursorLang/Services/IKeyboardLayoutService.cs b/CursorLang/Services/IKeyboardLayoutService.cs
index 3687168..ccb049b 100644
--- a/CursorLang/Services/IKeyboardLayoutService.cs
+++ b/CursorLang/Services/IKeyboardLayoutService.cs
@@ -3,12 +3,12 @@ using CursorLang.Models;
namespace CursorLang.Services;
///
-/// Следит за раскладкой активного окна — в том числе в чужих приложениях —
-/// и умеет её переключать.
+/// Tracks the layout of the active window — including in other applications —
+/// and can switch it.
///
public interface IKeyboardLayoutService
{
- /// Раскладка активного окна на текущий момент.
+ /// The layout of the active window at the moment.
KeyboardLayout Current { get; }
event EventHandler? LayoutChanged;
diff --git a/CursorLang/Services/ILayoutPopupService.cs b/CursorLang/Services/ILayoutPopupService.cs
index abea18f..71b1fa0 100644
--- a/CursorLang/Services/ILayoutPopupService.cs
+++ b/CursorLang/Services/ILayoutPopupService.cs
@@ -3,16 +3,16 @@ using CursorLang.Models;
namespace CursorLang.Services;
///
-/// Показывает подсказку с раскладкой у курсора.
+/// Shows the layout popup at the cursor.
///
public interface ILayoutPopupService
{
- /// Показывает подсказку и убирает её через заданное в настройках время.
+ /// Shows the popup and takes it down after the time set in the settings.
void Show(KeyboardLayout layout);
///
- /// Показывает подсказку до явного вызова : нужна там, где
- /// время показа задаёт не таймер, а действие пользователя.
+ /// Shows the popup until is called explicitly: needed where the
+ /// show time is set by a user action rather than by a timer.
///
void ShowUntilHidden(KeyboardLayout layout);
diff --git a/CursorLang/Services/ILocalizationService.cs b/CursorLang/Services/ILocalizationService.cs
index 6bab8a0..b3497e4 100644
--- a/CursorLang/Services/ILocalizationService.cs
+++ b/CursorLang/Services/ILocalizationService.cs
@@ -2,21 +2,21 @@ using System.ComponentModel;
namespace CursorLang.Services;
-/// Язык интерфейса для выбора в настройках.
-/// Код культуры: «ru», «en».
-/// Название на самом этом языке.
+/// An interface language to choose from in the settings.
+/// The culture code: "ru", "en".
+/// The name in that very language.
public sealed record LanguageOption(string Code, string DisplayName)
{
- // Средства доступности берут имя элемента списка отсюда
+ // Accessibility tools take the name of the list item from here
public override string ToString() => DisplayName;
}
///
-/// Даёт строки интерфейса и умеет менять язык без перезапуска.
+/// Provides the interface strings and can change the language without a restart.
///
public interface ILocalizationService : INotifyPropertyChanged
{
- /// Строка по ключу ресурса. Привязки обновляются при смене языка.
+ /// The string for a resource key. Bindings update when the language changes.
string this[string key] { get; }
IReadOnlyList AvailableLanguages { get; }
diff --git a/CursorLang/Services/IThemeService.cs b/CursorLang/Services/IThemeService.cs
index 4712949..8c1a6d3 100644
--- a/CursorLang/Services/IThemeService.cs
+++ b/CursorLang/Services/IThemeService.cs
@@ -4,16 +4,16 @@ using CursorLang.Models;
namespace CursorLang.Services;
///
-/// Применяет светлое или тёмное оформление к окнам приложения.
+/// Applies the light or the dark look to the windows of the application.
///
public interface IThemeService
{
- /// Тема, действующая сейчас.
+ /// The theme in effect right now.
AppTheme CurrentTheme { get; }
///
- /// Подключает окно к смене темы: заголовок окна рисует Windows,
- /// и его цвет приходится переключать для каждого окна отдельно.
+ /// Hooks a window up to theme changes: the window title bar is drawn by Windows,
+ /// and its colour has to be switched for each window separately.
///
void Register(Window window);
}
diff --git a/CursorLang/Services/KeyboardLayoutService.cs b/CursorLang/Services/KeyboardLayoutService.cs
index 4810e7e..d23015a 100644
--- a/CursorLang/Services/KeyboardLayoutService.cs
+++ b/CursorLang/Services/KeyboardLayoutService.cs
@@ -5,23 +5,24 @@ using CursorLang.Models;
namespace CursorLang.Services;
///
-/// Настройки слежения за раскладкой.
+/// The settings of layout tracking.
///
public sealed class KeyboardLayoutOptions
{
- /// Как часто проверять раскладку активного окна.
+ /// How often to check the layout of the active window.
public TimeSpan PollInterval { get; init; } = TimeSpan.FromMilliseconds(150);
}
///
-/// Опрашивает активное окно по таймеру.
+/// Polls the active window on a timer.
///
///
-/// Опрос выбран не от простоты: событийных способов узнать о смене раскладки
-/// в чужом процессе из managed-кода нет. HSHELL_LANGUAGE от RegisterShellHookWindow
-/// в Windows 10/11 не приходит, а уведомления TSF (ITfLanguageProfileNotifySink)
-/// сообщают только о смене языка внутри своего процесса — оба варианта проверены
-/// и не сработали. Один тик — три вызова Win32, читающих данные из памяти ядра.
+/// Polling was chosen not for simplicity: there is no event-based way to learn about
+/// a layout change in another process from managed code. HSHELL_LANGUAGE from
+/// RegisterShellHookWindow does not arrive on Windows 10/11, and TSF notifications
+/// (ITfLanguageProfileNotifySink) report only language changes inside our own process
+/// — both options were tried and did not work. One tick is three Win32 calls reading
+/// data from kernel memory.
///
public sealed class KeyboardLayoutService : IKeyboardLayoutService, IDisposable
{
@@ -75,9 +76,9 @@ public sealed class KeyboardLayoutService : IKeyboardLayoutService, IDisposable
_lastLocaleId = localeId;
- // Переход в другое приложение со своей раскладкой — не то же самое,
- // что переключение раскладки пользователем, и подписчики вправе
- // реагировать на эти случаи по-разному
+ // Moving to another application with a layout of its own is not the same as
+ // the user switching the layout, and the subscribers are free to react to
+ // these cases differently
LayoutChangeReason reason = appSwitched
? LayoutChangeReason.ApplicationSwitched
: LayoutChangeReason.UserSwitched;
diff --git a/CursorLang/Services/LayoutNotificationCoordinator.cs b/CursorLang/Services/LayoutNotificationCoordinator.cs
index 514dd1e..df6ba3d 100644
--- a/CursorLang/Services/LayoutNotificationCoordinator.cs
+++ b/CursorLang/Services/LayoutNotificationCoordinator.cs
@@ -3,8 +3,8 @@ using CursorLang.Models;
namespace CursorLang.Services;
///
-/// Связывает слежение за раскладкой с показом подсказки.
-/// Живёт всё время работы приложения независимо от открытых окон.
+/// Ties layout tracking to showing the popup.
+/// Lives for as long as the application runs, regardless of the open windows.
///
public sealed class LayoutNotificationCoordinator : IDisposable
{
@@ -31,8 +31,8 @@ public sealed class LayoutNotificationCoordinator : IDisposable
private void OnLayoutChanged(object? sender, LayoutChangedEventArgs e)
{
- // При переходе в другое приложение раскладка меняется без участия
- // пользователя, и всплывающая подсказка была бы навязчивой
+ // When moving to another application the layout changes without the user
+ // taking part, and a popup would be intrusive
if (e.Reason == LayoutChangeReason.UserSwitched)
{
_popupService.Show(e.Layout);
diff --git a/CursorLang/Services/LayoutPopupService.cs b/CursorLang/Services/LayoutPopupService.cs
index 124d23b..e2deda3 100644
--- a/CursorLang/Services/LayoutPopupService.cs
+++ b/CursorLang/Services/LayoutPopupService.cs
@@ -6,8 +6,8 @@ using CursorLang.Views;
namespace CursorLang.Services;
///
-/// Управляет временем жизни подсказки: окно отвечает только за показ,
-/// а решение «когда показать и когда убрать» принимается здесь.
+/// Manages the lifetime of the popup: the window is only responsible for showing it,
+/// while the decision of when to show and when to take it down is made here.
///
public sealed class LayoutPopupService : ILayoutPopupService, IDisposable
{
@@ -29,8 +29,8 @@ public sealed class LayoutPopupService : ILayoutPopupService, IDisposable
{
ShowUntilHidden(layout);
- // Длительность читаем при каждом показе: её меняют в настройках на лету.
- // Перезапуск таймера заодно продлевает показ при быстрых переключениях
+ // The duration is read on every show: it is changed in the settings on the fly.
+ // Restarting the timer also prolongs the show on quick switches
_hideTimer.Interval = _settings.Duration;
_hideTimer.Start();
}
diff --git a/CursorLang/Services/LocalizationService.cs b/CursorLang/Services/LocalizationService.cs
index d7697ba..b4b1575 100644
--- a/CursorLang/Services/LocalizationService.cs
+++ b/CursorLang/Services/LocalizationService.cs
@@ -6,7 +6,8 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace CursorLang.Services;
///
-/// Берёт строки из ресурсов и при смене языка просит WPF перечитать все привязки.
+/// Takes the strings from the resources and, when the language changes, asks WPF to
+/// re-read every binding.
///
public sealed class LocalizationService : ObservableObject, ILocalizationService
{
@@ -38,8 +39,8 @@ public sealed class LocalizationService : ObservableObject, ILocalizationService
OnPropertyChanged(nameof(CurrentLanguage));
- // Сообщаем об изменении индексатора: так обновляются все привязки
- // вида {Binding Localization[Key]}, то есть весь текст интерфейса
+ // We report a change of the indexer: that is how every binding of the
+ // {Binding Localization[Key]} kind updates, that is, all the interface text
OnPropertyChanged(Binding.IndexerName);
}
}
diff --git a/CursorLang/Services/MainWindowPlacement.cs b/CursorLang/Services/MainWindowPlacement.cs
index 8ee7d96..c4306c8 100644
--- a/CursorLang/Services/MainWindowPlacement.cs
+++ b/CursorLang/Services/MainWindowPlacement.cs
@@ -5,25 +5,26 @@ using CursorLang.Interop;
namespace CursorLang.Services;
///
-/// Решает, где появиться окну настроек: впервые за сеанс — по центру монитора,
-/// на котором пользователь работает, а затем — там, где он это окно оставил.
+/// Decides where the settings window shows up: for the first time in a session — in
+/// the centre of the monitor the user is working on, and after that — where they
+/// left that window.
///
///
-/// Положение живёт только в памяти и между запусками не сохраняется: набор
-/// мониторов к следующему запуску может стать другим, а «по центру активного»
-/// верно всегда.
+/// The position lives in memory only and is not kept between launches: the set of
+/// monitors may be different by the next launch, while "in the centre of the active
+/// one" is always right.
///
public sealed class MainWindowPlacement
{
private PopupWindowNative.Point? _position;
- // Окно сообщает о переносе и тогда, когда двигаем его мы сами;
- // запоминать надо только то, что выбрал пользователь
+ // The window reports a move when we move it ourselves as well;
+ // what has to be remembered is only what the user chose
private bool _isPlacing;
///
- /// Берёт на себя размещение окна: ставит его на место к первому показу
- /// и следит за тем, куда пользователь его переносит.
+ /// Takes over the placement of the window: puts it in place by the first show
+ /// and follows where the user moves it.
///
public void Attach(Window window)
{
@@ -32,13 +33,13 @@ public sealed class MainWindowPlacement
}
///
- /// Возвращает окно на запомненное место, а если за этот сеанс его ещё
- /// не показывали — ставит по центру активного монитора.
+ /// Returns the window to the remembered place, and when it has not been shown
+ /// yet during this session — puts it in the centre of the active monitor.
///
public void Apply(Window window)
{
- // У свёрнутого окна нет осмысленных границ: его разворачивают на прежнем
- // месте, и разместить его можно только после этого
+ // A minimized window has no meaningful bounds: it is restored in its former
+ // place, and it can be positioned only after that
if (window.WindowState != WindowState.Normal)
{
return;
@@ -69,10 +70,10 @@ public sealed class MainWindowPlacement
_position = target;
}
- // Высота окна подстраивается под содержимое, и до первой раскладки она
- // неизвестна — по центру встал бы пустой каркас окна. Раскладку поэтому
- // просим посчитать сразу: окно к этому моменту ещё не показано, так что
- // на прежнем месте оно не мелькнёт
+ // The window height adapts to its content and is unknown until the first layout
+ // pass — an empty window frame would end up in the centre. So we ask for the
+ // layout to be computed right away: by that moment the window is not shown yet,
+ // so it will not flash in its former place
private void OnSourceInitialized(object? sender, EventArgs e)
{
if (sender is not Window window)
@@ -115,15 +116,15 @@ public sealed class MainWindowPlacement
}
///
- /// Подтягивает окно в рабочую область ближайшего монитора.
+ /// Pulls the window into the work area of the nearest monitor.
///
///
- /// Нужно в двух случаях. Монитор, на который окно поставил пользователь,
- /// за сеанс может быть отключён — возвращать окно на его место значило бы
- /// оставить пользователя без окна, поэтому запомненное положение здесь
- /// пожелание, а не приказ. А высота окна равна высоте содержимого и на
- /// невысоком мониторе может рабочую область превысить — тогда у окна,
- /// поставленного по центру, заголовок ушёл бы за верхний край.
+ /// Needed in two cases. The monitor the user put the window on may be disconnected
+ /// during the session — returning the window to its place would then leave the
+ /// user without a window, so the remembered position is a wish here rather than an
+ /// order. And the window height equals the height of its content and may exceed
+ /// the work area on a short monitor — then the title bar of a window placed in the
+ /// centre would go past the top edge.
///
private static PopupWindowNative.Point? KeepOnScreen(
PopupWindowNative.Point position, PopupWindowNative.Rect bounds)
diff --git a/CursorLang/Services/SettingsService.cs b/CursorLang/Services/SettingsService.cs
index 792077d..1dd0ff0 100644
--- a/CursorLang/Services/SettingsService.cs
+++ b/CursorLang/Services/SettingsService.cs
@@ -9,8 +9,20 @@ using CursorLang.Models;
namespace CursorLang.Services;
///
-/// Хранит настройки в %APPDATA%\CursorLang\settings.json.
+/// Keeps the settings in the settings.json file.
///
+///
+/// The location of the file depends on how the application is installed. A package
+/// from the Store keeps its settings in a folder of its own: Windows removes it
+/// together with the application, and after the removal nothing superfluous is left
+/// in the system — that is what Store applications are expected to do. A separately
+/// installed application keeps its settings in %APPDATA%, as before.
+///
+/// Settings left over from a separately installed application are picked up by the
+/// package on the first launch and moved over. The original file stays where it is:
+/// both versions can be installed side by side, and the application has no right to
+/// delete settings that are not its own.
+///
public sealed class SettingsService : IDisposable
{
private static readonly JsonSerializerOptions SerializerOptions = new()
@@ -37,8 +49,8 @@ public sealed class SettingsService : IDisposable
}
///
- /// Читает настройки с диска либо отдаёт значения по умолчанию,
- /// и дальше сам сохраняет любые изменения.
+ /// Reads the settings from disk or returns the default values,
+ /// and from then on saves any changes by itself.
///
public AppSettings Load()
{
@@ -61,7 +73,7 @@ public sealed class SettingsService : IDisposable
}
catch (Exception e) when (e is IOException or UnauthorizedAccessException)
{
- // Настройки — не тот случай, ради которого стоит ронять приложение
+ // The settings are not the kind of thing worth bringing the application down for
}
}
@@ -112,7 +124,7 @@ public sealed class SettingsService : IDisposable
Save();
}
- // Color не сериализуется штатно, а хранить его читаемым в файле удобно
+ // Color is not serialized out of the box, and keeping it readable in the file is handy
private sealed class ColorJsonConverter : JsonConverter
{
public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
diff --git a/CursorLang/Services/SingleInstanceGate.cs b/CursorLang/Services/SingleInstanceGate.cs
index 1f11749..bded8a0 100644
--- a/CursorLang/Services/SingleInstanceGate.cs
+++ b/CursorLang/Services/SingleInstanceGate.cs
@@ -4,14 +4,15 @@ using CursorLang.Interop;
namespace CursorLang.Services;
///
-/// Пускает работать только один экземпляр приложения: повторный запуск не
-/// поднимает второе окно, а показывает окно уже работающего.
+/// Lets only one instance of the application run: a second launch does not bring up
+/// a second window but shows the window of the one already running.
///
///
-/// Имена объектов ядра оставлены без префикса Global, то есть живут в пространстве
-/// имён сеанса. Один экземпляр на всю машину дал бы при быстром переключении
-/// пользователей странную картину: второй пользователь остался бы без приложения,
-/// а показать ему окно первого всё равно нельзя — окна принадлежат сеансу.
+/// The kernel object names are left without the Global prefix, that is, they live in
+/// the session namespace. A single instance for the whole machine would make for an
+/// odd picture with fast user switching: the second user would be left without the
+/// application, and showing them the window of the first one is impossible anyway —
+/// windows belong to a session.
///
public sealed class SingleInstanceGate : IDisposable
{
@@ -29,8 +30,8 @@ public sealed class SingleInstanceGate : IDisposable
public event EventHandler? ActivationRequested;
///
- /// Занимает место единственного экземпляра. Если приложение уже работает,
- /// просит его показаться и возвращает false — вызвавшему остаётся выйти.
+ /// Takes the single-instance slot. When the application is already running, asks
+ /// it to show itself and returns false — the caller is left to exit.
///
public bool TryAcquire()
{
@@ -42,15 +43,15 @@ public sealed class SingleInstanceGate : IDisposable
}
catch (AbandonedMutexException)
{
- // Предыдущий экземпляр завершился аварийно и мьютекс не отпустил.
- // Владельца у него теперь нет, а значит место свободно
+ // The previous instance crashed and did not release the mutex.
+ // It has no owner now, which means the slot is free
_isOwner = true;
}
- // Событие открывают оба экземпляра: первый — чтобы ждать просьбы,
- // второй — чтобы её подать. Кто из них создаст объект, зависит от того,
- // кто оказался первым, и на работу не влияет
- _activationRequest = new EventWaitHandle(false, EventResetMode.AutoReset, ActivationEventName);
+ // The event is opened by both instances: the first one to wait for a request,
+ // the second one to make it. Which of them creates the object depends on who
+ // came first and does not affect the work
+ _activationRequest = new EventWaitHandle(false, EventResetMode.AutoReset, _activationEventName);
if (!_isOwner)
{
@@ -59,8 +60,8 @@ public sealed class SingleInstanceGate : IDisposable
return false;
}
- // Ожидание отдано пулу потоков: держать ради него свой поток не за чем,
- // а просьба может не прийти никогда
+ // The wait is handed over to the thread pool: there is no reason to hold a
+ // thread of our own for it, and the request may never come
_activationWait = ThreadPool.RegisterWaitForSingleObject(
_activationRequest,
OnActivationSignalled,
@@ -79,8 +80,8 @@ public sealed class SingleInstanceGate : IDisposable
_activationRequest?.Dispose();
_activationRequest = null;
- // Мьютекс отпускает тот же поток, что его занял: и то и другое
- // происходит на потоке пользовательского интерфейса
+ // The mutex is released by the same thread that took it: both happen
+ // on the user interface thread
if (_isOwner)
{
_mutex?.ReleaseMutex();
@@ -91,8 +92,8 @@ public sealed class SingleInstanceGate : IDisposable
_mutex = null;
}
- // Пул потоков сообщает о просьбе где придётся, а окно слушается только
- // своего потока
+ // The thread pool reports the request from wherever it happens to be, while the
+ // window obeys only its own thread
private void OnActivationSignalled(object? state, bool timedOut) =>
_dispatcher.BeginInvoke(() => ActivationRequested?.Invoke(this, EventArgs.Empty));
}
diff --git a/CursorLang/Services/ThemeService.cs b/CursorLang/Services/ThemeService.cs
index b8339a9..03a27e8 100644
--- a/CursorLang/Services/ThemeService.cs
+++ b/CursorLang/Services/ThemeService.cs
@@ -8,8 +8,8 @@ using Microsoft.Win32;
namespace CursorLang.Services;
///
-/// Держит в ресурсах приложения палитру выбранной темы и подменяет её
-/// при смене настройки — окна перекрашиваются без перезапуска.
+/// Keeps the palette of the chosen theme in the application resources and swaps it
+/// when the setting changes — the windows are recoloured without a restart.
///
public sealed class ThemeService : IThemeService, IDisposable
{
@@ -29,7 +29,7 @@ public sealed class ThemeService : IThemeService, IDisposable
Apply();
}
- /// Тема, которую видит пользователь: System здесь уже разрешён.
+ /// The theme the user sees: System is already resolved here.
public AppTheme CurrentTheme => _current;
public void Register(Window window)
@@ -60,8 +60,8 @@ public sealed class ThemeService : IThemeService, IDisposable
_windows.Clear();
}
- /// Тема приложений в настройках Windows.
- private static AppTheme DetectSystemTheme()
+ /// The app theme from the Windows settings.
+ internal static AppTheme DetectSystemTheme()
{
try
{
@@ -84,7 +84,7 @@ public sealed class ThemeService : IThemeService, IDisposable
}
}
- // Windows сообщает о смене оформления не из потока интерфейса
+ // Windows reports a theme change from outside the interface thread
private void OnUserPreferenceChanged(object? sender, UserPreferenceChangedEventArgs e) =>
Application.Current?.Dispatcher.InvokeAsync(Apply);
diff --git a/CursorLang/ViewModels/LayoutPopupViewModel.cs b/CursorLang/ViewModels/LayoutPopupViewModel.cs
index 990e113..b529d62 100644
--- a/CursorLang/ViewModels/LayoutPopupViewModel.cs
+++ b/CursorLang/ViewModels/LayoutPopupViewModel.cs
@@ -4,8 +4,8 @@ using CursorLang.Models;
namespace CursorLang.ViewModels;
///
-/// Содержимое подсказки у курсора. Внешний вид берётся прямо из настроек,
-/// поэтому их правка применяется без перезапуска.
+/// The content of the popup at the cursor. The look is taken straight from the
+/// settings, so editing them applies without a restart.
///
public sealed partial class LayoutPopupViewModel : ObservableObject
{
diff --git a/CursorLang/ViewModels/SettingsViewModel.cs b/CursorLang/ViewModels/SettingsViewModel.cs
index c459d6e..70361f7 100644
--- a/CursorLang/ViewModels/SettingsViewModel.cs
+++ b/CursorLang/ViewModels/SettingsViewModel.cs
@@ -8,9 +8,9 @@ using CursorLang.Services;
namespace CursorLang.ViewModels;
///
-/// Вариант выбора в списке. Подпись меняется вместе с языком, а сам объект
-/// живёт всё время работы окна: если пересоздавать элементы списка,
-/// ComboBox сбрасывает выбранное значение.
+/// An option in a list. The caption changes together with the language, while the
+/// object itself lives for as long as the window does: recreating the list items
+/// makes the ComboBox drop the selected value.
///
public sealed class EnumOption : ObservableObject where T : struct, Enum
{
@@ -30,13 +30,13 @@ public sealed class EnumOption : ObservableObject where T : struct, Enum
set => SetProperty(ref _display, value);
}
- // Средства доступности берут имя элемента списка отсюда
+ // Accessibility tools take the name of the list item from here
public override string ToString() => Display;
}
///
-/// Окно настроек. Значения правятся прямо в ,
-/// поэтому подсказка подхватывает их сразу, без кнопки «Применить».
+/// The settings window. The values are edited right in ,
+/// so the popup picks them up at once, without an "Apply" button.
///
public sealed partial class SettingsViewModel : ObservableObject, IDisposable
{
@@ -69,7 +69,7 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
Settings = settings;
Localization = localization;
- // Язык интерфейса — такая же настройка, как остальные, и хранится там же
+ // The interface language is a setting like any other and is stored in the same place
Localization.CurrentLanguage = settings.Language;
Settings.PropertyChanged += OnSettingsChanged;
Localization.PropertyChanged += OnLocalizationChanged;
@@ -110,8 +110,8 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
}
}
- // Подписи вариантов приходят из ресурсов, поэтому при смене языка
- // обновляем только текст — сами элементы списков остаются прежними
+ // The option captions come from the resources, so on a language change we update
+ // only the text — the list items themselves stay the same
private void OnLocalizationChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != Binding.IndexerName)
@@ -136,7 +136,7 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
}
}
- // Ключ ресурса собирается из имени типа и значения: PopupPlacementMode_AtCursor
+ // The resource key is built from the type name and the value: PopupPlacementMode_AtCursor
private string GetDisplayName(T value) where T : struct, Enum =>
Localization[$"{typeof(T).Name}_{value}"];
}
diff --git a/CursorLang/Views/Converters.cs b/CursorLang/Views/Converters.cs
index f0b5950..ae9ac5b 100644
--- a/CursorLang/Views/Converters.cs
+++ b/CursorLang/Views/Converters.cs
@@ -6,9 +6,9 @@ using System.Windows.Media;
namespace CursorLang.Views;
///
-/// Показывает элемент, если значение совпадает с одним из перечисленных
-/// в параметре через запятую. Нужен, чтобы настройки точки привязки
-/// и настройки экрана не показывались одновременно.
+/// Shows an element when the value matches one of those listed in the parameter,
+/// separated by commas. Needed so that the anchor point settings and the screen
+/// settings are not shown at the same time.
///
public sealed class EnumToVisibilityConverter : IValueConverter
{
@@ -27,8 +27,8 @@ public sealed class EnumToVisibilityConverter : IValueConverter
}
///
-/// Цвет в запись вида «#RRGGBB»: альфа не показывается, потому что
-/// за прозрачность отвечает отдельная настройка.
+/// A colour into a "#RRGGBB" notation: the alpha is not shown, because
+/// transparency is a separate setting.
///
public sealed class ColorToHexConverter : IValueConverter
{
@@ -40,7 +40,7 @@ public sealed class ColorToHexConverter : IValueConverter
}
///
-/// Цвет в кисть — для образцов палитры в списке.
+/// A colour into a brush — for the palette swatches in the list.
///
public sealed class ColorToBrushConverter : IValueConverter
{
diff --git a/CursorLang/Views/LayoutPopupWindow.xaml.cs b/CursorLang/Views/LayoutPopupWindow.xaml.cs
index 7e68645..fc1e4d2 100644
--- a/CursorLang/Views/LayoutPopupWindow.xaml.cs
+++ b/CursorLang/Views/LayoutPopupWindow.xaml.cs
@@ -7,9 +7,9 @@ using CursorLang.ViewModels;
namespace CursorLang.Views;
///
-/// Всплывающая подсказка с коротким именем раскладки.
-/// Отвечает только за показ, размер и место на экране: когда её убрать,
-/// решает .
+/// The popup with the short name of the layout.
+/// Responsible only for showing it, its size and its place on screen: when to take it
+/// down is decided by .
///
public partial class LayoutPopupWindow : Window
{
@@ -23,25 +23,25 @@ public partial class LayoutPopupWindow : Window
DataContext = viewModel;
_settings = settings;
- // Создаём окно заранее, чтобы задать его границы до первого показа:
- // иначе оно на мгновение появляется в размере по умолчанию
+ // We create the window in advance to set its bounds before the first show:
+ // otherwise it appears at the default size for a moment
new WindowInteropHelper(this).EnsureHandle();
}
///
- /// Показывает подсказку в месте, заданном настройками.
+ /// Shows the popup at the place set by the settings.
///
public void ShowPopup()
{
MeasureContent();
- // Уже созданное окно ставим на место до показа, чтобы оно не мелькнуло
- // на старой позиции; при самом первом вызове хэндла ещё нет
+ // An already created window is put in place before the show so that it does
+ // not flash in its old position; on the very first call there is no handle yet
ApplyBounds();
Show();
- // Повторяем после показа: до него окно подчиняется системному минимальному
- // размеру окна и получается заметно крупнее текста
+ // We repeat it after the show: before that the window is subject to the system
+ // minimum window size and comes out noticeably larger than its text
ApplyBounds();
}
@@ -52,9 +52,9 @@ public partial class LayoutPopupWindow : Window
PopupWindowNative.MakePassive(new WindowInteropHelper(this).Handle);
}
- // Размер содержимого нужен до показа: от него зависит и размер окна,
- // и позиция для углов. Рамок у окна нет, поэтому размер окна равен размеру
- // содержимого
+ // The content size is needed before the show: both the window size and the corner
+ // position depend on it. The window has no frame, so the window size equals the
+ // content size
private void MeasureContent()
{
var content = (FrameworkElement)Content;
@@ -63,9 +63,9 @@ public partial class LayoutPopupWindow : Window
_contentSize = content.DesiredSize;
}
- // Всё считается в физических пикселях: у мониторов разный масштаб, а
- // Window.Left/Top/Width/Height пересчитываются по DPI того монитора,
- // где окно сейчас, и на соседнем мониторе дают промах
+ // Everything is computed in physical pixels: monitors have different scaling,
+ // while Window.Left/Top/Width/Height are converted using the DPI of the monitor
+ // the window is on right now, which misses the target on a neighbouring monitor
private void ApplyBounds()
{
IntPtr handle = new WindowInteropHelper(this).Handle;
@@ -84,8 +84,8 @@ public partial class LayoutPopupWindow : Window
}
}
- // Точка привязки: каретка в поле ввода либо курсор мыши. Курсор — это
- // прямоугольник нулевого размера, поэтому расчёт углов у них общий
+ // The anchor point: the caret in the input field or the mouse cursor. The cursor
+ // is a rectangle of zero size, so the corner computation is shared by both
private PopupWindowNative.Rect GetAnchor()
{
if (_settings.PlacementMode == PopupPlacementMode.AtCaret &&
@@ -109,7 +109,7 @@ public partial class LayoutPopupWindow : Window
var anchorPoint = new PopupWindowNative.Point { X = anchor.Left, Y = anchor.Top };
double scale = PopupWindowNative.GetScaleAt(anchorPoint);
- // У каждого режима привязки своя сторона и свой отступ
+ // Every anchor mode has a side and an offset of its own
bool atCaret = _settings.PlacementMode == PopupPlacementMode.AtCaret;
AnchorSide side = atCaret ? _settings.CaretSide : _settings.CursorSide;
int offset = ToPixels(atCaret ? _settings.CaretOffset : _settings.CursorOffset, scale);
diff --git a/CursorLang/Views/MainWindow.xaml b/CursorLang/Views/MainWindow.xaml
index 3b41b14..19d86bf 100644
--- a/CursorLang/Views/MainWindow.xaml
+++ b/CursorLang/Views/MainWindow.xaml
@@ -35,6 +35,9 @@
+
diff --git a/CursorLang/Views/MainWindow.xaml.cs b/CursorLang/Views/MainWindow.xaml.cs
index 9663ddb..aa09bd5 100644
--- a/CursorLang/Views/MainWindow.xaml.cs
+++ b/CursorLang/Views/MainWindow.xaml.cs
@@ -5,7 +5,7 @@ using CursorLang.ViewModels;
namespace CursorLang.Views;
///
-/// Окно настроек приложения.
+/// The settings window of the application.
///
public partial class MainWindow : Window
{