Localise the UI into Russian and English, switchable without a restart
Strings move into Localization/Strings.resx plus a Russian satellite. XAML uses
a {l:Loc Key} markup extension that yields a binding through the localizer's
indexer rather than a resolved string, so changing the language raises
PropertyChanged for the indexer and every caption in the app re-reads at once.
Resolving strings once at load would have been simpler and would have needed an
app restart to take effect.
Three places where this is more than a string swap:
Russian has three plural forms, so counted messages are assembled from .One /
.Few / .Many keys via Localizer.Plural instead of "{0} records" with an English
plural glued on. "1 запись", "3 записи", "7 записей".
Enum captions in pickers go through LocalizedOption<T>. A value converter would
resolve the caption once and never notice a language change; the wrapper keeps
identity on the enum value so the selection survives, while the label follows
the localizer. It needed a non-generic base for DataTemplate x:DataType, since
Avalonia 12 compiles bindings by default and cannot infer one for an open
generic.
Text originating in the domain would otherwise have stayed English under a
Russian UI — the screenshots showed exactly that. AvParser.Core still knows
nothing about languages: ParseError now carries a Code and Arguments, and the UI
translates Parse.Error.{Code} with a fallback to the English message. Parser
names work the same way (Parser.{id}.Name falling back to DisplayName), which
keeps "add a parser = one registration line" true — an untranslated parser shows
its own name rather than a missing-key marker.
Both .resx files are generated from one table so a key cannot exist in one and
be missing from the other, and the tests assert that, plus no blank translations
and identical {0} placeholder sets — a translation that drops a placeholder
throws at runtime rather than merely reading oddly. A headless test switches
language on a live shell and asserts the rendered text changes without the tree
being rebuilt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9bf2ea5532
commit
8b552470b7
@@ -152,6 +152,41 @@ using (lease)
|
||||
SOCKS работает штатно: .NET понимает схемы `socks4/socks4a/socks5` в `WebProxy`. Учтите, что
|
||||
proxifly-запись с `"protocol": "https"` — это всё равно HTTP-прокси с CONNECT, а не схема `https://`.
|
||||
|
||||
## Локализация
|
||||
|
||||
Русский и английский, переключение **без перезапуска** — язык выбирается в настройках
|
||||
(«Системный» берёт язык ОС, если для него есть перевод, иначе английский).
|
||||
|
||||
Строки лежат в `UI/Localization/Strings.resx` и `Strings.ru.resx`; русский собирается в
|
||||
сателлитную сборку `ru/AvParser.UI.resources.dll`.
|
||||
|
||||
В XAML — разметочное расширение:
|
||||
|
||||
```xml
|
||||
<TextBlock Text="{l:Loc Parse.Run}" />
|
||||
```
|
||||
|
||||
Оно возвращает **биндинг** через индексатор `Localizer`, а не готовую строку: смена языка
|
||||
поднимает `PropertyChanged` для индексатора, и все такие биндинги перечитываются разом. Строка,
|
||||
разрешённая один раз при загрузке, потребовала бы перезапуска.
|
||||
|
||||
Три места, где локализация упирается в грамматику или в слои:
|
||||
|
||||
- **Множественные числа.** У русского три формы, поэтому счётчики собираются не из «{0} records»
|
||||
с приклеенным окончанием, а из ключей `.One` / `.Few` / `.Many` через `Localizer.Plural`.
|
||||
«1 запись», «3 записи», «7 записей».
|
||||
- **Значения перечислений.** Конвертер разрешил бы подпись один раз и не заметил смены языка,
|
||||
поэтому в списках лежат обёртки `LocalizedOption<T>`: идентичность — значение перечисления
|
||||
(выбор не слетает), подпись следует за локализатором.
|
||||
- **Текст из домена.** `AvParser.Core` о языках не знает. Парсеры отдают английское сообщение
|
||||
**и код**, а UI переводит `Parse.Error.{Code}` с откатом на сообщение. Так же и с именами
|
||||
парсеров: `Parser.{id}.Name` с откатом на `DisplayName`, поэтому новый парсер работает
|
||||
непереведённым, а не показывает `!ключ!`.
|
||||
|
||||
Оба `.resx` генерируются из одной таблицы, чтобы ключ не мог существовать в одном файле и
|
||||
отсутствовать в другом; тесты проверяют совпадение ключей, отсутствие пустых переводов и
|
||||
одинаковый набор плейсхолдеров `{0}`.
|
||||
|
||||
## Дизайн-токены
|
||||
|
||||
Все цвета, отступы, радиусы и типографика — в `Styles/Tokens.axaml`, с отдельными словарями
|
||||
|
||||
Reference in New Issue
Block a user