Skip to content

Commit 5a79b8d

Browse files
committed
动态更新Provider
1 parent cb944d6 commit 5a79b8d

5 files changed

Lines changed: 325 additions & 0 deletions

File tree

README.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ A lightweight, extensible, and pluggable internationalization library with hot-r
1313
- 🌍 **Multi-language Support** - Support for any number of languages
1414
- 🔄 **Hot Reload** - Dynamically switch languages at runtime without restart
1515
- 🔌 **Pluggable Architecture** - Support for custom data source providers
16+
- 🧩 **Plugin Support** - Dynamic provider registration/unregistration for plugin scenarios
1617
- 📦 **JSON Support** - Built-in JSON localization file support (flat and nested formats)
1718
- 📄 **RESX Support** - Built-in RESX resource file support
1819
- 🎯 **XAML Friendly** - Provides clean XAML markup extensions
@@ -481,6 +482,7 @@ Core culture service interface:
481482
| `RegisterProvider(ILocalizationProvider provider)` | Registers a localization provider |
482483
| `UnregisterProvider(string providerName)` | Unregisters a localization provider by name |
483484
| `CultureChanged` | Culture changed event |
485+
| `ProvidersChanged` | Provider registered/unregistered event |
484486

485487
### ILocalizationProvider
486488

@@ -548,6 +550,151 @@ public class DatabaseLocalizationProvider : ILocalizationProvider
548550
}
549551
```
550552

553+
## Plugin Integration
554+
555+
DynamicLocalization supports dynamic provider registration/unregistration, making it ideal for plugin architectures.
556+
557+
### Plugin Localization Setup
558+
559+
```csharp
560+
public class PluginLocalizationProvider : ILocalizationProvider
561+
{
562+
private readonly Dictionary<string, Dictionary<string, string>> _cache = new();
563+
564+
public string Name => "MyPlugin"; // Use a unique name to avoid conflicts
565+
566+
public PluginLocalizationProvider()
567+
{
568+
LoadFromEmbeddedResources();
569+
}
570+
571+
private void LoadFromEmbeddedResources()
572+
{
573+
var assembly = typeof(PluginLocalizationProvider).Assembly;
574+
var resourceNames = assembly.GetManifestResourceNames();
575+
576+
foreach (var name in resourceNames)
577+
{
578+
if (!name.Contains(".Localization.") || !name.EndsWith(".json"))
579+
continue;
580+
581+
var cultureName = ExtractCultureName(name);
582+
if (string.IsNullOrEmpty(cultureName)) continue;
583+
584+
using var stream = assembly.GetManifestResourceStream(name);
585+
if (stream == null) continue;
586+
587+
using var reader = new StreamReader(stream);
588+
var json = reader.ReadToEnd();
589+
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
590+
if (dict != null)
591+
{
592+
_cache[cultureName] = dict;
593+
}
594+
}
595+
}
596+
597+
// ... implement other interface methods
598+
}
599+
```
600+
601+
### Plugin Lifecycle Management
602+
603+
```csharp
604+
public class PluginEntryPoint
605+
{
606+
private readonly ICultureService _cultureService;
607+
private readonly PluginLocalizationProvider _provider;
608+
609+
public PluginEntryPoint(ICultureService cultureService)
610+
{
611+
_cultureService = cultureService;
612+
_provider = new PluginLocalizationProvider();
613+
}
614+
615+
public void Initialize()
616+
{
617+
_cultureService.RegisterProvider(_provider);
618+
// UI automatically refreshes to include plugin translations
619+
}
620+
621+
public void Unload()
622+
{
623+
_cultureService.UnregisterProvider(_provider.Name);
624+
// UI automatically refreshes to remove plugin translations
625+
}
626+
}
627+
```
628+
629+
### Key Naming Convention
630+
631+
Use a prefix to avoid key conflicts with the main application or other plugins:
632+
633+
| Format | Example |
634+
|--------|---------|
635+
| `{PluginName}.{Feature}.{Item}` | `MyPlugin.Menu.Open` |
636+
| `{PluginName}.{Item}` | `MyPlugin.Title` |
637+
638+
## Extending Providers
639+
640+
Both `JsonLocalizationProvider` and `ResxLocalizationProvider` are designed for inheritance. Key methods are `protected virtual` for easy customization.
641+
642+
### Extending JsonLocalizationProvider
643+
644+
```csharp
645+
public class CustomJsonProvider : JsonLocalizationProvider
646+
{
647+
public override string Name => "CustomJson"; // Custom provider name
648+
649+
protected override string? ExtractCultureName(string resourceName)
650+
{
651+
// Custom resource name parsing logic
652+
return base.ExtractCultureName(resourceName);
653+
}
654+
655+
protected override Dictionary<string, string>? ParseJsonToFlatDictionary(string json)
656+
{
657+
// Custom JSON parsing (e.g., support YAML or other formats)
658+
return base.ParseJsonToFlatDictionary(json);
659+
}
660+
}
661+
```
662+
663+
### Extending ResxLocalizationProvider
664+
665+
```csharp
666+
public class CustomResxProvider : ResxLocalizationProvider
667+
{
668+
public override string Name => "CustomResx";
669+
670+
protected override void DetectAvailableCultures()
671+
{
672+
// Custom culture detection logic
673+
base.DetectAvailableCultures();
674+
}
675+
}
676+
```
677+
678+
### Overridable Members
679+
680+
**JsonLocalizationProvider:**
681+
| Member | Description |
682+
|--------|-------------|
683+
| `Name` | Provider identifier |
684+
| `LoadAll()` | Load all resources |
685+
| `LoadFromEmbeddedResources()` | Load from embedded resources |
686+
| `LoadFromFiles()` | Load from file system |
687+
| `ExtractCultureName()` | Extract culture from resource name |
688+
| `ParseJsonToFlatDictionary()` | Parse JSON to dictionary |
689+
| `FlattenJsonObject()` | Flatten nested JSON |
690+
| `TryGetFromCulture()` | Get string from specific culture |
691+
692+
**ResxLocalizationProvider:**
693+
| Member | Description |
694+
|--------|-------------|
695+
| `Name` | Provider identifier |
696+
| `DetectAvailableCultures()` | Detect available cultures |
697+
551698
## Architecture
552699

553700
```

README.zh-CN.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- 🌍 **多语言支持** - 支持任意数量的语言
1414
- 🔄 **热重载** - 运行时动态切换语言,无需重启
1515
- 🔌 **可插拔架构** - 支持自定义数据源提供者
16+
- 🧩 **插件支持** - 动态注册/注销提供者,支持插件场景
1617
- 📦 **JSON 支持** - 内置 JSON 本地化文件支持(扁平格式和嵌套格式)
1718
- 📄 **RESX 支持** - 内置 RESX 资源文件支持
1819
- 🎯 **XAML 友好** - 提供简洁的 XAML 标记扩展
@@ -420,6 +421,7 @@ JSON 提供者支持两种格式:
420421
| `RegisterProvider(ILocalizationProvider provider)` | 注册本地化提供者 |
421422
| `UnregisterProvider(string providerName)` | 通过名称注销本地化提供者 |
422423
| `CultureChanged` | 文化更改事件 |
424+
| `ProvidersChanged` | 提供者注册/注销事件 |
423425

424426
### ILocalizationProvider
425427

@@ -487,6 +489,151 @@ public class DatabaseLocalizationProvider : ILocalizationProvider
487489
}
488490
```
489491

492+
## 插件集成
493+
494+
DynamicLocalization 支持动态注册/注销提供者,非常适合插件架构。
495+
496+
### 插件本地化设置
497+
498+
```csharp
499+
public class PluginLocalizationProvider : ILocalizationProvider
500+
{
501+
private readonly Dictionary<string, Dictionary<string, string>> _cache = new();
502+
503+
public string Name => "MyPlugin"; // 使用唯一名称避免冲突
504+
505+
public PluginLocalizationProvider()
506+
{
507+
LoadFromEmbeddedResources();
508+
}
509+
510+
private void LoadFromEmbeddedResources()
511+
{
512+
var assembly = typeof(PluginLocalizationProvider).Assembly;
513+
var resourceNames = assembly.GetManifestResourceNames();
514+
515+
foreach (var name in resourceNames)
516+
{
517+
if (!name.Contains(".Localization.") || !name.EndsWith(".json"))
518+
continue;
519+
520+
var cultureName = ExtractCultureName(name);
521+
if (string.IsNullOrEmpty(cultureName)) continue;
522+
523+
using var stream = assembly.GetManifestResourceStream(name);
524+
if (stream == null) continue;
525+
526+
using var reader = new StreamReader(stream);
527+
var json = reader.ReadToEnd();
528+
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
529+
if (dict != null)
530+
{
531+
_cache[cultureName] = dict;
532+
}
533+
}
534+
}
535+
536+
// ... 实现其他接口方法
537+
}
538+
```
539+
540+
### 插件生命周期管理
541+
542+
```csharp
543+
public class PluginEntryPoint
544+
{
545+
private readonly ICultureService _cultureService;
546+
private readonly PluginLocalizationProvider _provider;
547+
548+
public PluginEntryPoint(ICultureService cultureService)
549+
{
550+
_cultureService = cultureService;
551+
_provider = new PluginLocalizationProvider();
552+
}
553+
554+
public void Initialize()
555+
{
556+
_cultureService.RegisterProvider(_provider);
557+
// UI 自动刷新,显示插件翻译
558+
}
559+
560+
public void Unload()
561+
{
562+
_cultureService.UnregisterProvider(_provider.Name);
563+
// UI 自动刷新,移除插件翻译
564+
}
565+
}
566+
```
567+
568+
### 键命名规范
569+
570+
使用前缀避免与主程序或其他插件的键冲突:
571+
572+
| 格式 | 示例 |
573+
|--------|---------|
574+
| `{插件名}.{功能}.{项}` | `MyPlugin.Menu.Open` |
575+
| `{插件名}.{项}` | `MyPlugin.Title` |
576+
577+
## 扩展提供者
578+
579+
`JsonLocalizationProvider``ResxLocalizationProvider` 都设计为可继承的。关键方法使用 `protected virtual` 修饰,便于自定义。
580+
581+
### 扩展 JsonLocalizationProvider
582+
583+
```csharp
584+
public class CustomJsonProvider : JsonLocalizationProvider
585+
{
586+
public override string Name => "CustomJson"; // 自定义提供者名称
587+
588+
protected override string? ExtractCultureName(string resourceName)
589+
{
590+
// 自定义资源名称解析逻辑
591+
return base.ExtractCultureName(resourceName);
592+
}
593+
594+
protected override Dictionary<string, string>? ParseJsonToFlatDictionary(string json)
595+
{
596+
// 自定义 JSON 解析(如支持 YAML 或其他格式)
597+
return base.ParseJsonToFlatDictionary(json);
598+
}
599+
}
600+
```
601+
602+
### 扩展 ResxLocalizationProvider
603+
604+
```csharp
605+
public class CustomResxProvider : ResxLocalizationProvider
606+
{
607+
public override string Name => "CustomResx";
608+
609+
protected override void DetectAvailableCultures()
610+
{
611+
// 自定义文化检测逻辑
612+
base.DetectAvailableCultures();
613+
}
614+
}
615+
```
616+
617+
### 可重写成员
618+
619+
**JsonLocalizationProvider:**
620+
| 成员 | 描述 |
621+
|--------|-------------|
622+
| `Name` | 提供者标识符 |
623+
| `LoadAll()` | 加载所有资源 |
624+
| `LoadFromEmbeddedResources()` | 从嵌入资源加载 |
625+
| `LoadFromFiles()` | 从文件系统加载 |
626+
| `ExtractCultureName()` | 从资源名提取文化 |
627+
| `ParseJsonToFlatDictionary()` | 解析 JSON 为字典 |
628+
| `FlattenJsonObject()` | 扁平化嵌套 JSON |
629+
| `TryGetFromCulture()` | 从指定文化获取字符串 |
630+
631+
**ResxLocalizationProvider:**
632+
| 成员 | 描述 |
633+
|--------|-------------|
634+
| `Name` | 提供者标识符 |
635+
| `DetectAvailableCultures()` | 检测可用文化 |
636+
490637
## 架构
491638

492639
```

src/DynamicLocalization.Core/CultureService.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public class CultureService : ICultureService, INotifyPropertyChanged
1818

1919
public event EventHandler<CultureChangedEventArgs>? CultureChanged;
2020

21+
public event EventHandler? ProvidersChanged;
22+
2123
public CultureInfo CurrentCulture
2224
{
2325
get => _currentCultureField;
@@ -105,12 +107,16 @@ public void RegisterProvider(ILocalizationProvider provider)
105107
{
106108
_providers.Add(provider);
107109
_availableCultures = null;
110+
OnPropertyChanged(nameof(AvailableCultures));
111+
OnProvidersChanged();
108112
}
109113

110114
public void UnregisterProvider(string providerName)
111115
{
112116
_providers.RemoveAll(p => p.Name == providerName);
113117
_availableCultures = null;
118+
OnPropertyChanged(nameof(AvailableCultures));
119+
OnProvidersChanged();
114120
}
115121

116122
public void SetCulture(string cultureName, bool includeFormatting = false)
@@ -131,6 +137,11 @@ protected virtual void OnCultureChanged(CultureInfo newCulture, CultureInfo oldC
131137
CultureChanged?.Invoke(this, new CultureChangedEventArgs(newCulture, oldCulture));
132138
}
133139

140+
protected virtual void OnProvidersChanged()
141+
{
142+
ProvidersChanged?.Invoke(this, EventArgs.Empty);
143+
}
144+
134145
protected virtual void OnPropertyChanged(string propertyName)
135146
{
136147
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

src/DynamicLocalization.Core/ICultureService.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,13 @@ public interface ICultureService : INotifyPropertyChanged
150150
/// Occurs when the current culture changes.
151151
/// </summary>
152152
event EventHandler<CultureChangedEventArgs>? CultureChanged;
153+
154+
/// <summary>
155+
/// Occurs when a localization provider is registered or unregistered.
156+
/// </summary>
157+
/// <remarks>
158+
/// This event is raised when providers are dynamically added or removed,
159+
/// allowing UI elements to refresh their localized values.
160+
/// </remarks>
161+
event EventHandler? ProvidersChanged;
153162
}

0 commit comments

Comments
 (0)