为啥是NuGet
NuGet是一个开源的包管理开发工具,专注于在.NET应用开发过程中简单地合并第三方的组件库。NuGet允许开发者轻松地查找、安装和管理第三方库和工具,从而简化.NET开发过程。如果我们一些组件功能封装到这里,后续组件升级时,将会给引用此组件所有程序提供非常遍历的升级服务。
创建类库
可以这样想,所谓NuGet的包,其实就是我们平时用的Dll类库,引用此服务的应用,通过这个DLL类,或本地解析,或对远程服务端发起请求,实现某些业务,无非NuGet帮我们做了托管,提供更新等便利,那多语言的NuGet显然需要一些远程的支持,所以,要新建一个NuGet包,我们只要在VisualStudio中,新建->项目,选中类库,点击创建,然后等其他程序引用了这个NuGet包,再由这个包去访问远程的服务即可。
引入服务,以便未来获取将来引用此组件的配置
我们新增一个ServiceCollectionExtensions类库,用来获取调用程序的配置信息,注册服务本身需要实现的业务模块的Service,以便未来程序调用AddOrgConfClientSDK方法后,即可自动注册,如添加内存管理,语言管理等
public static class OrgConfClientSDKWebAppServiceCollectionExtensions
{
public static IServiceCollection AddOrgConfClientSDK(this IServiceCollection services, Action<OrgConfClientSDKOptions> options)
{
if (options == null)
{
throw new ArgumentNullException("权限平台启用失败:有配置为空!");
}
services.Configure(options);
services.AddHttpContextAccessor();
//下面是一些自己类库组件需要实现的Service,以便未来程序调用AddOrgConfClientSDK方法后,即可自动注册,如添加内存管理,语言管理等
services.AddScoped<ILanguageService, LanguageService>();
services.AddMemoryCache(options =>
{
//options.SizeLimit = 100 * 1024 * 1024; //100MB
});
return services;
}
}
创建多语言的服务ILanguageService,并且实现。
创建接口ILanguageService,我们利用CultureInfo类,给出当前服务能支持的语言方法SupportedCultures,按照当前前端给的CultureInfo,定义了一些让前端根据Key关键字,返回具体文字Show的方法
public interface ILanguageService
{
CultureInfo CurrentCulture { get; set; }
Task<string> ShowAsync(string key, CultureInfo culture = null);
Task<string> ShowAsync(string key, params object[] args);
Task<string> ShowAsync(string key, CultureInfo culture, params object[] args);
Task<List<LanageValueHashSet>> ShowGroupAsync(List<LanageReqHashSet> keys, CultureInfo culture = null);
string Show(string key, CultureInfo culture = null);
string Show(string key, params object[] args);
string Show(string key, CultureInfo culture, params object[] args);
List<LanageValueHashSet> ShowGroup(List<LanageReqHashSet> keys, CultureInfo culture = null);
IReadOnlyList<CultureInfo> SupportedCultures { get; }
}
public class CultureChangedEventArgs : EventArgs
{
public CultureInfo OldCulture { get; }
public CultureInfo NewCulture { get; }
public CultureChangedEventArgs(CultureInfo oldCulture, CultureInfo newCulture)
{
OldCulture = oldCulture;
NewCulture = newCulture;
}
}
/// <summary>
/// 返回的语言值
/// </summary>
public class LanageValueHashSet
{
/// <summary>
/// 语言关键字
/// </summary>
public string key { get; set; }
/// <summary>
/// 自定义参数
/// </summary>
public object[]? args { get; set; }
/// <summary>
/// 语言值
/// </summary>
public string value { get; set; }
}
/// <summary>
/// 获取语言值的请求
/// </summary>
public class LanageReqHashSet
{
/// <summary>
/// 语言关键字
/// </summary>
public string key { get; set; }
/// <summary>
/// 自定义参数
/// </summary>
public object[]? args { get; set; }
}
下面是简单实现此方法LanguageService,思路如下,实现逻辑是用HTTP头里的RequestCulture信息来获取当前HTTP请求指定的语言,这样,我们可以实现不同的用户请求同一个接口时候,根据用户喜好不同,Show方法可以返回不同的语言结果。后续我们只要在用户请求的时候,想办法吧语言信息写到RequestCulture里就可以了,好了,让我们开始
首先,我们先定义一个配置中心的类库OrgConfClientSDKOptions,用来未来程序调用我们NuGet服务时的授权,并且可以请求我们的远程服务端
public class OrgConfClientSDKOptions
{
/// <summary>
/// 未来我们解析服务的平台 URL 地址(例如:http://192.168.0.1:59897)
/// </summary>
public string AuthCentralHostEndPoint { get; set; } = "";
/// <summary>
/// 应用ID
/// </summary>
public string AuthCentralAppId { get; set; } = "";
/// <summary>
///应用秘钥
/// </summary>
public string AuthCentralAppSecret { get; set; } = "";
/// <summary>
/// 应用模块ID
/// </summary>
public string AuthCentralAppModuleId { get; set; } = "";
}
下面是实现语言服务,关键方法GetCurrentRequestCulture获取当前客户设置的语言,Show是用来返回当前客户端设置的语言对应的翻译结果
public class LanguageService : ILanguageService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private IOptions<OrgConfClientSDKOptions> _options;
private CultureInfo _currentCulture;
public LanguageService(IHttpContextAccessor httpContext, IOptions<OrgConfClientSDKOptions> options)
{
_httpContextAccessor = httpContext;
_currentCulture = CultureInfo.CurrentUICulture;
SetUpOptions(options);
}
public LanguageService()
{
_currentCulture = CultureInfo.CurrentUICulture;
}
private void SetUpOptions(IOptions<OrgConfClientSDKOptions> options)
{
if (options == null
|| string.IsNullOrEmpty(options.Value.AuthCentralHostEndPoint)
|| string.IsNullOrEmpty(options.Value.AuthCentralAppId)
|| string.IsNullOrEmpty(options.Value.AuthCentralAppSecret))
{
throw new ArgumentNullException("权限平台启用失败:有配置为空");
}
_options = options;
}
private CultureInfo GetCurrentRequestCulture()
{
return _httpContextAccessor.HttpContext?.Items["RequestCulture"] as CultureInfo
?? _currentCulture;
}
public IReadOnlyList<CultureInfo> SupportedCultures {
get
{
return new List<CultureInfo>() {
new CultureInfo("en-US"),
new CultureInfo("zh-CN"),
};
}
}
public CultureInfo CurrentCulture
{
get {
return _currentCulture;
}
set
{
_currentCulture = value;
}
}
public string Show(string key, CultureInfo culture = null)
{
culture ??= GetCurrentRequestCulture();
if (culture.Name == "zh-CN")
{
return key;
}
else if (culture.Name == "en-US")
{
return "This is English Test";
}
else
{
return key;
}
}
public string Show(string key, params object[] args)
{
_currentCulture = GetCurrentRequestCulture();
if (_currentCulture.Name == "zh-CN")
{
return key;
}
else if (_currentCulture.Name == "en-US")
{
return "This is English Test";
}
else
{
return key;
}
}
public string Show(string key, CultureInfo culture, params object[] args)
{
culture ??= GetCurrentRequestCulture();
if (culture.Name == "zh-CN")
{
return key;
}
else if (culture.Name == "en-US")
{
return "This is English Test";
}
else
{
return key;
}
}
public async Task<string> ShowAsync(string key, CultureInfo culture = null)
{
culture ??= GetCurrentRequestCulture();
if (culture.Name == "zh-CN")
{
return key;
}
else if (culture.Name == "en-US")
{
return "This is English Test";
}
else
{
return key;
}
}
public async Task<string> ShowAsync(string key, params object[] args)
{
_currentCulture = GetCurrentRequestCulture();
if (_currentCulture.Name == "zh-CN")
{
return key;
}
else if (_currentCulture.Name == "en-US")
{
return "This is English Test";
}
else
{
return key;
}
}
public async Task<string> ShowAsync(string key, CultureInfo culture, params object[] args)
{
culture ??= GetCurrentRequestCulture();
if (culture.Name == "zh-CN")
{
return key;
}
else if (culture.Name == "en-US")
{
return "This is English Test";
}
else
{
return key;
}
}
public List<LanageValueHashSet> ShowGroup(List<LanageReqHashSet> keys, CultureInfo culture = null)
{
culture ??= GetCurrentRequestCulture();
return keys.ConvertAll(i =>
{
string val = i.key;
if (culture.Name == "zh-CN")
{
val = i.key;
}
else if (culture.Name == "en-US")
{
val = "This is English Test";
}
else
{
val = i.key;
}
return new LanageValueHashSet()
{
key = i.key,
value = val,
args = i.args
};
});
}
public async Task<List<LanageValueHashSet>> ShowGroupAsync(List<LanageReqHashSet> keys, CultureInfo culture = null)
{
culture ??= GetCurrentRequestCulture();
return keys.ConvertAll(i => new LanageValueHashSet()
{
key = i.key,
value = i.key
});
}
截至目前,获取对应语言的部分已经简单空实现,下面我们为了未来程序可以快速调用,比如可以在服务里直接使用(Lan.Show(“请根据HEAD语言请求给我不同结果”)),考虑有静态类的方法来实现,新建静态方法Lan,里面包括一个SetCulture,用来给客户端请求,将偏好语言写入COOKIE,Show方法是用来调用LanguageService里的Show方法。
public static class Lan
{
private static IHttpContextAccessor _httpContextAccessor;
public static void Configure(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public static string Show(string key, params object[] args)
{
var localization = _httpContextAccessor.HttpContext?.RequestServices.GetService<ILanguageService>();
if (localization == null)
{
throw new InvalidOperationException("LocalizationService not available");
}
return args.Length > 0 ? localization.Show(key, args) : localization.Show(key);
}
public static List<LanageValueHashSet> ShowGroup(List<LanageReqHashSet> keys)
{
var localization = _httpContextAccessor.HttpContext?.RequestServices.GetService<ILanguageService>();
if (localization == null)
{
throw new InvalidOperationException("LocalizationService not available");
}
return localization.ShowGroup(keys);
}
public static List<CultureInfo> SupportedCultures()
{
var localization = _httpContextAccessor.HttpContext?.RequestServices.GetService<ILanguageService>();
if (localization == null)
{
throw new InvalidOperationException("LocalizationService not available");
}
return localization.SupportedCultures.ToList();
}
public static async void SetCulture(string CultureName)
{
var HttpContext = _httpContextAccessor.HttpContext;
try
{
HttpContext.Response.Cookies.Append("culture", CultureName);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Cookies not available:{ex.Message}");
}
}
public static CultureInfo CurrentCulture
{
get
{
var localization = _httpContextAccessor.HttpContext?.RequestServices.GetService<ILanguageService>();
return localization?.CurrentCulture;
}
}
}
下面引入中间件LanMiddle,以便未来程序在引用此NuGet包时候,可以在Program.cs中注册使用,此处,我们看到我们在解析客户端请求的Cookies,解析出语言偏好,设置到RequestCulture上下文中
/// <summary>
/// 多语言中间件
/// </summary>
public class LanMiddle
{
private readonly RequestDelegate _next;
private readonly IReadOnlyList<CultureInfo> _supportedCultures;
public LanMiddle(RequestDelegate next, IOptions<LanguageService> options)
{
_next = next;
_supportedCultures = options.Value.SupportedCultures;
}
/// <summary>
/// 确定本次请求的语言种类
/// </summary>
/// <param name="context"></param>
/// <param name="localization"></param>
/// <returns></returns>
public async Task InvokeAsync(HttpContext context, ILanguageService localization)
{
// 1. 从请求中确定语言 (Header/QueryString/Cookie等)
var culture = DetermineRequestCulture(context);
// 2. 设置本次请求的语言上下文 (不修改全局状态)
context.Items["RequestCulture"] = culture;
await _next(context);
}
private CultureInfo DetermineRequestCulture(HttpContext context)
{
// 实现从请求中解析语言的逻辑
// 例如:
// - Accept-Language header
// ?culture=zh-CN 查询参数
// 用户偏好设置等
var _defaultCulture = _supportedCultures.FirstOrDefault() ?? CultureInfo.InvariantCulture;
if (context.Request.Query.TryGetValue("culture", out var queryCulture))
{
return MatchCulture(queryCulture);
}
// 2. 自定义Header
if (context.Request.Headers.TryGetValue("X-Culture", out var headerCulture))
{
return MatchCulture(headerCulture);
}
// 3. Cookie
if(context.Request.Cookies.TryGetValue("culture", out var cookieCulture))
{
return MatchCulture(cookieCulture);
}
// 4. 标准Accept-Language
var acceptLanguage = context.Request.GetTypedHeaders().AcceptLanguage;
if (acceptLanguage?.Count > 0)
{
return MatchCulture(acceptLanguage[0].Value.Value);
}
// 4. 默认
return _defaultCulture;
}
private CultureInfo MatchCulture(string cultureName)
{
var _defaultCulture = _supportedCultures.FirstOrDefault() ?? CultureInfo.InvariantCulture;
try
{
var culture = new CultureInfo(cultureName);
return _supportedCultures.Contains(culture) ? culture : _defaultCulture;
}
catch
{
return _defaultCulture;
}
}
}
到此NuGet包撰写完成,现在我们需要把它放到我们的NuGet服务器里,让我们写一个Script脚本文件publish-baget.ps1,把他放到项目的scripts文件夹里

cd ..\
# 提示用户输入版本号
$version = Read-Host "请输入版本号(eg 1.0.0)"
# 确保版本号不为空
if ([string]::IsNullOrEmpty($version)) {
Write-Host "版本号不能为空。脚本已终止。"
exit
}
# 输出用户输入的版本号
Write-Host "你输入的版本号是: $version"
# 传递版本号给命令,然后执行
$publishCommand = "dotnet build -c Release /p:Version=$version"
Invoke-Expression $publishCommand
Write-Host "编译成功, 推送到 BaGet"
cd .\bin\Release
$pushBagetCommand = "dotnet nuget push -s http://nuget服务器IP:端口号/v3/index.json -k 用户名@密码 NuGet包名.$version.nupkg"
Invoke-Expression $pushBagetCommand
Read-Host "推送成功,按 Enter 键退出"
然后,我们到WINDOWS控制台,CD到此目录,输入“./publish-baget.ps1”执行即可
三方程序应用语言NuGet包
当第三方引用,在NuGet包里找到这个服务引用的时候,需要在Program.cs引入中间件
Lan.Configure(app.Services.GetRequiredService<IHttpContextAccessor>());
app.UseMiddleware<LanMiddle>();
让它看起来像这样

然后再在注册ConfigureServices方法中,传入指定的参数
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
ConfigureAuthentication(context, configuration);
ConfigureBundles();
ConfigureUrls(configuration);
ConfigureConventionalControllers();
ConfigureVirtualFileSystem(context);
ConfigureCors(context, configuration);
ConfigureSwaggerServices(context, configuration);
ConfigureCoreLibs(context, configuration);//新增的多语言方法
}
private void ConfigureCoreLibs(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddOrgConfClientSDK(options =>
{
options.AuthCentralAppId = configuration["AuthCentral:AppId"] ?? "c459c0d8-80bd-2831-12d4-3a18a5e5fc31";
options.AuthCentralAppSecret = configuration["AuthCentral:AppSecret"] ?? "a44a8637-c378-474d-aeaf-c95c9541bba5";
options.AuthCentralAppModuleId = configuration["AuthCentral:AppModuleId"] ?? "56cf6514-737e-4c9d-0bcc-3a18ab64095a";
options.AuthCentralHostEndPoint = configuration["AuthCentral:HostEndPoint"] ?? "http://10.119.220.101:59897";
});
}
第三方应用多语言设置
我们可以引入该包的应用里面创建ClientService服务,用来获取和设置NuGet包支持的语言
public class ClientService : ApplicationService, IClientService
{
public ClientService()
{
}
/// <summary>
/// 按照HTTPHeader中的X-Culture获取语言包
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput<List<LanageValueHashSet>>> GetLanageValuesAsync(List<LanageReqHashSet> keys)
{
var res = Lan.ShowGroup(keys);
return ResponseOutput.Ok(res);
}
/// <summary>
/// 获取应用支持的语言
/// </summary>
/// <returns></returns>
public async Task<IResponseOutput<List<Lanage>>> GetLanagesAsync()
{
var Cultures = Lan.SupportedCultures().ToList();
var res = Cultures.ConvertAll(i => new Lanage {
Code=i.Name,
Language=i.DisplayName
});
return ResponseOutput.Ok(res);
}
/// <summary>
/// 设置当前用户的应用语言
/// </summary>
/// <returns></returns>
public async Task<IResponseOutput> SetLanage(string LanageCode)
{
var _defaultCulture = Lan.CurrentCulture;
var culture = new CultureInfo(LanageCode);
culture = Lan.SupportedCultures().Contains(culture) ? culture : _defaultCulture;
Lan.SetCulture(culture.Name);
return ResponseOutput.Ok();
}
}
然后,就可以在所有地方,使用“Lan.Show”方法来进行多语言输出
