Silverlight localization from database, not resx - silverlight-4.0

I have a Silverlight 4 OOB application which needs localizing. In the past I have used the conventional resx route but I have been asked to follow the architecture of an existing winforms app.
All the strings are currently stored in a database - I use a webservice to pull these down and write them into a local Effiproz Isolated Storage database. On Login I load a Dictionary object with the language strings for the users language. This works fine.
However, I want to automate the UI localization (the WinForms app does it like this):
Loop through all the controls on the page and look for any Textblocks - if there is a text property I replace it with the localized version. If the text is not found, then I WRITE the string to the database for localization.
This works ok on simple forms but as soon as you have expanders/scrollviewers and content controls then the VisualTree parser does not return the children of the controls as they are not necessarily visible (see my code below). This is a known issue and thwarts my automation attempt.
My first question is: Is there a way of automating this on page load by looping through the complex (non-visual) elements and looking up the value in a dictionary?
My second question is: If not, then is the best way of handling this is to load the strings into an app resource dictionary and change all my pages to reference it, or should I look into generating resx files, either on the server (and package it with the app as per normal) or on the client (I have the downloaded strings, can I make and load resx files?)
Thanks for any pointers.
Here is my existing code that does not work on collapsed elements and complex content controls:
public void Translate(DependencyObject dependencyObject)
{
//this uses the VisualTreeHelper which only shows controls that are actually visible (so if they are in a collapsed expander they will not be returned). You need to call it OnLoaded to make sure all controls have been added
foreach (var child in dependencyObject.GetAllChildren(true))
{
TranslateTextBlock(child);
}
}
private void TranslateTextBlock(DependencyObject child)
{
var textBlock = child as TextBlock;
if (textBlock == null) return;
var value = (string)child.GetValue(TextBlock.TextProperty);
if (!string.IsNullOrEmpty(value))
{
var newValue = default(string);
if (!_languageMappings.TryGetValue(value, out newValue))
{
//write the value back to the collection so it can be marked for translation
_languageMappings.Add(value, string.Empty);
newValue = "Not Translated";
}
child.SetValue(TextBlock.TextProperty, newValue);
}
}
Then I have tried 2 different approaches:
1) Store the strings in a normal dictionary object
2) Store the strings in a normal dictionary object and add it to the Application as a Resource, then you can reference it as
TextBlock Text="{Binding Path=[Equipment], Source={StaticResource ResourceHandler}}"
App.GetApp.DictionaryStrings = new AmtDictionaryDAO().GetAmtDictionaryByLanguageID(App.GetApp.CurrentSession.DefaultLanguageId);
Application.Current.Resources.Add("ResourceHandler", App.GetApp.DictionaryStrings);
//http://forums.silverlight.net/forums/p/168712/383052.aspx

Ok, so nobody answered this and I came up with a solution.
Basically it seems that you can load the language dictionary into your global resources using
Application.Current.Resources.Add("ResourceHandler", App.GetApp.DictionaryStrings);
<TextBlock Text="{Binding [Equipment], Source={StaticResource ResourceHandler}}" />
and then access it like a normal StaticResource. We have the requirement of noting all our missing strings into a database for translation - for this reason I chose to use a Converter that calls a Localise extension method (so it can be done on any string in the code behind) which then looks up the string in the Dictionary (not the resource) and can do something with it (write it to a local DB) if it does not exist.
Text="{Binding Source='Logged on User', Converter={StaticResource LocalizationConverter}}"/>
This method works ok for us.

Related

How can I create the resource string without a big string?

In After Effects scripts, if you want your script to be able to be docked in the program's workspace, the only way to do it as far as I know is to use a resource string like this:
var res = "Group{orientation:'column', alignment:['fill', 'fill'], alignChildren:['fill', 'fill'],\
group1: Group{orientation:'column', alignment:['fill', ''], alignChildren:['fill', ''],\
button1: Button{text: 'Button'},\
},\
}";
myPanel.grp = myPanel.add(res);
The above code creates a script UI with one button ("button1") inside a group ("group1").
I would like to know other ways to create the same resource string. Is it possible to make it using a JSON object and then stringifying it??
I know it can be done somehow, because I have inspected the Duik Bassel script that is dockable and, for example, adds elements like this:
var button1 = myPal.add( 'button' );
but I cannot understand how to do it myself.
TL;DR: I want to make a dockable scriptUI without writing a giant string all at once, but bit by bit, like a floating script.
UI container elements have an add() method which allows you to add other UI elements to them, and you can treat them as normal objects.
var grp = myPanel.add("group");
grp.orientation = "column";
grp.alignment = ['fill', 'fill'];
grp.alignChildren = ['fill', 'fill'];
var group1 = grp.add("group");
…
var button1 = group1.add("button");
button1.text = 'Button'
More details and examples here: https://extendscript.docsforadobe.dev/user-interface-tools/types-of-controls.html#containers
Also worth checking out https://scriptui.joonas.me/ which is a visual scriptUI interface builder. You have to do some work on the code it produces to get panels for AE, but it's not hard.
extendscript still uses a 20th century version of javaScript, which doesn't have JSON built-in, but I have successfully used a JSON polyfill with it.
I used json2.js to get structured data in and out of Illustrator, and it worked beautifully, but I can see there's now a json3.js which might be better for whatever reason. This stackoverflow question addresses the differences.
To load another .js file (such as a polyfill) into your script, you need to do something like
var scriptsFolder = (new File($.fileName)).parent; // hacky but effective
$.evalFile(scriptsFolder + "/lib/json2.js"); // load JSON polyfill
These file locations may differ in other Adobe apps. Not sure what it would be in AfterEffects. I seem to remember that InDesign has a different location for scripts. You can also hardcode the path, of course.
Good luck!

How to make jedit file-dropdown to display absolute path (not filename followed by directory)?

All is in the title.
If a have opened the three files:
/some/relatively/long/path/dir1/file_a
/some/relatively/long/path/dir1/file_b
/some/relatively/long/path/dir2/file_a
The file dropdown contains:
file_a (/some/relatively/long/path/dir1)
file_a (/some/relatively/long/path/dir2)
file_b (/some/relatively/long/path/dir1)
And that bother me because I have to look on the right to differentiate the two file_a, and on the left for the others. This happens a lot to me mostly because I code in python, and thus I often have several __init__.py files opened.
How do I get jedit to display
/some/relatively/long/path/dir1/file_a
/some/relatively/long/path/dir1/file_b
/some/relatively/long/path/dir2/file_a
config:
jedit 5.1.0
java 1.6.0_26
mac osx 10.6
Unfortunately this is not easily possible currently, I just had a look at the source and this is not configurable.
You can:
Submit a Feature Request to make this configurable (good idea in any case)
Create or let create a startup macro that
registers an EBComponent with the EditBus that listens for new EditPanes getting created
retrieve the BufferSwitcher from the EditPane
retrieve the ListCellRenderer from the BufferSwitcher
set a new ListCellRenderer to the BufferSwitcher that first calls the retrieved ListCellRenderer and then additionally sets the text to value.getPath()
Try the Buffer List plugin as to whether it maybe suits your needs
Now follows code that implements the work-part of option two, runnable as BeanShell code which does this manipulation for the current edit pane. The third line is not necessary when done in an EBComponent, this is just that the on-the-fly manipulation is shown immediately.
r = editPane.getBufferSwitcher().getRenderer();
editPane.getBufferSwitcher().setRenderer(
new ListCellRenderer() {
public Component getListCellRendererComponent(list, value, index, isSelected, cellHasFocus) {
rc = r.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
rc.setText(value.getPath());
return rc;
}
});
editPane.repaint();

FormBlock Server Control in Ektron

I am working in Ektron 8.6.
I have a FormBlock Server Control in my Template Page,It is having a DefualutFormID of a valid HTML form from workarea.The form in the workarea have got few form fields and their corresponding values.
While the template page is rendering I need to GET those form field values and re-set them with some other values.
In which Page –Cycle event I should do this coding?
I tried this code in Pre-Render Event,but I am unable to GET the value there,but I am able to set a value.
I tried SaveStateComplete event as well,no luck.
String s=FormBlock1.Fields["FirstName"].Value;
If(s=”some text”)
{
// Re-set as some other vale.
FormBlock1.Fields["FirstName"].Value=”Some other value”;
}
In which event I can write this piece of code?
Page_Load works fine for changing the value of a form field. The default behavior is for the Ektron server controls to load their data during Page_Init.
The real problem is how to get the default value. I tried every possible way I could find to get at the data defining an Ektron form (more specifically, a field's default value), and here's what I came up with. I'll admit, this is a bit of a hack, but it works.
var xml = XElement.Parse("<ekForm>" + cmsFormBlock.EkItem.Html + "</ekForm>");
var inputField = xml.Descendants("input").FirstOrDefault(i => i.Attribute("id").Value == "SampleTextField");
string defaultValue = inputField.Attribute("value").Value;
if (defaultValue == "The default value for this field is 42")
{
// do stuff here...
}
My FormBlock server control is defined on the ASPX side, nothing fancy:
<CMS:FormBlock runat="server" ID="cmsFormBlock" DynamicParameter="ekfrm"/>
And, of course, XElement requires the following using statement:
using System.Xml.Linq;
So basically, I wrap the HTML with a single root element so that it becomes valid XML. Ektron is pretty good about requiring content to be XHTML, so this should work. Naturally, this should be tested on a more complicated form before using this in production. I'd also recommend a healthy dose of defensive programming -- null checks, try/catch, etc.
Once it is parsed as XML, you can get the value property of the form field by getting the value attribute. For my sample form that I set up, the following was part of the form's HTML (EkItem.Html):
<input type="text" value="The default value for this field is 42" class="design_textfield" size="24" title="Sample Text Field" ektdesignns_name="SampleTextField" ektdesignns_caption="Sample Text Field" id="SampleTextField" ektdesignns_nodetype="element" name="SampleTextField" />

Semantic zoom grouped collection

IN my metro application i want to create a semantic view for the page.
For that i am manually creating a grouped collection object using foreach loop. I am not using LINQ to group the object collection because of some reason .
SO now when i try to populate semantic zoom it displays nothing(no semantic zoom).
How can i bind my own collection to grouped collection source
XAML
<CollectionViewSource x:Name="GroupedSource" IsSourceGrouped="true" />
Code behind file
GroupedSource.Source =context.Collection; // my own grouped collection..
When using LINQ it working fine.But i cant use lINQ because of some reasons
Is there anything else i need to do to get
Try using this code to set the source:
(semanticZoom.ZoomedOutView as ListViewBase).ItemsSource = GroupedSource.View.CollectionGroups;
Need a bit more detail, but. The IsSourceGrouped="true" is only half of the story unless you bind to a hierrachal datasource. You need to specify the property that contains the child collection - ItemsPath="myItems" as an xaml property of the CollectionViewSource. If that is not the issue it may be a case of precidence of execution. put a break point and check context.Collection is populated before it is used, if context.Collection is an ObservableCollection you should be able to populate at anytime (ie. async fill).
<CollectionViewSource x:Name="GroupedSource" IsSourceGrouped="true" ItemsPath="Items" />

ObservableCollection<someentity> not refreshing

I am using Silverlight 4 and MVVM pattern for my application. I have a listbox that is bound to one page say one.xaml and it's viewmodel is oneviewmodel.cs. This is the page where i load my albums collection. I have a button on that page which popups a page to add a new album. Say that page is two.xaml and it's viewmodel is twoViewModel.cs. On this page i call ria services :-
context.albums.add(somealbum);
and submit the changes.The album gets added and i can see the record in sql server. However when the popup gets closed my listbox still shows the stale data. Do i need to again make a request to server to load the fresh entity just added? Thus, essentially i have to use messaging pattern and request oneviewmodel.cs to load the entities again. Is this correct way of doing?
This is my method of loading album entities :-
var qry = AlbumContext.GetAlbumsQuery(_profile.UserId);
AlbumContext.Load<Album>(qry, new Action<System.ServiceModel.DomainServices.Client.LoadOperation<Album>>(albums => {
if (GetAlbumsComplete != null)
{
if (albums.Error == null)
{
GetAlbumsComplete(this, new EntityResultArgs<Album>(albums.Entities));
}
else
{
GetAlbumsComplete(this,new EntityResultArgs<Album>(albums.Error));
}
}
}), null);
This is using the same pattern and classes as Shawn Wildermuth.
Thanks in advance :)
You do not need to load everything from the server again, but you need to add the new album to your ObservableCollection. So far you only added it to the DomainContext.
You could do one of the following two options:
1) Add the new album directly to the collection with
collection.Add(somealbum);
or
2) I assume that you fill the ObservableCollection in GetAlbumsComplete(). Just execute that part again, so that the ObservableCollection is filled with the content of your DomainContext.Albums.