דילוג לתוכן
  • דף הבית
  • קטגוריות
  • פוסטים אחרונים
  • משתמשים
  • חיפוש
  • חוקי הפורום
כיווץ
תחומים

תחומים - פורום חרדי מקצועי

💡 רוצה לזכור קריאת שמע בזמן? לחץ כאן!
  1. דף הבית
  2. תכנות
  3. מדריך: איך לייצר לוקליזציה ב-wpf בצורה פשוטה וקלילה

מדריך: איך לייצר לוקליזציה ב-wpf בצורה פשוטה וקלילה

מתוזמן נעוץ נעול הועבר תכנות
16 פוסטים 4 כותבים 832 צפיות
  • מהישן לחדש
  • מהחדש לישן
  • הכי הרבה הצבעות
התחברו כדי לפרסם תגובה
נושא זה נמחק. רק משתמשים עם הרשאות מתאימות יוכלו לצפות בו.
  • OdedDvirO מנותק
    OdedDvirO מנותק
    OdedDvir
    כתב ב נערך לאחרונה על ידי
    #6

    גם לו יצוייר שיהיה אי פעם תור כדי לטפוח למייקרוסופט על הגב - אני בטח לא אהיה מהראשונים
    אין לי בעיה שמישהו ממציא משהו קיים אם יש לזה ערך מוסף, אני בעצמי המצאתי כמה גלגלים מחדש (למרות שחלקם יצאו מרובעים...)
    אני מסכים שיש למנגנון התרגום המובנה חסרונות:

    1. קבצי ה-resource לא ניתנים לעריכה בעורך טקסט, בניגוד ל-json. אבל לענ"ד זה חיסרון שבא לידי ביטוי רק בכמות גדולה של מחרוזות, כשרוצים לסקור כמה שפות במקביל, ובמקרה הנ"ל, אף @pcinfogmach ציין שהפתרון שלו לוקה בחסר.
    2. לא בדיוק חסרון במנגנון, אבל סתם מעצבן, שבגרסת ה-preview של VS הם ניסו ליצור כלי יותר נוח לתחזוקה של כל השפות במקביל, רק שלא ידעתי שאין תאימות אחורה לגרסה היציבה של VS, וה-resources לא ייפתחו שם אח"כ...

    למרות האמור לעיל, אחרי עקומת הלמידה של ה-setup הראשוני, היישום בקוד ממש נוח לדעתי, ואני משתמש בזה תדיר.

    pcinfogmachP תגובה 1 תגובה אחרונה
    1
    • pcinfogmachP לא נמצא
      pcinfogmachP לא נמצא
      pcinfogmach
      השיב לOdedDvir ב נערך לאחרונה על ידי pcinfogmach
      #7

      @OdedDvir
      json הוא פורמט עבודה נוח וגמיש יותר בשבילי, את שאר הנימוקים כבר כתבתי וכתבו אחרים למעלה. כל אחד לפי טעמו כמובן. אני לא יכול להכחיש שיש איזושהי מעלה בפיתרון המובנה אישית זה היה מריטת עצבים עד שנמאס לי והלכתי על כיוון אחר. כשאתה בונה כמה פרוייקטים במקביל הפעולות המכניות הזוטרות האלה שחוזרים על עצמם שוב ושוב מתחילים להציק לך מאוד הם מפריעם לזרימה ולכיף שבתכנות. אז בחרתי בדרך קצת פחות מציקה.
      נקוט האי כללא בידך (בעירבון מוגבל): כל דבר שאפשר לעשות עליו העתק הדבק ולמחזר אותו עבור הפרוייקט הבא שלך שווה זהב. תרגומים של פקדים שהרבה פעמים חוזרים על עצמם שווים זהב ב-json.

      גמ"ח מידע מחשבים ואופיס

      קומפיונטק תגובה 1 תגובה אחרונה
      3
      • קומפיונטק מנותק
        קומפיונטק מנותק
        קומפיונט
        השיב לpcinfogmach ב נערך לאחרונה על ידי קומפיונט
        #8

        @pcinfogmach כתב במדריך: איך לייצר לוקליזציה ב-wpf בצורה פשוטה וקלילה:

        כשאתה בונה כמה פרוייקטים במקביל הפעולות המכניות הזוטרות האלה שחוזרים על עצמם שוב ושוב מתחילים להציק לך מאוד

        הפתרון הוא לכתוב ספרייה משותפת שמאגדת את כל הבסיס לפרויקטים שלך, לדוגמא: classים בסיסיים של ViewModel (או reference לספריית MVVM כלשהיא), Localization, Theme, DI וכל מה שאתה משתמש

        בכל פרויקט חדש אתה פשוט מוסיף reference לספרייה המשותפת (ואולי עוד כמה שורות קוד), וככה חסכת הרבה זמן בהקמה של הפרויקט

        תגובה 1 תגובה אחרונה
        1
        • pcinfogmachP לא נמצא
          pcinfogmachP לא נמצא
          pcinfogmach
          השיב לקומפיונט ב נערך לאחרונה על ידי pcinfogmach
          #9

          @קומפיונט כתב במדריך: איך לייצר לוקליזציה ב-wpf בצורה פשוטה וקלילה:

          תנסה ליישם משהו שמאפשר לשנות את השפה בצורה דינאמית, מבלי להפעיל מחדש

          אחרי תקופה הייתי צריך משהו כזה מצו"ב הקוד:
          את קבצי השפה של ה-json יש להכניס לתוך תיקייה בשם "Locale".
          קישור לפרוייקט דוגמא

          אשמח לקבל משוב

          using System;
          using System.Collections.Concurrent;
          using System.Collections.Generic;
          using System.IO;
          using System.Linq;
          using System.Reflection;
          using System.Text.Json;
          using System.Windows;
          using System.Windows.Controls;
          using System.Windows.Input;
          
          namespace Localization
          {
              public static class LocalizationExtension
              {
                  private static string _locale = "en";
                  private static readonly string _localeFolder = "Locale";
                  private static ConcurrentDictionary<string, string> translations;
                  private static readonly List<WeakReference<DependencyObject>> RegisteredElements = new List<WeakReference<DependencyObject>>();
          
                  public static string Locale
                  {
                      get => _locale;
                      set
                      {
                          if (value != _locale) 
                          {
                              _locale = value; 
                              UpdateAllRegisteredElements();
                          }               
                      }
                  }
          
                  public static readonly DependencyProperty KeyProperty =
                     DependencyProperty.RegisterAttached(
                         "Key",
                         typeof(string),
                         typeof(LocalizationExtension),
                         new PropertyMetadata(null, OnKeyChanged));
          
                  public static void SetKey(DependencyObject element, string value) =>
                      element.SetValue(KeyProperty, value);
          
                  public static string GetKey(DependencyObject element) =>
                      (string)element.GetValue(KeyProperty);
          
                  private static void OnKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
                  {
                      if (d == null) return;
                      SetText(d, (string)e.NewValue);
          
                      CleanupRegisteredElements();
                      RegisteredElements.Add(new WeakReference<DependencyObject>(d));
                      
                  }
          
                  public static RelayCommand<string> ChangeLocaleCommand = new RelayCommand<string>((value) => { Locale = value; } );
                  public static RelayCommand NextLocaleCommand = new RelayCommand(() => { NextLocale(); });
                  public static void NextLocale()
                  {
                      string localeFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), _localeFolder);
                      if (Directory.Exists(localeFolder))
                      {
                          var locales = Directory.GetFiles(localeFolder, "*.json")
                                                  .Select(Path.GetFileNameWithoutExtension)
                                                  .ToList();
          
                          if (locales.Count == 0)
                              return;
          
                          int currentIndex = locales.IndexOf(Locale);
          
                          if (currentIndex == -1 || currentIndex == locales.Count - 1)
                              Locale = locales[0];
                          else
                              Locale = locales[currentIndex + 1];
                      }
                  }
          
          
          
                  private static void UpdateAllRegisteredElements()
                  {
                      LoadTranslations();
                      CleanupRegisteredElements();
          
                      foreach (var weakReference in RegisteredElements)
                      {
                          if (weakReference.TryGetTarget(out var target))
                          {
                              var key = GetKey(target);
                              SetText(target, key);
                          }
                      }
                  }
          
                  private static void CleanupRegisteredElements() =>
                      RegisteredElements.RemoveAll(wr => !wr.TryGetTarget(out _));
          
                  public static void LoadTranslations()
                  {
                      string filePath = GetLocaleFilePath();
          
                      try
                      {
                          string json = File.ReadAllText(filePath);
                          translations = JsonSerializer.Deserialize<ConcurrentDictionary<string, string>>(json) ?? new ConcurrentDictionary<string, string>();
                      }
                      catch
                      {
                          translations = new ConcurrentDictionary<string, string>();
                      }
          
                  }
          
                  static string GetLocaleFilePath()
                  {
                      string localeFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), _localeFolder);
                      string filePath = Path.Combine(localeFolder, _locale + ".json");
          
                      if (!File.Exists(filePath))
                      {
                          if (Directory.Exists(localeFolder))
                          {
                              string[] files = Directory.GetFiles(localeFolder, "*.json");
                              if (files.Length > 0)
                                  filePath = filePath = files[0];
                              else
                                  filePath = null;
                          }
                          else
                              filePath = null;
                      }
          
                      return filePath;
                  }
          
                  public static void SetText(DependencyObject d, string key)
                  {
                      if (d == null || string.IsNullOrEmpty(key))
                          return;
          
                      if (translations == null)
                          LoadTranslations();
          
                      if (translations.TryGetValue(key, out var translation))
                      {
                          switch (d)
                          {
                              case ContentControl contentControl:
                                  contentControl.Content = translation;
                                  break;
                              default:
                                  var textProperty = d.GetType().GetProperty("Text");
                                  textProperty?.SetValue(d, translation);
                                  break;
                          }
                      }
                  }
              }
          
          

          הקוד משתמש ב-class שנקרא RelayCommand

              public class RelayCommand : ICommand
              {
                  private readonly Action _execute;
                  private readonly Func<bool> _canExecute;
          
                  public RelayCommand(Action execute, Func<bool> canExecute = null)
                  {
                      _execute = execute ?? throw new ArgumentNullException(nameof(execute));
                      _canExecute = canExecute;
                  }
          
                  public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
                  public void Execute(object parameter) => _execute();
          
                  public event EventHandler CanExecuteChanged
                  {
                      add => CommandManager.RequerySuggested += value;
                      remove => CommandManager.RequerySuggested -= value;
                  }
              }
          
              public class RelayCommand<T> : ICommand
              {
                  private readonly Action<T> _execute;
                  private readonly Func<T, bool> _canExecute;
          
                  public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
                  {
                      _execute = execute ?? throw new ArgumentNullException(nameof(execute));
                      _canExecute = canExecute;
                  }
          
                  public bool CanExecute(object parameter) => _canExecute?.Invoke((T)parameter) ?? true;
          
                  public void Execute(object parameter) => _execute((T)parameter);
          
                  public event EventHandler CanExecuteChanged
                  {
                      add => CommandManager.RequerySuggested += value;
                      remove => CommandManager.RequerySuggested -= value;
                  }
              }
          }
          

          דוגמת שימוש:

          <Button Width="200" Height="50"
                  locale:LocalizationExtension.Key="LoginButton"
                  Command="{x:Static locale:LocalizationExtension.NextLocaleCommand}"  
          

          גמ"ח מידע מחשבים ואופיס

          קומפיונטק תגובה 1 תגובה אחרונה
          1
          • קומפיונטק מנותק
            קומפיונטק מנותק
            קומפיונט
            השיב לpcinfogmach ב נערך לאחרונה על ידי קומפיונט
            #10

            @pcinfogmach מהקוד זה נראה שאתה שומר reference לכל הפקדים שצריכים להיות עם טקסט דינאמי, ומשנה אותם בהתאם מתי שצריך.

            אתה יכול במקום זה להשתמש עם DynamicResource ובכל פעם שמוחלפת השפה לשנות את ה - Resrouce של השפה ב - Application.Current.Resources.

            pcinfogmachP תגובה 1 תגובה אחרונה
            0
            • pcinfogmachP לא נמצא
              pcinfogmachP לא נמצא
              pcinfogmach
              השיב לקומפיונט ב נערך לאחרונה על ידי pcinfogmach
              #11

              @קומפיונט כתב במדריך: איך לייצר לוקליזציה ב-wpf בצורה פשוטה וקלילה:

              @pcinfogmach מהקוד זה נראה שאתה שומר reference לכל הפקדים שצריכים להיות עם טקסט דינאמי, ומשנה אותם בהתאם מתי שצריך.

              אתה יכול במקום זה להשתמש עם DynamicResource ובכל פעם שמוחלפת השפה לשנות את ה - Resrouce של השפה ב - Application.Current.Resources.

              0

              אכן זה עובד יפה ומייצר קוד הרבה יותר טוב. תודה.

              אשמח אם תוכל לענות לי על כמה שאלות:

              אני לא מצליח לטעון את זה ב-DesignTime זה מאוד מקשה על הפיתוח, מבחינת שגיאות לא אמיתיות ועוד ועוד. אלא אם כן אני ייצר rsourcedictionary ב-xaml כמו שמתארים פה ואז אני מאבד את כל הרעיון כי הרעיון היה לייצר קוד אחד שמתאים להרבה אפליקציות עם טעינה מקובץ json

              האם Application.Current.Resources. זמין ב-Vsto (תוספים לאופיס) שזה בעצם אפליקצייה של winforms שתומכת ב-wpf.

              להלן הקוד שיצרתי עם זה.

              using Localization;
              using System.Collections.Generic;
              using System.IO;
              using System.Linq;
              using System.Reflection;
              using System.Text.Json;
              using System.Windows;
              
              namespace localization
              {
                  public static class LocaleResouceManager
                  {
                      private static string _locale = "en";
                      private static readonly string _localeFolder = "Locale";
              
                      public static string Locale
                      {
                          get => _locale;
                          set
                          {
                              if (value != _locale)
                              {
                                  _locale = value;
                                  UpdateResources();
                              }
                          }
                      }
              
                      public static void UpdateResources()
                      {
                          string filePath = GetLocaleFilePath();
              
                          if (filePath == null)
                              return;
              
                          try
                          {
                              string json = File.ReadAllText(filePath);
                              var translations = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
              
                              if (translations != null)
                              {
                                  var applicationResources = Application.Current.Resources;
              
                                  foreach (var key in translations.Keys)
                                  {
                                      if (applicationResources.Contains(key))
                                          applicationResources[key] = translations[key];
                                      else
                                          applicationResources.Add(key, translations[key]);
                                  }
                              }
                          }
                          catch
                          {
                              // Handle errors (e.g., log or display error)
                          }
                      }
              
                      public static void NextLocale()
                      {
                          string localeFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), _localeFolder);
                          if (Directory.Exists(localeFolder))
                          {
                              var locales = Directory.GetFiles(localeFolder, "*.json")
                                                      .Select(Path.GetFileNameWithoutExtension)
                                                      .ToList();
              
                              if (locales.Count == 0)
                                  return;
              
                              int currentIndex = locales.IndexOf(Locale);
              
                              if (currentIndex == -1 || currentIndex == locales.Count - 1)
                                  Locale = locales[0];
                              else
                                  Locale = locales[currentIndex + 1];
                          }
                      }
              
                      private static string GetLocaleFilePath()
                      {
                          string localeFolder = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), _localeFolder);
                          string filePath = Path.Combine(localeFolder, _locale + ".json");
              
                          if (!File.Exists(filePath))
                          {
                              if (Directory.Exists(localeFolder))
                              {
                                  string[] files = Directory.GetFiles(localeFolder, "*.json");
                                  filePath = files.Length > 0 ? files[0] : null;
                              }
                              else
                              {
                                  filePath = null;
                              }
                          }
              
                          return filePath;
                      }
                  }
              }
              
              

              גמ"ח מידע מחשבים ואופיס

              pcinfogmachP תגובה 1 תגובה אחרונה
              0
              • pcinfogmachP לא נמצא
                pcinfogmachP לא נמצא
                pcinfogmach
                השיב לpcinfogmach ב נערך לאחרונה על ידי pcinfogmach
                #12

                @pcinfogmach כתב במדריך: איך לייצר לוקליזציה ב-wpf בצורה פשוטה וקלילה:

                האם Application.Current.Resources. זמין ב-Vsto (תוספים לאופיס) שזה בעצם אפליקצייה של winforms שתומכת ב-wpf.

                כאן יש כמה רעיונות:
                https://drwpf.com/blog/2007/10/05/managing-application-resources-when-wpf-is-hosted/

                רק נשאר הבעיה המצבנת של designtime

                גמ"ח מידע מחשבים ואופיס

                תגובה 1 תגובה אחרונה
                0
                • pcinfogmachP לא נמצא
                  pcinfogmachP לא נמצא
                  pcinfogmach
                  כתב נערך לאחרונה על ידי pcinfogmach
                  #13

                  סקיצה סופית על ידי markupExtension ו-יצירת binding דינאמי. עובד גם על ב-designtime.

                  LocaleDictionary.cs
                  LocaleExtension.cs
                  RelayCommand.cs

                  זמין גם בגיטהאב
                  https://github.com/pcinfogmach/Wpf.Localization

                  דוגמת שימוש

                  <Window x:Class="localizationTestApp.MainWindow"
                          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                          xmlns:local="clr-namespace:localizationTestApp"
                          xmlns:loc="clr-namespace:localization;assembly=localization"
                          mc:Ignorable="d"
                          Title="MainWindow" Height="450" Width="800">
                  
                      <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
                  
                          <Button Width="200" Height="50"
                                  Content="{loc:LocaleExtension Text=Login}"
                                  Command="{x:Static loc:LocaleDictionary.NextLocaleCommand}"/>
                  
                          <Button Width="200" Height="50"
                            Content="Load Hebrew"
                            Command="{x:Static loc:LocaleDictionary.ChangeLocaleCommand}"
                                  CommandParameter="he"/>
                          
                          <TextBlock Margin="10" 
                                     Text="{loc:LocaleExtension Text=Welcome}"/>
                  
                          <TextBox Width="200"
                                   Text="{loc:LocaleExtension Text=Placeholder}"/>
                  
                          <ComboBox Width="200" IsEditable="True"
                                    ItemsSource="{x:Static loc:LocaleDictionary.LocaleList}"
                                    Text="{Binding Path=(loc:LocaleDictionary.Locale)}"/>
                      </StackPanel>
                  </Window>
                  
                  

                  גמ"ח מידע מחשבים ואופיס

                  קומפיונטק תגובה 1 תגובה אחרונה
                  0
                  • קומפיונטק מנותק
                    קומפיונטק מנותק
                    קומפיונט
                    השיב לpcinfogmach נערך לאחרונה על ידי
                    #14

                    @pcinfogmach אתה יכול לקצר את loc:LocaleExtension ל - loc:Locale, הסיומת Extension לא נצרכת.
                    ואגב למה אתה משתמש ב - ConcurrentDictionary ולא ב - Dictionary רגיל?

                    pcinfogmachP תגובה 1 תגובה אחרונה
                    1
                    • pcinfogmachP לא נמצא
                      pcinfogmachP לא נמצא
                      pcinfogmach
                      השיב לקומפיונט נערך לאחרונה על ידי pcinfogmach
                      #15

                      @קומפיונט
                      שוב תודה על העזרה המדהימה
                      מה ההסבר למה בעצם אני יכול לקצר את זה? הרי הוא נמצא בתוך תיקייה וnamespace שנקראת locale זה לא יגרום בעיות?

                      השתמשתי ב-oncurrent dictionary כי לא ידעתי אם יכול להיות בעיות עם Threding אזמח לשמוע אם אתה בטוח שזה לא נצרך.

                      גמ"ח מידע מחשבים ואופיס

                      קומפיונטק תגובה 1 תגובה אחרונה
                      0
                      • קומפיונטק מנותק
                        קומפיונטק מנותק
                        קומפיונט
                        השיב לpcinfogmach נערך לאחרונה על ידי קומפיונט
                        #16

                        @pcinfogmach אתה יכול לקצר את זה כמו שאתה כותב StaticResource ,Binding וכו, והשם של הקלאס הוא StaticResourceExtension ,BindingExtension

                        ה - ConcurrentDictionary מיותר כי ממילא יש ל UI רק thread חוקי אחד, וכל האירועים שאתה מקבל כגון לחיצה על כפתור רצים על אותו thread ראשי.

                        תגובה 1 תגובה אחרונה
                        1

                        בא תתחבר לדף היומי!
                        • התחברות

                        • אין לך חשבון עדיין? הרשמה

                        • התחברו או הירשמו כדי לחפש.
                        • פוסט ראשון
                          פוסט אחרון
                        0
                        • דף הבית
                        • קטגוריות
                        • פוסטים אחרונים
                        • משתמשים
                        • חיפוש
                        • חוקי הפורום