88namespace TinyLocalization . Services ;
99
1010/// <summary>
11- /// Manager that modifies the translations database and invalidates associated FusionCache keys.
11+ /// Provides methods for managing localization resources, including adding, updating, removing, and invalidating
12+ /// translations across different cultures. Ensures consistency between the underlying database and the cache for
13+ /// localized content.
1214/// </summary>
13- /// <param name="db">The <see cref="LocalizationDbContext"/> used to access translations in the database.</param>
14- /// <param name="fusionCache">The <see cref="IFusionCache"/> used for local caching of translations.</param>
15- /// <param name="options">Options controlling localization behavior, including cache key prefix.</param>
16- /// <param name="publisher">Optional publisher used to broadcast cache invalidations to other instances.</param>
17- public class LocalizationManager ( LocalizationDbContext db , IFusionCache fusionCache , DbLocalizationOptions options ,
15+ /// <remarks>This class is designed for scenarios where localization data may be updated at runtime and must be
16+ /// kept consistent across multiple application instances. It provides asynchronous operations to ensure that changes to
17+ /// translations are reflected in both the database and the cache, and supports distributed cache invalidation when a
18+ /// publisher is supplied.</remarks>
19+ /// <param name="dbContext">The database context used to access and persist localization data.</param>
20+ /// <param name="fusionCache">The cache instance used to store and retrieve localization resources for improved performance.</param>
21+ /// <param name="options">The options that configure the behavior of the localization manager, such as cache key prefix settings.</param>
22+ /// <param name="publisher">An optional publisher used to broadcast cache invalidation messages to other application instances. If not provided,
23+ /// cache invalidation is performed only locally.</param>
24+ public class LocalizationManager ( LocalizationDbContext dbContext , IFusionCache fusionCache , DbLocalizationOptions options ,
1825 ICacheInvalidationPublisher ? publisher = null ) : ILocalizationManager
1926{
2027 /// <summary>
21- /// Builds the cache key used to store a translation in the cache .
28+ /// Generates a unique cache key that combines the specified resource identifier, key, and culture information .
2229 /// </summary>
23- /// <param name="resource">The resource name the translation belongs to.</param>
24- /// <param name="key">The translation key.</param>
25- /// <param name="culture">
26- /// The culture identifier for the translation. If <see cref="string.IsNullOrEmpty(string)"/> is true,
27- /// an underscore ("_") segment is used to represent the empty culture in the key.
28- /// </param>
29- /// <returns>A string representing the full cache key for the given resource/key/culture combination.</returns>
30+ /// <remarks>Use this method to ensure that cache keys are consistently formatted and unique across
31+ /// different resources and cultures. This is particularly useful in localization scenarios where cache entries must
32+ /// be separated by culture.</remarks>
33+ /// <param name="resource">The resource identifier used to distinguish the cache entry. Cannot be null.</param>
34+ /// <param name="key">The unique key associated with the resource for cache retrieval. Cannot be null.</param>
35+ /// <param name="culture">The culture name used to differentiate cache entries for localization. If null or empty, a default segment is
36+ /// used.</param>
37+ /// <returns>A formatted string representing the complete cache key, including the cache key prefix, resource, key, and
38+ /// culture segment.</returns>
3039 private string BuildCacheKey ( string resource , string key , string culture )
3140 {
3241 var cultureSegment = string . IsNullOrEmpty ( culture ) ? "_" : culture ;
3342 return $ "{ options . CacheKeyPrefix } :{ resource } :{ key } :{ cultureSegment } ";
3443 }
3544
3645 /// <summary>
37- /// Adds a new translation or updates an existing one in the database, then invalidates the corresponding cache entry.
46+ /// Adds a new translation or updates an existing translation asynchronously in the database for the specified
47+ /// resource, key, and culture.
3848 /// </summary>
39- /// <param name=" translation">The <see cref="Translation"/> entity to add or update.</param>
40- /// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
41- /// <returns>A task that represents the asynchronous operation.</returns>
42- /// < remarks>
43- /// If a translation with the same resource, key and culture already exists it will be updated; otherwise a new entry
44- /// will be added. After persisting changes it removes the matching FusionCache key locally and, when a publisher is
45- /// provided, publishes a single-item invalidation to other instances.
46- /// </remarks >
49+ /// <remarks>If a translation with the same resource, key, and culture already exists, its value is
50+ /// updated; otherwise, a new translation is added. After the operation, the local cache for the affected
51+ /// translation is invalidated, and an invalidation message is published to other instances if a publisher is
52+ /// available.</ remarks>
53+ /// <param name="translation">The translation to add or update. The resource, key, and culture identify the translation entry; the value is
54+ /// stored or updated.</param>
55+ /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
56+ /// <returns>A task that represents the asynchronous add or update operation.</returns >
4757 public async Task AddOrUpdateAsync ( Translation translation , CancellationToken cancellationToken = default )
4858 {
49- var existing = await db . Translations . FirstOrDefaultAsync ( t
59+ var existing = await dbContext . Translations . FirstOrDefaultAsync ( t
5060 => t . Resource == translation . Resource && t . Key == translation . Key && t . Culture == translation . Culture , cancellationToken ) ;
5161
5262 if ( existing == null )
5363 {
54- db . Translations . Add ( translation ) ;
64+ dbContext . Translations . Add ( translation ) ;
5565 }
5666 else
5767 {
5868 existing . Value = translation . Value ;
59- db . Translations . Update ( existing ) ;
69+ dbContext . Translations . Update ( existing ) ;
6070 }
6171
62- await db . SaveChangesAsync ( cancellationToken ) ;
72+ await dbContext . SaveChangesAsync ( cancellationToken ) ;
6373
6474 // Invalidate cache for that exact resource/key/culture locally
6575 var cacheKey = BuildCacheKey ( translation . Resource , translation . Key , translation . Culture ) ;
@@ -73,32 +83,29 @@ public async Task AddOrUpdateAsync(Translation translation, CancellationToken ca
7383 }
7484
7585 /// <summary>
76- /// Removes a translation for the specified resource/ key/ culture from the database and invalidates the cache .
86+ /// Removes a translation entry for the specified resource, key, and culture from the database asynchronously .
7787 /// </summary>
78- /// <param name="resource">The resource name the translation belongs to.</param>
79- /// <param name="key">The translation key to remove.</param>
80- /// <param name="culture">The culture identifier of the translation to remove.</param>
81- /// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
82- /// <returns>
83- /// A task that returns <c>true</c> if the translation was found and removed; otherwise <c>false</c> when no matching
84- /// translation exists.
85- /// </returns>
86- /// <remarks>
87- /// After removing the translation and saving changes the method removes the corresponding FusionCache entry locally
88- /// and, if a publisher is configured, publishes a single-item invalidation to other instances.
89- /// </remarks>
88+ /// <remarks>This method also invalidates the local cache for the removed translation and publishes an
89+ /// invalidation notification if a publisher is available. If no matching translation entry is found, no changes are
90+ /// made.</remarks>
91+ /// <param name="resource">The name of the resource associated with the translation entry to remove. Cannot be null.</param>
92+ /// <param name="key">The key that identifies the specific translation entry to remove. Cannot be null.</param>
93+ /// <param name="culture">The culture code that specifies which translation to remove. Cannot be null.</param>
94+ /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
95+ /// <returns>A task that represents the asynchronous operation. The task result is <see langword="true"/> if the translation
96+ /// entry was found and removed; otherwise, <see langword="false"/>.</returns>
9097 public async Task < bool > RemoveAsync ( string resource , string key , string culture , CancellationToken cancellationToken = default )
9198 {
92- var existing = await db . Translations . FirstOrDefaultAsync ( t
99+ var existing = await dbContext . Translations . FirstOrDefaultAsync ( t
93100 => t . Resource == resource && t . Key == key && t . Culture == culture , cancellationToken ) ;
94101
95102 if ( existing == null )
96103 {
97104 return false ;
98105 }
99106
100- db . Translations . Remove ( existing ) ;
101- await db . SaveChangesAsync ( cancellationToken ) ;
107+ dbContext . Translations . Remove ( existing ) ;
108+ await dbContext . SaveChangesAsync ( cancellationToken ) ;
102109
103110 // invalidate cache locally
104111 var cacheKey = BuildCacheKey ( resource , key , culture ) ;
@@ -114,20 +121,20 @@ public async Task<bool> RemoveAsync(string resource, string key, string culture,
114121 }
115122
116123 /// <summary>
117- /// Invalidates all cached entries for a given resource across known cultures and keys .
124+ /// Asynchronously invalidates all cached entries for the specified resource across all known cultures.
118125 /// </summary>
119- /// <param name="resource">The resource name whose cache entries should be invalidated.</param>
120- /// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
121- /// <returns>A task that represents the asynchronous operation.</returns>
122- /// < remarks>
123- /// The method enumerates the distinct cultures and keys for the resource in the database, removes the corresponding
124- /// FusionCache entries locally, and collects <see cref="InvalidationKey"/> instances to publish a resource-level
125- /// invalidation to other instances when a publisher is configured.
126- /// </remarks >
126+ /// <remarks>This method removes all cache entries associated with the specified resource for each culture
127+ /// present in the data store. If a publisher is available, a resource-level invalidation notification is published
128+ /// after the cache entries are removed. Use this method to ensure that changes to a resource are reflected across
129+ /// all cultures and that outdated translations are not served from the cache.</ remarks>
130+ /// <param name="resource"> The name of the resource to invalidate. This parameter cannot be null or empty.</param>
131+ /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation. The default value is <see
132+ /// cref="CancellationToken.None"/>.</param>
133+ /// <returns>A task that represents the asynchronous operation of invalidating the resource.</returns >
127134 public async Task InvalidateResourceAsync ( string resource , CancellationToken cancellationToken = default )
128135 {
129136 // enumerate known cultures for this resource and remove corresponding keys
130- var cultures = await db . Translations
137+ var cultures = await dbContext . Translations
131138 . Where ( t => t . Resource == resource )
132139 . Select ( t => t . Culture )
133140 . Distinct ( )
@@ -138,7 +145,7 @@ public async Task InvalidateResourceAsync(string resource, CancellationToken can
138145 foreach ( var culture in cultures )
139146 {
140147 // for each key too
141- var keys = await db . Translations
148+ var keys = await dbContext . Translations
142149 . Where ( t => t . Resource == resource && t . Culture == culture )
143150 . Select ( t => t . Key )
144151 . Distinct ( )
0 commit comments