Skip to content

Commit

Permalink
Add translation of settings form
Browse files Browse the repository at this point in the history
Also improve algorithm for repositioning controls after translation.
Also add concept of global constants in translations file.
  • Loading branch information
molsonkiko committed Jul 17, 2024
1 parent cc93a08 commit f2bf148
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 21 deletions.
36 changes: 30 additions & 6 deletions NppCSharpPluginPack/PluginInfrastructure/SettingsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,17 +215,18 @@ public void ShowDialog(bool debug = false)
var copy = (Settings)MemberwiseClone();

//// check the current settings
//var settings_sb = new StringBuilder();
//var settingsSb = new StringBuilder();
//foreach (System.Reflection.PropertyInfo p in GetType().GetProperties())
//{
// settings_sb.Append(p.ToString());
// settings_sb.Append($": {p.GetValue(this)}");
// settings_sb.Append(", ");
// settingsSb.Append(p.ToString());
// settingsSb.Append($": {p.GetValue(this)}");
// settingsSb.Append(", ");
//}
//MessageBox.Show(settings_sb.ToString());
//MessageBox.Show(settingsSb.ToString());

var dialog = new Form
{
Name = "SettingsForm",
Text = $"Settings - {Main.PluginName} plug-in",
ClientSize = new Size(DEFAULT_WIDTH, DEFAULT_HEIGHT),
MinimumSize = new Size(250, 250),
Expand Down Expand Up @@ -275,6 +276,7 @@ public void ShowDialog(bool debug = false)
},
}
};
Translator.TranslateForm(dialog);

dialog.Controls["Cancel"].Click += (a, b) => dialog.Close();
dialog.Controls["Ok"].Click += (a, b) =>
Expand All @@ -291,7 +293,19 @@ public void ShowDialog(bool debug = false)
var oldValue = propertyInfo.GetValue(this, null);
var newValue = propertyInfo.GetValue(copy, null);
if (!oldValue.Equals(newValue))
propertyInfo.SetValue(this, newValue, null);
{
try
{
propertyInfo.SetValue(this, newValue, null);
}
catch (Exception ex)
{
MessageBox.Show($"Could not change setting {propertyInfo.Name} to value {newValue}, so it will remain set as {oldValue}.\r\n" +
$"Got the following exception:\r\n{ex}",
$"Invalid value for setting {propertyInfo.Name}",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
OnSettingsChanged();
dialog.Close();
Expand All @@ -306,6 +320,16 @@ public void ShowDialog(bool debug = false)
OnSettingsChanged();
dialog.Close();
};
// close dialog on pressing Escape (this doesn't work if a grid cell is selected, but it does work if a button is selected)
KeyEventHandler keyDownHandler = (a, b) =>
{
if (b.KeyCode == Keys.Escape)
dialog.Close();
};
dialog.KeyDown += keyDownHandler;
foreach (Control ctrl in dialog.Controls)
ctrl.KeyDown += keyDownHandler;
// translate the descriptions of the settings
var grid = dialog.Controls["Grid"];
if (Translator.HasTranslations
&& grid.Controls.Count >= 1 && grid.Controls[0] is Control commentPane
Expand Down
4 changes: 2 additions & 2 deletions NppCSharpPluginPack/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@
// Build Number
// Revision
//
[assembly: AssemblyVersion("0.0.3.7")]
[assembly: AssemblyFileVersion("0.0.3.7")]
[assembly: AssemblyVersion("0.0.3.8")]
[assembly: AssemblyFileVersion("0.0.3.8")]
76 changes: 66 additions & 10 deletions NppCSharpPluginPack/Utils/Translator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@
using System.Reflection;
using System.ComponentModel;
using System.Globalization;
using System.Text.RegularExpressions;

namespace NppDemo.Utils
{
public static class Translator
{
public static string languageName { get; private set; } = "english";

private static JObject translations = null;
public static bool HasTranslations => !(translations is null);

public static void LoadTranslations()
{
string languageName = "english";
// TODO: maybe use Notepad++ nativeLang preference to guide translation?
// Possible references include:
// * https://github.com/daddel80/notepadpp-multireplace/blob/65411ac5754878bbf8af7a35dba7b35d7d919ff4/src/MultiReplacePanel.cpp#L6347
Expand Down Expand Up @@ -76,6 +78,7 @@ public static void LoadTranslations()
translationFileText = fp.ReadToEnd();
}
translations = (JObject)parser.Parse(translationFileText);
PropagateGlobalConstants();
//MessageBox.Show($"Found and successfully parsed translation file at path {translationFilename}");
}
catch/* (exception ex)*/
Expand All @@ -84,6 +87,59 @@ public static void LoadTranslations()
}
}

private static readonly Regex constantNameRegex = new Regex(@"\A\$[a-zA-Z_]+\$\z", RegexOptions.Compiled);

private static void PropagateGlobalConstants()
{
if (translations is JObject jobj
&& translations.children.TryGetValue("$constants$", out JNode constsNode) && constsNode is JObject consts)
{
var constants = new Dictionary<string, string>();
var invalidConstantNames = new JArray();
foreach (string key in consts.children.Keys)
{
if (constantNameRegex.IsMatch(key))
constants[key] = consts[key].ValueOrToString();
else
invalidConstantNames.children.Add(new JNode(key));
}
if (invalidConstantNames.Length > 0)
{
MessageBox.Show($"The \"$constants$\" field for the {languageName}.json5 file contains constants where the key does not match the regular expression {JNode.StrToString(constantNameRegex.ToString(), true)}:\r\n{invalidConstantNames.ToString()}",
"Invalid \"$constants$\" field", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
if (constants.Count > 0)
{
foreach (KeyValuePair<string, JNode> kv in jobj.children)
{
if (kv.Key != "$constants$")
PropagateGlobalConstantsHelper(kv.Value, constants);
}
}
}
}

private static void PropagateGlobalConstantsHelper(JNode node, Dictionary<string, string> constants)
{
if (node.value is string s && s.IndexOf('$') >= 0)
{
string newValue = s;
foreach (KeyValuePair<string, string> kv in constants)
newValue = newValue.Replace(kv.Key, kv.Value);
node.value = newValue;
}
else if (node is JArray jarr)
{
foreach (JNode child in jarr.children)
PropagateGlobalConstantsHelper(child, constants);
}
else if (node is JObject jobj)
{
foreach (JNode child in jobj.children.Values)
PropagateGlobalConstantsHelper(child, constants);
}
}

public static string GetTranslatedMenuItem(string menuItem)
{
if (translations is JObject jobj && jobj.children is Dictionary<string, JNode> dict
Expand Down Expand Up @@ -131,6 +187,8 @@ public static string TranslateSettingsDescription(PropertyInfo propertyInfo)
return "";
}

#region Form translation

/// <summary>
/// This assumes that each character in Size 7.8 font is 7 pixels across.<br></br>
/// In practice, this generally returns a number that is greater than the width of the text.
Expand Down Expand Up @@ -259,21 +317,18 @@ private static void TranslateControl(Control ctrl, Dictionary<string, JNode> con
}
int maxRight = 0;
foreach (Control child in ctrl.Controls)
{
maxRight = child.Right > maxRight ? child.Right : maxRight;
// unanchor everything from the right, so resizing the form doesn't move those controls
child.Anchor &= (AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left);
}
// Temporarily turn off the normal layout logic,
// because this would cause controls to move right when the form is resized
// (yes, even if they're not anchored right. No, that doesn't make sense)
ctrl.SuspendLayout();
if (maxRight > ctrl.Width)
{
int padding = ctrl is Form ? 25 : 5;
ctrl.Width = maxRight + padding;
}
foreach ((Control child, _, bool wasAnchoredRight) in childrenByLeft)
{
if (wasAnchoredRight)
child.Anchor |= AnchorStyles.Right;
}
// Resume layout logic, ignoring the pending requests to move controls right due to resizing of form
ctrl.ResumeLayout(false);
}
}

Expand All @@ -291,5 +346,6 @@ private static bool Overlap(Control ctrl1, Control ctrl2)
{
return HorizontalOverlap(ctrl1, ctrl2) && VerticalOverlap(ctrl1, ctrl2);
}
#endregion
}
}
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,9 @@ Currently NppCSharpPluginPack has been translated into the following languages:
The following aspects of NppCSharpPluginPack __can__ be translated:
- Forms (including all controls and items in drop-down menus)
- Items in the NppCSharpPluginPack sub-menu of the Notepad++ Plugins menu
- The descriptions of settings in the [`CSharpPluginPack.ini` config file](/docs/README.md#settings-form)
- The descriptions of settings in the [settings form](/docs/README.md#customizing-settings)
- The descriptions of settings in the [`CSharpPluginPack.ini` config file and settings form](/docs/README.md#settings-form)

The following aspects of NppCSharpPluginPack __will not__ be translated:
The following aspects of NppCSharpPluginPack __may eventually__ be translated:
- This documentation
- Message boxes (includes warnings, errors, requests for confirmation)
- Generic modal dialogs (for example, file-opening dialogs, directory selection dialogs)
Expand Down
15 changes: 15 additions & 0 deletions translation/english.json5
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
// DO NOT CHANGE THE KEYS OF ANY OBJECT, OR ADD OR REMOVE KEYS! ONLY CHANGE THE VALUES.
// 3. Close Notepad++ and reopen it, then look at the various CSharpPluginPack forms and menu items to see if the translation is satisfactory.
{
// This lists a set of global constants that can appear in other values throughout this config file.
// For example, if the "$PluginName$" constant is "FooBar", and another value in this config file is "$PluginName$ settings",
// that value would be changed to "FooBar settings" at the time the settings are loaded from file.
// Constant names must match the regular expression "\A\$[a-zA-Z_]+\$\z", as shown below.
"$constants$": {
"$PluginName$": "CSharpPluginPack"
},
"forms": {
"AboutForm": {
// this is the name that appears in the title bar for this form
Expand Down Expand Up @@ -70,6 +77,14 @@
"LoadSelectionsFromFileButton": "Load selections from config file",
"OpenDarkModeTestFormButton": "Open dark mode test form"
}
},
"SettingsForm": {
"title": "Settings - $PluginName$ plug-in",
"controls": {
"Cancel": "&Cancel",
"Reset": "&Reset",
"Ok": "&Ok"
}
}
},
// this controls the text of the main menu items
Expand Down
15 changes: 15 additions & 0 deletions translation/spanish.json5
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
// DO NOT CHANGE THE KEYS OF ANY OBJECT, OR ADD OR REMOVE KEYS! ONLY CHANGE THE VALUES.
// 3. Close Notepad++ and reopen it, then look at the various CSharpPluginPack forms and menu items to see if the translation is satisfactory.
{
// This lists a set of global constants that can appear in other values throughout this config file.
// For example, if the "$PluginName$" constant is "FooBar", and another value in this config file is "$PluginName$ settings",
// that value would be changed to "FooBar settings" at the time the settings are loaded from file.
// Constant names must match the regular expression "\A\$[a-zA-Z_]+\$\z", as shown below.
"$constants$": {
"$PluginName$": "CSharpPluginPack"
},
"forms": {
"AboutForm": {
// this is the name that appears in the title bar for this form
Expand Down Expand Up @@ -70,6 +77,14 @@
"LoadSelectionsFromFileButton": "Cargar selecciones desde un archivo de configuración",
"OpenDarkModeTestFormButton": "Abrir DarkModeTestForm"
}
},
"SettingsForm": {
"title": "Configuración - $PluginName$ plug-in",
"controls": {
"Cancel": "&Cancela",
"Reset": "&Restablecer",
"Ok": "&Ok"
}
}
},
// this controls the text of the main menu items
Expand Down

0 comments on commit f2bf148

Please sign in to comment.