Add localization for app names and descriptions (#769)

* Translate app names and descriptions in Config/Apps.json

* Document Apps section and first-ID translation key for multi-ID apps

* Fix app list breaking when "Only show installed" runs translation in an isolated runspace
This commit is contained in:
Sashank
2026-10-01 21:25:34 +02:00
committed by GitHub
parent 32024662f3
commit 6feebba878
11 changed files with 705 additions and 38 deletions
+9 -5
View File
@@ -196,7 +196,7 @@ Avoid these common mistakes when contributing:
8. **Hardcoded Paths**: When writing PowerShell logic, use `$PSScriptRoot` and script variables instead of hardcoded paths. This ensures the script works regardless of where it's installed. 8. **Hardcoded Paths**: When writing PowerShell logic, use `$PSScriptRoot` and script variables instead of hardcoded paths. This ensures the script works regardless of where it's installed.
9. **Missing Translation Entry**: A new Feature, Category, or UI Group needs a matching entry in `Config/Languages/en-US/` (see [Localizing UI Text](#localizing-ui-text)). Without one, the GUI silently falls back to displaying the raw `FeatureId`/`CategoryId`/`GroupId` instead of your `Label`/`ToolTip` text. 9. **Missing Translation Entry**: A new Feature, Category, UI Group, or App needs a matching entry in `Config/Languages/en-US/` (see [Localizing UI Text](#localizing-ui-text)). Without one, the GUI silently falls back to displaying the raw `FeatureId`/`CategoryId`/`GroupId`/`AppId` instead of your `Label`/`ToolTip`/`FriendlyName`/`Description` text.
## Implementing New Features ## Implementing New Features
@@ -233,7 +233,8 @@ To add a new app that can be removed via Win11Debloat:
- `FriendlyName`: Display name shown in the GUI. - `FriendlyName`: Display name shown in the GUI.
- `AppId`: The package name from `Get-AppxPackage` or the `Id` from - `AppId`: The package name from `Get-AppxPackage` or the `Id` from
`winget list`, depending on removal method. Use an array when one app `winget list`, depending on removal method. Use an array when one app
requires multiple identifiers. requires multiple identifiers. The GUI looks up the translation entry by
the first ID in the array, key `Apps.json` by that one.
- `Description`: Brief description of the app shown in the GUI. - `Description`: Brief description of the app shown in the GUI.
- `SelectedByDefault`: Set to `true` only for apps that are largely considered bloatware, otherwise set to `false`. - `SelectedByDefault`: Set to `true` only for apps that are largely considered bloatware, otherwise set to `false`.
- `Recommendation`: Indicates how strongly the app is recommended for removal. One of: - `Recommendation`: Indicates how strongly the app is recommended for removal. One of:
@@ -244,7 +245,9 @@ To add a new app that can be removed via Win11Debloat:
- `Appx` — remove as a standard Appx package via `Remove-AppxPackage` (most apps) - `Appx` — remove as a standard Appx package via `Remove-AppxPackage` (most apps)
- `WinGet` — remove via WinGet (`winget uninstall`). Use for non-Appx apps such as Microsoft Copilot. - `WinGet` — remove via WinGet (`winget uninstall`). Use for non-Appx apps such as Microsoft Copilot.
3. **Follow the Guidelines**: 3. **Add the English Translation Entry**: Add a matching entry for your `AppId` to `Config/Languages/en-US/Apps.json` (see [Localizing UI Text](#localizing-ui-text)) with `FriendlyName` and `Description` matching what you set above, so the GUI shows them instead of the raw `AppId`.
4. **Follow the Guidelines**:
- Use clear, user-friendly names for `FriendlyName` - Use clear, user-friendly names for `FriendlyName`
- Provide a concise description explaining what the app does - Provide a concise description explaining what the app does
@@ -451,11 +454,12 @@ Add a matching entry for your `GroupId` to `Config/Languages/en-US/Features.json
### Localizing UI Text ### Localizing UI Text
GUI text lives in `Config/Languages/en-US/`, not hardcoded in `Schemas/*.xaml` or `Scripts/GUI/*.ps1`. Adding or changing a Feature, Category, or UI Group means updating the matching English entry, or the GUI falls back to showing the raw ID. GUI text lives in `Config/Languages/en-US/`, not hardcoded in `Schemas/*.xaml` or `Scripts/GUI/*.ps1`. Adding or changing a Feature, Category, UI Group, or App means updating the matching English entry, or the GUI falls back to showing the raw ID.
- `Config/Languages/en-US/Features.json`, under `"Features"`, keyed by `FeatureId`. Add `Label`, `ToolTip`, `ApplyText`, `UndoLabel`, and `ApplyUndoText` to match whatever fields you set in `Config/Features.json`. - `Config/Languages/en-US/Features.json`, under `"Features"`, keyed by `FeatureId`. Add `Label`, `ToolTip`, `ApplyText`, `UndoLabel`, and `ApplyUndoText` to match whatever fields you set in `Config/Features.json`.
- `Config/Languages/en-US/Features.json`, under `"UiGroups"`, keyed by `GroupId`. Add `Label`, `ToolTip`, and a `Values` map of `FeatureId: "Option label"` matching the group's `Values` array. - `Config/Languages/en-US/Features.json`, under `"UiGroups"`, keyed by `GroupId`. Add `Label`, `ToolTip`, and a `Values` map of `FeatureId: "Option label"` matching the group's `Values` array.
- `Config/Languages/en-US/Categories.json`, keyed by `CategoryId`. Add a `Label` for every new category. - `Config/Languages/en-US/Categories.json`, keyed by `CategoryId`. Add a `Label` for every new category.
- `Config/Languages/en-US/Apps.json`, keyed by `AppId`. Add `FriendlyName` and `Description` for every new app.
- `Config/Languages/en-US/Chrome.json`, a flat key/value map for static GUI chrome: buttons, dialog titles, tooltips not tied to a specific Feature/Category/UiGroup. Add a key here only for new static text, and reference it from XAML with a `%LANG:YourKey%` marker or from PowerShell with `Get-Translation -Key 'YourKey'`. - `Config/Languages/en-US/Chrome.json`, a flat key/value map for static GUI chrome: buttons, dialog titles, tooltips not tied to a specific Feature/Category/UiGroup. Add a key here only for new static text, and reference it from XAML with a `%LANG:YourKey%` marker or from PowerShell with `Get-Translation -Key 'YourKey'`.
Check for an existing `Chrome.json` key with the same English text before adding a new one. Two keys holding identical strings is duplication waiting to drift. Check for an existing `Chrome.json` key with the same English text before adding a new one. Two keys holding identical strings is duplication waiting to drift.
@@ -468,7 +472,7 @@ A new language is its own pull request, not something bundled with unrelated cha
1. **Create the language folder**: copy `Config/Languages/en-US/` to `Config/Languages/<culture-code>/`, using the .NET culture code the GUI should match (`nl-NL`, `es-ES`). This is also the folder matched against the user's Windows display language: exact match first, then a language-only prefix match (`nl-BE` falls back to `nl-NL` if that's the only Dutch folder present), then `en-US` if nothing matches. 1. **Create the language folder**: copy `Config/Languages/en-US/` to `Config/Languages/<culture-code>/`, using the .NET culture code the GUI should match (`nl-NL`, `es-ES`). This is also the folder matched against the user's Windows display language: exact match first, then a language-only prefix match (`nl-BE` falls back to `nl-NL` if that's the only Dutch folder present), then `en-US` if nothing matches.
2. **Translate the three files** (`Chrome.json`, `Features.json`, `Categories.json`), keeping every key name exactly as it is in `en-US`. Only the values change. Leave a key untranslated and the GUI shows the `en-US` text for it instead of a blank or a crash, so a translation can land incrementally. 2. **Translate the four files** (`Chrome.json`, `Features.json`, `Categories.json`, `Apps.json`), keeping every key name exactly as it is in `en-US`. Only the values change. Leave a key untranslated and the GUI shows the `en-US` text for it instead of a blank or a crash, so a translation can land incrementally.
3. **Add the language's plural rule**. Strings using the `_one`/`_other` suffix convention (see `AppsSelectedCount_one` in `en-US/Chrome.json`) need a matching `case` in `Get-PluralCategory` (`Scripts/FileIO/Import-LanguageFile.ps1`), keyed by language prefix (`'nl'`). Several languages share English's rule, singular at exactly 1, plural otherwise, and can reuse it as-is. Others need more categories: CLDR defines `zero`/`one`/`two`/`few`/`many`/`other`. Match your key suffixes to whatever categories your language's rule actually uses. 3. **Add the language's plural rule**. Strings using the `_one`/`_other` suffix convention (see `AppsSelectedCount_one` in `en-US/Chrome.json`) need a matching `case` in `Get-PluralCategory` (`Scripts/FileIO/Import-LanguageFile.ps1`), keyed by language prefix (`'nl'`). Several languages share English's rule, singular at exactly 1, plural otherwise, and can reuse it as-is. Others need more categories: CLDR defines `zero`/`one`/`two`/`few`/`many`/`other`. Match your key suffixes to whatever categories your language's rule actually uses.
+566
View File
@@ -0,0 +1,566 @@
{
"Clipchamp.Clipchamp": {
"FriendlyName": "Clipchamp",
"Description": "Video editor from Microsoft"
},
"Microsoft.3DBuilder": {
"FriendlyName": "3D Builder",
"Description": "Basic 3D modeling software"
},
"Microsoft.549981C3F5F10": {
"FriendlyName": "Cortana",
"Description": "Microsoft Cortana voice assistant (Discontinued)"
},
"Microsoft.BingFinance": {
"FriendlyName": "Bing Finance",
"Description": "Finance news and tracking via Bing (Discontinued)"
},
"Microsoft.BingFoodAndDrink": {
"FriendlyName": "Bing Food And Drink",
"Description": "Recipes and food news via Bing (Discontinued)"
},
"Microsoft.BingHealthAndFitness": {
"FriendlyName": "Bing Health And Fitness",
"Description": "Health and fitness tracking/news via Bing (Discontinued)"
},
"Microsoft.BingNews": {
"FriendlyName": "Bing News",
"Description": "News aggregator via Bing (Replaced by Microsoft News/Start)"
},
"Microsoft.BingSports": {
"FriendlyName": "Bing Sports",
"Description": "Sports news and scores via Bing (Discontinued)"
},
"Microsoft.BingTranslator": {
"FriendlyName": "Bing Translator",
"Description": "Translation service via Bing"
},
"Microsoft.BingTravel": {
"FriendlyName": "Bing Travel",
"Description": "Travel planning and news via Bing (Discontinued)"
},
"Microsoft.BingWeather": {
"FriendlyName": "Bing Weather",
"Description": "Weather forecast via Bing"
},
"XP9CXNGPPJ97XX": {
"FriendlyName": "Microsoft Copilot",
"Description": "AI assistant integrated into Windows"
},
"Microsoft.Windows.AIHub": {
"FriendlyName": "Copilot+ AI Hub",
"Description": "Copilot+ AI Hub app (Windows 11 24H2+)"
},
"Microsoft.PCManager": {
"FriendlyName": "Microsoft PC Manager",
"Description": "Microsoft PC Manager system cleanup and optimization tool (often preinstalled)"
},
"Microsoft.Getstarted": {
"FriendlyName": "Get Started",
"Description": "Tips and introductory guide for Windows (Cannot be uninstalled in Windows 11)"
},
"Microsoft.Messaging": {
"FriendlyName": "Messaging",
"Description": "Messaging app, often integrates with Skype (Largely discontinued)"
},
"Microsoft.Microsoft3DViewer": {
"FriendlyName": "3D Viewer",
"Description": "Viewer for 3D models"
},
"Microsoft.MicrosoftJournal": {
"FriendlyName": "Microsoft Journal",
"Description": "Digital note-taking app optimized for pen input"
},
"Microsoft.MicrosoftOfficeHub": {
"FriendlyName": "Office Hub",
"Description": "Hub to access Microsoft Office apps and documents (Precursor to Microsoft 365 app)"
},
"Microsoft.MicrosoftPowerBIForWindows": {
"FriendlyName": "Power BI",
"Description": "Business analytics service client"
},
"Microsoft.MicrosoftSolitaireCollection": {
"FriendlyName": "Solitaire Collection",
"Description": "Collection of solitaire card games"
},
"Microsoft.MicrosoftStickyNotes": {
"FriendlyName": "Sticky Notes",
"Description": "Digital sticky notes app (Discontinued & replaced by OneNote)"
},
"Microsoft.MixedReality.Portal": {
"FriendlyName": "Mixed Reality Portal",
"Description": "Portal for Windows Mixed Reality headsets"
},
"Microsoft.NetworkSpeedTest": {
"FriendlyName": "Network Speed Test",
"Description": "Internet connection speed test utility"
},
"Microsoft.News": {
"FriendlyName": "Microsoft News",
"Description": "News aggregator (Replaced Bing News, now part of Microsoft Start)"
},
"Microsoft.Office.OneNote": {
"FriendlyName": "OneNote",
"Description": "Digital note-taking app (Universal Windows Platform version)"
},
"Microsoft.Office.Sway": {
"FriendlyName": "Sway",
"Description": "Presentation and storytelling app"
},
"Microsoft.OneConnect": {
"FriendlyName": "One Connect",
"Description": "Mobile Operator management app (Replaced by Mobile Plans)"
},
"Microsoft.Print3D": {
"FriendlyName": "Print 3D",
"Description": "3D printing preparation software"
},
"Microsoft.PowerAutomateDesktop": {
"FriendlyName": "Power Automate",
"Description": "Desktop automation tool (RPA)"
},
"Microsoft.SkypeApp": {
"FriendlyName": "Skype (UWP)",
"Description": "Skype communication app, Universal Windows Platform version (Discontinued)"
},
"Microsoft.Todos": {
"FriendlyName": "Microsoft To Do",
"Description": "To-do list and task management app"
},
"Microsoft.Windows.DevHome": {
"FriendlyName": "Dev Home",
"Description": "Developer dashboard and tool configuration utility (Discontinued)"
},
"Microsoft.WindowsAlarms": {
"FriendlyName": "Alarms & Clock",
"Description": "Alarms & Clock app"
},
"Microsoft.WindowsFeedbackHub": {
"FriendlyName": "Feedback Hub",
"Description": "App for providing feedback to Microsoft on Windows"
},
"Microsoft.WindowsMaps": {
"FriendlyName": "Windows Maps",
"Description": "Mapping and navigation app"
},
"Microsoft.WindowsSoundRecorder": {
"FriendlyName": "Sound Recorder",
"Description": "Basic audio recording app"
},
"Microsoft.XboxApp": {
"FriendlyName": "Xbox Console Companion",
"Description": "Old Xbox Console Companion App (Discontinued)"
},
"Microsoft.ZuneVideo": {
"FriendlyName": "Movies & TV",
"Description": "Movies & TV app for renting/buying/playing video content (Rebranded as \"Films & TV\")"
},
"MicrosoftCorporationII.MicrosoftFamily": {
"FriendlyName": "Family Safety",
"Description": "Family Safety App for managing family accounts and settings"
},
"MicrosoftCorporationII.QuickAssist": {
"FriendlyName": "Quick Assist",
"Description": "Remote assistance tool"
},
"MicrosoftTeams": {
"FriendlyName": "Microsoft Teams (Old)",
"Description": "Old Microsoft Teams personal (MS Store version)"
},
"MSTeams": {
"FriendlyName": "Microsoft Teams (New)",
"Description": "New Microsoft Teams app (Work/School or Personal)"
},
"ACGMediaPlayer": {
"FriendlyName": "ACG Media Player",
"Description": "Media player app"
},
"ActiproSoftwareLLC": {
"FriendlyName": "Actipro Software",
"Description": "Potentially UI controls or software components, often bundled by OEMs"
},
"AdobeSystemsIncorporated.AdobePhotoshopExpress": {
"FriendlyName": "Adobe Photoshop Express",
"Description": "Basic photo editing app from Adobe"
},
"Amazon.com.Amazon": {
"FriendlyName": "Amazon",
"Description": "Amazon shopping app"
},
"AmazonVideo.PrimeVideo": {
"FriendlyName": "Prime Video",
"Description": "Amazon Prime Video streaming service app"
},
"Asphalt8Airborne": {
"FriendlyName": "Asphalt 8",
"Description": "Racing game"
},
"AutodeskSketchBook": {
"FriendlyName": "Autodesk SketchBook",
"Description": "Digital drawing and sketching app"
},
"CaesarsSlotsFreeCasino": {
"FriendlyName": "Caesars Slots",
"Description": "Casino slot machine game"
},
"COOKINGFEVER": {
"FriendlyName": "Cooking Fever",
"Description": "Restaurant simulation game"
},
"CyberLinkMediaSuiteEssentials": {
"FriendlyName": "CyberLink Media Suite",
"Description": "Multimedia software suite (often preinstalled by OEMs)"
},
"DisneyMagicKingdoms": {
"FriendlyName": "Disney Magic Kingdoms",
"Description": "Disney theme park building game"
},
"Disney.37853FC22B2CE": {
"FriendlyName": "Disney+",
"Description": "Disney+ streaming service app"
},
"DrawboardPDF": {
"FriendlyName": "Drawboard PDF",
"Description": "PDF viewing and annotation app, often focused on pen input"
},
"Duolingo-LearnLanguagesforFree": {
"FriendlyName": "Duolingo",
"Description": "Language learning app"
},
"EclipseManager": {
"FriendlyName": "Eclipse Manager",
"Description": "Often related to specific OEM software or utilities (e.g. for managing screen settings)"
},
"FACEBOOK.FACEBOOK": {
"FriendlyName": "Facebook",
"Description": "Facebook social media app"
},
"FarmVille2CountryEscape": {
"FriendlyName": "FarmVille 2",
"Description": "Farming simulation game"
},
"Flipboard": {
"FriendlyName": "Flipboard",
"Description": "News and social network aggregator styled as a magazine"
},
"HiddenCity": {
"FriendlyName": "Hidden City",
"Description": "Hidden object puzzle adventure game"
},
"HULULLC.HULUPLUS": {
"FriendlyName": "Hulu",
"Description": "Hulu streaming service app"
},
"iHeartRadio": {
"FriendlyName": "iHeartRadio",
"Description": "Internet radio streaming app"
},
"Facebook.Instagram": {
"FriendlyName": "Instagram",
"Description": "Instagram social media app"
},
"king.com.BubbleWitch3Saga": {
"FriendlyName": "Bubble Witch 3",
"Description": "Puzzle game from King"
},
"king.com.CandyCrushSaga": {
"FriendlyName": "Candy Crush Saga",
"Description": "Puzzle game from King"
},
"king.com.CandyCrushSodaSaga": {
"FriendlyName": "Candy Crush Soda",
"Description": "Puzzle game from King"
},
"LinkedInforWindows": {
"FriendlyName": "LinkedIn",
"Description": "LinkedIn professional networking app"
},
"MarchofEmpires": {
"FriendlyName": "March of Empires",
"Description": "Strategy game"
},
"4DF9E0F8.Netflix": {
"FriendlyName": "Netflix",
"Description": "Netflix streaming service app"
},
"NYTCrossword": {
"FriendlyName": "NYT Crossword",
"Description": "New York Times crossword puzzle app"
},
"OneCalendar": {
"FriendlyName": "One Calendar",
"Description": "Calendar aggregation app"
},
"PandoraMediaInc": {
"FriendlyName": "Pandora",
"Description": "Pandora music streaming app"
},
"PhototasticCollage": {
"FriendlyName": "Phototastic Collage",
"Description": "Photo collage creation app"
},
"PicsArt-PhotoStudio": {
"FriendlyName": "PicsArt",
"Description": "Photo editing and creative app"
},
"PolarrPhotoEditorAcademicEdition": {
"FriendlyName": "Polarr Photo Editor",
"Description": "Photo editing app (Academic Edition)"
},
"flaregamesGmbH.RoyalRevolt": {
"FriendlyName": "Royal Revolt",
"Description": "Tower defense / strategy game"
},
"Sidia.LiveWallpaper": {
"FriendlyName": "Live Wallpaper",
"Description": "Live wallpaper app"
},
"SlingTV": {
"FriendlyName": "Sling TV",
"Description": "Live TV streaming service app"
},
"SpotifyAB.SpotifyMusic": {
"FriendlyName": "Spotify",
"Description": "Spotify music streaming app"
},
"BytedancePte.Ltd.TikTok": {
"FriendlyName": "TikTok",
"Description": "TikTok short-form video app"
},
"TuneInRadio": {
"FriendlyName": "TuneIn Radio",
"Description": "Internet radio streaming app"
},
"WinZipUniversal": {
"FriendlyName": "WinZip",
"Description": "File compression and extraction utility (Universal Windows Platform version)"
},
"Microsoft.BingSearch": {
"FriendlyName": "Bing Search",
"Description": "Web Search from Microsoft Bing (Integrates into Windows Search)"
},
"Microsoft.Edge": {
"FriendlyName": "Microsoft Edge",
"Description": "Windows' default browser, WARNING: Removing this app also removes the only browser from Windows Sandbox and could affect other apps"
},
"Microsoft.GamingApp": {
"FriendlyName": "Xbox Gaming App",
"Description": "Modern Xbox Gaming App, required for installing some PC games"
},
"Microsoft.GetHelp": {
"FriendlyName": "Get Help",
"Description": "Required for some Windows 11 Troubleshooters and support interactions"
},
"Microsoft.M365Companions": {
"FriendlyName": "Microsoft 365 Companions",
"Description": "Microsoft 365 (Business) Calendar, Files and People mini-apps, these apps may be reinstalled if enabled by your Microsoft 365 admin"
},
"Microsoft.MSPaint": {
"FriendlyName": "Paint 3D",
"Description": "Paint 3D (Modern paint application with 3D features)"
},
"Microsoft.OneDrive": {
"FriendlyName": "OneDrive",
"Description": "OneDrive consumer cloud storage client"
},
"Microsoft.OutlookForWindows": {
"FriendlyName": "Outlook for Windows",
"Description": "New Outlook for Windows mail client"
},
"Microsoft.Paint": {
"FriendlyName": "Paint",
"Description": "Classic Paint (Traditional 2D paint application)"
},
"Microsoft.People": {
"FriendlyName": "People",
"Description": "Required for & included with Mail & Calendar (discontinued)"
},
"Microsoft.RemoteDesktop": {
"FriendlyName": "Remote Desktop",
"Description": "Remote Desktop client app"
},
"Microsoft.ScreenSketch": {
"FriendlyName": "Snipping Tool",
"Description": "Snipping Tool (Screenshot and annotation tool)"
},
"Microsoft.StartExperiencesApp": {
"FriendlyName": "Widgets Experience",
"Description": "This app powers Windows Widgets My Feed"
},
"Microsoft.Whiteboard": {
"FriendlyName": "Whiteboard",
"Description": "Digital collaborative whiteboard app"
},
"Microsoft.Windows.Photos": {
"FriendlyName": "Photos",
"Description": "Default photo viewing and basic editing app"
},
"Microsoft.WindowsCalculator": {
"FriendlyName": "Calculator",
"Description": "Calculator app"
},
"Microsoft.WindowsCamera": {
"FriendlyName": "Camera",
"Description": "Camera app for using built-in or connected cameras"
},
"Microsoft.windowscommunicationsapps": {
"FriendlyName": "Mail & Calendar",
"Description": "Mail & Calendar app suite (Discontinued)"
},
"Microsoft.WindowsNotepad": {
"FriendlyName": "Notepad",
"Description": "Notepad text editor app"
},
"Microsoft.WindowsStore": {
"FriendlyName": "Microsoft Store",
"Description": "Microsoft Store, WARNING: This app cannot be reinstalled easily if removed!"
},
"Microsoft.WindowsTerminal": {
"FriendlyName": "Windows Terminal",
"Description": "Default terminal app in windows 11 (Command Prompt, PowerShell, WSL), WARNING: Do not remove if you launched Win11Debloat from Windows Terminal, as this will cause the script to fail."
},
"Microsoft.Xbox.TCUI": {
"FriendlyName": "Xbox TCUI Framework",
"Description": "UI framework, seems to be required for Microsoft Store, photos and certain games"
},
"Microsoft.XboxGameOverlay": {
"FriendlyName": "Xbox Game Overlay",
"Description": "Game overlay, required/useful for some games (Part of Xbox Game Bar)"
},
"Microsoft.XboxGamingOverlay": {
"FriendlyName": "Xbox Gaming Overlay",
"Description": "Game overlay, required/useful for some games (Part of Xbox Game Bar)"
},
"Microsoft.XboxIdentityProvider": {
"FriendlyName": "Xbox Identity Provider",
"Description": "Xbox sign-in framework, required for some games and Xbox services"
},
"Microsoft.XboxSpeechToTextOverlay": {
"FriendlyName": "Xbox Speech To Text",
"Description": "Accessibility feature required for some games, WARNING: This app cannot be reinstalled easily!"
},
"Microsoft.YourPhone": {
"FriendlyName": "Phone Link",
"Description": "Phone link (Connects Android/iOS phone to PC)"
},
"Microsoft.ZuneMusic": {
"FriendlyName": "Media Player",
"Description": "Modern Media Player (Replaced Groove Music, plays local audio/video)"
},
"MicrosoftWindows.CrossDevice": {
"FriendlyName": "Cross Device Experience",
"Description": "Phone integration within File Explorer, Camera and more (Part of Phone Link features)"
},
"MicrosoftWindows.Client.WebExperience": {
"FriendlyName": "Windows Web Experience Pack",
"Description": "Helps deliver and update certain features, like Widgets, through the Microsoft Store"
},
"Microsoft.WidgetsPlatformRuntime": {
"FriendlyName": "Widgets Platform Runtime",
"Description": "Runtime required for Windows Widgets to function"
},
"LGElectronics.LGMonitorApp": {
"FriendlyName": "LG Monitor App",
"Description": "LG OEM software for controlling your LG monitor, Windows Update may silently reinstall this app if you have an LG monitor connected"
},
"AD2F1837.HPAIExperienceCenter": {
"FriendlyName": "HP AI Experience Center",
"Description": "HP OEM software, AI-enhanced features and support"
},
"AD2F1837.HPConnectedMusic": {
"FriendlyName": "HP Connected Music",
"Description": "HP OEM software for music (Potentially discontinued)"
},
"AD2F1837.HPConnectedPhotopoweredbySnapfish": {
"FriendlyName": "HP Connected Photo",
"Description": "HP OEM software for photos, integrated with Snapfish (Potentially discontinued)"
},
"AD2F1837.HPDesktopSupportUtilities": {
"FriendlyName": "HP Desktop Support Utilities",
"Description": "HP OEM software providing desktop support tools"
},
"AD2F1837.HPEasyClean": {
"FriendlyName": "HP Easy Clean",
"Description": "HP OEM software for system cleaning or optimization"
},
"AD2F1837.HPFileViewer": {
"FriendlyName": "HP File Viewer",
"Description": "HP OEM software for viewing specific file types"
},
"AD2F1837.HPJumpStarts": {
"FriendlyName": "HP JumpStarts",
"Description": "HP OEM software for tutorials, app discovery, or quick access to HP features"
},
"AD2F1837.HPPCHardwareDiagnosticsWindows": {
"FriendlyName": "HP PC Hardware Diagnostics",
"Description": "HP OEM software for PC hardware diagnostics"
},
"AD2F1837.HPPowerManager": {
"FriendlyName": "HP Power Manager",
"Description": "HP OEM software for managing power settings and battery"
},
"AD2F1837.HPPrinterControl": {
"FriendlyName": "HP Printer Control",
"Description": "HP OEM software for managing HP printers"
},
"AD2F1837.HPPrivacySettings": {
"FriendlyName": "HP Privacy Settings",
"Description": "HP OEM software for managing privacy settings"
},
"AD2F1837.HPQuickDrop": {
"FriendlyName": "HP QuickDrop",
"Description": "HP OEM software for quick file transfer between devices"
},
"AD2F1837.HPQuickTouch": {
"FriendlyName": "HP QuickTouch",
"Description": "HP OEM software, possibly for touch-specific shortcuts or controls"
},
"AD2F1837.HPRegistration": {
"FriendlyName": "HP Registration",
"Description": "HP OEM software for product registration"
},
"AD2F1837.HPSupportAssistant": {
"FriendlyName": "HP Support Assistant",
"Description": "HP OEM software for support, updates, and troubleshooting"
},
"AD2F1837.HPSureShieldAI": {
"FriendlyName": "HP Sure Shield AI",
"Description": "HP OEM security software, likely AI-based threat protection"
},
"AD2F1837.HPSystemInformation": {
"FriendlyName": "HP System Information",
"Description": "HP OEM software for displaying system information"
},
"AD2F1837.HPWelcome": {
"FriendlyName": "HP Welcome",
"Description": "HP OEM software providing a welcome experience or initial setup help"
},
"AD2F1837.HPWorkWell": {
"FriendlyName": "HP WorkWell",
"Description": "HP OEM software focused on well-being, possibly with break reminders or ergonomic tips"
},
"AD2F1837.myHP": {
"FriendlyName": "myHP",
"Description": "HP OEM central hub app for device info, support, and services"
},
"E046963F.LenovoCompanion": {
"FriendlyName": "Lenovo Vantage",
"Description": "Lenovo OEM hub app for device settings, updates, and support"
},
"LenovoCompanyLimited.LenovoVantageService": {
"FriendlyName": "Lenovo Vantage Service",
"Description": "Background service component for Lenovo Vantage"
},
"DellInc.DellSupportAssistforPCs": {
"FriendlyName": "Dell SupportAssist",
"Description": "Dell OEM support, diagnostic, and update tool"
},
"DellInc.DellDigitalDelivery": {
"FriendlyName": "Dell Digital Delivery Services",
"Description": "Dell OEM software for delivering pre-purchased software"
},
"DellInc.DellMobileConnect": {
"FriendlyName": "Dell Mobile Connect",
"Description": "Dell OEM app for linking Android/iOS phone to PC (superseded by Phone Link)"
}
}
+51 -3
View File
@@ -70,8 +70,8 @@ function Import-AppDetailsFromJson {
# Use first AppId for fallback names, join all for display # Use first AppId for fallback names, join all for display
$primaryAppId = $appIdArray[0] $primaryAppId = $appIdArray[0]
$appIdDisplay = $appIdArray -join ', ' $appIdDisplay = $appIdArray -join ', '
$friendlyName = if ($appData.FriendlyName) { $appData.FriendlyName } else { $primaryAppId } $friendlyName = Get-Translation -Key $primaryAppId -Field 'FriendlyName' -Section 'Apps'
$displayName = if ($appData.FriendlyName) { "$($appData.FriendlyName) ($appIdDisplay)" } else { $appIdDisplay } $displayName = "$friendlyName ($appIdDisplay)"
$isChecked = if ($InitialCheckedFromJson) { $appData.SelectedByDefault } else { $false } $isChecked = if ($InitialCheckedFromJson) { $appData.SelectedByDefault } else { $false }
$apps += [PSCustomObject]@{ $apps += [PSCustomObject]@{
@@ -80,7 +80,7 @@ function Import-AppDetailsFromJson {
FriendlyName = $friendlyName FriendlyName = $friendlyName
DisplayName = $displayName DisplayName = $displayName
IsChecked = $isChecked IsChecked = $isChecked
Description = $appData.Description Description = Get-Translation -Key $primaryAppId -Field 'Description' -Section 'Apps'
SelectedByDefault = $appData.SelectedByDefault SelectedByDefault = $appData.SelectedByDefault
Recommendation = $appData.Recommendation Recommendation = $appData.Recommendation
RemovalMethod = if ($appData.RemovalMethod -and $appData.RemovalMethod -eq 'WinGet') { 'WinGet' } else { 'Appx' } RemovalMethod = if ($appData.RemovalMethod -and $appData.RemovalMethod -eq 'WinGet') { 'WinGet' } else { 'Appx' }
@@ -89,3 +89,51 @@ function Import-AppDetailsFromJson {
return $apps return $apps
} }
<#
.SYNOPSIS
Runs Import-AppDetailsFromJson in a background runspace via Invoke-NonBlocking.
.DESCRIPTION
The runspace inherits neither dot-sourced functions nor script-scoped variables, so
this dot-sources the app loader, its winget helper, and the language loader inside the
scriptblock, and passes $Lang through to be reassigned to $script:Lang there.
.PARAMETER OnlyInstalled
Filters the results to applications detected through Appx or the supplied
winget installation list.
.PARAMETER InstalledList
A pre-fetched winget installation list used when filtering installed apps.
.PARAMETER InitialCheckedFromJson
Sets each returned app's IsChecked value from its SelectedByDefault setting.
.PARAMETER Lang
The loaded language object ($script:Lang) to make available inside the runspace.
#>
function Invoke-AppDetailsFromJsonAsync {
param (
[string]$LoaderScriptPath = $script:LoadAppsDetailsScriptPath,
[string]$HelperScriptPath = $script:TestAppInWingetListScriptPath,
[string]$LanguageFileScriptPath = $script:ImportLanguageFileScriptPath,
[string]$AppsFilePath = $script:AppsListFilePath,
[object[]]$InstalledList = $null,
[switch]$OnlyInstalled,
[switch]$InitialCheckedFromJson,
[object]$Lang = $script:Lang
)
$onlyInstalledValue = [bool]$OnlyInstalled
$initialCheckedFromJsonValue = [bool]$InitialCheckedFromJson
return Invoke-NonBlocking -ScriptBlock {
param($loaderScript, $helperScript, $languageFileScript, $appsListFilePath, $installedList, $onlyInstalled, $initialCheckedFromJson, $Lang)
$script:AppsListFilePath = $appsListFilePath
$script:Lang = $Lang
. $helperScript
. $languageFileScript
. $loaderScript
Import-AppDetailsFromJson -OnlyInstalled:$onlyInstalled -InstalledList $installedList -InitialCheckedFromJson:$initialCheckedFromJson
} -ArgumentList $LoaderScriptPath, $HelperScriptPath, $LanguageFileScriptPath, $AppsFilePath, $InstalledList, $onlyInstalledValue, $initialCheckedFromJsonValue, $Lang
}
+17 -14
View File
@@ -25,7 +25,7 @@ function Resolve-LanguageFolder {
<# <#
.SYNOPSIS .SYNOPSIS
Loads Chrome/Features/Categories JSON for a language folder, or returns $null on failure. Loads Chrome/Features/Categories/Apps JSON for a language folder, or returns $null on failure.
#> #>
function Import-LanguageContent { function Import-LanguageContent {
param( param(
@@ -38,8 +38,9 @@ function Import-LanguageContent {
$chrome = Import-JsonFile -filePath (Join-Path $folderPath 'Chrome.json') $chrome = Import-JsonFile -filePath (Join-Path $folderPath 'Chrome.json')
$features = Import-JsonFile -filePath (Join-Path $folderPath 'Features.json') $features = Import-JsonFile -filePath (Join-Path $folderPath 'Features.json')
$categories = Import-JsonFile -filePath (Join-Path $folderPath 'Categories.json') $categories = Import-JsonFile -filePath (Join-Path $folderPath 'Categories.json')
$apps = Import-JsonFile -filePath (Join-Path $folderPath 'Apps.json')
if (-not $chrome -or -not $features -or -not $categories) { if (-not $chrome -or -not $features -or -not $categories -or -not $apps) {
return $null return $null
} }
@@ -49,6 +50,7 @@ function Import-LanguageContent {
Features = $features.Features Features = $features.Features
UiGroups = $features.UiGroups UiGroups = $features.UiGroups
Categories = $categories Categories = $categories
Apps = $apps
} }
} }
@@ -128,7 +130,7 @@ function Get-LanguageFallbackChain {
<# <#
.SYNOPSIS .SYNOPSIS
Finds the named section object (Chrome, Features, UiGroups, or Categories) that owns a key. Finds the named section object (Chrome, Features, UiGroups, Categories, or Apps) that owns a key.
.DESCRIPTION .DESCRIPTION
A FeatureId, GroupId, and CategoryId aren't guaranteed to be disjoint (Config/Features.json A FeatureId, GroupId, and CategoryId aren't guaranteed to be disjoint (Config/Features.json
@@ -142,7 +144,7 @@ function Find-TranslationSection {
[object]$Lang, [object]$Lang,
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$Key, [string]$Key,
[ValidateSet('', 'Chrome', 'Features', 'UiGroups', 'Categories')] [ValidateSet('', 'Chrome', 'Features', 'UiGroups', 'Categories', 'Apps')]
[string]$Section = '' [string]$Section = ''
) )
@@ -154,7 +156,7 @@ function Find-TranslationSection {
return $null return $null
} }
foreach ($sectionName in 'Chrome', 'Features', 'UiGroups', 'Categories') { foreach ($sectionName in 'Chrome', 'Features', 'UiGroups', 'Categories', 'Apps') {
$sectionObject = $Lang.$sectionName $sectionObject = $Lang.$sectionName
if ($sectionObject -and $sectionObject.PSObject.Properties[$Key]) { if ($sectionObject -and $sectionObject.PSObject.Properties[$Key]) {
return $sectionObject return $sectionObject
@@ -169,14 +171,15 @@ function Find-TranslationSection {
Looks up a translated value from the active language, falling back to en-US, then to the key itself. Looks up a translated value from the active language, falling back to en-US, then to the key itself.
.DESCRIPTION .DESCRIPTION
Chrome.json keys are flat strings, so -Field is omitted for those. Features/UiGroups/Categories Chrome.json keys are flat strings, so -Field is omitted for those. Features/UiGroups/Categories/Apps
keys (FeatureId/GroupId/CategoryId) resolve to an object, so -Field picks the property on it keys (FeatureId/GroupId/CategoryId/AppId) resolve to an object, so -Field picks the property on it
(Label, ToolTip, ApplyText, UndoLabel, ApplyUndoText). One generic lookup covers every section (Label, ToolTip, ApplyText, UndoLabel, ApplyUndoText for Features/UiGroups/Categories;
instead of a separate function per section. FriendlyName, Description for Apps). One generic lookup covers every section instead of a
separate function per section.
A FeatureId and a GroupId aren't guaranteed to be distinct strings, so pass -Section A FeatureId and a GroupId aren't guaranteed to be distinct strings, so pass -Section
('Features', 'UiGroups', or 'Categories') whenever the caller knows which one it means, ('Features', 'UiGroups', 'Categories', or 'Apps') whenever the caller knows which one it
rather than relying on Find-TranslationSection's search order to guess correctly. means, rather than relying on Find-TranslationSection's search order to guess correctly.
When -Count is supplied, tries the plural-suffixed key ("$Key`_$category") before the bare key, When -Count is supplied, tries the plural-suffixed key ("$Key`_$category") before the bare key,
so callers don't need to add a plural variant for every string, only the ones that need one. so callers don't need to add a plural variant for every string, only the ones that need one.
@@ -191,7 +194,7 @@ function Get-Translation {
[object]$Lang = $script:Lang, [object]$Lang = $script:Lang,
[Nullable[int]]$Count = $null, [Nullable[int]]$Count = $null,
[object[]]$FormatArgs = $null, [object[]]$FormatArgs = $null,
[ValidateSet('', 'Chrome', 'Features', 'UiGroups', 'Categories')] [ValidateSet('', 'Chrome', 'Features', 'UiGroups', 'Categories', 'Apps')]
[string]$Section = '' [string]$Section = ''
) )
@@ -282,7 +285,7 @@ function Get-GroupValueTranslation {
Flattens a loaded language object into a sorted set of dotted key paths. Flattens a loaded language object into a sorted set of dotted key paths.
.DESCRIPTION .DESCRIPTION
Chrome.json keys are already flat and are used as-is. Features/UiGroups/Categories Chrome.json keys are already flat and are used as-is. Features/UiGroups/Categories/Apps
keys are one level deeper (EntryId -> {Field: value}), so each field becomes its own keys are one level deeper (EntryId -> {Field: value}), so each field becomes its own
"Section.EntryId.Field" path (e.g. "Features.DisableTelemetry.Label"). UiGroups' nested "Section.EntryId.Field" path (e.g. "Features.DisableTelemetry.Label"). UiGroups' nested
Values map becomes "UiGroups.GroupId.Values.FeatureId". Used by Test-LanguageKeyCoverage Values map becomes "UiGroups.GroupId.Values.FeatureId". Used by Test-LanguageKeyCoverage
@@ -301,7 +304,7 @@ function Get-LanguageKeyPaths {
$paths.Add($chromeKey) $paths.Add($chromeKey)
} }
foreach ($sectionName in 'Features', 'Categories') { foreach ($sectionName in 'Features', 'Categories', 'Apps') {
$section = $Lang.$sectionName $section = $Lang.$sectionName
if (-not $section) { continue } if (-not $section) { continue }
+2 -14
View File
@@ -391,27 +391,15 @@ function Add-AppsToMainWindow {
$script:MainWindowLastSelectedCheckbox = $null $script:MainWindowLastSelectedCheckbox = $null
$loaderScriptPath = $script:LoadAppsDetailsScriptPath
$helperScriptPath = $script:TestAppInWingetListScriptPath
$appsFilePath = $script:AppsListFilePath
$onlyInstalled = [bool]$OnlyInstalledAppsBox.IsChecked $onlyInstalled = [bool]$OnlyInstalledAppsBox.IsChecked
# Use preloaded data if available; otherwise load in background job # Use preloaded data if available; otherwise load in a background job to keep the UI responsive.
if (-not $onlyInstalled -and $script:PreloadedAppData) { if (-not $onlyInstalled -and $script:PreloadedAppData) {
$rawAppData = $script:PreloadedAppData $rawAppData = $script:PreloadedAppData
$script:PreloadedAppData = $null $script:PreloadedAppData = $null
} }
else { else {
# Load apps details in a background job to keep the UI responsive. $rawAppData = Invoke-AppDetailsFromJsonAsync -OnlyInstalled:$onlyInstalled -InstalledList $ListOfApps
# The helper is dot-sourced inside the job because the runspace
# does not inherit the parent scope's dot-sourced functions.
$rawAppData = Invoke-NonBlocking -ScriptBlock {
param($loaderScript, $helperScript, $appsListFilePath, $installedList, $onlyInstalled)
$script:AppsListFilePath = $appsListFilePath
. $helperScript
. $loaderScript
Import-AppDetailsFromJson -OnlyInstalled:$onlyInstalled -InstalledList $installedList -InitialCheckedFromJson:$false
} -ArgumentList $loaderScriptPath, $helperScriptPath, $appsFilePath, $ListOfApps, $onlyInstalled
} }
$appsToAdd = @($rawAppData | Where-Object { $_ -and ($_.AppId -or $_.FriendlyName) } | Sort-Object -Property FriendlyName) $appsToAdd = @($rawAppData | Where-Object { $_ -and ($_.AppId -or $_.FriendlyName) } | Sort-Object -Property FriendlyName)
+9 -1
View File
@@ -16,13 +16,14 @@ Describe 'Resolve-LanguageFolder' {
} }
Describe 'Import-LanguageFile' { Describe 'Import-LanguageFile' {
It 'loads all three JSON files for the resolved language' { It 'loads all four JSON files for the resolved language' {
$lang = Import-LanguageFile -LanguageCode 'en-US' $lang = Import-LanguageFile -LanguageCode 'en-US'
$lang.LanguageCode | Should -Be 'en-US' $lang.LanguageCode | Should -Be 'en-US'
$lang.Chrome.TitleBarClose | Should -Be 'Close' $lang.Chrome.TitleBarClose | Should -Be 'Close'
$lang.Features.DisableTelemetry.Label | Should -Be 'Disable telemetry' $lang.Features.DisableTelemetry.Label | Should -Be 'Disable telemetry'
$lang.Categories.PrivacySuggestedContent.Label | Should -Be 'Privacy & Suggested Content' $lang.Categories.PrivacySuggestedContent.Label | Should -Be 'Privacy & Suggested Content'
$lang.Apps.'TestApp.One'.FriendlyName | Should -Be 'Test App'
} }
It 'attaches an en-US Fallback when a non-en-US language is loaded' { It 'attaches an en-US Fallback when a non-en-US language is loaded' {
@@ -124,6 +125,11 @@ Describe 'Get-Translation' {
Get-Translation -Key 'PrivacySuggestedContent' -Field 'Label' -Lang $script:EnLang | Should -Be 'Privacy & Suggested Content' Get-Translation -Key 'PrivacySuggestedContent' -Field 'Label' -Lang $script:EnLang | Should -Be 'Privacy & Suggested Content'
} }
It 'looks up a Field on an Apps entry' {
Get-Translation -Key 'TestApp.One' -Field 'FriendlyName' -Section 'Apps' -Lang $script:EnLang | Should -Be 'Test App'
Get-Translation -Key 'TestApp.One' -Field 'Description' -Section 'Apps' -Lang $script:EnLang | Should -Be 'A test app.'
}
It 'looks up a Field on a UiGroups entry' { It 'looks up a Field on a UiGroups entry' {
Get-Translation -Key 'SearchIcon' -Field 'Label' -Lang $script:EnLang | Should -Be 'Taskbar search style' Get-Translation -Key 'SearchIcon' -Field 'Label' -Lang $script:EnLang | Should -Be 'Taskbar search style'
} }
@@ -199,6 +205,8 @@ Describe 'Test-LanguageKeyCoverage' {
$coverage.MissingKeys | Should -Contain 'Features.DisableTelemetry.ToolTip' $coverage.MissingKeys | Should -Contain 'Features.DisableTelemetry.ToolTip'
$coverage.MissingKeys | Should -Contain 'UiGroups.SearchIcon.Label' $coverage.MissingKeys | Should -Contain 'UiGroups.SearchIcon.Label'
$coverage.MissingKeys | Should -Contain 'Categories.PrivacySuggestedContent.Label' $coverage.MissingKeys | Should -Contain 'Categories.PrivacySuggestedContent.Label'
$coverage.MissingKeys | Should -Contain 'Apps.TestApp.One.FriendlyName'
$coverage.MissingKeys | Should -Contain 'Apps.TestApp.One.Description'
$coverage.ExtraKeys | Should -BeNullOrEmpty $coverage.ExtraKeys | Should -BeNullOrEmpty
} }
+26
View File
@@ -1,7 +1,10 @@
BeforeAll { BeforeAll {
Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName PresentationFramework
function Invoke-DoEvents {}
. (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-JsonFile.ps1') . (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-JsonFile.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-LanguageFile.ps1') . (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-LanguageFile.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-AppDetailsFromJson.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\Threading\Invoke-NonBlocking.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\GUI\MainWindow-AppSelection.ps1') . (Join-Path $PSScriptRoot '..\Scripts\GUI\MainWindow-AppSelection.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\GUI\MainWindow-Deployment.ps1') . (Join-Path $PSScriptRoot '..\Scripts\GUI\MainWindow-Deployment.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\GUI\MainWindow-Navigation.ps1') . (Join-Path $PSScriptRoot '..\Scripts\GUI\MainWindow-Navigation.ps1')
@@ -9,6 +12,10 @@ BeforeAll {
. (Join-Path $PSScriptRoot '..\Scripts\GUI\Set-WindowThemeResources.ps1') . (Join-Path $PSScriptRoot '..\Scripts\GUI\Set-WindowThemeResources.ps1')
$script:LanguagesPath = Join-Path $PSScriptRoot '..\Config\Languages' $script:LanguagesPath = Join-Path $PSScriptRoot '..\Config\Languages'
$script:Lang = Import-LanguageFile -LanguageCode 'en-US' $script:Lang = Import-LanguageFile -LanguageCode 'en-US'
$script:LoadAppsDetailsScriptPath = Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-AppDetailsFromJson.ps1'
$script:TestAppInWingetListScriptPath = Join-Path $PSScriptRoot '..\Scripts\AppRemoval\Test-AppInWingetList.ps1'
$script:ImportLanguageFileScriptPath = Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-LanguageFile.ps1'
$script:AppsListFilePath = Join-Path $PSScriptRoot '..\Config\Apps.json'
function New-TestWindow { function New-TestWindow {
$window = New-Object System.Windows.Window $window = New-Object System.Windows.Window
@@ -19,6 +26,25 @@ BeforeAll {
} }
} }
Describe 'Invoke-AppDetailsFromJsonAsync' {
BeforeEach { $script:GuiWindow = [PSCustomObject]@{} }
It 'resolves translated FriendlyName and Description inside the background runspace' {
$result = @(Invoke-AppDetailsFromJsonAsync -OnlyInstalled:$false)
$result.Count | Should -Be 141
$clipchamp = $result | Where-Object { $_.AppId -contains 'Clipchamp.Clipchamp' }
$clipchamp.FriendlyName | Should -Be 'Clipchamp'
$clipchamp.Description | Should -Be 'Video editor from Microsoft'
}
It 'does not return null entries when OnlyInstalled filters the runspace results' {
$result = @(Invoke-AppDetailsFromJsonAsync -OnlyInstalled:$true -InstalledList @())
@($result | Where-Object { -not $_ }).Count | Should -Be 0
}
}
Describe 'ConvertTo-NormalizedCheckboxState' { Describe 'ConvertTo-NormalizedCheckboxState' {
It 'normalizes indeterminate tri-state checkboxes to checked' { It 'normalizes indeterminate tri-state checkboxes to checked' {
$checkBox = New-Object System.Windows.Controls.CheckBox $checkBox = New-Object System.Windows.Controls.CheckBox
+17 -1
View File
@@ -3,6 +3,7 @@ BeforeAll {
function Test-AppInWingetList { param($appId, $InstalledList) $false } function Test-AppInWingetList { param($appId, $InstalledList) $false }
. (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-JsonFile.ps1') . (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-JsonFile.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-LanguageFile.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\Helpers\Add-Parameter.ps1') . (Join-Path $PSScriptRoot '..\Scripts\Helpers\Add-Parameter.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\FileIO\Save-ToFile.ps1') . (Join-Path $PSScriptRoot '..\Scripts\FileIO\Save-ToFile.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-Settings.ps1') . (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-Settings.ps1')
@@ -12,6 +13,8 @@ BeforeAll {
. (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-AppPresetsFromJson.ps1') . (Join-Path $PSScriptRoot '..\Scripts\FileIO\Import-AppPresetsFromJson.ps1')
. (Join-Path $PSScriptRoot '..\Scripts\FileIO\Get-ValidatedAppList.ps1') . (Join-Path $PSScriptRoot '..\Scripts\FileIO\Get-ValidatedAppList.ps1')
$script:JsonFixturePath = Join-Path $PSScriptRoot 'TestData\JsonFileLoading' $script:JsonFixturePath = Join-Path $PSScriptRoot 'TestData\JsonFileLoading'
$script:LanguagesPath = Join-Path $PSScriptRoot '..\Config\Languages'
$script:Lang = Import-LanguageFile -LanguageCode 'en-US'
} }
Describe 'Save-ToFile' { Describe 'Save-ToFile' {
@@ -150,7 +153,8 @@ Describe 'Import-AppDetailsFromJson' {
$apps = @(Import-AppDetailsFromJson -InitialCheckedFromJson) $apps = @(Import-AppDetailsFromJson -InitialCheckedFromJson)
$apps | Should -HaveCount 2 $apps | Should -HaveCount 2
$apps[0].DisplayName | Should -Be 'One (One.App, Alias.App)' # 'One.App' has no en-US Apps.json entry, so Get-Translation falls back to the raw key.
$apps[0].DisplayName | Should -Be 'One.App (One.App, Alias.App)'
$apps[0].IsChecked | Should -BeTrue $apps[0].IsChecked | Should -BeTrue
$apps[0].RemovalMethod | Should -Be 'WinGet' $apps[0].RemovalMethod | Should -Be 'WinGet'
$apps[1].FriendlyName | Should -Be 'Two.App' $apps[1].FriendlyName | Should -Be 'Two.App'
@@ -159,6 +163,18 @@ Describe 'Import-AppDetailsFromJson' {
@($apps[1].AppId) | Should -HaveCount 1 @($apps[1].AppId) | Should -HaveCount 1
} }
It 'resolves FriendlyName and Description from Config/Languages/en-US/Apps.json for a real AppId' {
'{"Apps":[{"AppId":"Clipchamp.Clipchamp","FriendlyName":"stale JSON name, should not appear","Description":"stale JSON description, should not appear"}]}' |
Set-Content -LiteralPath $script:AppsListFilePath -Encoding UTF8
$apps = @(Import-AppDetailsFromJson)
$apps | Should -HaveCount 1
$apps[0].FriendlyName | Should -Be 'Clipchamp'
$apps[0].Description | Should -Be 'Video editor from Microsoft'
$apps[0].DisplayName | Should -Be 'Clipchamp (Clipchamp.Clipchamp)'
}
It 'skips missing, blank, and non-string app IDs without failing the complete catalog' { It 'skips missing, blank, and non-string app IDs without failing the complete catalog' {
'{"Apps":[{"AppId":null},{"FriendlyName":"Missing"},{"AppId":42},{"AppId":[" Valid.App ",null,{},""]}]}' | '{"Apps":[{"AppId":null},{"FriendlyName":"Missing"},{"AppId":42},{"AppId":[" Valid.App ",null,{},""]}]}' |
Set-Content -LiteralPath $script:AppsListFilePath -Encoding UTF8 Set-Content -LiteralPath $script:AppsListFilePath -Encoding UTF8
@@ -0,0 +1,6 @@
{
"TestApp.One": {
"FriendlyName": "Test App",
"Description": "A test app."
}
}
@@ -0,0 +1 @@
{}
+1
View File
@@ -200,6 +200,7 @@ $script:ImportExportConfigSchema = Join-Path $schemasPath 'ImportExportConfigWin
$script:RestoreBackupWindowSchema = Join-Path $schemasPath 'RestoreBackupWindow.xaml' $script:RestoreBackupWindowSchema = Join-Path $schemasPath 'RestoreBackupWindow.xaml'
$script:LoadAppsDetailsScriptPath = Join-Path (Join-Path $scriptsPath 'FileIO') 'Import-AppDetailsFromJson.ps1' $script:LoadAppsDetailsScriptPath = Join-Path (Join-Path $scriptsPath 'FileIO') 'Import-AppDetailsFromJson.ps1'
$script:TestAppInWingetListScriptPath = Join-Path (Join-Path $scriptsPath 'AppRemoval') 'Test-AppInWingetList.ps1' $script:TestAppInWingetListScriptPath = Join-Path (Join-Path $scriptsPath 'AppRemoval') 'Test-AppInWingetList.ps1'
$script:ImportLanguageFileScriptPath = Join-Path (Join-Path $scriptsPath 'FileIO') 'Import-LanguageFile.ps1'
$script:ControlParams = 'WhatIf', 'Confirm', 'Verbose', 'Debug', 'LogPath', 'Language', 'Silent', 'Sysprep', 'User', 'SkipExplorerRestart', 'SkipRegistryBackup', 'RunDefaults', 'RunDefaultsLite', 'RunSavedSettings', 'Config', 'CLI', 'AppRemovalTarget' $script:ControlParams = 'WhatIf', 'Confirm', 'Verbose', 'Debug', 'LogPath', 'Language', 'Silent', 'Sysprep', 'User', 'SkipExplorerRestart', 'SkipRegistryBackup', 'RunDefaults', 'RunDefaultsLite', 'RunSavedSettings', 'Config', 'CLI', 'AppRemovalTarget'