מדריך: איך לייצר לוקליזציה ב-wpf בצורה פשוטה וקלילה
-
פתרון פשוט וקליל ללוקליזציה באפליקציות 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'}">
-
-
@pcinfogmach לא עברתי על כל הקוד, אבל זה נראה מלמעלה שאי אפשר לשנות את השפה בזמן הריצה של האפליקציה, תנסה ליישם משהו שמאפשר לשנות את השפה בצורה דינאמית, מבלי להפעיל מחדש
-
@קומפיונט כתב במדריך: איך לייצר לוקליזציה ב-wpf בצורה פשוטה וקלילה:
תנסה ליישם משהו שמאפשר לשנות את השפה בצורה דינאמית, מבלי להפעיל מחדש
אם בשביל הספורט, אדרבא, שינסה או תנסה אתה או כל מי שרוצה לבדוק את כישוריו.
אבל אם בשביל טובת המוצר, צריך לזכור שאין שכלולים (פיצ'רים בלעז) בחינם.
אם אנחנו לא נעצרים לחשוב כמה השכלול נדרש וישר רוצים אותו כי זה פשוט כולו טוב - "שכלול",
אז עלולים לשלם מחירים יקרים להחריד בגישה הזו.
לכל שכלול יש מחיר. השכלול נשלף מהשרוול, והמחיר הוא לכל מה שכבר קיים, לעיתים יקר.
זה דרגה גבוהה של הביטוי "האוייב של הטוב הוא העוד יותר טוב".נ.ב. לא התייחסתי לדוגמה הזאת, ייתכן שזה שני מילות בקוד.
זה פשוט כלל שלמדתי בשנים האחרונות בצורה קשוחה בבית הספר של החיים.
ודוקא בגלל זה, שיש לשים לב מראש בשלב התכנון, האם השכלול הזה יהיה בעתיד הקרוב מאוד נדרש (במקרה של עתיד לא קרוב מאוד צריך "לקחת בחשבון" אבל לא להשקיע תכנונים של ממש), כדי שהשינוי לא יהיה בעל מחיר גבוה. -
@OdedDvir אני גם רצתי להסתכל על המנגנון המובנה שזכרתי שישנו,
(יתרה מזאת, אני עצמי עשיתי אי פעם תוכנה בWPF עם תמיכה בשני שפות +דו כיווניות, והכל עם המנגנון הרשמי לפי הספר).
טוב אחרי עיון של כמה דקות בתיעוד לא לגמרי הצלחתי להבין איך עושים, לעומת את של @pcinfogmach שכן הבנתי תוך דקה,
ואני חושד שבאמת יש מורכבות יתר בפתרון המובנה ואולי לכן @pcinfogmach לא השתמש בו. -
גם לו יצוייר שיהיה אי פעם תור כדי לטפוח למייקרוסופט על הגב - אני בטח לא אהיה מהראשונים
אין לי בעיה שמישהו ממציא משהו קיים אם יש לזה ערך מוסף, אני בעצמי המצאתי כמה גלגלים מחדש (למרות שחלקם יצאו מרובעים...)
אני מסכים שיש למנגנון התרגום המובנה חסרונות:- קבצי ה-resource לא ניתנים לעריכה בעורך טקסט, בניגוד ל-json. אבל לענ"ד זה חיסרון שבא לידי ביטוי רק בכמות גדולה של מחרוזות, כשרוצים לסקור כמה שפות במקביל, ובמקרה הנ"ל, אף @pcinfogmach ציין שהפתרון שלו לוקה בחסר.
- לא בדיוק חסרון במנגנון, אבל סתם מעצבן, שבגרסת ה-preview של VS הם ניסו ליצור כלי יותר נוח לתחזוקה של כל השפות במקביל, רק שלא ידעתי שאין תאימות אחורה לגרסה היציבה של VS, וה-resources לא ייפתחו שם אח"כ...
למרות האמור לעיל, אחרי עקומת הלמידה של ה-setup הראשוני, היישום בקוד ממש נוח לדעתי, ואני משתמש בזה תדיר.
-
@OdedDvir
json הוא פורמט עבודה נוח וגמיש יותר בשבילי, את שאר הנימוקים כבר כתבתי וכתבו אחרים למעלה. כל אחד לפי טעמו כמובן. אני לא יכול להכחיש שיש איזושהי מעלה בפיתרון המובנה אישית זה היה מריטת עצבים עד שנמאס לי והלכתי על כיוון אחר. כשאתה בונה כמה פרוייקטים במקביל הפעולות המכניות הזוטרות האלה שחוזרים על עצמם שוב ושוב מתחילים להציק לך מאוד הם מפריעם לזרימה ולכיף שבתכנות. אז בחרתי בדרך קצת פחות מציקה.
נקוט האי כללא בידך (בעירבון מוגבל): כל דבר שאפשר לעשות עליו העתק הדבק ולמחזר אותו עבור הפרוייקט הבא שלך שווה זהב. תרגומים של פקדים שהרבה פעמים חוזרים על עצמם שווים זהב ב-json. -
@pcinfogmach כתב במדריך: איך לייצר לוקליזציה ב-wpf בצורה פשוטה וקלילה:
כשאתה בונה כמה פרוייקטים במקביל הפעולות המכניות הזוטרות האלה שחוזרים על עצמם שוב ושוב מתחילים להציק לך מאוד
הפתרון הוא לכתוב ספרייה משותפת שמאגדת את כל הבסיס לפרויקטים שלך, לדוגמא: classים בסיסיים של ViewModel (או reference לספריית MVVM כלשהיא), Localization, Theme, DI וכל מה שאתה משתמש
בכל פרויקט חדש אתה פשוט מוסיף reference לספרייה המשותפת (ואולי עוד כמה שורות קוד), וככה חסכת הרבה זמן בהקמה של הפרויקט