Add multi-language support #30

This commit is contained in:
Nguyen Duy
2024-05-08 21:38:40 +07:00
parent a44e7d26df
commit 93ebbd21d8
11 changed files with 270 additions and 51 deletions
+5 -1
View File
@@ -4,6 +4,10 @@
xmlns:local="clr-namespace:ParsecVDisplay"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Languages/vi.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
+76 -6
View File
@@ -5,6 +5,11 @@ using System.Windows;
using System.Linq;
using System.Windows.Interop;
using System.Windows.Media;
using System.Reflection;
using System.Collections.Generic;
using System.Collections;
using System.Globalization;
using System.Resources;
namespace ParsecVDisplay
{
@@ -16,12 +21,8 @@ namespace ParsecVDisplay
public const string GITHUB_REPO = "nomi-san/parsec-vdd";
public static bool Silent { get; private set; }
static App()
{
var displays = Display.GetAllDisplays();
return;
}
public static string[] Languages => LanguageDicts.Keys.ToArray();
static Dictionary<string, ResourceDictionary> LanguageDicts;
protected override void OnStartup(StartupEventArgs e)
{
@@ -100,6 +101,7 @@ namespace ParsecVDisplay
});
base.OnStartup(e);
LoadLanguages();
// Disable GPU to prevent flickering when adding display
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
@@ -110,5 +112,73 @@ namespace ParsecVDisplay
ParsecVDD.Uninit();
base.OnExit(e);
}
static void LoadLanguages()
{
LanguageDicts = new Dictionary<string, ResourceDictionary>();
var assembly = Assembly.GetExecutingAssembly();
var rm = new ResourceManager(assembly.GetName().Name + ".g", assembly);
try
{
var list = rm.GetResourceSet(CultureInfo.CurrentCulture, true, true);
foreach (DictionaryEntry item in list)
{
if (item.Key is string key
&& key.StartsWith("languages/"))
{
var source = key
.Replace("languages/", "Languages/")
.Replace(".baml", ".xaml");
try
{
var dict = new ResourceDictionary();
dict.Source = new Uri(source, UriKind.Relative);
var name = dict["lang_name"].ToString();
LanguageDicts.Add(name, dict);
}
catch
{
}
}
}
}
finally
{
rm.ReleaseAllResources();
}
SetLanguage(Config.Language, saveConfig: false);
}
public static void SetLanguage(string name, bool saveConfig = true)
{
if (LanguageDicts.TryGetValue(name, out var dict))
{
Current.Resources.MergedDictionaries.Add(dict);
if (saveConfig)
{
Config.Language = name;
}
}
}
public static string GetTranslation(string key)
{
try
{
var trans = Current.FindResource(key);
if (trans != null && trans is string)
return trans as string;
}
catch
{
}
return string.Format("{{{{{0}}}}}", key);
}
}
}
+11 -9
View File
@@ -14,7 +14,7 @@
d:Background="#222"
>
<Grid>
<Label Content="Custom Display Modes" Foreground="White" Margin="5,5,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Label Content="{DynamicResource t_custom}" Foreground="White" Margin="5,5,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" />
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel.Resources>
<Style TargetType="{x:Type StackPanel}">
@@ -32,7 +32,7 @@
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<Label Width="50">Slot 1</Label>
<Label Width="50">Slot #1</Label>
<TextBox Width="80" />
<Label>×</Label>
<TextBox Width="80" />
@@ -41,7 +41,7 @@
<Label>Hz</Label>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Width="50">Slot 2</Label>
<Label Width="50">Slot #2</Label>
<TextBox Width="80" />
<Label>×</Label>
<TextBox Width="80" />
@@ -50,7 +50,7 @@
<Label>Hz</Label>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Width="50">Slot 3</Label>
<Label Width="50">Slot #3</Label>
<TextBox Width="80" />
<Label>×</Label>
<TextBox Width="80" />
@@ -59,7 +59,7 @@
<Label>Hz</Label>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Width="50">Slot 4</Label>
<Label Width="50">Slot #4</Label>
<TextBox Width="80" />
<Label>×</Label>
<TextBox Width="80" />
@@ -68,7 +68,7 @@
<Label>Hz</Label>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Width="50">Slot 5</Label>
<Label Width="50">Slot #5</Label>
<TextBox Width="80" />
<Label>×</Label>
<TextBox Width="80" />
@@ -78,7 +78,9 @@
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="25,0,0,25">
<TextBlock Text="Parent GPU:" FontSize="14" FontWeight="SemiBold" Foreground="White" VerticalAlignment="Center" />
<TextBlock FontSize="14" FontWeight="SemiBold" Foreground="White" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource t_parent_gpu}" />:
</TextBlock>
<ComboBox x:Name="xParentGPU" Width="80" FontSize="14" SelectedIndex="0" BorderBrush="Transparent" Background="Transparent" Margin="10,0,0,0">
<ComboBoxItem Content="Auto" />
<ComboBoxItem Content="AMD" />
@@ -91,11 +93,11 @@
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Image Source="../Resources/admin.png" Width="16" Height="16" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Text="APPLY" Margin="6,0,0,0" />
<TextBlock Grid.Column="1" Text="{DynamicResource t_apply}" Margin="6,0,0,0" />
</Grid>
</local:Button.Children>
</local:Button>
+9 -9
View File
@@ -33,23 +33,23 @@
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem x:Name="xResolution" Click="ChangeResolution" Header="Resolution" IsEnabled="{Binding Active}">
<MenuItem x:Name="xResolution" Click="ChangeResolution" Header="{DynamicResource t_resolution}" IsEnabled="{Binding Active}">
<MenuItem Header="3260 x 2140" />
<MenuItem Header="1920 x 1080" />
<MenuItem Header="1280 x 720" />
</MenuItem>
<MenuItem x:Name="xRefreshRate" Click="ChangeRefreshRate" Header="Refresh rate" IsEnabled="{Binding Active}">
<MenuItem x:Name="xRefreshRate" Click="ChangeRefreshRate" Header="{DynamicResource t_refresh_rate}" IsEnabled="{Binding Active}">
<MenuItem Header="60 Hz" />
</MenuItem>
<MenuItem x:Name="xOrientation" Click="ChangeOrientation" Header="Orientation" IsEnabled="{Binding Active}">
<MenuItem Header="Landscape" />
<MenuItem Header="Portrait" />
<MenuItem Header="Landscape (flipped)" />
<MenuItem Header="Portrait (flipped)" />
<MenuItem x:Name="xOrientation" Click="ChangeOrientation" Header="{DynamicResource t_orientation}" IsEnabled="{Binding Active}">
<MenuItem Header="{DynamicResource t_landscape}" />
<MenuItem Header="{DynamicResource t_portrait}" />
<MenuItem Header="{DynamicResource t_landscape_flipped}" />
<MenuItem Header="{DynamicResource t_portrait_flipped}" />
</MenuItem>
<MenuItem Click="TakeScreenshot" Header="Take Screenshot" IsEnabled="{Binding Active}" />
<MenuItem Click="TakeScreenshot" Header="{DynamicResource t_take_screenshot}" IsEnabled="{Binding Active}" />
<Separator />
<MenuItem Click="RemoveDisplay" Header="Remove" />
<MenuItem Click="RemoveDisplay" Header="{DynamicResource t_remove}" />
</ContextMenu>
</UserControl.ContextMenu>
</UserControl>
+1
View File
@@ -27,6 +27,7 @@ namespace ParsecVDisplay.Components
xRefreshRate.Items.Clear();
DataContext = this;
ContextMenu.Resources = App.Current.Resources;
}
internal DisplayItem(Display display) : this()
+17
View File
@@ -9,6 +9,12 @@ namespace ParsecVDisplay
static string REG_PATH => $"HKEY_CURRENT_USER\\SOFTWARE\\{App.NAME}";
static string REG_STARTUP_PATH => @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
public static string Language
{
get => GetString(nameof(Language), "English");
set => SetString(nameof(Language), value);
}
public static int DisplayCount
{
get => GetInt(nameof(DisplayCount));
@@ -36,6 +42,17 @@ namespace ParsecVDisplay
}
}
static string GetString(string key, string @default)
{
var value = Registry.GetValue(REG_PATH, key, null);
return value == null ? @default : Convert.ToString(value);
}
static void SetString(string key, string value)
{
Registry.SetValue(REG_PATH, key, value, RegistryValueKind.String);
}
static int GetInt(string key, int @default = 0)
{
var value = Registry.GetValue(REG_PATH, key, null);
+35
View File
@@ -0,0 +1,35 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
>
<system:String x:Key="lang_name">English</system:String>
<!--Interface-->
<system:String x:Key="t_add_display">Add display</system:String>
<system:String x:Key="t_hint_add_display_first">Add a virtual display first!</system:String>
<system:String x:Key="t_custom">Custom...</system:String>
<system:String x:Key="t_apply">Apply</system:String>
<system:String x:Key="t_parent_gpu">Parent GPU</system:String>
<system:String x:Key="t_driver_status">Driver status</system:String>
<system:String x:Key="t_language">Language</system:String>
<!--Context menu-->
<system:String x:Key="t_resolution">Resolution</system:String>
<system:String x:Key="t_refresh_rate">Refresh rate</system:String>
<system:String x:Key="t_orientation">Orientation</system:String>
<system:String x:Key="t_landscape">Landscape</system:String>
<system:String x:Key="t_portrait">Portrait</system:String>
<system:String x:Key="t_landscape_flipped">Landscape (flipped)</system:String>
<system:String x:Key="t_portrait_flipped">Portrait (flipped)</system:String>
<system:String x:Key="t_take_screenshot">Take screenshot</system:String>
<system:String x:Key="t_remove">Remove</system:String>
<!--Tray menu-->
<system:String x:Key="t_remove_last_display">Remove last display</system:String>
<system:String x:Key="t_run_on_startup">Run on startup</system:String>
<system:String x:Key="t_fallback_display">Fallback display</system:String>
<system:String x:Key="t_keep_screen_on">Keep screen on</system:String>
<system:String x:Key="t_check_for_update">Check for update</system:String>
<system:String x:Key="t_exit">Exit</system:String>
</ResourceDictionary>
+39
View File
@@ -0,0 +1,39 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
>
<system:String x:Key="lang_name">Tiếng Việt</system:String>
<!--Interface-->
<system:String x:Key="t_add_display">Cắm màn hình</system:String>
<system:String x:Key="t_hint_add_display_first">Hãy cắm màn hình ảo!</system:String>
<system:String x:Key="t_custom">Tùy chỉnh...</system:String>
<system:String x:Key="t_apply">Áp dụng</system:String>
<system:String x:Key="t_parent_gpu">GPU nguồn</system:String>
<system:String x:Key="t_driver_status">Trạng thái driver</system:String>
<system:String x:Key="t_language">Ngôn ngữ</system:String>
<!--Context menu-->
<system:String x:Key="t_resolution">Độ phân giải</system:String>
<system:String x:Key="t_refresh_rate">Tần số quét</system:String>
<system:String x:Key="t_orientation">Hướng màn hình</system:String>
<system:String x:Key="t_landscape">Ngang</system:String>
<system:String x:Key="t_portrait">Dọc</system:String>
<system:String x:Key="t_landscape_flipped">Ngang (lật ngược)</system:String>
<system:String x:Key="t_portrait_flipped">Dọc (lật ngược)</system:String>
<system:String x:Key="t_take_screenshot">Chụp màn hình</system:String>
<system:String x:Key="t_remove">Tháo bỏ</system:String>
<!--Tray menu-->
<system:String x:Key="t_remove_last_display">Bỏ màn hình cuối</system:String>
<system:String x:Key="t_run_on_startup">Chạy cùng hệ thống</system:String>
<system:String x:Key="t_fallback_display">Màn hình dự phòng</system:String>
<system:String x:Key="t_keep_screen_on">Luôn sáng màn hình</system:String>
<system:String x:Key="t_check_for_update">Kiểm tra cập nhật</system:String>
<system:String x:Key="t_exit">Thoát</system:String>
<!--Dialog messages-->
<system:String x:Key="t_msg_">Thoát</system:String>
</ResourceDictionary>
+34 -10
View File
@@ -23,7 +23,7 @@
<ImageBrush ImageSource="Resources/background.png"/>
</Grid.Background>
<Label x:Name="xNoDisplay" Content="Add a virtual display first!" Foreground="#AAA" FontSize="16"
<Label x:Name="xNoDisplay" Content="{DynamicResource t_hint_add_display_first}" Foreground="#AAA" FontSize="16"
HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,183,0,0" />
<WrapPanel x:Name="xDisplays" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20,0,20,52">
@@ -43,8 +43,16 @@
</components:Button.Children>
</components:Button>
<components:Button Click="OpenCustom" Children="CUSTOM..." FontSize="14" Margin="10,0,0,0" Width="100" />
<components:Button x:Name="xAdd" Click="AddDisplay" Children="ADD DISPLAY" FontSize="14" Margin="25,0,0,0" Width="120" />
<components:Button Click="OpenCustom" FontSize="14" Margin="10,0,0,0" Width="100">
<components:Button.Children>
<TextBlock Text="{DynamicResource t_custom}" />
</components:Button.Children>
</components:Button>
<components:Button x:Name="xAdd" Click="AddDisplay" FontSize="14" Margin="25,0,0,0" Width="120">
<components:Button.Children>
<TextBlock Text="{DynamicResource t_add_display}" />
</components:Button.Children>
</components:Button>
</StackPanel>
<StackPanel Margin="25,0,0,20" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="230">
@@ -78,6 +86,22 @@
</Label.Style>
</Label>
</StackPanel>
<TextBlock FontSize="14" Margin="5" Cursor="Hand" MouseDown="TextBlock_MouseDown" MouseUp="ChangeLanguage">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="#66b3ff" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
<TextBlock.ContextMenu>
<ContextMenu x:Name="xLanguageMenu" />
</TextBlock.ContextMenu>
<TextBlock Text="{DynamicResource t_language}" />: <TextBlock Text="{DynamicResource lang_name}" />
</TextBlock>
</StackPanel>
<Frame Visibility="Hidden" NavigationUIVisibility="Hidden" Background="#C222" x:Name="xFrame" />
@@ -92,16 +116,16 @@
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Add display" Click="AddDisplay" />
<MenuItem Header="Remove last display" Click="RemoveLastDisplay" />
<MenuItem Header="{DynamicResource t_add_display}" Click="AddDisplay" />
<MenuItem Header="{DynamicResource t_remove_last_display}" Click="RemoveLastDisplay" />
<Separator />
<MenuItem Header="Run on startup" IsCheckable="True" IsChecked="{Binding Path=(local:Config.RunOnStartup), Mode=TwoWay}" />
<MenuItem Header="Fallback display" IsCheckable="True" IsChecked="{Binding Path=(local:Config.FallbackDisplay), Mode=TwoWay}" />
<MenuItem Header="Keep screen on" IsCheckable="True" IsChecked="{Binding Path=(local:Config.KeepScreenOn), Mode=TwoWay}" />
<MenuItem Header="{DynamicResource t_run_on_startup}" IsCheckable="True" IsChecked="{Binding Path=(local:Config.RunOnStartup), Mode=TwoWay}" />
<MenuItem Header="{DynamicResource t_fallback_display}" IsCheckable="True" IsChecked="{Binding Path=(local:Config.FallbackDisplay), Mode=TwoWay}" />
<MenuItem Header="{DynamicResource t_keep_screen_on}" IsCheckable="True" IsChecked="{Binding Path=(local:Config.KeepScreenOn), Mode=TwoWay}" />
<Separator />
<MenuItem Header="Check for update" x:Name="xMICheckUpdate" Click="CheckUpdate" />
<MenuItem Header="{DynamicResource t_check_for_update}" x:Name="xMICheckUpdate" Click="CheckUpdate" />
<Separator />
<MenuItem Header="Exit" Click="ExitApp" />
<MenuItem Header="{DynamicResource t_exit}" Click="ExitApp" />
</ContextMenu>
</Window.ContextMenu>
</Window>
+37
View File
@@ -11,6 +11,8 @@ namespace ParsecVDisplay
{
public partial class MainWindow : Window
{
public static bool IsMenuOpen;
public MainWindow()
{
InitializeComponent();
@@ -64,7 +66,31 @@ namespace ParsecVDisplay
if (App.Silent)
Hide();
var defaultLang = Config.Language;
foreach (var item in App.Languages)
{
var mi = new MenuItem
{
Header = item,
IsCheckable = true,
IsChecked = item == defaultLang
};
mi.Click += delegate
{
foreach (MenuItem item2 in xLanguageMenu.Items)
item2.IsChecked = false;
mi.IsChecked = true;
App.SetLanguage(mi.Header.ToString());
};
xLanguageMenu.Items.Add(mi);
}
ContextMenu.DataContext = this;
ContextMenu.Resources = App.Current.Resources;
Tray.Init(this, ContextMenu);
ContextMenu = null;
@@ -199,5 +225,16 @@ namespace ParsecVDisplay
xMICheckUpdate.Header = oldText;
xMICheckUpdate.IsEnabled = true;
}
private void ChangeLanguage(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
(sender as TextBlock).ContextMenu.IsOpen = true;
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
}
}
}
+6 -16
View File
@@ -103,6 +103,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Languages\*.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
@@ -144,22 +148,8 @@
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\background.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\github.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\settings.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\sync.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\admin.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\icon.ico" />
<Resource Include="Resources\*.png" />
<Resource Include="Resources\*.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>