| | | 1 | | // ----------------------------------------------------------------------- |
| | | 2 | | // <copyright file="TranslationKeyResolver.cs" company="Stéphane ANDRE"> |
| | | 3 | | // Copyright (c) Stéphane ANDRE. All rights reserved. |
| | | 4 | | // </copyright> |
| | | 5 | | // ----------------------------------------------------------------------- |
| | | 6 | | |
| | | 7 | | using System; |
| | | 8 | | using System.Collections.Generic; |
| | | 9 | | using System.Diagnostics.CodeAnalysis; |
| | | 10 | | using System.Globalization; |
| | | 11 | | using System.Linq; |
| | | 12 | | using MyNet.Globalization.Inflection; |
| | | 13 | | |
| | | 14 | | namespace MyNet.Globalization.Localization.Translation.KeyResolving; |
| | | 15 | | |
| | | 16 | | /// <summary> |
| | | 17 | | /// Current implementation of <see cref="ITranslationKeyResolver"/>. |
| | | 18 | | /// </summary> |
| | | 19 | | public sealed class TranslationKeyResolver(IPluralizationService pluralizationService) : ITranslationKeyResolver |
| | | 20 | | { |
| | | 21 | | public const string DefaultSuffix = ""; |
| | | 22 | | public const string SymbolSuffix = nameof(DisplayStyle.Symbol); |
| | | 23 | | public const string AbbreviationSuffix = "Abbr"; |
| | | 24 | | public const string ShortSuffix = nameof(DisplayStyle.Short); |
| | | 25 | | public const string NarrowSuffix = nameof(DisplayStyle.Narrow); |
| | | 26 | | |
| | | 27 | | public const string PluralSuffix = "Plural"; |
| | | 28 | | public const string ZeroSuffix = "Zero"; |
| | | 29 | | |
| | 12 | 30 | | private static readonly Dictionary<DisplayStyle, string> StyleSuffix = new() |
| | 12 | 31 | | { |
| | 12 | 32 | | { DisplayStyle.Default, DefaultSuffix }, |
| | 12 | 33 | | { DisplayStyle.Symbol, SymbolSuffix }, |
| | 12 | 34 | | { DisplayStyle.Abbreviation, AbbreviationSuffix }, |
| | 12 | 35 | | { DisplayStyle.Short, ShortSuffix }, |
| | 12 | 36 | | { DisplayStyle.Narrow, NarrowSuffix } |
| | 12 | 37 | | }; |
| | | 38 | | |
| | 12 | 39 | | private static readonly Dictionary<DisplayStyle, DisplayStyle[]> StyleSuffixes = new() |
| | 12 | 40 | | { |
| | 12 | 41 | | { DisplayStyle.Default, [DisplayStyle.Default] }, |
| | 12 | 42 | | { DisplayStyle.Symbol, [DisplayStyle.Symbol, DisplayStyle.Abbreviation] }, |
| | 12 | 43 | | { DisplayStyle.Abbreviation, [DisplayStyle.Abbreviation, DisplayStyle.Short, DisplayStyle.Narrow] }, |
| | 12 | 44 | | { DisplayStyle.Short, [DisplayStyle.Short, DisplayStyle.Narrow, DisplayStyle.Abbreviation] }, |
| | 12 | 45 | | { DisplayStyle.Narrow, [DisplayStyle.Narrow, DisplayStyle.Short, DisplayStyle.Abbreviation] } |
| | 12 | 46 | | }; |
| | | 47 | | |
| | | 48 | | /// <inheritdoc /> |
| | | 49 | | public IReadOnlyCollection<TranslationKeyCandidate> Resolve(string baseKey, TranslationOptions options, CultureInfo |
| | | 50 | | { |
| | 1002 | 51 | | ArgumentException.ThrowIfNullOrWhiteSpace(baseKey); |
| | | 52 | | |
| | 1002 | 53 | | var list = new List<TranslationKeyCandidate>(); |
| | 1002 | 54 | | var seen = new HashSet<TranslationKeyCandidate>(); |
| | | 55 | | |
| | 1002 | 56 | | var pluralSuffixes = options.Quantity.HasValue ? ResolvePluralSuffixes(options.Quantity.Value, culture) : []; |
| | 1002 | 57 | | var styles = StyleSuffixes[options.Style]; |
| | | 58 | | |
| | | 59 | | // -------------------------------------------------- |
| | | 60 | | // 1. MOST SPECIFIC: STYLE + PLURAL |
| | | 61 | | // -------------------------------------------------- |
| | 2616 | 62 | | foreach (var plural in pluralSuffixes) |
| | | 63 | | { |
| | 1236 | 64 | | foreach (var style in styles) |
| | 312 | 65 | | add(baseKey, style, plural); |
| | | 66 | | } |
| | | 67 | | |
| | | 68 | | // -------------------------------------------------- |
| | | 69 | | // 2. PLURAL ONLY |
| | | 70 | | // -------------------------------------------------- |
| | 2616 | 71 | | foreach (var plural in pluralSuffixes) |
| | 306 | 72 | | add(baseKey, pluralSuffix: plural); |
| | | 73 | | |
| | | 74 | | // -------------------------------------------------- |
| | | 75 | | // 3. STYLE ONLY |
| | | 76 | | // -------------------------------------------------- |
| | 4068 | 77 | | foreach (var style in styles) |
| | 1032 | 78 | | add(baseKey, style); |
| | | 79 | | |
| | | 80 | | // -------------------------------------------------- |
| | | 81 | | // 4. FALLBACK BASE KEY |
| | | 82 | | // -------------------------------------------------- |
| | 1002 | 83 | | add(baseKey); |
| | | 84 | | |
| | 1002 | 85 | | return list; |
| | | 86 | | |
| | | 87 | | void add(string key, DisplayStyle? style = null, string? pluralSuffix = null) |
| | | 88 | | { |
| | | 89 | | var finalKey = BuildKey(key, style.HasValue ? StyleSuffix[style.Value] : null, pluralSuffix); |
| | | 90 | | |
| | | 91 | | var policy = GetPolicy(style, pluralSuffix); |
| | | 92 | | |
| | | 93 | | var candidate = new TranslationKeyCandidate(finalKey, policy); |
| | | 94 | | if (seen.Add(candidate)) |
| | | 95 | | list.Add(candidate); |
| | | 96 | | } |
| | | 97 | | } |
| | | 98 | | |
| | | 99 | | /// <inheritdoc /> |
| | | 100 | | public string ResolvePluralizedKey(string baseKey, decimal count, CultureInfo culture) |
| | | 101 | | { |
| | 6 | 102 | | ArgumentException.ThrowIfNullOrWhiteSpace(baseKey); |
| | | 103 | | |
| | 6 | 104 | | var suffix = ResolvePluralSuffixes(count, culture).FirstOrDefault(); |
| | | 105 | | |
| | 6 | 106 | | return BuildKey(baseKey, pluralSuffix: suffix); |
| | | 107 | | } |
| | | 108 | | |
| | | 109 | | /// <inheritdoc /> |
| | | 110 | | public string ResolveStyledKey(string baseKey, DisplayStyle style, CultureInfo culture) |
| | | 111 | | { |
| | 3 | 112 | | ArgumentException.ThrowIfNullOrWhiteSpace(baseKey); |
| | | 113 | | |
| | 3 | 114 | | var suffix = ResolveStyleSuffixes(style).FirstOrDefault(); |
| | | 115 | | |
| | 3 | 116 | | return BuildKey(baseKey, styleSuffix: suffix); |
| | | 117 | | } |
| | | 118 | | |
| | | 119 | | /// <summary> |
| | | 120 | | /// Builds a translation key by combining the base key with the specified suffix. The resulting key is formed by con |
| | | 121 | | /// </summary> |
| | | 122 | | /// <param name="baseKey">The base translation key.</param> |
| | | 123 | | /// <param name="styleSuffix">The optional style suffix to append to the base key.</param> |
| | | 124 | | /// <param name="pluralSuffix">The optional plural suffix to append to the base key.</param> |
| | | 125 | | /// <returns>The combined translation key.</returns> |
| | 2661 | 126 | | private static string BuildKey(string baseKey, string? styleSuffix = null, string? pluralSuffix = null) => string.Co |
| | | 127 | | |
| | | 128 | | /// <summary> |
| | | 129 | | /// Determines the translation fallback policy based on the presence of style and plural suffixes. The method evalua |
| | | 130 | | /// </summary> |
| | | 131 | | /// <param name="style">The display style of the translation key.</param> |
| | | 132 | | /// <param name="pluralSuffix">The plural suffix of the translation key.</param> |
| | | 133 | | /// <returns>The determined translation fallback policy.</returns> |
| | | 134 | | [SuppressMessage("ReSharper", "ConvertIfStatementToReturnStatement", Justification = "Readability")] |
| | | 135 | | private static TranslationFallbackPolicy GetPolicy(DisplayStyle? style, string? pluralSuffix) |
| | | 136 | | { |
| | | 137 | | // Symbol / Abbreviation => always strict |
| | 2652 | 138 | | if (style is DisplayStyle.Symbol or DisplayStyle.Abbreviation) |
| | 18 | 139 | | return TranslationFallbackPolicy.Strict; |
| | | 140 | | |
| | | 141 | | // Plural/Zero => strict (grammatical decision already made) |
| | 2634 | 142 | | if (!string.IsNullOrWhiteSpace(pluralSuffix)) |
| | 387 | 143 | | return TranslationFallbackPolicy.Strict; |
| | | 144 | | |
| | | 145 | | // Current / Short / Narrow => flexible |
| | 2247 | 146 | | return TranslationFallbackPolicy.Flexible; |
| | | 147 | | } |
| | | 148 | | |
| | | 149 | | /// <summary> |
| | | 150 | | /// Resolves display style suffixes ordered by preference. |
| | | 151 | | /// </summary> |
| | 3 | 152 | | private static IReadOnlyCollection<string> ResolveStyleSuffixes(DisplayStyle style) => [.. StyleSuffixes[style].Sele |
| | | 153 | | |
| | | 154 | | /// <summary> |
| | | 155 | | /// Resolves plural suffixes for the given count and culture, ordered by preference. The method first determines the |
| | | 156 | | /// </summary> |
| | | 157 | | /// <param name="count">The count to determine the plural category.</param> |
| | | 158 | | /// <param name="culture">The culture to use for pluralization rules.</param> |
| | | 159 | | /// <returns>An enumerable of plural suffixes ordered by preference.</returns> |
| | 312 | 160 | | private IReadOnlyCollection<string> ResolvePluralSuffixes(decimal count, CultureInfo culture) => pluralizationServic |
| | 312 | 161 | | { |
| | 3 | 162 | | PluralCategory.Zero => [ZeroSuffix], |
| | 114 | 163 | | PluralCategory.Singular => [DefaultSuffix], |
| | 195 | 164 | | _ => [PluralSuffix] |
| | 312 | 165 | | }; |
| | | 166 | | } |
| | | 167 | | |