From 315a42375011a9465ee04f422026c6de5470ae22 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 11 Dec 2025 17:18:33 +0000
Subject: [PATCH 1/8] Initial plan
From 82501b8ff986abcd5b04f7055729d50f7a408822 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 11 Dec 2025 17:49:00 +0000
Subject: [PATCH 2/8] Implement preferreduilang switch filtering in FSI script
args
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
---
src/Compiler/Interactive/fsi.fs | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs
index ca96324426..744d067c0f 100644
--- a/src/Compiler/Interactive/fsi.fs
+++ b/src/Compiler/Interactive/fsi.fs
@@ -1068,8 +1068,29 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s
(fun args ->
let scriptFile = args[0]
let scriptArgs = List.tail args
+
+ // Filter out and process preferreduilang from script args
+ let rec filterScriptArgs (args: string list) =
+ match args with
+ | [] -> []
+ | (arg: string) :: rest when
+ arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase) ||
+ arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase) ->
+ // Extract culture and set it
+ let culture = arg.Substring(arg.IndexOf(':') + 1)
+ try
+ tcConfigB.preferredUiLang <- Some culture
+ Thread.CurrentThread.CurrentUICulture <- CultureInfo(culture)
+ with _ ->
+ // Ignore invalid culture, just don't set it
+ ()
+ filterScriptArgs rest
+ | arg :: rest -> arg :: filterScriptArgs rest
+
+ let filteredScriptArgs = filterScriptArgs scriptArgs
+
inputFilesAcc <- inputFilesAcc @ [ (scriptFile, true) ] (* record script.fsx for evaluation *)
- List.iter recordExplicitArg scriptArgs (* record rest of line as explicit arguments *)
+ List.iter recordExplicitArg filteredScriptArgs (* record rest of line as explicit arguments *)
tcConfigB.noFeedback <- true (* "quiet", no banners responses etc *)
interact <- false (* --exec, exit after eval *)
[] (* no arguments passed on, all consumed here *)
From b1d162381b7a1c3cd63cc8dd2b1d6492724072d1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 11 Dec 2025 18:00:59 +0000
Subject: [PATCH 3/8] Add regression tests for preferreduilang switch in FSI
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
---
.../FSharp.Compiler.ComponentTests.fsproj | 1 +
.../Scripting/PreferredUiLangTests.fs | 147 ++++++++++++++++++
2 files changed, 148 insertions(+)
create mode 100644 tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs
diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
index 8f5ace7aad..5dac9eb8fa 100644
--- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
+++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
@@ -277,6 +277,7 @@
+
diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs
new file mode 100644
index 0000000000..63fbfb3df7
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs
@@ -0,0 +1,147 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+namespace Scripting
+
+open Xunit
+open System
+open System.IO
+open FSharp.Test
+open FSharp.Test.CompilerAssertHelpers
+open FSharp.Compiler.Interactive.Shell
+open FSharp.Compiler.Diagnostics
+
+module ``PreferredUiLang tests`` =
+
+ []
+ let ``preferreduilang switch before script is consumed from CommandLineArgs``() =
+ let scriptContent = """
+printfn "Args: %A" fsi.CommandLineArgs
+if fsi.CommandLineArgs.Length <> 2 then exit 1
+if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1
+if fsi.CommandLineArgs.[1] <> "arg1" then exit 1
+exit 0
+"""
+ let tmpFile = Path.GetTempFileName() + ".fsx"
+ try
+ File.WriteAllText(tmpFile, scriptContent)
+ let errors, _, _ =
+ CompilerAssert.RunScriptWithOptionsAndReturnResult
+ [| "--preferreduilang:es-ES"; tmpFile; "arg1" |]
+ ""
+
+ // Should succeed (exit 0)
+ Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors)
+ finally
+ if File.Exists(tmpFile) then File.Delete(tmpFile)
+
+ []
+ let ``preferreduilang switch after script is consumed from CommandLineArgs``() =
+ let scriptContent = """
+printfn "Args: %A" fsi.CommandLineArgs
+if fsi.CommandLineArgs.Length <> 2 then exit 1
+if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1
+if fsi.CommandLineArgs.[1] <> "arg1" then exit 1
+exit 0
+"""
+ let tmpFile = Path.GetTempFileName() + ".fsx"
+ try
+ File.WriteAllText(tmpFile, scriptContent)
+ let errors, _, _ =
+ CompilerAssert.RunScriptWithOptionsAndReturnResult
+ [| tmpFile; "--preferreduilang:es-ES"; "arg1" |]
+ ""
+
+ // Should succeed (exit 0)
+ Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors)
+ finally
+ if File.Exists(tmpFile) then File.Delete(tmpFile)
+
+ []
+ let ``preferreduilang with slash form is consumed from CommandLineArgs``() =
+ let scriptContent = """
+printfn "Args: %A" fsi.CommandLineArgs
+if fsi.CommandLineArgs.Length <> 2 then exit 1
+if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1
+if fsi.CommandLineArgs.[1] <> "arg1" then exit 1
+exit 0
+"""
+ let tmpFile = Path.GetTempFileName() + ".fsx"
+ try
+ File.WriteAllText(tmpFile, scriptContent)
+ let errors, _, _ =
+ CompilerAssert.RunScriptWithOptionsAndReturnResult
+ [| tmpFile; "/preferreduilang:de-DE"; "arg1" |]
+ ""
+
+ // Should succeed (exit 0)
+ Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors)
+ finally
+ if File.Exists(tmpFile) then File.Delete(tmpFile)
+
+ []
+ let ``preferreduilang sets CurrentUICulture correctly``() =
+ let scriptContent = """
+let culture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name
+printfn "Culture: %s" culture
+if not (culture.StartsWith("fr-FR")) then
+ printfn "Expected culture starting with fr-FR, got: %s" culture
+ exit 1
+exit 0
+"""
+ let tmpFile = Path.GetTempFileName() + ".fsx"
+ try
+ File.WriteAllText(tmpFile, scriptContent)
+ let errors, _, _ =
+ CompilerAssert.RunScriptWithOptionsAndReturnResult
+ [| "--preferreduilang:fr-FR"; tmpFile |]
+ ""
+
+ // Should succeed (exit 0)
+ Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors)
+ finally
+ if File.Exists(tmpFile) then File.Delete(tmpFile)
+
+ []
+ let ``preferreduilang after script also sets culture``() =
+ let scriptContent = """
+let culture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name
+printfn "Culture: %s" culture
+if not (culture.StartsWith("ja-JP")) then
+ printfn "Expected culture starting with ja-JP, got: %s" culture
+ exit 1
+exit 0
+"""
+ let tmpFile = Path.GetTempFileName() + ".fsx"
+ try
+ File.WriteAllText(tmpFile, scriptContent)
+ let errors, _, _ =
+ CompilerAssert.RunScriptWithOptionsAndReturnResult
+ [| tmpFile; "--preferreduilang:ja-JP" |]
+ ""
+
+ // Should succeed (exit 0)
+ Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors)
+ finally
+ if File.Exists(tmpFile) then File.Delete(tmpFile)
+
+ []
+ let ``invalid culture in preferreduilang is ignored gracefully``() =
+ let scriptContent = """
+printfn "Args: %A" fsi.CommandLineArgs
+if fsi.CommandLineArgs.Length <> 2 then exit 1
+if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1
+if fsi.CommandLineArgs.[1] <> "arg1" then exit 1
+exit 0
+"""
+ let tmpFile = Path.GetTempFileName() + ".fsx"
+ try
+ File.WriteAllText(tmpFile, scriptContent)
+ let errors, _, _ =
+ CompilerAssert.RunScriptWithOptionsAndReturnResult
+ [| tmpFile; "--preferreduilang:invalid-culture-xyz"; "arg1" |]
+ ""
+
+ // Should succeed - invalid culture is ignored, but switch is still consumed
+ Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors)
+ finally
+ if File.Exists(tmpFile) then File.Delete(tmpFile)
From 47d5e895c3afa7e095de2975b3d993de4a2f5e59 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 11 Dec 2025 18:07:20 +0000
Subject: [PATCH 4/8] Format code with fantomas
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
---
src/Compiler/Interactive/fsi.fs | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs
index 744d067c0f..b193e7bca4 100644
--- a/src/Compiler/Interactive/fsi.fs
+++ b/src/Compiler/Interactive/fsi.fs
@@ -1068,27 +1068,30 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s
(fun args ->
let scriptFile = args[0]
let scriptArgs = List.tail args
-
+
// Filter out and process preferreduilang from script args
let rec filterScriptArgs (args: string list) =
match args with
| [] -> []
- | (arg: string) :: rest when
- arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase) ||
- arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase) ->
+ | (arg: string) :: rest when
+ arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase)
+ || arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase)
+ ->
// Extract culture and set it
let culture = arg.Substring(arg.IndexOf(':') + 1)
+
try
tcConfigB.preferredUiLang <- Some culture
Thread.CurrentThread.CurrentUICulture <- CultureInfo(culture)
with _ ->
// Ignore invalid culture, just don't set it
()
+
filterScriptArgs rest
| arg :: rest -> arg :: filterScriptArgs rest
-
+
let filteredScriptArgs = filterScriptArgs scriptArgs
-
+
inputFilesAcc <- inputFilesAcc @ [ (scriptFile, true) ] (* record script.fsx for evaluation *)
List.iter recordExplicitArg filteredScriptArgs (* record rest of line as explicit arguments *)
tcConfigB.noFeedback <- true (* "quiet", no banners responses etc *)
From f40ffbb3f5e28f6ae2d1cd862b91faf20a69c6a7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 11 Dec 2025 18:13:57 +0000
Subject: [PATCH 5/8] Fix preferreduilang switch leaking into
fsi.CommandLineArgs
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
---
src/Compiler/Interactive/fsi.fs | 30 ++++++++++++++++++------------
1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs
index b193e7bca4..9e8551293a 100644
--- a/src/Compiler/Interactive/fsi.fs
+++ b/src/Compiler/Interactive/fsi.fs
@@ -1070,22 +1070,28 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s
let scriptArgs = List.tail args
// Filter out and process preferreduilang from script args
+ let isPreferredUiLangArg (arg: string) =
+ arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase)
+ || arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase)
+
let rec filterScriptArgs (args: string list) =
match args with
| [] -> []
- | (arg: string) :: rest when
- arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase)
- || arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase)
- ->
+ | (arg: string) :: rest when isPreferredUiLangArg arg ->
// Extract culture and set it
- let culture = arg.Substring(arg.IndexOf(':') + 1)
-
- try
- tcConfigB.preferredUiLang <- Some culture
- Thread.CurrentThread.CurrentUICulture <- CultureInfo(culture)
- with _ ->
- // Ignore invalid culture, just don't set it
- ()
+ let colonIndex = arg.IndexOf(':')
+
+ if colonIndex >= 0 && colonIndex < arg.Length - 1 then
+ let culture = arg.Substring(colonIndex + 1)
+
+ try
+ tcConfigB.preferredUiLang <- Some culture
+ Thread.CurrentThread.CurrentUICulture <- CultureInfo(culture)
+ with
+ | :? CultureNotFoundException
+ | :? ArgumentException ->
+ // Ignore invalid culture, just don't set it
+ ()
filterScriptArgs rest
| arg :: rest -> arg :: filterScriptArgs rest
From 328b7b35a7f38932971dd8987fdeb7489a129afa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Dec 2025 08:36:52 +0000
Subject: [PATCH 6/8] Fix invalid culture exception by validating before
setting tcConfigB
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
---
src/Compiler/Interactive/fsi.fs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs
index 9e8551293a..78f9e572d1 100644
--- a/src/Compiler/Interactive/fsi.fs
+++ b/src/Compiler/Interactive/fsi.fs
@@ -1085,8 +1085,11 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s
let culture = arg.Substring(colonIndex + 1)
try
+ // Validate culture first by creating CultureInfo
+ let cultureInfo = CultureInfo(culture)
+ // Only set if valid
tcConfigB.preferredUiLang <- Some culture
- Thread.CurrentThread.CurrentUICulture <- CultureInfo(culture)
+ Thread.CurrentThread.CurrentUICulture <- cultureInfo
with
| :? CultureNotFoundException
| :? ArgumentException ->
From 6e922805afdcb4b747bb3507add7d5de270a844a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 17 Dec 2025 17:08:31 +0000
Subject: [PATCH 7/8] Add release notes for preferreduilang fix
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
---
docs/release-notes/.FSharp.Compiler.Service/10.0.200.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md
index b647952d09..ccb62137c0 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md
@@ -1,6 +1,7 @@
### Fixed
* Type relations cache: optimize key generation ([Issue #19116](https://github.com/dotnet/fsharp/issues/18767)) ([PR #19120](https://github.com/dotnet/fsharp/pull/19120))
+* Fix `--preferreduilang` switch leaking into `fsi.CommandLineArgs` when positioned after script file ([PR #19171](https://github.com/dotnet/fsharp/pull/19171))
### Added
From 8c3ed3fe4ec250520b734981223e5253ebdd9098 Mon Sep 17 00:00:00 2001
From: Tomas Grosup
Date: Thu, 18 Dec 2025 15:05:59 +0100
Subject: [PATCH 8/8] Update
docs/release-notes/.FSharp.Compiler.Service/10.0.200.md
---
docs/release-notes/.FSharp.Compiler.Service/10.0.200.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md
index ccb62137c0..a5b9fe2ed7 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md
@@ -1,7 +1,7 @@
### Fixed
* Type relations cache: optimize key generation ([Issue #19116](https://github.com/dotnet/fsharp/issues/18767)) ([PR #19120](https://github.com/dotnet/fsharp/pull/19120))
-* Fix `--preferreduilang` switch leaking into `fsi.CommandLineArgs` when positioned after script file ([PR #19171](https://github.com/dotnet/fsharp/pull/19171))
+* Fix `--preferreduilang` switch leaking into `fsi.CommandLineArgs` when positioned after script file ([PR #19151](https://github.com/dotnet/fsharp/pull/19151))
### Added