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
@@ -107,6 +107,26 @@ dotnet csharpier check .
|
||||
значит карантинить здоровые прокси на каждый Cancel.
|
||||
- **`Select` и `Next` — зарезервированные слова для CA1716.** Метод стратегии называется `Pick`.
|
||||
|
||||
## Добавить строку в UI
|
||||
|
||||
1. Ключ и оба перевода — в `Strings.resx` и `Strings.ru.resx` (**оба**, иначе упадёт
|
||||
`LocalizationTests.Russian_translates_every_english_key`).
|
||||
2. В XAML — `{l:Loc Ключ}`, во ViewModel — `Localizer.Instance[...]` / `.Format(...)`.
|
||||
|
||||
Никакого хардкода в `Views/` кроме имени продукта и примеров адресов.
|
||||
|
||||
- **Счётчики — только через `Localizer.Plural`** с ключами `.One` / `.Few` / `.Many`. У русского
|
||||
три формы; «{0} records» с приклеенным окончанием непереводимо.
|
||||
- **Перечисления в списках — `LocalizedOption<T>`**, не сырые значения. Конвертер разрешил бы
|
||||
подпись один раз и не заметил смены языка. Идентичность обёртки — значение перечисления, чтобы
|
||||
выбор не слетал.
|
||||
- **Текст из домена переводится по коду.** `Core` о языках не знает: `ParseError` несёт `Code` и
|
||||
`Arguments`, UI ищет `Parse.Error.{Code}` с откатом на `Message`. Имена парсеров — так же:
|
||||
`Parser.{id}.Name` с откатом на `DisplayName`, поэтому обещание «добавить парсер = одна строка»
|
||||
остаётся в силе.
|
||||
- **VM, у которой есть производный от языка текст, переопределяет `OnLanguageChanged`** и зовёт
|
||||
`base`. Без этого заголовок страницы останется на прежнем языке.
|
||||
|
||||
## Грабли, уже оплаченные
|
||||
|
||||
- **Селектор типа в Avalonia матчит точный тип.** `UserControl.shell` не матчит `ShellView`
|
||||
|
||||
Reference in New Issue
Block a user