Skip to content

✨ feat(WebUI): complete dashboard internationalization system refactor #1822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from

Conversation

IGCrystal
Copy link
Member

@IGCrystal IGCrystal commented Jun 16, 2025

解决了 #XYZ

Motivation

Modifications

✨ 核心特性:

  • 实现模块化i18n架构,支持22个功能模块
  • 完成中英双语翻译文件(44个翻译文件)
  • 新增懒加载翻译模块,提升性能
  • 类型安全的翻译键值验证系统

🌐 国际化覆盖:

  • 所有主要页面(15+)完成国际化
  • 导航侧边栏、顶栏、共享组件全部支持
  • 仪表板统计组件完整国际化
  • 登录页面及认证流程完整国际化

🎨 UI/UX 优化:

  • 统一顶栏按钮样式(语言切换+主题切换)
  • 移动端登录页采用全屏设计
  • Logo组件智能换行支持中英文
  • 响应式语言切换组件

📱 移动端适配:

  • 登录卡片移动端全屏布局
  • 悬浮工具栏底部固定定位
  • 登录界面的触摸友好的交互设计
  • 登陆界面的多设备响应式支持

🔧 技术改进:

  • 模块化翻译文件结构 (core/, features/)
  • 懒加载机制减少初始包体积
  • TypeScript类型定义完整
  • 翻译键值自动验证

Check

  • 😊 我的 Commit Message 符合良好的规范
  • 👀 我的更改经过良好的测试
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。
  • 😮 我的更改没有引入恶意代码

好的,这是翻译成中文的 pull request 总结:

Sourcery 总结

重构 WebUI 为完全模块化的国际化架构,将所有硬编码文本替换为翻译键,提供双语翻译文件,支持延迟加载和类型安全验证,并添加全局语言切换器,同时更新应用程序引导程序以进行异步 i18n 初始化。

新特性:

  • 引入模块化国际化系统,具有延迟加载的翻译模块和 22 个功能命名空间
  • 为所有 UI 文本提供双语(zh-CN 和 en-US)JSON 翻译文件
  • 实现类型安全的翻译键验证和插值检查工具

增强功能:

  • 重构所有组件以使用 useI18n/useModuleI18n composables,并将硬编码字符串替换为翻译键
  • 添加可重用的 LanguageSwitcher 组件,用于在标题和登录页面中进行运行时区域设置切换
  • 更新应用程序引导程序以在挂载之前初始化和缓存翻译
  • 增强 Logo 组件以支持具有断字的响应式标题格式

构建:

  • 添加 vue-i18n 依赖项并集成动态翻译加载器和缓存逻辑

杂项:

  • 添加 i18n 实用程序,包括加载器、验证器、TypeScript 类型和 composable 助手
Original summary in English

好的,这是将拉取请求摘要翻译成中文的结果:

Sourcery 总结

通过将硬编码的 UI 文本替换为翻译键、动态加载双语翻译文件、强制执行类型安全的键验证以及添加全局语言切换器,将仪表板重构为完整的国际化系统。

新功能:

  • 模块化 i18n 架构,具有 22 个功能的延迟加载翻译模块
  • 涵盖所有 UI 文本的双语(zh-CN 和 en-US)JSON 翻译文件
  • 类型安全的翻译键验证和占位符插值检查

增强功能:

  • 重构组件和视图以使用 useI18n/useModuleI18n composables 而不是硬编码的字符串
  • 添加可重用的 LanguageSwitcher 组件并集成全局语言切换
  • 在 main.ts 中异步初始化 i18n,具有缓存和回退逻辑

构建:

  • 添加 vue-i18n 依赖项并集成动态翻译加载器

杂项:

  • src/i18n 下引入 TypeScript 翻译模式和加载器/验证器实用程序
  • 为 en-US 和 zh-CN 生成存根 JSON 语言环境文件
Original summary in English

Summary by Sourcery

Refactor the dashboard to a full internationalization system by replacing hardcoded UI text with translation keys, loading bilingual translation files dynamically, enforcing type-safe key validation, and adding a global language switcher.

New Features:

  • Modular i18n architecture with lazy‐loaded translation modules for 22 features
  • Dual-language (zh-CN and en-US) JSON translation files covering all UI texts
  • Type-safe translation key validation and placeholder interpolation checking

Enhancements:

  • Refactor components and views to use useI18n/useModuleI18n composables instead of hardcoded strings
  • Add a reusable LanguageSwitcher component and integrate global language switching
  • Initialize i18n asynchronously in main.ts with caching and fallback logic

Build:

  • Add vue-i18n dependency and integrate dynamic translation loader

Chores:

  • Introduce TypeScript translation schemas and loader/validator utilities under src/i18n
  • Generate stub JSON locale files for en-US and zh-CN

- 在 KnowledgeBase.vue 中修正无效的 v-class 指令为 class 属性的问题
- 在 ConsoleDisplayer.vue 中修正 historyNum 属性类型不匹配的问题
- 解决控制台中的 Vue 警告信息
- 在访问 status 前对 err.response 进行空值检查
- 防止“无法读取未定义对象的属性”错误
- 提高 catch 块中错误处理的健壮性
- 对 API 响应数据进行空值检查
- 在处理之前确保数组类型验证
- 修复“无法读取 null 对象的属性”错误
- 改进 beforeUnmount 生命周期中的 D3.js 清理工作
- 对图形数据处理添加防御性编程
✨ 核心特性:
- 实现模块化i18n架构,支持22个功能模块
- 完成中英双语翻译文件(44个翻译文件)
- 新增懒加载翻译模块,提升性能
- 类型安全的翻译键值验证系统

🌐 国际化覆盖:
- 所有主要页面(15+)完成国际化
- 导航侧边栏、顶栏、共享组件全部支持
- 仪表板统计组件完整国际化
- 登录页面及认证流程完整国际化

🎨 UI/UX 优化:
- 统一顶栏按钮样式(语言切换+主题切换)
- 移动端登录页采用全屏设计
- Logo组件智能换行支持中英文
- 响应式语言切换组件

📱 移动端适配:
- 登录卡片移动端全屏布局
- 悬浮工具栏底部固定定位
- 触摸友好的交互设计
- 多设备响应式支持

🔧 技术改进:
- 模块化翻译文件结构 (core/*, features/*)
- 懒加载机制减少初始包体积
- TypeScript类型定义完整
- 翻译键值自动验证
Copy link
Contributor

sourcery-ai bot commented Jun 16, 2025

## 审查者指南

此PR通过引入动态加载器、验证器和类型安全的翻译支持,将仪表板全面改造为完全模块化的国际化系统,然后重构几乎所有UI组件以使用翻译键而不是硬编码字符串,添加全局LanguageSwitcher,并更新应用程序引导程序以在挂载之前异步初始化i18n。

#### 带有i18n的应用程序初始化顺序图

```mermaid
sequenceDiagram
    participant App
    participant i18nInitializer as "setupI18n()"
    participant VueI18nInstance
    participant TranslationLoader
    participant TranslationCache
    participant Filesystem as "Translation Files (JSON)"

    App->>i18nInitializer: Start Initialization (async)
    activate i18nInitializer
    i18nInitializer->>VueI18nInstance: Create/Configure vue-i18n
    activate VueI18nInstance
    i18nInitializer->>TranslationLoader: Load Initial/Default Translations
    activate TranslationLoader
    TranslationLoader->>Filesystem: Read JSON files (e.g., core, default lang)
    Filesystem-->>TranslationLoader: JSON data
    TranslationLoader->>TranslationCache: Store translations
    activate TranslationCache
    TranslationCache-->>TranslationLoader: Stored
    deactivate TranslationCache
    TranslationLoader-->>i18nInitializer: Translations Ready
    deactivate TranslationLoader
    VueI18nInstance-->>i18nInitializer: Instance Ready
    deactivate VueI18nInstance
    i18nInitializer-->>App: Initialization Complete
    deactivate i18nInitializer
    App->>App: Mount Vue Components

类图:i18n Composables

classDiagram
  namespace i18nComposables {
    class useI18n {
      +t(key: String, ...args): String
      +locale: Ref<String>
      +setLocale(newLocale: String): void
      +availableLocales: Ref<String[]>
    }
    class useModuleI18n {
      +tm(key: String, ...args): String
      +locale: Ref<String> 
    }
  }
  useI18n ..> VueI18nInstance : interacts with
  useModuleI18n ..> VueI18nInstance : interacts with
Loading

文件级别更改

变更 详情 文件
引入模块化i18n基础设施
  • 实现具有缓存和延迟加载的动态翻译加载器
  • 添加i18n验证器,用于完整性、值、占位符和命名检查
  • 定义核心/功能/消息的TypeScript翻译模式和键类型
  • 提供composables (useI18n, useModuleI18n, useLanguageSwitcher) 和 CLI 工具
  • 添加i18n工具索引,用于验证器/加载器实用程序
dashboard/src/i18n/loader.ts
dashboard/src/i18n/validator.ts
dashboard/src/i18n/types.ts
dashboard/src/i18n/composables.ts
dashboard/src/i18n/tools/index.ts
在应用程序挂载之前初始化i18n
  • 将应用程序挂载包装在setupI18n promise中
  • 记录成功或回退,即使i18n失败也进行挂载
dashboard/src/main.ts
添加LanguageSwitcher组件并集成
  • 创建具有默认和标题变体的可重用LanguageSwitcher.vue
  • 将语言切换UI集成到LoginPage和VerticalHeader中
dashboard/src/components/shared/LanguageSwitcher.vue
dashboard/src/views/authentication/auth/LoginPage.vue
dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
重构UI以使用翻译函数
  • 用t()/tm()调用替换视图和组件中的所有硬编码文本
  • 利用模块范围的tm()进行功能命名空间
  • 将标签、工具提示和占位符绑定到翻译键
dashboard/src/views/ExtensionPage.vue
dashboard/src/views/ToolUsePage.vue
dashboard/src/views/ConversationPage.vue
dashboard/src/views/ChatPage.vue
为zh-CN和en-US添加locale JSON文件
  • 在22个功能命名空间和core/messages中提供双语JSON文件
  • 在src/i18n/locales/{zh-CN,en-US}/下构建locale资源
dashboard/src/i18n/locales/**

提示和命令

与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 以触发新的审查!

自定义您的体验

访问您的 dashboard 以:

  • 启用或禁用审查功能,例如Sourcery生成的pull request摘要、审查者指南等。
  • 更改审查语言。
  • 添加、删除或编辑自定义审查说明。
  • 调整其他审查设置。

获得帮助

```
Original review guide in English

Reviewer's Guide

This PR overhauls the dashboard into a fully modular internationalization system by introducing dynamic loaders, validators and type-safe translation support, then refactors nearly every UI component to consume translation keys instead of hard-coded strings, adds a global LanguageSwitcher, and updates the app bootstrap to asynchronously initialize i18n before mounting.

Sequence Diagram for App Initialization with i18n

sequenceDiagram
    participant App
    participant i18nInitializer as "setupI18n()"
    participant VueI18nInstance
    participant TranslationLoader
    participant TranslationCache
    participant Filesystem as "Translation Files (JSON)"

    App->>i18nInitializer: Start Initialization (async)
    activate i18nInitializer
    i18nInitializer->>VueI18nInstance: Create/Configure vue-i18n
    activate VueI18nInstance
    i18nInitializer->>TranslationLoader: Load Initial/Default Translations
    activate TranslationLoader
    TranslationLoader->>Filesystem: Read JSON files (e.g., core, default lang)
    Filesystem-->>TranslationLoader: JSON data
    TranslationLoader->>TranslationCache: Store translations
    activate TranslationCache
    TranslationCache-->>TranslationLoader: Stored
    deactivate TranslationCache
    TranslationLoader-->>i18nInitializer: Translations Ready
    deactivate TranslationLoader
    VueI18nInstance-->>i18nInitializer: Instance Ready
    deactivate VueI18nInstance
    i18nInitializer-->>App: Initialization Complete
    deactivate i18nInitializer
    App->>App: Mount Vue Components
Loading

Class Diagram: i18n Composables

classDiagram
  namespace i18nComposables {
    class useI18n {
      +t(key: String, ...args): String
      +locale: Ref<String>
      +setLocale(newLocale: String): void
      +availableLocales: Ref<String[]>
    }
    class useModuleI18n {
      +tm(key: String, ...args): String
      +locale: Ref<String> 
    }
  }
  useI18n ..> VueI18nInstance : interacts with
  useModuleI18n ..> VueI18nInstance : interacts with
Loading

File-Level Changes

Change Details Files
Introduce modular i18n infrastructure
  • Implement dynamic translation loader with caching and lazy-loading
  • Add i18n validator for completeness, value, placeholder and naming checks
  • Define TypeScript translation schemas and key types for core/features/messages
  • Provide composables (useI18n, useModuleI18n, useLanguageSwitcher) and CLI tools
  • Add i18n tooling index for validator/loader utilities
dashboard/src/i18n/loader.ts
dashboard/src/i18n/validator.ts
dashboard/src/i18n/types.ts
dashboard/src/i18n/composables.ts
dashboard/src/i18n/tools/index.ts
Initialize i18n before app mount
  • Wrap app mounting in setupI18n promise
  • Log success or fallback and mount even if i18n fails
dashboard/src/main.ts
Add LanguageSwitcher component and integrate
  • Create reusable LanguageSwitcher.vue with default and header variants
  • Integrate language switch UI into LoginPage and VerticalHeader
dashboard/src/components/shared/LanguageSwitcher.vue
dashboard/src/views/authentication/auth/LoginPage.vue
dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
Refactor UI to use translation functions
  • Replace all hard-coded text in views and components with t()/tm() calls
  • Leverage module-scoped tm() for feature namespaces
  • Bind labels, tooltips and placeholders to translation keys
dashboard/src/views/ExtensionPage.vue
dashboard/src/views/ToolUsePage.vue
dashboard/src/views/ConversationPage.vue
dashboard/src/views/ChatPage.vue
Add locale JSON files for zh-CN and en-US
  • Provide bilingual JSON files across 22 feature namespaces and core/messages
  • Structure locale assets under src/i18n/locales/{zh-CN,en-US}/
dashboard/src/i18n/locales/**

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

@IGCrystal
Copy link
Member Author

还未处理的组件:

WaitingForRestart.vue - 重启时显示
ExtensionCard.vue - 插件管理核心组件

ReadmeDialog.vue - 插件文档查看
AstrBotConfig.vue - 配置编辑

ListConfigItem.vue - 列表配置
ItemCardGrid.vue - 通用卡片网格

Copy link
Contributor

@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.

@IGCrystal - 我已经审查了你的更改 - 这里有一些反馈:

阻塞问题

  • 检测到一个通用 API 密钥,可能会暴露对各种服务和敏感操作的访问。(link)

一般评论

  • 最好将加载器从原始 fetch 切换到动态 import(),以便 Vite/Rollup 可以正确地进行代码拆分,并将 locale JSON 包含在构建中,而不是依赖于绝对 /src/... fetch 路径,这些路径在生产环境中可能会中断。
  • 在每个 <script setup> 中,导入和初始化 useI18n/useModuleI18n 存在大量重复的样板代码——考虑创建一个共享的 mixin 或 composable 来减少视图中的重复。
  • 仔细检查你的加载器注册表中的模块命名(例如 features/tooluse vs features/tool-use)是否与文件路径完全匹配,否则某些模块可能永远不会正确加载或缓存。
AI 代理的提示
请解决此代码审查中的评论:
## 总体评论
- 最好将加载器从原始 fetch 切换到动态 `import()`,以便 Vite/Rollup 可以正确地进行代码拆分,并将 locale JSON 包含在构建中,而不是依赖于绝对 `/src/...` fetch 路径,这些路径在生产环境中可能会中断。
- 在每个 `<script setup>` 中,导入和初始化 `useI18n`/`useModuleI18n` 存在大量重复的样板代码——考虑创建一个共享的 mixin 或 composable 来减少视图中的重复。
- 仔细检查你的加载器注册表中的模块命名(例如 `features/tooluse` vs `features/tool-use`)是否与文件路径完全匹配,否则某些模块可能永远不会正确加载或缓存。

## 单独评论

### 评论 1
<location> `dashboard/src/views/ConversationPage.vue:495` </location>
<code_context>
         this.fetchConversations();
     },

-    _methods: {
+    methods: {
         // Monaco编辑器挂载后的回调
</code_context>

<issue_to_address>
冗余的 _methods 属性和 getter/setter 应该被移除。

直接在 'methods' 属性下定义方法是标准的 Vue 方法。移除冗余的 _methods 属性将提高代码的清晰度和可维护性。

建议的实现方式:

```
    methods: {
        // Monaco编辑器挂载后的回调

```

如果在文件的其他地方有对 `this._methods` 的引用,应该更新为直接使用 `this``this.methods`(尽管在 Vue 中,方法是作为 `this.methodName` 访问的)。此外,如果存在任何针对 `_methods` 的 getter/setter 定义,则应将其删除。
</issue_to_address>

### 评论 2
<location> `dashboard/src/views/ConversationPage.vue:351` </location>
<code_context>
-                { title: '更新时间', key: 'updated_at', sortable: true, width: '180px' },
-                { title: '操作', key: 'actions', sortable: false, align: 'center', width: '240px' }
-            ],
+            headers: [],

             // 筛选条件
</code_context>

<issue_to_address>
headers 属性现在未使用,可以删除。

data() 中的 headers 数组现在可以删除,因为使用了 tableHeaders。
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
            search: '',
            headers: [],

            // 筛选条件
            platformFilter: [],
=======
            search: '',

            // 筛选条件
            platformFilter: [],
>>>>>>> REPLACE

</suggested_fix>

### 评论 3
<location> `dashboard/src/i18n/loader.ts:89` </location>
<code_context>
+
+    try {
+      // 使用fetch方式加载JSON文件
+      const modulePath = `/src/i18n/locales/${locale}/${moduleInfo.path}`;
+      const response = await fetch(modulePath);
+      
</code_context>

<issue_to_address>
硬编码路径在生产环境或不同的构建设置中可能会中断。

考虑用动态导入或 Vite 的资源处理替换硬编码路径,以确保它在不同的环境和构建输出中都能工作。
</issue_to_address>

### 评论 4
<location> `dashboard/src/i18n/loader.ts:212` </location>
<code_context>
+      const moduleName = moduleNames[index];
+      const nameParts = moduleName.split('/');
+      
+      // 构建嵌套对象结构(对所有模块统一处理)
+      let current = result;
+      for (let i = 0; i < nameParts.length - 1; i++) {
</code_context>

<issue_to_address>
如果模块共享相同的名称部分,合并模块可能会覆盖键。

如果两个模块共享相同的路径,除了最后一部分,它们的键在合并期间可能会被覆盖。建议添加对此类冲突的检查或警告,以防止细微的错误。
</issue_to_address>

### 评论 5
<location> `dashboard/src/i18n/composables.ts:65` </location>
<code_context>
+      }
+    }
+    
+    if (typeof value !== 'string') {
+      console.warn(`Translation value is not string: ${key}`);
+      return key;
</code_context>

<issue_to_address>
如果翻译值不是字符串,翻译函数会返回键,这可能会隐藏缺失的翻译。

考虑使用更明显的指示器,例如将键包裹在括号中,以便在开发过程中更容易检测到缺失的翻译。
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
      console.warn(`Translation value is not string: ${key}`);
      return key;
=======
      console.warn(`Translation value is not string: ${key}`);
      return `[${key}]`;
>>>>>>> REPLACE

</suggested_fix>

### 评论 6
<location> `dashboard/src/i18n/validator.ts:307` </location>
<code_context>
+   */
+  validateKeyNaming(localeData: Record<string, any>): ValidationError[] {
+    const errors: ValidationError[] = [];
+    const keyNamingPattern = /^[a-z][a-zA-Z0-9]*$/;
+
+    for (const [locale, data] of Object.entries(localeData)) {
</code_context>

<issue_to_address>
键命名模式对于嵌套键可能过于严格。

一些翻译键可能需要下划线或不同的约定,例如对于首字母缩略词。请检查此模式是否适合所有情况。
</issue_to_address>

### 评论 7
<location> `dashboard/src/i18n/validator.ts:388` </location>
<code_context>
+    }
+    
+        // 生成汇总报告
+    const totalKeys = results.length * 100; // 估算的总键数
+    const missingKeys = results.reduce((sum, r) => sum + r.missingKeys.length, 0);
+    
</code_context>

<issue_to_address>
总键数估计是硬编码的,可能无法反映实际的键数。

使用硬编码值作为总键数可能导致不准确的完整性指标。请动态计算键的总数。
</issue_to_address>

### 评论 8
<location> `dashboard/src/i18n/validator.ts:8` </location>
<code_context>
+
+import type { ValidationResult, ValidationError, UsageReport, TranslationStats } from './types';
+
+export class I18nValidator {
+  private baseLocale: string = 'zh-CN';
+  private supportedLocales: string[] = ['zh-CN', 'en-US'];
</code_context>

<issue_to_address>
考虑将 I18nValidator 类拆分为单独的模块,每个模块负责一个验证关注点,并共享一个通用的 traverse 辅助函数,以简化代码结构。

这里有一种方法可以在不改变任何行为的情况下驯服这个类——将每个“章节”(完整性、值检查、插值、命名、统计、使用情况、报告)拉到它自己的模块中,并共享一个 `traverse` 辅助函数。顶级的 `I18nValidator` 然后只进行协调。

---

### 1) 提取一个通用的 `traverse` 辅助函数

```ts
// utils/traverse.ts
export function traverse(
  obj: any,
  fn: (path: string, value: any) => void,
  prefix: string = ''
): void {
  for (const [k, v] of Object.entries(obj)) {
    const full = prefix ? `${prefix}.${k}` : k;
    fn(full, v);
    if (v && typeof v === 'object') {
      traverse(v, fn, full);
    }
  }
}
```

---

### 2) 将每个关注点拆分为它自己的文件

```ts
// validators/completeness.ts
import { traverse } from '../utils/traverse';
import type { ValidationResult, ValidationError } from '../types';

export function validateCompleteness(
  baseLocale: string,
  supported: string[],
  localeData: Record<string, any>
): ValidationResult {
  const errors: ValidationError[] = [];
  /* … your existing completeness logic, 
     replacing getAllKeys with traverse where appropriate … */
  return { /* same shape */ };
}
```

```ts
// validators/value.ts
import { traverse } from '../utils/traverse';
import type { ValidationError } from '../types';

export function validateValues(localeData: Record<string, any>): ValidationError[] {
  const errors: ValidationError[] = [];
  traverse(localeData, (path, v) => {
    if (typeof v === 'string') {
      if (!v.trim()) { /* push empty_value */ }
      const bad = (v.match(/\{[^}]+\}/g) || []).filter(ph => !/^{[A-Za-z_]\w*}$/.test(ph));
      bad.forEach(ph =>
        errors.push({ type: 'type_mismatch', key: path, message: `` })
      );
    } else if (v != null && typeof v !== 'object') {
      errors.push({ type: 'type_mismatch', key: path, message: `` });
    }
  });
  return errors;
}
```

…以及类似的 `interpolation.ts``naming.ts``usage.ts``stats.ts`---

### 3) 在你的编排器中连接它们

```ts
// I18nValidator.ts
import { validateCompleteness } from './validators/completeness';
import { validateValues }       from './validators/value';
import { validateInterpolation }from './validators/interpolation';
import { validateKeyNaming }    from './validators/naming';
import { validateUsage }        from './validators/usage';
import { generateStats }        from './validators/stats';
import type {
  ValidationResult, ValidationError, UsageReport, TranslationStats
} from './types';

export class I18nValidator {
  constructor(
    private baseLocale = 'zh-CN',
    private supportedLocales = ['zh-CN','en-US']
  ) {}

  generateReport(
    localeData: Record<string, any>,
    usedKeys: string[] = []
  ): {
    completeness: ValidationResult;
    values: ValidationError[];
    interpolation: ValidationError[];
    naming: ValidationError[];
    usage: UsageReport | null;
    stats: TranslationStats;
  } {
    const completeness = validateCompleteness(
      this.baseLocale, this.supportedLocales, localeData
    );
    const values        = validateValues(localeData);
    const interpolation = validateInterpolation(
      this.baseLocale, this.supportedLocales, localeData
    );
    const naming        = validateKeyNaming(localeData);
    const stats         = generateStats(localeData);

    const usage =
      usedKeys.length > 0
        ? validateUsage(this.baseLocale, localeData, usedKeys)
        : null;

    return { completeness, values, interpolation, naming, usage, stats };
  }
}
```

**优点:**
- 每个文件大约 50 行,而不是 >1000 行。
- 你可以单独对每个模块进行单元测试。
- “大局”编排器变得微不足道。
</issue_to_address>

### 评论 9
<location> `dashboard/src/i18n/loader.ts:115` </location>
<code_context>
+  /**
+   * 加载核心模块(最高优先级)
+   */
+  async loadCoreModules(locale: string): Promise<any> {
+    const coreModules = [
+      'core/common',
</code_context>

<issue_to_address>
考虑将三个模块加载方法重构为一个通用的加载器,该加载器使用注册表和可选的覆盖来减少重复。

```markdown
你可以将所有三个“load*Modules”方法折叠为一个通用的加载器,该加载器从注册表(或覆盖列表)中提取名称,从而消除手动数组和重复的循环。例如:

```ts
// 新增:通用模块加载器
private async loadModules(
  locale: string,
  prefix: string,
  overrideList: string[] = []
): Promise<any> {
  // 取 overrideList 或者 registry 中所有符合前缀的模块名
  const moduleNames = overrideList.length
    ? overrideList
    : Array.from(this.moduleRegistry.keys()).filter((k) => k.startsWith(prefix));

  const datas = await Promise.all(
    moduleNames.map((name) => this.loadModule(locale, name))
  );
  return this.mergeModules(datas, moduleNames);
}
```

然后把原来的三个方法改为:

```ts
async loadCoreModules(locale: string): Promise<any> {
  return this.loadModules(locale, 'core');
}

async loadFeatureModules(
  locale: string,
  features?: string[]
): Promise<any> {
  return this.loadModules(locale, 'features', features || []);
}

async loadMessageModules(locale: string): Promise<any> {
  return this.loadModules(locale, 'messages');
}
```

这样你就不需要在三个地方手动维护数组,也去掉了重复的 `Promise.all … mergeModules` 逻辑保留所有功能且更易维护。
</issue_to_address>

### 评论 10
<location> `dashboard/src/i18n/types.ts:7` </location>
<code_context>
+ */
+
+// 核心模块类型定义
+export interface CoreTranslations {
+  common: {
+    save: string;
</code_context>

<issue_to_address>
考虑使用 TypeScript 助手直接从你的 JSON 文件中推断翻译类型和键联合,而不是手动定义大型接口块。

```markdown
你可以通过直接从你的 JSON 文件中推断模式和有效的键字符串来消除 300 多行的手动接口和联合。这保持了完整的类型安全,并在你添加新键时自动更新。

1) 将你的 locale JSON 导入为 `const`
2) 推断一个单一的 `TranslationSchema`
3) 使用一个小的 `NestedKey<T>` 助手来派生每个有效的 `"a" | "a.b" | "a.b.c"````ts
// src/i18n/types.ts
import enUS from '../locales/en-US.json';
import zhCN from '../locales/zh-CN.json';

export const translations = {
  'en-US': enUS,
  'zh-CN': zhCN,
} as const;

// full schema for a single locale
export type TranslationSchema = typeof translations[keyof typeof translations];

// helper to extract nested key paths ("foo", "foo.bar", "foo.bar.baz", …)
type NestedKey<T> = T extends object
  ? { [K in keyof T & string]:
        T[K] extends object
          ? `${K}` | `${K}.${NestedKey<T[K]>}`
          : `${K}`
    }[keyof T & string]
  : never;

// the union of all valid translation keys
export type TranslationKey = NestedKey<TranslationSchema>;

// simplified t() signature
export function t(
  key: TranslationKey,
  params?: Record<string, string | number>
): string {
  // …implement lookup + interpolation
}
```

优点:
- **零手动重复**——添加到你的 JSON 中的任何键都会立即反映在 `TranslationKey` 中。
- **单一事实来源**——没有单独的 `interface` 块需要维护。
- **完全类型安全**——无效的键或拼写错误会在编译时被捕获。
```
</issue_to_address>

## 安全问题

### 问题 1
<location> `dashboard/src/views/ConfigPage.vue:105` </location>

<issue_to_address>
**security (generic-api-key):** 检测到通用 API 密钥,可能会暴露对各种服务和敏感操作的访问。

*Source: gitleaks*
</issue_to_address>

Sourcery 对开源项目是免费的 - 如果你喜欢我们的评论,请考虑分享它们 ✨
帮助我变得更有用!请在每条评论上点击 👍 或 👎,我将使用这些反馈来改进你的评论。
Original comment in English

Hey @IGCrystal - I've reviewed your changes - here's some feedback:

Blocking issues:

  • Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)

General comments:

  • It’s better to switch the loader from raw fetch to dynamic import() so that Vite/Rollup can properly code‐split and include locale JSON in the build rather than relying on absolute /src/... fetch paths that may break in production.
  • There’s a lot of repeated boilerplate in each <script setup> for importing and initializing useI18n/useModuleI18n—consider creating a shared mixin or composable to reduce duplication across views.
  • Double-check that the module naming in your loader registry (e.g. features/tooluse vs features/tool-use) exactly matches the file paths, otherwise some modules may never load or cache correctly.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- It’s better to switch the loader from raw fetch to dynamic `import()` so that Vite/Rollup can properly code‐split and include locale JSON in the build rather than relying on absolute `/src/...` fetch paths that may break in production.
- There’s a lot of repeated boilerplate in each `<script setup>` for importing and initializing `useI18n`/`useModuleI18n`—consider creating a shared mixin or composable to reduce duplication across views.
- Double-check that the module naming in your loader registry (e.g. `features/tooluse` vs `features/tool-use`) exactly matches the file paths, otherwise some modules may never load or cache correctly.

## Individual Comments

### Comment 1
<location> `dashboard/src/views/ConversationPage.vue:495` </location>
<code_context>
         this.fetchConversations();
     },

-    _methods: {
+    methods: {
         // Monaco编辑器挂载后的回调
</code_context>

<issue_to_address>
Redundant _methods property and getter/setter should be removed.

Defining methods directly under the 'methods' property is the standard Vue approach. Removing the redundant _methods property will improve clarity and maintainability.

Suggested implementation:

```
    methods: {
        // Monaco编辑器挂载后的回调

```

If there are any references to `this._methods` elsewhere in the file, they should be updated to use `this` directly or `this.methods` if needed (though in Vue, methods are accessed as `this.methodName`). Also, if there are any getter/setter definitions for `_methods`, they should be removed.
</issue_to_address>

### Comment 2
<location> `dashboard/src/views/ConversationPage.vue:351` </location>
<code_context>
-                { title: '更新时间', key: 'updated_at', sortable: true, width: '180px' },
-                { title: '操作', key: 'actions', sortable: false, align: 'center', width: '240px' }
-            ],
+            headers: [],

             // 筛选条件
</code_context>

<issue_to_address>
The headers property is now unused and can be removed.

The headers array in data() can be removed now that tableHeaders is used.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
            search: '',
            headers: [],

            // 筛选条件
            platformFilter: [],
=======
            search: '',

            // 筛选条件
            platformFilter: [],
>>>>>>> REPLACE

</suggested_fix>

### Comment 3
<location> `dashboard/src/i18n/loader.ts:89` </location>
<code_context>
+
+    try {
+      // 使用fetch方式加载JSON文件
+      const modulePath = `/src/i18n/locales/${locale}/${moduleInfo.path}`;
+      const response = await fetch(modulePath);
+      
</code_context>

<issue_to_address>
Hardcoded path may break in production or with different build setups.

Consider replacing the hardcoded path with a dynamic import or Vite's asset handling to ensure it works across different environments and build outputs.
</issue_to_address>

### Comment 4
<location> `dashboard/src/i18n/loader.ts:212` </location>
<code_context>
+      const moduleName = moduleNames[index];
+      const nameParts = moduleName.split('/');
+      
+      // 构建嵌套对象结构(对所有模块统一处理)
+      let current = result;
+      for (let i = 0; i < nameParts.length - 1; i++) {
</code_context>

<issue_to_address>
Merging modules may overwrite keys if modules share the same name parts.

If two modules share the same path except for the last part, their keys may be overwritten during merging. Adding a check or warning for such collisions is recommended to prevent subtle bugs.
</issue_to_address>

### Comment 5
<location> `dashboard/src/i18n/composables.ts:65` </location>
<code_context>
+      }
+    }
+    
+    if (typeof value !== 'string') {
+      console.warn(`Translation value is not string: ${key}`);
+      return key;
</code_context>

<issue_to_address>
Translation function returns key if value is not a string, which may hide missing translations.

Consider using a more visible indicator, like wrapping the key in brackets, to make missing translations easier to detect during development.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
      console.warn(`Translation value is not string: ${key}`);
      return key;
=======
      console.warn(`Translation value is not string: ${key}`);
      return `[${key}]`;
>>>>>>> REPLACE

</suggested_fix>

### Comment 6
<location> `dashboard/src/i18n/validator.ts:307` </location>
<code_context>
+   */
+  validateKeyNaming(localeData: Record<string, any>): ValidationError[] {
+    const errors: ValidationError[] = [];
+    const keyNamingPattern = /^[a-z][a-zA-Z0-9]*$/;
+
+    for (const [locale, data] of Object.entries(localeData)) {
</code_context>

<issue_to_address>
Key naming pattern may be too restrictive for nested keys.

Some translation keys may need underscores or different conventions, such as for acronyms. Please review if this pattern fits all scenarios.
</issue_to_address>

### Comment 7
<location> `dashboard/src/i18n/validator.ts:388` </location>
<code_context>
+    }
+    
+        // 生成汇总报告
+    const totalKeys = results.length * 100; // 估算的总键数
+    const missingKeys = results.reduce((sum, r) => sum + r.missingKeys.length, 0);
+    
</code_context>

<issue_to_address>
Total keys estimation is hardcoded and may not reflect actual key count.

Using a hardcoded value for total keys can result in inaccurate completeness metrics. Please calculate the total number of keys dynamically.
</issue_to_address>

### Comment 8
<location> `dashboard/src/i18n/validator.ts:8` </location>
<code_context>
+
+import type { ValidationResult, ValidationError, UsageReport, TranslationStats } from './types';
+
+export class I18nValidator {
+  private baseLocale: string = 'zh-CN';
+  private supportedLocales: string[] = ['zh-CN', 'en-US'];
</code_context>

<issue_to_address>
Consider splitting the I18nValidator class into separate modules for each validation concern and sharing a generic traverse helper to simplify the code structure.

Here’s one way to tame this class without changing any behavior—pull each “chapter” (completeness, value‐checks, interpolation, naming, stats, usage, report) into its own module and share a single `traverse` helper.  The top‐level `I18nValidator` then just coordinates.  

---

### 1) Extract a generic `traverse` helper

```ts
// utils/traverse.ts
export function traverse(
  obj: any,
  fn: (path: string, value: any) => void,
  prefix: string = ''
): void {
  for (const [k, v] of Object.entries(obj)) {
    const full = prefix ? `${prefix}.${k}` : k;
    fn(full, v);
    if (v && typeof v === 'object') {
      traverse(v, fn, full);
    }
  }
}
```

---

### 2) Split each concern into its own file

```ts
// validators/completeness.ts
import { traverse } from '../utils/traverse';
import type { ValidationResult, ValidationError } from '../types';

export function validateCompleteness(
  baseLocale: string,
  supported: string[],
  localeData: Record<string, any>
): ValidationResult {
  const errors: ValidationError[] = [];
  /* … your existing completeness logic, 
     replacing getAllKeys with traverse where appropriate … */
  return { /* same shape */ };
}
```

```ts
// validators/value.ts
import { traverse } from '../utils/traverse';
import type { ValidationError } from '../types';

export function validateValues(localeData: Record<string, any>): ValidationError[] {
  const errors: ValidationError[] = [];
  traverse(localeData, (path, v) => {
    if (typeof v === 'string') {
      if (!v.trim()) { /* push empty_value */ }
      const bad = (v.match(/\{[^}]+\}/g) || []).filter(ph => !/^{[A-Za-z_]\w*}$/.test(ph));
      bad.forEach(ph =>
        errors.push({ type: 'type_mismatch', key: path, message: `` })
      );
    } else if (v != null && typeof v !== 'object') {
      errors.push({ type: 'type_mismatch', key: path, message: `` });
    }
  });
  return errors;
}
```

…and similarly for `interpolation.ts`, `naming.ts`, `usage.ts`, `stats.ts`.

---

### 3) Wire them up in your orchestrator

```ts
// I18nValidator.ts
import { validateCompleteness } from './validators/completeness';
import { validateValues }       from './validators/value';
import { validateInterpolation }from './validators/interpolation';
import { validateKeyNaming }    from './validators/naming';
import { validateUsage }        from './validators/usage';
import { generateStats }        from './validators/stats';
import type {
  ValidationResult, ValidationError, UsageReport, TranslationStats
} from './types';

export class I18nValidator {
  constructor(
    private baseLocale = 'zh-CN',
    private supportedLocales = ['zh-CN','en-US']
  ) {}

  generateReport(
    localeData: Record<string, any>,
    usedKeys: string[] = []
  ): {
    completeness: ValidationResult;
    values: ValidationError[];
    interpolation: ValidationError[];
    naming: ValidationError[];
    usage: UsageReport | null;
    stats: TranslationStats;
  } {
    const completeness = validateCompleteness(
      this.baseLocale, this.supportedLocales, localeData
    );
    const values        = validateValues(localeData);
    const interpolation = validateInterpolation(
      this.baseLocale, this.supportedLocales, localeData
    );
    const naming        = validateKeyNaming(localeData);
    const stats         = generateStats(localeData);

    const usage =
      usedKeys.length > 0
        ? validateUsage(this.baseLocale, localeData, usedKeys)
        : null;

    return { completeness, values, interpolation, naming, usage, stats };
  }
}
```

**Benefits:**  
- Each file is ~50 lines instead of >1 000.  
- You can unit‐test each module in isolation.  
- The “big picture” orchestrator becomes trivial.
</issue_to_address>

### Comment 9
<location> `dashboard/src/i18n/loader.ts:115` </location>
<code_context>
+  /**
+   * 加载核心模块(最高优先级)
+   */
+  async loadCoreModules(locale: string): Promise<any> {
+    const coreModules = [
+      'core/common',
</code_context>

<issue_to_address>
Consider refactoring the three module-loading methods into a single generic loader that uses the registry and optional overrides to reduce duplication.

```markdown
You can collapse all three “load*Modules” methods into one generic loader that pulls names from the registry (or an override list), eliminating manual arrays and duplicated loops. For example:

```ts
// 新增:通用模块加载器
private async loadModules(
  locale: string,
  prefix: string,
  overrideList: string[] = []
): Promise<any> {
  // 取 overrideList 或者 registry 中所有符合前缀的模块名
  const moduleNames = overrideList.length
    ? overrideList
    : Array.from(this.moduleRegistry.keys()).filter((k) => k.startsWith(prefix));

  const datas = await Promise.all(
    moduleNames.map((name) => this.loadModule(locale, name))
  );
  return this.mergeModules(datas, moduleNames);
}
```

然后把原来的三个方法改为:

```ts
async loadCoreModules(locale: string): Promise<any> {
  return this.loadModules(locale, 'core');
}

async loadFeatureModules(
  locale: string,
  features?: string[]
): Promise<any> {
  return this.loadModules(locale, 'features', features || []);
}

async loadMessageModules(locale: string): Promise<any> {
  return this.loadModules(locale, 'messages');
}
```

这样你就不需要在三个地方手动维护数组,也去掉了重复的 `Promise.all … mergeModules` 逻辑保留所有功能且更易维护。
</issue_to_address>

### Comment 10
<location> `dashboard/src/i18n/types.ts:7` </location>
<code_context>
+ */
+
+// 核心模块类型定义
+export interface CoreTranslations {
+  common: {
+    save: string;
</code_context>

<issue_to_address>
Consider inferring translation types and key unions directly from your JSON files using TypeScript helpers instead of manually defining large interface blocks.

```markdown
You can eliminate the 300+ lines of manual interfaces & unions by inferring both the schema and the valid keystrings directly from your JSON files. This keeps full typesafety and auto-updates whenever you add new keys.

1) Import your locale JSONs as `const`  
2) Infer a single `TranslationSchema`  
3) Use a small `NestedKey<T>` helper to derive every valid `"a" | "a.b" | "a.b.c"` key  

```ts
// src/i18n/types.ts
import enUS from '../locales/en-US.json';
import zhCN from '../locales/zh-CN.json';

export const translations = {
  'en-US': enUS,
  'zh-CN': zhCN,
} as const;

// full schema for a single locale
export type TranslationSchema = typeof translations[keyof typeof translations];

// helper to extract nested key paths ("foo", "foo.bar", "foo.bar.baz", …)
type NestedKey<T> = T extends object
  ? { [K in keyof T & string]:
        T[K] extends object
          ? `${K}` | `${K}.${NestedKey<T[K]>}`
          : `${K}`
    }[keyof T & string]
  : never;

// the union of all valid translation keys
export type TranslationKey = NestedKey<TranslationSchema>;

// simplified t() signature
export function t(
  key: TranslationKey,
  params?: Record<string, string | number>
): string {
  // …implement lookup + interpolation
}
```

Advantages:
- **Zero manual repetition**—any key added to your JSONs is immediately reflected in `TranslationKey`.
- **Single source of truth**—no separate `interface` block to maintain.
- **Full type-safety**—invalid keys or typos are caught at compile time.
```
</issue_to_address>

## Security Issues

### Issue 1
<location> `dashboard/src/views/ConfigPage.vue:105` </location>

<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

*Source: gitleaks*
</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 on lines 896 to -882
this.messageType = 'error';
this.showMessage = true;
}
},
get methods() {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: 冗余的 _methods 属性和 getter/setter 应该被移除。

直接在 'methods' 属性下定义方法是标准的 Vue 方法。移除冗余的 _methods 属性将提高代码的清晰度和可维护性。

建议的实现方式:

    methods: {
        // Monaco编辑器挂载后的回调

如果在文件的其他地方有对 this._methods 的引用,应该更新为直接使用 thisthis.methods(尽管在 Vue 中,方法是作为 this.methodName 访问的)。此外,如果存在任何针对 _methods 的 getter/setter 定义,则应将其删除。

Original comment in English

suggestion: Redundant _methods property and getter/setter should be removed.

Defining methods directly under the 'methods' property is the standard Vue approach. Removing the redundant _methods property will improve clarity and maintainability.

Suggested implementation:

    methods: {
        // Monaco编辑器挂载后的回调

If there are any references to this._methods elsewhere in the file, they should be updated to use this directly or this.methods if needed (though in Vue, methods are accessed as this.methodName). Also, if there are any getter/setter definitions for _methods, they should be removed.

Comment on lines 350 to 354
search: '',
headers: [
{ title: '对话标题', key: 'title', sortable: true },
{ title: '平台', key: 'platform', sortable: true, width: '120px' },
{ title: '类型', key: 'messageType', sortable: true, width: '100px' },
{ title: 'ID', key: 'sessionId', sortable: true, width: '100px' },
{ title: '创建时间', key: 'created_at', sortable: true, width: '180px' },
{ title: '更新时间', key: 'updated_at', sortable: true, width: '180px' },
{ title: '操作', key: 'actions', sortable: false, align: 'center', width: '240px' }
],
headers: [],

// 筛选条件
platformFilter: [],
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: headers 属性现在未使用,可以删除。

data() 中的 headers 数组现在可以删除,因为使用了 tableHeaders。

Suggested change
search: '',
headers: [
{ title: '对话标题', key: 'title', sortable: true },
{ title: '平台', key: 'platform', sortable: true, width: '120px' },
{ title: '类型', key: 'messageType', sortable: true, width: '100px' },
{ title: 'ID', key: 'sessionId', sortable: true, width: '100px' },
{ title: '创建时间', key: 'created_at', sortable: true, width: '180px' },
{ title: '更新时间', key: 'updated_at', sortable: true, width: '180px' },
{ title: '操作', key: 'actions', sortable: false, align: 'center', width: '240px' }
],
headers: [],
// 筛选条件
platformFilter: [],
search: '',
// 筛选条件
platformFilter: [],
Original comment in English

suggestion: The headers property is now unused and can be removed.

The headers array in data() can be removed now that tableHeaders is used.

Suggested change
search: '',
headers: [
{ title: '对话标题', key: 'title', sortable: true },
{ title: '平台', key: 'platform', sortable: true, width: '120px' },
{ title: '类型', key: 'messageType', sortable: true, width: '100px' },
{ title: 'ID', key: 'sessionId', sortable: true, width: '100px' },
{ title: '创建时间', key: 'created_at', sortable: true, width: '180px' },
{ title: '更新时间', key: 'updated_at', sortable: true, width: '180px' },
{ title: '操作', key: 'actions', sortable: false, align: 'center', width: '240px' }
],
headers: [],
// 筛选条件
platformFilter: [],
search: '',
// 筛选条件
platformFilter: [],


try {
// 使用fetch方式加载JSON文件
const modulePath = `/src/i18n/locales/${locale}/${moduleInfo.path}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): 硬编码路径在生产环境或不同的构建设置中可能会中断。

考虑用动态导入或 Vite 的资源处理替换硬编码路径,以确保它在不同的环境和构建输出中都能工作。

Original comment in English

issue (bug_risk): Hardcoded path may break in production or with different build setups.

Consider replacing the hardcoded path with a dynamic import or Vite's asset handling to ensure it works across different environments and build outputs.

const moduleName = moduleNames[index];
const nameParts = moduleName.split('/');

// 构建嵌套对象结构(对所有模块统一处理)
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): 如果模块共享相同的名称部分,合并模块可能会覆盖键。

如果两个模块共享相同的路径,除了最后一部分,它们的键在合并期间可能会被覆盖。建议添加对此类冲突的检查或警告,以防止细微的错误。

Original comment in English

issue (bug_risk): Merging modules may overwrite keys if modules share the same name parts.

If two modules share the same path except for the last part, their keys may be overwritten during merging. Adding a check or warning for such collisions is recommended to prevent subtle bugs.

Comment on lines 66 to 67
console.warn(`Translation value is not string: ${key}`);
return key;
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: 如果翻译值不是字符串,翻译函数会返回键,这可能会隐藏缺失的翻译。

考虑使用更明显的指示器,例如将键包裹在括号中,以便在开发过程中更容易检测到缺失的翻译。

Suggested change
console.warn(`Translation value is not string: ${key}`);
return key;
console.warn(`Translation value is not string: ${key}`);
return `[${key}]`;
Original comment in English

suggestion: Translation function returns key if value is not a string, which may hide missing translations.

Consider using a more visible indicator, like wrapping the key in brackets, to make missing translations easier to detect during development.

Suggested change
console.warn(`Translation value is not string: ${key}`);
return key;
console.warn(`Translation value is not string: ${key}`);
return `[${key}]`;

/**
* 加载核心模块(最高优先级)
*/
async loadCoreModules(locale: string): Promise<any> {
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (complexity): 考虑将三个模块加载方法重构为一个通用的加载器,该加载器使用注册表和可选的覆盖来减少重复。

你可以将所有三个“load*Modules”方法折叠为一个通用的加载器,该加载器从注册表(或覆盖列表)中提取名称,从而消除手动数组和重复的循环。例如:

```ts
// 新增:通用模块加载器
private async loadModules(
  locale: string,
  prefix: string,
  overrideList: string[] = []
): Promise<any> {
  // 取 overrideList 或者 registry 中所有符合前缀的模块名
  const moduleNames = overrideList.length
    ? overrideList
    : Array.from(this.moduleRegistry.keys()).filter((k) => k.startsWith(prefix));

  const datas = await Promise.all(
    moduleNames.map((name) => this.loadModule(locale, name))
  );
  return this.mergeModules(datas, moduleNames);
}

然后把原来的三个方法改为:

async loadCoreModules(locale: string): Promise<any> {
  return this.loadModules(locale, 'core');
}

async loadFeatureModules(
  locale: string,
  features?: string[]
): Promise<any> {
  return this.loadModules(locale, 'features', features || []);
}

async loadMessageModules(locale: string): Promise<any> {
  return this.loadModules(locale, 'messages');
}

这样你就不需要在三个地方手动维护数组,也去掉了重复的 Promise.all … mergeModules 逻辑保留所有功能且更易维护。

Original comment in English

issue (complexity): Consider refactoring the three module-loading methods into a single generic loader that uses the registry and optional overrides to reduce duplication.

You can collapse all three “load*Modules” methods into one generic loader that pulls names from the registry (or an override list), eliminating manual arrays and duplicated loops. For example:

```ts
// 新增:通用模块加载器
private async loadModules(
  locale: string,
  prefix: string,
  overrideList: string[] = []
): Promise<any> {
  // 取 overrideList 或者 registry 中所有符合前缀的模块名
  const moduleNames = overrideList.length
    ? overrideList
    : Array.from(this.moduleRegistry.keys()).filter((k) => k.startsWith(prefix));

  const datas = await Promise.all(
    moduleNames.map((name) => this.loadModule(locale, name))
  );
  return this.mergeModules(datas, moduleNames);
}

然后把原来的三个方法改为:

async loadCoreModules(locale: string): Promise<any> {
  return this.loadModules(locale, 'core');
}

async loadFeatureModules(
  locale: string,
  features?: string[]
): Promise<any> {
  return this.loadModules(locale, 'features', features || []);
}

async loadMessageModules(locale: string): Promise<any> {
  return this.loadModules(locale, 'messages');
}

这样你就不需要在三个地方手动维护数组,也去掉了重复的 Promise.all … mergeModules 逻辑保留所有功能且更易维护。

*/

// 核心模块类型定义
export interface CoreTranslations {
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (complexity): 考虑使用 TypeScript 助手直接从你的 JSON 文件中推断翻译类型和键联合,而不是手动定义大型接口块。

你可以通过直接从你的 JSON 文件中推断模式和有效的键字符串来消除 300 多行的手动接口和联合。这保持了完整的类型安全,并在你添加新键时自动更新。

1) 将你的 locale JSON 导入为 `const`
2) 推断一个单一的 `TranslationSchema`
3) 使用一个小的 `NestedKey<T>` 助手来派生每个有效的 `"a" | "a.b" | "a.b.c"````ts
// src/i18n/types.ts
import enUS from '../locales/en-US.json';
import zhCN from '../locales/zh-CN.json';

export const translations = {
  'en-US': enUS,
  'zh-CN': zhCN,
} as const;

// full schema for a single locale
export type TranslationSchema = typeof translations[keyof typeof translations];

// helper to extract nested key paths ("foo", "foo.bar", "foo.bar.baz", …)
type NestedKey<T> = T extends object
  ? { [K in keyof T & string]:
        T[K] extends object
          ? `${K}` | `${K}.${NestedKey<T[K]>}`
          : `${K}`
    }[keyof T & string]
  : never;

// the union of all valid translation keys
export type TranslationKey = NestedKey<TranslationSchema>;

// simplified t() signature
export function t(
  key: TranslationKey,
  params?: Record<string, string | number>
): string {
  // …implement lookup + interpolation
}

优点:

  • 零手动重复——添加到你的 JSON 中的任何键都会立即反映在 TranslationKey 中。
  • 单一事实来源——没有单独的 interface 块需要维护。
  • 完全类型安全——无效的键或拼写错误会在编译时被捕获。

<details>
<summary>Original comment in English</summary>

**issue (complexity):** Consider inferring translation types and key unions directly from your JSON files using TypeScript helpers instead of manually defining large interface blocks.

```markdown
You can eliminate the 300+ lines of manual interfaces & unions by inferring both the schema and the valid key‐strings directly from your JSON files. This keeps full type‐safety and auto-updates whenever you add new keys.

1) Import your locale JSONs as `const`  
2) Infer a single `TranslationSchema`  
3) Use a small `NestedKey<T>` helper to derive every valid `"a" | "a.b" | "a.b.c"` key  

```ts
// src/i18n/types.ts
import enUS from '../locales/en-US.json';
import zhCN from '../locales/zh-CN.json';

export const translations = {
  'en-US': enUS,
  'zh-CN': zhCN,
} as const;

// full schema for a single locale
export type TranslationSchema = typeof translations[keyof typeof translations];

// helper to extract nested key paths ("foo", "foo.bar", "foo.bar.baz", …)
type NestedKey<T> = T extends object
  ? { [K in keyof T & string]:
        T[K] extends object
          ? `${K}` | `${K}.${NestedKey<T[K]>}`
          : `${K}`
    }[keyof T & string]
  : never;

// the union of all valid translation keys
export type TranslationKey = NestedKey<TranslationSchema>;

// simplified t() signature
export function t(
  key: TranslationKey,
  params?: Record<string, string | number>
): string {
  // …implement lookup + interpolation
}

Advantages:

  • Zero manual repetition—any key added to your JSONs is immediately reflected in TranslationKey.
  • Single source of truth—no separate interface block to maintain.
  • Full type-safety—invalid keys or typos are caught at compile time.

</details>

<small>{{ tm('help.helpPrefix') }}
<a href="https://astrbot.app/" target="_blank">{{ tm('help.documentation') }}</a>
{{ tm('help.helpMiddle') }}
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=EYGsuUTfe00_iOu9JTXS7_TEpMkXOvwv&jump_from=webapi&authKey=uUEMKCROfsseS+8IzqPjzV3y1tzy4AkykwTib2jNkOFdzezF9s9XknqnIaf3CDft" target="_blank">{{ tm('help.support') }}</a>{{ tm('help.helpSuffix') }}
Copy link
Contributor

Choose a reason for hiding this comment

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

security (generic-api-key): 检测到通用 API 密钥,可能会暴露对各种服务和敏感操作的访问。

Source: gitleaks

Original comment in English

security (generic-api-key): Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

Source: gitleaks

Comment on lines +13 to +29
function traverse(source: any, target: any, path: string = '') {
for (const key in source) {
const currentPath = path ? `${path}.${key}` : key;

if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) {
missing.push(currentPath);
} else {
traverse(source[key], target[key], currentPath);
}
} else {
if (!(key in target)) {
missing.push(currentPath);
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): 避免在块中使用函数声明,而倾向于使用函数赋值表达式。(avoid-function-declarations-in-blocks)

Explanation函数声明可能会在 Javascript 中被提升,但这种行为在不同浏览器之间是不一致的。 提升通常会令人困惑,应该避免。与其在块中使用函数声明,不如使用函数表达式,它会在作用域内创建函数。
Original comment in English

issue (code-quality): Avoid function declarations, favouring function assignment expressions, inside blocks. (avoid-function-declarations-in-blocks)

ExplanationFunction declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers. Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you should use function expressions, which create functions in-scope.

Comment on lines +17 to +27
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) {
missing.push(currentPath);
} else {
traverse(source[key], target[key], currentPath);
}
} else {
if (!(key in target)) {
missing.push(currentPath);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): 将 else 子句的嵌套 if 语句合并到 else if 中 (merge-else-if)

Suggested change
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) {
missing.push(currentPath);
} else {
traverse(source[key], target[key], currentPath);
}
} else {
if (!(key in target)) {
missing.push(currentPath);
}
}
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) {
missing.push(currentPath);
} else {
traverse(source[key], target[key], currentPath);
}
}
else if (!(key in target)) {
missing.push(currentPath);
}


Explanation展平嵌套在 else 子句中的 if 语句会生成更易于阅读和扩展的代码。

Original comment in English

suggestion (code-quality): Merge else clause's nested if statement into else if (merge-else-if)

Suggested change
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) {
missing.push(currentPath);
} else {
traverse(source[key], target[key], currentPath);
}
} else {
if (!(key in target)) {
missing.push(currentPath);
}
}
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) {
missing.push(currentPath);
} else {
traverse(source[key], target[key], currentPath);
}
}
else if (!(key in target)) {
missing.push(currentPath);
}


ExplanationFlattening if statements nested within else clauses generates code that is
easier to read and expand upon.

- Enhance i18n error handling and code quality - Fix SSE data processing in chat page - Improve responsive design for extension page - Add better debugging tools for development"
ExtensionCard.vue - 插件卡片组件 WaitingForRestart.vue - 重启等待组件 ReadmeDialog.vue - README对话框组件 AstrBotConfig.vue - 配置编辑器组件 ListConfigItem.vue - 列表配置项组件 ItemCardGrid.vue - 卡片网格组件
ChatPage.vue - 聊天页面的录音提示文本 ConfigPage.vue - 配置页面的状态消息 ExtensionPage.vue - 插件页面的加载和状态文本 OnlineTime.vue - 仪表板运行时间组件
- AlkaidPage_sigma.vue
- PlatformPage.vue
- LongTermMemory.vue
- KnowledgeBase.vue
- Migrate from manual TypeScript interfaces to automatic type generation
from JSON files. Eliminates sync issues and maintenance overhead.
@IGCrystal
Copy link
Member Author

国际化已经基本修改完毕,我这里测试没有问题,请求其他成员进行测试。

修复chat与chatbox之间切换后sse断开连接导致无法实时显示消息
修改键位为Ctrl + A ,以及还加入SSE断连提示
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