< Summary

Information
Class: TeleFlow.Telegram.I18n.Fluent.Internal.FluentTextFormatter
Assembly: TeleFlow.Framework.I18n.Fluent
File(s): /_/src/TeleFlow.Framework.I18n.Fluent/Internal/FluentTextFormatter.cs
Line coverage
73%
Covered lines: 56
Uncovered lines: 20
Coverable lines: 76
Total lines: 171
Line coverage: 73.6%
Branch coverage
72%
Covered branches: 16
Total branches: 22
Branch coverage: 72.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Format(...)100%11100%
FormatHtml(...)100%1133.33%
FormatMarkdownV2(...)100%1133.33%
FormatCore(...)75%88100%
ConvertArguments(...)83.33%7666.66%
ParseMessageId(...)62.5%11863.63%

File(s)

/_/src/TeleFlow.Framework.I18n.Fluent/Internal/FluentTextFormatter.cs

#LineLine coverage
 1using System.Globalization;
 2using Linguini.Bundle;
 3using Linguini.Shared.Types.Bundle;
 4using TeleFlow.Telegram.Formatting;
 5
 6namespace TeleFlow.Telegram.I18n.Fluent.Internal;
 7
 8/// <summary>
 9/// Formats messages from immutable startup-loaded catalogs and enforces argument escaping and failure semantics.
 10/// </summary>
 11internal sealed class FluentTextFormatter(FluentCatalog catalog) : IFluentTextFormatter
 12{
 13    public string Format(
 14        Locale locale,
 15        string messageId,
 16        params ReadOnlySpan<I18nArgument> arguments)
 17    {
 80518        return FormatCore(locale, messageId, arguments, FluentRenderingMode.Plain);
 19    }
 20
 21    public TelegramFormattedText FormatHtml(
 22        Locale locale,
 23        string messageId,
 24        params ReadOnlySpan<I18nArgument> arguments)
 25    {
 526        var text = FormatCore(locale, messageId, arguments, FluentRenderingMode.Html);
 27
 28        try
 29        {
 430            return TelegramHtml.UnsafeMarkup(text);
 31        }
 032        catch (ArgumentException exception)
 33        {
 034            throw new FluentLocalizationException(
 035                messageId,
 036                locale,
 037                "the formatted HTML message was empty",
 038                exception);
 39        }
 440    }
 41
 42    public TelegramFormattedText FormatMarkdownV2(
 43        Locale locale,
 44        string messageId,
 45        params ReadOnlySpan<I18nArgument> arguments)
 46    {
 247        var text = FormatCore(locale, messageId, arguments, FluentRenderingMode.MarkdownV2);
 48
 49        try
 50        {
 251            return TelegramMarkdownV2.UnsafeMarkup(text);
 52        }
 053        catch (ArgumentException exception)
 54        {
 055            throw new FluentLocalizationException(
 056                messageId,
 057                locale,
 058                "the formatted MarkdownV2 message was empty",
 059                exception);
 60        }
 261    }
 62
 63    private string FormatCore(
 64        Locale locale,
 65        string messageId,
 66        ReadOnlySpan<I18nArgument> arguments,
 67        FluentRenderingMode mode)
 68    {
 81269        ArgumentNullException.ThrowIfNull(locale);
 81270        ArgumentException.ThrowIfNullOrWhiteSpace(messageId);
 71
 81272        var (message, attribute) = ParseMessageId(messageId, locale);
 81273        var resolved = catalog.Resolve(locale, mode);
 74
 75        try
 76        {
 81277            var fluentArguments = ConvertArguments(
 81278                arguments,
 81279                resolved.Locale.Culture,
 81280                mode,
 81281                messageId,
 81282                locale);
 83
 81184            IReadBundle bundle = resolved.Bundle;
 85
 81186            if (!bundle.TryGetMessage(
 81187                    message,
 81188                    attribute,
 81189                    fluentArguments,
 81190                    out var errors,
 81191                    out var formatted) ||
 81192                formatted is null)
 93            {
 394                throw new FluentLocalizationException(
 395                    messageId,
 396                    locale,
 397                    errors is { Count: > 0 }
 398                        ? "the message, attribute, variable, or function could not be resolved"
 399                        : "the message or attribute was not found");
 100            }
 101
 806102            return formatted;
 103        }
 3104        catch (FluentLocalizationException)
 105        {
 3106            throw;
 107        }
 3108        catch (Exception exception)
 109        {
 3110            throw new FluentLocalizationException(
 3111                messageId,
 3112                locale,
 3113                "an argument or Fluent function could not be formatted",
 3114                exception);
 115        }
 806116    }
 117
 118    private static Dictionary<string, IFluentType>? ConvertArguments(
 119        ReadOnlySpan<I18nArgument> arguments,
 120        CultureInfo culture,
 121        FluentRenderingMode mode,
 122        string messageId,
 123        Locale requestedLocale)
 124    {
 812125        if (arguments.IsEmpty)
 126        {
 297127            return null;
 128        }
 129
 515130        var result = new Dictionary<string, IFluentType>(arguments.Length, StringComparer.Ordinal);
 131
 2061132        foreach (var argument in arguments)
 133        {
 516134            if (!result.TryAdd(
 516135                    argument.Name,
 516136                    FluentValueConverter.Convert(argument.Value, culture, mode)))
 137            {
 0138                throw new FluentLocalizationException(
 0139                    messageId,
 0140                    requestedLocale,
 0141                    $"argument '{argument.Name}' was provided more than once");
 142            }
 143        }
 144
 514145        return result;
 146    }
 147
 148    private static (string Message, string? Attribute) ParseMessageId(
 149        string messageId,
 150        Locale locale)
 151    {
 812152        var firstDot = messageId.IndexOf('.');
 153
 812154        if (firstDot < 0)
 155        {
 810156            return (messageId, null);
 157        }
 158
 2159        if (firstDot == 0 ||
 2160            firstDot == messageId.Length - 1 ||
 2161            messageId.IndexOf('.', firstDot + 1) >= 0)
 162        {
 0163            throw new FluentLocalizationException(
 0164                messageId,
 0165                locale,
 0166                "message identifiers must use 'message-id' or 'message-id.attribute'");
 167        }
 168
 2169        return (messageId[..firstDot], messageId[(firstDot + 1)..]);
 170    }
 171}