Skip to content

[Quick Accent] Move language data to PowerAccent.Common library, refactor#47211

Merged
moooyo merged 10 commits into
microsoft:mainfrom
daverayment:feat/quick-accent-common-language-data
May 22, 2026
Merged

[Quick Accent] Move language data to PowerAccent.Common library, refactor#47211
moooyo merged 10 commits into
microsoft:mainfrom
daverayment:feat/quick-accent-common-language-data

Conversation

@daverayment
Copy link
Copy Markdown
Collaborator

Summary of the Pull Request

Language data for Quick Accent was previously defined in PowerAccent.Core/Languages.cs, internal to the Quick Accent application itself. The Settings application had no access to the list and had to maintain a parallel, manually-synchronised list of the language names and groups, and there was no single place to look up the complete set of languages.

This PR resolves this and extracts the language data into a new PowerAccent.Common project with no external or UI dependencies, making it the single source of truth for both the character popup and Settings UI.

The implicit and non-obvious ordering of the old Languages.cs has been replaced with explicit ordering of the language groups and languages list.

A new unit test project for the application has also been added.

PR Checklist

Detailed Description of the Pull Request / Additional comments

The following changes and additions have been made:

New project PowerAccent.Common contains:

  • Language.cs - an enum of Quick Accent's pseudo-ISO identifiers for each of the supported languages
  • LetterKey.cs - mirrors the LetterKey enum defined in the KeyboardListener.idl, and must be kept in sync with that. This exposes the VK_* data to the Settings UI without it having to reference the PowerAccentKeyboardService project
  • LanguageGroup.cs - classifies languages as Language (for spoken languages), Special (for sets like Currency and International Phonetic Alphabet), or UserDefined (reserved for future work)
  • LanguageInfo.cs - a record which combines a language's identity, group, resource identifier, and character mappings
  • CharacterMappings.cs - a new version of the old Languages.cs, providing:
    • All - the canonical registry of every LanguageInfo entry
    • DisplayOrder - the explicit within-group ordering for languages, for the default popup ordering, i.e. when the user has not got frequency-based ordering enabled. This is a culturally-neutral alphabetical ordering by our pseudo-ISO language IDs, and close to the previous order, minimising any impact to users' muscle memory
    • GroupDisplayOrder - the explicit ordering of language groups for both the popup and the Settings UI
    • LanguageLookup - new O(1) Language to LanguageInfo dictionary
    • GetCharacters(LetterKey, Language[]) - a deduplicating character collector and sorter

A significant improvement over the old Languages.cs is that language ordering is now explicit and in one place. Previously, popup ordering was an implicit side-effect of a large Union chain across all language mappings, with the enum, the language mapping declarations and the union all carrying different orderings. This was a source of confusion and made adding new languages fragile.

The original Languages.cs has been deleted. There's now a thin CharacterMappings adapter that casts the LetterKey to the managed equivalent by numeric value. This is another effort to decouple the Settings UI and Quick Accent and only share the required elements.

In Settings, the PowerAccentViewModel class has been updated. It now derives the language list directly from CharacterMappings.All; the InitializeLanguages() call handles localisation, sorting and grouping in a single place using GroupDisplayOrder.

A new unit test project covers CharacterMappings, the LetterKey IDL-to-managed code bridge and general language declaration checks. The application is now much more robust against changes which alter the declared order, introduce empty/null elements, and so on. It is now impossible to add a new language and forget a constituent part (the enum entry, its place in the display order, the character mappings themselves) without a test failing.

Validation Steps Performed

See new unit test project for comprehensive tests.

Manually confirmed:

  • Triggering Quick Accent with no language selected does not report an error
  • Language order is consistent in the popup, no matter what order the languages are selected in Settings
  • The order of languages in Settings is consistent, alphabetically by localised name
  • All declared languages are present in Settings and no resource IDs were missed

… and Settings.

Moved language/character mapping data and enums (Language, LetterKey) from PowerAccent.Core to new PowerAccent.Common project. Introduced CharacterMappings class for shared mapping logic. Updated Core to use a thin adapter for WinRT/managed enum bridging. Removed old Languages.cs. Updated all references in Core and Settings to use shared types. Added unit tests to ensure enum sync. Updated solution and project files to include new shared and test projects.
…arian. Correct issue for Welsh double quotes.
…order for languages and language groups, and additional unit tests.
@daverayment daverayment added the Product-Quick Accent Refers to the Quick Accent PowerToy label Apr 25, 2026
@github-actions

This comment has been minimized.

@daverayment
Copy link
Copy Markdown
Collaborator Author

@snickler Just FYI, I've added new project file with an explicit TargetFramework reference here - PowerAccent.Common.csproj. I wanted to keep it clear of CsWinRT dependencies, so didn't pull in Common.Dotnet.CsWinRT.props. I thought I'd better let you know so you could add it to your 300 other places-to-update for .NET 10 😉

It also made me wonder if the version-related information from Common.Dotnet.CSWinRT.props could/should be moved to a separate file for this sort of thing, i.e. the first PropertyGroup. Maybe Common.Dotnet.Versions.props or Common.Dotnet.WindowsTargets.props or something?

@github-actions

This comment has been minimized.

@snickler
Copy link
Copy Markdown
Collaborator

@snickler Just FYI, I've added new project file with an explicit TargetFramework reference here - PowerAccent.Common.csproj. I wanted to keep it clear of CsWinRT dependencies, so didn't pull in Common.Dotnet.CsWinRT.props. I thought I'd better let you know so you could add it to your 300 other places-to-update for .NET 10 😉

It also made me wonder if the version-related information from Common.Dotnet.CSWinRT.props could/should be moved to a separate file for this sort of thing, i.e. the first PropertyGroup. Maybe Common.Dotnet.Versions.props or Common.Dotnet.WindowsTargets.props or something?

Hmmm yeah maybe we move the CoreTargetFramework property outside of there into its own props and then make sure that if CSWinRT.props is included, it loads that prop as well so it can be at least called standalone

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extracts Quick Accent language/character mapping data into a new PowerAccent.Common library so both the Quick Accent module and the Settings UI consume the same canonical dataset, with explicit ordering and new unit tests to prevent regressions.

Changes:

  • Introduces PowerAccent.Common containing Language/LetterKey/LanguageInfo and the centralized CharacterMappings registry + ordering/lookup APIs.
  • Updates Settings UI and PowerAccent.Core to consume CharacterMappings instead of the removed Languages.cs.
  • Adds PowerAccent.Common.UnitTests to validate enum sync, data completeness, and ordering invariants.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/settings-ui/Settings.UI/ViewModels/PowerAccentViewModel.cs Builds Settings language/group models from PowerAccent.Common.CharacterMappings instead of a hardcoded list.
src/settings-ui/Settings.UI/PowerToys.Settings.csproj References the new PowerAccent.Common project from Settings UI.
src/modules/poweraccent/PowerAccent.Core/Services/SettingsService.cs Switches selected language parsing/storage to PowerAccent.Common.Language.
src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs Uses the new CharacterMappings.GetCharacters API for popup character retrieval.
src/modules/poweraccent/PowerAccent.Core/PowerAccent.Core.csproj References the new PowerAccent.Common project from Core.
src/modules/poweraccent/PowerAccent.Core/Languages.cs Removes the old monolithic mapping implementation.
src/modules/poweraccent/PowerAccent.Core/CharacterMappings.cs Adds a thin adapter to bridge WinRT LetterKey to managed PowerAccent.Common.LetterKey.
src/modules/poweraccent/PowerAccent.Common/PowerAccent.Common.csproj New shared library project for mappings and related types.
src/modules/poweraccent/PowerAccent.Common/Language.cs New shared Language enum.
src/modules/poweraccent/PowerAccent.Common/LetterKey.cs New managed LetterKey enum mirroring the WinRT IDL values.
src/modules/poweraccent/PowerAccent.Common/LanguageGroup.cs Defines language group categories shared by UI and core.
src/modules/poweraccent/PowerAccent.Common/LanguageInfo.cs Defines the per-language metadata record used by the registry.
src/modules/poweraccent/PowerAccent.Common/CharacterMappings.cs New canonical mapping registry, explicit ordering, lookup dictionary, and character collection API.
src/modules/poweraccent/PowerAccent.Common.UnitTests/PowerAccent.Common.UnitTests.csproj New unit test project validating mapping invariants and enum sync.
src/modules/poweraccent/PowerAccent.Common.UnitTests/LetterKeyTests.cs Tests that managed LetterKey matches WinRT LetterKey names/values.
src/modules/poweraccent/PowerAccent.Common.UnitTests/CharacterMappingsTests.cs Tests completeness, ordering, deduping, caching behavior, and invariants.
PowerToys.slnx Adds the new Common library and unit test project to the solution.

Comment thread src/settings-ui/Settings.UI/ViewModels/PowerAccentViewModel.cs Outdated
Comment on lines +36 to +40
public static readonly IReadOnlyList<LanguageInfo> All =
[
new(Language.SPECIAL, "Special", LanguageGroup.Special, new Dictionary<LetterKey, string[]>
{
[LetterKey.VK_0] = ["₀", "⁰", "°", "↉", "₎", "⁾"],
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

CharacterMappings.All is exposed publicly but is backed by mutable Dictionary<LetterKey, string[]> instances (and the string[] values are mutable as well). Because LanguageInfo.Characters is an interface type, consumers can still downcast and mutate the underlying dictionaries/arrays, which could lead to hard-to-debug runtime behavior and cache inconsistencies. Consider exposing immutable/read-only collections (e.g., ReadOnlyDictionary/ImmutableDictionary and immutable character collections) or otherwise preventing mutation of the shared mapping data.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Acknowledged — the outer Characters property is already typed as IReadOnlyDictionary<LetterKey, string[]>, which prevents adding/removing entries through the interface. The remaining mutability is in the inner string[] values. Converting these to ReadOnlyMemory<string> or ImmutableArray<string> would require changing the LanguageInfo record signature and all consumers — escalating this as a design trade-off for the maintainer to decide.

- Remove unsupported UserDefined group from _groupResourceKeys
- Add .Where() guards for groups without resource keys to prevent
  KeyNotFoundException when GroupDisplayOrder includes future groups

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@MuyuanMS
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@niels9001 niels9001 removed the 0.100 label May 20, 2026
… 10, and add the .csproj to the exclusion list for the pipeline verification step, as it does not target WinRT.
@daverayment daverayment requested a review from a team as a code owner May 20, 2026 15:23
@github-actions

This comment has been minimized.

@moooyo
Copy link
Copy Markdown
Contributor

moooyo commented May 20, 2026

please merge main and resolve the conflict.

@daverayment
Copy link
Copy Markdown
Collaborator Author

@moooyo I've merged main and resolved the conflicts. Thanks.

Copy link
Copy Markdown
Contributor

@moooyo moooyo left a comment

Choose a reason for hiding this comment

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

LGTM, Good to merge after pipeline pass

@moooyo
Copy link
Copy Markdown
Contributor

moooyo commented May 21, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@moooyo
Copy link
Copy Markdown
Contributor

moooyo commented May 21, 2026

@daverayment still failed. src\common\interop\interop-tests\bin\x64\Release\Microsoft.Interop.Tests.dll(0,0): Error run failed: Tests failed: 'C:\a_work\1\s\src\common\interop\interop-tests\bin\x64\Release\TestResults\Microsoft.Interop.Tests_net10.0-windows10.0.26100.0_x64.log' [net10.0-windows10.0.26100.0|x64]

@daverayment
Copy link
Copy Markdown
Collaborator Author

@moooyo These PowerAccent changes have no effect on IPC messaging, which is what the Interop test is exercising.

The test has this note and ad-hoc 100ms delay, which indicates it may have failed because of a resource-constrained agent or other timing issue:

    // Test can be flaky as the pipes are still being set up and we end up receiving no message. Wait for a bit to avoid that.
    Thread.Sleep(100);

Could you try the run again, please?

@DHowett
Copy link
Copy Markdown
Member

DHowett commented May 21, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@moooyo
Copy link
Copy Markdown
Contributor

moooyo commented May 22, 2026

need a owner approval

@moooyo moooyo merged commit 81e251c into microsoft:main May 22, 2026
12 checks passed
niels9001 pushed a commit that referenced this pull request May 22, 2026
…ytonic resource (#48054)

## Summary

Fixes the **"Build PowerToys main project"** failure on every PR/CI
build off current `main` (e.g. Dart build
[`147597426`](https://microsoft.visualstudio.com/Dart/_build/results?buildId=147597426)):

```
WINAPPSDKGENERATEPROJECTPRIFILE: Error PRI175: Processing Resources failed with error: Duplicate Entry.
WINAPPSDKGENERATEPROJECTPRIFILE: Error PRI277: Conflicting values for resource
                                  'Resources/QuickAccent_SelectedLanguage_Greek_Polytonic'
```

## Root cause

`src/settings-ui/Settings.UI/Strings/en-us/Resources.resw` contained two
`<data name="QuickAccent_SelectedLanguage_Greek_Polytonic">` entries:

- Line 3597 — original, added by #47021 (*"[PowerAccent] adding greek
polytonic"*), placed in the alphabetized QuickAccent block.
- Line 6175 — duplicate, appended at the file tail by #47211 (*"[Quick
Accent] Move language data to PowerAccent.Common library, refactor"*).

The two entries are byte-identical (same value `Greek Polytonic`, same
`xml:space="preserve"`). The WinAppSDK PRI generator (`MakePri`) rejects
duplicates, so every build off current `main` fails before reaching the
compile step.

## Fix

Remove the tail duplicate (3-line block right before `</root>`). Keeps
the original entry in its alphabetized location.

## Validation

- `git diff` shows exactly 3 deleted lines.
- `[xml](Get-Content … -Raw)` round-trip succeeds — file is still
well-formed XML.
- `Select-String` count of
`QuickAccent_SelectedLanguage_Greek_Polytonic` in the file is now **1**
(was 2).
- Only `en-us` has this key (other localized `.resw` files are populated
later via Touchdown), so no other file needs touching.

## Note

Discovered while investigating a CI failure unrelated to my other PR
#48050 (signing of `WinUI3Apps\YamlDotNet.dll`). Both fixes are needed
for the 0.100 release pipeline; this PR unblocks PR builds off `main`,
and #48050 unblocks the signed release pipeline once both are merged to
`main` and the next `main → stable` sync happens.

Co-authored-by: Copilot <Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@daverayment daverayment deleted the feat/quick-accent-common-language-data branch May 22, 2026 14:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

0.100 Product-Quick Accent Refers to the Quick Accent PowerToy

Projects

None yet

10 participants