Sparkdo.Localization.Abstractions 3.0.0-preview.1

This is a prerelease version of Sparkdo.Localization.Abstractions.
dotnet add package Sparkdo.Localization.Abstractions --version 3.0.0-preview.1
                    
NuGet\Install-Package Sparkdo.Localization.Abstractions -Version 3.0.0-preview.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sparkdo.Localization.Abstractions" Version="3.0.0-preview.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Sparkdo.Localization.Abstractions" Version="3.0.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="Sparkdo.Localization.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Sparkdo.Localization.Abstractions --version 3.0.0-preview.1
                    
#r "nuget: Sparkdo.Localization.Abstractions, 3.0.0-preview.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Sparkdo.Localization.Abstractions@3.0.0-preview.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Sparkdo.Localization.Abstractions&version=3.0.0-preview.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Sparkdo.Localization.Abstractions&version=3.0.0-preview.1&prerelease
                    
Install as a Cake Tool

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 放进你的公共 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);
}

三条契约义务:

  1. 实现自己负责把资源解析完(按类型 / 按名字 / 默认资源),调用方只交出一个工厂。
  2. 找不到资源或键返回 ResourceNotFound = true 的标记值(Value == Name),不抛异常—— 兜底文案是调用方的决定,不是取值的失败。
  3. 契约不承诺幂等与缓存:同一实例多次 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 同上

三条纪律:

  1. 入参校验在实现侧。resourceName 为 null/空白时:标准(非 Sparkdo)工厂返回 null, Sparkdo 工厂抛 ArgumentException——这是刻意的降级方向(库侧不因宿主实现不同而抛异常), 但意味着你不能指望扩展方法替你校验参数。
  2. OrNull 用于「问一问」,非 OrNull 用于「必须要」。库代码优先用 OrNull 再自己兜底文案。
  3. 异步只服务于外部存储的资源发现。本地字典资源的实现是同步完成的快速路径, 调用方仍应 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 条,来自本包契约与运行时包的实现约定):

  1. Localize 不因未命中抛异常;未命中要么返回标记,要么返回你自己的兜底值。
  2. 同步与异步同时实现时,两条路径语义必须一致(同一入参、同一层的兜底顺序)。
  3. 只做一次工厂解析就返回;不要把 IStringLocalizer 缓存在实例字段里(工厂与资源注册表都可能换)。
  4. 实现 IAsyncLocalizableString 时,没有真异步来源就用 Task.FromResult,别 Task.Run 包同步。
  5. 需要构造期校验就把校验放在构造器(本包的 LocalizableString 就是这样:空白 name 立即抛)。
  6. 别重写 ToString() 来"返回文本"——调用方用的是 Localize,重写只会制造两种真相。
  7. 类要保持不可变(readonly/init);实例可能被抓进配置或缓存里长期持有。
  8. 若你的实现要能被序列化(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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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