Files
AtomUI/docs/reference/localization/public-api.md
T

229 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AtomUI 多语言公共 API
本文定义应用、类库、ViewModel、服务和 XAML 使用 AtomUI 本地化基础设施时可依赖的公共契约。
Catalog 和 XLIFF 的源文件规则见 [catalog-and-xliff.md](catalog-and-xliff.md),运行时语义见
[本地化运行时架构](../../architecture/systems/localization/runtime.md)。
## 根 Builder
`Application.UseAtomUI(...)` 的配置参数由主题专用的 `IThemeManagerBuilder` 升级为根级 `IAtomUIBuilder`。
根 Builder 组合主题、本地化、控件包和运行时初始化,不把本地化重新塞进主题 Builder。
```text
IAtomUIBuilder
├── IThemeBuilder
├── ILocalizationBuilder
└── Application initializers
```
常用能力继续通过根 Builder 扩展方法暴露,避免调用方依赖内部子 Builder:
```csharp
this.UseAtomUI(builder =>
{
builder.UseLanguages(
defaultLanguage: LanguageTags.ZhCN,
supportedLanguages:
[
LanguageTags.EnUS,
LanguageTags.ZhCN,
LanguageTags.ZhTW
]);
builder.UseDesktopControls();
});
```
命名必须保持以下层次:
- `UseAtomUI()` 是进入 AtomUI 框架的根入口。
- Builder 内使用 `UseLanguages()`、`UseDesktopControls()` 等领域名称,不重复 `AtomUI` 前缀。
- `defaultLanguage` 表示启动后首次提交的应用语言,不改变每个 Catalog 固定的 `en-US` 源语言。
- `defaultLanguage` 必须属于 `supportedLanguages`;Builder 对输入集合做防御性复制,并按首次出现顺序去重。
- 未调用 `UseLanguages()` 时采用 `en-US` 作为默认语言和唯一支持语言,保证最小应用可启动。
`UseLanguages()` 不接收包名、程序集、Descriptor 或语言包列表。应用 Catalog、组件 Catalog 和静态语言包
通过生成链注册,Builder 配置顺序不得影响最终结果。
## LanguageTag
语言身份使用开放且不可变的 BCP 47 值类型:
```csharp
public readonly record struct LanguageTag
{
public string Value { get; }
public static LanguageTag Parse(string value);
public static bool TryParse(string? value, out LanguageTag language);
public static LanguageTag FromCultureInfo(CultureInfo culture);
}
```
`LanguageTag` 构造后必须处于规范形式:language subtag 小写、script 首字母大写、region 大写,其他 subtag 按
BCP 47 规范化;已知 grandfathered/alias 映射使用仓库固定的 IANA/CLDR 数据。相等性和哈希只比较规范化后的
完整标签,使用 ordinal 语义。`ToString()` 返回规范标签,`default(LanguageTag)` 是无效值,任何公共入口都必须
拒绝它,不能把空值解释为系统语言或 `en-US`。
`Parse` 和 `TryParse` 用于配置、命令行、持久化和网络数据等字符串边界。普通程序代码应使用生成式常量:
```csharp
LanguageTags.EnUS
LanguageTags.EnGB
LanguageTags.ZhCN
LanguageTags.ZhHans
LanguageTags.ZhTW
LanguageTags.ZhHant
LanguageTags.JaJP
LanguageTags.ArSA
```
`LanguageTags` 根据仓库固定版本的 CLDR/IANA 数据生成常用语言、脚本和区域标签。BCP 47 可以组合脚本、
区域、variant、extension 和 private-use subtag,因此不得用 enum 声称穷举所有合法值。第三方私有标签应在
自己的静态类中封装:
```csharp
public static class AcmeLanguageTags
{
public static LanguageTag EnAcme { get; } = LanguageTag.Parse("en-x-acme");
}
```
## LanguageDefinition
`LanguageTag` 只表达身份。语言选择器显示名称、格式化 Culture 和文字方向属于注册元数据:
```csharp
public sealed record LanguageDefinition(
LanguageTag Tag,
CultureInfo FormattingCulture,
string NativeName,
LanguageTextDirection TextDirection);
```
标准标签优先使用生成的 CLDR/.NET 元数据。无法映射到 .NET `CultureInfo` 的私有标签必须由应用显式提供
`LanguageDefinition`;系统不得静默改用 `InvariantCulture`。
```csharp
var privateLanguage = LanguageTag.Parse("en-x-acme");
builder.Localization.AddLanguageDefinition(new LanguageDefinition(
privateLanguage,
CultureInfo.GetCultureInfo("en-US"),
"Acme English",
LanguageTextDirection.LeftToRight));
builder.UseLanguages(privateLanguage, [privateLanguage]);
```
同一标签重复注册显式 `LanguageDefinition` 会在启动构建时失败;这与 `supportedLanguages` 中允许重复并去重的
集合语义不同。
## ILanguageManager
`ILanguageManager` 是全局应用语言状态的唯一所有者:
```csharp
public interface ILanguageManager
{
LanguageState Current { get; }
IReadOnlyList<LanguageDefinition> SupportedLanguages { get; }
event EventHandler<LanguageChangedEventArgs>? LanguageChanged;
LanguageChangeResult ChangeLanguage(LanguageTag language);
}
```
静态语言包已经在应用构建时编译完成,因此全局切换是同步 Snapshot 提交,不设计伪异步 API。
`ChangeLanguage()` 必须在 UI 线程调用;切换到当前语言返回 `NoOp`,不重复发布资源通知或事件。
`LanguageChangeResult` 只有 `Committed` 和 `NoOp` 两种成功状态,并携带旧/新 `LanguageState`。未支持标签抛出
`LanguageNotSupportedException`,错误线程或已释放 Manager 抛出 `InvalidOperationException`;静态 Snapshot 不存在
表示启动构建不变量被破坏,不能包装成普通 `Failed` 结果继续运行。
`ILanguageManager` 和 `ILocalizer` 保持 DI 中立。AtomUI 将实例注册到自身运行时服务位置供控件使用,应用可以把
同一实例注册进 Microsoft.Extensions.DependencyInjection、Splat 或自己的容器,不创建第二个 Manager。
## ILocalizer
ViewModel、服务和普通 C# 代码通过 `ILocalizer` 读取当前 Snapshot:
```csharp
public interface ILocalizer
{
string Get<TResourceKind>(TResourceKind key)
where TResourceKind : struct, Enum;
string Format<TResourceKind>(TResourceKind key, params object?[] arguments)
where TResourceKind : struct, Enum;
}
```
```csharp
var title = localizer.Get(LoginLangResourceKind.Title);
var message = localizer.Format(OrderLangResourceKind.ItemCount, count);
```
`Get` 和 `Format` 根据 enum CLR 类型定位 Catalog,通过 Generator 输出的 enum member switch 定位资源 slot。enum
成员名是构建期 Catalog Key,但运行时不得调用 `ToString()` 或 `Enum.GetName()` 构造资源路径。格式化使用
`LanguageState.FormattingCulture`,参数签名在构建期跨语言校验。
XAML 动态资源自动响应语言变化。ViewModel 如果要缓存已经本地化的字符串,必须订阅 `LanguageChanged` 并重新
计算;更推荐保存稳定资源 enum 或业务状态,在属性 getter/投影阶段调用 `ILocalizer`。
## LanguageCatalogUnitDescriptor
生成的 Catalog registration 使用字符串 Key 描述资源项,公共 descriptor 不再同时暴露数字 `Id` 和字符串
`Name`:
```csharp
public sealed record LanguageCatalogUnitDescriptor
{
public LanguageCatalogUnitDescriptor(string key, bool isFormatted = false);
public string Key { get; }
public bool IsFormatted { get; }
}
```
`Key` 必须与 Catalog enum 成员名及 XLIFF `unit id` 完全一致,使用 ordinal、区分大小写的比较语义。构造函数拒绝
空值和空白,Catalog descriptor 拒绝重复 Key;Key 与 enum symbol 的一致性由 Generator 和 Build Tasks 校验。
`LanguageCatalogUnitDescriptor.Id`、`Name` 及接受数字 ID 的构造函数不保留兼容层,避免重新形成两套身份来源。
## XAML namespace 与 Markup Extension
应用为 Catalog 所在 C# namespace 声明普通 AXAML 别名:
```xml
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:login="using:MyApplication.Features.Login.Localization"
xmlns:atom="using:AtomUI.Controls">
<TextBlock Text="{login:LoginLangResource Title}" />
<TextBlock Text="{atom:DatePickerLangResource Today}" />
</UserControl>
```
`login` 只是当前 AXAML 文件定义的 XML namespace 别名,不是 AtomUI 固定关键字。应优先使用 `login`、
`orders`、`settings` 等功能域名称,而不是含义模糊的全局 `i18n` 别名。
Generator 为 `LoginLangResourceKind` 生成 `LoginLangResourceExtension`。Avalonia 允许在 Markup Extension 使用处
省略 `Extension` 后缀,因此 `{login:LoginLangResource Title}` 在 XAML 编译期解析为
`LoginLangResourceKind.Title`,不是运行时字符串查找。
## 公共 API 中禁止的形态
```csharp
builder.UseAtomUILanguages(...);
builder.UseAtomUIZhHans();
builder.UseAtomUIJaJP();
builder.UseLanguages(packages: ...);
localizer.Get("Login.Title");
```
```xml
<TextBlock Text="{atomui:Loc DatePicker.Today}" />
```
这些形态分别造成重复命名、API 数量随语言增长、语言包运行时泄漏、弱类型字符串键或破坏既有 XAML 契约。