Skip to content

fix(ui): mirror the page host so an RTL language applies on startup - #94

Merged
itsfatduck merged 1 commit into
itsfatduck:masterfrom
NimaHp:fix/rtl-direction
Oct 10, 2026
Merged

itsfatduck merged 1 commit into
itsfatduck:masterfrom
NimaHp:fix/rtl-direction

Conversation

@NimaHp

@NimaHp NimaHp commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Fixes right-to-left languages rendering left to right on a cold start.

Cause

PART_FrameCP is the ContentPresenter that hosts every page. It does not pick the inherited FlowDirection up when the template is applied, so with fa-IR, ar-SA or he-IL already stored in appsettings.json the whole content area stayed left to right. It only landed on the next change of the property, which made "switch to an LTR language and back" the only way out.

The parent NavigationViewContentPresenter was already right to left, so the navigation pane mirrored correctly while the content did not.

Fix

Bind FlowDirection off the templated parent on both page hosts. There are two NavigationViewContentPresenter templates in NavigationViewOverride.xaml: the pane templates used by the main navigation, and the scrollable top template that OptimizePage uses for its category tabs. One file, 18 lines.

Left to right is unaffected: the binding resolves to LeftToRight there, and it also covers a language change at runtime.

Verification

Walked the live visual tree of a cold start with fa-IR stored, before and after, and replayed the reported workaround.

Before:

NavigationViewContentPresenter RightToLeft
  ContentPresenter LeftToRight    <-- page host, left to right at startup

after switching to en-US and back to fa-IR:
  ContentPresenter RightToLeft

After the fix, on the cold start:

OptimizePage             RightToLeft  rtlAncestor=True
PerformanceOptimizerPage RightToLeft  rtlAncestor=True

No element is left to right under a right to left ancestor except the window chrome above MainWindow's root grid (AdornerDecorator, ClientAreaBorder, its ContentPresenter, AdornerLayer), which is outside the app content.

  • dotnet build optimizerDuck.slnx --configuration Release: 0 warnings, 0 errors
  • full suite with -longRunning 30: Total: 755, Errors: 0, Failed: 0, Skipped: 5
  • csharpier check .: clean (302 files)

Scope

Direction only, deliberately. This is what made the reported symptom reproducible and fixable in one place; anything else that is still left to right inside a mirrored page is separate work and not guessed at here.

No test covers a ControlTemplate binding, which is why the visual tree dump is the evidence.

PART_FrameCP is the ContentPresenter that hosts every page, and it does not pick the inherited FlowDirection up when the template is applied. It only landed on the next change of the property, so with fa-IR, ar-SA or he-IL already stored in appsettings.json the whole content area stayed left to right on a cold start. Switching to an LTR language and back was the only way out.

The parent NavigationViewContentPresenter was already right to left, which is why the navigation pane mirrored correctly while the content did not, and why the dashboard looked right while the optimize page did not.

Both page hosts need it. There are two NavigationViewContentPresenter templates in this file: the pane templates, used by the main navigation, and the scrollable top template, which OptimizePage uses for its category tabs. Fixing only the first left the optimize page left to right.

Binding the property off the templated parent also covers a direction change at runtime. Left to right is unaffected, the binding resolves to LeftToRight there.

Verified by walking the live visual tree of a cold start with fa-IR stored, before and after, and by replaying the reported workaround. Before:

  NavigationViewContentPresenter RightToLeft
    ContentPresenter LeftToRight    <-- page host, left to right at startup

  after switching to en-US and back to fa-IR:
    ContentPresenter RightToLeft

After the fix the page host is right to left on the cold start, OptimizePage and its category page inherit it, and the only remaining LeftToRight elements are the window chrome above MainWindow's root grid (AdornerDecorator, ClientAreaBorder, its ContentPresenter, AdornerLayer), which is outside the app content.

This is layout direction only. Rows that hardcode HorizontalAlignment or rely on the child order of a StackPanel or WrapPanel are still left to right inside the now mirrored pages, because WPF mirrors text and TextAlignment and no layout. That is separate work.

No test covers a ControlTemplate binding. Full suite with -longRunning 30: 755 total, 0 failed.
@NimaHp
NimaHp requested a review from itsfatduck as a code owner October 9, 2026 18:58

@itsfatduck itsfatduck left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for fixing this bug!

@itsfatduck
itsfatduck merged commit 469422f into itsfatduck:master Oct 10, 2026
1 check passed
@NimaHp
NimaHp deleted the fix/rtl-direction branch October 11, 2026 06:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants