Sparkdo.Localization.Abstractions
3.0.0-preview.1
dotnet add package Sparkdo.Localization.Abstractions --version 3.0.0-preview.1
NuGet\Install-Package Sparkdo.Localization.Abstractions -Version 3.0.0-preview.1
<PackageReference Include="Sparkdo.Localization.Abstractions" Version="3.0.0-preview.1" />
<PackageVersion Include="Sparkdo.Localization.Abstractions" Version="3.0.0-preview.1" />
<PackageReference Include="Sparkdo.Localization.Abstractions" />
paket add Sparkdo.Localization.Abstractions --version 3.0.0-preview.1
#r "nuget: Sparkdo.Localization.Abstractions, 3.0.0-preview.1"
#:package Sparkdo.Localization.Abstractions@3.0.0-preview.1
#addin nuget:?package=Sparkdo.Localization.Abstractions&version=3.0.0-preview.1&prerelease
#tool nuget:?package=Sparkdo.Localization.Abstractions&version=3.0.0-preview.1&prerelease
Sparkdo.Localization.Abstractions
Sparkdo 本地化契约层:可本地化字符串对象模型(ILocalizableString 家族)、
资源命名与继承特性、显示名解析扩展,以及置于 Microsoft.Extensions.Localization
命名空间的工厂扩展缝。它不含任何字典、解析或缓存实现——只定义形状,让库/包能依赖本地化
而不绑架宿主的实现选型。
除本地化生态的公共货币 Microsoft.Extensions.Localization.Abstractions(LocalizedString /
IStringLocalizer / IStringLocalizerFactory)外零外部依赖(Microsoft.Extensions.Localization.Abstractions 平台不内置,net10.0 与 net11.0 都显式引用);IsAotCompatible / IsTrimmable。
using Microsoft.Extensions.Localization;
using Sparkdo.Localization;
ILocalizableString title = LocalizableString.Create<MyResource>("Title"); // 按资源类型
string text = title.Localize(localizerFactory); // 现在才解析
目录
- 什么时候只引这个包
- 安装
- 类型清单
- ILocalizableString:延迟取值的契约
- LocalizableString 逐成员语义
- FixedLocalizableString
- LocalizableStringExtensions:统一异步入口
- 资源命名:LocalizationResourceNameAttribute
- 资源继承:为什么不在本包
- 显示名解析:IHasNameWithLocalizableDisplayName
- 工厂扩展缝:ISparkdoStringLocalizerFactory
- 实现自己的 ILocalizableString
- 典型用法配方
- 调用期拒绝清单与异常一览
- 设计立场
- 常见坑
- 与其他包的关系
什么时候只引这个包
| 你是谁 | 引哪个 |
|---|---|
| 类库/扩展包,只想让宿主决定「这段文本怎么本地化」 | 本包——把 ILocalizableString 放进你的公共 API,宿主有工厂就能解析 |
| 有实体/定义对象,需要「具名 + 可本地化显示名」的形状 | 本包 |
只依赖 IStringLocalizerFactory,但想用「按资源名 / 默认资源」寻址 |
本包(ISparkdoStringLocalizerFactory + 扩展方法) |
| 要注册资源、挂 JSON 文件、拿回退链与 DI 接线 | Sparkdo.Localization(本包是它的传递依赖) |
要序列化 ILocalizableString 到数据库 |
Sparkdo.Localization(ILocalizableStringSerializer 在运行时包) |
安装
dotnet add package Sparkdo.Localization.Abstractions
目标框架 net10.0;net11.0。
类型清单
| 类型 | 命名空间 | 种类 | 一句话 |
|---|---|---|---|
ILocalizableString |
Sparkdo.Localization |
接口(1 方法) | 延迟取值的最小契约 |
IAsyncLocalizableString |
Sparkdo.Localization |
接口(1 方法) | 异步取值形态,语义同同步 |
LocalizableString |
Sparkdo.Localization |
sealed 类 | 按类型 / 按资源名寻址的实现 |
FixedLocalizableString |
Sparkdo.Localization |
sealed 类 | 固定文本,不查资源 |
LocalizableStringExtensions |
Sparkdo.Localization |
静态扩展 | LocalizeAsync 统一入口(优先异步实现) |
LocalizationResourceNameAttribute |
Sparkdo.Localization |
类特性 | 给资源类型稳定名字;GetName(Type) 是名字推导入口 |
IHasNameWithLocalizableDisplayName |
Sparkdo.Localization |
接口 | 「具名 + 可本地化显示名」的形状 |
HasNameWithLocalizableDisplayNameExtensions |
Sparkdo.Localization |
静态扩展 | GetLocalizedDisplayName 解析链 |
ISparkdoStringLocalizerFactory |
Microsoft.Extensions.Localization |
接口 | 工厂扩展缝:默认资源 / 按资源名寻址 |
SparkdoStringLocalizerFactoryExtensions |
Microsoft.Extensions.Localization |
静态扩展 | 上述缝在任意 IStringLocalizerFactory 上的安全调用面 |
ISparkdoStringLocalizerFactory 及其扩展刻意放在 Microsoft.Extensions.Localization 命名空间:
它们是标准 IStringLocalizerFactory 的扩展位,调用方只需要 using 一个命名空间就能同时拿到标准接口与本包扩展。
ILocalizableString:延迟取值的契约
public interface ILocalizableString
{
LocalizedString Localize(IStringLocalizerFactory stringLocalizerFactory);
}
三条契约义务:
- 实现自己负责把资源解析完(按类型 / 按名字 / 默认资源),调用方只交出一个工厂。
- 找不到资源或键返回
ResourceNotFound = true的标记值(Value == Name),不抛异常—— 兜底文案是调用方的决定,不是取值的失败。 - 契约不承诺幂等与缓存:同一实例多次
Localize允许返回不同实例(FixedLocalizableString每次都新建)。 需要「解析一次长期持有」请自己把结果存成string。
LocalizedString 到 string 有隐式转换,所以 string s = x.Localize(factory); 取到的是 Value;
想区分「真的取到了」和「兜底回显」必须看 ResourceNotFound。
LocalizableString 逐成员语义
var byType = LocalizableString.Create<MyResource>("Title"); // 推荐:编译期可查
var byTypeObj = LocalizableString.Create(typeof(MyResource), "Title"); // 运行期类型
var byName = LocalizableString.Create("Title", "MyResource"); // 只有名字(跨程序集/无类型可见性)
var bare = new LocalizableString("Title"); // 无归属 → 直接走默认资源
| 成员 | 语义 |
|---|---|
LocalizableString(Type? resourceType, string name) |
resourceType 非空时构造期就经 LocalizationResourceNameAttribute.GetName 把 ResourceName 定下来(含名字推导失败抛异常);resourceType 可为 null |
LocalizableString(string name, string? resourceName = null) |
纯按名寻址;resourceName 为 null 即「无归属」 |
string Name { get; } |
资源键;空白或 null 抛(ArgumentException / ArgumentNullException) |
Type? ResourceType { get; } |
只有按类型构造才非空 |
string? ResourceName { get; } |
按类型构造 = 推导出的资源名;按名构造 = 传入值(可为 null) |
LocalizedString Localize(IStringLocalizerFactory) |
见下面解析顺序 |
Task<LocalizedString> LocalizeAsync(IStringLocalizerFactory) |
与同步同语义,仅把「按名寻址」换成异步资源发现(外部存储场景);LocalizableString 自身是同步完成的快速路径 |
static LocalizableString Create<TResource>(string name) |
where TResource : notnull |
static LocalizableString Create(Type resourceType, string name) |
不校验 resourceType 是否为资源类型;名字推导失败即抛 |
static LocalizableString Create(string name, string? resourceName = null) |
与按名构造等价 |
解析顺序(同步与异步一致):
ResourceType 非空 → factory.Create(resourceType)
↓ 否则
ResourceName 非空 → factory.CreateByResourceNameOrNull(resourceName) // 本地注册表 → 外部存储
↓ 仍为 null 或 ResourceType/ResourceName 都为空
factory.CreateDefaultOrNull()
↓ 仍为 null
返回 new LocalizedString(Name, Name, resourceNotFound: true)
拿到本地化器后查 localizer[Name];若 ResourceNotFound 且本对象有 ResourceName,
再对默认资源查一次(无归属的对象本来就打在默认资源上,没有第二枪)
要点:
- 按类型构造只走类型入口,因此它看不见按名注册的资源,也不查外部存储; 按名构造才能命中这两者。
- 「目标资源未命中 → 再兜默认资源」这一步只在
ResourceName != null时发生。 这是同步与异步共同的行为,不存在"异步更严格"的分支(源实现的LocalizeAsync在造不出本地化器时抛异常, 移植时统一为标记返回)。 - 本类型是引用类型且不重写相等性:别把它当值放进字典键或用它做
==比较。 - 没有
ToString()重载,隐式转换只存在于LocalizedString→string一侧。
FixedLocalizableString
var fixedText = new FixedLocalizableString("MIT License");
fixedText.Localize(anyFactory); // new LocalizedString("MIT License", "MIT License")
- 完全不经资源查找,也不接收
null(ArgumentNullException,参数名value)。 - 产出的
LocalizedString里Name与Value同为该文本,ResourceNotFound恒为false—— 哪怕文本长得像未命中的键。 - 不做任何修剪/规范化,也不会被格式化:
"{0}"就返回"{0}"。 需要占位符请走IStringLocalizer索引器(那是宿主的格式化决定)。 - 每次
Localize新建一个LocalizedString(未缓存)。高频调用请自己解析一次存成string。 - 适用场景:配置里直接给出的字面量、第三方系统传回的不该被本地化的文本、序列化的
F:兜底。
LocalizableStringExtensions:统一异步入口
public static Task<LocalizedString> LocalizeAsync(
this ILocalizableString localizableString,
IStringLocalizerFactory stringLocalizerFactory);
- 调用方不需要知道实现是不是
IAsyncLocalizableString:是就转给它,否则同步取值再包Task.FromResult(不额外起线程、不Task.Run)。 localizableString为null抛ArgumentNullException;stringLocalizerFactory的null检查 在实现侧(同步Localize里)而非扩展里。- 这是本包唯一把「同步/异步」两条路收敛成一个入口的地方;自己写实现时建议同时实现
IAsyncLocalizableString,否则异步调用点只是白转一圈。
资源命名:LocalizationResourceNameAttribute
[LocalizationResourceName("MyApp")] // 稳定资源名,跨版本重命名类型不影响资源键
public sealed class MyAppResource;
| 成员 | 语义 |
|---|---|
LocalizationResourceNameAttribute(string name) |
name 空白抛 ArgumentException,null 抛 ArgumentNullException |
string Name { get; } |
原样保存,不做任何规范化(大小写、空格都按你写的) |
static LocalizationResourceNameAttribute? GetOrNull(Type) |
读取类型上的本特性,inherit: true;未标注返回 null |
static string GetName(Type) |
标注优先,否则 Type.FullName;结果按类型缓存(ConcurrentDictionary,进程生命周期,命中返回同一字符串实例) |
三个必须知道的推导细节:
- 只允许标注在类上(
AttributeTargets.Class,AllowMultiple = false)——接口、结构、枚举标不上。 inherit: true会沿基类找:class TenantResource : CommonResource而CommonResource标了名字时, 子类型推导出的资源名就是基类的名字,两者注册会撞名(Add抛InvalidOperationException)。- 未标注时用
Type.FullName,包含命名空间;所以重构命名空间会改变资源名。想稳定就必须显式标注。DefaultResource(在运行时包)自带标注,名字是"Default"。 - 开放泛型类型(
typeof(List<>))有全名,得到"System.Collections.Generic.List1"这种能用的名字; **真正的泛型参数**(方法内typeof(T))没有全名,抛InvalidOperationException("资源类型 T 是泛型参数或开放泛型,无法推导资源名,请标注 LocalizationResourceNameAttribute。")`。 - 缓存按
Type为键且永不清理:正常应用的类型数量有限,无需担心;但它意味着 标注错误的名字一旦解析过就被记住。
资源继承:为什么不在本包
资源继承(InheritResourceAttribute、IInheritedResourceTypesProvider、AddBaseTypes / AddBaseResources)
属于运行时包 Sparkdo.Localization:声明继承只是往资源的 BaseResourceNames 里加名字,
而把名字解析成本地化器、按链回退取值都需要资源注册表与工厂,本包没有也不该有这些。
本包只负责链条上的最小一环:「资源类型 → 稳定资源名」(LocalizationResourceNameAttribute.GetName)。
库侧给资源类型标注名字就够了;是否继承、继承谁,由接了运行时包的宿主决定。
显示名解析:IHasNameWithLocalizableDisplayName
public interface IHasNameWithLocalizableDisplayName
{
string Name { get; }
ILocalizableString? DisplayName { get; }
}
这是权限定义、设置定义、功能定义这类对象在 Sparkdo 各域共享的形状。解析扩展:
string displayName = definition.GetLocalizedDisplayName(localizerFactory);
string bareNameOnly = definition.GetLocalizedDisplayName(localizerFactory, localizationNamePrefix: null);
解析链(自上而下,首个成立即返回):
① 显式 DisplayName(非 null)→ DisplayName.Localize(factory)
② 默认资源 CreateDefaultOrNull() 不可得 → 直接返回 source.Name(一次查表都不做)
③ defaultStringLocalizer["{prefix}{Name}"](默认 prefix = "DisplayName:")未命中则继续
④ defaultStringLocalizer[source.Name]
| 细节 | 行为 |
|---|---|
localizationNamePrefix 传 null 或 "" |
等价于跳过 ③,只按裸名查一次(③ 与 ④ 变成同一次查表,代码里以 string.IsNullOrEmpty 判定) |
自定义前缀(如 "Label:") |
③ 查 Label:{Name},未命中仍回落到 ④ 裸名 |
| ③ 命中但 ④ 也命中 | 只返回 ③ 的值——前缀优先 |
| 两级都未命中 | 返回 ④ 的未命中标记,其 Value 等于裸名 → 返回值永远可直接展示 |
显式 DisplayName 非 null |
短路整条链,即便它自己解析成未命中标记(返回值等于它的 Name) |
source / factory 为 null |
ArgumentNullException |
source.Name |
接口不校验,实现方给什么就用什么(给空白则拿空白去查表) |
工厂扩展缝:ISparkdoStringLocalizerFactory
public interface ISparkdoStringLocalizerFactory
{
IStringLocalizer? CreateDefaultOrNull();
IStringLocalizer? CreateByResourceNameOrNull(string resourceName);
Task<IStringLocalizer?> CreateByResourceNameOrNullAsync(string resourceName);
}
它不继承 IStringLocalizerFactory——它是"能力探针"式的附加缝,由实现方同时实现两个接口。
调用侧一律经 SparkdoStringLocalizerFactoryExtensions 在标准 IStringLocalizerFactory 上使用:
| 扩展方法 | 工厂未实现本缝时 | 资源/默认不可得时 |
|---|---|---|
CreateDefaultOrNull() |
返回 null |
null(运行时包:配了类型却没注册 → 抛 InvalidOperationException(fail-closed);AllowUnregisteredDefaultResource = true 才回退 resx 本地化器) |
CreateByResourceNameOrNull(name) |
返回 null |
null |
CreateByResourceName(name) |
抛 InvalidOperationException |
抛 InvalidOperationException("未找到资源名为「{name}」的本地化器。") |
CreateByResourceNameOrNullAsync(name) |
返回 null(同步完成) |
null |
CreateByResourceNameAsync(name) |
抛 InvalidOperationException |
同上 |
三条纪律:
- 入参校验在实现侧。
resourceName为null/空白时:标准(非 Sparkdo)工厂返回null, Sparkdo 工厂抛ArgumentException——这是刻意的降级方向(库侧不因宿主实现不同而抛异常), 但意味着你不能指望扩展方法替你校验参数。 OrNull用于「问一问」,非 OrNull 用于「必须要」。库代码优先用OrNull再自己兜底文案。- 异步只服务于外部存储的资源发现。本地字典资源的实现是同步完成的快速路径,
调用方仍应
await(别.Result),以便实现方将来换成真异步。
localizerFactory 为 null 时五个扩展方法都抛 ArgumentNullException——两个非空断言版本经内部转调
OrNull 版本,因此也是先参数校验、后资源判定。
实现自己的 ILocalizableString
典型动机:资源归属要运行期决定、需要远程取词、或者想给"未命中"绑定业务兜底文案。
using Microsoft.Extensions.Localization;
using Sparkdo.Localization;
/// 按「租户资源 → 公共资源 → 默认资源」顺序取值,全不中给业务兜底。
public sealed class TieredLocalizableString(string name, string? tenantResource)
: ILocalizableString, IAsyncLocalizableString
{
public LocalizedString Localize(IStringLocalizerFactory factory)
{
ArgumentNullException.ThrowIfNull(factory);
foreach (var resourceName in CandidateResources())
{
var localizer = factory.CreateByResourceNameOrNull(resourceName);
if (localizer is null)
{
continue; // 该层不存在:跳过,不是失败
}
var result = localizer[name];
if (!result.ResourceNotFound)
{
return result;
}
}
return new LocalizedString(name, DefaultText);
}
public Task<LocalizedString> LocalizeAsync(IStringLocalizerFactory factory)
=> Task.FromResult(Localize(factory)); // 无外部存储时保持同步完成
private IEnumerable<string> CandidateResources()
{
if (tenantResource is not null)
{
yield return tenantResource;
}
yield return "Common";
}
}
实现清单(8 条,来自本包契约与运行时包的实现约定):
Localize不因未命中抛异常;未命中要么返回标记,要么返回你自己的兜底值。- 同步与异步同时实现时,两条路径语义必须一致(同一入参、同一层的兜底顺序)。
- 只做一次工厂解析就返回;不要把
IStringLocalizer缓存在实例字段里(工厂与资源注册表都可能换)。 - 实现
IAsyncLocalizableString时,没有真异步来源就用Task.FromResult,别Task.Run包同步。 - 需要构造期校验就把校验放在构造器(本包的
LocalizableString就是这样:空白name立即抛)。 - 别重写
ToString()来"返回文本"——调用方用的是Localize,重写只会制造两种真相。 - 类要保持不可变(
readonly/init);实例可能被抓进配置或缓存里长期持有。 - 若你的实现要能被序列化(
ILocalizableStringSerializer),运行时包只认它的两种类型, 其它实现会抛InvalidOperationException——那种场景请自己提供序列化器实现。
典型用法配方
1. 库侧暴露"可本地化的东西"而不绑架实现
public sealed class MyFeatureDefinition
{
public string Name { get; init; } = string.Empty;
public ILocalizableString? DisplayName { get; init; } // 宿主决定怎么本地化
}
// 消费端(宿主)
var shown = definition.DisplayName?.Localize(factory) ?? definition.GetLocalizedDisplayName(factory);
ILocalizableString 出现在公共 API 上时,调用方只需要一个 IStringLocalizerFactory 就能取文本,
不需要注册任何资源。
2. 判"是否真取到了值"必须看 ResourceNotFound
var result = value.Localize(factory);
if (result.ResourceNotFound)
{
logger.LogWarning("本地化键缺失:{Name}", result.Name);
return Fallback; // 隐式转 string 会拿到回显的键名
}
return result;
Value == Name 就是未命中的信号,但用 ResourceNotFound 判定才不会被「键名恰好等于译文」骗过去。
3. 只要名字、跨程序集寻址
var text = LocalizableString.Create("Menu.Settings", "Acme.Ui").Localize(factory);
按名寻址会同时命中「按名注册的资源」和外部存储提供的资源;按类型寻址则要求类型对宿主可见。
4. 给不可见类型准备兜底:默认资源
ILocalizableString any = new LocalizableString("Some.Key"); // 无归属 → 直接打在默认资源上
无归属对象没有「第二枪」(本来就打在默认资源);有归属的对象未命中时会再兜一次默认资源。
5. 固定文本参与同一套模型
ILocalizableString license = new FixedLocalizableString("MIT");
string shown = license.Localize(factory); // "MIT",与有没有默认资源、什么文化都无关
这让「一部分字段要本地化、一部分不该」的模型保持单一类型。
6. 异步取值(外部存储场景)
var text = await someValue.LocalizeAsync(factory); // 扩展方法:有异步实现就走,没有就同步包一层
调用方永远写 LocalizeAsync,实现方按需实现 IAsyncLocalizableString。
7. 标准工厂宿主下的安全降级
// 宿主只调了 AddLocalization()(工厂不是 Sparkdo 实现)
var localizer = factory.CreateDefaultOrNull(); // null,不抛
var text = localizer is null ? key : localizer[key];
本包扩展的设计目标就是:在任意 IStringLocalizerFactory 上都能编译并安全运行,
能力缺失表现为 null,不是异常。
调用期拒绝清单与异常一览
| 异常 | 触发点 | 消息要点 |
|---|---|---|
ArgumentNullException |
LocalizableString 的 name 为 null;FixedLocalizableString(null);扩展方法(LocalizeAsync / GetLocalizedDisplayName / 工厂扩展)的 null 接收器或工厂 |
"Value cannot be null. (Parameter 'name'|'value'|'localizerFactory'|…)" |
ArgumentException |
空或空白的 name / resourceName(LocalizableString 构造)、LocalizationResourceNameAttribute 的空白 name |
BCL 英文空白消息 |
InvalidOperationException |
LocalizationResourceNameAttribute.GetName 拿到泛型参数类型;CreateByResourceName(Async) 资源不可得 |
"资源类型 X 是泛型参数或开放泛型,无法推导资源名,请标注 LocalizationResourceNameAttribute。"、"未找到资源名为「X」的本地化器。" |
不会抛的情况:任何「资源或键未命中」的取值路径——返回 ResourceNotFound 标记。
设计立场
- 契约不抛异常:未命中是数据状态,不是错误;兜底文案由调用方决定。
- 能力探针而非继承:
ISparkdoStringLocalizerFactory不继承标准工厂,标准实现上安全降级为null, 库代码因此不依赖宿主接了哪一套本地化实现。 - 同步/异步语义必须一致:源实现的
LocalizeAsync在工厂造不出本地化器时抛异常而同步路径返回标记, 移植时统一为标记返回(决策记录见运行时包的移植分析文档)。 - 名字推导按类型缓存,热路径零反射;
inherit: true保持与生态一致(子类可共享基类资源名)。 - 扩展缝放进生态的标准命名空间(
Microsoft.Extensions.Localization),与扩展方法的目标类型同域, 调用侧少一个using。 - 零运行时依赖:不引 DI、不引 Options、不引文件系统;只有
LocalizedString这套生态货币。
常见坑
| 坑 | 事实 |
|---|---|
LocalizableString.Create<T>(name) 打不到按名注册的资源 |
按类型寻址走 Create(Type),看不见名字索引与外部存储;要用按名重载 |
以为 Localize 未命中会抛 |
只会给 ResourceNotFound = true 且 Value == Name 的标记;隐式转 string 就拿到键名回显 |
把 FixedLocalizableString 当"能带占位符" |
它不查资源也不格式化,"{0}" 原样返回 |
class TenantRes : CommonRes 两个都注册成资源 |
特性读取是 inherit: true,子类推导出基类名字 → 注册撞名抛异常 |
| 未标注特性的类型改名/改命名空间后资源找不到 | 未标注时用 Type.FullName;要稳定名字必须显式标注 |
对泛型参数调用 GetName(typeof(T)) |
抛 InvalidOperationException;给泛型资源类型显式标注名字 |
依赖扩展方法校验 resourceName |
校验在实现侧;非 Sparkdo 工厂对 null/空白名返回 null,Sparkdo 工厂抛 ArgumentException |
以为 ISparkdoStringLocalizerFactory 能用 CreateByResourceName 扩展 |
扩展声明在 IStringLocalizerFactory 上,且本接口不继承它;注入标准接口再调扩展 |
把 ILocalizableString 实例当值比较或当字典键 |
LocalizableString 未重写相等性,是引用语义 |
缓存 Localize 结果却期待文化变化后自动更新 |
契约只保证一次取值;文化是环境状态(AsyncLocal 语义,详见运行时包的 CultureHelper.Use),缓存与失效策略归调用方 |
| 自己实现时给未命中抛异常 | 违反契约第 2 条,会让上层「标记语义」失效 |
只实现 IAsyncLocalizableString 不实现 ILocalizableString |
Localize 是契约入口,异步是附加形态;两者都要满足 |
在 LocalizableString 里塞可变属性再交给别处解析 |
名字推导在构造期已完成,实例应不可变 |
与其他包的关系
- 依赖:仅
Microsoft.Extensions.Localization.Abstractions(LocalizedString/IStringLocalizer/IStringLocalizerFactory;net11.0 的平台隐式引用不覆盖该包,故显式登记)。 - 实现层:
Sparkdo.Localization(字典式本地化器与文化回退链、JSON 字典解析、IFileProvider文件贡献者、 枚举本地化、语言模型、序列化器、AddSparkdoLocalization()接线)。它引用本包,方向不可逆。 - 消费方:任何「公共 API 里有
ILocalizableString或IHasNameWithLocalizableDisplayName」的 Sparkdo 域 只依赖本包即可发布,宿主是否接运行时包由宿主决定。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. net11.0 is compatible. |
-
net10.0
- Microsoft.Extensions.Localization.Abstractions (>= 10.0.8)
-
net11.0
- Microsoft.Extensions.Localization.Abstractions (>= 10.0.8)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Sparkdo.Localization.Abstractions:
| Package | Downloads |
|---|---|
|
Sparkdo.Localization
Sparkdo 本地化:字典式本地化器与文化回退链(精确文化 → 直接父文化 → 资源默认文化 → 基础资源链,命中路径零分配)、经 Sparkdo.Json 源生成 context 的 JSON 字典 UTF-8 直解析(无反射 fallback、AOT 友好)、标准 IFileProvider 文件贡献者与变更令牌热更新、枚举三阶键序本地化与语言模型,AddSparkdoLocalization() 一行接线并替换标准 IStringLocalizerFactory。 |
|
|
Sparkdo.ObjectExtending
Sparkdo 对象扩展模块,提供对象属性扩展功能 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0-preview.1 | 59 | 10/30/2026 |
| 2.0.0-preview.2 | 107 | 7/20/2026 |
| 2.0.0-preview.1 | 94 | 7/18/2026 |
| 1.0.4-preview.3 | 515 | 6/8/2026 |
| 1.0.4-preview.2 | 502 | 6/7/2026 |
| 1.0.4-preview.1 | 478 | 6/7/2026 |
| 1.0.3 | 1,178 | 6/6/2026 |
| 1.0.3-preview.1 | 494 | 5/17/2026 |
| 1.0.2 | 893 | 4/27/2026 |
| 1.0.2-preview.5 | 208 | 4/15/2026 |
| 1.0.2-preview.4 | 207 | 2/8/2026 |
| 1.0.2-preview.3 | 215 | 2/1/2026 |
| 1.0.2-preview.2 | 225 | 1/31/2026 |
| 1.0.2-preview.1 | 261 | 12/4/2025 |
| 1.0.1 | 2,930 | 11/27/2025 |
| 1.0.0 | 2,874 | 11/25/2025 |
| 1.0.0-preview.5 | 183 | 10/24/2025 |