Pārlūkot izejas kodu

feat: Add i18n for WinUI

Bruce Wayne 1 gadu atpakaļ
vecāks
revīzija
f65de9245c

+ 42 - 1
.github/workflows/CI.yaml

@@ -56,7 +56,10 @@ jobs:
 
       - name: Build
         shell: pwsh
-        run: .\build.ps1
+        run: |
+          .\build.ps1
+          .\build.WinUI.ps1 x64
+          .\build.WinUI.ps1 arm64
 
       - name: Upload
         uses: actions/upload-artifact@v3
@@ -64,6 +67,18 @@ jobs:
           name: ${{ env.ProjectName }}
           path: ${{ env.ProjectName }}\bin\${{ env.Configuration }}\${{ env.NET_TFM }}\generic\publish\
 
+      - name: Upload WinUI x64
+        uses: actions/upload-artifact@v3
+        with:
+          name: ${{ env.ProjectName }}.WinUI.x64
+          path: ${{ env.ProjectName }}.WinUI\bin\x64\${{ env.Configuration }}\${{ env.NET_TFM }}\win-x64\publish\
+
+      - name: Upload WinUI arm64
+        uses: actions/upload-artifact@v3
+        with:
+          name: ${{ env.ProjectName }}.WinUI.arm64
+          path: ${{ env.ProjectName }}.WinUI\bin\arm64\${{ env.Configuration }}\${{ env.NET_TFM }}\win-arm64\publish\
+
   nuget:
     needs: [test, check_format]
     if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}
@@ -102,6 +117,16 @@ jobs:
           name: ${{ env.ProjectName }}
           path: ${{ env.ProjectName }}
 
+      - uses: actions/download-artifact@v3
+        with:
+          name: ${{ env.ProjectName }}.WinUI.x64
+          path: ${{ env.ProjectName }}.WinUI.x64
+
+      - uses: actions/download-artifact@v3
+        with:
+          name: ${{ env.ProjectName }}.WinUI.arm64
+          path: ${{ env.ProjectName }}.WinUI.arm64
+
       - name: Get tag
         id: tag
         uses: dawidd6/action-get-tag@v1
@@ -114,6 +139,20 @@ jobs:
           7z a -mx9 "$zip_path" ${{ env.ProjectName }}
           echo "GENERIC_SHA256=$((Get-FileHash $zip_path -Algorithm SHA256).Hash)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
 
+      - name: Package WinUI x64
+        shell: pwsh
+        run: |
+          $zip_path = "builtfiles/$env:ProjectName-${{ steps.tag.outputs.tag }}-x64.7z"
+          7z a -mx9 "$zip_path" ${{ env.ProjectName }}.WinUI.x64
+          echo "X64_SHA256=$((Get-FileHash $zip_path -Algorithm SHA256).Hash)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+
+      - name: Package WinUI arm64
+        shell: pwsh
+        run: |
+          $zip_path = "builtfiles/$env:ProjectName-${{ steps.tag.outputs.tag }}-arm64.7z"
+          7z a -mx9 "$zip_path" ${{ env.ProjectName }}.WinUI.arm64
+          echo "ARM64_SHA256=$((Get-FileHash $zip_path -Algorithm SHA256).Hash)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+
       - name: Create a new GitHub release
         uses: ncipollo/release-action@v1
         with:
@@ -126,3 +165,5 @@ jobs:
             | Filename | SHA-256 |
             | :- | :- |
             | <sub>${{ env.ProjectName }}-${{ steps.tag.outputs.tag }}.7z</sub> | <sub>${{ env.GENERIC_SHA256 }}</sub> |
+            | <sub>${{ env.ProjectName }}-${{ steps.tag.outputs.tag }}-x64.7z</sub> | <sub>${{ env.X64_SHA256 }}</sub> |
+            | <sub>${{ env.ProjectName }}-${{ steps.tag.outputs.tag }}-arm64.7z</sub> | <sub>${{ env.ARM64_SHA256 }}</sub> |

+ 10 - 2
NatTypeTester.WinUI/Extensions/ContentDialogExtensions.cs

@@ -4,13 +4,21 @@ internal static class ContentDialogExtensions
 {
 	public static async ValueTask HandleExceptionWithContentDialogAsync(this Exception ex, XamlRoot root)
 	{
+		ResourceLoader resourceLoader = ResourceLoader.GetForViewIndependentUse();
 		ContentDialog dialog = new();
 		try
 		{
 			dialog.XamlRoot = root;
 			dialog.Title = nameof(NatTypeTester);
-			dialog.Content = ex.Message;
-			dialog.PrimaryButtonText = @"OK";
+
+			string content = resourceLoader.GetString(ex.Message);
+			if (string.IsNullOrEmpty(content))
+			{
+				content = ex.Message;
+			}
+			dialog.Content = content;
+
+			dialog.PrimaryButtonText = resourceLoader.GetString(@"OK");
 
 			await dialog.ShowAsync();
 		}

+ 1 - 1
NatTypeTester.WinUI/MainWindow.xaml.cs

@@ -9,7 +9,7 @@ public sealed partial class MainWindow : ISingletonDependency
 		Title = nameof(NatTypeTester);
 		ExtendsContentIntoTitleBar = true;
 
-		AppWindow.Resize(new SizeInt32(500, 590));
+		AppWindow.Resize(new SizeInt32(500, 560));
 		AppWindow.SetIcon(@"Assets\icon.ico");
 
 		// CenterScreen

+ 1 - 0
NatTypeTester.WinUI/NatTypeTesterModule.cs

@@ -20,6 +20,7 @@ global using Volo.Abp;
 global using Volo.Abp.Autofac;
 global using Volo.Abp.DependencyInjection;
 global using Volo.Abp.Modularity;
+global using Windows.ApplicationModel.Resources;
 global using Windows.Graphics;
 global using Windows.System;
 

+ 177 - 0
NatTypeTester.WinUI/Strings/en-US/Resources.resw

@@ -0,0 +1,177 @@
+<?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="BindingTestTextBox.Header" xml:space="preserve">
+    <value>Binding test</value>
+  </data>
+  <data name="FilteringBehaviorTextBox.Header" xml:space="preserve">
+    <value>Filtering behavior</value>
+  </data>
+  <data name="LocalEndComboBox.Header" xml:space="preserve">
+    <value>Local end</value>
+  </data>
+  <data name="LocalEndComboBox.PlaceholderText" xml:space="preserve">
+    <value>Any</value>
+  </data>
+  <data name="MappingBehaviorTextBox.Header" xml:space="preserve">
+    <value>Mapping behavior</value>
+  </data>
+  <data name="NoProxy.Content" xml:space="preserve">
+    <value>Don't use Proxy</value>
+  </data>
+  <data name="OK" xml:space="preserve">
+    <value>OK</value>
+  </data>
+  <data name="ProxyPasswordTextBox.Header" xml:space="preserve">
+    <value>Password</value>
+  </data>
+  <data name="ProxyRadioButtons.Header" xml:space="preserve">
+    <value>Proxy</value>
+  </data>
+  <data name="ProxyServerTextBox.Header" xml:space="preserve">
+    <value>Server</value>
+  </data>
+  <data name="ProxyUsernameTextBox.Header" xml:space="preserve">
+    <value>Username</value>
+  </data>
+  <data name="PublicEndTextBox.Header" xml:space="preserve">
+    <value>Public end</value>
+  </data>
+  <data name="RFC3489NatTypeTextBox.Header" xml:space="preserve">
+    <value>NAT type</value>
+  </data>
+  <data name="RFC3489Warning.Message" xml:space="preserve">
+    <value>This protocol is obsoleted and may not be suitable for modern routers or NAT.</value>
+  </data>
+  <data name="ServersComboBox.Header" xml:space="preserve">
+    <value>STUN Server</value>
+  </data>
+  <data name="SOCKS5Proxy.Content" xml:space="preserve">
+    <value>SOCKS5</value>
+  </data>
+  <data name="TestButton.Content" xml:space="preserve">
+    <value>Test</value>
+  </data>
+  <data name="Unknown proxy address" xml:space="preserve">
+    <value>Unknown proxy address</value>
+  </data>
+  <data name="Wrong STUN Server!" xml:space="preserve">
+    <value>Wrong STUN Server!</value>
+  </data>
+</root>

+ 177 - 0
NatTypeTester.WinUI/Strings/zh-CN/Resources.resw

@@ -0,0 +1,177 @@
+<?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="BindingTestTextBox.Header" xml:space="preserve">
+    <value>绑定测试</value>
+  </data>
+  <data name="FilteringBehaviorTextBox.Header" xml:space="preserve">
+    <value>过滤行为</value>
+  </data>
+  <data name="LocalEndComboBox.Header" xml:space="preserve">
+    <value>本地地址</value>
+  </data>
+  <data name="LocalEndComboBox.PlaceholderText" xml:space="preserve">
+    <value>任意地址</value>
+  </data>
+  <data name="MappingBehaviorTextBox.Header" xml:space="preserve">
+    <value>映射行为</value>
+  </data>
+  <data name="NoProxy.Content" xml:space="preserve">
+    <value>不使用代理</value>
+  </data>
+  <data name="OK" xml:space="preserve">
+    <value>确定</value>
+  </data>
+  <data name="ProxyPasswordTextBox.Header" xml:space="preserve">
+    <value>密码</value>
+  </data>
+  <data name="ProxyRadioButtons.Header" xml:space="preserve">
+    <value>代理设置</value>
+  </data>
+  <data name="ProxyServerTextBox.Header" xml:space="preserve">
+    <value>服务器</value>
+  </data>
+  <data name="ProxyUsernameTextBox.Header" xml:space="preserve">
+    <value>用户名</value>
+  </data>
+  <data name="PublicEndTextBox.Header" xml:space="preserve">
+    <value>公网地址</value>
+  </data>
+  <data name="RFC3489NatTypeTextBox.Header" xml:space="preserve">
+    <value>NAT 类型</value>
+  </data>
+  <data name="RFC3489Warning.Message" xml:space="preserve">
+    <value>该协议已过时,可能不适用于现代路由器或 NAT。</value>
+  </data>
+  <data name="ServersComboBox.Header" xml:space="preserve">
+    <value>STUN 服务器</value>
+  </data>
+  <data name="SOCKS5Proxy.Content" xml:space="preserve">
+    <value>SOCKS5 代理</value>
+  </data>
+  <data name="TestButton.Content" xml:space="preserve">
+    <value>测试</value>
+  </data>
+  <data name="Unknown proxy address" xml:space="preserve">
+    <value>代理服务器地址输入有误!</value>
+  </data>
+  <data name="Wrong STUN Server!" xml:space="preserve">
+    <value>STUN 服务器输入有误!</value>
+  </data>
+</root>

+ 5 - 3
NatTypeTester.WinUI/Views/MainPage.xaml

@@ -9,7 +9,7 @@
     mc:Ignorable="d"
     Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
 
-    <Grid RowDefinitions="28,Auto,*" >
+    <Grid RowDefinitions="28,Auto,*">
 
         <!-- TitleBar -->
         <StackPanel
@@ -23,11 +23,13 @@
                 Text="NatTypeTester" />
         </StackPanel>
 
-        <StackPanel Grid.Row="1">
+        <StackPanel Grid.Row="1"
+                    TabIndex="0"
+                    IsTabStop="True">
             <ComboBox x:Name="ServersComboBox"
+                      x:Uid="ServersComboBox"
                       Margin="10,10"
                       IsEditable="True"
-                      Header="STUN Server"
                       HorizontalAlignment="Stretch">
                 <ComboBox.ItemTemplate>
                     <DataTemplate>

+ 1 - 0
NatTypeTester.WinUI/Views/MainPage.xaml.cs

@@ -53,6 +53,7 @@ internal sealed partial class MainPage
 			NavigationView.SelectedItem = NavigationView.MenuItems.OfType<NavigationViewItem>().First();
 
 			ViewModel.LoadStunServer();
+			ServersComboBox.SelectedIndex = 0;
 		});
 	}
 }

+ 20 - 19
NatTypeTester.WinUI/Views/RFC3489Page.xaml

@@ -7,27 +7,28 @@
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     mc:Ignorable="d">
 
-    <Grid Margin="10" RowDefinitions="Auto,Auto,Auto,*">
+    <Grid Margin="10" RowDefinitions="Auto,*">
 
-        <TextBox x:Name="NatTypeTextBox" Grid.Row="0"
-                 Margin="5" IsReadOnly="True"
-                 VerticalContentAlignment="Center" VerticalAlignment="Center"
-                 Header="NAT type" />
-        <ComboBox x:Name="LocalEndComboBox" Grid.Row="1"
-                  Margin="5"
-                  IsEditable="True" HorizontalAlignment="Stretch"
-                  VerticalContentAlignment="Center" VerticalAlignment="Center"
-                  Header="Local end">
-            <ComboBoxItem>0.0.0.0:0</ComboBoxItem>
-            <ComboBoxItem>[::]:0</ComboBoxItem>
-        </ComboBox>
-        <TextBox x:Name="PublicEndTextBox" Grid.Row="2"
-                 Margin="5"
-                 IsReadOnly="True"
-                 VerticalContentAlignment="Center" VerticalAlignment="Center"
-                 Header="Public end" />
+        <StackPanel Spacing="5">
+            <InfoBar
+                IsOpen="True"
+                IsClosable="False"
+                Severity="Warning"
+                x:Uid="RFC3489Warning" />
 
-        <Button x:Name="TestButton" Grid.Row="3" HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Test" />
+            <TextBox x:Name="NatTypeTextBox"
+                     x:Uid="RFC3489NatTypeTextBox"
+                     IsReadOnly="True" />
+            <TextBox x:Name="LocalEndComboBox"
+                     x:Uid="LocalEndComboBox" />
+            <TextBox x:Name="PublicEndTextBox"
+                     x:Uid="PublicEndTextBox"
+                     IsReadOnly="True" />
+        </StackPanel>
 
+        <Button x:Name="TestButton"
+                x:Uid="TestButton"
+                Grid.Row="1"
+                HorizontalAlignment="Right" VerticalAlignment="Bottom" />
     </Grid>
 </views:RFC3489ReactivePage>

+ 1 - 10
NatTypeTester.WinUI/Views/RFC3489Page.xaml.cs

@@ -15,16 +15,7 @@ internal sealed partial class RFC3489Page : ITransientDependency
 
 			this.Bind(ViewModel, vm => vm.Result3489.LocalEndPoint, v => v.LocalEndComboBox.Text).DisposeWith(d);
 
-			LocalEndComboBox.Events().TextSubmitted.Subscribe(parameter =>
-			{
-				if (ViewModel.Result3489.LocalEndPoint is not null)
-				{
-					return;
-				}
-
-				LocalEndComboBox.Text = string.Empty;
-				parameter.args.Handled = true;
-			}).DisposeWith(d);
+			LocalEndComboBox.Events().LostFocus.Subscribe(_ => LocalEndComboBox.Text = ViewModel.Result3489.LocalEndPoint?.ToString()).DisposeWith(d);
 
 			this.OneWayBind(ViewModel, vm => vm.Result3489.PublicEndPoint, v => v.PublicEndTextBox.Text).DisposeWith(d);
 

+ 33 - 38
NatTypeTester.WinUI/Views/RFC5780Page.xaml

@@ -7,45 +7,40 @@
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     mc:Ignorable="d">
 
-    <Grid Margin="10" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*">
-        <Grid Grid.Row="0" Margin="5,0,5,5">
-            <RadioButtons x:Name="TransportTypeRadioButtons" MaxColumns="4">
-                <RadioButton Content="UDP" />
-                <RadioButton Content="TCP" />
-                <RadioButton Content="TLS" />
-            </RadioButtons>
-        </Grid>
-        <TextBox
-            x:Name="BindingTestTextBox" Grid.Row="1"
-            Margin="5" IsReadOnly="True"
-            VerticalContentAlignment="Center" VerticalAlignment="Center"
-            Header="Binding test" />
-        <TextBox
-            x:Name="MappingBehaviorTextBox" Grid.Row="2"
-            Margin="5" IsReadOnly="True"
-            VerticalContentAlignment="Center" VerticalAlignment="Center"
-            Header="Mapping behavior" />
-        <TextBox
-            x:Name="FilteringBehaviorTextBox" Grid.Row="3"
-            Margin="5" IsReadOnly="True"
-            VerticalContentAlignment="Center" VerticalAlignment="Center"
-            Header="Filtering behavior" />
-        <ComboBox x:Name="LocalAddressComboBox" Grid.Row="4"
-                  Margin="5"
-                  IsEditable="True" HorizontalAlignment="Stretch"
-                  VerticalContentAlignment="Center" VerticalAlignment="Center"
-                  Header="Local end">
-            <ComboBoxItem>0.0.0.0:0</ComboBoxItem>
-            <ComboBoxItem>[::]:0</ComboBoxItem>
-        </ComboBox>
-        <TextBox
-            x:Name="MappingAddressTextBox" Grid.Row="5"
-            Margin="5"
-            IsReadOnly="True"
-            VerticalContentAlignment="Center" VerticalAlignment="Center"
-            Header="Public end" />
+    <Grid Margin="10" RowDefinitions="Auto,*">
 
-        <Button x:Name="DiscoveryButton" Grid.Row="6" HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Test" />
+        <StackPanel Spacing="5">
+            <Grid>
+                <RadioButtons x:Name="TransportTypeRadioButtons" MaxColumns="4" Margin="0,0,0,5">
+                    <RadioButton Content="UDP" />
+                    <RadioButton Content="TCP" />
+                    <RadioButton Content="TLS" />
+                </RadioButtons>
+            </Grid>
+            <TextBox
+                x:Name="BindingTestTextBox"
+                x:Uid="BindingTestTextBox"
+                IsReadOnly="True" />
+            <TextBox
+                x:Name="MappingBehaviorTextBox"
+                x:Uid="MappingBehaviorTextBox"
+                IsReadOnly="True" />
+            <TextBox
+                x:Name="FilteringBehaviorTextBox"
+                x:Uid="FilteringBehaviorTextBox"
+                IsReadOnly="True" />
+            <TextBox x:Name="LocalAddressComboBox"
+                     x:Uid="LocalEndComboBox" />
+            <TextBox
+                x:Name="MappingAddressTextBox"
+                IsReadOnly="True"
+                x:Uid="PublicEndTextBox" />
+        </StackPanel>
+
+        <Button x:Name="DiscoveryButton"
+                x:Uid="TestButton"
+                Grid.Row="1"
+                HorizontalAlignment="Right" VerticalAlignment="Bottom" />
     </Grid>
 
 </views:RFC5780ReactivePage>

+ 1 - 10
NatTypeTester.WinUI/Views/RFC5780Page.xaml.cs

@@ -23,16 +23,7 @@ internal sealed partial class RFC5780Page : ITransientDependency
 
 			this.Bind(ViewModel, vm => vm.Result5389.LocalEndPoint, v => v.LocalAddressComboBox.Text).DisposeWith(d);
 
-			LocalAddressComboBox.Events().TextSubmitted.Subscribe(parameter =>
-			{
-				if (ViewModel.Result5389.LocalEndPoint is not null)
-				{
-					return;
-				}
-
-				LocalAddressComboBox.Text = string.Empty;
-				parameter.args.Handled = true;
-			}).DisposeWith(d);
+			LocalAddressComboBox.Events().LostFocus.Subscribe(_ => LocalAddressComboBox.Text = ViewModel.Result5389.LocalEndPoint?.ToString()).DisposeWith(d);
 
 			this.OneWayBind(ViewModel, vm => vm.Result5389.PublicEndPoint, v => v.MappingAddressTextBox.Text).DisposeWith(d);
 

+ 15 - 15
NatTypeTester.WinUI/Views/SettingPage.xaml

@@ -9,9 +9,9 @@
 
     <Grid RowDefinitions="Auto,Auto">
         <Grid Margin="15" Grid.Row="0">
-            <RadioButtons Header="Proxy" x:Name="ProxyRadioButtons">
-                <RadioButton Content="Don't use Proxy" />
-                <RadioButton Content="SOCKS5" />
+            <RadioButtons x:Name="ProxyRadioButtons" x:Uid="ProxyRadioButtons">
+                <RadioButton x:Uid="NoProxy" />
+                <RadioButton x:Uid="SOCKS5Proxy" />
             </RadioButtons>
         </Grid>
 
@@ -23,20 +23,20 @@
                     <RowDefinition Height="Auto" />
                 </Grid.RowDefinitions>
                 <TextBox
-                    x:Name="ProxyServerTextBox" Grid.Row="0"
-                    Margin="0,5" IsReadOnly="False"
-                    VerticalContentAlignment="Center" VerticalAlignment="Center"
-                    Header="Server" />
+                    x:Name="ProxyServerTextBox"
+                    x:Uid="ProxyServerTextBox"
+                    Grid.Row="0"
+                    Margin="0,5" IsReadOnly="False" />
                 <TextBox
-                    x:Name="ProxyUsernameTextBox" Grid.Row="1"
-                    Margin="0,5" IsReadOnly="False"
-                    VerticalContentAlignment="Center" VerticalAlignment="Center"
-                    Header="Username" />
+                    x:Name="ProxyUsernameTextBox"
+                    x:Uid="ProxyUsernameTextBox"
+                    Grid.Row="1"
+                    Margin="0,5" IsReadOnly="False" />
                 <TextBox
-                    x:Name="ProxyPasswordTextBox" Grid.Row="2"
-                    Margin="0,5"
-                    VerticalContentAlignment="Center" VerticalAlignment="Center"
-                    Header="Password" />
+                    x:Name="ProxyPasswordTextBox"
+                    x:Uid="ProxyPasswordTextBox"
+                    Grid.Row="2"
+                    Margin="0,5" />
             </Grid>
         </ContentControl>
 

+ 1 - 1
NatTypeTester/MainWindow.xaml.cs

@@ -31,7 +31,7 @@ public partial class MainWindow : ISingletonDependency
 
 			#endregion
 
-			this.Bind(ViewModel, vm => vm.Router, v => v.RoutedViewHost.Router).DisposeWith(d);
+			this.OneWayBind(ViewModel, vm => vm.Router, v => v.RoutedViewHost.Router).DisposeWith(d);
 
 			NavigationView.Events().SelectionChanged
 				.Subscribe(parameter =>

+ 1 - 1
NatTypeTester/NatTypeTester.csproj

@@ -6,7 +6,7 @@
     <TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
     <OutputType>WinExe</OutputType>
     <UseWPF>true</UseWPF>
-    <Version>7.0.0</Version>
+    <Version>8.0.0</Version>
     <ApplicationIcon>icon.ico</ApplicationIcon>
   </PropertyGroup>
 

+ 0 - 3
README.md

@@ -21,9 +21,6 @@ Stun.Net | [![NuGet.org](https://img.shields.io/nuget/v/Stun.Net.svg?logo=nuget)
 - [x] TCP
 - [x] TLS-over-TCP
 
-## Preview
-![](pic/1.png)
-
 ## RFC3489
 <details>
 

+ 6 - 1
STUN/STUN.csproj

@@ -11,10 +11,15 @@
     <PackageProjectUrl>https://github.com/HMBSbige/NatTypeTester</PackageProjectUrl>
     <RepositoryUrl>https://github.com/HMBSbige/NatTypeTester</RepositoryUrl>
     <PackageTags>stun;nat;rfc3489;rfc5389;rfc5780;rfc8489</PackageTags>
-    <Version>7.0.0</Version>
+    <Version>8.0.0</Version>
     <PackageId>Stun.Net</PackageId>
+    <PackageReadmeFile>README.md</PackageReadmeFile>
   </PropertyGroup>
 
+  <ItemGroup>
+    <None Include="../README.md" Pack="true" PackagePath="/" />
+  </ItemGroup>
+
   <ItemGroup>
     <PackageReference Include="Socks5" Version="1.0.2" />
   </ItemGroup>

+ 19 - 0
build.WinUI.ps1

@@ -0,0 +1,19 @@
+param([string]$platform = 'x64')
+$ErrorActionPreference = 'Stop'
+
+dotnet --info
+
+$proj = 'NatTypeTester.WinUI'
+$net_tfm = 'net8.0-windows10.0.22621.0'
+$configuration = 'Release'
+$proj_path = "$PSScriptRoot\$proj\$proj.csproj"
+
+$rid = "win-$platform"
+Write-Host "Building $rid"
+
+$publishDir = "$PSScriptRoot\$proj\bin\$platform\$configuration\$net_tfm\$rid\publish"
+
+Remove-Item $publishDir -Recurse -Force -Confirm:$false -ErrorAction Ignore
+
+dotnet publish -c $configuration -f $net_tfm -p:Platform=$platform -r $rid --self-contained $proj_path
+if ($LASTEXITCODE) { exit $LASTEXITCODE }

BIN
pic/1.png