diff --git a/System.CommandLine.sln b/System.CommandLine.sln
index bb83d4f820..ed35369bb2 100644
--- a/System.CommandLine.sln
+++ b/System.CommandLine.sln
@@ -54,9 +54,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Benchmar
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Hosting", "src\System.CommandLine.Hosting\System.CommandLine.Hosting.csproj", "{644C4B4A-4A32-4307-9F71-C3BF901FFB66}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.CommandLine.Hosting.Tests", "src\System.CommandLine.Hosting.Tests\System.CommandLine.Hosting.Tests.csproj", "{39483140-BC26-4CAD-BBAE-3DC76C2F16CF}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Hosting.Tests", "src\System.CommandLine.Hosting.Tests\System.CommandLine.Hosting.Tests.csproj", "{39483140-BC26-4CAD-BBAE-3DC76C2F16CF}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostingPlayground", "samples\HostingPlayground\HostingPlayground.csproj", "{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostingPlayground", "samples\HostingPlayground\HostingPlayground.csproj", "{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Localization", "src\System.CommandLine.Localization\System.CommandLine.Localization.csproj", "{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.CommandLine.Localization.Tests", "src\System.CommandLine.Localization.Tests\System.CommandLine.Localization.Tests.csproj", "{182581D0-4AAB-49EE-8713-D890A6401DCF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalizationPlayground", "samples\LocalizationPlayground\LocalizationPlayground.csproj", "{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -236,6 +242,42 @@ Global
 		{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x64.Build.0 = Release|Any CPU
 		{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x86.ActiveCfg = Release|Any CPU
 		{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906}.Release|x86.Build.0 = Release|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Debug|x64.Build.0 = Debug|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Debug|x86.Build.0 = Debug|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Release|x64.ActiveCfg = Release|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Release|x64.Build.0 = Release|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Release|x86.ActiveCfg = Release|Any CPU
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF}.Release|x86.Build.0 = Release|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Debug|x64.Build.0 = Debug|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Debug|x86.Build.0 = Debug|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Release|Any CPU.Build.0 = Release|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Release|x64.ActiveCfg = Release|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Release|x64.Build.0 = Release|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Release|x86.ActiveCfg = Release|Any CPU
+		{182581D0-4AAB-49EE-8713-D890A6401DCF}.Release|x86.Build.0 = Release|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Debug|x64.Build.0 = Debug|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Debug|x86.Build.0 = Debug|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Release|x64.ActiveCfg = Release|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Release|x64.Build.0 = Release|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Release|x86.ActiveCfg = Release|Any CPU
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -255,6 +297,9 @@ Global
 		{644C4B4A-4A32-4307-9F71-C3BF901FFB66} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
 		{39483140-BC26-4CAD-BBAE-3DC76C2F16CF} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
 		{0BF6958D-9EE3-4623-B3D6-4DA77EAC1906} = {6749FB3E-39DE-4321-A39E-525278E9408D}
+		{9FD1BB47-F1B9-48A2-BD54-C324357C7BEF} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
+		{182581D0-4AAB-49EE-8713-D890A6401DCF} = {E5B1EC71-0FC4-4FAA-9C65-32D5016FBC45}
+		{8B62D16B-1CB0-40E6-810F-2FC6F8215BE0} = {6749FB3E-39DE-4321-A39E-525278E9408D}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {5C159F93-800B-49E7-9905-EE09F8B8434A}
diff --git a/samples/LocalizationPlayground/.gitignore b/samples/LocalizationPlayground/.gitignore
new file mode 100644
index 0000000000..90dce1bc59
--- /dev/null
+++ b/samples/LocalizationPlayground/.gitignore
@@ -0,0 +1 @@
+xlf
\ No newline at end of file
diff --git a/samples/LocalizationPlayground/CultureEnvironmentCommandLineExtensions.cs b/samples/LocalizationPlayground/CultureEnvironmentCommandLineExtensions.cs
new file mode 100644
index 0000000000..b908097796
--- /dev/null
+++ b/samples/LocalizationPlayground/CultureEnvironmentCommandLineExtensions.cs
@@ -0,0 +1,30 @@
+using System;
+using System.CommandLine.Builder;
+using System.CommandLine.Invocation;
+using System.Globalization;
+using System.Threading;
+
+namespace LocalizationPlayground
+{
+    internal static class CultureEnvironmentCommandLineExtensions
+    {
+        internal static CommandLineBuilder UseCultureEnvironment(
+            this CommandLineBuilder builder)
+        {
+            return builder.UseMiddleware(async (context, next) =>
+            {
+                if (Environment.GetEnvironmentVariable("DOTNET_SYSTEM_GLOBALIZATION_CULTURE") is string culture &&
+                        !string.IsNullOrEmpty(culture))
+                    CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
+
+                await next(context).ConfigureAwait(false);
+
+                if (context.InvocationResult is IInvocationResult innerResult)
+                {
+                    var execCtx = ExecutionContext.Capture();
+                    context.InvocationResult = new ExecutionContextRestoringInvocationResult(execCtx, innerResult);
+                }
+            }, MiddlewareOrder.ExceptionHandler);
+        }
+    }
+}
diff --git a/samples/LocalizationPlayground/ExecutionContextRestoringInvocationResult.cs b/samples/LocalizationPlayground/ExecutionContextRestoringInvocationResult.cs
new file mode 100644
index 0000000000..dbe9ff49a1
--- /dev/null
+++ b/samples/LocalizationPlayground/ExecutionContextRestoringInvocationResult.cs
@@ -0,0 +1,35 @@
+using System;
+using System.CommandLine.Invocation;
+using System.Threading;
+
+namespace LocalizationPlayground
+{
+    internal class ExecutionContextRestoringInvocationResult : IInvocationResult
+    {
+        private static readonly ContextCallback executionContextApplyCallback = state =>
+        {
+            var (@this, context) = (ValueTuple<IInvocationResult, InvocationContext>)state!;
+            @this.Apply(context);
+        };
+
+        private readonly ExecutionContext? executionContext;
+        private readonly IInvocationResult innerResult;
+
+        public ExecutionContextRestoringInvocationResult(ExecutionContext? executionContext, IInvocationResult innerResult)
+        {
+            this.executionContext = executionContext;
+            this.innerResult = innerResult;
+        }
+
+        public void Apply(InvocationContext context)
+        {
+            if (executionContext is null)
+                innerResult.Apply(context);
+            else
+                ExecutionContext.Run(executionContext,
+                    executionContextApplyCallback,
+                    (innerResult, context)
+                    );
+        }
+    }
+}
diff --git a/samples/LocalizationPlayground/LocalizationPlayground.csproj b/samples/LocalizationPlayground/LocalizationPlayground.csproj
new file mode 100644
index 0000000000..f305902dd4
--- /dev/null
+++ b/samples/LocalizationPlayground/LocalizationPlayground.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Nullable>enable</Nullable>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\System.CommandLine\System.CommandLine.csproj" />
+    <ProjectReference Include="..\..\src\System.CommandLine.Localization\System.CommandLine.Localization.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/samples/LocalizationPlayground/Program.cs b/samples/LocalizationPlayground/Program.cs
new file mode 100644
index 0000000000..92f8e329a0
--- /dev/null
+++ b/samples/LocalizationPlayground/Program.cs
@@ -0,0 +1,74 @@
+using System;
+using System.CommandLine;
+using System.CommandLine.Builder;
+using System.CommandLine.Invocation;
+using System.CommandLine.Localization;
+using System.CommandLine.Parsing;
+using System.Globalization;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Localization;
+
+namespace LocalizationPlayground
+{
+    public static class Program
+    {
+        public static Task<int> Main(string[] args)
+        {
+            CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+            var parser = new CommandLineBuilder(
+                new RootCommand
+                {
+                    Description = "Playground for localized CommandLine",
+                    Handler = CommandHandler.Create((int count, string name, InvocationContext invocation, IStringLocalizerFactory localizerFactory) =>
+                    {
+                        var cult = CultureInfo.CurrentUICulture;
+                        var germanResourceNames = typeof(Program).Assembly
+                            .GetSatelliteAssembly(CultureInfo.GetCultureInfo("de"))
+                            .GetManifestResourceNames();
+
+                        var localizer = localizerFactory.Create(typeof(Program));
+                        var locCultureInfo = localizer.GetString("Current culture: {0}", cult.NativeName);
+                        Console.WriteLine(locCultureInfo);
+                        var locLine = localizer.GetString("Hello {0}!", name);
+
+                        var availableStrings = localizer.GetAllStrings(true);
+
+                        _ = germanResourceNames;
+                        _ = availableStrings;
+
+                        for (int i = 0; i < count; i++)
+                        {
+                            Console.WriteLine(locLine);
+                        }
+
+                        Console.WriteLine();
+                        invocation.InvocationResult = new HelpResult();
+                    }),
+                })
+                .AddOption(new Option<int>(new[] { "--count", "-c" }, () => 1)
+                {
+                    Name = "count",
+                    Description = "Count of lines to print",
+                    Argument =
+                    {
+                        Name = "COUNT",
+                        Description = "An integer value",
+                        Arity = ArgumentArity.ZeroOrOne,
+                    }
+                })
+                .AddArgument(new Argument<string>("NAME")
+                {
+                    Description = "The name to display",
+                    Arity = ArgumentArity.ExactlyOne,
+                })
+                .UseEnvironmentVariableDirective()
+                .UseDebugDirective()
+                .UseHelp()
+                .UseVersionOption()
+                .UseCultureEnvironment()
+                .UseLocalization()
+                .Build();
+            return parser.InvokeAsync(args ?? Array.Empty<string>());
+        }
+    }
+}
diff --git a/samples/LocalizationPlayground/Program.resx b/samples/LocalizationPlayground/Program.resx
new file mode 100644
index 0000000000..fbba8b0e68
--- /dev/null
+++ b/samples/LocalizationPlayground/Program.resx
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="An integer value" xml:space="preserve">
+    <value>An integer value</value>
+  </data>
+  <data name="Count of lines to print" xml:space="preserve">
+    <value>Count of lines to print</value>
+  </data>
+  <data name="Current culture: {0}" xml:space="preserve">
+    <value>Current language settings: {0}</value>
+  </data>
+  <data name="Hello {0}!" xml:space="preserve">
+    <value>Hello {0}!</value>
+  </data>
+  <data name="Playground for localized CommandLine" xml:space="preserve">
+    <value>Playground for localized command-line applications</value>
+  </data>
+  <data name="The name to display" xml:space="preserve">
+    <value>The name to display</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/samples/LocalizationPlayground/Properties/launchSettings.json b/samples/LocalizationPlayground/Properties/launchSettings.json
new file mode 100644
index 0000000000..5b5b00ebf3
--- /dev/null
+++ b/samples/LocalizationPlayground/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+  "profiles": {
+    "LocalizationPlayground": {
+      "commandName": "Project",
+      "commandLineArgs": "[env:DOTNET_SYSTEM_GLOBALIZATION_CULTURE=de-DE] -c 3 .NET"
+    }
+  }
+}
\ No newline at end of file
diff --git a/samples/LocalizationPlayground/xlf/Program.de.xlf b/samples/LocalizationPlayground/xlf/Program.de.xlf
new file mode 100644
index 0000000000..ddaf8fe60d
--- /dev/null
+++ b/samples/LocalizationPlayground/xlf/Program.de.xlf
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="de" original="../Program.resx">
+    <body>
+      <trans-unit id="An integer value">
+        <source>An integer value</source>
+        <target state="new">Eine Ganzzahl</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="Count of lines to print">
+        <source>Count of lines to print</source>
+        <target state="new">Anzahl angezeigter Zeilen</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="Current culture: {0}">
+        <source>Current language settings: {0}</source>
+        <target state="new">Aktuelle Spracheinstellungen: {0}</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="Hello {0}!">
+        <source>Hello {0}!</source>
+        <target state="new">Hallo {0}!</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="Playground for localized CommandLine">
+        <source>Playground for localized command-line applications</source>
+        <target state="new">Spielplatz für lokalisierte Kommandozeilen-Programme</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="The name to display">
+        <source>The name to display</source>
+        <target state="new">Angezeigter Name</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/System.CommandLine.Localization.Tests/LocalizationExtensionsTests.cs b/src/System.CommandLine.Localization.Tests/LocalizationExtensionsTests.cs
new file mode 100644
index 0000000000..2533724bda
--- /dev/null
+++ b/src/System.CommandLine.Localization.Tests/LocalizationExtensionsTests.cs
@@ -0,0 +1,34 @@
+using System.CommandLine.Builder;
+using System.CommandLine.Invocation;
+using System.CommandLine.Parsing;
+using FluentAssertions;
+using Microsoft.Extensions.Localization;
+using Xunit;
+
+namespace System.CommandLine.Localization.Tests
+{
+    public class LocalizationExtensionsTests
+    {
+        [Fact]
+        public void UseLocalization_registers_IStringLocalizerFactory_to_binding_context()
+        {
+            bool asserted = false;
+            var command = new RootCommand()
+            {
+                Handler = CommandHandler.Create((IStringLocalizerFactory localizerFactory) =>
+                {
+                    localizerFactory.Should().NotBeNull();
+
+                    asserted = true;
+                }),
+            };
+            var parser = new CommandLineBuilder(command)
+                .UseLocalization()
+                .Build();
+
+            parser.InvokeAsync("").ConfigureAwait(false).GetAwaiter().GetResult();
+
+            asserted.Should().BeTrue();
+        }
+    }
+}
diff --git a/src/System.CommandLine.Localization.Tests/System.CommandLine.Localization.Tests.csproj b/src/System.CommandLine.Localization.Tests/System.CommandLine.Localization.Tests.csproj
new file mode 100644
index 0000000000..541f0a229a
--- /dev/null
+++ b/src/System.CommandLine.Localization.Tests/System.CommandLine.Localization.Tests.csproj
@@ -0,0 +1,33 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp3.1</TargetFrameworks>
+    <TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">$(TargetFrameworks);net462</TargetFrameworks>
+    <LangVersion>latest</LangVersion>
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Remove="TestResults\**" />
+    <EmbeddedResource Remove="TestResults\**" />
+    <None Remove="TestResults\**" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="FluentAssertions" Version="5.10.3" />
+  </ItemGroup>
+
+
+  <ItemGroup Condition="'$(DisableArcade)' == '1'">
+    <PackageReference Include="xunit" Version="2.4.1" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\System.CommandLine.Localization\System.CommandLine.Localization.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/System.CommandLine.Localization/.gitignore b/src/System.CommandLine.Localization/.gitignore
new file mode 100644
index 0000000000..90dce1bc59
--- /dev/null
+++ b/src/System.CommandLine.Localization/.gitignore
@@ -0,0 +1 @@
+xlf
\ No newline at end of file
diff --git a/src/System.CommandLine.Localization/LocalizationExtensions.cs b/src/System.CommandLine.Localization/LocalizationExtensions.cs
new file mode 100644
index 0000000000..2a40244207
--- /dev/null
+++ b/src/System.CommandLine.Localization/LocalizationExtensions.cs
@@ -0,0 +1,83 @@
+using System.CommandLine.Binding;
+using System.CommandLine.Builder;
+using System.CommandLine.Invocation;
+using System.IO;
+using System.Reflection;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace System.CommandLine.Localization
+{
+    public static class LocalizationExtensions
+    {
+        public static CommandLineBuilder UseLocalization(
+            this CommandLineBuilder builder, Type? resourceSource = null)
+        {
+            _ = builder ?? throw new ArgumentNullException(nameof(builder));
+
+            builder.UseMiddleware((context, next) =>
+            {
+                var binding = context.BindingContext;
+
+                binding.AddService(serviceProvider =>
+                {
+                    ILoggerFactory? loggerFactory = null;
+                    // If using Generic Host integration
+                    if (GetDynamicLoadedIHostInstance(serviceProvider) is { Interface: Type iHostType, Instance: object iHostInstance })
+                    {
+                        const BindingFlags getProperty = BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty;
+                        var hostedServices = iHostType.InvokeMember(
+                            "Services", getProperty, Type.DefaultBinder,
+                            iHostInstance, null);
+                        if (hostedServices is IServiceProvider hostedServiceProvider)
+                        {
+                            if (hostedServiceProvider.GetService<IStringLocalizerFactory>() is { } hostedLocalizer)
+                                return hostedLocalizer;
+
+                            // Extract logger factory if possible
+                            loggerFactory = hostedServiceProvider.GetService<ILoggerFactory>();
+                        }
+                    }
+
+                    // Construct default localizer
+                    var options = serviceProvider.GetService<IOptions<LocalizationOptions>>() ??
+                        Options.Create(new LocalizationOptions());
+                    loggerFactory ??= serviceProvider.GetService<ILoggerFactory>() ??
+                        Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance;
+                    return new ResourceManagerStringLocalizerFactory(options, loggerFactory);
+
+                    static (Type? Interface, object? Instance) GetDynamicLoadedIHostInstance(IServiceProvider serviceProvider)
+                    {
+                        Assembly? hostingAbstractionAsm = null;
+                        try
+                        {
+                            hostingAbstractionAsm = Assembly.Load("Microsoft.Extensions.Hosting.Abstractions");
+                        }
+                        catch (Exception) { }
+                        if (hostingAbstractionAsm is null)
+                            return default;
+                        var iHostType = Type.GetType(@"Microsoft.Extensions.Hosting.IHost, Microsoft.Extensions.Hosting.Abstractions");
+                        if (iHostType is null)
+                            return default;
+                        var iHostInstance = serviceProvider.GetService(iHostType);
+                        return (iHostType, iHostInstance);
+                    }
+                });
+
+                return next(context);
+            }, MiddlewareOrder.ExceptionHandler);
+            builder.UseHelpBuilder(ctx =>
+            {
+                var binder = new ModelBinder<LocalizedHelpBuilderFactory>();
+                if (!(binder.CreateInstance(ctx) is LocalizedHelpBuilderFactory helpFactory))
+                    throw new InvalidOperationException("Unable to resolve a localized help builder instance from the binding context.");
+                return helpFactory.CreateHelpBuilder(resourceSource);
+            });
+
+            return builder;
+        }
+    }
+}
diff --git a/src/System.CommandLine.Localization/LocalizedHelpBuilder.cs b/src/System.CommandLine.Localization/LocalizedHelpBuilder.cs
new file mode 100644
index 0000000000..12f7609ecd
--- /dev/null
+++ b/src/System.CommandLine.Localization/LocalizedHelpBuilder.cs
@@ -0,0 +1,179 @@
+using System.CommandLine.Help;
+using System.Linq;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+
+namespace System.CommandLine.Localization
+{
+    public class LocalizedHelpBuilder : HelpBuilder
+    {
+        private readonly IStringLocalizer localizer;
+        private readonly IStringLocalizer helpLocalizer;
+
+        public LocalizedHelpBuilder(IStringLocalizerFactory localizerFactory,
+            Type resourceSource, IConsole console, int? columnGutter = null,
+            int? indentationSize = null, int? maxWidth = null)
+            : base(console, columnGutter, indentationSize, maxWidth)
+        {
+            localizerFactory ??= new ResourceManagerStringLocalizerFactory(
+                Options.Create(new LocalizationOptions()), NullLoggerFactory.Instance);
+
+            localizer = localizerFactory.Create(resourceSource);
+            helpLocalizer = localizerFactory.Create(GetType());
+
+            AdditionalArgumentsTitle = GetHelpBuilderLocalizedString(
+                "DefaultHelpText.AdditionalArguments.Title",
+                DefaultHelpText.AdditionalArguments.Title);
+            AdditionalArgumentsDescription = GetHelpBuilderLocalizedString(
+                "DefaultHelpText.AdditionalArguments.Description",
+                DefaultHelpText.AdditionalArguments.Description);
+            ArgumentsTitle = GetHelpBuilderLocalizedString(
+                "DefaultHelpText.Arguments.Title",
+                DefaultHelpText.Arguments.Title);
+            CommandsTitle = GetHelpBuilderLocalizedString(
+                "DefaultHelpText.Commands.Title",
+                DefaultHelpText.Commands.Title);
+            OptionsTitle = GetHelpBuilderLocalizedString(
+                "DefaultHelpText.Options.Title",
+                DefaultHelpText.Options.Title);
+            UsageAdditionalArgumentsText = GetHelpBuilderLocalizedString(
+                "DefaultHelpText.Usage.AdditionalArguments",
+                DefaultHelpText.Usage.AdditionalArguments);
+            UsageCommandText = GetHelpBuilderLocalizedString(
+                "DefaultHelpText.Usage.Command",
+                DefaultHelpText.Usage.Command);
+            UsageOptionsText = GetHelpBuilderLocalizedString(
+                "DefaultHelpText.Usage.Options",
+                DefaultHelpText.Usage.Options);
+            UsageTitle = GetHelpBuilderLocalizedString(
+                "DefaultHelpText.Usage.Title",
+                DefaultHelpText.Usage.Title);
+        }
+
+        public override void Write(ICommand command)
+        {
+            base.Write(GetLocalizedCommand(command));
+        }
+
+        public override void Write(IOption option)
+        {
+            base.Write(GetLocalizedOption(option));
+        }
+
+        private Command GetLocalizedCommand(ICommand command)
+        {
+            var lcmd = new Command(command.Name);
+            Localize(lcmd, command);
+
+            foreach (IOption option in command.Options)
+            {
+                lcmd.AddOption(GetLocalizedOption(option));
+            }
+
+            foreach (IArgument argument in command.Arguments)
+            {
+                lcmd.AddArgument(GetLocalizedArgument(argument));
+            }
+
+            lcmd.TreatUnmatchedTokensAsErrors = command.TreatUnmatchedTokensAsErrors;
+
+            return lcmd;
+        }
+
+        private Option GetLocalizedOption(IOption option)
+        {
+            var lopt = new Option(option.RawAliases.First());
+            Localize(lopt, option);
+
+            lopt.Name = option.Name;
+            if (!(option.Argument.Arity is { MaximumNumberOfValues: 0, MinimumNumberOfValues: 0 }))
+            {
+                lopt.Argument = GetLocalizedArgument(option.Argument);
+            }
+
+            lopt.IsRequired = option.IsRequired;
+
+            return lopt;
+        }
+
+        private Argument GetLocalizedArgument(IArgument argument)
+        {
+            var larg = new Argument(argument.Name);
+            Localize(larg, argument);
+            larg.ArgumentType = argument.ValueType;
+            larg.Arity = argument.Arity;
+            if (argument.HasDefaultValue)
+            {
+                larg.SetDefaultValueFactory(() => argument.GetDefaultValue());
+            }
+
+            larg.AddSuggestions(txtToMatch => argument.GetSuggestions(txtToMatch)!);
+
+            return larg;
+        }
+
+        private void Localize(Symbol symbol, ISymbol source)
+        {
+            if (!string.IsNullOrEmpty(source.Description))
+            {
+                var locDesc = localizer.GetString(source.Description);
+                if (locDesc.ResourceNotFound)
+                {
+                    if (source.GetType().Name.Equals("HelpOption", StringComparison.Ordinal))
+                    {
+                        symbol.Description = GetHelpBuilderLocalizedString(
+                            "HelpOption.Description",
+                            source.Description ?? "");
+                    }
+                    else if (source.Name.Equals("version", StringComparison.OrdinalIgnoreCase))
+                    {
+                        symbol.Description = GetHelpBuilderLocalizedString(
+                            "VersionOption.Description",
+                            source.Description ?? "");
+                    }
+                }
+                else
+                {
+                    symbol.Description = locDesc;
+                }
+            }
+
+            foreach (var alias in source.RawAliases)
+            {
+                symbol.AddAlias(alias);
+            }
+
+            symbol.IsHidden = source.IsHidden;
+        }
+
+        protected override string DefaultValueHint(IArgument argument, bool isSingleArgument = true)
+        {
+            if (argument.HasDefaultValue && isSingleArgument && ShouldShowDefaultValueHint(argument))
+            {
+                var locDefault = helpLocalizer.GetString(
+                    $"{nameof(HelpBuilder)}.{nameof(DefaultValueHint)}",
+                    argument.GetDefaultValue());
+                if (!(locDefault.ResourceNotFound || string.IsNullOrEmpty(locDefault)))
+                {
+                    return locDefault;
+                }
+            }
+
+            return base.DefaultValueHint(argument, isSingleArgument);
+        }
+
+        private string GetHelpBuilderLocalizedString(string key, string @default)
+        {
+            var localized = helpLocalizer.GetString(key);
+            string localizedValue = localized;
+            if (string.IsNullOrEmpty(localizedValue) ||
+                string.Equals(localizedValue, key, StringComparison.Ordinal))
+            {
+                return @default;
+            }
+
+            return localizedValue;
+        }
+    }
+}
diff --git a/src/System.CommandLine.Localization/LocalizedHelpBuilder.resx b/src/System.CommandLine.Localization/LocalizedHelpBuilder.resx
new file mode 100644
index 0000000000..465e59577e
--- /dev/null
+++ b/src/System.CommandLine.Localization/LocalizedHelpBuilder.resx
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="DefaultHelpText.AdditionalArguments.Description" xml:space="preserve">
+    <value>DefaultHelpText.AdditionalArguments.Description</value>
+  </data>
+  <data name="DefaultHelpText.AdditionalArguments.Title" xml:space="preserve">
+    <value>DefaultHelpText.AdditionalArguments.Title</value>
+  </data>
+  <data name="DefaultHelpText.Arguments.Title" xml:space="preserve">
+    <value>DefaultHelpText.Arguments.Title</value>
+  </data>
+  <data name="DefaultHelpText.Commands.Title" xml:space="preserve">
+    <value>DefaultHelpText.Commands.Title</value>
+  </data>
+  <data name="DefaultHelpText.Options.Title" xml:space="preserve">
+    <value>DefaultHelpText.Options.Title</value>
+  </data>
+  <data name="DefaultHelpText.Usage.AdditionalArguments" xml:space="preserve">
+    <value>DefaultHelpText.Usage.AdditionalArguments</value>
+  </data>
+  <data name="DefaultHelpText.Usage.Command" xml:space="preserve">
+    <value>DefaultHelpText.Usage.Command</value>
+  </data>
+  <data name="DefaultHelpText.Usage.Options" xml:space="preserve">
+    <value>DefaultHelpText.Usage.Options</value>
+  </data>
+  <data name="DefaultHelpText.Usage.Title" xml:space="preserve">
+    <value>DefaultHelpText.Usage.Title</value>
+  </data>
+  <data name="HelpBuilder.DefaultValueHint" xml:space="preserve">
+    <value>default: {0}</value>
+  </data>
+  <data name="HelpBuilder.Option.IsRequired" xml:space="preserve">
+    <value>(REQUIRED)</value>
+  </data>
+  <data name="HelpOption.Description" xml:space="preserve">
+    <value>HelpOption.Description</value>
+  </data>
+  <data name="VersionOption.Description" xml:space="preserve">
+    <value>VersionOption.Description</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/src/System.CommandLine.Localization/LocalizedHelpBuilderFactory.cs b/src/System.CommandLine.Localization/LocalizedHelpBuilderFactory.cs
new file mode 100644
index 0000000000..654d6ca41a
--- /dev/null
+++ b/src/System.CommandLine.Localization/LocalizedHelpBuilderFactory.cs
@@ -0,0 +1,38 @@
+using System.CommandLine.Help;
+using System.Reflection;
+using Microsoft.Extensions.Localization;
+
+namespace System.CommandLine.Localization
+{
+    internal class LocalizedHelpBuilderFactory
+    {
+        private readonly IStringLocalizerFactory localizerFactory;
+        private readonly IConsole console;
+        private readonly int? columnGutter;
+        private readonly int? indentationSize;
+        private readonly int? maxWidth;
+
+        public LocalizedHelpBuilderFactory(
+            IStringLocalizerFactory localizerFactory, IConsole console,
+            int? columnGutter = null, int? indentationSize = null,
+            int? maxWidth = null) : base()
+        {
+            this.localizerFactory = localizerFactory 
+                ?? throw new ArgumentNullException(nameof(localizerFactory));
+            this.console = console;
+            this.columnGutter = columnGutter;
+            this.indentationSize = indentationSize;
+            this.maxWidth = maxWidth;
+        }
+
+        internal IHelpBuilder CreateHelpBuilder(Type? resourceSource = null)
+        {
+            if (resourceSource is null)
+            {
+                resourceSource = Assembly.GetEntryAssembly().EntryPoint.DeclaringType;
+            }
+            return new LocalizedHelpBuilder(localizerFactory, resourceSource,
+                console, columnGutter, indentationSize, maxWidth);
+        }
+    }
+}
diff --git a/src/System.CommandLine.Localization/System.CommandLine.Localization.csproj b/src/System.CommandLine.Localization/System.CommandLine.Localization.csproj
new file mode 100644
index 0000000000..1a8fc6db02
--- /dev/null
+++ b/src/System.CommandLine.Localization/System.CommandLine.Localization.csproj
@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <IsPackable>true</IsPackable>
+    <PackageId>System.CommandLine.Localization</PackageId>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <LangVersion>8</LangVersion>
+    <Nullable>enable</Nullable>
+    <Description>This package provides localization support for System.CommandLine.</Description>
+  </PropertyGroup>
+
+  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+    <DebugType>portable</DebugType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.6" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\System.CommandLine\System.CommandLine.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="..\System.Diagnostics.CodeAnalysis.cs" Link="System.Diagnostics.CodeAnalysis.cs" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/System.CommandLine.Localization/xlf/LocalizedHelpBuilder.de.xlf b/src/System.CommandLine.Localization/xlf/LocalizedHelpBuilder.de.xlf
new file mode 100644
index 0000000000..109efff244
--- /dev/null
+++ b/src/System.CommandLine.Localization/xlf/LocalizedHelpBuilder.de.xlf
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="de" original="../LocalizedHelpBuilder.resx">
+    <body>
+      <trans-unit id="DefaultHelpText.AdditionalArguments.Description">
+        <source>DefaultHelpText.AdditionalArguments.Description</source>
+        <target state="new">An die Anwendung übergebende Argumente.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DefaultHelpText.AdditionalArguments.Title">
+        <source>DefaultHelpText.AdditionalArguments.Title</source>
+        <target state="new">Zusätzliche Argumente:</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DefaultHelpText.Arguments.Title">
+        <source>DefaultHelpText.Arguments.Title</source>
+        <target state="new">Argumente:</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DefaultHelpText.Commands.Title">
+        <source>DefaultHelpText.Commands.Title</source>
+        <target state="new">Befehle:</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DefaultHelpText.Options.Title">
+        <source>DefaultHelpText.Options.Title</source>
+        <target state="new">Optionen:</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DefaultHelpText.Usage.AdditionalArguments">
+        <source>DefaultHelpText.Usage.AdditionalArguments</source>
+        <target state="new">[[--] &lt;zusätzliche argumente&gt;...]]</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DefaultHelpText.Usage.Command">
+        <source>DefaultHelpText.Usage.Command</source>
+        <target state="new">[befehl]</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DefaultHelpText.Usage.Options">
+        <source>DefaultHelpText.Usage.Options</source>
+        <target state="new">[option]</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DefaultHelpText.Usage.Title">
+        <source>DefaultHelpText.Usage.Title</source>
+        <target state="new">Befehlszeile:</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="HelpBuilder.DefaultValueHint">
+        <source>default: {0}</source>
+        <target state="new">standard: {0}</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="HelpBuilder.Option.IsRequired">
+        <source>(REQUIRED)</source>
+        <target state="new">(ERFORDERLICH)</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="HelpOption.Description">
+        <source>HelpOption.Description</source>
+        <target state="new">Hilfe und Gebrauchsanweisung anzeigen</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="VersionOption.Description">
+        <source>VersionOption.Description</source>
+        <target state="new">Versionsinformation anzeigen</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/System.CommandLine/Help/HelpBuilder.cs b/src/System.CommandLine/Help/HelpBuilder.cs
index 07cab25b6a..94bdaad15b 100644
--- a/src/System.CommandLine/Help/HelpBuilder.cs
+++ b/src/System.CommandLine/Help/HelpBuilder.cs
@@ -28,6 +28,25 @@ public class HelpBuilder : IHelpBuilder
 
         public int MaxWidth { get; }
 
+        protected string AdditionalArgumentsTitle { get; set; } =
+            AdditionalArguments.Title;
+        protected string AdditionalArgumentsDescription { get; set; } =
+            AdditionalArguments.Description;
+        protected string ArgumentsTitle { get; set; } =
+            Arguments.Title;
+        protected string CommandsTitle { get; set; } =
+            Commands.Title;
+        protected string OptionsTitle { get; set; } =
+            Options.Title;
+        protected string UsageAdditionalArgumentsText { get; set; } =
+            Usage.AdditionalArguments;
+        protected string UsageCommandText { get; set; } =
+            Usage.Command;
+        protected string UsageOptionsText { get; set; } =
+            Usage.Options;
+        protected string UsageTitle { get; set; } =
+            Usage.Title;
+
         /// <summary>
         /// Brokers the generation and output of help text of <see cref="Symbol"/>
         /// and the <see cref="IConsole"/>
@@ -577,7 +596,7 @@ protected virtual void AddUsage(ICommand command)
 
             if (hasOptionHelp)
             {
-                usage.Add(Usage.Options);
+                usage.Add(UsageOptionsText);
             }
 
             usage.Add(FormatArgumentUsage(command.Arguments.ToArray()));
@@ -588,15 +607,15 @@ protected virtual void AddUsage(ICommand command)
 
             if (hasCommandHelp)
             {
-                usage.Add(Usage.Command);
+                usage.Add(UsageCommandText);
             }
 
             if (!command.TreatUnmatchedTokensAsErrors)
             {
-                usage.Add(Usage.AdditionalArguments);
+                usage.Add(UsageAdditionalArgumentsText);
             }
 
-            HelpSection.WriteHeading(this, Usage.Title, string.Join(" ", usage.Where(u => !string.IsNullOrWhiteSpace(u))));
+            HelpSection.WriteHeading(this, UsageTitle, string.Join(" ", usage.Where(u => !string.IsNullOrWhiteSpace(u))));
         }
 
         private string FormatArgumentUsage(IReadOnlyCollection<IArgument> arguments)
@@ -674,7 +693,7 @@ protected virtual void AddArguments(ICommand command)
 
             HelpSection.WriteItems(
                 this,
-                Arguments.Title,
+                ArgumentsTitle,
                 commands.SelectMany(GetArgumentHelpItems).Distinct().ToArray());
         }
 
@@ -693,7 +712,7 @@ protected virtual void AddOptions(ICommand command)
 
             HelpSection.WriteItems(
                 this,
-                Options.Title,
+                OptionsTitle,
                 options.SelectMany(GetOptionHelpItems).Distinct().ToArray());
         }
 
@@ -711,7 +730,7 @@ protected virtual void AddSubcommands(ICommand command)
                               .ToArray();
 
             HelpSection.WriteItems(this,
-                              Commands.Title,
+                              CommandsTitle,
                               subcommands.SelectMany(GetOptionHelpItems).ToArray());
         }
 
@@ -722,7 +741,9 @@ protected virtual void AddAdditionalArguments(ICommand command)
                 return;
             }
 
-            HelpSection.WriteHeading(this, AdditionalArguments.Title, AdditionalArguments.Description);
+            HelpSection.WriteHeading(this, 
+                AdditionalArgumentsTitle,
+                AdditionalArgumentsDescription);
         }
 
         private bool ShouldDisplayArgumentHelp(ICommand? command)
@@ -861,12 +882,12 @@ private static void AddInvocation(HelpBuilder builder, IReadOnlyCollection<HelpI
             }
         }
 
-        internal bool ShouldShowHelp(ISymbol symbol)
+        protected bool ShouldShowHelp(ISymbol symbol)
         {
             return !symbol.IsHidden;
         }
 
-        internal bool ShouldShowDefaultValueHint(IArgument argument)
+        protected bool ShouldShowDefaultValueHint(IArgument argument)
         {
             return argument.HasDefaultValue && ShouldShowHelp(argument);
         }