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]