Skip to content

feat(core): 添加本地化系统支持多语言功能#115

Merged
GeWuYou merged 8 commits into
mainfrom
feat/localization-system
Mar 19, 2026
Merged

feat(core): 添加本地化系统支持多语言功能#115
GeWuYou merged 8 commits into
mainfrom
feat/localization-system

Conversation

@GeWuYou
Copy link
Copy Markdown
Owner

@GeWuYou GeWuYou commented Mar 18, 2026

  • 实现 ILocalizationManager 接口及 LocalizationManager 管理器
  • 添加 ILocalizationTable 和 ILocalizationString 接口及其实现
  • 创建 LocalizationConfig 配置类用于管理本地化行为
  • 实现 ConditionalFormatter 和 PluralFormatter 内置格式化器
  • 添加本地化文档包括 API 参考和使用指南
  • 集成本地化系统到核心框架架构中

Summary by Sourcery

引入一个核心本地化系统,提供多语言支持,包括配置管理、文化区域感知的字符串获取,以及与现有系统架构的集成。

New Features:

  • 添加 ILocalizationManagerILocalizationTableILocalizationStringILocalizationFormatter 抽象及其核心实现,用于管理本地化文本和本地化表。
  • 提供 LocalizationConfig,用于配置默认/回退语言、资源路径,以及诸如热重载与校验等运行时行为。
  • 实现内置的 ConditionalFormatterPluralFormatter,以支持条件和复数感知的本地化字符串。
  • 在核心架构中公开本地化的使用方式,包括语言切换、变量插值,以及语言变更通知。

Enhancements:

  • 将本地化模块集成到核心框架命名空间和架构中,作为一等系统组件。

Documentation:

  • 为本地化系统新增完整的中文文档,包括 API 参考、配置说明、文件结构、使用模式和最佳实践。

Tests:

  • 新增单元测试和集成测试,覆盖本地化表行为、语言切换、变量格式化和语言变更回调等场景。
Original summary in English

Summary by Sourcery

Introduce a core localization system providing multi-language support, including configuration, culture-aware string retrieval, and integration with the existing system architecture.

New Features:

  • Add ILocalizationManager, ILocalizationTable, ILocalizationString, and ILocalizationFormatter abstractions and their core implementations for managing localized text and tables.
  • Provide LocalizationConfig for configuring default/fallback languages, resource paths, and runtime behavior such as hot reload and validation.
  • Implement built-in ConditionalFormatter and PluralFormatter to support conditional and plural-aware localized strings.
  • Expose localization usage within the core architecture, including language switching, variable interpolation, and language change notifications.

Enhancements:

  • Integrate the localization module into the core framework namespace and architecture as a first-class system component.

Documentation:

  • Add comprehensive Chinese documentation for the localization system, including API reference, configuration, file structure, usage patterns, and best practices.

Tests:

  • Add unit and integration tests covering localization table behaviors, language switching, variable formatting, and language change callbacks.

- 实现 ILocalizationManager 接口及 LocalizationManager 管理器
- 添加 ILocalizationTable 和 ILocalizationString 接口及其实现
- 创建 LocalizationConfig 配置类用于管理本地化行为
- 实现 ConditionalFormatter 和 PluralFormatter 内置格式化器
- 添加本地化文档包括 API 参考和使用指南
- 集成本地化系统到核心框架架构中
@deepsource-io
Copy link
Copy Markdown

deepsource-io Bot commented Mar 18, 2026

DeepSource Code Review

We reviewed changes in aee13c3...9ca28a4 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
C# Mar 19, 2026 5:15a.m. Review ↗
Secrets Mar 19, 2026 5:15a.m. Review ↗

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 18, 2026

审阅者指南

在核心框架中实现了完整的本地化子系统,包括抽象层、具体的管理器/表/字符串实现及格式化器支持,同时增加了集成测试,并更新了完整的中文文档和 API 参考。

格式化带变量的本地化字符串的时序图

sequenceDiagram
    actor GameCode
    participant LocalizationManager
    participant LocalizationString
    participant LocalizationTable
    participant Formatter as ILocalizationFormatter

    GameCode->>LocalizationManager: GetString(table, key)
    LocalizationManager-->>GameCode: LocalizationString instance

    GameCode->>LocalizationString: WithVariable(name, value)
    LocalizationString-->>GameCode: LocalizationString

    GameCode->>LocalizationString: Format()
    LocalizationString->>LocalizationString: GetRaw()
    LocalizationString->>LocalizationManager: TryGetText(table, key, out text)
    alt key exists
        LocalizationManager->>LocalizationTable: GetRawText(key)
        LocalizationTable-->>LocalizationManager: rawText
        LocalizationManager-->>LocalizationString: true, rawText
        LocalizationString->>LocalizationString: FormatString(rawText, variables, manager)
        loop each placeholder
            LocalizationString->>LocalizationManager: GetFormatter(formatterName)
            LocalizationManager-->>LocalizationString: Formatter or null
            alt formatter found and TryFormat succeeds
                LocalizationString->>Formatter: TryFormat(format, value, CurrentCulture, out result)
                Formatter-->>LocalizationString: true, result
            else no formatter or failure
                LocalizationString-->>LocalizationString: fallback ToString with CurrentCulture
            end
        end
        LocalizationString-->>GameCode: formattedText
    else key missing
        LocalizationManager-->>LocalizationString: false, empty
        LocalizationString-->>GameCode: "[table.key]"
    end
Loading

切换语言并通知监听者的时序图

sequenceDiagram
    actor GameCode
    participant LocalizationManager
    participant FileSystem as FileSystem
    participant Callback as LanguageChangeCallback

    GameCode->>LocalizationManager: SetLanguage(languageCode)
    alt languageCode is null or empty
        LocalizationManager-->>GameCode: ArgumentNullException
    else same as current language
        LocalizationManager-->>GameCode: return
    else new language
        LocalizationManager->>LocalizationManager: LoadLanguage(languageCode)
        alt language already loaded
            LocalizationManager-->>LocalizationManager: return
        else not loaded
            LocalizationManager->>LocalizationManager: LoadLanguage(FallbackLanguage)
            LocalizationManager->>FileSystem: Read localizationPath/languageCode/*.json
            FileSystem-->>LocalizationManager: JSON tables
            LocalizationManager->>LocalizationManager: Create LocalizationTable per file
            LocalizationManager-->>LocalizationManager: cache tables
        end
        LocalizationManager->>LocalizationManager: GetCultureInfo(languageCode)
        LocalizationManager-->>LocalizationManager: update CurrentLanguage, CurrentCulture
        LocalizationManager->>LocalizationManager: TriggerLanguageChange()
        loop each subscribed callback
            LocalizationManager->>Callback: Invoke(CurrentLanguage)
            Callback-->>LocalizationManager: return
        end
        LocalizationManager-->>GameCode: return
    end
Loading

本地化核心抽象的类图

classDiagram
    direction LR

    class ISystem {
    }

    class ILocalizationManager {
        <<interface>>
        +string CurrentLanguage
        +CultureInfo CurrentCulture
        +IReadOnlyList~string~ AvailableLanguages
        +void SetLanguage(string languageCode)
        +ILocalizationTable GetTable(string tableName)
        +string GetText(string table, string key)
        +ILocalizationString GetString(string table, string key)
        +bool TryGetText(string table, string key, out string text)
        +void RegisterFormatter(string name, ILocalizationFormatter formatter)
        +ILocalizationFormatter GetFormatter(string name)
        +void SubscribeToLanguageChange(Action~string~ callback)
        +void UnsubscribeFromLanguageChange(Action~string~ callback)
    }

    class ILocalizationTable {
        <<interface>>
        +string Name
        +string Language
        +ILocalizationTable Fallback
        +string GetRawText(string key)
        +bool ContainsKey(string key)
        +IEnumerable~string~ GetKeys()
        +void Merge(IReadOnlyDictionary~string, string~ overrides)
    }

    class ILocalizationString {
        <<interface>>
        +string Table
        +string Key
        +ILocalizationString WithVariable(string name, object value)
        +ILocalizationString WithVariables(params (string name, object value)[] variables)
        +string Format()
        +string GetRaw()
        +bool Exists()
    }

    class ILocalizationFormatter {
        <<interface>>
        +string Name
        +bool TryFormat(string format, object value, IFormatProvider provider, out string result)
    }

    class LocalizationConfig {
        +string DefaultLanguage
        +string FallbackLanguage
        +string LocalizationPath
        +string OverridePath
        +bool EnableHotReload
        +bool ValidateOnLoad
    }

    class LocalizationException {
        +LocalizationException()
        +LocalizationException(string message)
        +LocalizationException(string message, Exception innerException)
    }

    class LocalizationKeyNotFoundException {
        +string TableName
        +string Key
        +LocalizationKeyNotFoundException(string tableName, string key)
    }

    class LocalizationTableNotFoundException {
        +string TableName
        +LocalizationTableNotFoundException(string tableName)
    }

    ILocalizationManager ..|> ISystem
    LocalizationKeyNotFoundException --|> LocalizationException
    LocalizationTableNotFoundException --|> LocalizationException
    ILocalizationManager --> ILocalizationTable
    ILocalizationManager --> ILocalizationString
    ILocalizationManager --> ILocalizationFormatter
    ILocalizationTable --> ILocalizationTable : Fallback
Loading

本地化具体实现的类图

classDiagram
    direction LR

    class AbstractSystem {
        <<abstract>>
        #void OnInit()
        #void OnDestroy()
    }

    class LocalizationManager {
        -LocalizationConfig _config
        -Dictionary~string, ILocalizationFormatter~ _formatters
        -List~Action~string~~ _languageChangeCallbacks
        -Dictionary~string, Dictionary~string, ILocalizationTable~~ _tables
        -List~string~ _availableLanguages
        -CultureInfo _currentCulture
        -string _currentLanguage
        +LocalizationManager(LocalizationConfig config)
        +string CurrentLanguage
        +CultureInfo CurrentCulture
        +IReadOnlyList~string~ AvailableLanguages
        +void SetLanguage(string languageCode)
        +ILocalizationTable GetTable(string tableName)
        +string GetText(string table, string key)
        +ILocalizationString GetString(string table, string key)
        +bool TryGetText(string table, string key, out string text)
        +void RegisterFormatter(string name, ILocalizationFormatter formatter)
        +ILocalizationFormatter GetFormatter(string name)
        +void SubscribeToLanguageChange(Action~string~ callback)
        +void UnsubscribeFromLanguageChange(Action~string~ callback)
        #void OnInit()
        #void OnDestroy()
        -void ScanAvailableLanguages()
        -void LoadLanguage(string languageCode)
        -Dictionary~string, string~ LoadJsonFile(string filePath)
        -CultureInfo GetCultureInfo(string languageCode)
        -void TriggerLanguageChange()
    }

    class LocalizationTable {
        -Dictionary~string, string~ _data
        -Dictionary~string, string~ _overrides
        +LocalizationTable(string name, string language, IReadOnlyDictionary~string, string~ data, ILocalizationTable fallback)
        +string Name
        +string Language
        +ILocalizationTable Fallback
        +string GetRawText(string key)
        +bool ContainsKey(string key)
        +IEnumerable~string~ GetKeys()
        +void Merge(IReadOnlyDictionary~string, string~ overrides)
    }

    class LocalizationString {
        -ILocalizationManager _manager
        -Dictionary~string, object~ _variables
        +LocalizationString(ILocalizationManager manager, string table, string key)
        +string Table
        +string Key
        +ILocalizationString WithVariable(string name, object value)
        +ILocalizationString WithVariables(params (string name, object value)[] variables)
        +string Format()
        +string GetRaw()
        +bool Exists()
        -string FormatString(string template, Dictionary~string, object~ variables, ILocalizationManager manager)
        -ILocalizationFormatter GetFormatter(ILocalizationManager manager, string name)
    }

    class ConditionalFormatter {
        +string Name
        +bool TryFormat(string format, object value, IFormatProvider provider, out string result)
    }

    class PluralFormatter {
        +string Name
        +bool TryFormat(string format, object value, IFormatProvider provider, out string result)
    }

    AbstractSystem <|-- LocalizationManager
    ILocalizationManager <|.. LocalizationManager
    ILocalizationTable <|.. LocalizationTable
    ILocalizationString <|.. LocalizationString
    ILocalizationFormatter <|.. ConditionalFormatter
    ILocalizationFormatter <|.. PluralFormatter

    LocalizationManager --> LocalizationConfig
    LocalizationManager --> LocalizationTable
    LocalizationManager --> LocalizationString
    LocalizationManager --> ILocalizationFormatter
    LocalizationString --> ILocalizationManager
    LocalizationTable --> LocalizationKeyNotFoundException
    LocalizationManager --> LocalizationTableNotFoundException
Loading

文件级变更

Change Details Files
为管理器、表、字符串、格式化器、配置以及错误类型引入核心本地化抽象。
  • 定义 ILocalizationManager,提供语言管理、表/文本访问、格式化器注册以及语言变更订阅等 API。
  • 添加 ILocalizationTable、ILocalizationString 和 ILocalizationFormatter 接口,用于表示表、支持变量的可格式化字符串以及可插拔格式化器。
  • 添加 LocalizationConfig,用于配置默认/回退语言、资源/覆盖路径,以及校验/热重载开关。
  • 添加 LocalizationException 以及更具体的 LocalizationKeyNotFoundException 和 LocalizationTableNotFoundException 错误类型,用于处理资源缺失。
GFramework.Core.Abstractions/Localization/ILocalizationManager.cs
GFramework.Core.Abstractions/Localization/ILocalizationTable.cs
GFramework.Core.Abstractions/Localization/ILocalizationString.cs
GFramework.Core.Abstractions/Localization/ILocalizationFormatter.cs
GFramework.Core.Abstractions/Localization/LocalizationConfig.cs
GFramework.Core.Abstractions/Localization/LocalizationException.cs
GFramework.Core.Abstractions/Localization/LocalizationKeyNotFoundException.cs
GFramework.Core.Abstractions/Localization/LocalizationTableNotFoundException.cs
添加本地化运行时的具体实现,包括语言扫描/加载、带回退和覆盖的表管理,以及支持变量的字符串格式化。
  • 实现 LocalizationManager 作为一个 AbstractSystem,用于扫描语言目录、按语言加载 JSON 表、管理当前区域信息,并暴露 AvailableLanguages。
  • 实现 LocalizationTable,用于存储基础与覆盖字典、支持回退到其他表,并提供带优先级规则的键查找/合并语义。
  • 实现 LocalizationString,将 (table,key) 绑定到管理器,支持变量注册,并通过可选的已注册格式化器和区域性敏感的数字格式执行模板替换。
  • 在 LocalizationManager 中实现内部辅助方法,用于安全加载 JSON、将自定义代码映射到 CultureInfo,以及安全通知语言变更订阅者。
GFramework.Core/Localization/LocalizationManager.cs
GFramework.Core/Localization/LocalizationTable.cs
GFramework.Core/Localization/LocalizationString.cs
提供用于条件和复数文本的内置本地化格式化器。
  • 添加 ConditionalFormatter("if"),根据类似布尔值的输入在两个文本分支之间进行选择。
  • 添加 PluralFormatter("plural"),基于数值使用简单的 == 1 规则选择单数或复数形式。
  • 通过占位符语法 {name:formatter:args} 将格式化器接入 LocalizationString,并通过 ILocalizationManager 进行格式化器查找。
GFramework.Core/Localization/Formatters/ConditionalFormatter.cs
GFramework.Core/Localization/Formatters/PluralFormatter.cs
GFramework.Core/Localization/LocalizationString.cs
GFramework.Core.Abstractions/Localization/ILocalizationFormatter.cs
GFramework.Core.Abstractions/Localization/ILocalizationManager.cs
添加测试和中文文档,用于验证并描述本地化系统,并将其集成到核心文档导航中。
  • 创建 LocalizationTableTests,用于验证原始文本查找、回退行为、键存在性以及覆盖合并的优先级。
  • 创建 LocalizationIntegrationTests,用于端到端验证:基于文件系统路径的初始化、变量格式化、语言切换、回调以及可用语言枚举。
  • 扩展 zh-CN API 参考,列出本地化相关类型,并为 ILocalizationManager、ILocalizationString 和 LocalizationConfig 的用法添加接口示例。
  • 新增专门的 zh-CN 核心本地化指南,覆盖概念、文件布局、JSON 模式、变量/格式化器用法、回退/覆盖机制、错误处理和最佳实践,并在核心索引表中添加导航条目。
GFramework.Core.Tests/Localization/LocalizationTableTests.cs
GFramework.Core.Tests/Localization/LocalizationIntegrationTests.cs
docs/zh-CN/api-reference/index.md
docs/zh-CN/core/index.md
docs/zh-CN/core/localization.md

技巧与命令

与 Sourcery 交互

  • 触发新的审查: 在 Pull Request 上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 在某条审查评论下请求 Sourcery 从该评论创建一个 issue。你也可以在审查评论中回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题的任意位置写入 @sourcery-ai 以随时生成标题。你也可以在 Pull Request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 Pull Request 摘要: 在 Pull Request 正文任意位置写入 @sourcery-ai summary,即可在对应位置生成 PR 摘要。你也可以在 Pull Request 中评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审阅者指南: 在 Pull Request 中评论 @sourcery-ai guide,即可随时(重新)生成审阅者指南。
  • 一键解决所有 Sourcery 评论: 在 Pull Request 中评论 @sourcery-ai resolve 来解决所有 Sourcery 评论。如果你已经处理完所有评论且不希望再看到它们,这会非常有用。
  • 全部忽略 Sourcery 审查: 在 Pull Request 中评论 @sourcery-ai dismiss 来忽略所有已有的 Sourcery 审查。特别适合你希望从一个全新的审查开始——别忘了再评论 @sourcery-ai review 来触发新的审查!

自定义你的体验

访问你的控制面板 可以:

  • 启用或禁用审查功能,例如 Sourcery 生成的 Pull Request 摘要、审阅者指南等。
  • 修改审查使用的语言。
  • 添加、移除或编辑自定义审查指令。
  • 调整其他审查相关设置。

获取帮助

Original review guide in English

Reviewer's Guide

Implements a full localization subsystem in the core framework, including abstractions, concrete manager/table/string implementations with formatter support, integration tests, and comprehensive Chinese documentation and API reference updates.

Sequence diagram for formatting a localized string with variables

sequenceDiagram
    actor GameCode
    participant LocalizationManager
    participant LocalizationString
    participant LocalizationTable
    participant Formatter as ILocalizationFormatter

    GameCode->>LocalizationManager: GetString(table, key)
    LocalizationManager-->>GameCode: LocalizationString instance

    GameCode->>LocalizationString: WithVariable(name, value)
    LocalizationString-->>GameCode: LocalizationString

    GameCode->>LocalizationString: Format()
    LocalizationString->>LocalizationString: GetRaw()
    LocalizationString->>LocalizationManager: TryGetText(table, key, out text)
    alt key exists
        LocalizationManager->>LocalizationTable: GetRawText(key)
        LocalizationTable-->>LocalizationManager: rawText
        LocalizationManager-->>LocalizationString: true, rawText
        LocalizationString->>LocalizationString: FormatString(rawText, variables, manager)
        loop each placeholder
            LocalizationString->>LocalizationManager: GetFormatter(formatterName)
            LocalizationManager-->>LocalizationString: Formatter or null
            alt formatter found and TryFormat succeeds
                LocalizationString->>Formatter: TryFormat(format, value, CurrentCulture, out result)
                Formatter-->>LocalizationString: true, result
            else no formatter or failure
                LocalizationString-->>LocalizationString: fallback ToString with CurrentCulture
            end
        end
        LocalizationString-->>GameCode: formattedText
    else key missing
        LocalizationManager-->>LocalizationString: false, empty
        LocalizationString-->>GameCode: "[table.key]"
    end
Loading

Sequence diagram for switching language and notifying listeners

sequenceDiagram
    actor GameCode
    participant LocalizationManager
    participant FileSystem as FileSystem
    participant Callback as LanguageChangeCallback

    GameCode->>LocalizationManager: SetLanguage(languageCode)
    alt languageCode is null or empty
        LocalizationManager-->>GameCode: ArgumentNullException
    else same as current language
        LocalizationManager-->>GameCode: return
    else new language
        LocalizationManager->>LocalizationManager: LoadLanguage(languageCode)
        alt language already loaded
            LocalizationManager-->>LocalizationManager: return
        else not loaded
            LocalizationManager->>LocalizationManager: LoadLanguage(FallbackLanguage)
            LocalizationManager->>FileSystem: Read localizationPath/languageCode/*.json
            FileSystem-->>LocalizationManager: JSON tables
            LocalizationManager->>LocalizationManager: Create LocalizationTable per file
            LocalizationManager-->>LocalizationManager: cache tables
        end
        LocalizationManager->>LocalizationManager: GetCultureInfo(languageCode)
        LocalizationManager-->>LocalizationManager: update CurrentLanguage, CurrentCulture
        LocalizationManager->>LocalizationManager: TriggerLanguageChange()
        loop each subscribed callback
            LocalizationManager->>Callback: Invoke(CurrentLanguage)
            Callback-->>LocalizationManager: return
        end
        LocalizationManager-->>GameCode: return
    end
Loading

Class diagram for localization core abstractions

classDiagram
    direction LR

    class ISystem {
    }

    class ILocalizationManager {
        <<interface>>
        +string CurrentLanguage
        +CultureInfo CurrentCulture
        +IReadOnlyList~string~ AvailableLanguages
        +void SetLanguage(string languageCode)
        +ILocalizationTable GetTable(string tableName)
        +string GetText(string table, string key)
        +ILocalizationString GetString(string table, string key)
        +bool TryGetText(string table, string key, out string text)
        +void RegisterFormatter(string name, ILocalizationFormatter formatter)
        +ILocalizationFormatter GetFormatter(string name)
        +void SubscribeToLanguageChange(Action~string~ callback)
        +void UnsubscribeFromLanguageChange(Action~string~ callback)
    }

    class ILocalizationTable {
        <<interface>>
        +string Name
        +string Language
        +ILocalizationTable Fallback
        +string GetRawText(string key)
        +bool ContainsKey(string key)
        +IEnumerable~string~ GetKeys()
        +void Merge(IReadOnlyDictionary~string, string~ overrides)
    }

    class ILocalizationString {
        <<interface>>
        +string Table
        +string Key
        +ILocalizationString WithVariable(string name, object value)
        +ILocalizationString WithVariables(params (string name, object value)[] variables)
        +string Format()
        +string GetRaw()
        +bool Exists()
    }

    class ILocalizationFormatter {
        <<interface>>
        +string Name
        +bool TryFormat(string format, object value, IFormatProvider provider, out string result)
    }

    class LocalizationConfig {
        +string DefaultLanguage
        +string FallbackLanguage
        +string LocalizationPath
        +string OverridePath
        +bool EnableHotReload
        +bool ValidateOnLoad
    }

    class LocalizationException {
        +LocalizationException()
        +LocalizationException(string message)
        +LocalizationException(string message, Exception innerException)
    }

    class LocalizationKeyNotFoundException {
        +string TableName
        +string Key
        +LocalizationKeyNotFoundException(string tableName, string key)
    }

    class LocalizationTableNotFoundException {
        +string TableName
        +LocalizationTableNotFoundException(string tableName)
    }

    ILocalizationManager ..|> ISystem
    LocalizationKeyNotFoundException --|> LocalizationException
    LocalizationTableNotFoundException --|> LocalizationException
    ILocalizationManager --> ILocalizationTable
    ILocalizationManager --> ILocalizationString
    ILocalizationManager --> ILocalizationFormatter
    ILocalizationTable --> ILocalizationTable : Fallback
Loading

Class diagram for localization concrete implementations

classDiagram
    direction LR

    class AbstractSystem {
        <<abstract>>
        #void OnInit()
        #void OnDestroy()
    }

    class LocalizationManager {
        -LocalizationConfig _config
        -Dictionary~string, ILocalizationFormatter~ _formatters
        -List~Action~string~~ _languageChangeCallbacks
        -Dictionary~string, Dictionary~string, ILocalizationTable~~ _tables
        -List~string~ _availableLanguages
        -CultureInfo _currentCulture
        -string _currentLanguage
        +LocalizationManager(LocalizationConfig config)
        +string CurrentLanguage
        +CultureInfo CurrentCulture
        +IReadOnlyList~string~ AvailableLanguages
        +void SetLanguage(string languageCode)
        +ILocalizationTable GetTable(string tableName)
        +string GetText(string table, string key)
        +ILocalizationString GetString(string table, string key)
        +bool TryGetText(string table, string key, out string text)
        +void RegisterFormatter(string name, ILocalizationFormatter formatter)
        +ILocalizationFormatter GetFormatter(string name)
        +void SubscribeToLanguageChange(Action~string~ callback)
        +void UnsubscribeFromLanguageChange(Action~string~ callback)
        #void OnInit()
        #void OnDestroy()
        -void ScanAvailableLanguages()
        -void LoadLanguage(string languageCode)
        -Dictionary~string, string~ LoadJsonFile(string filePath)
        -CultureInfo GetCultureInfo(string languageCode)
        -void TriggerLanguageChange()
    }

    class LocalizationTable {
        -Dictionary~string, string~ _data
        -Dictionary~string, string~ _overrides
        +LocalizationTable(string name, string language, IReadOnlyDictionary~string, string~ data, ILocalizationTable fallback)
        +string Name
        +string Language
        +ILocalizationTable Fallback
        +string GetRawText(string key)
        +bool ContainsKey(string key)
        +IEnumerable~string~ GetKeys()
        +void Merge(IReadOnlyDictionary~string, string~ overrides)
    }

    class LocalizationString {
        -ILocalizationManager _manager
        -Dictionary~string, object~ _variables
        +LocalizationString(ILocalizationManager manager, string table, string key)
        +string Table
        +string Key
        +ILocalizationString WithVariable(string name, object value)
        +ILocalizationString WithVariables(params (string name, object value)[] variables)
        +string Format()
        +string GetRaw()
        +bool Exists()
        -string FormatString(string template, Dictionary~string, object~ variables, ILocalizationManager manager)
        -ILocalizationFormatter GetFormatter(ILocalizationManager manager, string name)
    }

    class ConditionalFormatter {
        +string Name
        +bool TryFormat(string format, object value, IFormatProvider provider, out string result)
    }

    class PluralFormatter {
        +string Name
        +bool TryFormat(string format, object value, IFormatProvider provider, out string result)
    }

    AbstractSystem <|-- LocalizationManager
    ILocalizationManager <|.. LocalizationManager
    ILocalizationTable <|.. LocalizationTable
    ILocalizationString <|.. LocalizationString
    ILocalizationFormatter <|.. ConditionalFormatter
    ILocalizationFormatter <|.. PluralFormatter

    LocalizationManager --> LocalizationConfig
    LocalizationManager --> LocalizationTable
    LocalizationManager --> LocalizationString
    LocalizationManager --> ILocalizationFormatter
    LocalizationString --> ILocalizationManager
    LocalizationTable --> LocalizationKeyNotFoundException
    LocalizationManager --> LocalizationTableNotFoundException
Loading

File-Level Changes

Change Details Files
Introduce core localization abstractions for manager, tables, strings, formatters, configuration, and error types.
  • Define ILocalizationManager with language management, table/text access, formatter registry, and language change subscription APIs.
  • Add ILocalizationTable, ILocalizationString, and ILocalizationFormatter interfaces to represent tables, format-capable strings with variables, and pluggable formatters.
  • Add LocalizationConfig to configure default/fallback language, resource/override paths, and validation/hot-reload flags.
  • Add LocalizationException plus specific LocalizationKeyNotFoundException and LocalizationTableNotFoundException error types for missing resources.
GFramework.Core.Abstractions/Localization/ILocalizationManager.cs
GFramework.Core.Abstractions/Localization/ILocalizationTable.cs
GFramework.Core.Abstractions/Localization/ILocalizationString.cs
GFramework.Core.Abstractions/Localization/ILocalizationFormatter.cs
GFramework.Core.Abstractions/Localization/LocalizationConfig.cs
GFramework.Core.Abstractions/Localization/LocalizationException.cs
GFramework.Core.Abstractions/Localization/LocalizationKeyNotFoundException.cs
GFramework.Core.Abstractions/Localization/LocalizationTableNotFoundException.cs
Add concrete localization runtime implementation including language scanning/loading, table management with fallback/overrides, and variable-aware string formatting.
  • Implement LocalizationManager as an AbstractSystem that scans language directories, loads JSON tables per language, manages current culture, and exposes AvailableLanguages.
  • Implement LocalizationTable to store base and override dictionaries, support fallback to another table, and provide key lookup/merge semantics with precedence rules.
  • Implement LocalizationString to bind a (table,key) to a manager, support variable registration, and perform template replacement with optional registered formatters and culture-aware numeric formatting.
  • Implement internal helpers in LocalizationManager for JSON loading, culture mapping from custom codes to CultureInfo, and safe notification of language change subscribers.
GFramework.Core/Localization/LocalizationManager.cs
GFramework.Core/Localization/LocalizationTable.cs
GFramework.Core/Localization/LocalizationString.cs
Provide built-in localization formatters for conditional and pluralized text.
  • Add ConditionalFormatter ("if") to choose between two text branches based on a boolean-like value.
  • Add PluralFormatter ("plural") to choose singular or plural form based on a numeric value using a simple == 1 rule.
  • Wire formatters into LocalizationString via placeholder syntax {name:formatter:args} and formatter lookup through ILocalizationManager.
GFramework.Core/Localization/Formatters/ConditionalFormatter.cs
GFramework.Core/Localization/Formatters/PluralFormatter.cs
GFramework.Core/Localization/LocalizationString.cs
GFramework.Core.Abstractions/Localization/ILocalizationFormatter.cs
GFramework.Core.Abstractions/Localization/ILocalizationManager.cs
Add tests and Chinese documentation to validate and describe the localization system and integrate it into the core docs navigation.
  • Create LocalizationTableTests to verify raw text lookup, fallback behavior, key existence, and override merge precedence.
  • Create LocalizationIntegrationTests to exercise end-to-end usage: initialization from a filesystem path, variable formatting, language switching, callbacks, and available language enumeration.
  • Extend zh-CN API reference to list localization types and add interface examples for ILocalizationManager, ILocalizationString, and LocalizationConfig usage.
  • Add a dedicated zh-CN core localization guide covering concepts, file layout, JSON schema, variable/formatter usage, fallback/override mechanisms, error handling, and best practices; add a navigation entry in the core index table.
GFramework.Core.Tests/Localization/LocalizationTableTests.cs
GFramework.Core.Tests/Localization/LocalizationIntegrationTests.cs
docs/zh-CN/api-reference/index.md
docs/zh-CN/core/index.md
docs/zh-CN/core/localization.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Comment thread GFramework.Core/Localization/LocalizationString.cs Outdated
Comment thread GFramework.Core/Localization/LocalizationTable.cs Outdated
Comment thread GFramework.Core/Localization/LocalizationTable.cs Outdated
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并提供了一些整体性的反馈:

  • LocalizationManager 目前会忽略 LocalizationConfig 中的 OverridePath、EnableHotReload 和 ValidateOnLoad;建议要么在 LoadLanguage/ScanAvailableLanguages 中接入这些配置(例如合并 override JSON、可选的加载时校验以及可选的文件监视),要么在文档中说明这些配置暂不支持,以避免造成困惑。
  • TryGetText 会捕获所有异常并返回空字符串,这可能会掩盖编程/配置错误;建议将捕获范围收窄到 LocalizationException(或更具体的异常类型),从而让意料之外的运行时错误仍然能够暴露出来。
  • LocalizationString.FormatString 每次调用都会分配一个新的 Regex;如果格式化字符串的性能比较重要,建议将该正则移动到一个 static readonly 字段中,或者使用 RegexOptions.Compiled 以降低开销。
给 AI 代理的提示词
请根据这次代码评审中的评论进行修改:

## 总体评论
- LocalizationManager 目前会忽略 LocalizationConfig 中的 OverridePath、EnableHotReload 和 ValidateOnLoad;建议要么在 LoadLanguage/ScanAvailableLanguages 中接入这些配置(例如合并 override JSON、可选的加载时校验以及可选的文件监视),要么在文档中说明这些配置暂不支持,以避免造成困惑。
- TryGetText 会捕获所有异常并返回空字符串,这可能会掩盖编程/配置错误;建议将捕获范围收窄到 LocalizationException(或更具体的异常类型),从而让意料之外的运行时错误仍然能够暴露出来。
- LocalizationString.FormatString 每次调用都会分配一个新的 Regex;如果格式化字符串的性能比较重要,建议将该正则移动到一个 static readonly 字段中,或者使用 RegexOptions.Compiled 以降低开销。

## 逐条评论

### 评论 1
<location path="GFramework.Core/Localization/LocalizationString.cs" line_range="100-101" />
<code_context>
+        }
+
+        // 匹配 {variableName} 或 {variableName:formatter:args}
+        var pattern = @"\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([a-zA-Z_][a-zA-Z0-9_]*)(?::([^}]+))?)?\}";
+        var regex = new Regex(pattern);
+
+        return regex.Replace(template, match =>
</code_context>
<issue_to_address>
**suggestion (performance):** 避免在每次调用 FormatString 时都分配一个新的 Regex 实例。

由于该方法可能被频繁调用,反复构造 Regex 会带来不必要的开销。建议将其移动到一个 static readonly 字段中,只创建一次并在整个生命周期内复用;如果可以接受一定的启动成本,也可以考虑启用 RegexOptions.Compiled。

建议实现如下:

```csharp
public class LocalizationString
{
    // Matches {variableName} or {variableName:formatter:args}
    private static readonly string FormatVariablePattern =
        @"\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([a-zA-Z_][a-zA-Z0-9_]*)(?::([^}]+))?)?\}";

    private static readonly Regex FormatVariableRegex =
        new Regex(FormatVariablePattern, RegexOptions.Compiled | RegexOptions.CultureInvariant);

```

```csharp
        // 使用预编译的静态正则表达式匹配 {variableName} 或 {variableName:formatter:args}
        return FormatVariableRegex.Replace(template, match =>

```

1. 如果包含此成员的类型并不叫 `LocalizationString`,请在第一个查找/替换代码块中替换为真实的类/结构体名称。
2. 确保文件顶部已包含 `using System.Text.RegularExpressions;`,如果没有请补充。
3. 如果你认为预编译正则的启动成本过高,可以去掉 `RegexOptions.Compiled`,只保留静态 `Regex` 来避免重复分配。
</issue_to_address>

### 评论 2
<location path="docs/zh-CN/core/localization.md" line_range="257-258" />
<code_context>
+    // 更新 UI、重新加载资源等
+});
+
+// 取消订阅
+locManager.UnsubscribeFromLanguageChange(callback);
+```
+
</code_context>
<issue_to_address>
**issue:** `callback` 在示例中未定义,取消订阅用法略显不清晰。

示例中只演示了用 lambda 直接订阅:`SubscribeToLanguageChange(language => { ... })`,但取消订阅时却使用了未定义的 `callback`。建议:要么补充完整示例(先将回调赋给变量,再用该变量取消订阅),要么让取消订阅示例与订阅方式一致,并说明如何保存回调以便后续取消订阅。
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • LocalizationManager currently ignores OverridePath, EnableHotReload, and ValidateOnLoad in LocalizationConfig; consider either wiring these options into LoadLanguage/ScanAvailableLanguages (e.g., merging override JSON, optional validation, and optional file watching) or documenting that they are not yet supported to avoid confusion.
  • TryGetText catches all exceptions and returns an empty string, which can hide programming/configuration errors; consider narrowing this to LocalizationException (or more specific types) so unexpected runtime errors still surface.
  • LocalizationString.FormatString allocates a new Regex on every call; you can reduce overhead by moving the regex to a static readonly field or using RegexOptions.Compiled if performance of formatted strings is important.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- LocalizationManager currently ignores OverridePath, EnableHotReload, and ValidateOnLoad in LocalizationConfig; consider either wiring these options into LoadLanguage/ScanAvailableLanguages (e.g., merging override JSON, optional validation, and optional file watching) or documenting that they are not yet supported to avoid confusion.
- TryGetText catches all exceptions and returns an empty string, which can hide programming/configuration errors; consider narrowing this to LocalizationException (or more specific types) so unexpected runtime errors still surface.
- LocalizationString.FormatString allocates a new Regex on every call; you can reduce overhead by moving the regex to a static readonly field or using RegexOptions.Compiled if performance of formatted strings is important.

## Individual Comments

### Comment 1
<location path="GFramework.Core/Localization/LocalizationString.cs" line_range="100-101" />
<code_context>
+        }
+
+        // 匹配 {variableName} 或 {variableName:formatter:args}
+        var pattern = @"\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([a-zA-Z_][a-zA-Z0-9_]*)(?::([^}]+))?)?\}";
+        var regex = new Regex(pattern);
+
+        return regex.Replace(template, match =>
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid allocating a new Regex instance on every FormatString call.

Since this method may be called often, repeatedly constructing the Regex is unnecessary overhead. Move it to a static readonly field so it’s created once and reused, and consider enabling RegexOptions.Compiled if startup cost is acceptable.

Suggested implementation:

```csharp
public class LocalizationString
{
    // Matches {variableName} or {variableName:formatter:args}
    private static readonly string FormatVariablePattern =
        @"\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([a-zA-Z_][a-zA-Z0-9_]*)(?::([^}]+))?)?\}";

    private static readonly Regex FormatVariableRegex =
        new Regex(FormatVariablePattern, RegexOptions.Compiled | RegexOptions.CultureInvariant);

```

```csharp
        // 使用预编译的静态正则表达式匹配 {variableName} 或 {variableName:formatter:args}
        return FormatVariableRegex.Replace(template, match =>

```

1. If the containing type is not actually named `LocalizationString`, adjust the first SEARCH/REPLACE block to match the real class/struct name.
2. Ensure `using System.Text.RegularExpressions;` is present at the top of the file if it isn’t already.
3. If you decide compiled regex startup cost is too high, you can remove `RegexOptions.Compiled` and keep the static `Regex` for allocation avoidance only.
</issue_to_address>

### Comment 2
<location path="docs/zh-CN/core/localization.md" line_range="257-258" />
<code_context>
+    // 更新 UI、重新加载资源等
+});
+
+// 取消订阅
+locManager.UnsubscribeFromLanguageChange(callback);
+```
+
</code_context>
<issue_to_address>
**issue:** `callback` 在示例中未定义,取消订阅用法略显不清晰。

示例中只演示了用 lambda 直接订阅:`SubscribeToLanguageChange(language => { ... })`,但取消订阅时却使用了未定义的 `callback`。建议:要么补充完整示例(先将回调赋给变量,再用该变量取消订阅),要么让取消订阅示例与订阅方式一致,并说明如何保存回调以便后续取消订阅。
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread GFramework.Core/Localization/LocalizationString.cs Outdated
Comment thread docs/zh-CN/core/localization.md Outdated
- 添加了配置项暂不支持的说明信息
- 扩展了语言变化监听的使用方式,增加命名方法订阅示例
- 完善了覆盖机制的文档说明
- 优化了异常处理逻辑,精确捕获本地化相关异常
- 实现了正则表达式的预编译以提升性能
- 添加了必要的命名空间引用
Comment thread GFramework.Core/Localization/LocalizationString.cs Outdated
GeWuYou added 6 commits March 18, 2026 23:25
- 添加对 GFramework.Core.Abstractions.Localization 的引用
- 确保测试文件能够访问本地化相关的抽象接口
- 为后续的本地化功能扩展做好准备
- 保持代码结构的一致性与可维护性
- 将 FormatVariablePattern 从 readonly 字段改为 const 常量
- 提取复杂的正则替换逻辑到独立的 FormatMatch 方法中
- 新增 GetOptionalGroupValue 辅助方法处理可选组值提取
- 分离格式化值和尝试格式化的逻辑到独立方法
- 简化条件判断并提高代码可读性
- 优化错误处理流程,保持原有功能不变
- 为构造函数添加更详细的参数描述和异常说明
- 为WithVariable方法添加完整的XML文档注释
- 为WithVariables方法添加完整的XML文档注释
- 为Format方法添加详细的返回值和备注信息
- 为GetRaw方法添加完整的XML文档注释
- 为Exists方法添加完整的XML文档注释
- 为私有FormatString方法添加参数和返回值说明
- 为私有FormatMatch方法添加详细的处理逻辑描述
- 为私有TryFormatValue方法添加格式化器相关参数说明
- 为私有FormatValue方法添加默认格式化逻辑说明
- 为私有GetOptionalGroupValue方法添加功能说明
- 为私有GetFormatter方法添加获取格式化器的详细描述
- 添加同步锁对象以保护处理器集合的并发访问
- 在注册和注销操作中加入线程安全锁机制
- 实现快照创建方法以避免迭代期间的并发修改
- 重构触发器逻辑以使用线程安全的快照创建
- 在计数器方法中添加同步保护
- 确保所有集合操作都在安全锁内执行
- 添加 System.IO 命名空间引用以支持文件操作
- 实现 CreateTestLocalizationFiles 方法创建测试用的多语言文件
- 使用 GUID 生成唯一的临时目录路径避免冲突
- 添加 TearDown 方法清理测试过程中创建的临时文件
- 在 Setup 方法中调用文件创建方法初始化测试环境
- 将目标框架配置改为可配置的条件变量方式
- 将 Fallback.ContainsKey 检查替换为更安全的模式匹配语法
- 避免了潜在的空引用异常风险
- 提高了代码的可读性和健壮性
- 保持了原有的功能逻辑不变
@GeWuYou GeWuYou merged commit ba8369c into main Mar 19, 2026
9 checks passed
@GeWuYou GeWuYou deleted the feat/localization-system branch March 19, 2026 05:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant