ABP 自定义实现多语言处理

2023年7月27日 1156点热度 1人点赞 0条评论
内容纲要

在 ABP 中,默认只有本地 JSON 语言处理,但是在业务上我们可能有好多定制需求。本文介绍如何自己根据 redis 实现一个多语言处理,通过 redis 取得语言信息。

ABP 官方文档:https://docs.abp.io/en/abp/latest/Localization

ABP 是这样配置多语言的:

services.Configure<AbpLocalizationOptions>(options =>
{
    options.Resources
        .Add<TestResource>("en") //Define the resource by "en" default culture
        .AddVirtualJson("/Localization/Resources/Test") //Add strings from virtual json files
        .AddBaseTypes(typeof(AbpValidationResource)); //Inherit from an existing resource
});

在这个基础上,我们来实现直接的多语言处理。

首先要实现 ILocalizationResourceContributor 接口,提供多语言字符串查询。

笔者使用 FreeRedis 实现本地缓存和动态变更缓存。

在 redis 中,我们设置这样的 key 格式:language:{语言名称} ,比如 language:zh-CN ,然后 Key 使用 hash 类型,里面存储字符串关键字和对应语言翻译。

RedisResourceOptions 随便写,比如 Redis key 前缀。

public class RedisLocalizationResource : ILocalizationResourceContributor
    {
        private readonly RedisResourceOptions _options;
        private readonly RedisClient.DatabaseHook _db;
        private readonly ConcurrentDictionary<string, string> _languages = new ConcurrentDictionary<string, string>();
        private readonly string _keyPrefix;

        internal RedisLocalizationResource(RedisClient.DatabaseHook db, RedisResourceOptions options)
        {
            _options = options;
            _keyPrefix = options.KeyPrefix;
            _db = db;
        }

        /// <summary>
        /// 属于动态获取语言信息
        /// </summary>
        public bool IsDynamic => true;

        /// <summary>
        /// 填充,IsDynamic = false 才会用到
        /// </summary>
        public void Fill(string cultureName, Dictionary<string, LocalizedString> dictionary)
        {
            var hash = _db.HGetAll($"{_keyPrefix}:{cultureName}");
            foreach (var item in hash)
            {
                dictionary.Add(item.Key, new LocalizedString(item.Key, item.Value));
            }
        }

        /// <summary>
        /// 填充,IsDynamic = false 才会用到
        /// </summary>
        public async Task FillAsync(string cultureName, Dictionary<string, LocalizedString> dictionary)
        {
            var hash = await _db.HGetAllAsync($"{_keyPrefix}:{cultureName}");
            foreach (var item in hash)
            {
                dictionary.Add(item.Key, new LocalizedString(item.Key, item.Value));
            }
        }

        /// <summary>
        /// 获取本地化字符串
        /// </summary>
        /// <param name="cultureName">语言名称</param>
        /// <param name="name">key</param>
        /// <returns></returns>
        public LocalizedString GetOrNull(string cultureName, string name)
        {
            var key = GetLanguageKey(cultureName);

            if (key == default) return null!;

            var value = _db.HGet(key, name);
            if (!string.IsNullOrEmpty(value))
                return new LocalizedString(name, value);

            return new LocalizedString(name, name);
        }

        /// <summary>
        /// 支持的语言
        /// </summary>
        /// <returns></returns>
        public async Task<IEnumerable<string>> GetSupportedCulturesAsync()
        {
            await Task.CompletedTask;
            var languageKeys = new List<string>();
            List<string> languages = new List<string>();

            foreach (var keys in  _db.Scan(_keyPrefix, 20, null))
            {
                languageKeys.AddRange(keys);
            }

            foreach (var key in languageKeys)
            {
                var language = key.Split(":").LastOrDefault();
                if (language == null) continue;
                languages.Add(language);
            }
            return languages;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public void Initialize(LocalizationResourceInitializationContext context)
        {
            _languages.Clear();

            var languageKeys = new List<string>();

            foreach (var keys in _db.Scan(_keyPrefix, 20, null))
            {
                languageKeys.AddRange(keys);
            }

            foreach (var key in languageKeys)
            {
                var language = key.Split(":").LastOrDefault();
                if (language == null) continue;
                _languages.TryAdd(language, key);
            }
        }

        private DateTime _currentTime = DateTime.Now;

        private string? GetLanguageKey(string cultureName)
        {
            var key = $"{_keyPrefix}:{cultureName}";

            // 本地没有这个 key
            if (!_languages.ContainsKey(cultureName))
            {
                // 5 s 内只能检测重新检查一次 key
                if (DateTime.Now - _currentTime > TimeSpan.FromSeconds(5))
                {
                    _currentTime = DateTime.Now;

                    var exist = _db.Exists(key);
                    if (!exist) return default;
                    _languages.TryAdd(cultureName, key);
                }
                else return null;
            }

            return key;
        }
    }

然后编写扩展,注入这个自定义语言服务。

    /// <summary>
    /// 动态多语言扩展配置
    /// </summary>
    public static class LocalizationExtensions
    {
        public static TLocalizationResource AddRedisResource<TLocalizationResource>(
            [NotNull] this TLocalizationResource localizationResource,
            int dbIndex = 0,
            string keyPrefix = "language")
            where TLocalizationResource : LocalizationResourceBase
        {
            ArgumentNullException.ThrowIfNull(keyPrefix);

            keyPrefix = keyPrefix.ToLower();

            var db = RedisHelper.Client.GetDatabase(dbIndex);

            // 这里使用 FreeRedis 动态更新本地缓存,提供响应速度和性能
            db.UseClientSideCaching(new ClientSideCachingOptions
            {
                // 客户端缓存容量
                Capacity = 20,
                // 过滤
                KeyFilter = key => key.StartsWith(keyPrefix),
                // 检查长时间不使用的缓存
                CheckExpired = (key, dt) => DateTime.Now.Subtract(dt) > TimeSpan.FromSeconds(5),
            });

            localizationResource.Contributors.Add(new RedisLocalizationResource(db, new RedisResourceOptions(dbIndex, keyPrefix)));
            return localizationResource;
        }
    }

然后直接使用即可:

            Configure<AbpLocalizationOptions>(options =>
            {
                options.Resources
                // 设置默认语言
                .Add<TestResource>("en")
                // 配置 redis 信息
                .AddRedisResource(dbIndex: 0, keyPrefix: "language");
            });

痴者工良

高级程序员劝退师

文章评论