From 69c9834570387f2a4c700814d4f4dd8203900743 Mon Sep 17 00:00:00 2001 From: walterlv Date: Wed, 8 Jul 2026 11:53:17 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Dictionary=20=E7=94=9F?= =?UTF-8?q?=E6=88=90=E6=A8=A1=E5=BC=8F=E6=97=B6=EF=BC=8C=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E9=80=9A=E8=BF=87=20`[key]`=20=E8=AE=BF=E9=97=AE=E5=A4=9A?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E9=A1=B9=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CompiledValuesCodeGenerator.cs | 15 +-------- .../DictionaryValuesCodeGenerator.cs | 4 +-- .../InterfaceCodeGenerator.cs | 32 ++++++++++++++++--- .../LocalizationMainClassGenerator.cs | 10 +++--- 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/CompiledValuesCodeGenerator.cs b/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/CompiledValuesCodeGenerator.cs index 64e31f8..415d63d 100644 --- a/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/CompiledValuesCodeGenerator.cs +++ b/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/CompiledValuesCodeGenerator.cs @@ -34,12 +34,6 @@ public string Generate(LocalizationGeneratingModel model, string ietfLanguageTag t.AddAttribute($"""[global::System.Diagnostics.DebuggerDisplay("[{ietfLanguageTag}]")]"""); t.AddBaseTypes(allInterfaces.ToArray()); t.AddRawMembers($"public static LocalizedValues_{tagIdentifier} Instance {{ get; }} = new();"); - if (!isNestedSource) - { - t.AddRawMembers( - $"""public string IetfLanguageTag => "{ietfLanguageTag}";""", - """public string this[string key] => throw new global::System.NotSupportedException("Compiled 模式不支持基于字符串 key 的动态索引,请使用类型化属性。");"""); - } t.AddRawMembers(GenerateCompiledExplicitMembers(referenceTransformer.Tree, transformer)); }); }; @@ -70,8 +64,7 @@ public string GenerateNotifiable(LocalizationGeneratingModel model) if (!isNestedSource) { builder - .Using("DotNetCampus.Localizations") - .UsingTypeAlias("ILocalizedStringProvider", "DotNetCampus.Localizations.ILocalizedStringProvider"); + .Using("DotNetCampus.Localizations"); } var allInterfaces = new List { "ILocalizedValues" }; @@ -94,12 +87,6 @@ public string GenerateNotifiable(LocalizationGeneratingModel model) t.AddBaseTypes(allInterfaces.ToArray()); t.AddRawMembers(GenerateNotifiableCompiledFields(nonLeafNodes)); t.AddRawMembers(GenerateNotifiableCompiledConstructor(nonLeafNodes)); - if (!isNestedSource) - { - t.AddRawMembers( - "public string IetfLanguageTag => (_inner as ILocalizedStringProvider)?.IetfLanguageTag ?? \"\";", - "public string this[string key] => (_inner as ILocalizedStringProvider)?[key] ?? \"\";"); - } t.AddRawMembers(GenerateCompiledExplicitMembersForNotifiable(transformer.Tree)); t.AddRawMembers( GenerateCompiledSetInnerMethod(nonLeafNodes), diff --git a/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/DictionaryValuesCodeGenerator.cs b/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/DictionaryValuesCodeGenerator.cs index 7b58a75..ad9c1a5 100644 --- a/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/DictionaryValuesCodeGenerator.cs +++ b/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/DictionaryValuesCodeGenerator.cs @@ -78,7 +78,7 @@ private void AddImmutableValuesDeclarations(IAllowTypeDeclaration target, string target.AddTypeDeclaration("internal sealed class ImmutableLocalizedValues(ILocalizedStringProvider provider)", t => t .AddGeneratedToolAndEditorBrowsingAttributes() .AddAttribute("[global::System.Diagnostics.DebuggerDisplay(\"[{LocalizedStringProvider.IetfLanguageTag}] " + typeName + ".???\")]") - .AddBaseTypes("ILocalizedValues") + .AddBaseTypes("IDictionaryLocalizedValues") .AddRawMembers( "public ILocalizedStringProvider LocalizedStringProvider => provider;", "public string IetfLanguageTag => provider.IetfLanguageTag;", @@ -106,7 +106,7 @@ private void AddNotifiableValuesDeclarations(IAllowTypeDeclaration target, strin .WithSummaryComment("提供可通知属性变更的本地化字符串集,当语言文化切换时会发出属性变更通知。") .AddGeneratedToolAndEditorBrowsingAttributes() .AddAttribute("[global::System.Diagnostics.DebuggerDisplay(\"[{LocalizedStringProvider.IetfLanguageTag}] " + typeName + ".???\")]") - .AddBaseTypes("ILocalizedValues", "INotifyPropertyChanged") + .AddBaseTypes("IDictionaryLocalizedValues", "INotifyPropertyChanged") .AddRawMembers( "public ILocalizedStringProvider LocalizedStringProvider { get; private set; }", GenerateNotifiableConstructor("NotifiableLocalizedValues", root), diff --git a/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/InterfaceCodeGenerator.cs b/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/InterfaceCodeGenerator.cs index c287d73..214ceae 100644 --- a/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/InterfaceCodeGenerator.cs +++ b/src/DotNetCampus.Localizations.Analyzer/Generators/CodeTransforming/InterfaceCodeGenerator.cs @@ -12,9 +12,8 @@ public string Generate(LocalizationGeneratingModel model) using var builder = new SourceTextBuilder(GeneratorInfo.RootNamespace); builder .Using("DotNetCampus.Localizations") - .UsingTypeAlias("ILocalizedStringProvider", "DotNetCampus.Localizations.ILocalizedStringProvider") .UsingTypeAlias("LocalizedString", "DotNetCampus.Localizations.LocalizedString"); - AddInterfaceDeclarations(builder, model.TypeAccessibility, includeBaseInterface: true); + AddInterfaceDeclarations(builder, model, model.TypeAccessibility); return builder.ToString(); } @@ -23,20 +22,43 @@ public string GenerateNested(LocalizationGeneratingModel model) using var builder = new SourceTextBuilder(model.Namespace); builder.AddTypeDeclaration($"partial class {model.TypeName}", wrapper => { - AddInterfaceDeclarations(wrapper, "public", includeBaseInterface: false); + AddInterfaceDeclarations(wrapper, model, "public"); }); return builder.ToString(); } - private void AddInterfaceDeclarations(IAllowTypeDeclaration builder, string accessibility, bool includeBaseInterface) + private void AddInterfaceDeclarations(IAllowTypeDeclaration builder, LocalizationGeneratingModel model, string accessibility) { + // ILocalizedValues 仅承载 Lang.A.B.C 形式的强类型导航语法糖,不携带任何随生成模式变化的运行时能力。 builder.AddTypeDeclaration($"{accessibility} partial interface ILocalizedValues", t => t .WithSummaryComment("提供本地化字符串的访问接口。通过属性导航访问各分组和叶子节点的本地化值。") .AddGeneratedToolAndEditorBrowsingAttributes() - .If(includeBaseInterface, t => t.AddBaseTypes("ILocalizedStringProvider")) .AddRawMembers(GenerateInterfacePropertyMembers(transformer.Tree)) ); + // 仅 Dictionary 模式提供基于运行时字符串 key 的动态索引能力;Compiled 模式此能力应在编译期即不可用。 + if (model.GenerationMode == GenerationMode.Dictionary) + { + builder.AddTypeDeclaration($"{accessibility} partial interface IDictionaryLocalizedValues : ILocalizedValues", t => t + .WithSummaryComment("在强类型导航访问之外,额外支持通过运行时字符串 key 动态访问本地化字符串。仅 Dictionary 生成模式提供。") + .AddGeneratedToolAndEditorBrowsingAttributes() + .AddRawMembers( + """ + /// + /// 获取指定键的本地化字符串。如果字符串包含占位符,则返回值会包含形如 "{0}" "{1}" 的占位符用于格式化。 + /// + /// 要获取的本地化字符串的键。 + string this[string key] { get; } + """, + """ + /// + /// 获取符合 IETF 规范的当前语言标签。 + /// + string IetfLanguageTag { get; } + """) + ); + } + foreach (var node in transformer.EnumerateAllNonLeafDescendants(transformer.Tree)) { var nodeTypeName = node.GetFullIdentifierKey("_"); diff --git a/src/DotNetCampus.Localizations.Analyzer/Generators/LocalizationMainClassGenerator.cs b/src/DotNetCampus.Localizations.Analyzer/Generators/LocalizationMainClassGenerator.cs index 6006775..03faefd 100644 --- a/src/DotNetCampus.Localizations.Analyzer/Generators/LocalizationMainClassGenerator.cs +++ b/src/DotNetCampus.Localizations.Analyzer/Generators/LocalizationMainClassGenerator.cs @@ -142,7 +142,7 @@ private string GenerateDictionaryMainClass( /// /// 获取默认语言的本地化字符串集。 /// - public static {typePrefix}ILocalizedValues Default => _default; + public static {typePrefix}IDictionaryLocalizedValues Default => _default; """, $""" /// @@ -166,7 +166,7 @@ public static void SetCurrent(string languageTag) /// /// 语言标签。 /// 对应语言的本地化字符串集。 - public static {typePrefix}ILocalizedValues Create(string languageTag) => new {typePrefix}ImmutableLocalizedValues(CreateLocalizedStringProvider(languageTag)); + public static {typePrefix}IDictionaryLocalizedValues Create(string languageTag) => new {typePrefix}ImmutableLocalizedValues(CreateLocalizedStringProvider(languageTag)); """, $$""" private static {{typePrefix}}ILocalizedStringProvider CreateLocalizedStringProvider(string languageTag) @@ -223,13 +223,13 @@ public static void SetCurrent(string languageTag) /// /// 获取默认语言的本地化字符串集。 /// - public static {typePrefix}ILocalizedValues Default => _default; + public static {typePrefix}IDictionaryLocalizedValues Default => _default; """, $""" /// /// 获取当前语言的本地化字符串集。调用 后需重新访问此属性获取新值。 /// - public static {typePrefix}ILocalizedValues Current => _current; + public static {typePrefix}IDictionaryLocalizedValues Current => _current; """, $$""" /// @@ -247,7 +247,7 @@ public static void SetCurrent(string languageTag) /// /// 语言标签。 /// 对应语言的本地化字符串集。 - public static {typePrefix}ILocalizedValues Create(string languageTag) => GetOrCreateLocalizedValues(languageTag); + public static {typePrefix}IDictionaryLocalizedValues Create(string languageTag) => GetOrCreateLocalizedValues(languageTag); """, $$""" private static {{typePrefix}}ImmutableLocalizedValues GetOrCreateLocalizedValues(string languageTag) From b3112d622db7f39f9b37984db230c3e5bffc8163 Mon Sep 17 00:00:00 2001 From: walterlv Date: Wed, 8 Jul 2026 13:11:20 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=87=A0=E4=B8=AA?= =?UTF-8?q?=E4=BC=9A=E8=B7=9F=20MSBuild=20=20=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E4=B8=94=E5=8F=AF=E8=83=BD=E5=86=B2=E7=AA=81=E7=9A=84?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++--- .../LocalizedConfigurationAttribute.cs | 49 ------------------- 2 files changed, 6 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 6f76135..9440692 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,8 @@ namespace SampleApp; // The default language is used to generate localization interfaces, so it must be the most complete one. // The current language is optional. If not specified, the current OS UI language will be used. -// The notification is optional. If true, when the current language changes, the UI will be notified to update the localization text. -[LocalizedConfiguration(Default = "en", Current = "zh-hans", SupportsNotification = false)] +// The notification is optional. Use NotificationMode.CurrentCulturePropertyChanged to notify the UI to update the localization text when the current language changes. +[LocalizedConfiguration(Default = "en", Current = "zh-hans", NotificationMode = NotificationMode.InitOnly)] public partial class LocalizedText; ``` @@ -143,7 +143,7 @@ public sealed partial class MainPage : Page If you want to add real-time language switching support, you can modify the `LocalizedText` class as follows: ```csharp -[LocalizedConfiguration(Default = "en-US", SupportsNotification = true)] +[LocalizedConfiguration(Default = "en-US", NotificationMode = NotificationMode.CurrentCulturePropertyChanged)] public static partial class LocalizedText { public static AppBuilder UseCompiledLang(this AppBuilder appBuilder) @@ -151,7 +151,7 @@ public static partial class LocalizedText if (OperatingSystem.IsWindows()) { var language = GetUserProfileLanguage() ?? CultureInfo.CurrentUICulture.Name; - _ = SetCurrent(language); + SetCurrent(language); SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged; } else @@ -166,12 +166,12 @@ public static partial class LocalizedText { if (e.Category is UserPreferenceCategory.Locale) { - Dispatcher.UIThread.InvokeAsync(async () => + Dispatcher.UIThread.InvokeAsync(() => { var language = GetUserProfileLanguage(); if (language is not null) { - await SetCurrent(language); + SetCurrent(language); } }, DispatcherPriority.Background); } diff --git a/src/DotNetCampus.Localizations/LocalizedConfigurationAttribute.cs b/src/DotNetCampus.Localizations/LocalizedConfigurationAttribute.cs index c382aa6..3a821a0 100644 --- a/src/DotNetCampus.Localizations/LocalizedConfigurationAttribute.cs +++ b/src/DotNetCampus.Localizations/LocalizedConfigurationAttribute.cs @@ -34,14 +34,6 @@ public class LocalizedConfigurationAttribute : Attribute /// public string? Current { get; init; } - /// - /// 指定开发时预览语言项文字所用的语言。指定后可在语言项的文档注释中查看此语言项的文本,方便开发时人工检查语言项的正确性。 - /// - /// - /// 一般无需指定。如果没有指定,则会使用编译时当前计算机的语言文化。 - /// - public string? Preview { get; init; } - /// /// 指定是否确保所有语言文件中的键都是一致的。默认值为 false,不执行检查。如果是 true,则会在生成代码时检查所有语言文件中的键是否一致,如果不相同则会报错。 /// @@ -62,26 +54,6 @@ public class LocalizedConfigurationAttribute : Attribute /// public DependencyMode DependencyMode { get; init; } - /// - /// 指定本地化文件的格式。 - /// - public LocalizationFileFormat FileFormat { get; init; } - - /// - /// 默认情况下,DotNetCampus.SourceLocalizations 会自动在项目中寻找看起来像本地化文件的文件(.toml/.yaml/.yml,且文件名或文件夹名中包含语言文化名称)。
- /// 但如果多语言文件在项目之外,或你不希望项目中某些看起来像本地化文件的文件被视作最终的本地化文件,可以通过此属性来指定本地化文件的搜索正则表达式。
- /// 为此,你需要做这些事情: - /// - /// 修改项目文件(csproj)或其他 MSBuild 属性文件(.props/.targets),确保在时机 DotNetCampusGenerateLocalizations 之前,修改 LocalizationFile 集合,使其包含所有你希望被视作本地化文件的文件。 - /// 如果保持此属性为 ,那么上一步中的所有文件都将成为目标本地化文件;而指定此属性会过滤这些文件,仅包含符合此属性指定模式的文件。(指定此属性不会导致引入比上一步中更多的文件。) - /// - /// 此正则表达式将匹配路径,包含其所在的文件夹路径和文件名(包含扩展名)。 - ///
- /// - /// 如果项目中只存在一套多语言机制,通常不需要指定此属性,直接修改 .csproj/.props/.targets 中的 LocalizationFile 集合即可。 - /// - public string? LocalizationFileRegex { get; init; } - /// /// 是否支持在修改当前语言时,发出属性变更通知,可用于数据绑定。 /// @@ -157,24 +129,3 @@ public enum DependencyMode /// NestedSource, } - -/// -/// 指定本地化文件的格式。 -/// -public enum LocalizationFileFormat -{ - /// - /// 根据文件的扩展名决定文件格式。 - /// - AutoDetect, - - /// - /// 视目标文件为 TOML 格式。 - /// - Toml, - - /// - /// 视目标文件为 YAML 格式。 - /// - Yaml, -}