This is an automated email from the ASF dual-hosted git repository.

liutianyou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git


The following commit(s) were added to refs/heads/master by this push:
     new 67c11b9128 fix: Add i18n support for setting-drawer component (#4006)
67c11b9128 is described below

commit 67c11b9128b0bb7bff25bb58c0a178ac61698f3a
Author: Talha Amjad <[email protected]>
AuthorDate: Fri Feb 27 19:48:44 2026 +0500

    fix: Add i18n support for setting-drawer component (#4006)
    
    Co-authored-by: Logic <[email protected]>
    Co-authored-by: Tomsun28 <[email protected]>
    Co-authored-by: Sherlock Yin <[email protected]>
    Co-authored-by: liutianyou <[email protected]>
---
 web-app/src/app/layout/basic/basic.component.ts    |   2 +-
 .../setting-drawer-i18n.directive.spec.ts          | 159 +++++++++
 .../directives/setting-drawer-i18n.directive.ts    | 377 +++++++++++++++++++++
 web-app/src/app/layout/layout.module.ts            |   4 +-
 web-app/src/assets/i18n/en-US.json                 |  18 +-
 web-app/src/assets/i18n/ja-JP.json                 |  18 +-
 web-app/src/assets/i18n/pt-BR.json                 |  18 +-
 web-app/src/assets/i18n/zh-CN.json                 |  18 +-
 web-app/src/assets/i18n/zh-TW.json                 |  18 +-
 9 files changed, 625 insertions(+), 7 deletions(-)

diff --git a/web-app/src/app/layout/basic/basic.component.ts 
b/web-app/src/app/layout/basic/basic.component.ts
index 1c92a136f4..1e7ab83474 100644
--- a/web-app/src/app/layout/basic/basic.component.ts
+++ b/web-app/src/app/layout/basic/basic.component.ts
@@ -67,7 +67,7 @@ import { AiChatModalService } from 
'../../shared/services/ai-chat-modal.service'
         Licensed under the Apache License, Version 2.0
       </div>
     </global-footer>
-    <setting-drawer *ngIf="showSettingDrawer"></setting-drawer>
+    <setting-drawer *ngIf="showSettingDrawer" 
appSettingDrawerI18n></setting-drawer>
 
     <!-- AI Chat Button -->
     <div class="ai-chat-button-container">
diff --git 
a/web-app/src/app/layout/basic/directives/setting-drawer-i18n.directive.spec.ts 
b/web-app/src/app/layout/basic/directives/setting-drawer-i18n.directive.spec.ts
new file mode 100644
index 0000000000..55519cc87f
--- /dev/null
+++ 
b/web-app/src/app/layout/basic/directives/setting-drawer-i18n.directive.spec.ts
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { HttpClientTestingModule, HttpTestingController } from 
'@angular/common/http/testing';
+import { Component, NgZone } from '@angular/core';
+import { ComponentFixture, TestBed, fakeAsync, tick } from 
'@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { I18NService } from '@core';
+import { ALAIN_I18N_TOKEN } from '@delon/theme';
+import { of } from 'rxjs';
+
+import { SettingDrawerI18nDirective } from './setting-drawer-i18n.directive';
+
+@Component({
+  template: `<setting-drawer appSettingDrawerI18n>
+    <div class="setting-drawer__content">
+      <div>主题色</div>
+      <div>设置</div>
+      <div>配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改参数配置文件 src/styles/theme.less</div>
+    </div>
+  </setting-drawer>`
+})
+class TestComponent {}
+
+describe('SettingDrawerI18nDirective', () => {
+  let component: TestComponent;
+  let fixture: ComponentFixture<TestComponent>;
+  let directive: SettingDrawerI18nDirective;
+  let i18nService: jasmine.SpyObj<I18NService>;
+  let httpMock: HttpTestingController;
+  let mockTranslations: { [key: string]: string };
+
+  const mockI18nData = {
+    'zh-CN': {
+      'setting.drawer.theme.color': '主题色',
+      'setting.drawer.settings': '设置',
+      'setting.drawer.info.message': '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改参数配置文件 
src/styles/theme.less'
+    },
+    'en-US': {
+      'setting.drawer.theme.color': 'Theme Color',
+      'setting.drawer.settings': 'Settings',
+      'setting.drawer.info.message':
+        'The configuration panel is only for preview in the development 
environment, it will not be displayed in the production environment. Please 
copy and manually modify the parameter configuration file src/styles/theme.less'
+    },
+    'ja-JP': {
+      'setting.drawer.theme.color': 'テーマカラー',
+      'setting.drawer.settings': '設定',
+      'setting.drawer.info.message':
+        '設定パネルは開発環境でのプレビューのみに使用され、本番環境では表示されません。コピーして、パラメータ設定ファイル 
src/styles/theme.less を手動で変更してください'
+    },
+    'pt-BR': {
+      'setting.drawer.theme.color': 'Cor do Tema',
+      'setting.drawer.settings': 'Configurações',
+      'setting.drawer.info.message':
+        'O painel de configuração é apenas para visualização no ambiente de 
desenvolvimento, não será exibido no ambiente de produção. Por favor, copie e 
modifique manualmente o arquivo de configuração de parâmetros 
src/styles/theme.less'
+    },
+    'zh-TW': {
+      'setting.drawer.theme.color': '主題色',
+      'setting.drawer.settings': '設置',
+      'setting.drawer.info.message': '配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改參數配置文件 
src/styles/theme.less'
+    }
+  };
+
+  beforeEach(async () => {
+    mockTranslations = {
+      'setting.drawer.theme.color': 'Theme Color',
+      'setting.drawer.settings': 'Settings',
+      'setting.drawer.info.message':
+        'The configuration panel is only for preview in the development 
environment, it will not be displayed in the production environment. Please 
copy and manually modify the parameter configuration file src/styles/theme.less'
+    };
+
+    const i18nServiceSpy = jasmine.createSpyObj('I18NService', ['fanyi', 
'change'], {
+      change: of('en-US')
+    });
+
+    i18nServiceSpy.fanyi.and.callFake((key: string) => {
+      return mockTranslations[key] || key;
+    });
+
+    await TestBed.configureTestingModule({
+      imports: [HttpClientTestingModule],
+      declarations: [TestComponent, SettingDrawerI18nDirective],
+      providers: [{ provide: ALAIN_I18N_TOKEN, useValue: i18nServiceSpy }, 
NgZone]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(TestComponent);
+    component = fixture.componentInstance;
+    i18nService = TestBed.inject(ALAIN_I18N_TOKEN) as 
jasmine.SpyObj<I18NService>;
+    httpMock = TestBed.inject(HttpTestingController);
+
+    const directiveEl = 
fixture.debugElement.query(By.directive(SettingDrawerI18nDirective));
+    if (directiveEl) {
+      directive = directiveEl.injector.get(SettingDrawerI18nDirective);
+    }
+  });
+
+  afterEach(() => {
+    httpMock.verify();
+  });
+
+  it('should create', () => {
+    expect(directive).toBeTruthy();
+  });
+
+  it('should load mappings from i18n files', fakeAsync(() => {
+    fixture.detectChanges();
+
+    const languages = ['zh-CN', 'en-US', 'ja-JP', 'pt-BR', 'zh-TW'];
+    const requests = languages.map(lang => 
httpMock.expectOne(`./assets/i18n/${lang}.json`));
+
+    languages.forEach((lang, index) => {
+      requests[index].flush(mockI18nData[lang as keyof typeof mockI18nData]);
+    });
+
+    tick(100);
+    fixture.detectChanges();
+
+    expect(i18nService.fanyi).toHaveBeenCalled();
+  }));
+
+  it('should replace Chinese text with translations', fakeAsync(() => {
+    fixture.detectChanges();
+
+    const languages = ['zh-CN', 'en-US', 'ja-JP', 'pt-BR', 'zh-TW'];
+    const requests = languages.map(lang => 
httpMock.expectOne(`./assets/i18n/${lang}.json`));
+
+    languages.forEach((lang, index) => {
+      requests[index].flush(mockI18nData[lang as keyof typeof mockI18nData]);
+    });
+
+    tick(2000);
+    fixture.detectChanges();
+    tick(100);
+
+    const content = 
fixture.nativeElement.querySelector('.setting-drawer__content');
+    if (content) {
+      const themeColorDiv = content.querySelector('div:first-child');
+      if (themeColorDiv) {
+        expect(themeColorDiv.textContent).toContain('Theme Color');
+      }
+    }
+  }));
+});
diff --git 
a/web-app/src/app/layout/basic/directives/setting-drawer-i18n.directive.ts 
b/web-app/src/app/layout/basic/directives/setting-drawer-i18n.directive.ts
new file mode 100644
index 0000000000..3e45a41605
--- /dev/null
+++ b/web-app/src/app/layout/basic/directives/setting-drawer-i18n.directive.ts
@@ -0,0 +1,377 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { HttpClient } from '@angular/common/http';
+import { Directive, ElementRef, Inject, OnInit, OnDestroy, AfterViewInit, 
NgZone } from '@angular/core';
+import { I18NService } from '@core';
+import { ALAIN_I18N_TOKEN } from '@delon/theme';
+import { Subject, forkJoin } from 'rxjs';
+import { takeUntil, map } from 'rxjs/operators';
+
+@Directive({
+  selector: 'setting-drawer[appSettingDrawerI18n]'
+})
+export class SettingDrawerI18nDirective implements OnInit, AfterViewInit, 
OnDestroy {
+  private destroy$ = new Subject<void>();
+  private mutationObserver?: MutationObserver;
+  private replaceInterval?: any;
+
+  private textMappings: { [key: string]: string } = {};
+  private mappingsReady = false;
+
+  private readonly settingDrawerKeys = [
+    'setting.drawer.theme.color',
+    'setting.drawer.settings',
+    'setting.drawer.top',
+    'setting.drawer.sidebar',
+    'setting.drawer.content',
+    'setting.drawer.other',
+    'setting.drawer.height',
+    'setting.drawer.background.color',
+    'setting.drawer.background.color.default',
+    'setting.drawer.top.padding',
+    'setting.drawer.fixed.header.sidebar',
+    'setting.drawer.colorblind.mode',
+    'setting.drawer.preview',
+    'setting.drawer.reset',
+    'setting.drawer.copy',
+    'setting.drawer.info.message'
+  ];
+
+  private readonly languages = ['zh-CN', 'en-US', 'ja-JP', 'pt-BR', 'zh-TW'];
+
+  constructor(
+    private el: ElementRef<HTMLElement>,
+    @Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService,
+    private ngZone: NgZone,
+    private http: HttpClient
+  ) {
+    this.loadMappingsFromI18nFiles();
+  }
+
+  private loadMappingsFromI18nFiles(): void {
+    const requests = this.languages.map(lang =>
+      this.http.get<{ [key: string]: string 
}>(`./assets/i18n/${lang}.json`).pipe(map(data => ({ lang, data })))
+    );
+
+    forkJoin(requests)
+      .pipe(takeUntil(this.destroy$))
+      .subscribe(results => {
+        this.textMappings = {};
+
+        for (const key of this.settingDrawerKeys) {
+          for (const result of results) {
+            const translation = result.data[key];
+            if (translation && translation.trim()) {
+              this.textMappings[translation] = key;
+            }
+          }
+        }
+
+        const sortedEntries = Object.entries(this.textMappings).sort((a, b) => 
b[0].length - a[0].length);
+        this.textMappings = Object.fromEntries(sortedEntries);
+
+        this.mappingsReady = true;
+
+        setTimeout(() => this.replaceText(), 0);
+      });
+  }
+
+  ngOnInit(): void {
+    setTimeout(() => this.replaceText(), 0);
+    setTimeout(() => this.replaceText(), 100);
+    setTimeout(() => this.replaceText(), 500);
+    setTimeout(() => this.replaceText(), 1000);
+  }
+
+  ngAfterViewInit(): void {
+    setTimeout(() => this.replaceText(), 0);
+    setTimeout(() => this.replaceText(), 100);
+    setTimeout(() => this.replaceText(), 200);
+    setTimeout(() => this.replaceText(), 500);
+    setTimeout(() => this.replaceText(), 1000);
+    setTimeout(() => this.replaceText(), 2000);
+
+    this.ngZone.runOutsideAngular(() => {
+      let timeoutId: any;
+      this.mutationObserver = new MutationObserver(mutations => {
+        if (mutations.length > 0) {
+          if (timeoutId) {
+            clearTimeout(timeoutId);
+          }
+          timeoutId = setTimeout(() => {
+            this.ngZone.run(() => {
+              this.replaceText();
+            });
+          }, 50);
+        }
+      });
+
+      this.mutationObserver.observe(this.el.nativeElement, {
+        childList: true,
+        subtree: true,
+        characterData: true,
+        attributes: false
+      });
+    });
+
+    this.replaceInterval = setInterval(() => {
+      this.replaceText();
+    }, 500);
+
+    this.i18nSvc.change.pipe(takeUntil(this.destroy$)).subscribe(() => {
+      this.replaceText();
+    });
+  }
+
+  ngOnDestroy(): void {
+    if (this.mutationObserver) {
+      this.mutationObserver.disconnect();
+    }
+    if (this.replaceInterval) {
+      clearInterval(this.replaceInterval);
+    }
+    this.destroy$.next();
+    this.destroy$.complete();
+  }
+
+  private replaceText(): void {
+    if (!this.mappingsReady || Object.keys(this.textMappings).length === 0) {
+      return;
+    }
+
+    requestAnimationFrame(() => {
+      const drawerContent =
+        document.querySelector('.setting-drawer__content') || 
document.querySelector('setting-drawer') || this.el.nativeElement;
+
+      if (!drawerContent) {
+        return;
+      }
+
+      const currentTranslations = new Map<string, string>();
+      for (const key of this.settingDrawerKeys) {
+        const translation = this.i18nSvc.fanyi(key);
+        if (translation && translation !== key) {
+          currentTranslations.set(key, translation);
+        }
+      }
+
+      const currentTranslationValues = 
Array.from(currentTranslations.values());
+
+      const processedNodes = new Set<Node>();
+
+      const walker = document.createTreeWalker(drawerContent as Node, 
NodeFilter.SHOW_TEXT, {
+        acceptNode: (node: Node) => {
+          const parent = node.parentElement;
+          if (parent && (parent.tagName === 'SCRIPT' || parent.tagName === 
'STYLE')) {
+            return NodeFilter.FILTER_REJECT;
+          }
+          if (parent && (parent.tagName === 'INPUT' || parent.tagName === 
'TEXTAREA' || parent.tagName === 'SELECT')) {
+            return NodeFilter.FILTER_REJECT;
+          }
+          return NodeFilter.FILTER_ACCEPT;
+        }
+      });
+
+      let textNode;
+      while ((textNode = walker.nextNode())) {
+        if (processedNodes.has(textNode)) continue;
+        processedNodes.add(textNode);
+
+        const textContent = textNode.textContent || '';
+        if (!textContent.trim()) continue;
+
+        let newText = textContent;
+        let hasChanges = false;
+
+        const alreadyTranslated = currentTranslationValues.some(trans => 
textContent.includes(trans));
+        if (alreadyTranslated) {
+          continue;
+        }
+
+        for (const [knownText, translationKey] of 
Object.entries(this.textMappings)) {
+          const currentTranslation = currentTranslations.get(translationKey);
+          if (!currentTranslation) continue;
+
+          if (newText.includes(knownText) && knownText !== currentTranslation 
&& !newText.includes(currentTranslation)) {
+            newText = newText.replace(new RegExp(this.escapeRegExp(knownText), 
'g'), currentTranslation);
+            hasChanges = true;
+          }
+        }
+
+        if (hasChanges && newText !== textContent) {
+          textNode.textContent = newText;
+        }
+      }
+
+      const allElements = (drawerContent as HTMLElement).querySelectorAll('*');
+      for (let i = 0; i < allElements.length; i++) {
+        const el = allElements[i];
+        const htmlElement = el as HTMLElement;
+
+        if (['SCRIPT', 'STYLE', 'INPUT', 'TEXTAREA', 
'SELECT'].includes(htmlElement.tagName)) {
+          continue;
+        }
+
+        if (htmlElement.children.length === 0 && htmlElement.textContent) {
+          const textContent = htmlElement.textContent;
+          if (!textContent.trim()) continue;
+
+          if (htmlElement.childNodes.length === 1 && 
htmlElement.childNodes[0].nodeType === Node.TEXT_NODE) {
+            if (processedNodes.has(htmlElement.childNodes[0])) {
+              continue;
+            }
+          }
+
+          const alreadyTranslated = currentTranslationValues.some(trans => 
textContent.includes(trans));
+          if (alreadyTranslated) {
+            continue;
+          }
+
+          let newText = textContent;
+          let hasChanges = false;
+
+          for (const [knownText, translationKey] of 
Object.entries(this.textMappings)) {
+            const currentTranslation = currentTranslations.get(translationKey);
+            if (!currentTranslation) continue;
+
+            if (newText.includes(knownText) && knownText !== 
currentTranslation && !newText.includes(currentTranslation)) {
+              newText = newText.replace(new 
RegExp(this.escapeRegExp(knownText), 'g'), currentTranslation);
+              hasChanges = true;
+            }
+          }
+
+          if (hasChanges && newText !== textContent) {
+            htmlElement.textContent = newText;
+          }
+        }
+      }
+
+      const infoMessageKey = 'setting.drawer.info.message';
+      const currentInfoMessage = currentTranslations.get(infoMessageKey);
+
+      if (currentInfoMessage) {
+        const allInfoMessages: string[] = [];
+        for (const [text, key] of Object.entries(this.textMappings)) {
+          if (key === infoMessageKey && text !== currentInfoMessage) {
+            allInfoMessages.push(text);
+          }
+        }
+
+        const chinesePhrases = [
+          '配置栏只在开发环境用于',
+          '配置栏只在開發環境用於',
+          '生产环境不会展现',
+          '生產環境不會展現',
+          '请拷贝后手动修改',
+          '請拷貝後手動修改',
+          '参数配置文件',
+          '參數配置文件',
+          'src/styles/theme.less'
+        ];
+
+        const containers = (drawerContent as 
HTMLElement).querySelectorAll('div, span, p, li, td, section, article');
+        for (let i = 0; i < containers.length; i++) {
+          const container = containers[i] as HTMLElement;
+          const containerText = container.textContent || '';
+
+          if (containerText.includes(currentInfoMessage)) {
+            continue;
+          }
+
+          let containsInfoMessage = false;
+          for (const knownInfoMsg of allInfoMessages) {
+            if (containerText.includes(knownInfoMsg)) {
+              containsInfoMessage = true;
+              break;
+            }
+          }
+
+          if (!containsInfoMessage) {
+            containsInfoMessage = chinesePhrases.some(phrase => 
containerText.includes(phrase));
+          }
+
+          const hasChineseChars = /[\u4e00-\u9fa5]/.test(containerText);
+          const isLongEnough = containerText.length > 50;
+
+          if (containsInfoMessage || (hasChineseChars && isLongEnough && 
containerText.includes('src/styles/theme.less'))) {
+            const walker = document.createTreeWalker(container, 
NodeFilter.SHOW_TEXT, null);
+
+            let textNode;
+            const textNodes: Node[] = [];
+
+            while ((textNode = walker.nextNode())) {
+              if (!processedNodes.has(textNode)) {
+                textNodes.push(textNode);
+              }
+            }
+
+            if (textNodes.length > 0) {
+              textNodes[0].textContent = currentInfoMessage;
+              processedNodes.add(textNodes[0]);
+
+              for (let j = 1; j < textNodes.length; j++) {
+                textNodes[j].textContent = '';
+                processedNodes.add(textNodes[j]);
+              }
+            } else {
+              container.textContent = currentInfoMessage;
+            }
+            break;
+          }
+        }
+      }
+
+      for (let i = 0; i < allElements.length; i++) {
+        const el = allElements[i];
+        const htmlElement = el as HTMLElement;
+
+        const attributesToCheck = ['placeholder', 'title', 'aria-label', 
'alt'];
+        for (let j = 0; j < attributesToCheck.length; j++) {
+          const attr = attributesToCheck[j];
+          if (htmlElement.hasAttribute(attr)) {
+            const attrValue = htmlElement.getAttribute(attr);
+            if (attrValue) {
+              let newValue = attrValue;
+              let hasChanges = false;
+
+              for (const [knownText, translationKey] of 
Object.entries(this.textMappings)) {
+                const currentTranslation = 
currentTranslations.get(translationKey);
+                if (!currentTranslation) continue;
+
+                if (newValue.includes(knownText) && knownText !== 
currentTranslation && !newValue.includes(currentTranslation)) {
+                  newValue = newValue.replace(new 
RegExp(this.escapeRegExp(knownText), 'g'), currentTranslation);
+                  hasChanges = true;
+                }
+              }
+
+              if (hasChanges && newValue !== attrValue) {
+                htmlElement.setAttribute(attr, newValue);
+              }
+            }
+          }
+        }
+      }
+    });
+  }
+
+  private escapeRegExp(text: string): string {
+    return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+  }
+}
diff --git a/web-app/src/app/layout/layout.module.ts 
b/web-app/src/app/layout/layout.module.ts
index f10d631068..2dc0ed5fb7 100644
--- a/web-app/src/app/layout/layout.module.ts
+++ b/web-app/src/app/layout/layout.module.ts
@@ -25,8 +25,10 @@ import { HeaderAiChatComponent } from 
'./basic/widgets/chat-input.component';
 import { HeaderUserComponent } from './basic/widgets/user.component';
 import { HeaderNotifyComponent } from './basic/widgets/notify.component';
 import { LayoutBlankComponent } from './blank/blank.component';
+import { SettingDrawerI18nDirective } from 
'./basic/directives/setting-drawer-i18n.directive';
 
 const COMPONENTS = [LayoutBasicComponent, LayoutBlankComponent, 
HeaderI18nComponent];
+const DIRECTIVES = [SettingDrawerI18nDirective];
 
 const HEADER_COMPONENTS = [
   HeaderAiChatComponent,
@@ -78,7 +80,7 @@ const PASSPORT = [LayoutPassportComponent];
     NzTooltipDirective,
     NzCheckboxComponent
   ],
-  declarations: [...COMPONENTS, ...HEADER_COMPONENTS, ...PASSPORT],
+  declarations: [...COMPONENTS, ...HEADER_COMPONENTS, ...PASSPORT, 
...DIRECTIVES],
   exports: [...COMPONENTS, ...PASSPORT]
 })
 export class LayoutModule {}
diff --git a/web-app/src/assets/i18n/en-US.json 
b/web-app/src/assets/i18n/en-US.json
index 013c6c9b72..9b6dc5f423 100644
--- a/web-app/src/assets/i18n/en-US.json
+++ b/web-app/src/assets/i18n/en-US.json
@@ -1149,5 +1149,21 @@
   "ai.chat.error.chat.response": "Failed to get AI response:",
   "ai.chat.error.processing": "Sorry, there was an error processing your 
request. Please check if the AI Agent service is running and try again.",
   "ai.chat.offline.mode": "AI Chat service unavailable. Running in offline 
mode.",
-  "ai.chat.offline.response": "I apologize, but the AI Chat service is 
currently unavailable. Please ensure the HertzBeat AI module is running and try 
again later."
+  "ai.chat.offline.response": "I apologize, but the AI Chat service is 
currently unavailable. Please ensure the HertzBeat AI module is running and try 
again later.",
+  "setting.drawer.theme.color": "Theme Color",
+  "setting.drawer.settings": "Settings",
+  "setting.drawer.top": "Top",
+  "setting.drawer.sidebar": "Sidebar",
+  "setting.drawer.content": "Content",
+  "setting.drawer.other": "Other",
+  "setting.drawer.height": "Height",
+  "setting.drawer.background.color": "Background Color",
+  "setting.drawer.background.color.default": "Default same as main color 
system",
+  "setting.drawer.top.padding": "Top left and right padding",
+  "setting.drawer.fixed.header.sidebar": "Fixed header and sidebar",
+  "setting.drawer.colorblind.mode": "Colorblind mode",
+  "setting.drawer.preview": "Preview",
+  "setting.drawer.reset": "Reset",
+  "setting.drawer.copy": "Copy",
+  "setting.drawer.info.message": "The configuration panel is only for preview 
in the development environment, it will not be displayed in the production 
environment. Please copy and manually modify the parameter configuration file 
src/styles/theme.less"
 }
diff --git a/web-app/src/assets/i18n/ja-JP.json 
b/web-app/src/assets/i18n/ja-JP.json
index 99e4206259..ed2648d9ff 100644
--- a/web-app/src/assets/i18n/ja-JP.json
+++ b/web-app/src/assets/i18n/ja-JP.json
@@ -1095,5 +1095,21 @@
   "ai.chat.error.chat.response": "AI の応答の取得に失敗しました:",
   "ai.chat.error.processing": "申し訳ございませんが、リクエストの処理中にエラーが発生しました。AI 
エージェントサービスが実行されているかご確認の上、再度お試しください。",
   "ai.chat.offline.mode": "AI チャットサービスが利用できません。オフラインモードで実行中です。",
-  "ai.chat.offline.response": "申し訳ございませんが、AI チャットサービスは現在利用できません。HertzBeat AI 
モジュールが実行されていることを確認して、後でもう一度お試しください。"
+  "ai.chat.offline.response": "申し訳ございませんが、AI チャットサービスは現在利用できません。HertzBeat AI 
モジュールが実行されていることを確認して、後でもう一度お試しください。",
+  "setting.drawer.theme.color": "テーマカラー",
+  "setting.drawer.settings": "設定",
+  "setting.drawer.top": "上部",
+  "setting.drawer.sidebar": "サイドバー",
+  "setting.drawer.content": "コンテンツ",
+  "setting.drawer.other": "その他",
+  "setting.drawer.height": "高さ",
+  "setting.drawer.background.color": "背景色",
+  "setting.drawer.background.color.default": "デフォルトはメインカラーシステムと同じ",
+  "setting.drawer.top.padding": "上部左右のパディング",
+  "setting.drawer.fixed.header.sidebar": "ヘッダーとサイドバーを固定",
+  "setting.drawer.colorblind.mode": "色覚異常モード",
+  "setting.drawer.preview": "プレビュー",
+  "setting.drawer.reset": "リセット",
+  "setting.drawer.copy": "コピー",
+  "setting.drawer.info.message": 
"設定パネルは開発環境でのプレビューのみに使用され、本番環境では表示されません。コピーして、パラメータ設定ファイル src/styles/theme.less 
を手動で変更してください"
 }
diff --git a/web-app/src/assets/i18n/pt-BR.json 
b/web-app/src/assets/i18n/pt-BR.json
index ea86ea3072..8b255db6f2 100644
--- a/web-app/src/assets/i18n/pt-BR.json
+++ b/web-app/src/assets/i18n/pt-BR.json
@@ -1228,5 +1228,21 @@
   "common.notify.import-progress": "Progresso da Importação",
   "common.notify.query-fail": "Falha na Consulta",
   "define.disable": "Desabilitar",
-  "define.enable": "Habilitar"
+  "define.enable": "Habilitar",
+  "setting.drawer.theme.color": "Cor do Tema",
+  "setting.drawer.settings": "Configurações",
+  "setting.drawer.top": "Topo",
+  "setting.drawer.sidebar": "Barra Lateral",
+  "setting.drawer.content": "Conteúdo",
+  "setting.drawer.other": "Outro",
+  "setting.drawer.height": "Altura",
+  "setting.drawer.background.color": "Cor de Fundo",
+  "setting.drawer.background.color.default": "Padrão igual ao sistema de cores 
principal",
+  "setting.drawer.top.padding": "Preenchimento superior esquerdo e direito",
+  "setting.drawer.fixed.header.sidebar": "Cabeçalho e barra lateral fixos",
+  "setting.drawer.colorblind.mode": "Modo daltônico",
+  "setting.drawer.preview": "Visualizar",
+  "setting.drawer.reset": "Redefinir",
+  "setting.drawer.copy": "Copiar",
+  "setting.drawer.info.message": "O painel de configuração é apenas para 
visualização no ambiente de desenvolvimento, não será exibido no ambiente de 
produção. Por favor, copie e modifique manualmente o arquivo de configuração de 
parâmetros src/styles/theme.less"
 }
diff --git a/web-app/src/assets/i18n/zh-CN.json 
b/web-app/src/assets/i18n/zh-CN.json
index d6ed897a01..d2b8f6a597 100644
--- a/web-app/src/assets/i18n/zh-CN.json
+++ b/web-app/src/assets/i18n/zh-CN.json
@@ -1152,5 +1152,21 @@
   "ai.chat.error.chat.response": "获取 AI 回复失败:",
   "ai.chat.error.processing": "处理您的请求时出错。请检查 AI 智能体服务是否正在运行,然后重试。",
   "ai.chat.offline.mode": "AI 聊天服务不可用。正在离线模式下运行。",
-  "ai.chat.offline.response": "抱歉,AI 聊天服务当前不可用。请确保 HertzBeat AI 模块正在运行,稍后再试。"
+  "ai.chat.offline.response": "抱歉,AI 聊天服务当前不可用。请确保 HertzBeat AI 模块正在运行,稍后再试。",
+  "setting.drawer.theme.color": "主题色",
+  "setting.drawer.settings": "设置",
+  "setting.drawer.top": "顶部",
+  "setting.drawer.sidebar": "侧边栏",
+  "setting.drawer.content": "内容",
+  "setting.drawer.other": "其它",
+  "setting.drawer.height": "高",
+  "setting.drawer.background.color": "背景色",
+  "setting.drawer.background.color.default": "默认同主色系",
+  "setting.drawer.top.padding": "顶部左右内边距",
+  "setting.drawer.fixed.header.sidebar": "固定头和侧边栏",
+  "setting.drawer.colorblind.mode": "色弱模式",
+  "setting.drawer.preview": "预览",
+  "setting.drawer.reset": "重置",
+  "setting.drawer.copy": "拷贝",
+  "setting.drawer.info.message": "配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改参数配置文件 
src/styles/theme.less"
 }
diff --git a/web-app/src/assets/i18n/zh-TW.json 
b/web-app/src/assets/i18n/zh-TW.json
index 28470ff6d5..211027a20e 100644
--- a/web-app/src/assets/i18n/zh-TW.json
+++ b/web-app/src/assets/i18n/zh-TW.json
@@ -1109,5 +1109,21 @@
   "ai.chat.error.chat.response": "取得 AI 回覆失敗:",
   "ai.chat.error.processing": "處理您的請求時發生錯誤。請檢查 AI 智能體服務是否正在執行,然後重試。",
   "ai.chat.offline.mode": "AI 聊天服務不可用。正在離線模式下執行。",
-  "ai.chat.offline.response": "抱歉,AI 聊天服務目前不可用。請確保 HertzBeat AI 模組正在執行,稍後再試。"
+  "ai.chat.offline.response": "抱歉,AI 聊天服務目前不可用。請確保 HertzBeat AI 模組正在執行,稍後再試。",
+  "setting.drawer.theme.color": "主題色",
+  "setting.drawer.settings": "設置",
+  "setting.drawer.top": "頂部",
+  "setting.drawer.sidebar": "側邊欄",
+  "setting.drawer.content": "內容",
+  "setting.drawer.other": "其它",
+  "setting.drawer.height": "高",
+  "setting.drawer.background.color": "背景色",
+  "setting.drawer.background.color.default": "默認同主色系",
+  "setting.drawer.top.padding": "頂部左右內邊距",
+  "setting.drawer.fixed.header.sidebar": "固定頭和側邊欄",
+  "setting.drawer.colorblind.mode": "色弱模式",
+  "setting.drawer.preview": "預覽",
+  "setting.drawer.reset": "重置",
+  "setting.drawer.copy": "拷貝",
+  "setting.drawer.info.message": "配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改參數配置文件 
src/styles/theme.less"
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to