From 69af5b6ac33d22f203306fe038e389373870abe4 Mon Sep 17 00:00:00 2001 From: PointStar Date: Mon, 11 May 2026 15:52:59 +0300 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20Task=20#7=20=E2=80=94=20Hebrew/En?= =?UTF-8?q?glish=20localization=20infrastructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Strings.resx (Hebrew, default) + Strings.en-US.resx (English override) cover every dialog/sidebar/ribbon string, plus filing summary format templates (`Msg_FilingSummary_*`) - LocalizationManager singleton wraps ResourceManager with Get() and Format() helpers; SetCulture honors AppSettings.Locale - LocExtension XAML markup ({l:Loc Key}) for bindings - Themes/RtlFonts.xaml — global RightToLeft FlowDirection and Hebrew font stack (Segoe UI / David / Frank Ruehl / Arial) - AddInHost calls LocalizationManager.SetCulture(Settings.Locale) before any UI thread reads a resource Existing dialogs keep their hardcoded Hebrew for v1 — those are already in the correct (he-IL) language. Future iterations can switch to {l:Loc …} bindings; the resource files already enumerate every string so the migration is mechanical. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/OutlookAddin.UI/Markup/LocExtension.cs | 30 ++++++++ .../Resources/Strings.en-US.resx | 63 +++++++++++++++++ src/OutlookAddin.UI/Resources/Strings.resx | 70 +++++++++++++++++++ .../Services/LocalizationManager.cs | 58 +++++++++++++++ src/OutlookAddin.UI/Themes/RtlFonts.xaml | 15 ++++ src/OutlookAddin/Services/AddInHost.cs | 4 ++ 6 files changed, 240 insertions(+) create mode 100644 src/OutlookAddin.UI/Markup/LocExtension.cs create mode 100644 src/OutlookAddin.UI/Resources/Strings.en-US.resx create mode 100644 src/OutlookAddin.UI/Resources/Strings.resx create mode 100644 src/OutlookAddin.UI/Services/LocalizationManager.cs create mode 100644 src/OutlookAddin.UI/Themes/RtlFonts.xaml diff --git a/src/OutlookAddin.UI/Markup/LocExtension.cs b/src/OutlookAddin.UI/Markup/LocExtension.cs new file mode 100644 index 0000000..d4ae594 --- /dev/null +++ b/src/OutlookAddin.UI/Markup/LocExtension.cs @@ -0,0 +1,30 @@ +using System; +using System.Windows.Markup; +using MarcusLaw.OutlookAddin.UI.Services; + +namespace MarcusLaw.OutlookAddin.UI.Markup +{ + /// + /// XAML markup extension that resolves a resource key against + /// . Usage: + /// <TextBlock Text="{l:Loc Settings_Title}" /> + /// + /// Resolution is one-shot — culture changes require a restart, matching + /// the design in PROJECT-BRIEFING.md ("Override via SettingsManager… + /// requires restart"). + /// + [MarkupExtensionReturnType(typeof(string))] + public sealed class LocExtension : MarkupExtension + { + public string? Key { get; set; } + + public LocExtension() { } + + public LocExtension(string key) { Key = key; } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return string.IsNullOrEmpty(Key) ? string.Empty : LocalizationManager.Instance.Get(Key!); + } + } +} diff --git a/src/OutlookAddin.UI/Resources/Strings.en-US.resx b/src/OutlookAddin.UI/Resources/Strings.en-US.resx new file mode 100644 index 0000000..534e97a --- /dev/null +++ b/src/OutlookAddin.UI/Resources/Strings.en-US.resx @@ -0,0 +1,63 @@ + + + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Save + Cancel + File + Continue + Test connection + + File mail to EspoCRM + Search case, account, contact… + Recent + Create new contact from sender + + Compose mail from case + Search case: + + EspoCRM Add-in settings + General + Folders + About + EspoCRM server URL + Username + API key + Interface language + Pick folders to watch. In Auto-file mode, the add-in files mail it can match with high confidence; otherwise it tags "Needs filing". + Watch + Folder + Account + Mode + Notify only + Auto-file + + Case info + Select a mail to see case details + No contact match for + Search manually in EspoCRM + Loading… + Contact + Account + Recent cases + (no open cases) + File to this case + File elsewhere… + Open in EspoCRM + + File to EspoCRM + File the selected mail to EspoCRM + Compose from case + Settings + + Add-in is not configured yet. Open settings now? + No mail selected. Pick one or more messages and try again. + API key was rejected. Update settings and retry. + Filed {0} message(s) to "{1}" + {0} queued for retry + {0} failed + Nothing was filed. + diff --git a/src/OutlookAddin.UI/Resources/Strings.resx b/src/OutlookAddin.UI/Resources/Strings.resx new file mode 100644 index 0000000..b4d03ac --- /dev/null +++ b/src/OutlookAddin.UI/Resources/Strings.resx @@ -0,0 +1,70 @@ + + + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + שמור + ביטול + תייק + המשך + בדוק חיבור + + + תיוק מייל ל-EspoCRM + חפש תיק, לקוח, איש קשר… + אחרונים + צור איש קשר חדש מהשולח + + + כתוב מייל מתיק + חפש תיק: + + + הגדרות תוסף EspoCRM + כללי + תיקיות + אודות + כתובת שרת EspoCRM + שם משתמש + מפתח API + שפת ממשק + סמן תיקיות לניטור. במצב 'אוטומטי' התוסף יתייק לבד מיילים שזוהו בוודאות, אחרת רק יסמן 'נדרש תיוק'. + נטור + תיקייה + חשבון + מצב + התרעה בלבד + תיוק אוטומטי + + + פרטי תיק + סמן מייל כדי לראות את פרטי התיק + לא נמצא איש קשר ל- + חפש ידנית ב-EspoCRM + טוען… + איש קשר + לקוח/חברה + תיקים אחרונים + (אין תיקים פתוחים) + תייק לתיק הזה + תייק למקום אחר… + פתח ב-EspoCRM + + + תייק ל-EspoCRM + תייק את המייל הנבחר ל-EspoCRM + כתוב מתיק + הגדרות + + + התוסף עדיין לא מוגדר. לפתוח את חלון ההגדרות עכשיו? + לא נבחר מייל לתיוק. סמן מייל אחד או יותר ונסה שוב. + מפתח ה-API נדחה. יש לעדכן את ההגדרות ולנסות שוב. + תויקו {0} מיילים אל "{1}" + {0} בתור לניסיון חוזר + {0} נכשלו + לא נשלחו פריטים. + diff --git a/src/OutlookAddin.UI/Services/LocalizationManager.cs b/src/OutlookAddin.UI/Services/LocalizationManager.cs new file mode 100644 index 0000000..4aa4543 --- /dev/null +++ b/src/OutlookAddin.UI/Services/LocalizationManager.cs @@ -0,0 +1,58 @@ +using System; +using System.Globalization; +using System.Reflection; +using System.Resources; +using System.Threading; + +namespace MarcusLaw.OutlookAddin.UI.Services +{ + /// + /// Strongly-typed wrapper around . The + /// default culture is Hebrew (he-IL) — that's the user base. Switch by + /// calling from SettingsViewModel. + /// + public sealed class LocalizationManager + { + private const string BaseName = "MarcusLaw.OutlookAddin.UI.Resources.Strings"; + + public static LocalizationManager Instance { get; } = new LocalizationManager(); + + public ResourceManager ResourceManager { get; } + + private LocalizationManager() + { + ResourceManager = new ResourceManager(BaseName, typeof(LocalizationManager).Assembly); + } + + public string Get(string key) + { + try + { + return ResourceManager.GetString(key, CultureInfo.CurrentUICulture) ?? key; + } + catch + { + return key; + } + } + + public string Format(string key, params object[] args) + { + var template = Get(key); + try { return string.Format(CultureInfo.CurrentUICulture, template, args); } + catch { return template; } + } + + public static void SetCulture(string cultureName) + { + if (string.IsNullOrWhiteSpace(cultureName)) return; + try + { + var ci = CultureInfo.GetCultureInfo(cultureName); + Thread.CurrentThread.CurrentUICulture = ci; + CultureInfo.DefaultThreadCurrentUICulture = ci; + } + catch (CultureNotFoundException) { /* leave as-is */ } + } + } +} diff --git a/src/OutlookAddin.UI/Themes/RtlFonts.xaml b/src/OutlookAddin.UI/Themes/RtlFonts.xaml new file mode 100644 index 0000000..5dab7f2 --- /dev/null +++ b/src/OutlookAddin.UI/Themes/RtlFonts.xaml @@ -0,0 +1,15 @@ + + + + + diff --git a/src/OutlookAddin/Services/AddInHost.cs b/src/OutlookAddin/Services/AddInHost.cs index 04968d8..b9d9658 100644 --- a/src/OutlookAddin/Services/AddInHost.cs +++ b/src/OutlookAddin/Services/AddInHost.cs @@ -57,6 +57,10 @@ namespace OutlookAddin.Services SettingsManager = new SettingsManager(); Settings = SettingsManager.Load(); + + // Apply the chosen UI locale before any view-model touches resource strings. + MarcusLaw.OutlookAddin.UI.Services.LocalizationManager.SetCulture(Settings.Locale); + CredentialStore = new DpapiCredentialStore(); RetryQueue = new DiskRetryQueue(Logger);