Skip to content

Give FSharpPackage its own VSPackage resource set - #20408

Open
xperiandri wants to merge 3 commits into
dotnet:mainfrom
xperiandri:pr/fsharp-editor-vspackage-resx
Open

xperiandri wants to merge 3 commits into
dotnet:mainfrom
xperiandri:pr/fsharp-editor-vspackage-resx

Conversation

@xperiandri

@xperiandri xperiandri commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

FSharpPackage's registration attributes reference numeric resource IDs resolved through ResourceManager("VSPackage", ...), but FSharp.Editor's assembly has never embedded a VSPackage.resources — so the shell throws MissingManifestResourceException when it looks up the language name, the editor factory's display name, the Tools → Options page names, and the F# Interactive key binding table entry. Added vsintegration/src/FSharp.Editor/VSPackage.resx, embedded the same way FSharp.ProjectSystem.FSharp.fsproj already backs its own VSPackage.resx; every value matches the literal fallback string already passed alongside its resource ID, so no displayed text changes and these strings can now be localized through the normal xlf pipeline.

FSharp.Editor.resx already carries a dead, unreferenced copy of the page-name strings (60086012, 6014) — left untouched here rather than churning 13 more .xlf files for a no-op removal.

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`vsintegration/src` docs/release-notes/.VisualStudio/18.vNext.md

@github-actions github-actions Bot added the ⚠️ Affects-Build-Infra Tooling check: PR touches build infrastructure label Aug 31, 2026
@github-actions

This comment has been minimized.

@xperiandri
xperiandri force-pushed the pr/fsharp-editor-vspackage-resx branch from 0216897 to f0edf06 Compare September 2, 2026 10:21

@T-Gro T-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Really nice piece of work — the diagnosis here is unusually precise and the fix is the minimal, correct one. A few things I verified and loved, plus two small notes.

What I checked

  • Mechanism is sound. FSharpPackage derives from AbstractPackage<_,_> → MPF Package, whose shell-side string lookups go through SVsResourceManager with the well-known VSPackage basename. Wiring the resx with ManifestResourceName=VSPackage embeds it as VSPackage.resources, exactly matching FSharp.ProjectSystem.FSharp.fsproj. So ResourceManager("VSPackage", FSharp.Editor) will now resolve.
  • Every referenced ID is covered. 100 (ProvideLanguageService.languageResourceID), 101s (ProvideEditorFactory), 6000s/6001s (ProvideOptionPage + ProvideKeyBindingTable), and 6008–6012/6014 (the six ProvideLanguageEditorOptionPage page-name IDs). I grepped the whole attribute block — no other numeric ID is left unbacked (ProvideToolWindow carries no resource ID here).
  • The asymmetry actually confirms the diagnosis. The last arg of each ProvideLanguageEditorOptionPage (IntelliSensePageKeywords, …) already lives in FSharp.Editor.resx and has worked for years — because those are resolved by the option-page automation via the assembly's default resource set, whereas the numeric page-name IDs are resolved by the shell via the VSPackage convention. That's precisely why the numeric IDs threw while the keyword strings didn't. Great catch.
  • Loc pipeline lines up. 13 xlf languages, matching the existing FSharp.Editor.resx set; each has all 10 trans-units with original="../VSPackage.resx"; state="new" is normal for fresh strings. No GenerateSource on the resx is correct (numeric names aren't valid identifiers).

Two small notes (non-blocking)

  1. The PR body says "every value is copied verbatim from the literal fallback string already passed alongside its resource ID." That's true for 6000/6001/6008–6014, but 100 (F#) and 101 (F# Source File) have no literal fallbackProvideLanguageService/ProvideEditorFactory only take the numeric ID. F# tracks strLanguageName, but F# Source File is a genuinely new string. It's a reasonable editor-factory display name; just worth confirming that's the wording you want, since it's the one value not anchored to anything pre-existing.
  2. Consider adding one line to the resx header comment explaining the split — i.e. why the page-name IDs belong in VSPackage.resx while the sibling *PageKeywords stay in FSharp.Editor.resx. The next person to touch this will absolutely wonder, and you've already worked out the answer.

Nice, surgical change with a real experimental-hive repro behind it. LGTM once you're happy with the 101 wording.

@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label Sep 2, 2026
@T-Gro
T-Gro self-requested a review September 2, 2026 14:36
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Sep 3, 2026
Address review feedback on dotnet#20408.

Every one of the ten strings already exists, translated, elsewhere in
vsintegration, so borrow those targets instead of shipping the new
VSPackage.resx English-only: "F# Source File" and "F# Tools" from
FSharp.ProjectSystem.FSharp's VSPackage.resx, the six option-page names
from FSharp.UIResources' Strings.resx, "F# Interactive" from the FSI
command table.

Also record in the resx header why the page-name IDs live here while the
sibling *PageKeywords stay in FSharp.Editor.resx: the shell resolves the
former through SVsResourceManager against the fixed "VSPackage" basename,
the option-page automation resolves the latter itself out of the
assembly's default resource set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@xperiandri

Copy link
Copy Markdown
Contributor Author

Thanks for the careful read — both notes addressed in 694e0d2.

1. The 101 wording

You're right that ProvideEditorFactory carries no literal fallback, but F# Source File turns out not to be new either — it's the verbatim string the legacy project system has shipped for years as the "Add New Item" name for a blank .fs:

vsintegration/src/FSharp.ProjectSystem.FSharp/VSPackage.resx:5011  ->  F# Source File

and it's already translated into all 13 languages there (Исходный файл F#, F#-Quelldatei, F# ソース ファイル, …). So the editor-factory display name and the item-template name now agree, which is what a user would expect to see. I've noted the anchor in the resx comment for 101.

That check turned up something better, though: every one of the ten strings already exists translated somewhere in vsintegration, so shipping them state="new" would have meant an English-only Tools → Options tree in a localized VS until the next loc pass. The commit seeds all 130 targets from those existing units:

IDs Borrowed from
101, 6000 FSharp.ProjectSystem.FSharp/xlf/VSPackage.*.xlf
6001 FSharp.VS.FSI/xlf/MenusAndCommands.vsct.*.xlf
60086012, 6014 FSharp.UIResources/xlf/Strings.*.xlf

Only one source string had two candidate translations — zh-Hans Formatting, which is 格式设置 in FSharp.UIResources (the options-page label) and 正在格式化 in FSharp.Editor.resx's dead 6014 copy. The latter is the progress verb "formatting…", wrong for a page name, so 格式设置 won.

Verified in the built satellite rather than by eye:

> $asm = [Reflection.Assembly]::LoadFrom('artifacts/bin/FSharp.Editor/Debug/net472/ru/FSharp.Editor.resources.dll')
> $asm.GetManifestResourceNames()
VSPackage.ru.resources
LegacyResolver.ru.resources
FSharp.Editor.ru.resources

> # VSPackage.ru.resources
100  = F#
101  = Исходный файл F#
6000 = Инструменты F#
6001 = F# Interactive
6008 = IntelliSense
6009 = QuickInfo
6010 = Исправления кода
6011 = Производительность
6012 = Дополнительный
6014 = Форматирование

2. The resx header comment

Added, phrased as the rule for the next person rather than as history:

<!--
  Only the strings the Visual Studio shell resolves on FSharpPackage's behalf belong here.
  The shell goes through SVsResourceManager, which looks up the numeric ID against the fixed
  basename "VSPackage" in the package assembly, so this file must embed as VSPackage.resources
  (see ManifestResourceName in FSharp.Editor.fsproj). Strings the managed option-page code looks
  up itself - the *PageKeywords named as the last argument of each ProvideLanguageEditorOptionPage
  - come from the assembly's default resource set and stay in FSharp.Editor.resx.
-->

One thing I found but deliberately did not touch

FSharp.Editor.resx already contains 60086012 and 6014 with the same values — someone added the page names to the default resource set, which is precisely where the shell never looks, and they've been dead ever since (the MissingManifestResourceException in the PR description is the proof: had anything resolved them, it wouldn't have thrown). Nothing but the ProvideLanguageEditorOptionPage attributes references those IDs, and those go through VSPackage.

Deleting them is correct but churns 13 more FSharp.Editor.*.xlf files for zero functional change, so I left it out of this PR rather than widening the diff. Happy to do it here or in a follow-up — your call.

🤖 Generated with Claude Code

xperiandri added a commit to xperiandri/fsharp that referenced this pull request Sep 3, 2026
Address review feedback on dotnet#20408.

Every one of the ten strings already exists, translated, elsewhere in
vsintegration, so borrow those targets instead of shipping the new
VSPackage.resx English-only: "F# Source File" and "F# Tools" from
FSharp.ProjectSystem.FSharp's VSPackage.resx, the six option-page names
from FSharp.UIResources' Strings.resx, "F# Interactive" from the FSI
command table.

Also record in the resx header why the page-name IDs live here while the
sibling *PageKeywords stay in FSharp.Editor.resx: the shell resolves the
former through SVsResourceManager against the fixed "VSPackage" basename,
the option-page automation resolves the latter itself out of the
assembly's default resource set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the pr/fsharp-editor-vspackage-resx branch from 694e0d2 to 387eb55 Compare September 3, 2026 16:50
T-Gro
T-Gro approved these changes Sep 4, 2026
@github-project-automation github-project-automation Bot moved this from New to In Progress in F# Compiler and Tooling Sep 4, 2026
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Sep 11, 2026
Address review feedback on dotnet#20408.

Every one of the ten strings already exists, translated, elsewhere in
vsintegration, so borrow those targets instead of shipping the new
VSPackage.resx English-only: "F# Source File" and "F# Tools" from
FSharp.ProjectSystem.FSharp's VSPackage.resx, the six option-page names
from FSharp.UIResources' Strings.resx, "F# Interactive" from the FSI
command table.

Also record in the resx header why the page-name IDs live here while the
sibling *PageKeywords stay in FSharp.Editor.resx: the shell resolves the
former through SVsResourceManager against the fixed "VSPackage" basename,
the option-page automation resolves the latter itself out of the
assembly's default resource set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the pr/fsharp-editor-vspackage-resx branch from 387eb55 to 453cbca Compare September 11, 2026 16:21
@github-actions github-actions Bot added the ⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager label Sep 11, 2026
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Build-Infra, Affects-Design-Time
Affects-Build-Infra: Changes the production editor project resource configuration.
Affects-Design-Time: Changes resources loaded by Visual Studio package.

Generated by PR Tooling Safety Check · gpt56 3.1M ·

@T-Gro T-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖🕵️ Please shorten the description using this guidance. Focus on the problem and why the change is needed, in simplified technical English. Leave the implementation inventory to the Files tab and retain necessary caveats.

@xperiandri

Copy link
Copy Markdown
Contributor Author

Moved out of the PR description per the pr-description guidance — implementation inventory and test plan for reference:

IDs covered by the new resx

Attribute IDs
ProvideLanguageService(..., languageResourceID = 100, ...) 100
ProvideEditorFactory(typeof<FSharpEditorFactory>, 101s, ...) 101
ProvideOptionPage(typeof<FsiPropertyPage>, "F# Tools", "F# Interactive", 6000s, 6001s, true) 6000, 6001
ProvideKeyBindingTable("{dee22b65-...}", 6001s) 6001
ProvideLanguageEditorOptionPage(...) ×6 6008–6012, 6014

The values these IDs carry live in other assemblies today — 6000 (F# Tools) and 6001 (F# Interactive) in FSharp.ProjectSystem.FSharp/VSPackage.resx, 101 in FSharp.VS.FSI/VSPackage.resx — where the shell never looks for them on FSharpPackage's behalf.

Test plan

  • Rebuilt FSharp.Editor.fsproj (0 errors); the assembly manifest now lists VSPackage.resources alongside FSharp.Editor.resources and LegacyResolver.resources.
  • Deployed into a RoslynDev experimental hive and opened Tools → Options; FSharp.VS.FSI.dll (hosting FsiPropertyPage) and FSharp.UIResources.dll (hosting the OptionsUI.*OptionPage controls) both loaded and the pages rendered with no MissingManifestResourceException in the debug output.
  • CI

@vzarytovskii

Copy link
Copy Markdown
Member

@T-Gro You should keep an eye on the Perf DDIRTs and OptProf tests after you insert (or during insertion), due to how VS loads resources, this can cause regressions in other extensions/packages. We've hit this before so many times.

xperiandri and others added 3 commits September 15, 2026 12:57
FSharpPackage's registration attributes in LanguageService.fs (ProvideOptionPage,
ProvideKeyBindingTable, ProvideEditorFactory, ProvideLanguageService, and six
ProvideLanguageEditorOptionPage entries) reference numeric resource IDs — 100, 101,
6000, 6001, 6008-6012, 6014 — that the shell resolves via a ResourceManager("VSPackage",
...) against FSharp.Editor's own assembly. No such resource set was ever embedded there,
so any lookup throws MissingManifestResourceException.

Add VSPackage.resx with those IDs, wired in with ManifestResourceName=VSPackage the same
way FSharp.ProjectSystem.FSharp.fsproj already backs its own VSPackage.resx. Every value
is copied verbatim from the literal fallback string already passed alongside its resource
ID in the attribute that references it.

Verified the rebuilt FSharp.Editor.dll's manifest now lists VSPackage.resources, which is
what the exception reported missing.
Address review feedback on dotnet#20408.

Every one of the ten strings already exists, translated, elsewhere in
vsintegration, so borrow those targets instead of shipping the new
VSPackage.resx English-only: "F# Source File" and "F# Tools" from
FSharp.ProjectSystem.FSharp's VSPackage.resx, the six option-page names
from FSharp.UIResources' Strings.resx, "F# Interactive" from the FSI
command table.

Also record in the resx header why the page-name IDs live here while the
sibling *PageKeywords stay in FSharp.Editor.resx: the shell resolves the
former through SVsResourceManager against the fixed "VSPackage" basename,
the option-page automation resolves the latter itself out of the
assembly's default resource set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the pr/fsharp-editor-vspackage-resx branch from 453cbca to ffa1b3f Compare September 15, 2026 10:57
@xperiandri
xperiandri requested a review from T-Gro September 15, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ Affects-Build-Infra Tooling check: PR touches build infrastructure ⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager AI-reviewed PR reviewed by AI review council

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants