I18N.DotNet 1.3.1

dotnet add package I18N.DotNet --version 1.3.1
NuGet\Install-Package I18N.DotNet -Version 1.3.1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="I18N.DotNet" Version="1.3.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add I18N.DotNet --version 1.3.1
#r "nuget: I18N.DotNet, 1.3.1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install I18N.DotNet as a Cake Addin
#addin nuget:?package=I18N.DotNet&version=1.3.1

// Install I18N.DotNet as a Cake Tool
#tool nuget:?package=I18N.DotNet&version=1.3.1

About

I18N.DotNet is a .NET library written in C# to enable simple internationalization (I18N) / localization (L10N) (i.e. translation to different languages) of .NET applications and libraries.

The companion utility I18N.DotNet Tool is provided to ease management of translation files.

Installation

The easiest way to install I18N.DotNet is using the NuGet package: https://www.nuget.org/packages/I18N.DotNet/

Getting Started

To use the I18N.DotNet library, three steps must be followed:

  1. Write/modify the source code to internationalize strings that must be translated (see Writing/Adapting Source Code (I18N)).
  2. Write translations for internationalized strings (see Writing Translations (L10N)).
  3. Embed the translations file in the executable (see Embedding the Translations File).

Writing/Adapting Source Code (I18N)

When writing internationalized source code, the strings to be translated must be wrapped with a call to I18N.DotNet.GlobalLocalizer.Localize().

The easier and most convenient approach for writing internationalized software is to choose a language that will be used as the base language throughout the software development (e.g., English), and then write the software just as any non-internationalized source code, except that strings to be translated must be wrapped with calls to Localize(). This way the base language will act as the default language when translations are not available for the current target language.

Adapting existing non-internationalized source code is as easy as wrapping the existing strings to be translated with calls to Localize().

Example (C#)
using static I18N.DotNet.GlobalLocalizer;
using System;
using System.IO;

public class Program
{
  static void Main( string[] args )
  {
    int i = 0x555;

    Console.WriteLine( Localize( "Plain string to be translated" ) );
    Console.WriteLine( Localize( $"Interpolated string to be translated with value {i:X4}" ) );
  }
}

Writing Translations (L10N)

String translations must be stored in an XML file (the translations file) with root element I18N.

For each string than has been internationalized an Entry element under the root must be defined, with:

  • A single Key child element which value is the internationalized string defined in the code (replacing for interpolated strings the interpolated expressions with their positional index).
  • Several Value child elements with their attribute lang set to the target language of the translation and which value is the translated string.

It is not necessary to add translations (i.e., Value elements) for the development base language, since the value of the Key element will be used as the default translation when a translation for a specific language is not found.

Example
<?xml version="1.0" encoding="utf-8"?>
<I18N>
  <Entry>
    <Key>Plain string to be translated</Key>
    <Value lang="es">String simple a traducir</Value>
    <Value lang="fr">String simple à traduire</Value>
  </Entry>
  <Entry>
    <Key>Interpolated string to be translated with value {0:d}</Key>
    <Value lang="es">String interpolado a traducir con valor {0:dd/MM/yy}</Value>
    <Value lang="fr">String interpolé à traduire avec valeur {0:d}</Value>
  </Entry>
</I18N>

NOTE: The companion utility I18N.DotNet Tool can be used to ease the creation of the translations file by scanning source files and automatically generating entries for discovered internationalized strings.

Embedding the Translations File

A very convenient way of distributing the translations for an application is to embedded the translations file in the executable assembly as an embedded resource identified by Resources.I18N.xml.

Using Visual Studio, the easiest way to achieve this is to deploy the translations file as a file named "I18N.xml" in a directory named "Resources" inside the VS project directory, and then configure the file in the VS project as an embedded resource (i.e., set its Build Action to "Embedded resource" in the IDE, or add <EmbeddedResource Include="Resources\I18N.xml" /> to an ItemGroup in the project file).

Example (.csproj)
<Project Sdk="Microsoft.NET.Sdk">
  ...
  <ItemGroup>
    <EmbeddedResource Include="Resources\I18N.xml" />
  </ItemGroup>
  ...
</Project>

NOTE: The companion utility I18N.DotNet Tool can be used to generate translations files optimized for deployment from the separate translations files used during development and during the translation process.

Advanced Usage (Internationalizing Applications)

Global Localizer

The static class GlobalLocalizer has the property Localizer which contains the global localizer. This instance is shared and can be conveniently used by all software components. In fact all the methods exposed by the GlobalLocalizer class are just convenience wrappers that call the global localizer.

The property GlobalLocalizer.Localizer is an instance of AutoLoadLocalizer that on first usage (if translations have not been previously loaded) tries to load the translations from an embedded resource identified by "Resources.I18N.xml" inside the entry (application) assembly using the current UI language as the target language.

The default behavior is just right for most use cases, but if the translations file is stored in an embedded resource with a different identifier, or in a separate file (e.g., installed alongside the application executable), one of the LoadXML methods can be invoked on the global localizer to load it (see Loading Translations).

Non-Default usage Example (C#)
void SetupI18N( string language, string directoryPath )
{
  GlobalLocalizer.Localizer.LoadXML( directoryPath + "/I18N.xml", language );
}

Local Localizers

Instances of Localizer can be created (local localizers), loaded with string translations, and then passed to software components for being used instead of the global localizer.

For most cases using the global localizer (and optionally contexts) is just enough, but local localizers can be useful for example to implement report generation in different languages than the application UI language (see Loading Translations and Specifying the Translation Target Language).

Example (C#)
Report GenerateReport( string language )
{
  var reportLocalizer = new Localizer();
  reportLocalizer.LoadXML( Assembly.GetExecutingAssembly(), "Reports.I18N.xml", language )
  return GenerateReport( reportLocalizer );
}

Report GenerateReport( ILocalizer localizer )
{
  var report = new Report();
  report.AddEntry( localizer.Localize( $"Date: {DateTime.Now:d}" ) );
  ...
  return report;
}

String Format

Calls to String.Format() where the format string has to be internationalized should be replaced by a call to GlobalLocalizer.LocalizeFormat() / ILocalizer.LocalizeFormat() instead of just internationalizing the format string.

Strings formatted using LocalizeFormat() and interpolated strings localized using Localize() are conveniently formatted using the formatting conventions of the localizer's target language/culture.

Strings formatted using String.Format(), when no explicit format provider is used, will use the format conventions of the default culture, which may be different from the localizer's target culture, and can therefore produce unexpected localization results.

Example (C#)
String.Format( Localize( "Format string to be translated with value {0}" ), myVar );
// should be replaced by
LocalizeFormat( "Format string to be translated with value {0}", myVar );
// which is equivalent to
Localize( $"Format string to be translated with value {myVar}" );

Language Identifiers & Variants

Any arbitrary string can be used for identifying languages, although it is recommended to use identifiers formed by a ISO 639-1 alpha-2 language name (2-letter language codes, e.g., "en", "es"), additionally followed by an hyphen and a ISO 3166-1 alpha-2 country/region name (e.g., "en-US", "es-ES"). Specifically, it is recommended to use one of the language/region names supported by Windows.

Language identifiers are processed as case-insensitive (i.e., "fr-FR" is equivalent to "fr-fr").

When using language identifiers formed by a primary code and a variant code separated by an hyphen (e.g., "en-us", "es-es"), if a localized conversion for the language variant is not found then a conversion for the primary (base) language is tried too.

For example, when loading the translations on a Localizer created for the "en-gb" language, for each string to be translated a translation for the language "en-gb" will be searched first, and if not found then a translation for the language "en" will be searched next.

It is therefore recommended to:

  • In source code:
    • Use primary-variant code (e.g., "en-us", "es-es") as target language identifiers (e.g., as string arguments to the LoadXML methods) or when obtaining target cultures (e.g., to use as CultureInfo arguments to the LoadXML methods).
  • In translation files:
    • Use primary code (e.g., "en", "fr") as translation language identifiers (i.e, as the lang attribute values of XML I18N.Entry.Value entries) for generic (non variant-specific) translations.
    • Use primary code-variant (e.g., "en-gb", "es-ar") as translation language identifiers (i.e, as the lang attribute values of XML I18N.Entry.Value entries) for variant-specific translations.

Contexts

Sometimes the same source language string has different translations in different contexts (e.g., English "OK" should be translated in Spanish to "Aceptar" for a button label but to "Correcto" for a successful outcome indication).

Since the source language key is the same in both cases, context partitioning must be used, which affects the source code side and the translations file side.

Context Partitioning in Source Code (I18N)

In source code, the context of the key can be explicitly indicated when the string is being internationalized by calling GlobalLocalizer.Context() / ILocalizer.Context() and passing it the context identifier, and then calling the localization methods on the returned context (which is an ILocalizer).

Contexts can be nested. A chain of successively nested contexts can be identified by joining their identifiers using the dot character ('.') as a composite context identifier.

Translations in a context are searched hierarchically: if a translation is not found for the target language in a context (neither for the language variant nor the primary language), then a translation is searched again on its parent context (if it exists). Finally, if no translation is found in the context hierarchy, then the base language translation is used (i.e., the value of the argument passed to the Localize method).

Example (C#)
Button.Label = Context( "GUI.Button" ).Localize( "OK" );
// ...
TextBox.Text = Context( "GUI" ).Context( "Status" ).Localize( "OK" );
Context Partitioning in the Translation File (L10N)

Context partitioning is performed in the translations XML file using Context elements as children of the root element or nested within other Context elements. These elements must have an id attribute to indicate the context identifier (which can be a composite context identifier), and are containers for the Entry elements that define the translations for that context.

Example
<?xml version="1.0" encoding="utf-8"?>
<I18N>
  <Entry>
    <Key>OK</Key>
    <Value lang="fr">O.K.</Value>
  </Entry>
  <Context id="GUI">
    <Context id="Button">
      <Entry>
        <Key>OK</Key>
        <Value lang="es">Aceptar</Value>
      </Entry>
    </Context>
    <Context id="Status">
      <Entry>
        <Key>OK</Key>
        <Value lang="es">Correcto</Value>
      </Entry>
    </Context>
  </Context>
</I18N>

Loading Translations

The translations can be (re)loaded into a localizer implementing ILoadableLocalizer by different ways:

Automatically

The global localizer and AutoLoadLocalizer instances load their translations automatically from an embedded resource when any of their localization methods (those defined in ILocalizer) is called (only if translations have not been previously loaded explicitly).

From an Embedded Resource

A very convenient way of using translation files is to embed them into an executable assembly (application or library), then load them into a localizer implementing ILoadableLocalizer using a LoadXML method passing as arguments the assembly to load the embedded resource from and its identifier (and optionally the target language for translations).

Example (C#)
void SetupI18N()
{
  GlobalLocalizer.Localizer.LoadXML( Assembly.GetExecutingAssembly(), "I18N.Translations.xml" );
}
From a Standalone File

If the translations file is stored as a separate file (e.g., installed alongside the application executable), a LoadXML method can be invoked on a localizer implementing ILoadableLocalizer passing the path to the file as an argument (and passing optionally the target language for translations).

Example (C#)
void SetupI18N()
{
  var programPath = Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location );
  GlobalLocalizer.Localizer.LoadXML( programPath + "/I18N.xml" );
}
From a Stream

When the translations file is neither stored as a file nor as an embedded resource (e.g., downloading the translations from a remote server to local memory, obtaining the translations from a database), a LoadXML method can be invoked on an ILoadableLocalizer instance passing as an argument a System.IO.Stream object that must provide the file contents (and passing optionally the target language for translations).

From an XML Document

Additionally, translations can be loaded from an XML document by invoking a LoadXML method on an ILoadableLocalizer instance passing as an argument a System.Xml.Linq.XDocument object loaded with the translations (and passing optionally the target language for translations).

Specifying the Translation Target Language

The target language (and associated culture for formatting operations) used by localizers (e.g., the global localizer or a local localizer) is selected only when translations are loaded on the localizer.

When translations are loaded (automatically or by means of explicit calls to LoadXML methods), if an explicit target language (or culture) is not indicated, then the current UI language (obtained from System.Globalization.CultureInfo.CurrentUICulture) is used by default as the target language (and culture).

Selecting a different language (or culture) than the default can be achieved by different ways:

Changing the UI Culture During Startup

An easy way of changing the target language is, during application startup, before any localization method is called, to set System.Globalization.CultureInfo.CurrentUICulture to the culture corresponding to the desired target language.

Automatically-loading localizers do not select their default language when they are created, but they instead delay this selection until they load their translations, which will not happen automatically until a localization method is called. Therefore the default UI culture can be changed after these localizers have been created, and the new culture will still be used properly when localization methods are called for the first time.

This approach to make the global localizer use a specific language (e.g., use a language configured by the user) is very simple, and it has the advantage that resources localized by other means may probably also use the same target language and culture.

Example (C#)
using System.Globalization;

public class Program
{
  static void Main( string[] args )
  {
    if( args.Length >= 1 )
    {
      CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo( args[0] );
    }
    ...
  }
}

NOTE: It may also be useful to set System.Globalization.CultureInfo CurrentCulture, System.Globalization.CultureInfo DefaultThreadCurrentUICulture, and/or System.Globalization.CultureInfo DefaultThreadCurrentCulture.

Changing the UI Culture Dynamically

When the application is already running, changing the UI culture will have no immediate effect on the localizers which translations have already been loaded.

Automatically-loading localizers (i.e., instances of AutoLoadLocalizer, like the global localizer) can be manually forced to reload its translations to enforce dynamic changes of the UI culture to take effect.

Example (C#)
void SetupI18N( string language )
{
  CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo( language );
  GlobalLocalizer.Localizer.Load( null );
}
(Re)Loading Translations of Automatically-Loading Localizers

The AutoLoadLocalizer class provides Load methods that accept the target language as a language identifier or culture (see TargetCulture) parameter.

The AutoLoadLocalizer.Load methods can be called during application startup or during runtime to (re)load the translations from the embedded resource for a specific language.

Example (C#)
void SetupI18N( CultureInfo culture )
{
  GlobalLocalizer.Localizer.Load( culture );
}
(Re)Loading Translations of Loadable Localizers

The ILoadableLocalizer interface defines LoadXML methods that accept the target language as an optional language identifier or culture (see TargetCulture) parameter.

The ILoadableLocalizer.LoadXML methods can be called during application startup or during runtime to (re)load the translations for a specific language.

Example (C#)
void SetupI18N( string language )
{
  var programPath = Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location );
  GlobalLocalizer.Localizer.LoadXML( programPath + "/I18N.xml", language );
}
Target Culture

When selecting the target language for translations, it can be specified either with a language identifier (using a string) or with a culture (using a CultureInfo).

When specifying the target language with a language identifier, the target culture for the localizer is automatically set to the culture associated in the system with the language identifier (obtained using System.Globalization.CultureInfo.GetCultureInfo). If the system does not support a culture for the target language, then the invariant culture will be used.

The localizer's target culture is used during formatting operations.

Advanced Usage (Internationalizing Libraries)

Library Localizers

The global localizer is convenient for usage in applications (i.e., which are implemented in the entry assembly), but libraries should not use the global localizer because they would depend on the application to load the translations for its internationalized strings, or risk the application discarding the translations after the library has merged its own translations automatically during library initialization.

For libraries the easiest solution is to define their own "global" localizer as a static property inside a static class, similar to the GlobalLocalizer class but only intended for the scope of the library.

This library localizer can be initialized using an instance of AutoLoadLocalizer, which is a special localizer that automatically loads the translations file from an embedded resource.

The static class can be declared with internal scope, or better with public scope to allow applications to access the library localizer (e.g., to add more translations, change them, reload them, etc.).

Finally, the translations file for the library must be embedded in the library assembly as an embedded resource identified by Resources.I18N.xml (just like with an application), which the AutoLoadLocalizer instance will try to load by default.

Library Localizer Implementation Example (C#)
using I18N.DotNet;
using System;

namespace ExampleLibrary
{
  public static class LibraryLocalizer
  {
    public static ILocalizer Localizer { get; } = new AutoLoadLocalizer();

    internal static string Localize( PlainString text ) => Localizer.Localize( text );
    internal static string Localize( FormattableString text ) => Localizer.Localize( text );
  }
}
Library Localizer Usage Example (C#)
using static ExampleLibrary.LibraryLocalizer;
using System;

namespace ExampleLibrary
{
  public class ExampleClass
  {
    public void SomeMethod()
    {
      Console.WriteLine( Localize( "Plain string to be translated" ) );
      Console.WriteLine( Localize( $"Interpolated string to be translated with value {i:X4}" ) );
    }
  }
}

API Documentation

ILocalizer Interface

The ILocalizer interface represents classes which provide localization functionality to software components (i.e. perform string translations) for a single target language:

  • Localize methods to translate strings, interpolated strings and collections of strings.
  • LocalizeFormat method to format and translate strings.
  • Context methods to access contexts and subcontexts (see Contexts).
ILoadableLocalizer Interface

The ILoadableLocalizer interface is an extension of ILocalizer that represents localizer classes which provide functionality to load translations for a single target language from different sources:

  • LoadXML methods to (re)load/merge translations from a file in the filesystem.
  • LoadXML methods to (re)load/merge translations from a Stream.
  • LoadXML methods to (re)load/merge translations from an XML document (XDocument).
  • LoadXML methods to (re)load/merge translations from an embedded resource in an assembly.
Localizer Class

The Localizer class is a simple implementation of ILoadableLocalizer which is capable of loading string translations for a single target language and then providing localization functionality.

AutoLoadLocalizer Class

The AutoLoadLocalizer class is an implementation of ILoadableLocalizer that on first call of any of its localization methods (i.e., those specified by ILocalizer), loads automatically the translations from an embedded resource in an assembly using the current UI language as the target language (if translations have not been previously loaded).

The default parameters for the AutoLoadLocalizer constructor make the created instance load the translations file from an embedded resource identified by Resources.I18N.xml in the calling assembly (i.e., in the assembly that creates the instance).

A different resource identifier or assembly can be passed as parameters to the AutoLoadLocalizer constructor if necessary.

Additionally, this class provides:

  • Load method to (re)load translations from the configured embedded resource for a given language.
GlobalLocalizer Class

The GlobalLocalizer static class provides access to the global localizer:

  • Localizer static property that provides the global localizer as a localizer instance.
  • Localize static methods to translate strings, interpolated strings and collections of strings.
  • LocalizeFormat static method to format and translate strings.
  • Context static methods to access contexts and subcontexts (see Contexts).
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Framework net472 is compatible.  net48 is compatible.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 4.7.2

    • No dependencies.
  • .NETFramework 4.8

    • No dependencies.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.3.1 1,059 3/12/2024
1.3.0 84 3/11/2024
1.2.1 91 3/8/2024
1.2.0 75 3/7/2024
1.1.1 386 7/7/2023