In this article, im going to explain how to create a localized silverlight application that only downloads the culture resources as required and in the process breifly i'll explain
1. On Demand Assembly deployment and the onDemandLoc class
2. ObservableCollection & INotifyCollectionChanged
3. Build xaps in batch file
4. Source Code
1. On Demand Assembly deployment and the onDemandLoc class
On Demand Assembly deployment is useful to increase startup performance of a RIA sites and allows the developer to only load the parts of application a user need in an "on demand" basis. This is of course especially useful when we need to use our culture resource files as well. Imagine 1000 words to download for 20 languages, when all you require in application in one culture. It is a waste of bandwidth and the user patience.
Below is a small class that allows you to download a class based on the current culture in use.
It use the webclient to download a xap of the cultures name it extract the culture dll of your namespace and then loads the assemably into memory.
public class onDemandLoc
{
private static ICollection<string> LoadedLang;
private string CurrentLCID;
static onDemandLoc() {
LoadedLang = App.getlang();
}
internal void LoadLocDll()
{
CurrentLCID = System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
System.Net.WebClient wc = new System.Net.WebClient();
wc.OpenReadCompleted += new OpenReadCompletedEventHandler(OpenXAP);
wc.OpenReadAsync(new Uri(CurrentLCID + ".xap", UriKind.Relative));
}
internal void OpenXAP(object sender, OpenReadCompletedEventArgs e)
{
if ((e.Error == null) && (e.Cancelled == false))
{
Assembly a = LoadAssemblyFromXap(App.Current.GetType().Namespace + "." + CurrentLCID + ".resources.dll", e.Result);
LoadedLang.Add(CurrentLCID);
}
}
internal Assembly LoadAssemblyFromXap(string relativeUriString, Stream xapPackageStream)
{
Uri uri = new Uri(relativeUriString, UriKind.Relative);
StreamResourceInfo xapPackageSri = new StreamResourceInfo(xapPackageStream, null);
StreamResourceInfo assemblySri = Application.GetResourceStream(xapPackageSri, uri);
AssemblyPart assemblyPart = new AssemblyPart();
Assembly a = assemblyPart.Load(assemblySri.Stream);
return a;
}
}
2. ObservableCollection & INotifyCollectionChanged
Once the assembly is loaded into memory it can be used, but i also add the culture to our list of loaded cultures to avoid re-downloading the same culture twice. I used an ObservableCollection<T> class and used the INotifyCollectionChanged event to trigger the event that the culture is downloaded and ready for use and thus we can safely refresh our page content using the new culture strings. Im not sure this is the best method, but i wanted to make it as re-useable as possible. I would love to hear suggestions of how others do it.
private static ObservableCollection<string> LoadedLang;
public App()
{
this.Startup += this.Application_Startup;
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;
InitializeComponent();
LoadedLang = GetNewCollection();
INotifyCollectionChanged collection = LoadedLang as INotifyCollectionChanged;
collection.CollectionChanged += new NotifyCollectionChangedEventHandler(LocCollectionChangedEvt);
}
void LocCollectionChangedEvt(object sender, NotifyCollectionChangedEventArgs e)
{
Page page1 = new Page();
((Page)this.RootVisual).LayoutRoot.Children.Clear();
((Page)this.RootVisual).LayoutRoot.Children.Add(page1);
}
public static ObservableCollection<string> GetNewCollection()
{
return new ObservableCollection<string>();
}
public static ICollection<string> getlang()
{
return LoadedLang;
}
3. Build xaps in batch file
Lastly we need to build out language xaps using the Visual Studio post build event and a small batch file so the user can download, remember if the user culture does not exist it fall back to english. I use some helper vbs file to help create and to zip files. Some people said in the past they had issues with the vbs zip functions i updated them a little and hopefully they are more intl friendly
for %%i in (da de fr ja nl) do (
if exist "$(TargetDir)%%i\%%i.zip" del "$(TargetDir)%%i\%%i.zip"
cscript /nologo "$(SolutionDir)$(ProjectName)\createzip.vbs" "$(TargetDir)%%i\%%i.zip"
echo ^<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" ^>^<Deployment.Parts^>^<AssemblyPart Source="$(ProjectName).%%i.resources.dll" /^>^</Deployment.Parts^>^</Deployment^> > "$(TargetDir)%%i\appmanifest.xaml"
cscript /nologo "$(SolutionDir)$(ProjectName)\addfiletozip.vbs" "$(TargetDir)%%i\%%i.zip" "$(TargetDir)%%i\appmanifest.xaml"
if exist "$(TargetDir)%%i\$(ProjectName).%%i.resources.dll" del "$(TargetDir)%%i\$(ProjectName).%%i.resources.dll"
move /Y "$(TargetDir)%%i\$(ProjectName).resources.dll" "$(TargetDir)%%i\$(ProjectName).%%i.resources.dll"
cscript /nologo "$(SolutionDir)$(ProjectName)\addfiletozip.vbs" "$(TargetDir)%%i\%%i.zip" "$(TargetDir)%%i\$(ProjectName).%%i.resources.dll"
move /Y "$(TargetDir)%%i\%%i.zip" "$(SolutionDir)$(ProjectName)Web\ClientBin\%%i.xap"
del "$(TargetDir)%%i\appmanifest.xaml"
del "$(TargetDir)%%i\$(ProjectName).%%i.resources.dll"
)
4. Source Code