translation using NGettext.Wpf

This commit is contained in:
stax76
2023-12-08 06:23:17 +01:00
parent 8997a2eacb
commit 5dd3716012
73 changed files with 3001 additions and 17699 deletions

View File

@@ -165,11 +165,16 @@ public class GuiCommand
proc.WaitForExit();
if (proc.ExitCode == 0)
Msg.ShowInfo("File associations were successfully " +
(perceivedType == "unreg" ? "removed" : "created") +
".\n\nFile Explorer icons will refresh after process restart.");
{
string msgRestart = _("File Explorer icons will refresh after process restart.");
if (perceivedType == "unreg")
Msg.ShowInfo(_("File associations were successfully removed.") + BR2 + msgRestart);
else
Msg.ShowInfo(_("File associations were successfully created.") + BR2 + msgRestart);
}
else
Msg.ShowError("Error creating file associations.");
Msg.ShowError(_("Error creating file associations."));
}
catch { }
}

View File

@@ -21,6 +21,7 @@
<ItemGroup>
<ProjectReference Include="..\MpvNet\MpvNet.csproj" />
<ProjectReference Include="..\NGettext.Wpf\NGettext.Wpf.csproj" />
</ItemGroup>
<ItemGroup>
@@ -32,8 +33,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,5 @@

using System.Globalization;
using System.Windows.Forms;
using System.Threading;
@@ -6,6 +7,7 @@ using MpvNet.Windows.Native;
using MpvNet.Help;
using MpvNet.Windows.UI;
using MpvNet.Windows.Help;
using MpvNet.Windows.WPF;
namespace MpvNet.Windows;
@@ -17,6 +19,7 @@ static class Program
try
{
RegistryHelp.ProductName = AppInfo.Product;
Translator.Current = new WpfTranslator();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

View File

@@ -518,6 +518,16 @@ file = mpv
directory = Input
help = Number of key presses to generate per second on autorepeat.
name = language
file = mpvnet
default = system
directory = UI
help = User interface display language.\nmpv.net must be restarted after a change.
option = system
option = english
option = chinese-china
option = german
name = dark-mode
file = mpvnet
default = always

View File

@@ -762,7 +762,7 @@ namespace HandyControl.Tools.Interop
this.DataStream = dataStream ?? throw new ArgumentNullException(nameof(dataStream));
}
private void ActualizeVirtualPosition()
void ActualizeVirtualPosition()
{
if (_virtualPosition == -1) return;

View File

@@ -58,7 +58,7 @@ public partial class InputWindow : Window
else
return item.Input.ToLower().Contains(searchText);
}
else if (searchText.StartsWith("m ") || searchText.StartsWith("m:"))
else if (searchText.StartsWith("n ") || searchText.StartsWith("n:"))
return item.Comment.ToLower().Contains(searchText.Substring(2).Trim());
else if (searchText.StartsWith("c ") || searchText.StartsWith("c:"))
return item.Command.ToLower().Contains(searchText.Substring(2).Trim());
@@ -93,22 +93,7 @@ public partial class InputWindow : Window
}
}
void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
CollectionView.Refresh();
if (SearchControl.SearchTextBox.Text == "?")
{
SearchControl.SearchTextBox.Text = "";
Msg.ShowInfo("Filtering" + BR2 +
"Reduce the filter scope with:" + BR2 +
"i input" + BR2 +
"m menu" + BR2 +
"c command" + BR2 +
"If only one character is entered input search is performed.");
}
}
void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e) => CollectionView.Refresh();
void Window_Loaded(object sender, RoutedEventArgs e) => Keyboard.Focus(SearchControl.SearchTextBox);
@@ -132,7 +117,7 @@ public partial class InputWindow : Window
File.WriteAllText(App.InputConf.Path, App.InputConf.Content = newContent);
}
Msg.ShowInfo("Changes will be available on next startup.");
Msg.ShowInfo(_("Changes will be available on next startup."));
}
void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)

View File

@@ -350,7 +350,7 @@ public partial class MessageBoxEx : Window, INotifyPropertyChanged
}
}
private void InitBottom(MessageBoxImage image)
void InitBottom(MessageBoxImage image)
{
MessageBackground = (MessageBackground == null) ? new SolidColorBrush(Colors.White) : MessageBackground;
MessageForeground = (MessageForeground == null) ? new SolidColorBrush(Colors.Black) : MessageForeground;
@@ -498,7 +498,7 @@ public partial class MessageBoxEx : Window, INotifyPropertyChanged
}
}
private void FindDefaultButtonEx(MessageBoxButtonDefault buttonDefault)
void FindDefaultButtonEx(MessageBoxButtonDefault buttonDefault)
{
// determine default button
IsDefaultOK = false;
@@ -634,7 +634,7 @@ public partial class MessageBoxEx : Window, INotifyPropertyChanged
}
}
private void FindDefaultButton(MessageBoxButtonDefault buttonDefault)
void FindDefaultButton(MessageBoxButtonDefault buttonDefault)
{
// determine default button
IsDefaultOK = false;
@@ -735,7 +735,7 @@ public partial class MessageBoxEx : Window, INotifyPropertyChanged
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnOK_Click(object sender, RoutedEventArgs e)
void BtnOK_Click(object sender, RoutedEventArgs e)
{
this.MessageResult = MessageBoxResult.OK;
this.MessageResultEx = MessageBoxResultEx.OK;
@@ -747,7 +747,7 @@ public partial class MessageBoxEx : Window, INotifyPropertyChanged
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnYes_Click(object sender, RoutedEventArgs e)
void BtnYes_Click(object sender, RoutedEventArgs e)
{
this.MessageResult = MessageBoxResult.Yes;
this.MessageResultEx = MessageBoxResultEx.Yes;
@@ -759,28 +759,28 @@ public partial class MessageBoxEx : Window, INotifyPropertyChanged
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnNo_Click(object sender, RoutedEventArgs e)
void BtnNo_Click(object sender, RoutedEventArgs e)
{
this.MessageResult = MessageBoxResult.No;
this.MessageResultEx = MessageBoxResultEx.No;
this.DialogResult = true;
}
private void BtnAbort_Click(object sender, RoutedEventArgs e)
void BtnAbort_Click(object sender, RoutedEventArgs e)
{
this.MessageResult = MessageBoxResult.None;
this.MessageResultEx = MessageBoxResultEx.Abort;
this.DialogResult = true;
}
private void BtnRetry_Click(object sender, RoutedEventArgs e)
void BtnRetry_Click(object sender, RoutedEventArgs e)
{
this.MessageResult = MessageBoxResult.None;
this.MessageResultEx = MessageBoxResultEx.Retry;
this.DialogResult = true;
}
private void BtnIgnore_Click(object sender, RoutedEventArgs e)
void BtnIgnore_Click(object sender, RoutedEventArgs e)
{
this.MessageResult = MessageBoxResult.None;
this.MessageResultEx = MessageBoxResultEx.Ignore;
@@ -792,7 +792,7 @@ public partial class MessageBoxEx : Window, INotifyPropertyChanged
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnCancel_Click(object sender, RoutedEventArgs e)
void BtnCancel_Click(object sender, RoutedEventArgs e)
{
this.MessageResult = MessageBoxResult.Cancel;
this.MessageResultEx = MessageBoxResultEx.Cancel;

View File

@@ -0,0 +1,63 @@

using NGettext.Wpf;
using System.Globalization;
namespace MpvNet.Windows.WPF;
public class WpfTranslator : ITranslator
{
static Language[] Languages { get; } = new Language[] {
new("english", "en", "en"),
new("chinese-china", "zh-Hans", "zh"), // Chinese (Simplified)
new("german", "de", "de"),
};
public string Gettext(string msgId)
{
InitNGettextWpf();
return Translation._(msgId);
}
void InitNGettextWpf()
{
if (Translation.Localizer == null)
CompositionRoot.Compose("mpvnet", GetCulture(App.Language), Folder.Startup + "Locale");
}
string GetSystemLanguage()
{
string twoLetterName = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
if (twoLetterName == "zh")
return "chinese-china"; // Chinese (Simplified)
return new CultureInfo(twoLetterName).EnglishName.ToLowerInvariant();
}
CultureInfo GetCulture(string name)
{
if (name == "system")
name = GetSystemLanguage();
foreach (Language lang in Languages)
if (lang.MpvNetName == name)
return new CultureInfo(lang.CultureInfoName);
throw new Exception($"Unknown language {name}.");
}
class Language
{
public string MpvNetName { get; }
public string CultureInfoName { get; }
public string TwoLetterName { get; }
public Language(string mpvNetName, string cultureInfoName, string twoLetterName)
{
MpvNetName = mpvNetName;
CultureInfoName = cultureInfoName;
TwoLetterName = twoLetterName;
}
}
}

View File

@@ -27,7 +27,7 @@ partial class MainForm
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));

View File

@@ -286,7 +286,7 @@ public partial class MainForm : Form
lock (Player.MediaTracksLock)
{
var trackMenuItem = FindMenuItem("Track");
var trackMenuItem = FindMenuItem(_("Track"));
if (trackMenuItem != null)
{
@@ -348,7 +348,7 @@ public partial class MainForm : Form
}
}
var chaptersMenuItem = FindMenuItem("Chapters");
var chaptersMenuItem = FindMenuItem(_("Chapter"));
if (chaptersMenuItem != null)
{
@@ -369,7 +369,7 @@ public partial class MainForm : Form
}
}
var recentMenuItem = FindMenuItem("Recent");
var recentMenuItem = FindMenuItem(_("Recent Files"));
if (recentMenuItem != null)
{
@@ -391,7 +391,7 @@ public partial class MainForm : Form
recentMenuItem.Items.Add(clearMenuItem);
}
var titlesMenuItem = FindMenuItem("Titles");
var titlesMenuItem = FindMenuItem(_("Title"));
if (titlesMenuItem != null)
{
@@ -424,7 +424,7 @@ public partial class MainForm : Form
}
}
var profilesMenuItem = FindMenuItem("Profile");
var profilesMenuItem = FindMenuItem(_("Profile"));
if (profilesMenuItem != null && !profilesMenuItem.HasItems)
{
@@ -446,7 +446,7 @@ public partial class MainForm : Form
}
}
var customMenuItem = FindMenuItem("Custom");
var customMenuItem = FindMenuItem(_("Custom"));
if (customMenuItem != null)
{
@@ -770,16 +770,24 @@ public partial class MainForm : Form
foreach (Binding binding in menuBindings)
{
Binding tempBinding = binding;
if (MenuItemDuplicate.ContainsKey(tempBinding.Command) && tempBinding.Input != "")
{
var mi = MenuItemDuplicate[tempBinding.Command];
mi.InputGestureText = mi.InputGestureText + ", " + tempBinding.Input;
}
if (!binding.IsMenu)
continue;
Binding tempBinding = binding;
var menuItem = MenuHelp.Add(ContextMenu.Items, tempBinding.Comment);
var menuItem = MenuHelp.Add(ContextMenu.Items, tempBinding.Comment);
if (menuItem != null)
{
MenuItemDuplicate[tempBinding.Comment] = menuItem;
if (tempBinding.Input != "")
MenuItemDuplicate[tempBinding.Command] = menuItem;
menuItem.Click += (sender, args) => {
try {
TaskHelp.Run(() => {
@@ -1286,7 +1294,7 @@ public partial class MainForm : Form
Player.CommandV("quit");
if (!Player.ShutdownAutoResetEvent.WaitOne(10000))
Msg.ShowError("Shutdown thread failed to complete within 10 seconds.");
Msg.ShowError(_("Shutdown thread failed to complete within 10 seconds."));
Player.Destroy();
}

View File

@@ -4,6 +4,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
VisualStudioVersion = 17.4.33213.308
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MpvNet.Windows", "MpvNet.Windows\MpvNet.Windows.csproj", "{ADC341B5-863A-4DFB-9352-475518FABE91}"
ProjectSection(ProjectDependencies) = postProject
{0B7958FD-2138-482A-A21B-481AE7A0F851} = {0B7958FD-2138-482A-A21B-481AE7A0F851}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MpvNet", "MpvNet\MpvNet.csproj", "{0B524801-DA28-433F-808D-3F74EF81EB53}"
EndProject
@@ -12,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NGettext.Wpf", "NGettext.Wpf\NGettext.Wpf.csproj", "{0B7958FD-2138-482A-A21B-481AE7A0F851}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -26,6 +31,10 @@ Global
{0B524801-DA28-433F-808D-3F74EF81EB53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B524801-DA28-433F-808D-3F74EF81EB53}.Release|Any CPU.Build.0 = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B7958FD-2138-482A-A21B-481AE7A0F851}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,9 +1,10 @@

using CommunityToolkit.Mvvm.Messaging;
using Microsoft.VisualBasic;
using MpvNet.ExtensionMethod;
using MpvNet.Help;
using MpvNet.MVVM;
using System.Runtime.CompilerServices;
namespace MpvNet;
@@ -17,6 +18,7 @@ public class AppClass
public string DarkTheme { get; set; } = "dark";
public string LightTheme { get; set; } = "light";
public string StartSize { get; set; } = "height-session";
public string Language { get; set; } = "system";
public bool AutoLoadFolder { get; set; } = true;
public bool DebugMode { get; set; }
@@ -139,6 +141,7 @@ public class AppClass
case "dark-theme": DarkTheme = value.Trim('\'', '"'); return true;
case "debug-mode": DebugMode = value == "yes"; return true;
case "image-file-extensions": FileTypes.Image = value.Split(" ,;".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); return true;
case "language": Language = value; return true;
case "light-theme": LightTheme = value.Trim('\'', '"'); return true;
case "media-info": MediaInfo = value == "yes"; return true;
case "minimum-aspect-ratio-audio": MinimumAspectRatioAudio = value.ToFloat(); return true;

View File

@@ -8,4 +8,6 @@ public static class Global
public static readonly MainPlayer Player = new MainPlayer();
public static readonly MainPlayer Core = Player; // deprecated
public static readonly AppClass App = new AppClass();
public static string _(string value) => Translator.Current!.Gettext(value);
}

View File

@@ -13,12 +13,13 @@ public static class InputHelp
new (_("File"), _("Open URL or file from clipboard"), "script-message-to mpvnet open-clipboard", "Ctrl+v"),
new (_("File"), _("Open DVD/Blu-ray Drive/Folder..."), "script-message-to mpvnet open-optical-media"),
new (_("File"), "-"),
new (_("File"), _("Load external audio files..."), "script-message-to mpvnet load-audio", "Alt+a"),
new (_("File"), _("Load external subtitle files..."), "script-message-to mpvnet load-sub", "Alt+s"),
new (_("File"), _("Add external audio files..."), "script-message-to mpvnet load-audio", "Alt+a"),
new (_("File"), _("Add external subtitle files..."), "script-message-to mpvnet load-sub", "Alt+s"),
new (_("File"), "-"),
new (_("File"), _("Add files to playlist..."), "script-message-to mpvnet open-files append"),
new (_("File"), _("Recent Files")),
new (_("File"), "-"),
new (_("File"), _("Recent")),
new (_("File"), _("Exit"), "quit", "Esc"),
new (_("Playback"), _("Play/Pause"), "script-message-to mpvnet play-pause", "Space"),
new (_("Playback"), _("Stop"), "stop", "Ctrl+s"),
new (_("Navigate"), _("Previous File"), "playlist-prev", "F11"),
@@ -39,8 +40,8 @@ public static class InputHelp
new (_("Navigate"), _("Jump 5 min forward"), "seek 300", "Ctrl+Right"),
new (_("Navigate"), _("Jump 5 min backward"), "seek -300", "Ctrl+Left"),
new (_("Navigate"), "-"),
new (_("Navigate"), _("Titles")),
new (_("Navigate"), _("Chapters")),
new (_("Navigate"), _("Title")),
new (_("Navigate"), _("Chapter")),
new (_("Pan & Scan"), _("Decrease Size"), "add video-zoom -0.1", "Ctrl+-"),
new (_("Pan & Scan"), _("Increase Size"), "add video-zoom 0.1", "Ctrl++"),
new (_("Pan & Scan"), "-"),
@@ -69,13 +70,13 @@ public static class InputHelp
new (_("Video"), _("Take Screenshot"), "async screenshot", "s"),
new (_("Video"), _("Take Screenshot without subtitles"), "async screenshot video", "S"),
new (_("Video"), _("Toggle Deinterlace"), "cycle deinterlace", "d"),
new (_("Video"), _("Cycle Aspect Ratio"), "cycle-values video-aspect-override 16:9 4:3 2.35:1 -1", "a"),
new (_("Video"), _("Change Aspect Ratio"), "cycle-values video-aspect-override 16:9 4:3 2.35:1 -1", "a"),
new (_("Video"), _("Rotate Video"), "cycle-values video-rotate 90 180 270 0", "Ctrl+r"),
new (_("Audio"), _("Cycle/Next"), "cycle audio", "KP7"),
new (_("Audio"), _("Next Track"), "cycle audio", "KP7"),
new (_("Audio"), "-"),
new (_("Audio"), _("Delay +0.1"), "add audio-delay 0.1", "Ctrl+d"),
new (_("Audio"), _("Delay -0.1"), "add audio-delay -0.1", "Ctrl+D"),
new (_("Subtitle"), _("Cycle/Next"), "cycle sub", "KP8"),
new (_("Subtitle"), _("Next Track"), "cycle sub", "KP8"),
new (_("Subtitle"), _("Toggle Visibility"), "cycle sub-visibility", "v"),
new (_("Subtitle"), "-"),
new (_("Subtitle"), _("Delay -0.1"), "add sub-delay -0.1", "z"),
@@ -84,10 +85,10 @@ public static class InputHelp
new (_("Subtitle"), _("Move Up"), "add sub-pos -1", "r"),
new (_("Subtitle"), _("Move Down"), "add sub-pos 1", "R"),
new (_("Subtitle"), "-"),
new (_("Subtitle"), _("Decrease Subtitle Font Size"), "add sub-scale -0.1", "F"),
new (_("Subtitle"), _("Increase Subtitle Font Size"), "add sub-scale 0.1", "G"),
new (_("Subtitle"), _("Decrease Font Size"), "add sub-scale -0.1", "F"),
new (_("Subtitle"), _("Increase Font Size"), "add sub-scale 0.1", "G"),
new (_("Subtitle"), "-"),
new (_("Subtitle > Advanced"), _("Toggle overriding SSA/ASS styles with normal styles"), "cycle-values sub-ass-override force no", "u"),
new (_("Subtitle") + " > " + _("More"), _("Toggle overriding SSA/ASS styles with normal styles"), "cycle-values sub-ass-override force no", "u"),
new ("", _("Track")),
new (_("Volume"), _("Up"), "add volume 2", "+"),
new (_("Volume"), _("Down"), "add volume -2", "-"),
@@ -100,46 +101,45 @@ public static class InputHelp
new (_("Speed"), _("Double"), "multiply speed 2.0", "}"),
new (_("Speed"), "-"),
new (_("Speed"), _("Reset"), "set speed 1", "BS"),
new (_("View"), _("Fullscreen"), "cycle fullscreen", "Enter"),
new (_("View > Zoom"), _("Enlarge"), "script-message-to mpvnet scale-window 1.2", "Alt++"),
new (_("View > Zoom"), _("Shrink"), "script-message-to mpvnet scale-window 0.8", "Alt+-"),
new (_("View > Zoom"), "-"),
new (_("View > Zoom"), _("50 %"), "script-message-to mpvnet window-scale 0.5", "Alt+0"),
new (_("View > Zoom"), _("100 %"), "script-message-to mpvnet window-scale 1.0", "Alt+1"),
new (_("View > Zoom"), _("200 %"), "script-message-to mpvnet window-scale 2.0", "Alt+2"),
new (_("View > Zoom"), _("300 %"), "script-message-to mpvnet window-scale 3.0", "Alt+3"),
new (_("View > Move"), _("Left"), "script-message-to mpvnet move-window left", "Alt+Left"),
new (_("View > Move"), _("Right"), "script-message-to mpvnet move-window right", "Alt+Right"),
new (_("View > Move"), _("Top"), "script-message-to mpvnet move-window top", "Alt+Up"),
new (_("View > Move"), _("Bottom"), "script-message-to mpvnet move-window bottom", "Alt+Down"),
new (_("View > Move"), _("Center"), "script-message-to mpvnet move-window center", "Alt+BS"),
new (_("View"), _("Show Profiles"), "script-message-to mpvnet show-profiles", "Ctrl+P"),
new (_("View"), _("Toggle Border"), "cycle border", "b"),
new (_("View"), _("Toggle On Top"), "cycle ontop", "Ctrl+t"),
new (_("View"), _("Toggle Statistics"), "script-binding stats/display-stats-toggle", "t"),
new (_("View"), _("Toggle OSC Visibility"), "script-binding osc/visibility", "Del"),
new (_("View"), _("Show Media Info On-Screen"), "script-message-to mpvnet show-media-info osd", "i"),
new (_("View"), _("Show Media Info Message Box"), "script-message-to mpvnet show-media-info msgbox", "Ctrl+m"),
new (_("View"), _("Show Progress"), "show-progress", "p"),
new (_("View > Advanced"), _("Show Console"), "script-binding console/enable", "`"),
new (_("View > Advanced"), _("Show Audio Devices"), "script-message-to mpvnet show-audio-devices"),
new (_("View > Advanced"), _("Show Commands"), "script-message-to mpvnet show-commands", "C"),
new (_("View > Advanced"), _("Show Demuxers"), "script-message-to mpvnet show-demuxers"),
new (_("View > Advanced"), _("Show Decoders"), "script-message-to mpvnet show-decoders"),
new (_("View > Advanced"), _("Show Bindings"), "script-message-to mpvnet show-bindings"),
new (_("View") + " > " + _("More"), _("Show Console"), "script-binding console/enable", "`"),
new (_("View") + " > " + _("More"), _("Show Audio Devices"), "script-message-to mpvnet show-audio-devices"),
new (_("View") + " > " + _("More"), _("Show Commands"), "script-message-to mpvnet show-commands", "C"),
new (_("View") + " > " + _("More"), _("Show Demuxers"), "script-message-to mpvnet show-demuxers"),
new (_("View") + " > " + _("More"), _("Show Decoders"), "script-message-to mpvnet show-decoders"),
new (_("View") + " > " + _("More"), _("Show Bindings"), "script-message-to mpvnet show-bindings"),
new (_("Window"), _("Fullscreen"), "cycle fullscreen", "Enter"),
new (_("Window") + " > " + _("Zoom"), _("Enlarge"), "script-message-to mpvnet scale-window 1.2", "Alt++"),
new (_("Window") + " > " + _("Zoom"), _("Shrink"), "script-message-to mpvnet scale-window 0.8", "Alt+-"),
new (_("Window") + " > " + _("Zoom"), "-"),
new (_("Window") + " > " + _("Zoom"), _("50 %"), "script-message-to mpvnet window-scale 0.5", "Alt+0"),
new (_("Window") + " > " + _("Zoom"), _("100 %"), "script-message-to mpvnet window-scale 1.0", "Alt+1"),
new (_("Window") + " > " + _("Zoom"), _("200 %"), "script-message-to mpvnet window-scale 2.0", "Alt+2"),
new (_("Window") + " > " + _("Zoom"), _("300 %"), "script-message-to mpvnet window-scale 3.0", "Alt+3"),
new (_("Window") + " > " + _("Move"), _("Left"), "script-message-to mpvnet move-window left", "Alt+Left"),
new (_("Window") + " > " + _("Move"), _("Right"), "script-message-to mpvnet move-window right", "Alt+Right"),
new (_("Window") + " > " + _("Move"), _("Up"), "script-message-to mpvnet move-window top", "Alt+Up"),
new (_("Window") + " > " + _("Move"), _("Down"), "script-message-to mpvnet move-window bottom", "Alt+Down"),
new (_("Window") + " > " + _("Move"), _("Center"), "script-message-to mpvnet move-window center", "Alt+BS"),
new (_("Window"), _("Toggle Border"), "cycle border", "b"),
new (_("Window"), _("Toggle On Top"), "cycle ontop", "Ctrl+t"),
new ("", _("Profile")),
new (_("Settings"), _("Show Config Editor"), "script-message-to mpvnet show-conf-editor", "Ctrl+,"),
new (_("Settings"), _("Show Input Editor"), "script-message-to mpvnet show-input-editor", "Ctrl+i"),
new (_("Settings"), _("Open Config Folder"), "script-message-to mpvnet open-conf-folder", "Ctrl+f"),
new (_("Settings > Setup"), _("Register video file associations"), "script-message-to mpvnet reg-file-assoc video"),
new (_("Settings > Setup"), _("Register audio file associations"), "script-message-to mpvnet reg-file-assoc audio"),
new (_("Settings > Setup"), _("Register image file associations"), "script-message-to mpvnet reg-file-assoc image"),
new (_("Settings > Setup"), _("Unregister file associations"), "script-message-to mpvnet reg-file-assoc unreg"),
new (_("Settings") + " > " + _("Setup"), _("Register video file associations"), "script-message-to mpvnet reg-file-assoc video"),
new (_("Settings") + " > " + _("Setup"), _("Register audio file associations"), "script-message-to mpvnet reg-file-assoc audio"),
new (_("Settings") + " > " + _("Setup"), _("Register image file associations"), "script-message-to mpvnet reg-file-assoc image"),
new (_("Settings") + " > " + _("Setup"), _("Unregister file associations"), "script-message-to mpvnet reg-file-assoc unreg"),
new (_("Tools"), _("Set/clear A-B loop points"), "ab-loop", "l"),
new (_("Tools"), _("Toggle infinite file looping"), "cycle-values loop-file inf no", "L"),
new (_("Tools"), _("Shuffle Playlist"), "playlist-shuffle"),
new (_("Tools"), _("Toggle Hardware Decoding"), "cycle-values hwdec auto no", "Ctrl+h"),
new (_("Tools"), _("Exit"), "quit", "Esc"),
new (_("Tools"), _("Exit Watch Later"), "quit-watch-later", "Q"),
new ("", _("Custom")),
new (_("Help"), _("Website mpv"), "script-message-to mpvnet shell-execute https://mpv.io", "Ctrl+Home"),
@@ -150,7 +150,6 @@ public static class InputHelp
new (_("Help"), "-"),
new (_("Help"), _("awesome-mpv"), "script-message-to mpvnet shell-execute https://github.com/stax76/awesome-mpv", "Ctrl+a"),
new (_("Help"), _("About mpv.net"), "script-message-to mpvnet show-about"),
new ("", _("Exit"), "quit", "Esc"),
new ("", "", "quit", "q", _("Exit")),
new ("", "", "script-message-to mpvnet show-menu", "MBTN_Right", _("Show Menu")),
new ("", "", "quit", "Power", _("Exit")),
@@ -161,9 +160,9 @@ public static class InputHelp
new ("", "", "stop", "Stop", _("Stop")),
new ("", "", "seek 60", "Forward", _("Forward")),
new ("", "", "seek -60", "Rewind", _("Backward")),
new ("", "", "add volume 2", "Wheel_Up", _("Volume Up")),
new ("", "", "add volume 2", "Wheel_Up", _("Volume Up")),
new ("", "", "add volume -2", "Wheel_Down", _("Volume Down")),
new ("", "", "add volume 2", "Wheel_Right", _("Volume Up")),
new ("", "", "add volume 2", "Wheel_Right", _("Volume Up")),
new ("", "", "add volume -2", "Wheel_Left", _("Volume Down")),
new ("", "", "playlist-prev", "Prev", _("Previous File")),
new ("", "", "playlist-next", "Next", _("Next File")),
@@ -174,7 +173,6 @@ public static class InputHelp
new ("", "", "ignore", "MBTN_Left", _("Ignore left mouse butten")),
new ("", "", "cycle fullscreen", "f", _("Fullscreen")),
new ("", "", "cycle fullscreen", "MBTN_Left_DBL", _("Fullscreen")),
new ("", "", "cycle fullscreen", "KP_Enter", _("Fullscreen")),
new ("", "", "no-osd seek 1 exact", "Shift+Right", _("Seek Forward")),
new ("", "", "no-osd seek -1 exact", "Shift+Left", _("Seek Backward")),
new ("", "", "no-osd seek 5 exact", "Shift+Up", _("Seek Forward")),
@@ -432,6 +430,4 @@ public static class InputHelp
}
return bindings;
}
public static string _(string value) => value;
}

View File

@@ -22,7 +22,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="NGettext" Version="0.6.7" />
</ItemGroup>
</Project>

View File

@@ -254,22 +254,25 @@ public class MainPlayer : MpvClient
_Conf = new Dictionary<string, string>();
if (File.Exists(ConfPath))
foreach (var i in File.ReadAllLines(ConfPath))
if (i.Contains('=') && !i.TrimStart().StartsWith("#"))
{
string key = i[..i.IndexOf("=")].Trim();
string value = i[(i.IndexOf("=") + 1)..].Trim();
{
foreach (string? it in File.ReadAllLines(ConfPath))
{
string line = it.TrimStart(' ', '-').TrimEnd();
if (key.StartsWith("-"))
key = key.TrimStart('-');
if (!line.Contains('=') || line.StartsWith("#"))
continue;
if (value.Contains('#') && !value.StartsWith("#") &&
!value.StartsWith("'#") && !value.StartsWith("\"#"))
string key = line[..line.IndexOf("=")].Trim();
string value = line[(line.IndexOf("=") + 1)..].Trim();
value = value[..value.IndexOf("#")].Trim();
if (value.Contains('#') && !value.StartsWith("#") &&
!value.StartsWith("'#") && !value.StartsWith("\"#"))
_Conf[key] = value;
}
value = value[..value.IndexOf("#")].Trim();
_Conf[key] = value;
}
}
foreach (var i in _Conf)
ProcessProperty(i.Key, i.Value);

12
src/MpvNet/Translator.cs Normal file
View File

@@ -0,0 +1,12 @@

namespace MpvNet;
public class Translator
{
public static ITranslator? Current;
}
public interface ITranslator
{
public string Gettext(string msgId);
}

View File

@@ -0,0 +1,32 @@

using System.Globalization;
using System.Windows.Input;
namespace NGettext.Wpf
{
public class ChangeCultureCommand : ICommand
{
public bool CanExecute(object? parameter)
{
return CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Any(cultureInfo => cultureInfo.Name == (string)parameter);
}
public void Execute(object? parameter)
{
if (CultureTracker is null)
{
CompositionRoot.WriteMissingInitializationErrorMessage();
return;
}
CultureTracker.CurrentCulture =
CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Single(cultureInfo => cultureInfo.Name == (string)parameter);
}
public event EventHandler? CanExecuteChanged;
public static ICultureTracker? CultureTracker { get; set; }
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NGettext.Wpf.Common
{
public class GettextStringFormatConverter : IValueConverter
{
public string MsgId { get; private set; }
public GettextStringFormatConverter(string msgId)
{
this.MsgId = msgId;
}
public static ILocalizer Localizer { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Localizer.Gettext(MsgId, value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,29 @@

using System.Globalization;
using NGettext.Wpf.Common;
using NGettext.Wpf.EnumTranslation;
namespace NGettext.Wpf
{
public static class CompositionRoot
{
public static void Compose(string domainName, CultureInfo cultureInfo, string localeFolder)
{
var cultureTracker = new CultureTracker();
cultureTracker.CurrentCulture = cultureInfo;
var localizer = new Localizer(cultureTracker, domainName, localeFolder);
ChangeCultureCommand.CultureTracker = cultureTracker;
GettextExtension.Localizer = localizer;
TrackCurrentCultureBehavior.CultureTracker = cultureTracker;
LocalizeEnumConverter.EnumLocalizer = new EnumLocalizer(localizer);
Translation.Localizer = localizer;
GettextStringFormatConverter.Localizer = localizer;
}
internal static void WriteMissingInitializationErrorMessage()
{
Console.Error.WriteLine("NGettext.Wpf: NGettext.Wpf.CompositionRoot.Compose() must be called at the entry point of the application for localization to work");
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Globalization;
namespace NGettext.Wpf
{
public class CultureEventArgs : EventArgs
{
public CultureEventArgs(CultureInfo cultureInfo)
{
if (cultureInfo == null) throw new ArgumentNullException(nameof(cultureInfo));
CultureInfo = cultureInfo;
}
public CultureInfo CultureInfo { get; }
}
}

View File

@@ -0,0 +1,64 @@

using System.Globalization;
namespace NGettext.Wpf;
public interface ICultureTracker
{
[Obsolete("Use AddWeakCultureObserver() instead. Otherwise the culture tracker (which is probably a singleton) will keep your object alive for longer than it needs to. This method will be removed in 2.x")]
event EventHandler<CultureEventArgs> CultureChanged;
event EventHandler<CultureEventArgs> CultureChanging;
CultureInfo CurrentCulture { get; set; }
void AddWeakCultureObserver(IWeakCultureObserver weakCultureObserver);
}
public class CultureTracker : ICultureTracker
{
private CultureInfo _currentCulture = CultureInfo.CurrentUICulture;
private List<WeakReference<IWeakCultureObserver>> _weakObservers = new List<WeakReference<IWeakCultureObserver>>();
public event EventHandler<CultureEventArgs> CultureChanged;
public CultureInfo CurrentCulture
{
get => _currentCulture;
set
{
if (_currentCulture == value)
return;
CultureChanging?.Invoke(this, new CultureEventArgs(value));
_currentCulture = value;
RaiseCultureChanged();
}
}
protected virtual void RaiseCultureChanged()
{
var cultureEventArgs = new CultureEventArgs(CurrentCulture);
CultureChanged?.Invoke(this, cultureEventArgs);
var weakObserversStillAlive = new List<WeakReference<IWeakCultureObserver>>();
foreach (var weakReference in _weakObservers)
{
if (!weakReference.TryGetTarget(out var observer))
continue;
observer.HandleCultureChanged(this, cultureEventArgs);
weakObserversStillAlive.Add(weakReference);
}
_weakObservers = weakObserversStillAlive;
}
public event EventHandler<CultureEventArgs> CultureChanging;
public void AddWeakCultureObserver(IWeakCultureObserver weakCultureObserver)
{
_weakObservers.Add(new WeakReference<IWeakCultureObserver>(weakCultureObserver));
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Linq;
using System.Reflection;
namespace NGettext.Wpf.EnumTranslation
{
public interface IEnumLocalizer
{
string LocalizeEnum(Enum value);
}
public class EnumLocalizer : IEnumLocalizer
{
private readonly ILocalizer _localizer;
public EnumLocalizer(ILocalizer localizer)
{
_localizer = localizer ?? throw new ArgumentNullException(nameof(localizer));
}
public string LocalizeEnum(Enum value)
{
var type = value.GetType();
var enumMemberName = value.ToString();
var msgIdAttribute = (EnumMsgIdAttribute)type.GetMember(enumMemberName).SingleOrDefault()?.GetCustomAttribute(typeof(EnumMsgIdAttribute), true);
if (msgIdAttribute is null)
{
Console.Error.WriteLine($"{type}.{enumMemberName} lacks the [MsgId(\"...\")] attribute.");
return enumMemberName;
}
return _localizer.Gettext(msgIdAttribute.MsgId);
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace NGettext.Wpf.EnumTranslation
{
public class EnumMsgIdAttribute : Attribute
{
public EnumMsgIdAttribute(string msgId)
{
MsgId = msgId;
}
public string MsgId { get; }
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NGettext.Wpf.EnumTranslation
{
public class LocalizeEnumConverter : IValueConverter
{
private IEnumLocalizer _enumLocalizer;
public LocalizeEnumConverter()
{
}
public LocalizeEnumConverter(IEnumLocalizer enumLocalizer)
{
_enumLocalizer = enumLocalizer;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var enumLocalizer = GetEnumLocalizer();
if (enumLocalizer is null)
{
return value;
}
if (value is Enum enumValue)
{
return enumLocalizer.LocalizeEnum(enumValue);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public static IEnumLocalizer EnumLocalizer { get; set; }
private IEnumLocalizer GetEnumLocalizer()
{
var result = _enumLocalizer ?? EnumLocalizer;
if (result is null)
{
CompositionRoot.WriteMissingInitializationErrorMessage();
}
return result;
}
}
}

View File

@@ -0,0 +1,96 @@

using System.ComponentModel;
using System.Windows;
using System.Windows.Markup;
namespace NGettext.Wpf
{
[MarkupExtensionReturnType(typeof(string))]
public class GettextExtension : MarkupExtension, IWeakCultureObserver
{
private DependencyObject _dependencyObject;
private DependencyProperty _dependencyProperty;
[ConstructorArgument("params")] public object[] Params { get; set; }
[ConstructorArgument("msgId")] public string MsgId { get; set; }
public GettextExtension(string msgId)
{
MsgId = msgId;
Params = new object[] { };
}
public GettextExtension(string msgId, params object[] @params)
{
MsgId = msgId;
Params = @params;
}
public static ILocalizer Localizer { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
if (provideValueTarget.TargetObject is DependencyObject dependencyObject)
{
_dependencyObject = dependencyObject;
if (DesignerProperties.GetIsInDesignMode(_dependencyObject))
{
return Gettext();
}
AttachToCultureChangedEvent();
_dependencyProperty = (DependencyProperty)provideValueTarget.TargetProperty;
KeepGettextExtensionAliveForAsLongAsDependencyObject();
}
else
{
System.Console.WriteLine("NGettext.Wpf: Target object of type {0} is not yet implemented", provideValueTarget.TargetObject?.GetType());
}
return Gettext();
}
private string Gettext()
{
return Params.Any() ? Localizer.Gettext(MsgId, Params) : Localizer.Gettext(MsgId);
}
void KeepGettextExtensionAliveForAsLongAsDependencyObject()
{
SetGettextExtension(_dependencyObject, this);
}
void AttachToCultureChangedEvent()
{
if (Localizer is null)
{
Console.Error.WriteLine("NGettext.WPF.GettextExtension.Localizer not set. Localization is disabled.");
return;
}
Localizer.CultureTracker.AddWeakCultureObserver(this);
}
public void HandleCultureChanged(ICultureTracker sender, CultureEventArgs eventArgs)
{
_dependencyObject.SetValue(_dependencyProperty, Gettext());
}
public static readonly DependencyProperty GettextExtensionProperty = DependencyProperty.RegisterAttached(
"GettextExtension", typeof(GettextExtension), typeof(GettextExtension), new PropertyMetadata(default(GettextExtension)));
public static void SetGettextExtension(DependencyObject element, GettextExtension value)
{
element.SetValue(GettextExtensionProperty, value);
}
public static GettextExtension GetGettextExtension(DependencyObject element)
{
return (GettextExtension)element.GetValue(GettextExtensionProperty);
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Windows.Markup;
using NGettext.Wpf.Common;
namespace NGettext.Wpf
{
public class GettextFormatConverterExtension : MarkupExtension
{
public GettextFormatConverterExtension(string msgId)
{
MsgId = msgId;
}
[ConstructorArgument("msgId")] public string MsgId { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new GettextStringFormatConverter(MsgId);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace NGettext.Wpf
{
public interface IWeakCultureObserver
{
void HandleCultureChanged(ICultureTracker sender, CultureEventArgs eventArgs);
}
}

View File

@@ -0,0 +1,117 @@

using System.Globalization;
using System.IO;
namespace NGettext.Wpf
{
public interface ILocalizer
{
ICatalog Catalog { get; }
ICatalog GetCatalog(CultureInfo cultureInfo);
ICultureTracker CultureTracker { get; }
}
public class Localizer : IDisposable, ILocalizer
{
string _domainName;
string _localeFolder;
public Localizer(ICultureTracker cultureTracker, string domainName, string localeFolder)
{
_domainName = domainName;
_localeFolder = localeFolder;
CultureTracker = cultureTracker;
if (cultureTracker == null)
throw new ArgumentNullException(nameof(cultureTracker));
cultureTracker.CultureChanging += ResetCatalog;
ResetCatalog(cultureTracker.CurrentCulture);
}
void ResetCatalog(object sender, CultureEventArgs e)
{
ResetCatalog(e.CultureInfo);
}
void ResetCatalog(CultureInfo cultureInfo)
{
Catalog = GetCatalog(cultureInfo);
}
public ICatalog GetCatalog(CultureInfo cultureInfo) =>
new Catalog(_domainName, _localeFolder, cultureInfo);
public ICatalog Catalog { get; private set; }
public ICultureTracker CultureTracker { get; }
public void Dispose()
{
CultureTracker.CultureChanging -= ResetCatalog;
}
}
public static class LocalizerExtensions
{
internal struct MsgIdWithContext
{
internal string Context { get; set; }
internal string MsgId { get; set; }
}
internal static MsgIdWithContext ConvertToMsgIdWithContext(string msgId)
{
var result = new MsgIdWithContext { MsgId = msgId };
if (msgId.Contains("|"))
{
var pipePosition = msgId.IndexOf('|');
result.Context = msgId.Substring(0, pipePosition);
result.MsgId = msgId.Substring(pipePosition + 1);
}
return result;
}
internal static string Gettext(this ILocalizer localizer, string msgId, params object[] values)
{
if (msgId == null)
return "";
var msgIdWithContext = ConvertToMsgIdWithContext(msgId);
if (localizer is null)
{
CompositionRoot.WriteMissingInitializationErrorMessage();
return string.Format(msgIdWithContext.MsgId, values);
}
if (msgIdWithContext.Context != null)
{
return localizer.Catalog.GetParticularString(msgIdWithContext.Context, msgIdWithContext.MsgId, values);
}
return localizer.Catalog.GetString(msgIdWithContext.MsgId, values);
}
internal static string? Gettext(this ILocalizer localizer, string msgId)
{
if (msgId is null)
return null;
var msgIdWithContext = ConvertToMsgIdWithContext(msgId);
if (localizer is null)
{
CompositionRoot.WriteMissingInitializationErrorMessage();
return msgIdWithContext.MsgId;
}
if (msgIdWithContext.Context != null)
return localizer.Catalog.GetParticularString(msgIdWithContext.Context, msgIdWithContext.MsgId);
return localizer.Catalog.GetString(msgIdWithContext.MsgId);
}
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
<PackageReference Include="NGettext" Version="0.6.7" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>NGettext.Wpf</id>
<version>1.2.6-alpha</version>
<title>WPF support for NGettext</title>
<authors>Robert Jørgensgaard Engdahl</authors>
<owners>Robert Jørgensgaard Engdahl</owners>
<license type="expression">LGPL-3.0-or-later</license>
<projectUrl>https://github.com/robert-j-engdahl/ngettext-wpf</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Proper internationalization support for WPF (via NGettext). In particular a GetTextMarkupExtension is included, which is what everyone uses anyway.</description>
<copyright>Copyright 2017, 2018, 2019 Accuratech ApS</copyright>
<tags>gettext wpf ngettext gettextmarkupextension xgettext-xaml</tags>
<dependencies>
<dependency id="NGettext" version="0.6.3" />
<dependency id="Expression.Blend.Sdk" version="1.0.2" />
</dependencies>
</metadata>
<files>
<file src="../XGetText.Xaml/XGetText-Xaml.ps1" target="tools" />
<file src="../XGetText.Xaml/Init.ps1" target="tools" />
</files>
</package>

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("NGettext.Wpf")]
[assembly: AssemblyDescription("Proper internationalizations upport for WPF (via NGettext).")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("NGettext.Wpf")]
[assembly: AssemblyCopyright("Copyright © 2017, 2018, 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("4bb0eb35-fe32-4295-bf39-4da1c9b67bb8")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.2.2.0")]
[assembly: AssemblyFileVersion("1.2.2.0")]

View File

@@ -0,0 +1,38 @@
Releasing
=========
- Update the `.nuspec` file with a new version number.
- Build in release mode
- Invoke `PM> nuget pack`; no options needed. But it will release the debug version, so make sure your build configuration match! Say `PM> nuget pack -Prop Configuration=Release`.
- Sign the created package, for example by
```
PM> nuget sign .\NGettext.Wpf.1.1.0-alpha.nupkg -Timestamper http://timestamp.digicert.com -CertificateFingerprint 79a047643b02e7b677d5d0a962bc02ac19e63ca8
```
- Verify signing with
```
PM> nuget verify -Signatures .\NGettext.Wpf.1.1.0-alpha.nupkg
Verifying NGettext.Wpf.1.1.0-alpha
C:\Git\ngettext-wpf\NGettext.Wpf\.\NGettext.Wpf.1.1.0-alpha.nupkg
Signature Hash Algorithm: SHA256
Signature type: Author
Verifying the author primary signature with certificate:
Subject Name: CN=ACCURATECH ApS, O=ACCURATECH ApS, L=Holstebro, C=DK, SERIALNUMBER=27635652, OID.2.5.4.15=Private Organization, OID.1.3.6.1.4.1.311.60.2.1.3=DK
SHA1 hash: 79A047643B02E7B677D5D0A962BC02AC19E63CA8
SHA256 hash: A66690776D4B00270DAA40F0336E4EE8288D2B2F9F77E6B132B63D18F0F408FF
Issued by: CN=DigiCert EV Code Signing CA (SHA2), OU=www.digicert.com, O=DigiCert Inc, C=US
Valid from: 06-02-2018 01:00:00 to 09-02-2021 13:00:00
Timestamp: 19-02-2019 12:32:52
Verifying author primary signature's timestamp with timestamping service certificate:
Subject Name: CN=DigiCert SHA2 Timestamp Responder, O=DigiCert, C=US
SHA1 hash: 400191475C98891DEBA104AF47091B5EB6D4CBCB
SHA256 hash: FC834D5BFFDE31DBA5B79BF95F573F7953BCBF9156E8525163E828EB92EA8A93
Issued by: CN=DigiCert SHA2 Assured ID Timestamping CA, OU=www.digicert.com, O=DigiCert Inc, C=US
Valid from: 04-01-2017 01:00:00 to 18-01-2028 01:00:00
Successfully verified package 'NGettext.Wpf.1.1.0-alpha'
```

View File

@@ -0,0 +1,47 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Markup;
using Microsoft.Xaml.Behaviors;
namespace NGettext.Wpf
{
/// <summary>
/// Makes sure that the CultureInfo used for all binding operations inside the associated
/// FrameworkElement follows the CurrentCulture of the CultureTracker injected to the static
/// CultureTracker property.
///
/// For instance, dates and numbers bound with a culture specific StringFormat will be formatted
/// according to the tracked culture and even reformatted on culture changed.
/// </summary>
public class TrackCurrentCultureBehavior : Behavior<FrameworkElement>, IWeakCultureObserver
{
public static ICultureTracker CultureTracker { get; set; }
protected override void OnAttached()
{
if (!DesignerProperties.GetIsInDesignMode(AssociatedObject))
{
if (CultureTracker is null)
{
CompositionRoot.WriteMissingInitializationErrorMessage();
return;
}
CultureTracker.AddWeakCultureObserver(this);
UpdateAssociatedObjectCulture();
}
base.OnAttached();
}
void UpdateAssociatedObjectCulture()
{
if (AssociatedObject is null) return;
AssociatedObject.Language = XmlLanguage.GetLanguage(CultureTracker.CurrentCulture.IetfLanguageTag);
}
public void HandleCultureChanged(ICultureTracker sender, CultureEventArgs eventArgs)
{
UpdateAssociatedObjectCulture();
}
}
}

View File

@@ -0,0 +1,65 @@

using System.Globalization;
namespace NGettext.Wpf
{
public static class Translation
{
public static string _(string msgId) => Localizer?.Gettext(msgId) ?? "";
public static string _(string msgId, params object[] parameters)
{
return parameters.Any()
? Localizer?.Gettext(msgId, parameters) ?? ""
: Localizer?.Gettext(msgId) ?? "";
}
public static ILocalizer? Localizer { get; set; }
public static string Noop(string msgId) => msgId;
[Obsolete("Use GetPluralString() instead. This method will be removed in 2.x")]
public static string PluralGettext(int n, string singularMsgId, string pluralMsgId, params object[] @params)
{
return GetPluralString(singularMsgId, pluralMsgId, n, @params);
}
public static string GetPluralString(string singularMsgId, string pluralMsgId, int n, params object[] args)
{
if (Localizer is null)
{
CompositionRoot.WriteMissingInitializationErrorMessage();
return string.Format(CultureInfo.InvariantCulture, n == 1 ? singularMsgId : pluralMsgId, args);
}
return args.Any()
? Localizer.Catalog.GetPluralString(singularMsgId, pluralMsgId, n, args)
: Localizer.Catalog.GetPluralString(singularMsgId, pluralMsgId, n);
}
public static string GetParticularPluralString(string context, string text, string pluralText, int n, params object[] args)
{
if (Localizer is null)
{
CompositionRoot.WriteMissingInitializationErrorMessage();
return string.Format(CultureInfo.InvariantCulture, n == 1 ? text : pluralText, args);
}
return args.Any()
? Localizer.Catalog.GetParticularPluralString(context, text, pluralText, n, args)
: Localizer.Catalog.GetParticularPluralString(context, text, pluralText, n);
}
public static string GetParticularString(string context, string text, params object[] args)
{
if (Localizer is null)
{
CompositionRoot.WriteMissingInitializationErrorMessage();
return (args.Any() ? string.Format(CultureInfo.InvariantCulture, text, args) : text);
}
return args.Any()
? Localizer.Catalog.GetParticularString(context, text, args)
: Localizer.Catalog.GetParticularString(context, text);
}
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Expression.Blend.Sdk" version="1.0.2" targetFramework="net45" />
<package id="NGettext" version="0.6.3" targetFramework="net45" />
<package id="NuGet.CommandLine" version="4.7.1" targetFramework="net45" developmentDependency="true" />
</packages>