Language Library — High-Level Plan
Context
The framework currently has runtime theme switching (Light / Blue / Dark) via a clean two-layer service architecture, but all UI text is hardcoded English. The user wants an analogous Language library so users can switch GUI language at runtime from OptionsDialog, with the same UX as theme switching.
Target user base (from USACE partnerships, deployments, and Google Analytics):
- Direct partnerships: UK, Netherlands, Spain (dam and levee safety)
- Wide deployment: US, Australia, Canada
- Heavy analytics traffic: Singapore, China
- External peer reviewer: Ribatet (France)
- Industry citation network: Italian statisticians (academic engineering community)
- Strategic allies / engineering research communities: Germany, Japan, Sweden
- Strategic Latin American expansion: Brazil
Selected Languages (locked)
The initial release supports 10 languages:
| # |
Language |
Locale |
Audience driver |
AvalonDock free? |
| 1 |
English |
en |
Base — UK, US, AU, anglo-CA, Singapore |
n/a (source) |
| 2 |
Spanish |
es-ES |
Spain partnership; serves LatAm acceptably |
✓ |
| 3 |
Dutch |
nl-NL |
Netherlands partnership (water-management authority audience) |
✓ (nl-BE ~98% compatible) |
| 4 |
Simplified Chinese |
zh-Hans |
China users + Singapore Mandarin speakers |
✓ |
| 5 |
French |
fr-FR |
Ribatet (external peer reviewer) + Quebec users (fr-FR ≈ fr-CA for UI strings) |
✓ |
| 6 |
German |
de-DE |
Ally + active dam/civil engineering community |
✓ |
| 7 |
Portuguese (Brazil) |
pt-BR |
Strategic Brazil/LatAm reach |
✓ |
| 8 |
Italian |
it-IT |
Italian statisticians cited in domain |
✓ |
| 9 |
Japanese |
ja-JP |
World-class tsunami/dam engineering research; ally |
✓ |
| 10 |
Swedish |
sv-SE |
Nordic ally; AvalonDock ships it free |
✓ |
Result: 9 of 10 get AvalonDock localization for free (Dutch via the close-cousin nl-BE bundle). Only the framework's own strings need translation.
Answers to the User's 3 Questions
1. Is this possible?
Yes, fully. WPF supports runtime UI culture changes. Because the framework already uses DynamicResource heavily (for theme brushes), the same mechanism swaps localized strings: replace hardcoded Header="File" with Header="{DynamicResource Strings.Menu.File}", then swap a ResourceDictionary at runtime. No app restart required.
The cleanest approach is mirror the theme architecture exactly — same shape, same patterns, same persistence layer. This minimizes architectural surface area and lets the same patterns reviewers already know carry forward.
2. How much effort is it?
Medium — roughly 5–8 developer-weeks for the locked 10 languages, broken down:
| Phase |
Effort |
What |
| Infrastructure |
1–2 weeks |
Language enum, services, markup extension, settings wiring |
| String extraction |
1–2 weeks |
Find/replace ~1,300 XAML strings + ~200 code-behind strings, build base EnglishStrings.xaml dictionary with keys (per-library: FrameworkUI, DatabaseControls, OxyPlotControls, GenericControls, etc.) |
| Translation (LLM-assisted) |
~1 week |
LLM-produces all 9 non-English dictionaries from canonical English file. Most time spent on QA/glossary, not raw translation |
| Native-speaker review pass |
~1 week |
Especially critical for Dutch (high English fluency) and Chinese (technical-term variance). Other 7 languages can ship LLM-only with a glossary review |
| QA / visual review |
~1 week |
Verify each dialog renders correctly in each language. German + Dutch are longest-text — watch for clipped buttons/labels |
Vendor code: AvalonDock already ships translations for 9 of the 10 selected languages — zero work. OxyPlot has 1 hardcoded string — negligible.
Per-language additional cost is small once infrastructure exists — adding an 11th language post-launch is mostly translation work + a one-line enum entry.
3. How should we design it?
Mirror the theme architecture, with one twist: pipe Thread.CurrentUICulture so AvalonDock's existing .resx-based localization comes along for the ride.
Recommended Architecture
Core library — src/Themes/Core/ (extend existing) or new src/Languages/Core/
Mirror exactly:
| Theme system |
Language system |
Theme enum (Light/Blue/Dark) |
Language enum — English, Spanish, Dutch, ChineseSimplified, French, German, PortugueseBrazil, Italian, Japanese, Swedish |
ThemeService (singleton) |
LanguageService (singleton) |
ThemeResourceHelper (URI constants) |
LanguageResourceHelper (URI constants + culture codes — see locale column above) |
ThemeChanged event |
LanguageChanged event |
MergedDictionaries swap on app resources |
MergedDictionaries swap on app resources (same mechanism!) |
LanguageService.SetLanguage(Language):
- Remove old strings dictionary from
Application.Current.Resources.MergedDictionaries
- Add new strings dictionary
- Set
Thread.CurrentThread.CurrentUICulture = newCulture (this makes AvalonDock's .resx files pick up the new language automatically)
- Update
FrameworkElement.LanguageProperty (already done in App.xaml.cs:47-51 for number formatting — extend it)
- Raise
LanguageChanged event
FrameworkUI facade — src/FrameworkUI/Languages/
|
|
ThemeManager (static facade) |
LanguageManager (static facade) |
ThemeColor enum |
UILanguage enum |
Same shape as ThemeManager. Subscribers can hook LanguageChanged if they need to refresh non-string content (e.g., re-rendered icons or auto-generated text).
Per-language string dictionaries
src/Languages/Resources/Strings/EnglishStrings.xaml (and Spanish*, French*, etc.) — same structure as LightColors.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="Strings.Menu.File">File</sys:String>
<sys:String x:Key="Strings.Menu.New">New Project...</sys:String>
<sys:String x:Key="Strings.Dialog.ConfirmDelete">Are you sure you would like to delete the selected project elements? This action is permanent.</sys:String>
...
</ResourceDictionary>
Each language file uses identical x:Key names (same convention as the theme color files). English is the canonical/default fallback.
Per-library dictionaries — exactly like themes
Yes — every consuming library/app ships its own per-language dictionaries, mirroring how each control library has its own theme dictionary today (e.g., FrameworkUI/Themes/VS2013/{Light,Blue,Dark}Theme.xaml, AvalonDock's Generic.xaml, etc.).
Layered structure:
| Tier |
Theme example |
Language example |
| Core/shared |
Themes/Resources/Colors/LightColors.xaml (universal brushes) |
Languages/Resources/Strings/EnglishStrings.xaml (universal terms: OK, Cancel, Yes, No, File, Open...) |
| Per-library |
FrameworkUI/Themes/VS2013/LightTheme.xaml |
FrameworkUI/Languages/EnglishStrings.xaml |
| Per-library |
(DatabaseControls would have its own theme dict if needed) |
DatabaseControls/Languages/EnglishStrings.xaml |
| Per-library |
(OxyPlotControls) |
OxyPlotControls/Languages/EnglishStrings.xaml |
| Per-app |
(apps add their own theme dict if they have custom controls) |
MyApp/Languages/EnglishStrings.xaml |
Each library gets its own keyspace prefix to avoid collisions (Strings.FrameworkUI.Menu.File, Strings.OxyPlotControls.Toolbar.Zoom, Strings.DatabaseControls.Stats.Title).
Registration pattern — libraries register their dictionary set with LanguageService at startup:
// In a library's module init or App.xaml.cs OnStartup
LanguageService.Instance.RegisterStringSet(
setKey: "DatabaseControls",
dictionariesByLanguage: new Dictionary<Language, Uri>
{
[Language.English] = new Uri("pack://application:,,,/DatabaseControls;component/Languages/EnglishStrings.xaml"),
[Language.Spanish] = new Uri("pack://application:,,,/DatabaseControls;component/Languages/SpanishStrings.xaml"),
// ...
});
When SetLanguage(Language.Spanish) is called, LanguageService walks every registered set, removes the current dictionary, and adds the Spanish one. Adding a new control library to a downstream app is just one RegisterStringSet call — exact mirror of how themes work today.
This is also how end-user apps add their own app-specific strings (custom dialog titles, menu items, etc.) without modifying the framework.
XAML usage
Replace literals with DynamicResource:
<!-- before -->
<MenuItem Header="File">
<!-- after -->
<MenuItem Header="{DynamicResource Strings.Menu.File}">
Optional convenience: add a {loc:String Strings.Menu.File} markup extension wrapping DynamicResource for terser syntax later.
Code-behind usage
Add a small helper:
public static class LocalizedStrings
{
public static string Get(string key) =>
Application.Current?.TryFindResource(key) as string ?? key;
public static string Format(string key, params object[] args) =>
string.Format(Get(key), args);
}
Replace MessageBox.Show("Are you sure...") with MessageBox.Show(LocalizedStrings.Get("Strings.Dialog.ConfirmDelete")).
OptionsDialog wiring
Mirror GeneralOptions.xaml:34-36 (the existing theme combo) — add a sibling LanguageComboBox:
<ComboBox x:Name="LanguageComboBox"
ItemsSource="{Binding LanguageList}"
SelectedValue="{Binding Language, ...}"/>
OptionsDialog.xaml.cs Apply handler calls LanguageManager.SetLanguage(...) right next to the existing ThemeManager.SetTheme(...) call.
Persistence
Add one line to UserSettings.cs next to ColorTheme:
public static string Language { get; set; } = "English";
Already saved/loaded by the same XML mechanism — no changes to persistence code.
Startup
App.xaml.cs Application_Startup: add LanguageManager.SetLanguage(parsed) next to the existing ThemeManager.SetTheme(...) call (line ~105). Defaults to system culture or English.
Critical Files to Create / Modify
Create — core (one-time):
src/Languages/Core/Language.cs — enum
src/Languages/Core/LanguageService.cs — singleton with RegisterStringSet + SetLanguage
src/Languages/Core/LanguageResourceHelper.cs — URI / culture-code constants
src/Languages/Resources/Strings/{English,Spanish,Dutch,ChineseSimplified,French,German,PortugueseBrazil,Italian,Japanese,Swedish}Strings.xaml — universal/shared terms (10 files)
src/FrameworkUI/Languages/LanguageManager.cs — facade
src/FrameworkUI/Languages/UILanguage.cs — enum
src/FrameworkUI/Languages/LocalizedStrings.cs — code-behind helper
- (Optional)
src/FrameworkUI/Languages/LocStringExtension.cs — {loc:String Key} markup extension
Create — per-library (one set per consuming library, mirroring theme dictionaries):
src/FrameworkUI/Languages/{10 language}Strings.xaml + RegisterStringSet call at startup
src/DatabaseControls/Languages/{10 language}Strings.xaml + registration
src/OxyPlotControls/Languages/{10 language}Strings.xaml + registration
src/GenericControls/Languages/{10 language}Strings.xaml + registration
- (Same pattern for any other control library: NumericControls, etc.)
- Downstream apps would create their own
MyApp/Languages/{10 language}Strings.xaml for app-specific text
Modify:
src/FrameworkUI/User Settings/UserSettings.cs — add Language property (~line 31)
src/FrameworkUI/Tools Menu/Options/GeneralOptions.xaml(.cs) — add language ComboBox + DependencyProperty
src/FrameworkUI/Tools Menu/Options/OptionsDialog.xaml.cs — call LanguageManager.SetLanguage() in Apply handler (~line 138)
src/FrameworkUI.Demo/App.xaml.cs — call LanguageManager.SetLanguage() at startup (~line 105)
src/FrameworkUI/Main Window/MainWindow.xaml.cs — subscribe to LanguageChanged (next to existing ThemeChanged at ~line 95) for any non-resource refresh logic
- All XAML/code with hardcoded strings — extraction work
Existing Utilities to Reuse
Themes/Core/ThemeService.cs — copy/adapt the singleton initialization, MergedDictionaries swap, and event-raising pattern verbatim.
UserSettings XML save/load — already atomic (temp-file + rename); just add the Language property.
App.xaml.cs:47-51 already sets FrameworkElement.LanguageProperty = CultureInfo.CurrentCulture.IetfLanguageTag for number formatting — generalize to react to language changes.
- AvalonDock's existing
.resx localization — works automatically when Thread.CurrentUICulture is set; do not duplicate.
Translation Tooling — Do You Need an External Service?
No — I (the assistant) can produce the translations directly as part of the work. UI strings are mostly short, idiomatic phrases ("File", "Save As...", "Are you sure?", "Connection failed"), which modern LLMs translate at near-professional quality for the major European languages (ES/FR/NL/DE/IT/PT/etc.).
Tradeoffs across the realistic options:
| Option |
Cost |
Speed |
Quality |
Best for |
| LLM (me) |
Free |
Hours |
~95% pro for short UI strings; weaker on highly technical terms |
Initial pass for all languages, especially during infrastructure rollout |
| DeepL / Google Translate API |
Cheap |
Minutes |
Solid but more literal; less context-aware |
Bulk machine baseline if you don't want LLM-in-the-loop |
| Professional human translation |
$$ ($1.5–3k/lang for ~1.5k strings) |
Weeks |
Gold standard |
Final polish for shipped product, especially for languages outside your team's reading ability |
| Native-speaker engineer review |
Internal time |
Days |
Catches dialect/register issues |
Recommended pass on top of any of the above |
Recommended workflow:
- Build out
EnglishStrings.xaml with all keys (the canonical source).
- Hand me the English
.xaml file; I produce SpanishStrings.xaml, FrenchStrings.xaml, etc. directly. Output format matches input — same x:Key values, same XML structure, just translated <sys:String> content.
- Have a native speaker (or pro translator) review the longest dialog texts and any domain-specific terms (database, charting jargon).
- For specialized terminology, maintain a glossary file (e.g.,
LocalizationGlossary.md) that pins canonical translations of recurring terms ("project", "database connection", "axis", "series") so revisions stay consistent.
- Translation memory: keep the source
EnglishStrings.xaml as the single source of truth. When new strings are added, only the new keys need re-translation — existing translations stay stable.
No external converter required, but if you prefer bulk machine output as a baseline, DeepL has the best quality among the API services for the European languages you mentioned.
Phased Rollout Recommendation
- Infrastructure-only first. Land the services, enums, settings, OptionsDialog wiring, with English as the only language. Verify hot-swap works by adding a temporary stub "TestLanguage" with a few translated keys. Get the architecture reviewed before mass extraction.
- Extract strings region by region. Start with
MainWindow.xaml (highest visibility), then dialog-by-dialog. Each region is an independently shippable PR. Build out per-library English dictionaries in parallel.
- Translation rollout — recommended order:
- Spanish first — large speaker base, similar string lengths to English, low layout risk. Validates the translation pipeline.
- German second — long words stress-test the layout (catches clipped buttons/labels early, before more translations pile on).
- Dutch + French + Italian + Portuguese (BR) — Latin/Germanic batch, similar handling.
- Chinese (zh-Hans) + Japanese + Swedish — different script / typography surface; handle as a final batch with extra QA on font rendering.
- Translation memory: treat
EnglishStrings.xaml (per library) as the single source of truth. New strings added later only require translating the diff, not re-translating everything.
Risks / Open Questions
- Layout breakage in long-text languages — German and Dutch are the main risks; Swedish and Finnish-style compound words are also long. Many fixed-width buttons and labels will need
MinWidth audits.
- CJK typography — Chinese and Japanese rendering may need font-family fallbacks (default WPF font may render poorly for some glyphs). Test with the actual selected font on a Windows install without CJK fonts pre-installed.
- Right-to-left languages (Arabic, Hebrew) — not in current scope. If added later, requires
FlowDirection="RightToLeft" plumbing through the entire UI.
- Pluralization ("1 file" vs "5 files") —
string.Format covers most cases; dedicated pluralization libraries (e.g., SmartFormat) are overkill for current scope.
- Strings in vendored OxyPlot — only 1 string; leave English or add to its own dictionary if/when more accumulate.
- fr-CA fidelity — fr-FR ships first; if Quebec users push back on idioms, add a thin fr-CA overlay later (only the strings that differ — not a full re-translation).
- High-stakes audiences — Dutch (high English fluency, will judge translation quality harshly) and French (Ribatet is an external peer reviewer evaluating the software). Both deserve extra native-speaker review.
Verification
End-to-end test once infrastructure lands:
- Run
FrameworkUI.Demo.
- Open Tools → Options, switch language ComboBox from English to (test) Spanish, click Apply.
- Verify menus, toolbars, and any open dialogs update without restart.
- Open a fresh dialog (e.g., About) — verify it uses the new language.
- Open an AvalonDock floating window's context menu — verify it picks up Spanish from AvalonDock's bundled
.resx.
- Close and reopen the app — verify language persists via
UserSettings.
- Compare against the same flow for theme switching — they should feel identical.
Unit test target: LanguageService.SetLanguage() raises LanguageChanged and updates Application.Current.Resources.MergedDictionaries correctly.
Language Library — High-Level Plan
Context
The framework currently has runtime theme switching (Light / Blue / Dark) via a clean two-layer service architecture, but all UI text is hardcoded English. The user wants an analogous Language library so users can switch GUI language at runtime from
OptionsDialog, with the same UX as theme switching.Target user base (from USACE partnerships, deployments, and Google Analytics):
Selected Languages (locked)
The initial release supports 10 languages:
Result: 9 of 10 get AvalonDock localization for free (Dutch via the close-cousin nl-BE bundle). Only the framework's own strings need translation.
Answers to the User's 3 Questions
1. Is this possible?
Yes, fully. WPF supports runtime UI culture changes. Because the framework already uses
DynamicResourceheavily (for theme brushes), the same mechanism swaps localized strings: replace hardcodedHeader="File"withHeader="{DynamicResource Strings.Menu.File}", then swap a ResourceDictionary at runtime. No app restart required.The cleanest approach is mirror the theme architecture exactly — same shape, same patterns, same persistence layer. This minimizes architectural surface area and lets the same patterns reviewers already know carry forward.
2. How much effort is it?
Medium — roughly 5–8 developer-weeks for the locked 10 languages, broken down:
EnglishStrings.xamldictionary with keys (per-library: FrameworkUI, DatabaseControls, OxyPlotControls, GenericControls, etc.)Vendor code: AvalonDock already ships translations for 9 of the 10 selected languages — zero work. OxyPlot has 1 hardcoded string — negligible.
Per-language additional cost is small once infrastructure exists — adding an 11th language post-launch is mostly translation work + a one-line enum entry.
3. How should we design it?
Mirror the theme architecture, with one twist: pipe
Thread.CurrentUICultureso AvalonDock's existing.resx-based localization comes along for the ride.Recommended Architecture
Core library —
src/Themes/Core/(extend existing) or newsrc/Languages/Core/Mirror exactly:
Themeenum (Light/Blue/Dark)Languageenum —English, Spanish, Dutch, ChineseSimplified, French, German, PortugueseBrazil, Italian, Japanese, SwedishThemeService(singleton)LanguageService(singleton)ThemeResourceHelper(URI constants)LanguageResourceHelper(URI constants + culture codes — see locale column above)ThemeChangedeventLanguageChangedeventMergedDictionariesswap on app resourcesMergedDictionariesswap on app resources (same mechanism!)LanguageService.SetLanguage(Language):Application.Current.Resources.MergedDictionariesThread.CurrentThread.CurrentUICulture = newCulture(this makes AvalonDock's.resxfiles pick up the new language automatically)FrameworkElement.LanguageProperty(already done inApp.xaml.cs:47-51for number formatting — extend it)LanguageChangedeventFrameworkUI facade —
src/FrameworkUI/Languages/ThemeManager(static facade)LanguageManager(static facade)ThemeColorenumUILanguageenumSame shape as
ThemeManager. Subscribers can hookLanguageChangedif they need to refresh non-string content (e.g., re-rendered icons or auto-generated text).Per-language string dictionaries
src/Languages/Resources/Strings/EnglishStrings.xaml(andSpanish*,French*, etc.) — same structure asLightColors.xaml:Each language file uses identical
x:Keynames (same convention as the theme color files). English is the canonical/default fallback.Per-library dictionaries — exactly like themes
Yes — every consuming library/app ships its own per-language dictionaries, mirroring how each control library has its own theme dictionary today (e.g.,
FrameworkUI/Themes/VS2013/{Light,Blue,Dark}Theme.xaml, AvalonDock'sGeneric.xaml, etc.).Layered structure:
Themes/Resources/Colors/LightColors.xaml(universal brushes)Languages/Resources/Strings/EnglishStrings.xaml(universal terms: OK, Cancel, Yes, No, File, Open...)FrameworkUI/Themes/VS2013/LightTheme.xamlFrameworkUI/Languages/EnglishStrings.xamlDatabaseControls/Languages/EnglishStrings.xamlOxyPlotControls/Languages/EnglishStrings.xamlMyApp/Languages/EnglishStrings.xamlEach library gets its own keyspace prefix to avoid collisions (
Strings.FrameworkUI.Menu.File,Strings.OxyPlotControls.Toolbar.Zoom,Strings.DatabaseControls.Stats.Title).Registration pattern — libraries register their dictionary set with
LanguageServiceat startup:When
SetLanguage(Language.Spanish)is called,LanguageServicewalks every registered set, removes the current dictionary, and adds the Spanish one. Adding a new control library to a downstream app is just oneRegisterStringSetcall — exact mirror of how themes work today.This is also how end-user apps add their own app-specific strings (custom dialog titles, menu items, etc.) without modifying the framework.
XAML usage
Replace literals with
DynamicResource:Optional convenience: add a
{loc:String Strings.Menu.File}markup extension wrappingDynamicResourcefor terser syntax later.Code-behind usage
Add a small helper:
Replace
MessageBox.Show("Are you sure...")withMessageBox.Show(LocalizedStrings.Get("Strings.Dialog.ConfirmDelete")).OptionsDialog wiring
Mirror
GeneralOptions.xaml:34-36(the existing theme combo) — add a siblingLanguageComboBox:OptionsDialog.xaml.csApply handler callsLanguageManager.SetLanguage(...)right next to the existingThemeManager.SetTheme(...)call.Persistence
Add one line to
UserSettings.csnext toColorTheme:Already saved/loaded by the same XML mechanism — no changes to persistence code.
Startup
App.xaml.csApplication_Startup: addLanguageManager.SetLanguage(parsed)next to the existingThemeManager.SetTheme(...)call (line ~105). Defaults to system culture or English.Critical Files to Create / Modify
Create — core (one-time):
src/Languages/Core/Language.cs— enumsrc/Languages/Core/LanguageService.cs— singleton withRegisterStringSet+SetLanguagesrc/Languages/Core/LanguageResourceHelper.cs— URI / culture-code constantssrc/Languages/Resources/Strings/{English,Spanish,Dutch,ChineseSimplified,French,German,PortugueseBrazil,Italian,Japanese,Swedish}Strings.xaml— universal/shared terms (10 files)src/FrameworkUI/Languages/LanguageManager.cs— facadesrc/FrameworkUI/Languages/UILanguage.cs— enumsrc/FrameworkUI/Languages/LocalizedStrings.cs— code-behind helpersrc/FrameworkUI/Languages/LocStringExtension.cs—{loc:String Key}markup extensionCreate — per-library (one set per consuming library, mirroring theme dictionaries):
src/FrameworkUI/Languages/{10 language}Strings.xaml+RegisterStringSetcall at startupsrc/DatabaseControls/Languages/{10 language}Strings.xaml+ registrationsrc/OxyPlotControls/Languages/{10 language}Strings.xaml+ registrationsrc/GenericControls/Languages/{10 language}Strings.xaml+ registrationMyApp/Languages/{10 language}Strings.xamlfor app-specific textModify:
src/FrameworkUI/User Settings/UserSettings.cs— addLanguageproperty (~line 31)src/FrameworkUI/Tools Menu/Options/GeneralOptions.xaml(.cs)— add language ComboBox + DependencyPropertysrc/FrameworkUI/Tools Menu/Options/OptionsDialog.xaml.cs— callLanguageManager.SetLanguage()in Apply handler (~line 138)src/FrameworkUI.Demo/App.xaml.cs— callLanguageManager.SetLanguage()at startup (~line 105)src/FrameworkUI/Main Window/MainWindow.xaml.cs— subscribe toLanguageChanged(next to existingThemeChangedat ~line 95) for any non-resource refresh logicExisting Utilities to Reuse
Themes/Core/ThemeService.cs— copy/adapt the singleton initialization, MergedDictionaries swap, and event-raising pattern verbatim.UserSettingsXML save/load — already atomic (temp-file + rename); just add theLanguageproperty.App.xaml.cs:47-51already setsFrameworkElement.LanguageProperty = CultureInfo.CurrentCulture.IetfLanguageTagfor number formatting — generalize to react to language changes..resxlocalization — works automatically whenThread.CurrentUICultureis set; do not duplicate.Translation Tooling — Do You Need an External Service?
No — I (the assistant) can produce the translations directly as part of the work. UI strings are mostly short, idiomatic phrases ("File", "Save As...", "Are you sure?", "Connection failed"), which modern LLMs translate at near-professional quality for the major European languages (ES/FR/NL/DE/IT/PT/etc.).
Tradeoffs across the realistic options:
Recommended workflow:
EnglishStrings.xamlwith all keys (the canonical source)..xamlfile; I produceSpanishStrings.xaml,FrenchStrings.xaml, etc. directly. Output format matches input — samex:Keyvalues, same XML structure, just translated<sys:String>content.LocalizationGlossary.md) that pins canonical translations of recurring terms ("project", "database connection", "axis", "series") so revisions stay consistent.EnglishStrings.xamlas the single source of truth. When new strings are added, only the new keys need re-translation — existing translations stay stable.No external converter required, but if you prefer bulk machine output as a baseline, DeepL has the best quality among the API services for the European languages you mentioned.
Phased Rollout Recommendation
MainWindow.xaml(highest visibility), then dialog-by-dialog. Each region is an independently shippable PR. Build out per-library English dictionaries in parallel.EnglishStrings.xaml(per library) as the single source of truth. New strings added later only require translating the diff, not re-translating everything.Risks / Open Questions
MinWidthaudits.FlowDirection="RightToLeft"plumbing through the entire UI.string.Formatcovers most cases; dedicated pluralization libraries (e.g., SmartFormat) are overkill for current scope.Verification
End-to-end test once infrastructure lands:
FrameworkUI.Demo..resx.UserSettings.Unit test target:
LanguageService.SetLanguage()raisesLanguageChangedand updatesApplication.Current.Resources.MergedDictionariescorrectly.