diff --git a/se5/AmericanToBritish/MainWindow.axaml b/se5/AmericanToBritish/MainWindow.axaml
index d2912c7..c5bb5ff 100644
--- a/se5/AmericanToBritish/MainWindow.axaml
+++ b/se5/AmericanToBritish/MainWindow.axaml
@@ -32,10 +32,19 @@
IsDefault="True" />
-
-
-
-
+
+
+
+
+
+
+
_blocks;
private readonly ObservableCollection _proposals = new();
+ private readonly string _wordListPath = LocalWordList.GetDefaultPath("AmericanToBritish.xml");
private TextBlock _summaryLabel = null!;
private TextBlock _subtitleLabel = null!;
@@ -30,30 +31,50 @@ public MainWindow(PluginRequest request)
InitializeComponent();
_blocks = SubRipParser.Parse(request.Subtitle.SubRip);
- BuildProposals();
-
_changesList.ItemsSource = _proposals;
+ RefreshProposals();
+ }
+
+ private void RefreshProposals()
+ {
+ foreach (var proposal in _proposals)
+ {
+ proposal.PropertyChanged -= OnProposalChanged;
+ }
+ _proposals.Clear();
- var scope = request.SelectedIndices.Count > 0
- ? $"the {request.SelectedIndices.Count} selected line(s)"
+ var scope = _request.SelectedIndices.Count > 0
+ ? $"the {_request.SelectedIndices.Count} selected line(s)"
: "all lines";
_subtitleLabel.Text = $"Convert American to British English spellings in {scope}.";
- if (_proposals.Count == 0)
+ LocalWordList? localWordList = null;
+ try
+ {
+ localWordList = LocalWordList.Load(_wordListPath);
+ }
+ catch (Exception ex)
+ {
+ _subtitleLabel.Text += $" Local word list ignored - could not read {_wordListPath}: {ex.Message}";
+ }
+
+ BuildProposals(localWordList);
+
+ var hasChanges = _proposals.Count > 0;
+ _noChangesLabel.IsVisible = !hasChanges;
+ _changesList.IsVisible = hasChanges;
+ if (!hasChanges)
{
- _noChangesLabel.IsVisible = true;
- _changesList.IsVisible = false;
_applyButton.IsEnabled = false;
_summaryLabel.Text = string.Empty;
+ return;
}
- else
+
+ foreach (var proposal in _proposals)
{
- foreach (var proposal in _proposals)
- {
- proposal.PropertyChanged += OnProposalChanged;
- }
- UpdateSummary();
+ proposal.PropertyChanged += OnProposalChanged;
}
+ UpdateSummary();
}
private void InitializeComponent()
@@ -72,9 +93,9 @@ protected override void OnOpened(EventArgs e)
this.BringToForeground();
}
- private void BuildProposals()
+ private void BuildProposals(LocalWordList? localWordList)
{
- var converter = new EnglishVariantConverter(EnglishVariantDirection.UsToBr);
+ var converter = new EnglishVariantConverter(EnglishVariantDirection.UsToBr, localWordList);
var selected = new HashSet(_request.SelectedIndices);
var applyToAll = selected.Count == 0;
@@ -124,6 +145,26 @@ private void OnSelectNone(object? sender, RoutedEventArgs e)
}
}
+ private async void OnEditWordList(object? sender, RoutedEventArgs e)
+ {
+ var list = new LocalWordList();
+ string? loadError = null;
+ try
+ {
+ list = LocalWordList.Load(_wordListPath);
+ }
+ catch (Exception ex)
+ {
+ loadError = $"Could not read the existing file ({ex.Message}). Clicking OK replaces it with the list below.";
+ }
+
+ var saved = await new WordListWindow(_wordListPath, list, loadError).ShowDialog(this);
+ if (saved)
+ {
+ RefreshProposals();
+ }
+ }
+
private void OnCancel(object? sender, RoutedEventArgs e)
{
App.Response = new PluginResponse { Status = PluginStatus.Cancelled };
diff --git a/se5/AmericanToBritish/README.md b/se5/AmericanToBritish/README.md
index 12dc409..b978244 100644
--- a/se5/AmericanToBritish/README.md
+++ b/se5/AmericanToBritish/README.md
@@ -4,6 +4,24 @@ Converts American English spellings to British English in the subtitle, using a
bundled word list (~1850 pairs). Shows a checkable preview of every proposed
change so you can review and toggle individual conversions before applying.
+## Local word list
+
+The gear button opens the local word list, where you can add your own conversions
+(words or phrases) and ignore built-in ones. It is saved as
+`Plugins/AmericanToBritish.xml` in the Subtitle Edit data folder - the same file
+and format as the Subtitle Edit 4 plugin, so an existing list keeps working:
+
+```xml
+
+
+
+
+
+```
+
+A `Word` with the same American spelling as a built-in pair replaces it; `Ignore`
+turns a built-in pair off.
+
First Subtitle Edit 5 plugin built on top of the shared
[`Plugin-Shared`](../Plugin-Shared/) library — Avalonia app boot, theme passing,
SRT parsing, and the JSON contract all come from there. The plugin code itself
diff --git a/se5/AmericanToBritish/WordListWindow.axaml b/se5/AmericanToBritish/WordListWindow.axaml
new file mode 100644
index 0000000..e30c256
--- /dev/null
+++ b/se5/AmericanToBritish/WordListWindow.axaml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/se5/AmericanToBritish/WordListWindow.axaml.cs b/se5/AmericanToBritish/WordListWindow.axaml.cs
new file mode 100644
index 0000000..6483d40
--- /dev/null
+++ b/se5/AmericanToBritish/WordListWindow.axaml.cs
@@ -0,0 +1,218 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using SubtitleEdit.Plugins.Shared;
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace SubtitleEdit.Plugins.AmericanToBritish;
+
+public sealed record WordPair(string Us, string Br);
+
+///
+/// Edits the local word list: extra/override pairs and ignored built-in words.
+/// Closes with true when the list was saved.
+///
+public partial class WordListWindow : Window
+{
+ private readonly string _path;
+ private readonly ObservableCollection _words = new();
+ private readonly ObservableCollection _ignored = new();
+
+ private TextBlock _errorLabel = null!;
+ private ListBox _wordsList = null!;
+ private ListBox _ignoredList = null!;
+ private TextBox _americanBox = null!;
+ private TextBox _britishBox = null!;
+ private TextBox _ignoreBox = null!;
+ private Button _removeWordButton = null!;
+ private Button _removeIgnoredButton = null!;
+
+ public WordListWindow() : this(LocalWordList.GetDefaultPath("AmericanToBritish.xml"), new LocalWordList()) { }
+
+ public WordListWindow(string path, LocalWordList list, string? loadError = null)
+ {
+ _path = path;
+ InitializeComponent();
+
+ if (loadError != null)
+ {
+ ShowError(loadError);
+ }
+
+ foreach (var (us, br) in list.Words)
+ {
+ _words.Add(new WordPair(us, br));
+ }
+
+ foreach (var us in list.Ignored)
+ {
+ _ignored.Add(us);
+ }
+
+ _wordsList.ItemsSource = _words;
+ _ignoredList.ItemsSource = _ignored;
+ this.FindControl("FileLabel")!.Text = path;
+
+ _wordsList.SelectionChanged += (_, _) => _removeWordButton.IsEnabled = _wordsList.SelectedItems?.Count > 0;
+ _ignoredList.SelectionChanged += (_, _) => _removeIgnoredButton.IsEnabled = _ignoredList.SelectedItems?.Count > 0;
+ _wordsList.SelectionChanged += (_, _) =>
+ {
+ if (_wordsList.SelectedItem is WordPair pair)
+ {
+ _americanBox.Text = pair.Us;
+ _britishBox.Text = pair.Br;
+ }
+ };
+
+ _americanBox.KeyDown += OnWordBoxKeyDown;
+ _britishBox.KeyDown += OnWordBoxKeyDown;
+ _ignoreBox.KeyDown += (_, e) =>
+ {
+ if (e.Key == Key.Enter)
+ {
+ e.Handled = true;
+ OnAddIgnored(null, new RoutedEventArgs());
+ }
+ };
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ _errorLabel = this.FindControl("ErrorLabel")!;
+ _wordsList = this.FindControl("WordsList")!;
+ _ignoredList = this.FindControl("IgnoredList")!;
+ _americanBox = this.FindControl("AmericanBox")!;
+ _britishBox = this.FindControl("BritishBox")!;
+ _ignoreBox = this.FindControl("IgnoreBox")!;
+ _removeWordButton = this.FindControl