LocalizationProvider

repository·master·Indexed 41 days ago

https://github.com/valdisiljuconoks/localizationprovider

A database-driven localization framework for ASP.NET MVC applications. It replaces static resource files with database storage, featuring an Administration UI for editors, support for automatic machine translations via Azure AI, and client-side localization for JavaScript. Version 9.0 targets .NET 10 and supports SQL Server, PostgreSql, MongoDb, and Azure Tables storage providers.

Tokens
31.2K
Snippets
106
Records
130
Agent score
51%

What's inside LocalizationProvider

  1. What is LocalizationProvider?

    master

    LocalizationProvider is a database-driven localization provider for ASP.NET MVC web applications. It provides advanced features beyond standard resource files, including:

    • Database-driven storage: Manage translations in a database rather than static files.
    • Easy registration: Register resources directly via code.
    • Hierarchical organization: Support for hierarchical resource structures using child classes.
    • Administration UI: A built-in interface for editors to manage, add, and edit translations for various languages.
  2. New features in LocalizationProvider v9.0

    master

    Version 9.0 targets .NET 10 and introduces several productivity and performance enhancements:

    Translations & Admin UI

    • Automatic translations: Pluggable model supporting Azure AI / Cognitive Services. Supports single-resource translation and batch translations.
    • Batch translations: Select multiple resources, choose source/target languages, and preview proposed translations before saving.
    • Per-resource notes: Attach translator-facing hints using the [Notes] attribute in code or via the Admin UI. These round-trip through CSV import/export.
    • Bulk delete: Remove multiple resources at once.
    • Polished UI: Styled in-app confirmation dialogs.

    Performance

    • Optimized Sync: SQL Server synchronization uses a temp-table + bulk-copy path for large datasets.
    • Refactored Caching: Uses CachedTranslations with targeted invalidation and reduced allocations.
    • Memoization: Expression-based resource keys and cached handler chains.
  3. How the Synchronization Process works

    master

    The synchronization process runs during application startup to scan and register localization resources. It discovers resources by looking for types annotated with [LocalizedResource] or [LocalizedModel]. Once a type is found, the process scans its properties to identify localization keys.

    There are two types of localization resources:

    • Resources: POCO objects consisting of key/value pairs (the key is the resource identifier, and the value is the translation).
    • Models: Typically view models that use DataAnnotation attributes (such as [Display] or [Required]) for localization.
  4. Fetch translations as JSON via API

    master

    If you want to avoid polluting the global window object and prefer to fetch translations dynamically (e.g., via AJAX/XHR), you can request JSON directly from the localization provider endpoint.

    Direct JSON Request

    Append the json query parameter to your request URL to receive only the JSON representation of the resource class translations.

    AJAX/XHR Detection

    The localization provider automatically detects standard XHR/AJAX requests and will serve translations in JSON format by default.

  5. How fallback language lookup logic works

    master

    The translation lookup follows a sequential priority based on your configuration:

    1. Requested Culture: The provider first attempts to find the translation for the culture requested via ILocalizationProvider.GetString(() => ...).
    2. Fallback Chain: If the requested culture fails, the provider checks the FallbackCultures list in the order provided (e.g., if configured with sv -> no -> en, it checks no next).
    3. Invariant Culture: If all fallback languages fail, the provider will attempt to return a translation in the InvariantCulture if EnableInvariantCultureFallback is set to true.
  6. Use Export & Import with View Diff

    master

    The Administration UI includes an enhanced Export & Import feature designed to handle complex synchronization scenarios between environments (e.g., moving translations from Staging to Production).

    Unlike previous versions that only supported 'partial import' (new resources only) or 'full import' (flushing the target database), the current version allows you to view differences between your exported file and the target database. This helps prevent overwriting manual changes made by editors in the production environment while still allowing for the synchronization of new or updated translations.

  7. Localize Model Data Annotations

    master

    When using [LocalizedModel], the library automatically discovers resources for standard ASP.NET DataAnnotation attributes. This allows you to translate validation error messages.

    If a property has [Required] and [StringLength(5)], the library discovers keys for the property itself and the specific validation constraints (e.g., Property-Required).

    Supported attributes include:

    • [Display(Name = "...")] or [DisplayName]
    • [Required]
    • [StringLength]
    • Any attribute where you specify a custom ErrorMessage.
    namespace MySampleProject {
    
        [LocalizedModel]
        public class MyViewModel
        {
            [Display(Name = "This is default value")]
            [Required]
            [StringLength(5)]
            public string SampleProperty { get; set; }
        }
    }
    
    // Discovered keys:
    // MySampleProject.MyViewModel.SampleProperty
    // MySampleProject.MyViewModel.SampleProperty-Required
    // MySampleProject.MyViewModel.SampleProperty-StringLength
  8. Organize resources with Nested Localized Resources

    master

    You can create a resource hierarchy to avoid polluting the global namespace. To enable a natural usage path in code (e.g., MyPageResources.Header.HelloMessage), define the nested resource type as an instance property (do not use the static keyword on the nested property) within the parent [LocalizedResource] class.

    Usage Pattern:

    • Instance Property (Recommended): Allows usage like @Html.Translate(() => MyPageResources.Header.HelloMessage). The key becomes MyProject.MyPageResources.Header.HelloMessage.
    • Static Property: Requires usage like @Html.Translate(() => MyPageResources.HeaderResources.HelloMessage). This makes the path harder to discover in code.
    namespace MyProject
    {
        [LocalizedResource]
        public class MyPageResources
        {
            public static string ThisIsErrorMessage => "This is default value from code";
            public static HeaderResources Header { get; }
    
            public class HeaderResources
            {
                // Note: No 'static' keyword here to allow nested path usage
                public string HelloMessage => "Well, hello there!";
            }
        }
    }
    
    // Usage in View:
    // @Html.Translate(() => MyPageResources.Header.HelloMessage)
  9. Understand DbLocalizationProvider benchmark scenarios

    master

    The benchmarks focus on the performance of the ILocalizationProvider.GetString(...) method. The following scenarios are measured:

    • string-key, exact culture (cache hit): Calls GetString("known.key", en) where the translation is already in the cache. This measures the dispatcher, cache-hit, and return path without a fallback walk.
    • string-key, fr-BE -> fr fallback walk (cache hit): A request for fr-BE where the translation only exists for the parent culture fr. This measures the parent-culture fallback walk logic on top of a cache hit.
    • expression-key, exact culture (cache hit): Calls GetString(() => Resources.X) where X is already in the cache. This measures expression-tree walking and resource-key resolution on top of a cache hit.
  10. Reference shared resources with [UseResource]

    master

    To prevent polluting your localization database with duplicate entries for common properties (like Yes, No, or FirstName), you can use the [UseResource] attribute to point a model property to an existing resource defined in a different class.

    When a property is decorated with [UseResource], the localization engine will use the key from the referenced resource instead of generating a new key based on the current model's type and property name. This ensures that updating a single shared resource updates all properties that reference it.

    // 1. Define a class for shared/common resources
    [LocalizedResource]
    public class CommonResources
    {
        public static string CommonProp => "Common Value";
    }
    
    // 2. Reference that resource in your specific model
    [LocalizedModel]
    public class ModelWithOtherResourceUsage
    {
        [UseResource(typeof(CommonResources), nameof(CommonResources.CommonProp))]
        public string SomeProperty { get; set; }
    }
    
    // 3. Use standard translation helpers in your views
    // This will now resolve to 'CommonResources.CommonProp' instead of 'ModelWithOtherResourceUsage.SomeProperty'
    @Html.TranslateFor(m => m.SomeProperty)