نقل قول نوشته شده توسط Amir Oveisi مشاهده تاپیک
PDF‌:‌دارد
نمونه کد: دارد

مقدمه
با گسترش استفاده از كامپيوتر در بسياري از امور روزمره انسان ها سازگار بودن برنامه ها با سليقه كاربران به يكي از نياز هاي اصلي برنامه هاي كامپيوتري تبديل شده است. بدون شك زبان و فرهنگ يكي از مهم ترين عوامل در ايجاد ارتباط نزديك بين برنامه و كاربر به شمار مي رود و نقشي غير قابل انكار در ميزان موفقيت يك برنامه به عهده دارد. از اين رو در اين نوشته تلاش بر آن است تا يكي از ساده ترين و در عين حال كارا ترين راه هاي ممكن براي ايجاد برنامه هاي چند زبانه با استفاده از تكنولوژي WPF آموزش داده شود.

مروري بر روش هاي موجود
همواره روش هاي مختلفي براي پياده سازي يك ايده در دنياي نرم افزار وجود دارد كه هر روش را مي توان بر حسب نياز مورد استفاده قرار داد. در برنامه هاي مبتني بر WPF معمولا از دو روش عمده براي اين منظور استفاده مي شود:
1- استفاده از فايل هاي .resx
در اين روش كه براي Win App نيز استفاده مي شود، اطلاعات مورد نياز براي هر زبان به شكل جدول هايي داراي كليد و مقدار در داخل يك فايل .resx نگهداري مي شود و در زمان اجراي برنامه بر اساس انتخاب كاربر اطلاعات زبان مورد نظر از داخل فايل .resx load شده و نمايش داده مي شود. يكي از ضعف هايي كه اين روش در عين ساده بودن دارد اين است كه همه اطلاعات مورد نياز داخل assembly اصلي برنامه قرار مي گيرد و امكان افزودن زبان هاي جديد بدون تغيير دادن برنامه اصلي ممكن نخواهد بود.
2- استفاده از فايل هاي .csv كه به فايل هاي .dll تبديل مي شوند
در اين روش كه مختص برنامه هاي WPF مي باشد با استفاده از ابزار هاي موجود در كامپايلر WPF براي هر كنترل يك property به نام Uid ايجاد شده و مقدار دهي مي شود. سپس با ابزار ديگري ( كه جزو ابزار هاي كامپايلر محسوب نمي شود ) از فايل .csproj پروژه يك خروجي اكسل با فرمت .csv ايجاد مي شود كه شامل Uid هاي كنترل ها و مقادير آن ها است. پس از ترجمه متون مورد نظر به زبان مقصد با كمك ابزار ديگري فايل اكسل مورد نظر به يك .net assembly تبديل مي شود و داخل پوشه اي با نام culture استاندارد ذخيره مي شود. ( مثلا براي زبان فارسي نام پوشه fa-IR خواهد بود ). زماني كه برنامه اجرا مي شود بر اساس culture اي كه در سيستم عامل انتخاب شده است و در صورتي كه براي آن culture فايل .dll اي موجود باشد، زبان مربوط به آن culture را load خواهد كرد. با وجود اين كه اين روش مشكل روش قبلي را ندارد و بيشتر با ويژگي هاي WPF سازگار است اما پروسه اي طولاني براي انجام كار ها دارد و به ازاي هر تغييري بايد كل مراحل هر بار تكرار شوند. همچنين مشكلاتي در نمايش برخي زبان ها ( از جمله فارسي ) در اين روش مشاهده شده است.

روش سوم!
روش سوم اما كاملا بر پايه WPF و در اصطلاح WPF-Native مي باشد. ايده از آنجا ناشي شده است كه براي ايجاد skin در برنامه هاي WPF استفاده مي شود. در ايجاد برنامه هاي Skin-Based به اين شيوه عمل مي شود كه skin هاي مورد نظر به صورت style هايي در داخل resource dictionary ها قرار مي گيرند. سپس آن resource dictionary به شكل .dll كامپايل مي شود. در برنامه اصلي نيز همه كنترل ها style هايشان را به شكل dynamic resource از داخل يك resource dictionary مشخص شده load مي كنند. حال كافي است براي تغيير skin، resource dictionary مورد نظر از .dll مشخص load شود و resource dictionary اي كه در حال حاضر در برنامه از آن استفاده مي شود با resource dictionary اي كه load شده جايگزين شود. كنترل ها مقادير جديد را از resource dictionary جديد به شكل كاملا خودكار دريافت خواهند كرد.
خوب! به سادگي مي توان از اين روش براي تغيير زبان برنامه نيز استفاده كرد.

پياده سازي
در اين قسمت نحوه پياده سازي اين روش با ايجاد يك نمونه برنامه ساده كه داراي دو زبان انگليسي و فارسي خواهد بود آموزش داده مي شود.
ابتدا يك پروژه WPF Application در Visual Studio 2010 ايجاد كنيد. در MainWindow سه كنترل Button قرار دهيد و يك ComboBox كه قرار است زبان هاي موجود را نمايش دهد و با انتخاب يك زبان، نوشته هاي درون Button ها متناسب با آن تغيير خواهند كرد.


توجه داشته باشيد كه براي Button ها نبايد به صورت مستقيم مقداري به Content شان داده شود. بلكه بايد مقدار مورد نظر از داخل resource dictionary كه خواهيم ساخت به شكل dynamic گرفته خواهد شد. پس در اين مرحله يك ResourceDictionary به پروژه اضافه خواهيم كرد و در آن resource هايي به شكل string ايجاد خواهيم كرد. هر resource داراي يك Key مي باشد كه بر اساس آن، Button مورد نظر مقدار آن Resource را load خواهد كرد. فايل ResourceDictionary را Culture_en-US.xaml نامگذاري كنيد و مقادير مورد نظر را به آن اضافه نماييد.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="button1">Hello!</system:String>
<system:String x:Key="button2">How Are You?</system:String>
<system:String x:Key="button3">Are You OK?</system:String>

</ResourceDictionary>


دقت كنيد كه namespace اي كه كلاس string در آن قرار دارد به فايل xaml اضافه شده است.

پس از اين كار فايل App.xaml به اين شكل خواهد بود:
<Application x:Class="BeRMOoDA.WPF.LocalizationSample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>

<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Culture_en-US.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

</Application.Resources>
</Application>


براي استفاده از اين resource ها به عنوان Content براي Button ها نيز آن ها را به صورت DynamicResource، load خواهيم كرد:
<Button Content="{DynamicResource ResourceKey=button1}" />
<Button Content="{DynamicResource ResourceKey=button2}" />
<Button Content="{DynamicResource ResourceKey=button3}" />


بسيار خوب! اكنون بايد شروع به ايجاد يك ResourceDictionary براي زبان فارسي كنيم و آن را به صورت يك فايل .dll كامپايل نماييم.
براي اين كار يك پروژه جديد در قسمت WPF از نوع User control ايجاد مي كنيم و نام آن را Culture_fa-IR_Farsi قرار مي دهيم. لطفا شيوه نامگذاري را رعايت كنيد چرا كه در ادامه به آن نياز خواهيم داشت.
پس از ايجاد پروژه فايل UserControl1.xaml را از پروژه حذف كنيد و يك ResourceDictionary با نام Culture_fa-IR.xaml اضافه كنيد. محتواي آن را پاك كنيد و محتواي فايل Culture_en-US.xaml را از پروژه قبلي به صورت كامل در فايل جديد كپي كنيد. دو فايل بايد ساختار كاملا يكساني از نظر key براي resource هاي موجود داشته باشند. حالا زمان ترجمه فرا رسيده است! رشته هاي دلخواه را ترجمه كنيد و پروژه را build نماييد.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Culture_fa-IR_Farsi.xaml"/>
</ResourceDictionary.MergedDictionaries>
<system:String x:Key="button1">سلام!</system:String>
<system:String x:Key="button2">حالت چطوره؟</system:String>
<system:String x:Key="button3">خوبی؟</system:String>
</ResourceDictionary>


خروجي يك فايل با نام Culture_fa-IR_Farsi.dll خواهد بود.

در اين مرحله كاري كه بايد انجام دهيم چيست؟
راهكاري ارئه دهيم تا بتوان فايل هاي .dll مربوط به زبان ها را در زمان اجراي برنامه load كرده و نام زبان ها را در داخل ComboBox اي كه داريم نشان دهد. سپس با انتخاب هر زبان در ComboBox، محتواي Button ها بر اساس زبان انتخاب شده تغيير كند.
براي سهولت كار، نام فايل ها را به گونه اي انتخاب كرديم كه بتوانيم ساده تر به اين هدف برسيم. نام هر فايل از سه بخش تشكيل شده است:
Culture_[standard culture notation]_[display name for this culture].dll
يعني اگر فايل Culture_fa-IR_Farsi.dll را در نظر بگيريم، Culture نشان دهنده اين است كه اين فايل مربوط به يك culture مي باشد. fa-IR نمايش استاندارد culture براي كشور ايران و زبان فارسي است و Farsi هم مقداري است كه مي خواهيم در ComboBox براي اين زبان نمايش داده شود.
پوشه اي با نام Languages در كنار فايل .exe برنامه ايجاد كنيد و فايل Culture_fa-IR_Farsi.dll را درون آن كپي كنيد. تصميم داريم همه .dll هاي مربوط به زبان ها را داخل اين پوشه قرار دهيم تا مديريت آن ها ساده تر شود.
براي مديريت بهتر فايل هاي مربوط به زبان ها يك كلاس با نام CultureAssemblyModel خواهيم ساخت كه هر object از آن نشانگر يك فايل زبان خواهد بود. يك كلاس با اين نام به پروژه اضافه كنيد و property هاي زير را در آن تعريف نماييد:
public class CultureAssemblyModel
{
//the text will be displayed to user as language name (like Farsi)
public string DisplayText { get; set; }
//name of .dll file (like Culture_fa-IR_Farsi.dll)
public string Name { get; set; }
//standar notation of this culture (like fa-IR)
public string Culture { get; set; }
//name of resource dictionary file name inside the loaded .dll (like Culture_fa-IR.xaml)
public string XamlFileName { get; set; }
}


براي ComboBox نيز يك DataTemplate تعريف مي كنيم تا فقط فيلد DisplayText از اين كلاس را نمايش دهد:
<ComboBox HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" MinWidth="100" Name="comboboxLanguages">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayText}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>


براي load كردن نام فايل هاي .dll مربوط به زبان ها متدي تعريف مي كنيم به اسم LoadCultureAssmeblies به شكل زير:
//will keep information about loaded assemblies
public List<CultureAssemblyModel> CultureAssemblies { get; set; }

//loads assmeblies in languages folder and adds their info to list
void LoadCultureAssemblies()
{
//we should be sure that list is empty before adding info (do u want to add some cultures more than one? of course u dont!)
CultureAssemblies.Clear();
//creating a directory represents applications directory\languages
DirectoryInfo dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirector y + "\\languages");
//getting all .dll files in the language folder and its sub dirs. (who knows? maybe someone keeps each culture file in a seperate folder!)
var assemblies = dir.GetFiles("*.dll", SearchOption.AllDirectories);
//for each found .dll we will create a model and set its properties and then add to list for (int i = 0; i < assemblies.Count(); i++)
{
CultureAssemblyModel model = new CultureAssemblyModel() { DisplayText = assemblies[i].Name.Split('.', '_')[2], Culture = assemblies[i].Name.Split('.', '_')[1], Name = assemblies[i].Name, XamlFileName =assemblies[i].Name.Substring(0, assemblies[i].Name.LastIndexOf(".")) + ".xaml" };
CultureAssemblies.Add(model);
}
}


اطلاعات مورد نياز از فايل هاي .dll خوانده شده و در ليست نگهداري مي شوند. براي نمايش دادن نام زبان ها در ComboBox كافي است اين ليست را به عنوان ItemsSource قرار دهيم:
comboboxLanguages.ItemsSource = CultureAssemblies;


با handle كردن رويداد selectionChanged مربوط به ComboBox زبان انتخاب شده را به متد LoadCulture كه در فايل App.cs (كلاس اصلي برنامه) تعريف كرده ايم ارسال مي كنيم:
//loads selected culture
public void LoadCulture(CultureAssemblyModel culture)
{
//setting current culture of applpications main thread to selected one.
System.Threading.Thread.CurrentThread.CurrentUICul ture = new System.Globalization.CultureInfo(culture.Culture);
System.Threading.Thread.CurrentThread.CurrentCultu re = new System.Globalization.CultureInfo(culture.Culture);
//creating a FileInfo object represents .dll file of selected cultur
FileInfo assemblyFile = new FileInfo("languages\\" + culture.Name);
//loading .dll into memory as a .net assembly
var assembly = Assembly.LoadFile(assemblyFile.FullName);
//getting .dll file name
var assemblyName = assemblyFile.Name.Substring(0, assemblyFile.Name.LastIndexOf("."));
//creating string represents structure of a pack uri (something like this: /{myassemblyname;component/myresourcefile.xaml}
string packUri = string.Format(@"/{0};component/{1}", assemblyName, culture.XamlFileName);
//creating a pack uri
Uri uri = new Uri(packUri, UriKind.Relative);
//now we have created a pack uri that represents a resource object in loaded assembly
//and its time to load that as a resource dictionary (do u remember that we had resource dictionary in culture assemblies? don't u?)
var dic = Application.LoadComponent(uri) as ResourceDictionary;
dic.Source = uri;
//here we will remove current merged dictionaries in our resource dictionary and add recently-loaded resource dictionary as e merged dictionary
var mergedDics = this.Resources.MergedDictionaries;
if (mergedDics.Count > 0)
mergedDics.Clear();
mergedDics.Add(dic);
}


void comboboxLanguages_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedCulture = (CultureAssemblyModel)comboboxLanguages.SelectedIt em;
App app = Application.Current as App;
app.LoadCulture(selectedCulture);
}



متد LoadCulture با گرفتن مشخصات زبان ارسال شده، culture سيستم را به زبان مورد نظر تغيير داده و resource dictionary مربوط به آن را از داخل فايل .dll آن زبان load كرده و آن را با resource dictionary فعلي برنامه جايگزين مي كند. Button ها كه محتواي خود را به شكل dynamic از داخل اين resource dictionary دريافت مي كردند حالا از داخل فايل جايگزين شده خواهند گرفت.

كار انجام شد!
از مزيت هاي اين روش مي توان به WPF-Native بودن، سادگي در پياده سازي، قابليت load كردن هر زبان جديدي در زمان اجرا بدون نياز به كوچك ترين تغيير در برنامه اصلي و همچنين پشتيباني كامل از نمايش زبان هاي مختلف از جمله فارسي اشاره كرد.

لینک اول PDF و لینک دوم نمونه برنامه هستند.
دوست عزیز کلا این قسمت رو از فایل های xaml زبون ها وردار
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Culture_fa-IR_Farsi.xaml"/>
</ResourceDictionary.MergedDictionaries>