פתרון פשוט וקליל ללוקליזציה באפליקציות WPF
(הקוד נמצא בסוף הכתבה)
קראתי הרבה מדריכים בנושא לוקליזציה ונתקלתי בהרבה ספריות מתקדמות ומורכבות. בסופו של דבר, רוב הפתרונות הללו הופכים את ניהול התרגום למשימה מסובכת, במיוחד כאשר יש צורך לבצע תרגום מחדש או להוסיף שפות חדשות בשלב מאוחר יותר.
לאחר שחיפשתי פתרון פשוט ויעיל, הגעתי לגישה הבאה. יש לציין כי פתרון זה מתאים בעיקר לאפליקציות קטנות ובינוניות, מאחר שהוא טוען את כל המידע לזיכרון בבת אחת. עבור אפליקציות גדולות, המצריכות פתרון מתוחכם יותר, כמו שימוש במסד נתונים, פתרון זה לא יתאים. (ייתכן שיישום Dictionary מסוג חסכוני יותר בזכרון יפתור את הבעיה ללא צורך במסד, אבל לא בדקתי את זה)
למעשה, עבור יישומים שאינם דורשים מורכבות כזו, גישה זו יכולה לחסוך הרבה זמן ומאמץ.
השלבים לבניית הפתרון:
א. שימוש בקובץ JSON
קובץ JSON מאפשר לנו לנהל ולערוך את כל המחרוזות של האפליקציה בצורה מסודרת ונוחה. המפתח (Key) מייצג את שם הפריט, והערך (Value) מייצג את המחרוזת המתורגמת.
עבור כל שפה נייצר קובץ נפרד עם הקידומת של השפה בתוך התיקייה הייעודית (בקוד להלן אני בחרתי "Asstes//Locale").
הקידומת תהיה בת שני אותיות בלבד ובאותיות קטנות למשל "he" או "en".
שימו לב! בקוד שכתבתי שפת המערכת מזוהה באופן אוטומטי ייתכן וגישה זו איננה מתאימה לכם.
ב. שימוש במבנה Dictionary סטטי
אנו נטען את תוכן ה-JSON לקובץ זיכרון באמצעות מילון (Dictionary) סטטי, שיהיה זמין לכל רכיבי האפליקציה.
ג. שימוש ב-Markup Extension
ניצור הרחבה מותאמת אישית (Markup Extension) שתאפשר לנו לגשת למחרוזות מתוך ה-JSON ישירות מתוך ה-XAML. (כמו"כ נוכל לצפות בתוצאות בתצוגה המקדימה של עורך ה-Xaml).
דגשים חשובים לניהול הפתרון:
-
שמות זהים לשמות הפקדים: הקפידו על כך ששמות המפתחות ב-JSON יהיו זהים לשמות הפקדים שאותם הם מתארים. כך, תוכלו לשמור על עקביות ולמנוע טעויות.
-
מבנה עם תיאור ברור: הוסיפו תיאור לכל מפתח בקובץ ה-JSON, כך שיהיה קל להבין מה הוא מייצג. מומלץ לצורך זה להשתמש בתיאורים המובנים של WPF. לדוגמה:
{
"Tooltip.MinimizeButton": "Minimize",
"Tooltip.CloseButton": "Close",
"Title.MainWindowTitle": "My Application"
}
העיקרון הזה יקל עליכם גם בכתיבת ה-JSON וגם בייחוס המפתחות ישירות מתוך ה-XAML.
סדר אלפא ביתי של המפתחות ייקל על עריכת שינויים והתמצאות כללית ב-JSON.
להלן הקוד:
using System.Globalization;
using System.IO;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Markup;
namespace Assets.Locale
{
public class LocalizedStringExtension : MarkupExtension
{
public string Key { get; set; }
public LocalizedStringExtension() { }
public LocalizedStringExtension(string key) { Key = key; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (string.IsNullOrEmpty(Key)) return "[Missing Key]";
else return LocaleHelper.LocaleDictionary.TryGetValue(Key, out var value) ? value : $"[{Key}]";
}
}
public static class LocaleHelper
{
private static readonly string LocaleDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "Locale");
private static string LocalePath =>
Path.Combine(LocaleDir, $"{CultureInfo.CurrentCulture.TwoLetterISOLanguageName}.json") is string path && File.Exists(path)
? path
: Path.Combine(LocaleDir, "en.json");
private static Dictionary<string, string>? _localeDictionary;
public static Dictionary<string, string> LocaleDictionary => _localeDictionary ??= LoadLocaleDictionary();
private static Dictionary<string, string> LoadLocaleDictionary()
{
if (!File.Exists(LocalePath))
return new Dictionary<string, string>();
try
{
string json = File.ReadAllText(LocalePath);
return JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
}
catch
{
// Return an empty dictionary if reading or deserialization fails
return new Dictionary<string, string>();
}
}
}
}
ובתוך ה-xaml יש להוסיף
xmlns:locale="clr-namespace:Assets.Locale"
ואז תוכלו למשל לעשות כך
<Button x:Name="MinimizeButton"
ToolTip="{locale:LocalizedString Key='Tooltip.MinimizeButton'}">