ILine
ILine is a set of interfaces for broad variety of features. Lines can carry hints, key references and strings and binary resources. They can be implemented with one class, but are are typically used as compositions of smaller classes that form linked lists.
ILine key = LineRoot.Global.PluralRules("Unicode.CLDR35").Key("Key").Format("Hello, {0}");
Or constructed to span a tree structure (trie).
ILineRoot root = new LineRoot();
ILine hint1 = root.PluralRules("Unicode.CLDR35");
ILine section1 = hint1.Section("Section2");
ILine section1_1 = hint1.Section("Section1.1");
ILine key1_1_1 = section1_1.Key("Key1");
ILine key1_1_2 = section1_1.Key("Key2");
ILine value1_1_1 = key1_1_1.Format("Hello, {0}");
// ...
Parts
.Asset(IAsset) adds extra asset as line part.
IAsset resourceAsset = new ResourceDictionary( new Dictionary<ILine, byte[]>() );
ILine line = LineRoot.Global.Asset(resourceAsset);
.Logger(ILogger) adds logger as line part.
ILine line = LineRoot.Global.Logger(Console.Out, LineStatusSeverity.Ok);
.CulturePolicy(ICulturePolicy) adds a culture policy that determines which culture is active in a given execution context.
ICulturePolicy culturePolicy = new CulturePolicy().SetToCurrentCulture();
ILine line = LineRoot.Global.CulturePolicy(culturePolicy);
Hints
Hints are line parts that can be appended from localization file as well as from ILine.
Hints.ini
[PluralRules:Unicode.CLDR35]
Key:Error:FormatProvider:docs.CustomFormat,docs = Error, (Date = {0:DATE})
Key:Ok:StringFormat:Lexical.Localization.StringFormat.TextFormat,Lexical.Localization = OK {C# format is not in use here}.
.StringFormat(IStringFormat) and .StringFormat(string) add a string format, that determines the way the consequtive "String" parameters are parsed.
ILine line1 = LineRoot.Global.StringFormat(TextFormat.Default).String("Text");
ILine line2 = LineRoot.Global.StringFormat("Lexical.Localization.StringFormat.TextFormat,Lexical.Localization")
.String("Text");
.PluralRules(IPluralRules) and .PluralRules(string) add plural rules that determine how plurality are used in further line parts.
IPluralRules pluralRules = PluralRulesResolver.Default.Resolve("Unicode.CLDR35");
ILine line1 = LineRoot.Global.PluralRules(pluralRules);
ILine line2 = LineRoot.Global.PluralRules("Unicode.CLDR35");
ILine line3 = LineRoot.Global.PluralRules("[Category=cardinal,Case=one]n=1[Category=cardinal,Case=other]true");
.FormatProvider(IFormatProvider) and .FormatProvider(string) add a custom format provider that provides special format handling.
IFormatProvider customFormat = new CustomFormat();
ILine line1 = LineRoot.Global.FormatProvider(customFormat).Format("{0:DATE}").Value(DateTime.Now);
ILine line2 = LineRoot.Global.FormatProvider("docs.CustomFormat,docs").Format("{0:DATE}").Value(DateTime.Now);
public class CustomFormat : IFormatProvider, ICustomFormatter
{
public string Format(string format, object arg, IFormatProvider formatProvider)
{
if (format == "DATE" && arg is DateTime time)
{
return time.Date.ToString();
}
return null;
}
public object GetFormat(Type formatType)
=> formatType == typeof(ICustomFormatter) ? this : default;
}
.StringResolver(IStringResolver) adds IStringResolver as line part.
ResolveSource[] resolveSequence =
new ResolveSource[] { ResolveSource.Inline, ResolveSource.Asset, ResolveSource.Line };
IStringResolver stringResolver = new StringResolver(Resolvers.Default, resolveSequence);
ILine line = LineRoot.Global.StringResolver(stringResolver);
.StringResolver(string) adds assembly qualified class name to IStringResolver.
ILine line = LineRoot.Global.StringResolver("Lexical.Localization.StringFormat.StringResolver");
.BinaryResolver(IBinaryResolver) adds IBinaryResolver as line part.
ResolveSource[] resolveSequence =
new ResolveSource[] { ResolveSource.Inline, ResolveSource.Asset, ResolveSource.Line };
IBinaryResolver BinaryResolver = new BinaryResolver(Resolvers.Default, resolveSequence);
ILine line = LineRoot.Global.BinaryResolver(BinaryResolver);
.BinaryResolver(string) adds assembly qualified class name to IBinaryResolver.
ILine line = LineRoot.Global.BinaryResolver("Lexical.Localization.Binary.BinaryResolver");
Canonically compared keys
Key parts give ILines hash-equal comparison information. Key parts are used to match the line to a corresponding line of another culture in localization files.
Canonically compared key parts are compared so that the position of occurance is relevant.
.Key(string) appends "Key" key part.
ILine line = LineRoot.Global.Key("Ok");
.Section(string) appends "Section" key part.
ILine line = LineRoot.Global.Section("Resources").Key("Ok");
.Location(string) appends "Location" key part.
ILine line = LineRoot.Global.Location(@"c:\dir");
.BaseName(string) appends "BaseName" key part.
ILine line = LineRoot.Global.BaseName("docs.Resources");
Non-canonically compared keys
Non-canonically compared key parts are compared so that the position doesn't matter. The first occurance of each type is considered effective, rest are ignored.
.Assembly(Assembly) and .Assembly(string) append "Assembly" key.
Assembly asm = typeof(ILine_Examples).Assembly;
ILine line1 = LineRoot.Global.Assembly(asm);
ILine line2 = LineRoot.Global.Assembly("docs");
.Culture(CultureInfo) and .Culture(string) append "Culture" key.
CultureInfo culture = CultureInfo.GetCultureInfo("en");
ILine line1 = LineRoot.Global.Culture(culture);
ILine line2 = LineRoot.Global.Culture("en");
.Type<T>(), .Type(Type) and .Type(string) append "Type" key.
ILine line1 = LineRoot.Global.Type<ILine_Examples>();
ILine line2 = LineRoot.Global.Type(typeof(ILine_Examples));
Strings
.String(IString) appends preparsed default string value.
IString str = CSharpFormat.Default.Parse("ErrorCode = 0x{0:X8}");
ILine line = LineRoot.Global.Key("Error").String(str);
.String(string) appends "String" hint. This needs preceding "StringFormat" part in order to determine the way to parse. If "StringFormat" is not provided, then C# format is used as default.
ILine line = LineRoot.Global.Key("Error").StringFormat(CSharpFormat.Default).String("ErrorCode = 0x{0:X8}");
.Format(string) appends C# String formulation value.
ILine line = LineRoot.Global.Key("Error").Format("ErrorCode = 0x{0:X8}");
.Format($interpolated_string) appends formulation and value using string interpolation.
int code = 0x100;
ILine line = LineRoot.Global.Key("Error").Format($"ErrorCode = 0x{code:X8}");
.Text(string) appends plain text "String" value.
ILine line = LineRoot.Global.Key("Hello").Text("Hello World");
.ResolveString() resolves the string in current executing context. The result struct is LineString.
ILine line = LineRoot.Global.Key("Error").Format("ErrorCode={0}").Value(0x100);
LineString result = line.ResolveString();
Inlining
Code can be automatically scanned for inlined strings and exported to localization files.
They can be used as templates for further translation process.
This way the templates don't need to be manually updated as the code evolves.
.Inline(string subkey, string text) appends an inlined sub-line.
ILine line = LineRoot.Global.Section("Section").Key("Success")
.Format("Success") // Add inlining to the root culture ""
.Inline("Culture:en", "Success") // Add inlining to culture "en"
.Inline("Culture:fi", "Onnistui") // Add inlining to culture "fi"
.Inline("Culture:sv", "Det funkar"); // Add inlining to culture "sv"
.en(string) appends inlined value for that culture "en".
ILine line = LineRoot.Global.Section("Section").Key("Success")
.Format("Success") // Add inlining to the root culture ""
.en("Success") // Add inlining to culture "en"
.fi("Onnistui") // Add inlining to culture "fi"
.sv("Det funkar"); // Add inlining to culture "sv"
It's recommended to put inlined lines to variables for better performance. Inline allocates a Dictionary internally.
class MyController__
{
static ILine localization = LineRoot.Global.Assembly("docs").Type<MyControllerB>();
static ILine Success = localization.Key("Success").Format("Success").sv("Det funkar").fi("Onnistui");
public string Do()
{
return Success.ToString();
}
}
Enumerations
Enumerables can be localized just as any other type.
[Flags]
enum CarFeature
{
// Fuel Type
Electric = 0x0001,
Petrol = 0x0002,
NaturalGas = 0x0003,
// Door count
TwoDoors = 0x0010,
FourDoors = 0x0020,
FiveDoors = 0x0030,
// Color
Red = 0x0100,
Black = 0x0200,
White = 0x0300,
}
.Assembly<T>() and .Type<T>() appends "Assembly" and "Type" keys to refer to an enumeration type.
ILine carFeature = LineRoot.Global.Assembly("docs").Type<CarFeature>().Format("{0}");
.InlineEnum<T>() inlines every case of enum for culture "". It applies [Description] attribute when available.
ILine carFeature = LineRoot.Global.Assembly("docs").Type<CarFeature>().InlineEnum<CarFeature>().Format("{0}");
Enum localization strings can be supplied from files. (See CarFeatures.ini)
IAssetSource assetSource = LineReaderMap.Default.FileAssetSource(@"ILine\CarFeature.ini");
LineRoot.Builder.AddSource(assetSource).Build();
A single enum case can be matched with .Key(case).
Console.WriteLine(carFeature.Key(CarFeature.Petrol));
Console.WriteLine(carFeature.Key(CarFeature.Petrol).Culture("fi"));
Console.WriteLine(carFeature.Key(CarFeature.Petrol).Culture("sv"));
The result of the example above.
Petrol
Bensiini
Bensin
If enum value contains multiple cases, it must be resolved with inside a formulated string. Localization strings for the refered enum value are matched against keys "Assembly:asm:Type:enumtype:Key:case" from the IAsset. Inlined strings only apply with ILine instance that contains the inlinings.
CarFeature features = CarFeature.Petrol | CarFeature.FiveDoors | CarFeature.Black;
Console.WriteLine(carFeature.Value(features));
The result of the example above.
Petrol, FiveDoors, Black
Bensiini, Viisiovinen, Musta
Bensin, Femdörras, Svart
.InlineEnum(enumCase, culture, text) inlines culture specific texts to the ILine reference.
ILine carFeature = LineRoot.Global.Assembly("docs").Type<CarFeature>()
.InlineEnum<CarFeature>()
.InlineEnum(CarFeature.Electric, "fi", "Sähkö")
.InlineEnum(CarFeature.Petrol, "fi", "Bensiini")
.InlineEnum(CarFeature.NaturalGas, "fi", "Maakaasu")
.InlineEnum(CarFeature.TwoDoors, "fi", "Kaksiovinen")
.InlineEnum(CarFeature.FourDoors, "fi", "Neliovinen")
.InlineEnum(CarFeature.FiveDoors, "fi", "Viisiovinen")
.InlineEnum(CarFeature.Red, "fi", "Punainen")
.InlineEnum(CarFeature.Black, "fi", "Musta")
.InlineEnum(CarFeature.White, "fi", "Valkoinen")
.Format("{0}");
If placeholder format is "{enum:|}" then the printed string uses "|" as separator. "{enum: |}" prints with " | ".
Console.WriteLine(carFeature.Formulate($"{CarFeature.Petrol | CarFeature.Black:|}").Culture("fi"));
Console.WriteLine(carFeature.Formulate($"{CarFeature.Petrol | CarFeature.Black: |}").Culture("fi"));
Bensiini|Musta
Bensiini | Musta
Inlines placed in an ILine instance are applicable in another ILine instances, if the .Value() is supplied as ILine with inlinings and not as Enum.
ILine carFeature = LineRoot.Global.Assembly("docs").Type<CarFeature>().InlineEnum<CarFeature>()
.InlineEnum(CarFeature.Electric, "de", "Elektroauto")
.InlineEnum(CarFeature.Petrol, "de", "Benzinwagen")
.InlineEnum(CarFeature.NaturalGas, "de", "Erdgasauto")
.InlineEnum(CarFeature.TwoDoors, "de", "Zweitürig")
.InlineEnum(CarFeature.FourDoors, "de", "Viertürig")
.InlineEnum(CarFeature.FiveDoors, "de", "Fünftürige")
.InlineEnum(CarFeature.Red, "de", "Rot")
.InlineEnum(CarFeature.Black, "de", "Schwartz")
.InlineEnum(CarFeature.White, "de", "Weiß")
.Format("{0}");
ILine message = LineRoot.Global.Assembly("docs").Type("MyClass").Key("Msg")
.Format("Your car has following features: {0}")
.de("Ihr Auto hat folgende Eigenschaften: {0}");
CarFeature features = CarFeature.Petrol | CarFeature.Red | CarFeature.TwoDoors;
// Inlined enum strings don't work as Enum (unless tool is used)
Console.WriteLine(message.Value(features).Culture("de"));
// But works when ILine reference is used.
Console.WriteLine(message.Value(carFeature.Value(features)).Culture("de"));
Ihr Auto hat folgende Eigenschaften: Petrol, TwoDoors, Red
Ihr Auto hat folgende Eigenschaften: Benzinwagen, Zweitürig, Rot
However, if Lexical.Localization.Tool is used in the build process, then inlined strings are available as enums too. Tool picks up the inlined strings and places them into localization file.
Ihr Auto hat folgende Eigenschaften: Benzinwagen, Zweitürig, Rot
Ihr Auto hat folgende Eigenschaften: Benzinwagen, Zweitürig, Rot
Files that supply enumeration localization should use key in format of "Assembly:asm:Type:enumtype:Key:case".
CarFeatures.ini example file
[Assembly:docs:Type:docs.CarFeature:Culture:sv]
// Fuel Type
Key:Electric = El
Key:Petrol = Bensin
Key:NaturalGas = Naturgas
// Door count
Key:TwoDoors = Tvådörras
Key:FourDoors = Fyrdörras
Key:FiveDoors = Femdörras
// Color
Key:Red = Röd
Key:Black = Svart
Key:White = Vit
[Culture:sv]
Assembly:docs:Type:MyClass:Key:Msg = Din bil har: {0}
[Assembly:docs:Type:docs.CarFeature:Culture:fi]
// Fuel Type
Key:Electric = Sähkö
Key:Petrol = Bensiini
Key:NaturalGas = Maakaasu
// Door count
Key:TwoDoors = Kaksiovinen
Key:FourDoors = Neliovinen
Key:FiveDoors = Viisiovinen
// Color
Key:Red = Punainen
Key:Black = Musta
Key:White = Valkoinen
[Culture:fi]
Assembly:docs:Type:MyClass:Key:Msg = Autosi ominaisuudet: {0}
Resources
.ResolveBytes() resolves the line to bytes in the current executing context. The result struct is LineResourceBytes.
ILine line = LineRoot.Global.Key("Error").Binary(new byte[] { 1, 2, 3 });
LineBinaryBytes result = line.ResolveBytes();
.ResolveStream() resolves the line to string in the current executing context. The result struct is LineResourceStream.
ILine line = LineRoot.Global.Key("Error").Binary(new byte[] { 1, 2, 3 });
using (LineBinaryStream result = line.ResolveStream())
{
}
Use in classes
If class is designed to support dependency injection without string localizers, the constructors should take in argument ILine<T> and possibly ILineRoot. See more in Best Practices. Constructor argument ILine<T> helps the Dependency Injection to assign the localization so that it is scoped in to correct typesection.
class MyController
{
ILine localization;
public MyController(ILine<MyController> localization)
{
this.localization = localization;
}
public MyController(ILineRoot localizationRoot)
{
this.localization = localizationRoot.Assembly("docs").Type<MyController>();
}
public void Do()
{
string str = localization.Key("Success").en("Success").fi("Onnistui").ToString();
}
}
If class is designed to use static instance and without dependency injection, localization reference can be acquired from LineRoot.
class MyControllerB
{
static ILine localization = LineRoot.Global.Assembly("docs").Type<MyControllerB>();
public void Do()
{
string str = localization.Key("Success").en("Success").fi("Onnistui").ToString();
}
}