在Blazor应用中为IStringLocalizer动态注入数据库中的翻译资源
在Blazor应用中为IStringLocalizer动态注入数据库中的翻译资源
当然可以实现!我之前也遇到过类似的需求,下面给你分享几种可行的方案,你可以根据自己的场景选最合适的:
方案一:自定义StringLocalizerProvider(整合到Blazor原生本地化系统)
这种方式能把数据库的翻译完全整合到Blazor的IStringLocalizer体系里,和你现有的resx资源无缝配合,用法和原生的localizer完全一致。
步骤1:定义数据库实体
先创建一个存储翻译的实体类,对应数据库表:
public class TranslationEntry { public int Id { get; set; } public string CultureCode { get; set; } = string.Empty; // 比如 "en-GB"、"da-DK" public string Key { get; set; } = string.Empty; // 翻译键,比如 "fish" public string Value { get; set; } = string.Empty; // 对应语言的翻译值 }
步骤2:实现自定义的StringLocalizer和Provider
我们需要包装原生的IStringLocalizer,在获取翻译时优先查数据库,找不到再用resx的资源:
// 自定义Localizer,负责合并数据库和resx的翻译 public class DbStringLocalizer : IStringLocalizer { private readonly IStringLocalizer _defaultLocalizer; private readonly IDbContextFactory<AppDbContext> _dbContextFactory; private readonly Dictionary<string, Dictionary<string, string>> _cachedTranslations = new(); public DbStringLocalizer(IStringLocalizer defaultLocalizer, IDbContextFactory<AppDbContext> dbContextFactory) { _defaultLocalizer = defaultLocalizer; _dbContextFactory = dbContextFactory; LoadTranslationsFromDb(); // 应用启动时加载一次数据库翻译 } // 从数据库加载翻译并缓存 private void LoadTranslationsFromDb() { using var context = _dbContextFactory.CreateDbContext(); var translations = context.TranslationEntries.ToList(); foreach (var entry in translations) { if (!_cachedTranslations.ContainsKey(entry.CultureCode)) { _cachedTranslations[entry.CultureCode] = new Dictionary<string, string>(); } _cachedTranslations[entry.CultureCode][entry.Key] = entry.Value; } } // 获取单条翻译 public LocalizedString this[string name] { get { var currentCulture = CultureInfo.CurrentUICulture.Name; // 先查数据库缓存 if (_cachedTranslations.TryGetValue(currentCulture, out var cultureTranslations) && cultureTranslations.TryGetValue(name, out var value)) { return new LocalizedString(name, value, resourceNotFound: false); } // 数据库没找到就用原生resx的翻译 return _defaultLocalizer[name]; } } // 带参数的翻译 public LocalizedString this[string name, params object[] arguments] { get { var localizedString = this[name]; return !localizedString.ResourceNotFound ? new LocalizedString(name, string.Format(localizedString.Value, arguments), false) : _defaultLocalizer[name, arguments]; } } // 获取所有翻译(合并数据库和resx) public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) { var defaultStrings = _defaultLocalizer.GetAllStrings(includeParentCultures); var currentCulture = CultureInfo.CurrentUICulture.Name; if (_cachedTranslations.TryGetValue(currentCulture, out var dbStrings)) { foreach (var (key, value) in dbStrings) { yield return new LocalizedString(key, value, resourceNotFound: false); } } foreach (var str in defaultStrings) { yield return str; } } public IStringLocalizer WithCulture(CultureInfo culture) { return new DbStringLocalizer(_defaultLocalizer.WithCulture(culture), _dbContextFactory); } } // 自定义Provider,告诉Blazor用我们的DbStringLocalizer public class DbStringLocalizerProvider : IStringLocalizerProvider { private readonly IDbContextFactory<AppDbContext> _dbContextFactory; private readonly IStringLocalizerProvider _defaultProvider; public DbStringLocalizerProvider(IDbContextFactory<AppDbContext> dbContextFactory, IStringLocalizerProvider defaultProvider) { _dbContextFactory = dbContextFactory; _defaultProvider = defaultProvider; } public IStringLocalizer Create(Type resourceSource) { var defaultLocalizer = _defaultProvider.Create(resourceSource); return new DbStringLocalizer(defaultLocalizer, _dbContextFactory); } public IStringLocalizer Create(string baseName, string location) { var defaultLocalizer = _defaultProvider.Create(baseName, location); return new DbStringLocalizer(defaultLocalizer, _dbContextFactory); } }
步骤3:注册自定义Provider
在Program.cs里替换Blazor默认的本地化Provider:
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); // 替换默认的StringLocalizerProvider为我们自定义的 builder.Services.Replace(ServiceDescriptor.Singleton<IStringLocalizerProvider, DbStringLocalizerProvider>());
方案二:自定义Localizer(简单动态添加资源)
如果只是需要像你提到的那样手动添加资源,或者只需要为特定组件提供动态翻译,可以创建一个自定义的Localizer类,直接注入使用:
public class CustomLocalizer { private readonly IStringLocalizer<SharedResource> _defaultLocalizer; private readonly IDbContextFactory<AppDbContext> _dbContextFactory; private readonly Dictionary<string, Dictionary<string, string>> _dynamicResources = new(); public CustomLocalizer(IStringLocalizer<SharedResource> defaultLocalizer, IDbContextFactory<AppDbContext> dbContextFactory) { _defaultLocalizer = defaultLocalizer; _dbContextFactory = dbContextFactory; LoadDynamicResourcesFromDb(); // 启动时加载数据库翻译 } private void LoadDynamicResourcesFromDb() { using var context = _dbContextFactory.CreateDbContext(); var entries = context.TranslationEntries.ToList(); foreach (var entry in entries) { if (!_dynamicResources.ContainsKey(entry.CultureCode)) _dynamicResources[entry.CultureCode] = new(); _dynamicResources[entry.CultureCode][entry.Key] = entry.Value; } } // 你想要的手动添加资源的方法 public void AddResource(string culture, string key, string value) { if (!_dynamicResources.ContainsKey(culture)) _dynamicResources[culture] = new(); _dynamicResources[culture][key] = value; } // 获取翻译 public string this[string key] { get { var culture = CultureInfo.CurrentUICulture.Name; if (_dynamicResources.TryGetValue(culture, out var cultureResources) && cultureResources.TryGetValue(key, out var val)) return val; return _defaultLocalizer[key]; } } }
然后在Program.cs注册为单例:
builder.Services.AddSingleton<CustomLocalizer>();
之后在Blazor组件里直接注入CustomLocalizer使用即可,比如:
@inject CustomLocalizer Localizer <p>@Localizer["fish"]</p>
注意事项
- 缓存策略:因为你说数据库不会频繁更新,所以启动时加载一次缓存就够了。如果需要实时更新,可以添加一个刷新方法,比如定时刷新或者提供一个API触发缓存更新。
- 键冲突处理:如果数据库和resx有相同的键,上面的方案里数据库的翻译会优先生效,你可以根据需求调整优先级。
- 文化代码规范:确保数据库里的
CultureCode是标准的.NET文化名称(比如"en-GB"、"da-DK"),和Blazor的本地化体系保持一致。
备注:内容来源于stack exchange,提问作者Erik Thysell
相关产品推荐
相关产品推荐

