Tech Mail

November 2015

News and Updates | Preparing your .NET project for Localization | Localizing .NET framework applications
Best practices for developing localizable .NET applications | Mapping of XLIFF state to Catalyst status

 

Dear Alchemy Technology User,


Hello from the Emerald isle (Ireland), my name is Cyril Vallin, I am the Product Support Manager at Alchemy Software Development.  This TechBytes quarterly Email is to share the experiences from our users so that we can all benefit and enhance our use of Alchemy technology. In Support we sometimes see similar topics being queried and I hope that sharing this knowledge will promote advancement amongst our users.

In this edition of Techbytes, I concentrate on .NET projects, from preparing project files for Localization to Localizing them in Catalyst. With some really handy tips on best practices in developing localizable .NET projects to maximize Catalyst's Visual translation environment.

Remember all the previous released articles are available on our website, in the Support section. Use your Support Centre login to access all the past articles along with other resources available to our premium support members.

As always, don't hesitate to let me know if there is any topic you would like covered in this TechBytes newsletter.

Cyril Vallin
Product Support Manager @ Alchemy Software Development


 

News and Updates

 

Catalyst 11 SP2 released


The Service Pack 2 for Catalyst 11 was released this month. I would recommend you update your installation if you have not done so already.

Review the notification Email directly at this page: Catalyst 11 SP2 release

 

 
Preparing your .NET project for Localization

Where to put source language resources

The source language is the language in which a piece of software has been developed.  Resources in this language can be placed in a main assembly, which will then contain code and resources, or in a satellite, the satellite will then contain just resources and the main assembly will contain just code.

Our recommendation is that the source language resources be stored in the main assembly.  This is the default setting in the development environment.  However, as well as ensuring that there is no <UICulture> entry in the csproj file, the following should be added in AssemblyInfo.cs, it explicitly states the location of the source language resources:

[assembly:NeutralResourcesLanguage("en-IE", UltimateResourceFallbackLocation.MainAssembly)]

String tables

If you are writing a purely XAML GUI application, we recommend you don't use a ResX string table to store UI strings. Leave them all in XAML declaration or alternatively  use a resource dictionary. A resource dictionary is a XAML construct; however it does not typically store any UI, just a list of strings with IDs, wrapped in some XAML mark-up.

Resource dictionaries can also store style information, themes and binary resources among other things. For simplicity, each type of resource can be factored into a different resource dictionary and these can be merged later if required.

The resource dictionary can be stored at application level and referenced throughout the WPF application.  There is a lot of built-in support for this mechanism and it's very flexible when setup correctly, more so than ResX files. Resource dictionaries should not be used to store visual templates as the contents of resource dictionaries cannot be visualized.

File Preparation

 XAML (Extensible Application Markup Language; pronounced "zammel") is a declarative XML-based language that defines rich GUIs.  A typical WPF project will consist of a mixture of code files and XAML files.

Catalyst requires that all UI elements in XAML have a unique identifier (UId) and will alert the user in the Results window if they do not appear to be present.  UIds are used to keep track of changes to files and to identify items that must be translated.  It’s essential that they are present as Microsoft has not provided a mechanism to manually parse resources in assemblies, Catalyst is tied to their APIs and these in turn rely on UIds being present.  To add UIds to XAML files in a project, the MsBuild tool must be run on the project file:

Msbuild /t:updateuid [Project Name].csproj

Then, to check that there are no missing or duplicate UIds run:

Msbuild /t:checkuid [Project Name].csproj

The project should now be recompiled.

 

 

Localizing .NET framework applications

 

Localizing .NET applications

Alchemy Catalyst is a Visual localization tool designed to parse compiled  .NET Framework applications. The support is for WinForms, .resx,  WPF and even Hybrid files. All versions of .NET Frameworks are supported, from 1.1 to 4.6.

.NET uses a common language runtime enabling the use of Culture (language) specific resources deployed in the form of satellite assemblies. Satellite assemblies include only resource files, they do not contain executable code.

Applications consists of a Main assembly and satellite assemblies for the default or neutral culture (language). Then separate satellite assemblies are generated for each Culture to be included.
 

Using the user's language settings (set in Control panel > Region > format), the .NET Framework loads the satellite assemblies in the matching Culture directory to display the resources. If that Culture (for example fr-FR for a French user in France) is not found, the default assemblies are used as a fall back method.

General translation process with Catalyst

Project files are created within Catalyst which parses all the localizable resources found in the compiled assemblies (.exe and .dll files). This includes version, menus, dialogs, graphics and string tables.

With only the localizable elements exposed in Catalyst, a localization engineer can add metadata to the project (locks, memos, keywords, context links, max length) and use the Leverage Expert to automatically apply any existing translations from Translation Memories. The project can then be passed on to a translator.

Once translation is complete, Catalyst's Validation Expert is ran to catch all common translation mistakes and greatly reducing the amount of Localization bugs introduced.

The final step is for Catalyst to generate the satellite assemblies which are automatically placed into a folder labelled with Cultures language code. This folder name is based on the Catalyst project's target language.

a

Note: With WinForms satellite assemblies (and .resx files), Catalyst includes the option to "Extract only modified resources".
This options is found in FILE > Options > .NET Framework.

  

Localizing .resx resource files or compiled .NET assemblies

Catalyst supports both .resx and compiled binaries (.exe & .dll), and in both cases no configuration of parsing rules is necessary. As we advise with Win32 files, localizing compiled binaries in Catalyst  is a better option considering the following benefits:

  • Full Visual View in Catalyst with all referenced assemblies loaded.
  • Source code is undisclosed.
  • Translated satellite assemblies are ready for localized application.
  • Extracted from Catalyst, the satellite assembly can be tested immediately in a test build.
  • No recompiling needed. Localization bug fixing is easier and faster.
  • Work with one binary file instead of multiple .resx files.

 

Referenced assemblies

In order to generate a visual display of the Forms and BAML dialogs Catalyst uses the .NET Framework to emulate the UI. Consequently all referenced assemblies need to be available to successfully display dialogs in their intended layout.

Catalyst gives invaluable feedback when inserting assemblies into a project. This feedback is listed in the Results window. For example, here is a standalone WPF application inserted successfully. The Results window reports no missing referenced assembly:

a

If the assemblies inserted in a Catalyst project depend on referenced assemblies to render dialogs and forms, the later need to be present in the same directory. Catalyst loads all referenced assemblies along with the main assemblies. Although referenced assemblies are not visible in Catalyst's Navigator Window, they are loaded and available for the .NET Framework while emulating the assemblies' code.

Example, if you are inserting MyApp.dll which has 2 referenced assemblies (filea.dll and fileb.dll), those 2 referenced assembly files need to be in the same folder as MyApp.dll. With the files present, the Results window will not report errors. If missing, the Results window will warn on each missing referenced assembly.

a Although it is possible to insert and translate assemblies in Catalyst without the referenced assemblies available, it is not recommended as important information is missing.

 

Example of missing referenced assembly on inserting a main assembly in Catalyst. The Form displayed in Visual View is missing resources:

a

The same form in visual view with the Referenced assembly present on insertion:

a

 

 

Signed or Strong-named assemblies

Assembly signing (also called strong-name signing) gives an application or component a unique identity that other software can use to identify and refer explicitly to it. A strong name consists of its simple text name, version number, culture information (if provided), plus a public/private key pair. This information is stored in a key file.

Localizing a signed main assembly requires the localized assemblies signed also. The key file (.snk) can be configured in Catalyst's options under FILE > Options > .NET Framework.

a

Often the private key file is not available to the localization team, or even development team until the product release. The public key however is often available. In this case, you may opt to delay sign the satellite assemblies. Select "Auto detect public keys and delay sign satellites on creation" option as seen in screenshot above.

Microsoft reference on delay signing: http://msdn.microsoft.com/en-us/library/t07a3dye.aspx

 

Delayed signed assemblies

Delayed signing refers to a technique of partially signing dlls while they are in the development or localization phase.  It is achieved by using a public key to encode parts of the assembly. The public key is embedded in the assembly and can be used by third-parties who want to reference the assembly.

There are many more benefits to signing an assembly, but the main purpose of delayed signing is to allow a company to protect and control the private key and only use it during the final packaging process. The public key can be issued to third parties such as localization service providers allowing them to delay sign the assembly.  Delay signed assemblies can be used for test purposes, but should not be shipped as they are not fully protected.

The private key can be later applied to a delay signed assembly. Full signing, in this way, completes the security process and can only be done by the software company using the private key that they maintain under their own control.

As a security measure, Catalyst can only insert fully signed or unsigned dlls. Delay signed assemblies cannot be inserted.

 

If you are working with delayed signed assemblies, you can turn the verification of the signature off. Sn.exe which deals with .NET signing (a Microsoft executable, see http://msdn.microsoft.com/en-us/library/k5b5tt23.aspx) is included in the Catalyst install folder. Using this tool, you can prepare the assemblies by turning off its signature verification.

Run the –Vr option with the filename in question:

sn.exe –Vr myAssembly.dll

 

Once done, Catalyst will insert the assembly without complaining that it is delayed signed (you have to restart Catalyst if it is running when you do this).

Use either of the other options highlighted in the sn.exe help screenshot to revert the verification skipping.

a

Obfuscated assemblies

In .NET, the inner workings of assemblies can be examined using tools such as .NET Reflector or ILSpy.  This can reveal a lot about what a DLL does and how it works.  This is useful from an openness and transparency point of view, but less so from the point of view of protecting intellectual property.

To hide code and algorithms and to protect their intellectual property, assemblies can be obfuscated.  Obfuscation is a process that encrypts class and function names so they are no longer readable. The downside is that this process also encrypts the information Catalyst uses to load these assemblies either as referenced assemblies or for localization.

If you are localizing assemblies which are obfuscated, you will need to require unobfuscated copies of the assemblies. The localized satellite assemblies can then be obfuscated along with the main assemblies.

 

 

 

Best practices for developing localizable .NET applications

Catalyst’s Visual View calls on the Microsoft .NET Framework to create instances of complex .NET windows for display. The .NET Framework is sometimes unable to create these objects. After a failure Catalyst attempts to generate an alternative fall-back Visual View. This fall-back view can result in windows that don’t appear exactly as they would at runtime in the parent application. While all content is made available for localization, missing user interface components can sometimes lead to loss of context during the translation process.

One of the most common causes of a window not displaying is unhandled exceptions in client code. The .NET Framework is unable to create a window because its constructor throws an exception and this exception remains unhandled. There are a number of suggestions for avoiding this situation. These are outlined below.

Try-Catch Blocks

Avoid unhandled exceptions: Use a ‘try-catch’ statement around code in the constructor that may throw an exception.

Sample Code Comment

MyClass::MyClass()

     {

          InitializeComponents();

          OpenFile(“c:\Info.dat”);

     }

 

If a constructor refers to a server or a database that may not exist in the localization environment.  The constructor may fail.  Catalyst will not be able to visually render this form if the constructor fails.

MyClass::MyClass()

     {

          try

             {   

                  InitializeComponents();

                  OpenFile(“c:\Info.dat”);

              }

          catch(Exception e)

             {

              }

     }
 

A simple try-catch statement such as this will catch the exception.

Even if there is no actual handling code in the catch block, the exception does not bubble up and cause an unhandled exception in Catalyst.

In the situation where the file does not exist in the localization environment, Catalyst will still be able to visually construct this form.

Check if Catalyst is calling a constructor

Another option for avoiding unhandled exceptions is to check if Catalyst is calling your constructor, you can then follow an appropriate code path:

   class MyClass
    {
        public MyClass()
        {            
            Assembly CallingAssem = Assembly.GetCallingAssembly();
            if(null != CallingAssem)
            {
                string name = CallingAssem.FullName;
                if (-1 != CallingAssem.FullName.IndexOf("CatAl20"))
                {
                    Debug.WriteLine("Catalyst calling");
                }
            }
        }
    }

 

Attach Event Handlers

Where appropriate, attach event handlers to these events:

Application.ThreadException

AppDomain.CurrentDomain.UnhandledException

This will deal with these exceptions. These event handlers would not generally terminate the thread but would allow components of the user interface to continue functioning.  These user interface components may be required by Catalyst.

Call InitializeComponents() Early

Wait until after the call to InitializeComponent () before adding more logic to the constructor. The InitializeComponent () method should be complete before any code that is liable to throw an exception is added.

Sample Code Comment

MyClass::MyClass()

     {

          OpenFile(“c:\Info.dat”);

          InitializeComponents();

     }

 

If the constructor fails before InitializeComponent () is called then no user interface components will be initialized.

MyClass::MyClass()

     {

          try

             {   

             InitializeComponents();

             OpenFile(“c:\Info.dat”);

             }

     catch(Exception e)

             {

              }

}
 

Simply placing any complicated logic after the InitializeComponent () call will ensure that the UI components are initialized before any other code may fail.

 

Avoid resources that may not be present

If possible, try to avoid connecting to data sources or resources that may not exist in certain environments.  When an application is installed on a client’s machine, it may be that a certain file or a database is always present (the installer may look after this).  However, this may not be the case during localisation. Try to avoid dependencies such as this in the constructor or InitializeComponent () calls. This is not always possible and if not, that resource will need to be present during localization.

 

Public Constructors with no Arguments

For a given window’s class, implement a public constructor, which takes no arguments, if one does not already exist.  Such a constructor is easier for Catalyst to call.

 

Handle Null Parameters

In certain circumstances Catalyst can ask the .NET Framework to create objects using a constructor which takes parameters; however, when the constructor is called by Catalyst the parameters passed can be null. Ideally, code should check for the possibility of null parameters and handle any resulting exceptions gracefully.

Sample Code Comment

MyClass::MyClass (myFile File)

     {

          OpenFile(File);

          InitializeComponents();

     }

 

When Catalyst calls a constructor that takes a parameter, it may pass in null as the parameter.  Code such as this will throw an exception. An uncaught exception will result in the form not being created.

MyClass::MyClass (myFile File)

     {

          try

             {   

             InitializeComponents();

             If(File)

                  OpenFile(File);

             }

          catch(Exception e)

}
 

Checking the parameter for validity before use is a good idea and allows the form to be created without difficulty.

 

 

 

 

Mapping of XLIFF state to Catalyst status

This article references the correspondence (mapping) between the State attribute found in XLIFF for individual string and the Status in Alchemy Catalyst.

Inserting an XLIFF file in a Catalyst project, a mapping of the state and state-qualifier values to Catalyst states is applied as follows:

 

STATE (independent values)

State = The status of a particular translation in a <target> or <bin-target> element in XLIFF format

XLIFF State

Catalyst Status

Description

final

Signed Off

Indicates the terminating state.

signed-off

Indicates that changes are reviewed and approved.

needs-review-adaptation

For Review

Indicates only non-textual information needs review.

needs-review-l10n

Indicates both text and non-textual information needs review.

needs-review-translation

Indicates that only the text of the item needs to be reviewed.

translated

Indicates that the item has been translated.

needs-adaptation

Untranslated

(except when Source != Target;

then it becomes For Review)

Indicates only non-textual information needs adaptation.

needs-l10n

Indicates both text and non-textual information needs adaptation.

needs-translation

Indicates that the item needs to be translated.

new

Indicates that the item is new. For example, translation units that were not in a previous version of the document.

This Source != Target takes precedence when dealing with the Untranslated states.

 

STATE-QUALIFIER (independent values)

State-qualifier = Describes the state of a particular translation in a <target> or <bin-target> element in XLIFF format.

XLIFF State-qualifier

Catalyst Status

Description

exact-match

Leveraged

Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously.

id-match

Indicates a match based on matching IDs (in addition to matching text).

leveraged-glossary

Indicates a translation derived from a glossary.

leveraged-inherited

Indicates a translation derived from existing translation.

leveraged-repository

Indicates a translation derived from a translation repository.

leveraged-tm

Indicates a translation derived from a translation memory.

tm-suggestion

Indicates the translation is suggested by translation memory.

fuzzy-match

Fuzzy Match

Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discrepancy, etc.).

leveraged-mt

Machine Translated

Indicates a translation derived from machine translation.

mt-suggestion

Indicates the translation is suggested by machine translation.

rejected-grammar

Nothing – A Catalyst status is not assigned

Indicates that the item has been rejected because of incorrect grammar.

rejected-inaccurate

Indicates that the item has been rejected because it is incorrect.

rejected-length

Indicates that the item has been rejected because it is too long or too short.

rejected-spelling

Indicates that the item has been rejected because of incorrect spelling.

 

STATE and STATE-QUALIFIER (combined values)

When both state and state-qualifier exists in the same <trans-unit>, this is the combined status that should be adopted when parsing the file into Catalyst:

xliff ‘state’ attribute

Catalyst Status

final

Signed Off

(state-qualifier ignored)

signed-off

needs-review-adaptation

For Review

When mapping to

For Review in Catalyst, we look at the state-qualifier - according to table

needs-review-l10n

needs-review-translation

translated

needs-adaptation

Untranslated

(state-qualifier ignored)

(If Source != Target,

it becomes For Review)

needs-l10n

needs-translation

new

 
 

XLIFF ‘state-qualifier’ attribute

Catalyst Status

exact-match

Leveraged

id-match

leveraged-glossary

leveraged-inherited

leveraged-repository

leveraged-tm

tm-suggestion

fuzzy-match

Fuzzy Match

leveraged-mt

Machine Translated

mt-suggestion

rejected-grammar

Remains with standard For Review

rejected-inaccurate

rejected-length

rejected-spelling

 

When neither state nor state-qualifier is found in the <trans-unit> and Source does not equal Target, the Target must contain a translation; therefore Catalyst parses the segments with For Review status.

Source != Target takes precedence when dealing with the Untranslated state.

 

Extracting out of Catalyst to XLIFF

When writing out to XLIFF format  through Extract Files... the Catalyst status is mapped to the XLIFF state values as follows:

Writing out Edited segments

Catalyst Status

xliff state attribute

state-qualifier attribute

Signed Off

signed-off

 

Leveraged

needs-review-l10n

exact-match

For Review

 

Fuzzy Match

fuzzy-matched

Machine Translated

mt-suggestion

Untranslated

Nothing – A state is not written into the file

 

 

Maintaining XLIFF state and state-qualifier attribute that have not been edited

Maintain state attributes

If we are not editing segments, we should maintain the state value that was there e.g.

  1. We read in an XLIFF file with a segment that says needs-adaptation.
  2. We map it as Untranslated within Catalyst.
  3. If no editing or translation happens to that segment, we should try to write it back out as needs-adaptation.

Maintain state-qualifier attributes

If we are not editing segments, we should maintain the state-qualifier value that was there e.g.

  1. We read in an XLIFF file with a segment that says rejected-spelling
  2. We ignore it from a Catalyst point of view
  3. If no editing or translation happens to that segment, we should maintain that value when extracting the file - rejected-spelling

 

Reverted XLIFF written out of Catalyst

When a file already containing existing states in the XLIFF file has been reverted within Catalyst and then extracted without any translation, this is the status on those target nodes when extracted.

Original State

Catalyst Status

Reverted Catalyst Status

Extracted XLIFF Status

final

Signed Off

Untranslated

needs-translation

(state-qualifier if it was present is also removed)

signed-off

needs-review-adaptation

For Review

needs-review-l10n

needs-review-translation

translated

needs-adaptation

Untranslated

(except when Source != Target)

Preserves original state and state-qualifier of the segment as these were seen as an Untranslated state, and Catalyst preserves the original state when not edited.

needs-l10n

needs-translation

new

 

 

 

 



 
Conclusion

Thanks for taking the time to read this instalment of TechBytes. It has been fun to write and I hope you found some if not all of it beneficial. We always welcome new article ideas, so if there is a feature you feel works really well and is worth mentioning, or indeed if clarification on a particular topic would help you, please let me know so together we can make TechBytes as useful as possible for everyone.

My best wishes
Cyril Vallin