translation using NGettext.Wpf
This commit is contained in:
@@ -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 { }
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
63
src/MpvNet.Windows/WPF/WpfTranslator.cs
Normal file
63
src/MpvNet.Windows/WPF/WpfTranslator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/MpvNet.Windows/WinForms/MainForm.Designer.cs
generated
2
src/MpvNet.Windows/WinForms/MainForm.Designer.cs
generated
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
12
src/MpvNet/Translator.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
namespace MpvNet;
|
||||
|
||||
public class Translator
|
||||
{
|
||||
public static ITranslator? Current;
|
||||
}
|
||||
|
||||
public interface ITranslator
|
||||
{
|
||||
public string Gettext(string msgId);
|
||||
}
|
||||
32
src/NGettext.Wpf/ChangeCultureCommand.cs
Normal file
32
src/NGettext.Wpf/ChangeCultureCommand.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
28
src/NGettext.Wpf/Common/GettextStringFormatConverter.cs
Normal file
28
src/NGettext.Wpf/Common/GettextStringFormatConverter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/NGettext.Wpf/CompositionRoot.cs
Normal file
29
src/NGettext.Wpf/CompositionRoot.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/NGettext.Wpf/CultureEventArgs.cs
Normal file
16
src/NGettext.Wpf/CultureEventArgs.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
64
src/NGettext.Wpf/CultureTracker.cs
Normal file
64
src/NGettext.Wpf/CultureTracker.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
36
src/NGettext.Wpf/EnumTranslation/EnumLocalizer.cs
Normal file
36
src/NGettext.Wpf/EnumTranslation/EnumLocalizer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/NGettext.Wpf/EnumTranslation/EnumMsgIdAttribute.cs
Normal file
14
src/NGettext.Wpf/EnumTranslation/EnumMsgIdAttribute.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
55
src/NGettext.Wpf/EnumTranslation/LocalizeEnumConverter.cs
Normal file
55
src/NGettext.Wpf/EnumTranslation/LocalizeEnumConverter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/NGettext.Wpf/GettextExtension.cs
Normal file
96
src/NGettext.Wpf/GettextExtension.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/NGettext.Wpf/GettextFormatConverterExtension.cs
Normal file
21
src/NGettext.Wpf/GettextFormatConverterExtension.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/NGettext.Wpf/IWeakCultureObserver.cs
Normal file
7
src/NGettext.Wpf/IWeakCultureObserver.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace NGettext.Wpf
|
||||
{
|
||||
public interface IWeakCultureObserver
|
||||
{
|
||||
void HandleCultureChanged(ICultureTracker sender, CultureEventArgs eventArgs);
|
||||
}
|
||||
}
|
||||
117
src/NGettext.Wpf/Localizer.cs
Normal file
117
src/NGettext.Wpf/Localizer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/NGettext.Wpf/NGettext.Wpf.csproj
Normal file
16
src/NGettext.Wpf/NGettext.Wpf.csproj
Normal 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>
|
||||
24
src/NGettext.Wpf/NGettext.Wpf.nuspec
Normal file
24
src/NGettext.Wpf/NGettext.Wpf.nuspec
Normal 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>
|
||||
36
src/NGettext.Wpf/Properties/AssemblyInfo.cs
Normal file
36
src/NGettext.Wpf/Properties/AssemblyInfo.cs
Normal 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")]
|
||||
38
src/NGettext.Wpf/Releasing.md
Normal file
38
src/NGettext.Wpf/Releasing.md
Normal 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'
|
||||
```
|
||||
47
src/NGettext.Wpf/TrackCurrentCultureBehavior.cs
Normal file
47
src/NGettext.Wpf/TrackCurrentCultureBehavior.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
65
src/NGettext.Wpf/Translation.cs
Normal file
65
src/NGettext.Wpf/Translation.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/NGettext.Wpf/packages.config
Normal file
6
src/NGettext.Wpf/packages.config
Normal 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>
|
||||
Reference in New Issue
Block a user