C# Class Loyc.Localize

Localize is a global hook into which a string-mapping localizer can be installed. It is designed to make internationalization exceptionally easy for developers. TODO: expand I18N features based on Mozilla's L20N.
All Loyc code should call this hook in order to localize text (although as of June 2015, no one has made any translation tables). Use it like this: string result = Localize.From("Hello, {0}", userName); If you use this facility frequently in a given class, you may want to shorten your typing using a static variable: protected static readonly FormatterDelegate L = Localize.From; Then you can simply write L("Hello, {0}", userName) instead. Either way, 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. Alternately, assuming you have the ability to change the table of translations, you can use a Symbol in your code and call the other overload of From() to look up the text that should be shown to the end user: string result = Localize.From((Symbol)"MY_STRING")); string result = Localize.From(@@MY_STRING); // EC# syntax 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.

Localize.Formatter() is then called to make the completed string, unless the variable argument list is empty. It is possible to perform formatting separately, for example: Console.WriteLine(Localize.From("{0} is {0:X} in hexadecimal"), N); Here, writeline performs the formatting instead. However, Localize's default formatter, Strings.Format, has an extra feature that the standard formatter does not: named arguments. Here is an example: ... string verb = Localize.From(IsFileLoaded ? "parse" : "load"); MessageBox.Show( Localize.From("Not enough memory to {load/parse} '{filename}'." {Message}", "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 indicate to the translator person what a particular argument is for.

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("MyStrings", this.GetType().Assembly); Console.Writeline(rm.GetString("HEXER"), N); This approach has drawbacks: * It may be cumbersome to pass around a ResourceManager instance between all classes that might contain localizable strings; a global facility is much more convenient. * The programmer has to put all translations in the resource file; consequently, writing the code is bothersome because the programmer has to switch to the resource file and add the string to it. Someone reading the code, in turn, can't tell what the string says and has to load up the resource file to find out. * It is nontrivial to change the localization manager; for instance, what if someone wants to store translations in an .ini or .xml file rather than inside the assembly? What if the user wants to centralize all translations for a set of assemblies, rather than having separate resources in each assembly? * Keeping in mind that the guy in charge of translation is typically different than the guys writing most of the code, it makes sense to keep translations separate from everything else.

The idea of the 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, since Localize.From() is no harder to type than String.Format().

The translation system itself is separate, and connected to Localize by a delegate, for two reasons:

  1. Multiple translation systems are possible. This class should be suitable for any .NET program, and some programs using this utility will want to plug-in a different localizer.
  2. I personally don't have the time or expertise to write a localizer at this time. So until I do, the Localize class will make my code ready for translation, although not actually localized.
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 Loyc extensions made by many different people.

To facilitate this, I propose that in addition to a translator, a Loyc extension 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 ISimpleMessageSink.Write(). See LocalizableAttribute.

Show file Open project: qwertie/ecsharp Class Usage Examples

Public Properties

Property Type Description
_formatter ThreadLocalVariable
_localizer ThreadLocalVariable

Public Methods

Method Description
Localized ( [ message ) : string

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

Localized ( [ message, object arg1 ) : string
Localized ( [ message, object arg1, object arg2 ) : string
Passthru ( Symbol msgId, string msg ) : string

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.

SetFormatter ( FormatterDelegate newValue ) : SavedValue

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.

SetLocalizer ( LocalizerDelegate newValue ) : SavedValue

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

Symbol ( Symbol resourceId, [ message, object arg1 ) : string
Symbol ( Symbol resourceId, [ message, object arg1, object arg2 ) : string
Symbol ( this resourceId, [ message ) : string

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

UseResourceManager ( ResourceManager manager, CultureInfo culture = null, string>.Func resxNameCalculator = null, bool fallbackToPrevious = true ) : SavedValue

Uses a standard ResourceManager object to obtain translations.

Method Details

Localized() public static method

Finds and formats a localization of the given message. If none is found, the original string is formatted.
public static Localized ( [ message ) : string
message [ The message to translate, which may include argument /// placeholders (e.g. "{0}"). The default formatter also accepts named /// parameters like "{firstName}"; see for /// details.
return string

Localized() public static method

public static Localized ( [ message, object arg1 ) : string
message [
arg1 object
return string

Localized() public static method

public static Localized ( [ message, object arg1, object arg2 ) : string
message [
arg1 object
arg2 object
return string

Passthru() public static method

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.
public static Passthru ( Symbol msgId, string msg ) : string
msgId Symbol
msg string
return string

SetFormatter() public static method

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.
public static SetFormatter ( FormatterDelegate newValue ) : SavedValue
newValue FormatterDelegate
return SavedValue

SetLocalizer() public static method

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
public static SetLocalizer ( LocalizerDelegate newValue ) : SavedValue
newValue LocalizerDelegate
return SavedValue

Symbol() public static method

public static Symbol ( Symbol resourceId, [ message, object arg1 ) : string
resourceId Symbol
message [
arg1 object
return string

Symbol() public static method

public static Symbol ( Symbol resourceId, [ message, object arg1, object arg2 ) : string
resourceId Symbol
message [
arg1 object
arg2 object
return string

Symbol() public static method

This is the heart of the Localize class, which localizes and formats a string.
public static Symbol ( this resourceId, [ message ) : string
resourceId this Resource ID used to look up a translated format /// string using the current user-defined . 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.
message [ The message to be translated, which may include /// argument placeholders (e.g. "{0}"). The default formatter also accepts /// named parameters like "{firstName}"; see /// for details.
return string

UseResourceManager() public static method

Uses a standard ResourceManager object to obtain translations.
public static UseResourceManager ( ResourceManager manager, CultureInfo culture = null, string>.Func resxNameCalculator = null, bool fallbackToPrevious = true ) : SavedValue
manager ResourceManager A ResourceManager that provides access to resources (resx embedded in an assembly)
culture CultureInfo
resxNameCalculator string>.Func An 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 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. ///
fallbackToPrevious bool If a translation was not found in the /// specified ResourceManager and this parameter is true, the previously- /// installed is called instead. Otherwise, the /// dummy translator is used.
return SavedValue

Property Details

_formatter public static property

public static ThreadLocalVariable _formatter
return ThreadLocalVariable

_localizer public static property

public static ThreadLocalVariable _localizer
return ThreadLocalVariable