• Avalanche
  • Avalanche.Core
  • Lexical

    Show / Hide Table of Contents
    • Avalanche.Accessor
      • Introduction
      • IAccessor
        • IAccessor
        • IListAccessor
        • IMapAccessor
        • IRecordAccessor
        • IContentAccessor
        • IOneOfAccessor
        • IAnyAccessor
      • .Net
        • Introduction
        • IList<T>
        • IDictionary<K,V>
        • FieldInfo
        • OneOfAttribute
        • StructLayoutAttribute
        • Class
      • Protobuf
        • Introduction
      • Articles
        • Dependency Injection
        • AccessorMessages
    • Avalanche.Binding
      • Introduction
    • Avalanche.Core
      • License
      • Order
    • Avalanche.DataType
      • Introduction
      • DataType
        • IDataType
        • IListType
        • IMapType
        • IRecordType
        • IFieldType
        • IOneOfType
        • IAnyType
        • IStringType
        • IValueType
        • IIntegerType
        • IEnumerationType
        • IRealType
      • .Net
        • Introduction
        • IList<T>
        • IDictionary<K,V>
        • FieldInfo
        • Enum
        • OneOfAttribute
        • StructLayoutAttribute
        • Class
      • Protobuf
        • Introduction
      • Articles
        • DataTypeRequest
        • PrintTree
        • DataTypeMessages
    • Avalanche.Emit
      • Introduction
      • TypeBuilder
      • ConstructorBuilder
      • MethodBuilder
      • PropertyBuilder
      • FieldBuilder
      • Emit
      • Utilities
    • Avalanche.FileSystem
      • Introduction
      • Abstractions
        • IFileSystem
          • IFileSystemBrowse
          • IFileSystemCreateDirectory
          • IFileSystemDelete
          • IFileSystemFileAttribute
          • IFileSystemMount
          • IFileSystemMove
          • IFileSystemObserve
          • IFileSystemOpen
        • IEvent
        • IEntry
        • IOption
        • IToken
      • FileSystem
      • VirtualFileSystem
      • MemoryFileSystem
      • EmbeddedFileSystem
      • HttpFileSystem
      • Decoration
      • IFileProvider
      • Events
      • Utilities
        • Dispose
        • File Scanner
        • Visit Tree
        • File Operation
    • Avalanche.Identity
      • Introduction
      • Identity
      • IdentityParts
      • IdentityInterner
      • IdentityComparer
      • Print Tree
      • IdentityAccessors
        • Introduction
        • TypeName
    • Avalanche.Localization
      • Introduction
      • Localization
      • LocalizationFile
      • LocalizationFiles
      • LocalizationFileSystem
      • LocalizationFileFormat
      • LocalizationLine
      • LocalizationLines
      • TemplateFormat
      • CultureProvider
      • FallbackCultureProvider
      • ResourceManager
      • LocalizationError
      • Microsoft.Extensions
        • Introduction
        • DependencyInjection
        • FileProvider
        • Logging
        • ITextLocalizer
        • IFileLocalizer
        • Localization
        • Asp.Net
        • Asp.Net (Min Api)
      • Pluralization
        • Introduction
        • Multiple plural parameters
        • Custom PluralRules
        • Invariant Culture
        • Unit Prefix
        • IPluralRule
        • IPluralNumber
        • IPluralRules
        • CLDRs
        • Unicode.CLDR41
        • Unicode.CLDR40
      • Articles
        • Alphabet localization
        • Benchmarks
        • Caching
        • Class Library
        • Demo
        • Diagnostics
        • Embedded resources
        • Emplacement
        • File localization
        • Text localization
    • Avalanche.Message
      • Introduction
      • IMessage
      • IMessageProvider
      • IMessageDescription
      • IMessageDescriptions
      • MessageLevel
      • Message printing
      • Messages and Exceptions
      • Logging
      • Validation
      • Localization
    • Avalanche.Service
      • Introduction
      • Service
        • Introduction
        • IService
        • IServiceDisposable
        • IServiceDecoration
        • IServiceCast
        • IServiceObservable
        • IServiceContainer
        • Construction
        • Query
        • CancellationToken
        • CachePolicy
        • Scope
      • Handler
        • Introduction
        • IHandler
        • IHandlerCast
        • IHandlerDecoration
        • IHandlerWithOrder
        • CancellationToken
        • Cyclicity
        • Delegates
        • Invokable
        • ExportAttribute
        • OrderAttribute
        • PrintTree
        • Recursion
      • Query
        • Introduction
        • IQuery
        • IQueryCast
        • IQueryDecoration
      • Entry
        • Introduction
        • IEntry
        • IEntryCast
        • IEntryDecoration
        • IEntryObservable
        • IEntryVisitable
        • EntryState
      • Request
        • Introduction
        • IRequest
        • IRequestFor
        • IRequestToBeCached
        • IRequestToBeDisposed
        • RequestAttribute
        • ContextParameterAttribute
        • Print Tree
      • Dependency Injection
        • Introduction
        • Asp.Net
        • ServiceRequest<T>
        • Decorating a service
        • Handler
        • CachePolicy
        • CancellationToken
        • QueryLogger
      • Examples
        • NodeCount
        • Expression
        • Mapper
      • Articles
        • Benchmarks
        • Error Handling
        • ServiceMessages
    • Avalanche.StatusCode
      • Introduction
      • HResult
        • Introduction
        • HResult.Facilities
        • BasicMessages
        • RpcMessages
        • DispatchMessages
        • ItfMessages
        • Win32Messages
        • ClrMessages
      • System
        • Introduction
        • AccessControlMessages
        • AggregateMessages
        • AppDomainMessages
        • ArgumentMessages
        • ArgumentNullMessages
        • ArgumentOutOfRangeMessages
        • ArithmeticMessages
        • ArrayMessages
        • AssemblyMessages
        • BadImageFormatMessages
        • CodeContractMessages
        • CodePageMessages
        • CollectionsMessages
        • CompilerServiceMessages
        • CryptographyMessages
        • CultureMessages
        • DiagnosticsMessages
        • EventSourceMessages
        • ExecutionEngineMessages
        • FormatMessages
        • HostProtectionMessages
        • IOMessages
        • IndexOutOfRangeMessages
        • InteropServiceMessages
        • InvalidCastMessages
        • InvalidOperationMessages
        • IsolatedStorageMessages
        • LazyMessages
        • MarshalerMessages
        • MemoryMessages
        • MiscellaneousMessages
        • NotImplementedMessages
        • NotSupportedMessages
        • ObjectDisposedMessages
        • OperationCanceledMessages
        • OverflowMessages
        • PlatformMessages
        • PolicyMessages
        • PrincipalMessages
        • ProgramMessages
        • ReferenceMessages
        • ReflectionMessages
        • RegionMessages
        • RemotingMessages
        • ResourcesMessages
        • SecurityMessages
        • SerializationMessages
        • StackMessages
        • TaskMessages
        • TextMessages
        • ThreadingMessages
        • TimeZoneMessages
        • TypeMessages
        • XmlMessages
    • Avalanche.Template
      • Introduction
      • TemplateFormats
      • ITemplatePrintable
      • ITemplateFormatPrintable
      • ITemplateText
      • ITemplateBreakdown
      • ITemplateFormat
      • ITemplateFormats
      • Extract Arguments
      • Emplacement
    • Avalanche.Tokenizer
      • Introduction
      • IToken
      • ITokenizer
      • Tokenizers
    • Avalanche.Utilities
      • Introduction
      • Collections
        • Tuples
        • StructList
        • ArrayList
        • BijectionMap
        • LocakableDictionary
        • LockableList
        • MapList
        • Pipe
        • RingQueue
        • EnumerableExtensions
        • TupleUtilities
        • ArrayUtilities
      • Comparers
        • IGraphComparer
        • IGraphComparable
        • AlphaNumericComparer
        • EnumerableComparer
        • EnumerableGraphComparer
        • ReferenceComparer
        • KeyValuePairComparer
        • DefaultComparerProvider
        • RecordComparer
      • Cloners
        • ICloner
        • IGraphCloner
        • IGraphCloneable
        • ListCloner
        • DictionaryCloner
        • FieldCloner
        • PassthroughCloner
        • RecordCloner
        • ClonerProvider
      • Dispose
        • IDisposeAttachable
        • IDisposeBelatable
      • Provider
        • Introduction
        • ProviderBase
        • Delegate
        • Pipe
        • Cache
        • ResultCapture
        • AsReadOnly
        • AsService
        • IProviderEvent
      • Record
        • IRecordDescription
        • IFieldDescription
        • IConstructorDescription
        • IConstructionDescription
        • IParameterDescription
        • IRecordProviders
        • RecordDelegates
          • RecordCreate
          • RecordClone
          • RecordCopy
          • IRecordDelegates
        • FieldDelegates
          • FieldRead
          • FieldWrite
          • RecreateWith
          • IFieldDelegates
      • String
        • IEscaper
        • UnicodeString
      • Miscellaneous
        • IIdGenerator
        • Permutation
        • IReadOnly
        • IUserDataContainer
        • Void
    • Avalanche.Writer
      • Introduction
      • ConstantWriter
      • Context
      • ConvertWriter
      • DefaultConstructor
      • DelegateWriter
      • PassthroughWriter
      • Referer
      • TypeCast
      • Writer
      • WriterPipe
      • WriterMessages

    Asp.Net

    This article describes how to localize a asp application. There is a sample library on github.

    To run Avalanche.Localization on Asp.Net, make sure the Application's .csproj is targeting platform .NET6 or higher.

      <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework><EnablePreviewFeatures>true</EnablePreviewFeatures>
      </PropertyGroup>
    

    Add package references.

    <PropertyGroup>
        <RestoreAdditionalProjectSources>https://avalanche.fi/Avalanche.Core/nupkg/index.json</RestoreAdditionalProjectSources>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Avalanche.Localization"/>
        <PackageReference Include="Avalanche.Localization.Cldr"/>
        <PackageReference Include="Avalanche.Localization.Extensions"/>
        <PackageReference Include="Avalanche.Localization.Asp"/>
    </ItemGroup>
    

    Go to appsettings.json and add "Debug" level for "Avalanche" to get notified on localization issues.

      "Logging": {
        "LogLevel": {
          "Default": "Warning",
          "Avalanche": "Debug"
        }
      },
    

    Get IServiceCollection reference from WebApplicationBuilder or run this in Startup.cs.

    IServiceCollection services = builder.Services;
    

    Remove possible previous .AddLocalization() service, ...

    
    services.AddLocalization(options =>
    {
        options.ResourcesPath = "Resources";
    });
    
    

    Add .AddAvalancheLocalizationService().

    using Avalanche.Localization;
    
    services
        .AddAvalancheLocalizationService()
        .AddAvalancheLocalizationEmbeddedResourceProviderDefault()
        .AddAvalancheLocalizationResourceManagerProvider()
        .AddAvalancheLocalizationAspSupport()
        .AddAvalancheStringLocalizer()
        .AddSingleton(typeof(ILocalizationFilePatterns), new LocalizationFilePatterns("Pages/{Key}", "Pages/{Culture}/{Key}"))
        .AddAvalancheLocalizationEmbeddedResourceProvider("*/*.Pages.{Key}", "*/*.Pages.{Culture}.{Key}")
        .Configure<Microsoft.Extensions.Localization.LocalizationOptions>(options =>
        {
            options.ResourcesPath = null!;
        });
    

    Choose localization file source. The .AddAvalancheLocalizationService() above adds application root (calls AddAvalancheLocalizationFileSystemApplicationRoot()). This can be replaced with a file provider.

    services.Remove(LocalizationServiceDescriptors.Instance.LocalizationFileSystemApplicationRoot);
    services.AddAvalancheLocalizationToUseFileProvider();
    

    Possibly add IFileProvider.

    services
        .AddSingleton(typeof(IFileProvider), new PhysicalFileProvider(AppDomain.CurrentDomain.BaseDirectory));
    

    You may or may not want to add IViewLocalizer and IHtmlLocalizer services from Microsoft.AspNetCore.Mvc.Localization.

    using Microsoft.Extensions.DependencyInjection;
    services.AddRazorPages().AddViewLocalization();
    

    After 'app' construction, add workaround to a known issue: Flushes cache if Assembly is loaded later. Late loaded assembly may supply localization lines that have already been queried and cached. This issue will be fixed properly later.

    AppDomain.CurrentDomain.AssemblyLoad += (object? sender, AssemblyLoadEventArgs args) =>
        (app.Services.GetRequiredService<ILocalization>() as ICached)?.InvalidateCache(true);
    

    More information in Microsoft.Extensions.DependencyInjection.

    Note

    AspNetCore support is experimental.

    Supplying localization

    Localization can be supplied in both .resx and .yaml files. .yaml can be dropped in the Resources/ folder either as embedded resource or as output copied resource.

    Note that it may be good idea to have "null" as Resources path in Startup.cs to have easier keys and consistent across service interfaces.

    services.Configure<Microsoft.Extensions.Localization.LocalizationOptions>(options => 
        {
            options.ResourcesPath = null;
        });
    

    Example: Resources/samples.asp.Pages.IndexModel.yaml (Mark: Copy to Output Directory: Copy if newver)

    TemplateFormat: BraceNumeric
    PluralRules: Unicode.CLDR41
    
    English:
    - Culture: "en"
      Items:
      - Key: samples.asp.Pages.IndexModel.Home
        Text: "Home page"
      - Key: samples.asp.Pages.IndexModel.Welcome
        Text: "Welcome"
      - Key: samples.asp.Pages.IndexModel.Content
        Text: Learn about <a href="http://avalanche.fi/Avalanche.Core/Avalanche.Localization/docs/aspnet/index.html">Avalanche.Localization</a>.
    

    Example: Resources/fi/samples.asp.Pages.IndexModel.yaml

    TemplateFormat: BraceNumeric
    PluralRules: Unicode.CLDR41
    
    Finnish:
    - Culture: "fi"
      Items:
      - Key: samples.asp.Pages.IndexModel.Home
        Text: "Kotisivu"
      - Key: samples.asp.Pages.IndexModel.Welcome
        Text: "Tervetuloa"
      - Key: samples.asp.Pages.IndexModel.Content
        Text: Lue lisää <a href="http://avalanche.fi/Avalanche.Core/Avalanche.Localization/docs/aspnet/index.html">Avalanche.Localization:sta</a>.
    

    Injecting to pages

    Page can be designed to use either Microsoft's service interfaces or Avalanche's localization interfaces, or both.

    If Microsoft's interface is used then @inject with IStringLocalizer<T>, IViewLocalizer and IHtmlLocalizer<T>. The localization files must be supply lines to key "Assembly[.Resources].Namespace.Key".

    Open Pages/_ViewImports.cshtml and add:

    // Ms.Localization
    @using System.Globalization
    @using Microsoft.AspNetCore.Localization
    @using Microsoft.AspNetCore.Mvc.Localization
    @using Microsoft.Extensions.Localization
    @inject IViewLocalizer ViewLocalizer
    
    @page
    @inject IStringLocalizer<IndexModel> Localizer
    @inject IHtmlLocalizer<IndexModel> HtmlLocalizer
    
    @model IndexModel
    
    @{
        ViewData["Title"] = Localizer["Home"];
    }
    
    <div class="text-center">
        <h1 class="display-4">@Localizer["Welcome"]</h1>
        <p>@HtmlLocalizer["Content"]</p>
    </div>
    


    If page uses Avalanche's interface then @inject with ITextLocalizer, ITextLocalizer<T>, IFileLocalizer, IFileLocalizer<T>, ILocalization interfaces.

    Open Pages/_ViewImports.cshtml and add:

    // Av.Localization
    @using System.Globalization
    @using Avalanche.Localization
    @inject ITextLocalizer TextLocalizer
    @inject IFileLocalizer FileLocalizer
    

    There is extension method .Html(args?) and .Localize(args?) (Avalanche.Localization.Asp.dll) that adapt to LocalizedHtmlString and LocalizedString respective.

    @{
        ViewData["Title"] = TextLocalizer["samples.asp.Pages.IndexModel.Home"];
    }
    
    <div class="text-center">
        <h1 class="display-4">@Localizer["samples.asp.Pages.IndexModel.Welcome"]</h1>
        <p>@TextLocalizer["samples.asp.Pages.IndexModel.Content"].LocalizeHtml()</p>
    </div>
    

    Or use the model specific localizer ITextLocalizer<T>:

    @page
    @inject ITextLocalizer<IndexModel> IndexLocalizer
    @model IndexModel
    
    @{
        ViewData["Title"] = IndexLocalizer["Home"];
    }
    
    <div class="text-center">
        <h1 class="display-4">@Localizer["Welcome"]</h1>
        <p>@IndexLocalizer["Content"].LocalizeHtml()</p>
    </div>
    
    Warning

    Asp <form> element is based on ResourceTypeAttribute on model seems to use direct ResourceManager reference.

    Element localization doesn't go through DependencyInjection, IServiceProvider and IStringLocalizer stack and thus doesn't get localized. There is inconsistent design, some parts are dependency injectible, others not.

    <form method="post" asp-page="Index">
        <div class="form-group">
            <label asp-for="Hero" class="control-label"></label>
            <input asp-for="Hero" class="form-control"/>
            <span asp-validation-for="Hero" class="text-danger"></span>
        </div>
        <button type="submit">@Localizer["Submit"]</button>
    </form>
    

    Culture Assigner

    This section describes how to add a culture assigner selection dropdown.

    Culture assigner

    Add IOptions<RequestLozalizationOptions> service.

    services.Configure<RequestLocalizationOptions>(options =>
        {
            options.AddSupportedCultures("en", "fi", "sv");
            options.AddSupportedUICultures("en", "fi", "sv");
            options.SetDefaultCulture("en");
            options.FallBackToParentCultures = true;
            options.FallBackToParentUICultures = true;
        });
    

    Add .UseRequestLocalization to 'app'.

    RequestLocalizationOptions localizationOptions = app.Services.GetRequiredService<IOptions<RequestLocalizationOptions>>().Value;
    app.UseRequestLocalization(localizationOptions);
    

    Add Models/CultureAssignmentModel.cs

    namespace samples.asp.Models;
    using System.Globalization;
    
    /// <summary>Culture assignment model</summary>
    public record CultureAssignmentModel(CultureInfo CurrentUICulture, IList<CultureInfo> SupportedCultures);
    

    Add Pages/Components/CultureAssignment/default.cshtml.

    @using System.Globalization
    @model samples.asp.Models.CultureAssignmentModel
    
    <form id="culture-assignment">
        <select name="culture" onchange="this.parentElement.submit();">
            @foreach (CultureInfo culture in Model.SupportedCultures)
            {
                <option value="@culture.Name" selected="@(Model!.CurrentUICulture.Name == culture.Name)">@culture.DisplayName</option>
            }
        </select>
    </form>
    

    Add Pages/ViewComponents/CultureAssignmentViewComponent.cs.

    namespace samples.asp.ViewComponents;
    using System.Globalization;
    using Microsoft.AspNetCore.Localization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Options;
    using samples.asp.Models;
    
    /// <summary></summary>
    public class CultureAssignmentViewComponent : ViewComponent
    {
        /// <summary>Options</summary>
        IOptions<RequestLocalizationOptions> localizationOptions;
    
        /// <summary>Create component</summary>
        public CultureAssignmentViewComponent(IOptions<RequestLocalizationOptions> localizationOptions)
        {
            // Assert not null
            ArgumentNullException.ThrowIfNull(localizationOptions);
    
            this.localizationOptions = localizationOptions;
        }
    
        /// <summary>Assign culture</summary>
        public IViewComponentResult Invoke()
        {
            // Get culture feature with assigned culture
            IRequestCultureFeature cultureFeature = HttpContext.Features.Get<IRequestCultureFeature>()!;
            // Create model with culture assignment record
            CultureAssignmentModel model = new CultureAssignmentModel(
                CurrentUICulture: cultureFeature.RequestCulture.UICulture,
                SupportedCultures: localizationOptions.Value.SupportedUICultures?.ToArray() ?? Array.Empty<CultureInfo>()
            );
            // Return partial view
            return View(model);
        }
    }
    

    Add @addTagHelper *, samples.asp to Pages/_ViewImports.cshtml.

    @using samples.asp
    @using samples.asp.Models
    @namespace samples.asp.Pages
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    @addTagHelper *, samples.asp
    

    Add <vc:culture-assignment/> to Pages/Shared/_Layout.cshtml.

    <div class="container">
        <a class="navbar-brand" asp-area="" asp-page="/Index">samples.asp</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
            aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
            <ul class="navbar-nav flex-grow-1">
                <li class="nav-item">
                    <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
                </li>
            </ul>
        </div>
        <vc:culture-assignment/>
    </div>
    

    Diagnostics

    If localization issues require diagnosis, the reference for localization can be acquired in Program.cs.

    IHostBuilder hostBuilder = CreateHostBuilder(args);
    IHost host = hostBuilder.Build();
    ILocalization localization = (ILocalization)host.Services.GetService(typeof(ILocalization));
    

    Following snippet in Program.cs prints accessible localization lines.

    var app = builder.Build();
    
    // Localization diagnostics
    {
        // Get localization context
        ILocalization localization = app.Services.GetService<ILocalization>()!;
        // Get logger
        ILogger logger = app.Services.GetService<ILogger<Program>>()!;
        // Print lines that are visible to localization
        if (localization.LineQueryCached.TryGetValue((null, null), out IEnumerable<IEnumerable<KeyValuePair<string, MarkedText>>> lines))
        {
            foreach (var line in lines)
            {
                // Read values
                line.ReadValues(out MarkedText pluralRules, out MarkedText culture, out MarkedText key, out MarkedText plurals, out MarkedText template, out MarkedText text);
                // No value
                if (!key.HasValue || !text.HasValue) continue;
                // Log
                logger.LogInformation("Culture={Culture}, Key={Key}, Text={Text}, Template={Template}, PluralRules={PluralRules}, Plurals={Plurals}", culture.Text, key.Text, text.Text, template.Text, pluralRules.Text, plurals.Text);
            }
        }
    }
    
    Back to top Copyright © 2022 Toni Kalajainen, contact@avalanche.fi