Skip to content

Feature: i18n for AstrBot #1817

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

Closed
wants to merge 1 commit into from
Closed

Feature: i18n for AstrBot #1817

wants to merge 1 commit into from

Conversation

Soulter
Copy link
Member

@Soulter Soulter commented Jun 15, 2025

Motivation

This PR aims to make AstrBot more international.

Check

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

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

Sourcery 总结

通过添加 vue-i18n 支持、将硬编码文本替换为翻译键以及提供运行时语言切换和持久性,在 AstrBot 仪表板中集成国际化。

新特性:

  • 添加 vue-i18n 插件,其中包含中文和英文的语言环境资源文件
  • 将所有视图和组件中的静态 UI 标签和消息替换为 t(…) 翻译调用
  • 在“设置”和“登录”页面以及应用程序初始化时公开语言选择器
  • 在 localStorage 中持久保存首选语言,并在启动时自动检测浏览器语言环境

增强功能:

  • 重构组件和脚本以使用 Composition API 的 useI18n hook
  • 将语言环境回退配置为英语,以处理缺失的翻译

构建:

  • 添加 "vue-i18n" 依赖项并在 main.ts 中初始化它
Original summary in English

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

Sourcery 总结

将国际化集成到 AstrBot 仪表板中,通过添加 vue-i18n 支持、翻译所有 UI 文本,并启用具有持久性和回退处理的运行时语言选择。

新特性:

  • 添加包含中文 (zh-CN) 和英文 (en-US) 区域设置资源文件的 vue-i18n 插件
  • 在“设置”和“登录”页面中公开语言选择器,并在启动时检测语言
  • 在 localStorage 中持久保存用户的首选语言并自动检测浏览器区域设置

增强功能:

  • 将所有硬编码的 UI 字符串替换为跨视图和组件的 t() 翻译调用
  • 重构组件以使用 Composition API 的 useI18n hook 进行翻译
  • 为缺少的翻译配置英语回退区域设置

构建:

  • 添加 vue-i18n 依赖项并在 main.ts 中初始化 i18n 插件
Original summary in English

Summary by Sourcery

Integrate internationalization into the AstrBot dashboard by adding vue-i18n support, translating all UI text, and enabling runtime language selection with persistence and fallback handling

New Features:

  • Add vue-i18n plugin with Chinese (zh-CN) and English (en-US) locale resource files
  • Expose language selector in Settings and Login pages and detect language on startup
  • Persist user’s preferred language in localStorage and auto-detect browser locale

Enhancements:

  • Replace all hard-coded UI strings across views and components with t() translation calls
  • Refactor components to use the Composition API’s useI18n hook for translations
  • Configure English fallback locale for missing translations

Build:

  • Add vue-i18n dependency and initialize the i18n plugin in main.ts

Copy link
Contributor

sourcery-ai bot commented Jun 15, 2025

## 审查者指南

此 PR 通过配置插件、将所有硬编码的 UI 字符串替换为翻译调用、使用 useI18n 将关键组件(特别是 ProviderPage)重构为 Composition API,并添加具有持久性和自动检测的运行时语言选择,从而在 AstrBot 仪表板中集成 vue-i18n 支持。

#### 初始语言设置的顺序图

```mermaid
sequenceDiagram
    actor UserBrowser as "用户/浏览器"
    participant AppVue as "App.vue"
    participant localStorageApi as "localStorage"
    participant i18nInstance as "i18n 实例"

    AppVue->>localStorageApi: getItem('preferred-language')
    localStorageApi-->>AppVue: savedLanguage
    alt savedLanguage 有效
        AppVue->>i18nInstance: locale.value = savedLanguage
        AppVue->>localStorageApi: setItem('preferred-language', savedLanguage) 
    else 没有有效的 savedLanguage 或新的应用程序版本
        AppVue->>UserBrowser: navigator.language
        UserBrowser-->>AppVue: browserLanguage
        AppVue->>AppVue: 确定区域设置(来自 browserLanguage 或回退 'zh-CN')
        AppVue->>i18nInstance: locale.value = determinedLocale
        AppVue->>localStorageApi: setItem('preferred-language', determinedLocale)
    end

用户更改语言的顺序图

sequenceDiagram
    actor User
    participant PageVue as "Settings.vue / LoginPage.vue"
    participant localStorageApi as "localStorage"
    participant i18nInstance as "i18n 实例 (通过 useI18n)"

    User->>PageVue: 选择新语言 (例如,'en-US')
    PageVue->>PageVue: 用户操作调用 changeLanguage('en-US')
    PageVue->>i18nInstance: locale.value = 'en-US'
    PageVue->>localStorageApi: setItem('preferred-language', 'en-US')
    i18nInstance-->>PageVue: UI 使用新的翻译重新渲染
Loading

i18n 初始化和设置的类图

classDiagram
  class MainTs {
    <<TypeScript Module>>
    +app: VueApp
    +main() void
  }
  class I18nIndexTs {
    <<TypeScript Module>>
    +i18n: I18nInstance
    +createI18n(options): I18nInstance
  }
  class VueApp {
    <<Vue Application Instance>>
    +use(plugin: Plugin): VueApp
    +mount(rootContainer: string): void
  }
  class I18nInstance {
    <<vue-i18n Instance>>
    +legacy: false
    +locale: string
    +fallbackLocale: string
    +messages: object
    +t(key: string): string
  }
  class LocaleJson {
      <<JSON Resource>>
      + "key": "value"
  }

  MainTs ..> I18nIndexTs : imports i18n (instance)
  MainTs ..> VueApp : app.use(i18n)
  I18nIndexTs ..> I18nInstance : creates & exports
  I18nInstance ..> LocaleJson : loads messages (e.g. en-US.json, zh-CN.json)
Loading

App.vue 的更新类图 (i18n 逻辑)

classDiagram
  class AppVue {
    <<VueComponent>>
    #setup()
    +locale: Ref~string~ (from useI18n)
    +onMounted() void
    -setupInitialLanguage() void
  }
  AppVue ..> i18nHook : uses
  AppVue ..> localStorageApi : uses
  class i18nHook {
    <<vue-i18n Composition API Hook>>
    +locale: WritableComputedRef~string~
    +t(key: string): string
  }
  class localStorageApi {
    <<Browser API>>
    +getItem(key: string): string
    +setItem(key: string, value: string): void
  }
Loading

Settings.vue 的更新类图 (i18n)

classDiagram
  class SettingsVue {
    <<VueComponent>>
    #setup()
    +locale: Ref~string~ (from useI18n)
    +languages: LanguageOption[]
    +selectedGitHubProxy: Ref~string~
    +t(key: string): string (from useI18n)
    +changeLanguage(lang: string): void
    +onMounted(): void
  }
  class LanguageOption {
    +value: string
    +label: string
  }
  SettingsVue ..> i18nHook : uses
  SettingsVue ..> localStorageApi : uses
  class i18nHook {
    <<vue-i18n Composition API Hook>>
    +locale: WritableComputedRef~string~
    +t(key: string): string
  }
  class localStorageApi {
    <<Browser API>>
    +getItem(key: string): string
    +setItem(key: string, value: string): void
  }
Loading

LoginPage.vue 的更新类图 (i18n)

classDiagram
  class LoginPageVue {
    <<VueComponent>>
    #setup()
    +locale: Ref~string~ (from useI18n)
    +languages: LanguageOption[]
    +t(key: string): string (from useI18n)
    +changeLanguage(lang: string): void
    +onMounted(): void
  }
  class LanguageOption {
    +value: string
    +title: string
  }
  LoginPageVue ..> i18nHook : uses
  LoginPageVue ..> localStorageApi : uses
  class i18nHook {
    <<vue-i18n Composition API Hook>>
    +locale: WritableComputedRef~string~
    +t(key: string): string
  }
  class localStorageApi {
    <<Browser API>>
    +getItem(key: string): string
    +setItem(key: string, value: string): void
  }
Loading

ProviderPage.vue 的更新类图 (Composition API & i18n 重构)

classDiagram
  class ProviderPageVue {
    <<VueComponent>>
    #setup()
    +t(key: string): string (from useI18n)
    +config_data: Ref~object~
    +metadata: Ref~object~
    +activeProviderTab: Ref~string~
    +filteredProviders: ComputedRef~object[]~
    +getTemplatesByType(type: string): object
    +getTabTypeName(tabType: string): string
    +selectProviderTemplate(name: string): void
    +newProvider(): void
    +onMounted(): void
    // ... other reactive properties and methods from setup()
  }
  ProviderPageVue ..> i18nHook : uses
  class i18nHook {
    <<vue-i18n Composition API Hook>>
    +t(key: string): string
  }
  note for ProviderPageVue "从 Options API 重构为 Composition API。\n所有 data、computed、methods 现在都是 setup() 的一部分。\n使用 useI18n() 进行翻译。"
Loading

文件级别的更改

变更 详情 文件
集成 vue-i18n 插件和配置
  • 将 vue-i18n 添加到依赖项并安装插件
  • 创建包含区域设置消息和回退的 i18n 设置文件
  • 在应用程序启动时在 main.ts 中初始化 i18n
package.json
src/main.ts
src/i18n/index.ts
将硬编码的 UI 文本替换为翻译调用
  • 更新视图/组件中的模板以使用 t(…) 作为标签、占位符、工具提示和警报
  • 在脚本块中导入 useI18n 或设置 t
  • 删除静态中文/英文字符串,而使用翻译键
src/views/**/*.vue
src/components/**/*.vue
使用 useI18n 将 ProviderPage 重构为 Composition API
  • 将 Options API 转换为 <script setup> 样式
  • 将 data()、computed、methods 替换为 refs、computed 和独立函数
  • 使用 useI18n hook 处理所有文本并删除 this 上下文
src/views/ProviderPage.vue
添加运行时语言选择器、持久性和自动检测
  • 在“设置”和“登录”页面中公开语言下拉列表
  • 实现 changeLanguage 以更新区域设置并存储在 localStorage 中
  • 在应用程序挂载时检测并恢复 preferred-language 或浏览器区域设置
src/views/Settings.vue
src/views/authentication/auth/LoginPage.vue
src/App.vue

提示和命令

与 Sourcery 互动

  • 触发新的审查: 在拉取请求上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub 问题: 通过回复审查评论,要求 Sourcery 从审查评论创建一个问题。您也可以回复审查评论并使用 @sourcery-ai issue 从中创建一个问题。
  • 生成拉取请求标题: 在拉取请求标题中的任何位置写入 @sourcery-ai 以随时生成标题。您也可以在拉取请求上评论 @sourcery-ai title 以随时(重新)生成标题。
  • 生成拉取请求摘要: 在拉取请求正文中的任何位置写入 @sourcery-ai summary 以随时在您想要的位置生成 PR 摘要。您也可以在拉取请求上评论 @sourcery-ai summary 以随时(重新)生成摘要。
  • 生成审查者指南: 在拉取请求上评论 @sourcery-ai guide 以随时(重新)生成审查者指南。
  • 解决所有 Sourcery 评论: 在拉取请求上评论 @sourcery-ai resolve 以解决所有 Sourcery 评论。如果您已经解决了所有评论并且不想再看到它们,这将非常有用。
  • 关闭所有 Sourcery 审查: 在拉取请求上评论 @sourcery-ai dismiss 以关闭所有现有的 Sourcery 审查。如果您想从新的审查开始,这将特别有用 - 不要忘记评论 @sourcery-ai review 以触发新的审查!

自定义您的体验

访问您的 仪表板 以:

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

获取帮助

```
Original review guide in English

Reviewer's Guide

This PR integrates vue-i18n support across the AstrBot dashboard by configuring the plugin, replacing all hardcoded UI strings with translation calls, refactoring key components (notably ProviderPage) to the Composition API using useI18n, and adding runtime language selection with persistence and automatic detection.

Sequence Diagram for Initial Language Setup

sequenceDiagram
    actor UserBrowser as "User/Browser"
    participant AppVue as "App.vue"
    participant localStorageApi as "localStorage"
    participant i18nInstance as "i18n Instance"

    AppVue->>localStorageApi: getItem('preferred-language')
    localStorageApi-->>AppVue: savedLanguage
    alt savedLanguage is valid
        AppVue->>i18nInstance: locale.value = savedLanguage
        AppVue->>localStorageApi: setItem('preferred-language', savedLanguage) 
    else no valid savedLanguage or new app version
        AppVue->>UserBrowser: navigator.language
        UserBrowser-->>AppVue: browserLanguage
        AppVue->>AppVue: Determine locale (from browserLanguage or fallback 'zh-CN')
        AppVue->>i18nInstance: locale.value = determinedLocale
        AppVue->>localStorageApi: setItem('preferred-language', determinedLocale)
    end
Loading

Sequence Diagram for User Changing Language

sequenceDiagram
    actor User
    participant PageVue as "Settings.vue / LoginPage.vue"
    participant localStorageApi as "localStorage"
    participant i18nInstance as "i18n Instance (via useI18n)"

    User->>PageVue: Selects new language (e.g., 'en-US')
    PageVue->>PageVue: User action calls changeLanguage('en-US')
    PageVue->>i18nInstance: locale.value = 'en-US'
    PageVue->>localStorageApi: setItem('preferred-language', 'en-US')
    i18nInstance-->>PageVue: UI re-renders with new translations
Loading

Class Diagram for i18n Initialization and Setup

classDiagram
  class MainTs {
    <<TypeScript Module>>
    +app: VueApp
    +main() void
  }
  class I18nIndexTs {
    <<TypeScript Module>>
    +i18n: I18nInstance
    +createI18n(options): I18nInstance
  }
  class VueApp {
    <<Vue Application Instance>>
    +use(plugin: Plugin): VueApp
    +mount(rootContainer: string): void
  }
  class I18nInstance {
    <<vue-i18n Instance>>
    +legacy: false
    +locale: string
    +fallbackLocale: string
    +messages: object
    +t(key: string): string
  }
  class LocaleJson {
      <<JSON Resource>>
      + "key": "value"
  }

  MainTs ..> I18nIndexTs : imports i18n (instance)
  MainTs ..> VueApp : app.use(i18n)
  I18nIndexTs ..> I18nInstance : creates & exports
  I18nInstance ..> LocaleJson : loads messages (e.g. en-US.json, zh-CN.json)
Loading

Updated Class Diagram for App.vue (i18n Logic)

classDiagram
  class AppVue {
    <<VueComponent>>
    #setup()
    +locale: Ref~string~ (from useI18n)
    +onMounted() void
    -setupInitialLanguage() void
  }
  AppVue ..> i18nHook : uses
  AppVue ..> localStorageApi : uses
  class i18nHook {
    <<vue-i18n Composition API Hook>>
    +locale: WritableComputedRef~string~
    +t(key: string): string
  }
  class localStorageApi {
    <<Browser API>>
    +getItem(key: string): string
    +setItem(key: string, value: string): void
  }
Loading

Updated Class Diagram for Settings.vue (i18n)

classDiagram
  class SettingsVue {
    <<VueComponent>>
    #setup()
    +locale: Ref~string~ (from useI18n)
    +languages: LanguageOption[]
    +selectedGitHubProxy: Ref~string~
    +t(key: string): string (from useI18n)
    +changeLanguage(lang: string): void
    +onMounted(): void
  }
  class LanguageOption {
    +value: string
    +label: string
  }
  SettingsVue ..> i18nHook : uses
  SettingsVue ..> localStorageApi : uses
  class i18nHook {
    <<vue-i18n Composition API Hook>>
    +locale: WritableComputedRef~string~
    +t(key: string): string
  }
  class localStorageApi {
    <<Browser API>>
    +getItem(key: string): string
    +setItem(key: string, value: string): void
  }
Loading

Updated Class Diagram for LoginPage.vue (i18n)

classDiagram
  class LoginPageVue {
    <<VueComponent>>
    #setup()
    +locale: Ref~string~ (from useI18n)
    +languages: LanguageOption[]
    +t(key: string): string (from useI18n)
    +changeLanguage(lang: string): void
    +onMounted(): void
  }
  class LanguageOption {
    +value: string
    +title: string
  }
  LoginPageVue ..> i18nHook : uses
  LoginPageVue ..> localStorageApi : uses
  class i18nHook {
    <<vue-i18n Composition API Hook>>
    +locale: WritableComputedRef~string~
    +t(key: string): string
  }
  class localStorageApi {
    <<Browser API>>
    +getItem(key: string): string
    +setItem(key: string, value: string): void
  }
Loading

Updated Class Diagram for ProviderPage.vue (Composition API & i18n Refactor)

classDiagram
  class ProviderPageVue {
    <<VueComponent>>
    #setup()
    +t(key: string): string (from useI18n)
    +config_data: Ref~object~
    +metadata: Ref~object~
    +activeProviderTab: Ref~string~
    +filteredProviders: ComputedRef~object[]~
    +getTemplatesByType(type: string): object
    +getTabTypeName(tabType: string): string
    +selectProviderTemplate(name: string): void
    +newProvider(): void
    +onMounted(): void
    // ... other reactive properties and methods from setup()
  }
  ProviderPageVue ..> i18nHook : uses
  class i18nHook {
    <<vue-i18n Composition API Hook>>
    +t(key: string): string
  }
  note for ProviderPageVue "Refactored from Options API to Composition API.\nAll data, computed, methods are now part of setup().\nUses useI18n() for translations."
Loading

File-Level Changes

Change Details Files
Integrate vue-i18n plugin and configuration
  • Add vue-i18n to dependencies and install plugin
  • Create i18n setup file with locale messages and fallback
  • Initialize i18n in main.ts on app startup
package.json
src/main.ts
src/i18n/index.ts
Replace hardcoded UI text with translation calls
  • Update templates across views/components to use t(…) for labels, placeholders, tooltips, and alerts
  • Import useI18n or setup t in script blocks
  • Remove static Chinese/English strings in favor of translation keys
src/views/**/*.vue
src/components/**/*.vue
Refactor ProviderPage to Composition API with useI18n
  • Convert Options API to <script setup> style
  • Replace data(), computed, methods with refs, computed, and standalone functions
  • Use useI18n hook for all text and remove this-context
src/views/ProviderPage.vue
Add runtime language selector, persistence, and auto-detection
  • Expose language dropdown in Settings and Login pages
  • Implement changeLanguage to update locale and store in localStorage
  • Detect and restore preferred-language or browser locale on app mount
src/views/Settings.vue
src/views/authentication/auth/LoginPage.vue
src/App.vue

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

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.

@Soulter - 我已经查看了你的更改,它们看起来很棒!

AI 代理的提示
请解决此代码审查中的评论:
## 单独的评论

### 评论 1
<location> `dashboard/src/views/ProviderPage.vue:416` </location>
<code_context>
+  return filtered;
+};
+
+const getProviderIcon = (type) => {
+  const icons = {
+    'OpenAI': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/openai.svg',
</code_context>

<issue_to_address>
图标选择逻辑可能无法稳健地处理模糊或相似的提供商名称。

使用 `type.startsWith(key)` 可能会在提供商名称重叠时导致不正确的图标分配。为了提高准确性,请使用更严格的匹配或按降序长度对键进行排序,以优先考虑更具体的匹配。
</issue_to_address>

### 评论 2
<location> `dashboard/src/views/ProviderPage.vue:561` </location>
<code_context>
-}
+};
+
+const deleteProvider = (provider) => {
+  if (confirm(`确定要删除服务提供商 ${provider.id} 吗?`)) {
+    axios.post('/api/config/provider/delete', { id: provider.id }).then((res) => {
</code_context>

<issue_to_address>
直接使用 `confirm` 进行用户提示可能与 UI/UX 不一致。

考虑将浏览器的 `confirm` 对话框替换为 Vuetify 对话框,以获得更一致和易于访问的用户体验。
</issue_to_address>

### 评论 3
<location> `dashboard/src/views/authentication/authForms/AuthLogin.vue:54` </location>
<code_context>
+      :append-icon="show1 ? 'mdi-eye' : 'mdi-eye-off'" :type="show1 ? 'text' : 'password'"
+      @click:append="show1 = !show1" class="pwd-input" prepend-inner-icon="mdi-lock" :disabled="loading"></v-text-field>
+
+    <v-btn color="secondary" :loading="isSubmitting || loading" block class="login-btn mt-8" variant="flat" size="large"
+      :disabled="valid" type="submit" elevation="2">
+      <span class="login-btn-text">{{ t('auth.login') }}</span>
</code_context>

<issue_to_address>
登录按钮在 `valid` 时禁用,这可能无法反映表单的有效性。

当表单有效时,应启用该按钮。请反转逻辑或使用正确的验证状态进行 `:disabled` 绑定。
</issue_to_address>

### 评论 4
<location> `dashboard/src/views/Settings.vue:58` </location>
<code_context>
-        }
-    },
-    mounted() {
-        this.selectedGitHubProxy = localStorage.getItem('selectedGitHubProxy') || "";
+  components: {
+    WaitingForRestart,
</code_context>

<issue_to_address>
没有对用户提供的 GitHub 代理 URL 进行验证。

验证用户输入的 GitHub 代理 URL 可以防止格式错误的条目和潜在的安全风险。建议强制执行正确的 URL 格式并限制为 HTTPS。

建议的实施方式:

```
  components: {
    WaitingForRestart,
  },
  data() {
    return {
      selectedGitHubProxy: localStorage.getItem('selectedGitHubProxy') || "",
      githubProxyUrlError: "",
    };
  },
  methods: {
    validateGitHubProxyUrl(url) {
      // Only allow HTTPS URLs, basic validation
      try {
        const parsed = new URL(url);
        if (parsed.protocol !== "https:") {
          return "URL must start with https://";
        }
        return "";
      } catch (e) {
        return "Invalid URL format";
      }
    },
    onGitHubProxyUrlChange() {
      this.githubProxyUrlError = this.validateGitHubProxyUrl(this.selectedGitHubProxy);
      if (!this.githubProxyUrlError) {
        localStorage.setItem('selectedGitHubProxy', this.selectedGitHubProxy);
      }
    },
  },

```

```
        this.selectedGitHubProxy = localStorage.getItem('selectedGitHubProxy') || "";

```

您还需要更新 `Settings.vue` 的模板部分,以:
- 将 GitHub 代理 URL 的输入绑定到 `selectedGitHubProxy`- 使用 `@change``@input` 调用 `onGitHubProxyUrlChange`-`githubProxyUrlError` 显示为向用户的验证消息。

示例(放置在模板中):
```vue
<input
  v-model="selectedGitHubProxy"
  @change="onGitHubProxyUrlChange"
  placeholder="Enter GitHub Proxy URL (https://...)"
/>
<div v-if="githubProxyUrlError" class="error-message">{{ githubProxyUrlError }}</div>
```
您可能需要调整模板代码以适应您的 UI 框架和样式。
</issue_to_address>

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

Hey @Soulter - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `dashboard/src/views/ProviderPage.vue:416` </location>
<code_context>
+  return filtered;
+};
+
+const getProviderIcon = (type) => {
+  const icons = {
+    'OpenAI': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/openai.svg',
</code_context>

<issue_to_address>
Icon selection logic may not handle ambiguous or similar provider names robustly.

Using `type.startsWith(key)` may cause incorrect icon assignment when provider names overlap. To improve accuracy, use stricter matching or sort keys by descending length to prioritize more specific matches.
</issue_to_address>

### Comment 2
<location> `dashboard/src/views/ProviderPage.vue:561` </location>
<code_context>
-}
+};
+
+const deleteProvider = (provider) => {
+  if (confirm(`确定要删除服务提供商 ${provider.id} 吗?`)) {
+    axios.post('/api/config/provider/delete', { id: provider.id }).then((res) => {
</code_context>

<issue_to_address>
Direct use of `confirm` for user prompts may not be consistent with UI/UX.

Consider replacing the browser's `confirm` dialog with a Vuetify dialog for a more consistent and accessible user experience.
</issue_to_address>

### Comment 3
<location> `dashboard/src/views/authentication/authForms/AuthLogin.vue:54` </location>
<code_context>
+      :append-icon="show1 ? 'mdi-eye' : 'mdi-eye-off'" :type="show1 ? 'text' : 'password'"
+      @click:append="show1 = !show1" class="pwd-input" prepend-inner-icon="mdi-lock" :disabled="loading"></v-text-field>
+
+    <v-btn color="secondary" :loading="isSubmitting || loading" block class="login-btn mt-8" variant="flat" size="large"
+      :disabled="valid" type="submit" elevation="2">
+      <span class="login-btn-text">{{ t('auth.login') }}</span>
</code_context>

<issue_to_address>
Login button disables on `valid`, which may not reflect form validity.

The button should be enabled when the form is valid. Please invert the logic or use the correct validation state for the `:disabled` binding.
</issue_to_address>

### Comment 4
<location> `dashboard/src/views/Settings.vue:58` </location>
<code_context>
-        }
-    },
-    mounted() {
-        this.selectedGitHubProxy = localStorage.getItem('selectedGitHubProxy') || "";
+  components: {
+    WaitingForRestart,
</code_context>

<issue_to_address>
No validation for user-provided GitHub proxy URLs.

Validating user input for GitHub proxy URLs can prevent malformed entries and potential security risks. Recommend enforcing proper URL format and restricting to HTTPS.

Suggested implementation:

```
  components: {
    WaitingForRestart,
  },
  data() {
    return {
      selectedGitHubProxy: localStorage.getItem('selectedGitHubProxy') || "",
      githubProxyUrlError: "",
    };
  },
  methods: {
    validateGitHubProxyUrl(url) {
      // Only allow HTTPS URLs, basic validation
      try {
        const parsed = new URL(url);
        if (parsed.protocol !== "https:") {
          return "URL must start with https://";
        }
        return "";
      } catch (e) {
        return "Invalid URL format";
      }
    },
    onGitHubProxyUrlChange() {
      this.githubProxyUrlError = this.validateGitHubProxyUrl(this.selectedGitHubProxy);
      if (!this.githubProxyUrlError) {
        localStorage.setItem('selectedGitHubProxy', this.selectedGitHubProxy);
      }
    },
  },

```

```
        this.selectedGitHubProxy = localStorage.getItem('selectedGitHubProxy') || "";

```

You will also need to update the template section of `Settings.vue` to:
- Bind the input for the GitHub proxy URL to `selectedGitHubProxy`.
- Use `@change` or `@input` to call `onGitHubProxyUrlChange`.
- Display `githubProxyUrlError` as a validation message to the user.

Example (to be placed in the template):
```vue
<input
  v-model="selectedGitHubProxy"
  @change="onGitHubProxyUrlChange"
  placeholder="Enter GitHub Proxy URL (https://...)"
/>
<div v-if="githubProxyUrlError" class="error-message">{{ githubProxyUrlError }}</div>
```
You may need to adjust the template code to fit your UI framework and style.
</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.

return filtered;
};

const getProviderIcon = (type) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): 图标选择逻辑可能无法稳健地处理模糊或相似的提供商名称。

使用 type.startsWith(key) 可能会在提供商名称重叠时导致不正确的图标分配。为了提高准确性,请使用更严格的匹配或按降序长度对键进行排序,以优先考虑更具体的匹配。

Original comment in English

suggestion (bug_risk): Icon selection logic may not handle ambiguous or similar provider names robustly.

Using type.startsWith(key) may cause incorrect icon assignment when provider names overlap. To improve accuracy, use stricter matching or sort keys by descending length to prioritize more specific matches.

}
};

const deleteProvider = (provider) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: 直接使用 confirm 进行用户提示可能与 UI/UX 不一致。

考虑将浏览器的 confirm 对话框替换为 Vuetify 对话框,以获得更一致和易于访问的用户体验。

Original comment in English

suggestion: Direct use of confirm for user prompts may not be consistent with UI/UX.

Consider replacing the browser's confirm dialog with a Vuetify dialog for a more consistent and accessible user experience.

:append-icon="show1 ? 'mdi-eye' : 'mdi-eye-off'" :type="show1 ? 'text' : 'password'"
@click:append="show1 = !show1" class="pwd-input" prepend-inner-icon="mdi-lock" :disabled="loading"></v-text-field>

<v-btn color="secondary" :loading="isSubmitting || loading" block class="login-btn mt-8" variant="flat" size="large"
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): 登录按钮在 valid 时禁用,这可能无法反映表单的有效性。

当表单有效时,应启用该按钮。请反转逻辑或使用正确的验证状态进行 :disabled 绑定。

Original comment in English

issue (bug_risk): Login button disables on valid, which may not reflect form validity.

The button should be enabled when the form is valid. Please invert the logic or use the correct validation state for the :disabled binding.

<v-icon>mdi-translate</v-icon>
</template>
</v-list-item>
</template>
Copy link
Contributor

Choose a reason for hiding this comment

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

🚨 suggestion (security): 没有对用户提供的 GitHub 代理 URL 进行验证。

验证用户输入的 GitHub 代理 URL 可以防止格式错误的条目和潜在的安全风险。建议强制执行正确的 URL 格式并限制为 HTTPS。

建议的实施方式:

  components: {
    WaitingForRestart,
  },
  data() {
    return {
      selectedGitHubProxy: localStorage.getItem('selectedGitHubProxy') || "",
      githubProxyUrlError: "",
    };
  },
  methods: {
    validateGitHubProxyUrl(url) {
      // Only allow HTTPS URLs, basic validation
      try {
        const parsed = new URL(url);
        if (parsed.protocol !== "https:") {
          return "URL must start with https://";
        }
        return "";
      } catch (e) {
        return "Invalid URL format";
      }
    },
    onGitHubProxyUrlChange() {
      this.githubProxyUrlError = this.validateGitHubProxyUrl(this.selectedGitHubProxy);
      if (!this.githubProxyUrlError) {
        localStorage.setItem('selectedGitHubProxy', this.selectedGitHubProxy);
      }
    },
  },

        this.selectedGitHubProxy = localStorage.getItem('selectedGitHubProxy') || "";

您还需要更新 Settings.vue 的模板部分,以:

  • 将 GitHub 代理 URL 的输入绑定到 selectedGitHubProxy
  • 使用 @change@input 调用 onGitHubProxyUrlChange
  • githubProxyUrlError 显示为向用户的验证消息。

示例(放置在模板中):

<input
  v-model="selectedGitHubProxy"
  @change="onGitHubProxyUrlChange"
  placeholder="Enter GitHub Proxy URL (https://...)"
/>
<div v-if="githubProxyUrlError" class="error-message">{{ githubProxyUrlError }}</div>

您可能需要调整模板代码以适应您的 UI 框架和样式。

Original comment in English

🚨 suggestion (security): No validation for user-provided GitHub proxy URLs.

Validating user input for GitHub proxy URLs can prevent malformed entries and potential security risks. Recommend enforcing proper URL format and restricting to HTTPS.

Suggested implementation:

  components: {
    WaitingForRestart,
  },
  data() {
    return {
      selectedGitHubProxy: localStorage.getItem('selectedGitHubProxy') || "",
      githubProxyUrlError: "",
    };
  },
  methods: {
    validateGitHubProxyUrl(url) {
      // Only allow HTTPS URLs, basic validation
      try {
        const parsed = new URL(url);
        if (parsed.protocol !== "https:") {
          return "URL must start with https://";
        }
        return "";
      } catch (e) {
        return "Invalid URL format";
      }
    },
    onGitHubProxyUrlChange() {
      this.githubProxyUrlError = this.validateGitHubProxyUrl(this.selectedGitHubProxy);
      if (!this.githubProxyUrlError) {
        localStorage.setItem('selectedGitHubProxy', this.selectedGitHubProxy);
      }
    },
  },

        this.selectedGitHubProxy = localStorage.getItem('selectedGitHubProxy') || "";

You will also need to update the template section of Settings.vue to:

  • Bind the input for the GitHub proxy URL to selectedGitHubProxy.
  • Use @change or @input to call onGitHubProxyUrlChange.
  • Display githubProxyUrlError as a validation message to the user.

Example (to be placed in the template):

<input
  v-model="selectedGitHubProxy"
  @change="onGitHubProxyUrlChange"
  placeholder="Enter GitHub Proxy URL (https://...)"
/>
<div v-if="githubProxyUrlError" class="error-message">{{ githubProxyUrlError }}</div>

You may need to adjust the template code to fit your UI framework and style.

@IGCrystal
Copy link
Member

IGCrystal commented Jun 15, 2025

感觉单体翻译文件(zh-CN.json 和 en-US.json)不利于后期维护。需要分结构出来,不过我也在做这个 #1822

@Soulter Soulter closed this Jun 16, 2025
@Soulter
Copy link
Member Author

Soulter commented Jun 16, 2025

Closed due to a better solution #1822

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.

2 participants