Ecng.Localization
1.0.225
See the version list below for details.
dotnet add package Ecng.Localization --version 1.0.225
NuGet\Install-Package Ecng.Localization -Version 1.0.225
<PackageReference Include="Ecng.Localization" Version="1.0.225" />
<PackageVersion Include="Ecng.Localization" Version="1.0.225" />
<PackageReference Include="Ecng.Localization" />
paket add Ecng.Localization --version 1.0.225
#r "nuget: Ecng.Localization, 1.0.225"
#:package Ecng.Localization@1.0.225
#addin nuget:?package=Ecng.Localization&version=1.0.225
#tool nuget:?package=Ecng.Localization&version=1.0.225
Ecng.Localization
A lightweight and flexible localization engine for .NET applications that provides centralized string resource management with support for multiple cultures and easy resource overrides.
Table of Contents
- Overview
- Key Features
- Installation
- Quick Start
- Core Concepts
- Usage Guide
- Advanced Scenarios
- Integration with .NET Attributes
- Best Practices
- API Reference
Overview
Ecng.Localization provides a simple yet powerful localization infrastructure that allows you to:
- Centralize all localizable strings in your application
- Support multiple languages and cultures
- Implement custom translation providers
- Integrate seamlessly with .NET data annotations
- Maintain type-safe resource keys
The library uses a provider pattern through the ILocalizer interface, making it easy to plug in custom localization strategies ranging from simple in-memory dictionaries to database-backed translation systems.
Key Features
- Pluggable Architecture: Implement custom localizers via
ILocalizerinterface - Two Localization Modes: Translate by English text or by resource key
- Resource Type Support: Works with
DisplayAttributefor declarative localization - Extension Methods: Convenient string extension methods for inline localization
- Thread-Safe: Can be safely used in multi-threaded applications
- No External Dependencies: Minimal footprint with no third-party dependencies
- Fallback Behavior: Returns original text when translation is not found
- Type-Safe Keys: Const keys prevent typos in resource references
Installation
Add a reference to the Ecng.Localization project or NuGet package in your application:
<ItemGroup>
<ProjectReference Include="path\to\Ecng.Localization.csproj" />
</ItemGroup>
Quick Start
Basic Usage
using Ecng.Localization;
// Use the default localizer (returns input as-is)
string text = "Hello".Localize();
Console.WriteLine(text); // Output: Hello
// Use predefined resource keys
string name = LocalizedStrings.Name;
Console.WriteLine(name); // Output: Name (in English by default)
Implementing Custom Localization
using Ecng.Localization;
// 1. Create a custom localizer
public class RussianLocalizer : ILocalizer
{
private readonly Dictionary<string, string> _translations = new()
{
["Name"] = "Имя",
["Warnings"] = "Предупреждения",
["Errors"] = "Ошибки",
["Info"] = "Информация"
};
public string Localize(string enStr)
{
// Translate by English text
return _translations.GetValueOrDefault(enStr, enStr);
}
public string LocalizeByKey(string key)
{
// Translate by resource key
return _translations.GetValueOrDefault(key, key);
}
}
// 2. Register the localizer globally
LocalizedStrings.Localizer = new RussianLocalizer();
// 3. Use localized strings throughout your application
Console.WriteLine(LocalizedStrings.Name); // Output: Имя
Console.WriteLine(LocalizedStrings.Warnings); // Output: Предупреждения
Console.WriteLine("Name".Localize()); // Output: Имя
Core Concepts
ILocalizer Interface
The ILocalizer interface is the foundation of the localization system:
public interface ILocalizer
{
/// <summary>
/// Localizes a string by its English text.
/// </summary>
string Localize(string enStr);
/// <summary>
/// Localizes a string by its resource key.
/// </summary>
string LocalizeByKey(string key);
}
Two Translation Approaches:
- Localize(enStr): Uses English text as the key. Good for dynamic strings.
- LocalizeByKey(key): Uses a constant key. Better for compile-time safety.
LocalizedStrings Class
The static LocalizedStrings class provides:
- Global localizer instance via
Localizerproperty - Predefined resource keys (e.g.,
NameKey,IdKey,WarningsKey) - Predefined localized string properties (e.g.,
Name,Id,Warnings) - Extension methods for string localization
Default Behavior
By default, the library uses a null localizer that returns strings as-is:
// Default behavior (no translation)
"Hello".Localize(); // Returns: "Hello"
"MyKey".LocalizeByKey(); // Returns: "MyKey"
LocalizedStrings.Name; // Returns: "Name"
Usage Guide
1. Using Predefined Resource Keys
The library provides predefined keys for common UI elements:
using Ecng.Localization;
// Logging-related resources
Console.WriteLine(LocalizedStrings.Inherited); // "Inherited"
Console.WriteLine(LocalizedStrings.Verbose); // "Verbose"
Console.WriteLine(LocalizedStrings.Debug); // "Debug"
Console.WriteLine(LocalizedStrings.Info); // "Info"
Console.WriteLine(LocalizedStrings.Warnings); // "Warnings"
Console.WriteLine(LocalizedStrings.Errors); // "Errors"
Console.WriteLine(LocalizedStrings.Off); // "Off"
// General purpose resources
Console.WriteLine(LocalizedStrings.Id); // "Id"
Console.WriteLine(LocalizedStrings.Name); // "Name"
Console.WriteLine(LocalizedStrings.Logging); // "Logging"
Console.WriteLine(LocalizedStrings.LogLevel); // "LogLevel"
// Chart-related resources
Console.WriteLine(LocalizedStrings.Line2); // "Line2"
Console.WriteLine(LocalizedStrings.Area); // "Area"
Console.WriteLine(LocalizedStrings.Histogram); // "Histogram"
Console.WriteLine(LocalizedStrings.Band); // "Band"
2. Using String Extension Methods
Localize any string inline using extension methods:
using Ecng.Localization;
// Localize by English text
string message = "Connection established".Localize();
// Localize by key
string errorMsg = "ERROR_CONNECTION".LocalizeByKey();
// Use in string interpolation
Console.WriteLine($"Status: {"Active".Localize()}");
3. Implementing a Database-Backed Localizer
using Ecng.Localization;
public class DatabaseLocalizer : ILocalizer
{
private readonly ITranslationRepository _repository;
private readonly string _cultureCode;
private readonly Dictionary<string, string> _cache;
public DatabaseLocalizer(ITranslationRepository repository, string cultureCode)
{
_repository = repository;
_cultureCode = cultureCode;
_cache = new Dictionary<string, string>();
LoadTranslations();
}
private void LoadTranslations()
{
// Load all translations for the current culture into cache
var translations = _repository.GetTranslations(_cultureCode);
foreach (var translation in translations)
{
_cache[translation.Key] = translation.Value;
}
}
public string Localize(string enStr)
{
if (_cache.TryGetValue(enStr, out var translation))
return translation;
// Log missing translation for later addition
_repository.LogMissingTranslation(enStr, _cultureCode);
return enStr;
}
public string LocalizeByKey(string key)
{
return _cache.GetValueOrDefault(key, key);
}
}
// Usage
var repository = new SqlTranslationRepository(connectionString);
LocalizedStrings.Localizer = new DatabaseLocalizer(repository, "ru-RU");
4. Implementing a Resource File Localizer
using Ecng.Localization;
using System.Resources;
using System.Globalization;
public class ResourceFileLocalizer : ILocalizer
{
private readonly ResourceManager _resourceManager;
private readonly CultureInfo _culture;
public ResourceFileLocalizer(Type resourceType, CultureInfo culture)
{
_resourceManager = new ResourceManager(resourceType);
_culture = culture;
}
public string Localize(string enStr)
{
try
{
// Try to get the resource using the English string as the key
var result = _resourceManager.GetString(enStr, _culture);
return result ?? enStr;
}
catch
{
return enStr;
}
}
public string LocalizeByKey(string key)
{
try
{
var result = _resourceManager.GetString(key, _culture);
return result ?? key;
}
catch
{
return key;
}
}
}
// Usage
LocalizedStrings.Localizer = new ResourceFileLocalizer(
typeof(MyResources),
new CultureInfo("fr-FR")
);
5. Culture-Aware Localizer with Fallback
using Ecng.Localization;
using System.Globalization;
public class MultiCultureLocalizer : ILocalizer
{
private readonly Dictionary<string, Dictionary<string, string>> _translations;
private CultureInfo _currentCulture;
public MultiCultureLocalizer()
{
_translations = new Dictionary<string, Dictionary<string, string>>
{
["en-US"] = new Dictionary<string, string>(),
["ru-RU"] = new Dictionary<string, string>
{
["Name"] = "Имя",
["Errors"] = "Ошибки",
["Warnings"] = "Предупреждения"
},
["de-DE"] = new Dictionary<string, string>
{
["Name"] = "Name",
["Errors"] = "Fehler",
["Warnings"] = "Warnungen"
}
};
_currentCulture = CultureInfo.CurrentUICulture;
}
public CultureInfo Culture
{
get => _currentCulture;
set => _currentCulture = value ?? CultureInfo.InvariantCulture;
}
public string Localize(string enStr)
{
return LocalizeByKey(enStr);
}
public string LocalizeByKey(string key)
{
// Try exact culture match
if (_translations.TryGetValue(_currentCulture.Name, out var cultureDict))
{
if (cultureDict.TryGetValue(key, out var translation))
return translation;
}
// Try neutral culture (e.g., "ru" from "ru-RU")
if (!_currentCulture.IsNeutralCulture)
{
var neutralCulture = _currentCulture.Parent.Name;
if (_translations.TryGetValue(neutralCulture, out var neutralDict))
{
if (neutralDict.TryGetValue(key, out var translation))
return translation;
}
}
// Fallback to English or return key
if (_translations.TryGetValue("en-US", out var enDict))
{
if (enDict.TryGetValue(key, out var enTranslation))
return enTranslation;
}
return key;
}
public void AddTranslation(string culture, string key, string value)
{
if (!_translations.ContainsKey(culture))
_translations[culture] = new Dictionary<string, string>();
_translations[culture][key] = value;
}
}
// Usage
var localizer = new MultiCultureLocalizer();
localizer.Culture = new CultureInfo("ru-RU");
LocalizedStrings.Localizer = localizer;
Console.WriteLine(LocalizedStrings.Name); // Output: Имя
Console.WriteLine(LocalizedStrings.Warnings); // Output: Предупреждения
Advanced Scenarios
Thread-Safe Localizer with Caching
using Ecng.Localization;
using System.Collections.Concurrent;
public class CachedLocalizer : ILocalizer
{
private readonly ILocalizer _innerLocalizer;
private readonly ConcurrentDictionary<string, string> _cache;
private readonly int _maxCacheSize;
public CachedLocalizer(ILocalizer innerLocalizer, int maxCacheSize = 1000)
{
_innerLocalizer = innerLocalizer;
_cache = new ConcurrentDictionary<string, string>();
_maxCacheSize = maxCacheSize;
}
public string Localize(string enStr)
{
return _cache.GetOrAdd(enStr, key =>
{
if (_cache.Count >= _maxCacheSize)
_cache.Clear();
return _innerLocalizer.Localize(key);
});
}
public string LocalizeByKey(string key)
{
return _cache.GetOrAdd($"KEY_{key}", _ =>
{
if (_cache.Count >= _maxCacheSize)
_cache.Clear();
return _innerLocalizer.LocalizeByKey(key);
});
}
public void ClearCache() => _cache.Clear();
}
Composite Localizer with Multiple Sources
using Ecng.Localization;
public class CompositeLocalizer : ILocalizer
{
private readonly List<ILocalizer> _localizers;
public CompositeLocalizer(params ILocalizer[] localizers)
{
_localizers = new List<ILocalizer>(localizers);
}
public string Localize(string enStr)
{
foreach (var localizer in _localizers)
{
var result = localizer.Localize(enStr);
if (result != enStr)
return result;
}
return enStr;
}
public string LocalizeByKey(string key)
{
foreach (var localizer in _localizers)
{
var result = localizer.LocalizeByKey(key);
if (result != key)
return result;
}
return key;
}
}
// Usage: Try database first, then fall back to resource files
var dbLocalizer = new DatabaseLocalizer(repository, "ru-RU");
var fileLocalizer = new ResourceFileLocalizer(typeof(Resources), culture);
LocalizedStrings.Localizer = new CompositeLocalizer(dbLocalizer, fileLocalizer);
Logging Localizer (Debugging)
using Ecng.Localization;
public class LoggingLocalizer : ILocalizer
{
private readonly ILocalizer _innerLocalizer;
private readonly Action<string> _logger;
public LoggingLocalizer(ILocalizer innerLocalizer, Action<string> logger)
{
_innerLocalizer = innerLocalizer;
_logger = logger;
}
public string Localize(string enStr)
{
var result = _innerLocalizer.Localize(enStr);
_logger($"Localize('{enStr}') -> '{result}'");
return result;
}
public string LocalizeByKey(string key)
{
var result = _innerLocalizer.LocalizeByKey(key);
_logger($"LocalizeByKey('{key}') -> '{result}'");
return result;
}
}
// Usage
var baseLocalizer = new RussianLocalizer();
LocalizedStrings.Localizer = new LoggingLocalizer(
baseLocalizer,
msg => Console.WriteLine($"[LOCALIZATION] {msg}")
);
Integration with .NET Attributes
The localization system integrates seamlessly with .NET's DisplayAttribute for declarative localization in data models:
Using with Enum Types
using System.ComponentModel.DataAnnotations;
using Ecng.Localization;
public enum LogLevels
{
[Display(ResourceType = typeof(LocalizedStrings), Name = nameof(LocalizedStrings.InheritedKey))]
Inherit,
[Display(ResourceType = typeof(LocalizedStrings), Name = nameof(LocalizedStrings.VerboseKey))]
Verbose,
[Display(ResourceType = typeof(LocalizedStrings), Name = nameof(LocalizedStrings.DebugKey))]
Debug,
[Display(ResourceType = typeof(LocalizedStrings), Name = nameof(LocalizedStrings.InfoKey))]
Info,
[Display(ResourceType = typeof(LocalizedStrings), Name = nameof(LocalizedStrings.WarningsKey))]
Warning,
[Display(ResourceType = typeof(LocalizedStrings), Name = nameof(LocalizedStrings.ErrorsKey))]
Error
}
// Usage with Ecng.ComponentModel extensions
using Ecng.ComponentModel;
var level = LogLevels.Warning;
string displayName = level.GetFieldDisplayName(); // Gets localized "Warnings"
Using with Class Properties
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Ecng.Localization;
public class LogSource
{
[Display(
ResourceType = typeof(LocalizedStrings),
Name = nameof(LocalizedStrings.IdKey),
Description = nameof(LocalizedStrings.IdKey),
GroupName = nameof(LocalizedStrings.LoggingKey),
Order = 1)]
public Guid Id { get; set; }
[Display(
ResourceType = typeof(LocalizedStrings),
Name = nameof(LocalizedStrings.NameKey),
Description = nameof(LocalizedStrings.LogSourceNameKey),
GroupName = nameof(LocalizedStrings.LoggingKey),
Order = 2)]
public string Name { get; set; }
[Display(
ResourceType = typeof(LocalizedStrings),
Name = nameof(LocalizedStrings.LogLevelKey),
Description = nameof(LocalizedStrings.LogLevelDescKey),
GroupName = nameof(LocalizedStrings.LoggingKey),
Order = 3)]
public LogLevels LogLevel { get; set; }
}
Custom Resource Keys
Extend LocalizedStrings with your own resource keys:
using Ecng.Localization;
public static class MyLocalizedStrings
{
// Define keys
public const string UserNameKey = nameof(UserName);
public const string PasswordKey = nameof(Password);
public const string LoginButtonKey = nameof(LoginButton);
// Define properties that use the localizer
public static string UserName => UserNameKey.LocalizeByKey();
public static string Password => PasswordKey.LocalizeByKey();
public static string LoginButton => LoginButtonKey.LocalizeByKey();
}
// Usage in attributes
public class LoginModel
{
[Display(
ResourceType = typeof(MyLocalizedStrings),
Name = nameof(MyLocalizedStrings.UserNameKey))]
public string UserName { get; set; }
[Display(
ResourceType = typeof(MyLocalizedStrings),
Name = nameof(MyLocalizedStrings.PasswordKey))]
public string Password { get; set; }
}
Best Practices
1. Initialize Localizer Early
Set up your localizer during application startup:
public class Program
{
public static void Main(string[] args)
{
// Initialize localization before anything else
InitializeLocalization();
var app = CreateApplication();
app.Run();
}
private static void InitializeLocalization()
{
var culture = GetUserPreferredCulture();
LocalizedStrings.Localizer = new MyLocalizer(culture);
}
}
2. Use Resource Keys for Stability
Prefer LocalizeByKey() over Localize() for better refactoring support:
// Good: Key-based (won't break if English text changes)
public const string ErrorKey = "ERROR_INVALID_INPUT";
string message = ErrorKey.LocalizeByKey();
// Less ideal: Text-based (fragile to typos and changes)
string message = "Invalid input".Localize();
3. Provide Fallback Behavior
Always return meaningful fallback text:
public string Localize(string enStr)
{
// Try translation
if (_translations.TryGetValue(enStr, out var result))
return result;
// Fallback to original (better than throwing exception)
return enStr;
}
4. Don't Set Localizer to Null
The library explicitly prevents null localizers:
// This will throw ArgumentNullException
LocalizedStrings.Localizer = null; // ERROR!
// Instead, use a pass-through localizer if needed
LocalizedStrings.Localizer = new PassThroughLocalizer();
5. Cache Translations When Possible
Avoid repeated lookups for frequently used strings:
// Cache in a field or property
private readonly string _errorMessage = LocalizedStrings.Errors;
public void LogError()
{
// Reuse cached translation
Console.WriteLine(_errorMessage);
}
6. Test Missing Translations
Implement tests to catch missing translations:
[Test]
public void AllKeysHaveTranslations()
{
var localizer = new RussianLocalizer();
LocalizedStrings.Localizer = localizer;
// Test all predefined keys
Assert.AreNotEqual("Name", LocalizedStrings.Name);
Assert.AreNotEqual("Errors", LocalizedStrings.Errors);
Assert.AreNotEqual("Warnings", LocalizedStrings.Warnings);
}
API Reference
ILocalizer Interface
public interface ILocalizer
{
/// <summary>
/// Localizes a string using the English text as the lookup key.
/// </summary>
/// <param name="enStr">The English string to localize.</param>
/// <returns>The localized string, or the original if not found.</returns>
string Localize(string enStr);
/// <summary>
/// Localizes a string using a resource key for lookup.
/// </summary>
/// <param name="key">The resource key.</param>
/// <returns>The localized string, or the key if not found.</returns>
string LocalizeByKey(string key);
}
LocalizedStrings Class
Properties
public static class LocalizedStrings
{
/// <summary>
/// Gets or sets the global localizer instance.
/// Cannot be set to null.
/// </summary>
public static ILocalizer Localizer { get; set; }
// Predefined localized string properties
public static string Inherited { get; }
public static string Verbose { get; }
public static string Debug { get; }
public static string Info { get; }
public static string Warnings { get; }
public static string Errors { get; }
public static string Off { get; }
public static string Id { get; }
public static string Logging { get; }
public static string Name { get; }
public static string LogSourceName { get; }
public static string LogLevel { get; }
public static string LogLevelDesc { get; }
// ... and more
}
Resource Keys
public static class LocalizedStrings
{
// Resource key constants for use with DisplayAttribute
public const string InheritedKey = "Inherited";
public const string VerboseKey = "Verbose";
public const string DebugKey = "Debug";
public const string InfoKey = "Info";
public const string WarningsKey = "Warnings";
public const string ErrorsKey = "Errors";
public const string OffKey = "Off";
public const string IdKey = "Id";
public const string LoggingKey = "Logging";
public const string NameKey = "Name";
// ... and more
}
Extension Methods
public static class LocalizedStrings
{
/// <summary>
/// Localizes a string using English text as the key.
/// </summary>
public static string Localize(this string enStr);
/// <summary>
/// Localizes a string using a resource key.
/// </summary>
public static string LocalizeByKey(this string key);
}
Usage Examples
// Get localized string via property
string name = LocalizedStrings.Name;
// Get localized string via extension method
string custom = "My Text".Localize();
// Get localized string via key
string byKey = "CustomKey".LocalizeByKey();
// Use in DisplayAttribute
[Display(ResourceType = typeof(LocalizedStrings), Name = LocalizedStrings.NameKey)]
public string UserName { get; set; }
License
This project is part of the Ecng framework. Please refer to the main repository for licensing information.
Contributing
Contributions are welcome! When adding new localization features:
- Ensure backward compatibility
- Add resource keys to
LocalizedStringsclass - Provide both key constants and localized properties
- Update documentation with examples
- Add unit tests for new functionality
Support
For issues, questions, or contributions, please refer to the main Ecng repository.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Ecng.Common (>= 1.0.231)
-
net10.0
- Ecng.Common (>= 1.0.231)
-
net6.0
- Ecng.Common (>= 1.0.231)
NuGet packages (6)
Showing the top 5 NuGet packages that depend on Ecng.Localization:
| Package | Downloads |
|---|---|
|
Ecng.ComponentModel
Ecng system framework |
|
|
Ecng.Net.SocketIO
Ecng system framework |
|
|
Ecng.Drawing
Ecng system framework |
|
|
Ecng.Compilation.Roslyn
Ecng system framework |
|
|
Ecng.Logging
Ecng system framework |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.226 | 0 | 12/25/2025 |
| 1.0.225 | 454 | 12/22/2025 |
| 1.0.224 | 479 | 12/21/2025 |
| 1.0.223 | 588 | 12/19/2025 |
| 1.0.222 | 571 | 12/19/2025 |
| 1.0.221 | 907 | 12/17/2025 |
| 1.0.220 | 1,357 | 12/15/2025 |
| 1.0.219 | 2,170 | 12/12/2025 |
| 1.0.218 | 2,519 | 12/12/2025 |
| 1.0.217 | 2,568 | 11/29/2025 |
| 1.0.216 | 620 | 11/28/2025 |
| 1.0.215 | 596 | 11/28/2025 |
| 1.0.214 | 662 | 11/27/2025 |
| 1.0.213 | 770 | 11/24/2025 |
| 1.0.212 | 689 | 11/24/2025 |
| 1.0.211 | 682 | 11/23/2025 |
| 1.0.210 | 1,145 | 11/22/2025 |
| 1.0.209 | 1,626 | 11/20/2025 |
| 1.0.208 | 930 | 11/18/2025 |
| 1.0.207 | 856 | 11/18/2025 |
| 1.0.206 | 872 | 11/13/2025 |
| 1.0.205 | 772 | 11/10/2025 |
| 1.0.204 | 1,672 | 11/1/2025 |
| 1.0.203 | 922 | 10/28/2025 |
| 1.0.202 | 870 | 10/27/2025 |
| 1.0.201 | 731 | 10/27/2025 |
| 1.0.200 | 622 | 10/25/2025 |
| 1.0.199 | 4,278 | 10/3/2025 |
| 1.0.198 | 2,655 | 9/25/2025 |
| 1.0.197 | 4,831 | 9/5/2025 |
| 1.0.196 | 6,074 | 8/30/2025 |
| 1.0.195 | 1,691 | 8/19/2025 |
| 1.0.194 | 7,488 | 7/13/2025 |
| 1.0.193 | 616 | 7/13/2025 |
| 1.0.192 | 617 | 7/12/2025 |
| 1.0.191 | 1,991 | 7/8/2025 |
| 1.0.190 | 6,921 | 6/16/2025 |
| 1.0.189 | 800 | 6/9/2025 |
| 1.0.188 | 719 | 6/8/2025 |
| 1.0.187 | 2,277 | 5/21/2025 |
| 1.0.186 | 831 | 5/17/2025 |
| 1.0.185 | 2,377 | 5/12/2025 |
| 1.0.184 | 735 | 5/12/2025 |
| 1.0.183 | 2,845 | 4/17/2025 |
| 1.0.182 | 6,461 | 3/20/2025 |
| 1.0.181 | 667 | 3/19/2025 |
| 1.0.180 | 5,388 | 2/26/2025 |
| 1.0.179 | 720 | 2/26/2025 |
| 1.0.178 | 9,153 | 2/5/2025 |
| 1.0.177 | 1,189 | 2/5/2025 |
| 1.0.176 | 2,690 | 4/14/2024 |
| 1.0.175 | 5,678 | 3/28/2024 |
| 1.0.174 | 365 | 3/17/2024 |
| 1.0.173 | 2,487 | 2/23/2024 |
| 1.0.172 | 300 | 2/23/2024 |
| 1.0.171 | 3,572 | 2/18/2024 |
| 1.0.170 | 307 | 2/16/2024 |
| 1.0.169 | 2,261 | 2/13/2024 |
| 1.0.168 | 2,139 | 2/8/2024 |
| 1.0.167 | 2,534 | 2/4/2024 |
| 1.0.166 | 2,583 | 1/23/2024 |
| 1.0.165 | 387 | 1/12/2024 |
| 1.0.164 | 4,849 | 1/2/2024 |
| 1.0.163 | 330 | 12/29/2023 |
| 1.0.162 | 13,135 | 11/12/2023 |
| 1.0.161 | 336 | 11/10/2023 |
| 1.0.160 | 335 | 11/10/2023 |
| 1.0.159 | 340 | 11/9/2023 |
| 1.0.158 | 361 | 11/3/2023 |
| 1.0.157 | 347 | 11/1/2023 |
| 1.0.156 | 339 | 11/1/2023 |
| 1.0.155 | 24,702 | 9/8/2023 |
| 1.0.154 | 382 | 9/8/2023 |
| 1.0.153 | 405 | 9/3/2023 |
| 1.0.152 | 460 | 8/21/2023 |
| 1.0.151 | 445 | 8/14/2023 |
| 1.0.150 | 404 | 8/10/2023 |
| 1.0.149 | 39,333 | 6/29/2023 |
| 1.0.148 | 13,797 | 5/27/2023 |
| 1.0.147 | 459 | 5/19/2023 |
| 1.0.146 | 26,146 | 5/8/2023 |
| 1.0.145 | 537 | 4/21/2023 |
| 1.0.144 | 50,457 | 4/3/2023 |
| 1.0.143 | 663 | 3/13/2023 |
| 1.0.142 | 18,301 | 3/6/2023 |
| 1.0.141 | 568 | 2/26/2023 |
| 1.0.140 | 46,703 | 2/9/2023 |
| 1.0.139 | 15,883 | 2/7/2023 |
| 1.0.138 | 594 | 2/4/2023 |
| 1.0.137 | 18,889 | 2/2/2023 |
| 1.0.136 | 17,389 | 1/30/2023 |
| 1.0.135 | 582 | 1/18/2023 |
| 1.0.134 | 42,165 | 12/30/2022 |
| 1.0.133 | 617 | 12/23/2022 |
| 1.0.132 | 20,248 | 12/12/2022 |
| 1.0.131 | 20,365 | 12/4/2022 |
| 1.0.130 | 620 | 12/4/2022 |
| 1.0.129 | 644 | 11/30/2022 |
| 1.0.128 | 626 | 11/28/2022 |
| 1.0.127 | 678 | 11/18/2022 |
| 1.0.126 | 28,364 | 11/11/2022 |
| 1.0.125 | 645 | 11/11/2022 |
| 1.0.124 | 606 | 11/10/2022 |
| 1.0.123 | 697 | 11/5/2022 |
| 1.0.122 | 678 | 11/4/2022 |
| 1.0.121 | 23,715 | 11/1/2022 |
| 1.0.120 | 24,820 | 10/16/2022 |
| 1.0.119 | 879 | 9/10/2022 |
| 1.0.118 | 49,874 | 9/8/2022 |
| 1.0.117 | 728 | 9/8/2022 |
| 1.0.116 | 642 | 9/8/2022 |
| 1.0.115 | 755 | 9/4/2022 |
| 1.0.114 | 88,246 | 8/24/2022 |
| 1.0.113 | 848 | 8/8/2022 |
| 1.0.112 | 864 | 7/26/2022 |
| 1.0.111 | 788 | 7/26/2022 |
| 1.0.110 | 51,936 | 7/19/2022 |
| 1.0.109 | 45,046 | 7/18/2022 |
| 1.0.108 | 823 | 7/8/2022 |
| 1.0.107 | 851 | 6/18/2022 |
| 1.0.106 | 829 | 6/6/2022 |
| 1.0.105 | 95,136 | 4/30/2022 |
| 1.0.104 | 814 | 4/20/2022 |
| 1.0.103 | 832 | 4/10/2022 |
| 1.0.102 | 818 | 4/7/2022 |
| 1.0.101 | 792 | 4/7/2022 |
| 1.0.100 | 817 | 4/2/2022 |
| 1.0.99 | 12,174 | 3/29/2022 |
| 1.0.98 | 804 | 3/27/2022 |
| 1.0.97 | 259,493 | 1/24/2022 |
| 1.0.96 | 157,251 | 12/29/2021 |
| 1.0.95 | 27,943 | 12/20/2021 |
| 1.0.94 | 681 | 12/13/2021 |
| 1.0.93 | 54,670 | 12/6/2021 |
| 1.0.92 | 616 | 12/2/2021 |
| 1.0.91 | 589 | 12/2/2021 |
| 1.0.90 | 33,428 | 11/29/2021 |
| 1.0.89 | 32,106 | 11/22/2021 |
| 1.0.88 | 4,092 | 11/17/2021 |
| 1.0.87 | 33,778 | 11/13/2021 |
| 1.0.86 | 7,578 | 11/10/2021 |
| 1.0.85 | 4,301 | 11/9/2021 |
| 1.0.84 | 66,813 | 11/5/2021 |
| 1.0.83 | 6,055 | 11/4/2021 |
| 1.0.82 | 4,129 | 11/4/2021 |
| 1.0.81 | 4,027 | 11/3/2021 |
| 1.0.80 | 4,369 | 10/30/2021 |
| 1.0.79 | 35,463 | 10/21/2021 |
| 1.0.78 | 4,838 | 10/17/2021 |
| 1.0.77 | 65,433 | 10/14/2021 |
| 1.0.76 | 15,495 | 10/13/2021 |
| 1.0.75 | 4,474 | 10/12/2021 |
| 1.0.74 | 36,036 | 10/11/2021 |
| 1.0.73 | 4,260 | 10/9/2021 |
| 1.0.72 | 39,119 | 10/7/2021 |
| 1.0.71 | 41,229 | 10/7/2021 |
| 1.0.70 | 4,322 | 10/7/2021 |
| 1.0.69 | 4,240 | 10/6/2021 |
| 1.0.68 | 2,591 | 9/28/2021 |
| 1.0.67 | 36,176 | 9/23/2021 |
| 1.0.66 | 4,197 | 9/10/2021 |
| 1.0.65 | 2,276 | 9/9/2021 |
| 1.0.64 | 2,281 | 9/8/2021 |
| 1.0.63 | 2,283 | 9/8/2021 |
| 1.0.62 | 32,947 | 9/6/2021 |
| 1.0.61 | 2,510 | 8/31/2021 |
| 1.0.60 | 2,460 | 8/30/2021 |
| 1.0.59 | 35,628 | 7/31/2021 |
| 1.0.58 | 61,442 | 7/30/2021 |
| 1.0.57 | 2,922 | 7/26/2021 |
| 1.0.56 | 90,799 | 7/5/2021 |
| 1.0.55 | 2,925 | 7/1/2021 |
| 1.0.54 | 64,680 | 6/4/2021 |
| 1.0.53 | 92,173 | 4/26/2021 |
| 1.0.52 | 33,433 | 4/19/2021 |
| 1.0.51 | 150,296 | 4/7/2021 |
| 1.0.50 | 32,584 | 4/3/2021 |
| 1.0.49 | 178,844 | 3/22/2021 |
| 1.0.48 | 113,318 | 3/4/2021 |
| 1.0.47 | 35,631 | 2/26/2021 |
| 1.0.46 | 167,888 | 2/2/2021 |
| 1.0.45 | 59,546 | 1/26/2021 |
| 1.0.44 | 58,499 | 1/24/2021 |
| 1.0.43 | 3,135 | 1/24/2021 |
| 1.0.42 | 3,195 | 1/23/2021 |
| 1.0.41 | 60,120 | 1/20/2021 |
| 1.0.40 | 3,202 | 1/20/2021 |
| 1.0.39 | 31,246 | 1/18/2021 |
| 1.0.38 | 3,141 | 1/18/2021 |
| 1.0.37 | 30,265 | 1/16/2021 |
| 1.0.36 | 119,846 | 12/16/2020 |
| 1.0.35 | 57,700 | 12/14/2020 |
| 1.0.34 | 35,540 | 12/9/2020 |
| 1.0.33 | 5,513 | 12/6/2020 |
| 1.0.32 | 3,671 | 12/2/2020 |
| 1.0.31 | 3,568 | 12/2/2020 |
| 1.0.30 | 31,176 | 12/1/2020 |
| 1.0.29 | 187,309 | 11/12/2020 |
| 1.0.29-atestpub | 1,655 | 11/11/2020 |
| 1.0.28 | 32,486 | 10/11/2020 |
| 1.0.27 | 112,568 | 9/9/2020 |
| 1.0.26 | 30,949 | 9/3/2020 |
| 1.0.25 | 31,281 | 8/20/2020 |
| 1.0.24 | 85,567 | 8/9/2020 |
| 1.0.23 | 31,735 | 7/28/2020 |
| 1.0.22 | 30,703 | 7/19/2020 |
| 1.0.21 | 57,322 | 7/6/2020 |
| 1.0.20 | 85,802 | 6/6/2020 |
| 1.0.19 | 32,034 | 6/4/2020 |
| 1.0.18 | 58,765 | 5/29/2020 |
| 1.0.17 | 58,726 | 5/21/2020 |
| 1.0.16 | 4,234 | 5/17/2020 |
| 1.0.15 | 58,078 | 5/12/2020 |
| 1.0.14 | 113,002 | 5/4/2020 |
| 1.0.13 | 8,236 | 4/24/2020 |
| 1.0.12 | 10,887 | 4/22/2020 |
| 1.0.11 | 4,029 | 4/22/2020 |
| 1.0.10 | 4,015 | 4/21/2020 |
| 1.0.9 | 33,142 | 4/18/2020 |
| 1.0.8 | 30,885 | 4/16/2020 |
| 1.0.7 | 3,917 | 4/16/2020 |
| 1.0.6 | 26,413 | 4/15/2020 |
| 1.0.5 | 28,947 | 4/11/2020 |
| 1.0.4 | 27,973 | 4/3/2020 |
| 1.0.3 | 3,582 | 4/1/2020 |
| 1.0.2 | 14,778 | 3/27/2020 |
| 1.0.1 | 13,875 | 3/22/2020 |
| 1.0.0 | 6,064 | 3/22/2020 |