 |
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.

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:

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.
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:

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

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.

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: https://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 https://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. |
 |
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.
- We read in an XLIFF file with a segment that says needs-adaptation.
- We map it as Untranslated within Catalyst.
- 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.
- We read in an XLIFF file with a segment that says rejected-spelling
- We ignore it from a Catalyst point of view
- 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 |
|