< Summary

Information
Class: MyNet.Globalization.ServiceCollectionExtensions
Assembly: MyNet.Globalization
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Globalization/Extensions/ServiceCollectionExtensions.cs
Tag: 323_28699572109
Line coverage
98%
Covered lines: 72
Uncovered lines: 1
Coverable lines: 73
Total lines: 253
Line coverage: 98.6%
Branch coverage
80%
Covered branches: 8
Total branches: 10
Branch coverage: 80%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddGlobalization(...)50%22100%
AddLocalization(...)50%2296.96%
AddInflection(...)100%11100%
AddLocalizationService(...)100%66100%
ConfigureLocalizationService(...)100%11100%
AddTranslationResource(...)100%11100%
UseGlobalization(...)100%11100%
UseLocalization(...)100%11100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Globalization/Extensions/ServiceCollectionExtensions.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="ServiceCollectionExtensions.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Globalization;
 9using System.Linq;
 10using System.Resources;
 11using Microsoft.Extensions.DependencyInjection;
 12using Microsoft.Extensions.DependencyInjection.Extensions;
 13using MyNet.Globalization.Culture;
 14using MyNet.Globalization.DateTime;
 15using MyNet.Globalization.Events;
 16using MyNet.Globalization.Facade;
 17using MyNet.Globalization.Inflection;
 18using MyNet.Globalization.Inflection.Cultures;
 19using MyNet.Globalization.Localization.Providers;
 20using MyNet.Globalization.Localization.Providers.Factories;
 21using MyNet.Globalization.Localization.Providers.Registration;
 22using MyNet.Globalization.Localization.Translation;
 23using MyNet.Globalization.Localization.Translation.Catalog;
 24using MyNet.Globalization.Localization.Translation.KeyGeneration;
 25using MyNet.Globalization.Localization.Translation.KeyResolving;
 26
 27#pragma warning disable IDE0130 // Namespace does not match folder structure
 28namespace MyNet.Globalization;
 29#pragma warning restore IDE0130 // Namespace does not match folder structure
 30
 31public static class ServiceCollectionExtensions
 32{
 33    extension(IServiceCollection services)
 34    {
 35        /// <summary>
 36        /// Adds globalization services (culture, time zone, events) to the service collection.
 37        /// Registers <see cref="ICultureService"/> as the application-level <see cref="ICultureContext"/>.
 38        /// </summary>
 39        /// <param name="configure">An optional action to configure the <see cref="GlobalizationOptions"/>.</param>
 40        /// <returns>The updated service collection.</returns>
 41        public IServiceCollection AddGlobalization(Action<GlobalizationOptions>? configure = null)
 42        {
 943            var options = new GlobalizationOptions();
 944            configure?.Invoke(options);
 45
 946            services.TryAddSingleton(options);
 47
 48            // Core services
 949            services.TryAddSingleton<ICultureService>(static sp => new CultureService(sp.GetRequiredService<Globalizatio
 950            services.TryAddSingleton<ITimeZoneService>(static sp => new TimeZoneService(sp.GetRequiredService<Globalizat
 951            services.TryAddSingleton<IGlobalizationService, GlobalizationService>();
 52
 53            // Events
 954            services.TryAddSingleton<IGlobalizationEvents, GlobalizationEvents>();
 55
 956            return services;
 57        }
 58
 59        /// <summary>
 60        /// Adds localization services (translation pipeline, provider resolver) to the service collection.
 61        /// Requires that an <see cref="ICultureContext"/> has already been registered —
 62        /// either via <c>AddGlobalization()</c> (which registers the application-level <see cref="ICultureService"/>)
 63        /// or by registering a custom <see cref="ICultureContext"/> implementation.
 64        /// When neither is present, falls back to reading <see cref="System.Globalization.CultureInfo.CurrentCulture"/>
 65        /// of the current thread via <c>ThreadCultureContext</c>.
 66        /// </summary>
 67        /// <returns>The updated service collection.</returns>
 68        public IServiceCollection AddLocalization(Action<LocalizationServiceFactoryBuilder<IInflector>>? configure = nul
 69        {
 70            // ------------------------------------------------------------------
 71            // Culture context
 72            // Prefer the application-level ICultureService (registered by AddGlobalization).
 73            // Fall back to ThreadCultureContext only when neither is available.
 74            // ------------------------------------------------------------------
 9375            services.TryAddSingleton<ICultureContext>(sp => sp.GetService<ICultureService>() ?? (ICultureContext)new Thr
 76
 77            // ------------------------------------------------------------------
 78            // Translation catalog
 79            // ------------------------------------------------------------------
 9380            services.TryAddSingleton<ITranslationCatalog>(sp =>
 9381            {
 9382                var builder = new TranslationCatalogBuilder();
 9383
 9384                foreach (var contribution in sp.GetServices<ITranslationCatalogContribution>().OrderBy(x => x.Priority))
 9385                {
 9386                    contribution.Apply(builder);
 9387                }
 9388
 9389                return builder.Build();
 9390            });
 91
 92            // ------------------------------------------------------------------
 93            // Localization provider registry (uses IServiceProvider to resolve factories on demand)
 94            // ------------------------------------------------------------------
 9395            services.TryAddSingleton<ILocalizationFactoryRegistry>(sp =>
 9396            {
 9397                var factories = sp.GetServices<ILocalizationServiceFactory>().ToDictionary(x => x.TargetType, x => x);
 9398
 9399                return new LocalizationFactoryRegistry(factories);
 93100            });
 101
 102            // ------------------------------------------------------------------
 103            // Localization provider resolver
 104            // ------------------------------------------------------------------
 93105            services.TryAddSingleton<ILocalizationServiceResolver, LocalizationServiceResolver>();
 93106            services.TryAddSingleton(typeof(ICultureScopedServiceSource<>), typeof(CultureScopedServiceSource<>));
 107
 108            // ------------------------------------------------------------------
 109            // Translation pipeline
 110            // ------------------------------------------------------------------
 93111            services.TryAddSingleton<ITranslationKeyProvider, TranslationKeyProvider>();
 93112            services.TryAddSingleton<ITranslationKeyResolver, TranslationKeyResolver>();
 113
 114            // --- Pure, stateless translator ---
 93115            services.TryAddSingleton<ITranslator>(sp =>
 93116                new Translator(sp.GetRequiredService<ITranslationCatalog>(),
 93117                    sp.GetRequiredService<ITranslationKeyResolver>(),
 93118                    sp.GetRequiredService<IPluralizationService>(),
 93119                    sp.GetService<GlobalizationOptions>()?.CultureFallbackPolicy));
 120
 121            // --- Contextual translation service ---
 93122            services.TryAddSingleton<ITranslationService, TranslationService>();
 123
 124            // Static facade initialization
 93125            services.TryAddSingleton<ILocalizationRuntime, LocalizationRuntime>();
 126
 127            // ------------------------------------------------------------------
 128            // Inflection providers
 129            // ------------------------------------------------------------------
 93130            services.AddInflection();
 131
 93132            if (configure is not null)
 0133                services.ConfigureLocalizationService(configure);
 134
 93135            return services;
 136        }
 137
 138        /// <summary>
 139        /// Adds the default inflection provider to the service collection with built-in support for English and French.
 140        /// Additional cultures can be registered via <c>ConfigureLocalizationService</c>.
 141        /// </summary>
 142        /// <returns>The updated service collection.</returns>
 143        public IServiceCollection AddInflection()
 144        {
 99145            services.TryAddSingleton<IPluralizationService, PluralizationService>();
 99146            services.AddLocalizationService<IInflector>((_, _) => Inflectors.Invariant);
 147
 99148            services.ConfigureLocalizationService<IInflector>(builder => builder
 99149                .RegisterCulture(SupportedCultures.English, () => Inflectors.English)
 99150                .RegisterCulture(SupportedCultures.French, () => Inflectors.French));
 151
 99152            return services;
 153        }
 154
 155        /// <summary>
 156        /// Registers a culture-aware localization provider of type <typeparamref name="TService"/>.
 157        /// The <paramref name="defaultFactory"/> is invoked for cultures that have no explicit registration.
 158        /// Use <c>ConfigureLocalizationService</c> to register culture-specific overrides.
 159        /// </summary>
 160        /// <param name="defaultFactory">Factory invoked with (IServiceProvider, CultureInfo) to create the default prov
 161        /// <typeparam name="TService">The culture-aware provider type to register.</typeparam>
 162        /// <returns>The updated service collection.</returns>
 163        public IServiceCollection AddLocalizationService<TService>(Func<IServiceProvider, CultureInfo, TService> default
 164            where TService : class, ICultureScoped
 165        {
 166            // Guard against duplicate registrations (TryAddEnumerable with factory delegates
 167            // cannot distinguish entries by implementation type and would throw ArgumentException).
 171168            if (services.Any(d => d.ServiceType == typeof(ILocalizationServiceFactory<TService>)))
 59169                return services;
 170
 112171            services.AddSingleton<ILocalizationServiceFactory<TService>>(sp =>
 112172            {
 112173                var builder = new LocalizationServiceFactoryBuilder<TService>(x => defaultFactory(sp, x));
 112174
 112175                var services = sp.GetServices<ILocalizationFactoryRegistration<TService>>()
 112176                    .OrderBy(x => x.Priority).ToList();
 112177
 112178                services.ForEach(cfg => cfg.Configure(builder));
 112179
 112180                return builder.Build();
 112181            });
 182
 183            // Register the non-generic marker so the registry can discover all factories.
 184            // We use AddSingleton (not TryAddEnumerable) because TryAddEnumerable requires
 185            // a concrete implementation type for deduplication, which factory-delegate descriptors lack.
 112186            services.AddSingleton<ILocalizationServiceFactory>(sp => sp.GetRequiredService<ILocalizationServiceFactory<T
 187
 112188            return services;
 189        }
 190
 191        /// <summary>
 192        /// Registers culture-specific overrides for a previously added <typeparamref name="TService"/>.
 193        /// Multiple calls are allowed; registrations are applied in ascending priority order.
 194        /// </summary>
 195        /// <param name="configure">Action that registers culture-specific factories on the builder.</param>
 196        /// <param name="priority">Priority of this registration. Higher values are applied later (override earlier ones
 197        /// <typeparam name="TService">The culture-aware provider type to configure.</typeparam>
 198        /// <returns>The updated service collection.</returns>
 199        public IServiceCollection ConfigureLocalizationService<TService>(Action<LocalizationServiceFactoryBuilder<TServi
 200            where TService : class, ICultureScoped
 201        {
 127202            services.TryAddEnumerable(ServiceDescriptor.Singleton<ILocalizationFactoryRegistration<TService>>(new Locali
 203
 127204            return services;
 205        }
 206
 207        /// <summary>
 208        /// Contributes a translation resource manager to the catalog at startup.
 209        /// </summary>
 210        /// <param name="resourceKey">Unique key identifying the resource (e.g., "DateTimeResources").</param>
 211        /// <param name="resourceManager">The resource manager instance.</param>
 212        /// <param name="priority">Contribution priority. Higher values are applied later.</param>
 213        /// <returns>The updated service collection.</returns>
 214        public IServiceCollection AddTranslationResource(string resourceKey, ResourceManager resourceManager, int priori
 215        {
 63216            ArgumentException.ThrowIfNullOrWhiteSpace(resourceKey);
 63217            ArgumentNullException.ThrowIfNull(resourceManager);
 218
 219            // Multiple resources share the same contribution implementation type,
 220            // so we must not use TryAddEnumerable here (it deduplicates by implementation type).
 63221            services.AddSingleton<ITranslationCatalogContribution>(
 63222                new TranslationCatalogContribution(registry => registry.Register(resourceKey, resourceManager), priority
 223
 63224            return services;
 225        }
 226    }
 227
 228    extension(IServiceProvider serviceProvider)
 229    {
 230        /// <summary>
 231        /// Initializes the globalization static facade by configuring it with the registered <see cref="IGlobalizationS
 232        /// </summary>
 233        /// <returns>The updated service provider.</returns>
 234        public IServiceProvider UseGlobalization()
 235        {
 3236            GlobalizationServices.Configure(serviceProvider.GetRequiredService<IGlobalizationService>());
 237
 3238            return serviceProvider;
 239        }
 240
 241        /// <summary>
 242        /// Initializes the localization static facade by configuring it with the registered <see cref="ILocalizationRun
 243        /// </summary>
 244        /// <returns>The updated service provider.</returns>
 245        public IServiceProvider UseLocalization()
 246        {
 11247            Localizer.Configure(serviceProvider.GetRequiredService<ILocalizationRuntime>());
 248
 11249            return serviceProvider;
 250        }
 251    }
 252}
 253