Enhanced C#
Language of your choice: library documentation
Public static fields | Properties | Static Public Member Functions | List of all members
Loyc.Localize Class Reference

Localize is a global hook into which a string-mapping localizer can be installed. It makes your program localization-ready with no effort. More...


Source file:

Remarks

Localize is a global hook into which a string-mapping localizer can be installed. It makes your program localization-ready with no effort.

The idea of this Localize facility is to convince programmers to support localization by making it dead-easy to do. By default it is not connected to any translator (it just passes strings through), so people who are only writing a program for a one-language market can easily make their code "multiligual-ready" without doing any extra work. All you do is call the .Localized() extension method, which is actually shorter than the traditional string.Format().

The translation system itself is separate from this class, and connected to Localized() by a delegate, so that multiple translation systems are possible. This class should be suitable for use in any .NET program, and some programs using this utility will want to plug-in a different localizer.

Use it like this:

string result = "Hello, {0}".Localized(userName);

Or, for increased clarity, use named placeholders:

string result = "Hello, {person's name}".Localized("person's name", userName);

Whatever localizer is installed will look up the text in its database and return a translation. If no translation to the end user's language is available, an appropriate default translation should be returned: either the original text, or a translation to some default language, e.g. English.

The localizer will need an external table of translations, conceptually like this:

| Key name      | Language | Translated text |
| "Hello, {0}"  | "es"     | "Hola, {0}"     |
| "Hello, {0}"  | "fr"     | "Bonjour, {0}"  |
| "Load"        | "es"     | "Cargar"        |
| "Load"        | "fr"     | "Charge"        |
| "Save"        | "es"     | "Guardar"       |
| "Save"        | "fr"     | "Enregistrer"   |

Many developers use a resx file to store translations. This class supports that approach, as explained below.

For longer messages, it is preferable to use a short name to represent the message so that, when the English language text is edited, the translation tables do not have to be updated. To do this, use the Symbol(string, string, object[]) method:

string result = Localize.Symbol("ConfirmQuitWithoutSaving", 
    "Are you sure you want to quit without saving '{filename}'?", "filename", fileName);
// Enhanced C# syntax with symbol literal
string result = Localize.Symbol(@ConfirmQuitWithoutSaving, 
    "Are you sure you want to quit without saving '{filename}'?", "filename", fileName);

This is most useful for long strings or paragraphs of text, but I expect that some projects, as a policy, will use symbols for all localizable text. (When using Localize.Symbol, the actual message is allowed to be null. In that case, the symbol (first argument) is returned as a last resort if no translation is found.

If the variable argument list is not empty, Localize.Formatter is called to build the completed string from the format string. It is possible to perform formatting separately, for example:

Console.WriteLine("{0} is {0:X} in hexadecimal".Localized(), N);

Here, WriteLine itself performs the formatting instead.

As demonstrated above, Localize's default formatter, StringExt.FormatCore, has an extra feature that the standard formatter does not: named arguments. Here is an example:

...
string verb = (IsFileLoaded ? "parse" : "load").Localized();
MessageBox.Show(
"Not enough memory to {load/parse} '{filename}'.".Localized(
"load/parse", verb, "filename", FileName));

As you can see, named arguments are mentioned in the format string by specifying an argument name such as {filename} instead of a number like {0}. The variable argument list contains the same name followed by its value, e.g. "filename", FileName. This feature gives you, the developer, the opportunity to tell the person writing translations what the purpose of a particular argument is.

The translator must not change any of the arguments: the word "{filename}" is not to be translated.

At run-time, the format string with named arguments is converted to a "normal" format string with numbered arguments. The above example would become "Could not {1} the file: {3}" and then be passed to string.Format.

Design rationale

Many developers don't want to spend time writing internationalization or localization code, and are tempted to write code that is only for one language. It's no wonder, because it's a relative pain in the neck. Microsoft suggests that code carry around a "ResourceManager" object and directly request strings from it:

private ResourceManager rm;
rm = new ResourceManager("AssemblyName.Resources", this.GetType().Assembly);
Console.Writeline(rm.GetString("StringIdentifier"));

This approach has drawbacks:

Microsoft does address the first of these drawbacks by providing a code generator built into Visual Studio that gives you a global property for each string; see http://stackoverflow.com/questions/1142802/how-to-use-localization-in-c-sharp

Even so, you may find that this class provides a more convenient approach because your native-language strings are written right in your code, and because you are guaranteed to get a string at runtime (not null) if the desired language is not available.

This class supports ResourceManager via the UseResourceManager helper method. For example, after calling Localize.UseResourceManager(resourceManager), if you write

"Save As...".Localized()

Then resourceManager.GetString("Save As...") is called to get the translated string, or the original string if no translation was found. You can even add a "name calculator" to encode your resx file's naming convention, e.g. by removing spaces and punctuation (for details, see UseResourceManager.)

It is conventional in .NET programs to have one "main" resx file, e.g. Resources.resx, that contains default strings, along other files with non-English translations (e.g. Resources.es.resx for Spanish). When using Localized() you would typically use a slightly different approach: you still have a Resources.resx file, but you leave the string table empty. This causes Visual Studio to generate a Resources class with a ResourceManager property so that you need can easily get the ResourceManager object.

  1. When your program starts, call Localize.UseResourceManager(Resources.ResourceManager).
  2. Use the Localized() extension method to get translations of short strings.
  3. For long strings, use Localize.Symbol("ShortAlias", "Long string", params...). The first argument is the string passed to ResourceManager.GetString()

In the open source world, most developers don't have a team of translators ready make translations for them. The idea of Loyc, for example, is that many different individuals–not one big team–of programmers will create and maintain features. By centralizing this translation facility, it should be straightforward for a single multilingual individual to translate the text of many modules made by many different people.

To facilitate this, I propose that in addition to a translator, a program should be made to figure out all the strings/symbols for which translations are needed. To do this it would scan source code (at compile time) for calls to methods in this class and generate a list of strings and symbols needing translation. It would also have to detect certain calls that perform translation implicity, such as IMessageSink.Write(). See LocalizableAttribute.

TODO: expand I18N features based on Mozilla's L20N.

Public static fields

static ThreadLocalVariable< LocalizerDelegate > _localizer = new ThreadLocalVariable<LocalizerDelegate>(Passthru, autoFallback: true)
 
static ThreadLocalVariable< FormatterDelegate > _formatter = new ThreadLocalVariable<FormatterDelegate>(StringExt.FormatCore, autoFallback: true)
 

Properties

static LocalizerDelegate Localizer [get]
 Localizer method, which is a do-nothing pass-through by default More...
 
static FormatterDelegate Formatter [get]
 Formatting delegate, which is StringExt.FormatCore by default. More...
 

Static Public Member Functions

static SavedValue< LocalizerDelegate > SetLocalizer (LocalizerDelegate newValue)
 Sets the localizer method. More...
 
static SavedValue< LocalizerDelegate > UseResourceManager (ResourceManager manager, CultureInfo culture=null, Func< string, string > resxNameCalculator=null, bool fallbackToPrevious=true)
 Uses a standard ResourceManager object to obtain translations. More...
 
static SavedValue< FormatterDelegate > SetFormatter (FormatterDelegate newValue)
 Sets the formatter method. More...
 
static string Passthru (Symbol msgId, string msg)
 This is the dummy translator, which is the default value of Localizer. It passes strings through untranslated. A msgId symbol cannot be handled so it is simply converted to a string. More...
 
static string Symbol (this Symbol resourceId, [Localizable] string message, params object[] args)
 This is the heart of the Localize class, which localizes and formats a string. More...
 
static string Symbol (this string resourceId, [Localizable] string message, params object[] args)
 
static string Localized ([Localizable] this string message, params object[] args)
 Finds and formats a localization of the given message. If none is found, the original string is formatted. More...
 
static string Localized ([Localizable] this string message)
 
static string Localized ([Localizable] this string message, object arg1)
 
static string Symbol (Symbol resourceId, [Localizable] string message, object arg1)
 
static string Localized ([Localizable] this string message, object arg1, object arg2)
 
static string Symbol (Symbol resourceId, [Localizable] string message, object arg1, object arg2)
 

Member Function Documentation

static string Loyc.Localize.Localized ( [Localizable] this string  message,
params object[]  args 
)
inlinestatic

Finds and formats a localization of the given message. If none is found, the original string is formatted.

Parameters
messageThe message to translate, which may include argument placeholders (e.g. "{0}"). The default formatter also accepts named parameters like "{firstName}"; see StringExt.FormatCore for details.
argsArguments given to Formatter to fill in placeholders after the Localizer is called. If args is null or empty then Formatter is not called.
Returns
The translated and formatted string.

References Loyc.Localize.Symbol(), and Loyc.Threading.ScratchBuffer< T >.Value.

Referenced by Loyc.Collections.Impl.CPByteTrie< TValue >.Add(), Loyc.Collections.MSet< Symbol >.Add(), Loyc.Collections.Impl.CPIntTrie< TValue >.Add(), Loyc.Collections.AListBase< K, T >.AListBase(), Loyc.Collections.Set< Loyc.LLParserGenerator.AndPred >.Find(), Loyc.Collections.VListBlock< T >.FindNextBlock(), Loyc.MessageSink.FormatMessage(), Loyc.Collections.WListProtected< T >.IndexOf(), Loyc.Syntax.BaseParser< Token >.LaIndexToMsgContext(), Loyc.LLPG.Macros.LllpgMacro(), Loyc.Syntax.BaseParser< Token >.MatchError(), Loyc.Syntax.Les.Les2Lexer.ParseNumberCore(), Loyc.Syntax.ParsingService.ParseSingle(), Loyc.Collections.DList< Pair< LNode, int > >.RemoveAll(), Loyc.LLParserGenerator.LLParserGenerator.Run(), Loyc.Syntax.StreamCharSource.Slice(), Loyc.Syntax.Les.Les2Lexer.SupportDotIndents(), Loyc.Collections.CPStringTrie< TValue >.TryAdd(), Loyc.Collections.Impl.CPByteTrie< TValue >.TryAdd(), and Loyc.Syntax.Les.Les2Lexer.UnescapeQuotedString().

static string Loyc.Localize.Passthru ( Symbol  msgId,
string  msg 
)
inlinestatic

This is the dummy translator, which is the default value of Localizer. It passes strings through untranslated. A msgId symbol cannot be handled so it is simply converted to a string.

References Loyc.Symbol.Name.

Referenced by Loyc.Localize.SetLocalizer().

static SavedValue<FormatterDelegate> Loyc.Localize.SetFormatter ( FormatterDelegate  newValue)
inlinestatic

Sets the formatter method.

Formatter is a thread-local value, but since .NET does not support inheritance of thread-local values, this method also sets the global default used by threads on which this method was never called.

References Loyc.StringExt.FormatCore().

static SavedValue<LocalizerDelegate> Loyc.Localize.SetLocalizer ( LocalizerDelegate  newValue)
inlinestatic

Sets the localizer method.

Localizer is a thread-local value, but since .NET does not support inheritance of thread-local values, this method also sets the global default used by threads on which this method was never called.

This property follows the Ambient Service Pattern: http://core.loyc.net/essentials/ambient-service-pattern.html

References Loyc.Localize.Passthru().

Referenced by Loyc.Localize.UseResourceManager().

static string Loyc.Localize.Symbol ( this Symbol  resourceId,
[Localizable] string  message,
params object[]  args 
)
inlinestatic

This is the heart of the Localize class, which localizes and formats a string.

Parameters
resourceIdResource ID used to look up a translated format string using the current user-defined Localizer. If this parameter is null, a message must be provided; otherwise, the message is only used if no translation is associated with the specified Symbol.
messageThe message to be translated, which may include argument placeholders (e.g. "{0}"). The default formatter also accepts named parameters like "{firstName}"; see StringExt.FormatCore for details.
argsArguments given to Formatter to fill in placeholders after the Localizer is called. If args is null or empty then Formatter is not called.
Returns
The translated and formatted string.

References Loyc.Localize.Formatter, and Loyc.Localize.Localizer.

Referenced by Loyc.Localize.Localized(), and Loyc.Localize.Symbol().

static SavedValue<LocalizerDelegate> Loyc.Localize.UseResourceManager ( ResourceManager  manager,
CultureInfo  culture = null,
Func< string, string >  resxNameCalculator = null,
bool  fallbackToPrevious = true 
)
inlinestatic

Uses a standard ResourceManager object to obtain translations.

Parameters
managerA ResourceManager that provides access to resources (resx embedded in an assembly)
cultureA value of CultureInfo that represents the language to look up in the ResourceManager. If this is null, the ResourceManager will use CultureInfo.CurrentUICulture.
resxNameCalculatorAn optional function that will be called when a translation is requested without providing a resource key symbol. For example, if someone writes "Save as...".Localized() using the Localized(string) extension method, this function is called on the string "Save as...". This function could be used to compute a resource name such as "strSaveAs" automatically, according to whatever naming convention is used in your resource file.
fallbackToPreviousIf a translation was not found in the specified ResourceManager and this parameter is true, the previously- installed Localizer is called instead. Otherwise, the dummy translator Passthru is used.
Returns

References Loyc.Localize.Formatter, Loyc.Localize.Localizer, Loyc.Symbol.Name, and Loyc.Localize.SetLocalizer().

Property Documentation

FormatterDelegate Loyc.Localize.Formatter
staticget

Formatting delegate, which is StringExt.FormatCore by default.

Referenced by Loyc.Localize.Symbol(), and Loyc.Localize.UseResourceManager().

LocalizerDelegate Loyc.Localize.Localizer
staticget

Localizer method, which is a do-nothing pass-through by default

Referenced by Loyc.Localize.Symbol(), and Loyc.Localize.UseResourceManager().