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

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

commit 3832485f0fe73bca9ebd2f9db86797e970fa3efa
Author: Logic <[email protected]>
AuthorDate: Wed Jun 3 11:40:48 2026 +0800

    refactor: externalize backend localization messages
---
 .../impl/FeiShuAppAlertNotifyHandlerImpl.java      |  14 +-
 .../notice/impl/FlyBookAlertNotifyHandlerImpl.java |  15 +-
 .../impl/FeiShuAppAlertNotifyHandlerImplTest.java  |  17 ++-
 .../impl/FlyBookAlertNotifyHandlerImplTest.java    |  16 ++-
 .../service/HuaweiCloudExternAlertServiceTest.java |  28 +++-
 .../hertzbeat/common/entity/job/Metrics.java       |   3 +-
 .../entity/job/MetricsSourceLocalizationTest.java  |  41 ++++++
 .../dto/EvidenceDtoMigrationTest.java              |   2 +-
 .../hertzbeat/common/util/CommonUtilTest.java      |   4 +-
 .../common/entity/manager/ParamDefine.java         |   2 +-
 .../manager/ParamDefineSourceLocalizationTest.java |  41 ++++++
 .../EntityObservabilityDtoMigrationTest.java       |   3 +-
 .../support/ResourceBundleUtf8ControlTest.java     |   2 +-
 .../src/test/resources/msg.properties              |   2 +-
 .../manager/controller/AppControllerTest.java      |   4 +-
 .../manager/controller/MonitorControllerTest.java  |   6 +-
 .../manager/service/ObserveEntityServiceTest.java  |   7 +-
 ...ityDetailObservabilityReadModelServiceTest.java |   2 +-
 .../service/impl/OtlpGrpcIngestionServiceImpl.java |  26 ++--
 .../service/impl/OtlpIngestionMessages.java        |  45 ++++++
 .../impl/OtlpIngestionWorkspaceServiceImpl.java    | 156 +++++++++++++--------
 .../impl/EntityObservabilityGatewayImpl.java       |  59 ++++----
 .../shared/service/impl/ObservabilityMessages.java |  45 ++++++
 .../service/impl/TelemetryIntakeServiceImpl.java   |  57 ++++----
 .../main/resources/observability_en_US.properties  | 120 ++++++++++++++++
 .../main/resources/observability_zh_CN.properties  | 120 ++++++++++++++++
 .../controller/OtlpIngestionControllerTest.java    |   5 +-
 ...OtlpIngestionServiceSourceLocalizationTest.java |  47 +++++++
 .../OtlpIngestionWorkspaceServiceImplTest.java     |  21 ++-
 .../impl/EntityObservabilityGatewayImplTest.java   |  66 ++++++---
 .../impl/TelemetryIntakeServiceImplTest.java       |  40 +++++-
 31 files changed, 832 insertions(+), 184 deletions(-)

diff --git 
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/notice/impl/FeiShuAppAlertNotifyHandlerImpl.java
 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/notice/impl/FeiShuAppAlertNotifyHandlerImpl.java
index 51f961bff9..83dca2f0b4 100644
--- 
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/notice/impl/FeiShuAppAlertNotifyHandlerImpl.java
+++ 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/notice/impl/FeiShuAppAlertNotifyHandlerImpl.java
@@ -293,9 +293,9 @@ public class FeiShuAppAlertNotifyHandlerImpl extends 
AbstractAlertNotifyHandlerI
                                                   "tag": "button",
                                                   "text": {
                                                       "tag": "plain_text",
-                                                      "content": "登入控制台",
+                                                      "content": "%s",
                                                       "i18n_content": {
-                                                          "en_us": "Login In"
+                                                          "en_us": "%s"
                                                       }
                                                   },
                                                   "type": "default",
@@ -326,9 +326,9 @@ public class FeiShuAppAlertNotifyHandlerImpl extends 
AbstractAlertNotifyHandlerI
                       "header": {
                           "title": {
                               "tag": "plain_text",
-                              "content": "HertzBeat 告警",
+                              "content": "%s",
                               "i18n_content": {
-                                  "en_us": "HertzBeat Alarm"
+                                  "en_us": "%s"
                               }
                           },
                           "subtitle": {
@@ -355,7 +355,11 @@ public class FeiShuAppAlertNotifyHandlerImpl extends 
AbstractAlertNotifyHandlerI
         }
         String jsonStr = String.format(larkCardMessage,
                 notificationContent.replace("\"", "\\\"") + atUserElement,
-                alerterProperties.getConsoleUrl());
+                bundle.getString("alerter.notify.console"),
+                bundle.getString("alerter.notify.console"),
+                alerterProperties.getConsoleUrl(),
+                bundle.getString("alerter.notify.title"),
+                bundle.getString("alerter.notify.title"));
         return JsonUtil.fromJson(jsonStr);
     }
 
diff --git 
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/notice/impl/FlyBookAlertNotifyHandlerImpl.java
 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/notice/impl/FlyBookAlertNotifyHandlerImpl.java
index ab61acb199..1aa7944a95 100644
--- 
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/notice/impl/FlyBookAlertNotifyHandlerImpl.java
+++ 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/notice/impl/FlyBookAlertNotifyHandlerImpl.java
@@ -137,9 +137,9 @@ final class FlyBookAlertNotifyHandlerImpl extends 
AbstractAlertNotifyHandlerImpl
                                                   "tag": "button",
                                                   "text": {
                                                       "tag": "plain_text",
-                                                      "content": "登入控制台",
+                                                      "content": "%s",
                                                       "i18n_content": {
-                                                          "en_us": "Login In"
+                                                          "en_us": "%s"
                                                       }
                                                   },
                                                   "type": "default",
@@ -170,9 +170,9 @@ final class FlyBookAlertNotifyHandlerImpl extends 
AbstractAlertNotifyHandlerImpl
                       "header": {
                           "title": {
                               "tag": "plain_text",
-                              "content": "HertzBeat 告警",
+                              "content": "%s",
                               "i18n_content": {
-                                  "en_us": "HertzBeat Alarm"
+                                  "en_us": "%s"
                               }
                           },
                           "subtitle": {
@@ -200,7 +200,12 @@ final class FlyBookAlertNotifyHandlerImpl extends 
AbstractAlertNotifyHandlerImpl
 
         return String.format(larkCardMessage,
                 notificationContent.replace("\"", "\\\"") + atUserElement,
-                alerterProperties.getConsoleUrl(), TITLE_COLOR[priority]);
+                bundle.getString("alerter.notify.console"),
+                bundle.getString("alerter.notify.console"),
+                alerterProperties.getConsoleUrl(),
+                bundle.getString("alerter.notify.title"),
+                bundle.getString("alerter.notify.title"),
+                TITLE_COLOR[priority]);
     }
 
     @Override
diff --git 
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/notice/impl/FeiShuAppAlertNotifyHandlerImplTest.java
 
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/notice/impl/FeiShuAppAlertNotifyHandlerImplTest.java
index 8ce2607188..1a05827e97 100644
--- 
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/notice/impl/FeiShuAppAlertNotifyHandlerImplTest.java
+++ 
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/notice/impl/FeiShuAppAlertNotifyHandlerImplTest.java
@@ -26,9 +26,11 @@ import 
org.apache.hertzbeat.common.entity.alerter.SingleAlert;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.client.RestTemplate;
@@ -37,8 +39,11 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.ResourceBundle;
+import java.util.regex.Pattern;
 
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -51,6 +56,8 @@ import static org.mockito.Mockito.when;
 @ExtendWith(MockitoExtension.class)
 class FeiShuAppAlertNotifyHandlerImplTest {
 
+    private static final Pattern HAN_SCRIPT = Pattern.compile("\\p{IsHan}");
+
     @Mock
     private RestTemplate restTemplate;
 
@@ -92,7 +99,9 @@ class FeiShuAppAlertNotifyHandlerImplTest {
         template.setContent("test content");
 
         
lenient().when(bundle.getString("alerter.notify.title")).thenReturn("Alert 
Notification");
+        
lenient().when(bundle.getString("alerter.notify.console")).thenReturn("Console 
Login");
         
lenient().when(alerterProperties.getConsoleUrl()).thenReturn("https://console.hertzbeat.com";);
+        feiShuAppAlertNotifyHandler.bundle = bundle;
     }
 
     /**
@@ -116,6 +125,8 @@ class FeiShuAppAlertNotifyHandlerImplTest {
                 new FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse();
         messageResp.setCode(0);
         messageResp.setMsg("success");
+        
ArgumentCaptor<HttpEntity<FeiShuAppAlertNotifyHandlerImpl.FeiShuAppMessageDto>> 
messageRequestCaptor =
+                ArgumentCaptor.forClass(HttpEntity.class);
 
         // Mock restTemplate calls
         when(restTemplate.exchange(
@@ -128,10 +139,14 @@ class FeiShuAppAlertNotifyHandlerImplTest {
         when(restTemplate.exchange(
                 anyString(),
                 eq(org.springframework.http.HttpMethod.POST),
-                any(),
+                messageRequestCaptor.capture(),
                 eq(FeiShuAppAlertNotifyHandlerImpl.FeiShuAppResponse.class)))
                 .thenReturn(new ResponseEntity<>(messageResp, HttpStatus.OK));
         feiShuAppAlertNotifyHandler.send(receiver, template, groupAlert);
+        FeiShuAppAlertNotifyHandlerImpl.FeiShuAppMessageDto messageDto = 
messageRequestCaptor.getValue().getBody();
+        assertTrue(messageDto.getContent().contains("Alert Notification"), 
messageDto.getContent());
+        assertTrue(messageDto.getContent().contains("Console Login"), 
messageDto.getContent());
+        assertFalse(HAN_SCRIPT.matcher(messageDto.getContent()).find(), 
messageDto.getContent());
     }
 
     /**
diff --git 
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/notice/impl/FlyBookAlertNotifyHandlerImplTest.java
 
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/notice/impl/FlyBookAlertNotifyHandlerImplTest.java
index 033de34613..61464c1e8e 100644
--- 
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/notice/impl/FlyBookAlertNotifyHandlerImplTest.java
+++ 
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/notice/impl/FlyBookAlertNotifyHandlerImplTest.java
@@ -18,11 +18,14 @@
 package org.apache.hertzbeat.alert.notice.impl;
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
 import org.apache.hertzbeat.alert.AlerterProperties;
+import org.springframework.http.HttpEntity;
 import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
 import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
 import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
@@ -31,6 +34,7 @@ import org.apache.hertzbeat.alert.notice.AlertNoticeException;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -42,6 +46,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.ResourceBundle;
+import java.util.regex.Pattern;
 
 /**
  * Test case for FlyBook Alert Notify
@@ -49,6 +54,8 @@ import java.util.ResourceBundle;
 @ExtendWith(MockitoExtension.class)
 class FlyBookAlertNotifyHandlerImplTest {
 
+    private static final Pattern HAN_SCRIPT = Pattern.compile("\\p{IsHan}");
+
     @Mock
     private RestTemplate restTemplate;
     
@@ -87,6 +94,8 @@ class FlyBookAlertNotifyHandlerImplTest {
         template.setContent("test content");
         
         when(bundle.getString("alerter.notify.title")).thenReturn("Alert 
Notification");
+        when(bundle.getString("alerter.notify.console")).thenReturn("Console 
Login");
+        flyBookAlertNotifyHandler.bundle = bundle;
     }
 
     @Test
@@ -101,14 +110,19 @@ class FlyBookAlertNotifyHandlerImplTest {
         successResp.setErrCode(0);
         ResponseEntity<CommonRobotNotifyResp> responseEntity = 
             new ResponseEntity<>(successResp, HttpStatus.OK);
+        ArgumentCaptor<HttpEntity<String>> requestCaptor = 
ArgumentCaptor.forClass(HttpEntity.class);
 
         when(restTemplate.postForEntity(
                 any(String.class),
-                any(),
+                requestCaptor.capture(),
                 eq(CommonRobotNotifyResp.class)
         )).thenReturn(responseEntity);
 
         flyBookAlertNotifyHandler.send(receiver, template, groupAlert);
+        String body = requestCaptor.getValue().getBody();
+        assertTrue(body.contains("\"content\": \"Alert Notification\""));
+        assertTrue(body.contains("\"content\": \"Console Login\""));
+        assertFalse(HAN_SCRIPT.matcher(body).find());
     }
 
     @Test
diff --git 
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/HuaweiCloudExternAlertServiceTest.java
 
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/HuaweiCloudExternAlertServiceTest.java
index 3859303c7e..07ec65cadc 100644
--- 
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/HuaweiCloudExternAlertServiceTest.java
+++ 
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/HuaweiCloudExternAlertServiceTest.java
@@ -77,7 +77,10 @@ public class HuaweiCloudExternAlertServiceTest {
                 + 
"vaGePIRITakoynYyYr9zZIpdx9jXhQNlgF8np1+t0JxNeoIq0DYWgH52tsodwqOm+OnmkcHwCRo/1rFv85KrKAaX2gy3sNwX"
                 + 
"w1hKnAwAw0mJlxHHSf/N3+7j6GoxCNV7fN9K4CpJiLMGNvUa7zVmG0U9mPvt/7Lac155kPPQ9lYyeL7vVI0e4sfRbuQruz3E"
                 + 
"0ZP40TKx0afoeR0/Bx/IoZzRP1La7pKlbEISvkcM7TqW/IOGQTkhVsQ32RFRxZWO2snw==");
-        externAlert.setSubject("[华为云][紧急告警恢复]云监控通知:分布式缓存服务-DCS Redis实例 
“dcs-h4tv” 的每秒并发操作数已恢复正常。");
+        
externAlert.setSubject("[\u534e\u4e3a\u4e91][\u7d27\u6025\u544a\u8b66\u6062\u590d]"
+                + 
"\u4e91\u76d1\u63a7\u901a\u77e5:\u5206\u5e03\u5f0f\u7f13\u5b58\u670d\u52a1-DCS 
Redis"
+                + "\u5b9e\u4f8b “dcs-h4tv” 
\u7684\u6bcf\u79d2\u5e76\u53d1\u64cd\u4f5c\u6570"
+                + "\u5df2\u6062\u590d\u6b63\u5e38。");
         
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
         externAlert.setMessageId("d3672d737bb742cf8c2aa3f0fd72d4d1");
         externAlert.setType("Notification");
@@ -94,15 +97,23 @@ public class HuaweiCloudExternAlertServiceTest {
         
externAlert.setSignature("Igs0bBhzw0JGmlgBH+9ejw2xWfPTXjAatAEsKDkkWcC5bZ/jveckdRZdgp/S0JER9eiJfMF427YDABufIN0sv/vBRXaRQKfRBLTJYbSTl+AQpEbIW5yUfJSRLEG3HNEhUDjASolbrW7zdPCoGkkqjifE23FCvw"
                 + 
"+4tewMzqmHnfJHcFBq3W89CJzdPBjwO1UcY9C39moUZgqZk+qDVLpxb4bHSrEYAwPOSrOPR7TZpETJ30UOgFYajJydQk692edfs0NeVutHoQiOJ5/YC83ULHft0aXhichjtfZE4KF69nROAKez0ubk3l"
                 + "Ey/mBIM9Ylbxn5b84OIrzzZQrIWe8Syw==");
-        externAlert.setSubject("[华为云][紧急告警]云监控通知:分布式缓存服务-DCS Redis实例 
“dcs-h4tv” 的每秒并发操作数已触发告警。");
+        externAlert.setSubject("[\u534e\u4e3a\u4e91][\u7d27\u6025\u544a\u8b66]"
+                + 
"\u4e91\u76d1\u63a7\u901a\u77e5:\u5206\u5e03\u5f0f\u7f13\u5b58\u670d\u52a1-DCS 
Redis"
+                + "\u5b9e\u4f8b “dcs-h4tv” 
\u7684\u6bcf\u79d2\u5e76\u53d1\u64cd\u4f5c\u6570"
+                + "\u5df2\u89e6\u53d1\u544a\u8b66。");
         
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
         externAlert.setMessageId("1565df032a19494590d61e05f7b0dc0e");
         externAlert.setType("Notification");
-        
externAlert.setMessage("{\"version\":\"v1\",\"data\":{\"AccountName\":\"hid_hk6tij5o1v-95zn\",\"Namespace\":\"分布式缓存服务\",\"DimensionName\":\"DCS
 Redis实例\",\"ResourceName\""
-                + 
":\"dcs-h4tv\",\"MetricName\":\"每秒并发操作数\",\"IsAlarm\":true,\"AlarmLevel\":\"紧急\",\"Region\":\"华东-上海一\",\"RegionId\":\"cn-east-3\",\"ResourceId\":\"3dc7b9ea"
+        
externAlert.setMessage("{\"version\":\"v1\",\"data\":{\"AccountName\":\"hid_hk6tij5o1v-95zn\","
+                + 
"\"Namespace\":\"\u5206\u5e03\u5f0f\u7f13\u5b58\u670d\u52a1\","
+                + "\"DimensionName\":\"DCS 
Redis\u5b9e\u4f8b\",\"ResourceName\""
+                + 
":\"dcs-h4tv\",\"MetricName\":\"\u6bcf\u79d2\u5e76\u53d1\u64cd\u4f5c\u6570\","
+                + 
"\"IsAlarm\":true,\"AlarmLevel\":\"\u7d27\u6025\",\"Region\":\"\u534e\u4e1c-\u4e0a\u6d77\u4e00\","
+                + "\"RegionId\":\"cn-east-3\",\"ResourceId\":\"3dc7b9ea"
                 + 
"-70b4-4c38-942d-e2636e6d844c\",\"PrivateIp\":\"192.168.0.54\",\"CurrentData\":\"6.00
 count\",\"AlarmTime\":\"2025/06/02 22:56:15 GMT+08:00\","
                 + "\"AlarmRecordID\":\"ah1748876175242njvndyzMZ\","
-                + 
"\"AlarmRuleName\":\"alarm-c5jj\",\"IsOriginalValue\":true,\"Filter\":\"原始值\",\"ComparisonOperator\":\"\\u003e\",\"Value\":\"5
 count\",\"Unit\":\"count\",\"Count\":2}}");
+                + 
"\"AlarmRuleName\":\"alarm-c5jj\",\"IsOriginalValue\":true,\"Filter\":\"\u539f\u59cb\u503c\","
+                + "\"ComparisonOperator\":\"\\u003e\",\"Value\":\"5 
count\",\"Unit\":\"count\",\"Count\":2}}");
         
externAlert.setSigningCertUrl("https://smn.cn-north-4.myhuaweicloud.com/smn/SMN_cn-north-4_b98100ca131b4116ab8ee7ccedbaae99.pem";);
         externAlert.setTimestamp("2025-06-02T14:56:17Z");
         externAlertService.addExternAlert(JsonUtil.toJson(externAlert));
@@ -135,7 +146,10 @@ public class HuaweiCloudExternAlertServiceTest {
         
externAlert.setSignature("TImrLoeb0tV1JZJSPyA0rpC9mNqH3MmhwQ4tgpuHHa+JztfGVZFvkU//OthKKhzpDAoYiXOYG9DbzXCLbvaGePIRITakoynYyYr9zZIpdx9jXhQNlgF8np"
                 + 
"1+t0JxNeoIq0DYWgH52tsodwqOm+OnmkcHwCRo/1rFv85KrKAaX2gy3sNwXw1hKnAwAw0mJlxHHSf/N3+7j6GoxCNV7fN9K4CpJiLMGNvUa7zVmG0U9mPvt/7Lac155kPPQ9l"
                 + 
"YyeL7vVI0e4sfRbuQruz3E0+ZP40TKx0afoeR0/Bx/IoZzRP1La7pKlbEISvkcM7TqW/IOGQTkhVsQ32RFRxZWO2snw==");
-        externAlert.setSubject("[华为云][紧急告警恢复]云监控通知:分布式缓存服务-DCS Redis实例 
“dcs-h4tv” 的每秒并发操作数已恢复正常。");
+        
externAlert.setSubject("[\u534e\u4e3a\u4e91][\u7d27\u6025\u544a\u8b66\u6062\u590d]"
+                + 
"\u4e91\u76d1\u63a7\u901a\u77e5:\u5206\u5e03\u5f0f\u7f13\u5b58\u670d\u52a1-DCS 
Redis"
+                + "\u5b9e\u4f8b “dcs-h4tv” 
\u7684\u6bcf\u79d2\u5e76\u53d1\u64cd\u4f5c\u6570"
+                + "\u5df2\u6062\u590d\u6b63\u5e38。");
         
externAlert.setTopicUrn("urn:smn:cn-north-4:477a784601d744e4ab9ab83986502d31:CES_notification_group_bngJ2aMpX");
         externAlert.setMessageId("d3672d737bb742cf8c2aa3f0fd72d4d1");
         externAlert.setType("UnsubscribeConfirmation");
@@ -184,4 +198,4 @@ public class HuaweiCloudExternAlertServiceTest {
         verify(alarmCommonReduce, 
never()).reduceAndSendAlarm(any(SingleAlert.class));
     }
 
-}
\ No newline at end of file
+}
diff --git 
a/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java
 
b/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java
index 3b010bc399..ca10145bc9 100644
--- 
a/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java
+++ 
b/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java
@@ -87,8 +87,7 @@ public class Metrics {
     private String name;
     /**
      * metrics name's i18n value
-     * zh-CN: CPU信息
-     * en-US: CPU Info
+     * Example: {"en-US": "CPU Info"}
      */
     private Map<String, String> i18n;
     /**
diff --git 
a/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/entity/job/MetricsSourceLocalizationTest.java
 
b/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/entity/job/MetricsSourceLocalizationTest.java
new file mode 100644
index 0000000000..7a633d8f1e
--- /dev/null
+++ 
b/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/entity/job/MetricsSourceLocalizationTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package org.apache.hertzbeat.common.entity.job;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
+
+class MetricsSourceLocalizationTest {
+
+    private static final Path PRODUCTION_SOURCE = Path.of(
+            
"src/main/java/org/apache/hertzbeat/common/entity/job/Metrics.java");
+    private static final Pattern HAN_SCRIPT = 
Pattern.compile("\\p{Script=Han}");
+
+    @Test
+    void productionSourceShouldNotContainHanScriptLiterals() throws 
IOException {
+        String source = Files.readString(PRODUCTION_SOURCE);
+
+        assertFalse(HAN_SCRIPT.matcher(source).find(),
+                () -> "source file must not contain Han-script literals: " + 
PRODUCTION_SOURCE);
+    }
+}
diff --git 
a/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/observability/dto/EvidenceDtoMigrationTest.java
 
b/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/observability/dto/EvidenceDtoMigrationTest.java
index 8ebc8758da..9c8f57194c 100644
--- 
a/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/observability/dto/EvidenceDtoMigrationTest.java
+++ 
b/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/observability/dto/EvidenceDtoMigrationTest.java
@@ -71,7 +71,7 @@ class EvidenceDtoMigrationTest {
         EntityResponseHandoffInfo traceHandoff = new EntityResponseHandoffInfo(
                 "trace-1", "open", "critical", "checkout", "trace content", 
"trace-1", "span-1",
                 "checkout", "commerce", "ERROR", "trace_id='trace-1'", 
"platform", "checkout-system",
-                "prod", 1000L, 2000L, "otlp", "trace", codeNavigationHint, 
"/entities/1", "返回实体"
+                "prod", 1000L, 2000L, "otlp", "trace", codeNavigationHint, 
"/entities/1", "Back to entity"
         );
         EntityResponseHandoffsInfo handoffsInfo = new 
EntityResponseHandoffsInfo(
                 null, null, null, traceHandoff, null, null
diff --git 
a/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/util/CommonUtilTest.java
 
b/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/util/CommonUtilTest.java
index c99e77c977..be6f0eb5a0 100644
--- 
a/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/util/CommonUtilTest.java
+++ 
b/hertzbeat-common-core/src/test/java/org/apache/hertzbeat/common/util/CommonUtilTest.java
@@ -139,10 +139,10 @@ class CommonUtilTest {
     @Test
     void testGetLangMappingValueFromI18nMap() {
         Map<String, String> i18nMap = new HashMap<>();
-        i18nMap.put("zh-CN", "中文");
+        i18nMap.put("zh-CN", "Simplified Chinese");
         i18nMap.put("ja", null);
         i18nMap.put("en-US", "English");
-        assertEquals("中文", CommonUtil.getLangMappingValueFromI18nMap("zh-CN", 
i18nMap));
+        assertEquals("Simplified Chinese", 
CommonUtil.getLangMappingValueFromI18nMap("zh-CN", i18nMap));
         assertEquals("English", 
CommonUtil.getLangMappingValueFromI18nMap("en-US", i18nMap));
         assertNull(CommonUtil.getLangMappingValueFromI18nMap("zh", new 
HashMap<>()));
         assertNotNull(CommonUtil.getLangMappingValueFromI18nMap("ja", 
i18nMap));
diff --git 
a/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/manager/ParamDefine.java
 
b/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/manager/ParamDefine.java
index d4b54d2380..437bd43b01 100644
--- 
a/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/manager/ParamDefine.java
+++ 
b/hertzbeat-common-spring/src/main/java/org/apache/hertzbeat/common/entity/manager/ParamDefine.java
@@ -73,7 +73,7 @@ public class ParamDefine {
      * Parameter field external display name
      * Port
      */
-    @Schema(description = "The parameter field displays the internationalized 
name", example = "{zh-CN: '端口'}",
+    @Schema(description = "The parameter field displays the internationalized 
name", example = "{\"en-US\":\"Port\"}",
             accessMode = READ_WRITE)
     @Convert(converter = JsonMapAttributeConverter.class)
     @SuppressWarnings("JpaAttributeTypeInspection")
diff --git 
a/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/entity/manager/ParamDefineSourceLocalizationTest.java
 
b/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/entity/manager/ParamDefineSourceLocalizationTest.java
new file mode 100644
index 0000000000..a5ca7b9801
--- /dev/null
+++ 
b/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/entity/manager/ParamDefineSourceLocalizationTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package org.apache.hertzbeat.common.entity.manager;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
+
+class ParamDefineSourceLocalizationTest {
+
+    private static final Path PRODUCTION_SOURCE = Path.of(
+            
"src/main/java/org/apache/hertzbeat/common/entity/manager/ParamDefine.java");
+    private static final Pattern HAN_SCRIPT = 
Pattern.compile("\\p{Script=Han}");
+
+    @Test
+    void productionSourceShouldNotContainHanScriptLiterals() throws 
IOException {
+        String source = Files.readString(PRODUCTION_SOURCE);
+
+        assertFalse(HAN_SCRIPT.matcher(source).find(),
+                () -> "source file must not contain Han-script literals: " + 
PRODUCTION_SOURCE);
+    }
+}
diff --git 
a/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/observability/dto/entity/EntityObservabilityDtoMigrationTest.java
 
b/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/observability/dto/entity/EntityObservabilityDtoMigrationTest.java
index 327114042d..d288774f89 100644
--- 
a/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/observability/dto/entity/EntityObservabilityDtoMigrationTest.java
+++ 
b/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/observability/dto/entity/EntityObservabilityDtoMigrationTest.java
@@ -51,7 +51,8 @@ class EntityObservabilityDtoMigrationTest {
         EntityUnifiedEvidenceSummary unifiedSummary = new 
EntityUnifiedEvidenceSummary(3, true, true, false,
                 2L, 3, 0, 8L, List.of("metrics", "logs"));
         EntityTriageRecommendation recommendation = new 
EntityTriageRecommendation(
-                "rule", "metrics", "优先查看监控", "监控异常最多", "down monitors", 
"查看监控", 9L
+                "rule", "metrics", "Review monitors first", "Most monitors are 
abnormal", "down monitors",
+                "View monitors", 9L
         );
 
         assertEquals("checkout-http", abnormalMonitor.getName());
diff --git 
a/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/support/ResourceBundleUtf8ControlTest.java
 
b/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/support/ResourceBundleUtf8ControlTest.java
index c30f5a29eb..6289642de2 100644
--- 
a/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/support/ResourceBundleUtf8ControlTest.java
+++ 
b/hertzbeat-common-spring/src/test/java/org/apache/hertzbeat/common/support/ResourceBundleUtf8ControlTest.java
@@ -43,7 +43,7 @@ class ResourceBundleUtf8ControlTest {
 
         bundle = control.newBundle(baseName, Locale.ROOT, "java.properties", 
loader, false);
         assertNotNull(bundle);
-        assertEquals("你好", bundle.getString("hello"));
+        assertEquals("Ola, Mundo!", bundle.getString("hello"));
     }
 
     @Test
diff --git a/hertzbeat-common-spring/src/test/resources/msg.properties 
b/hertzbeat-common-spring/src/test/resources/msg.properties
index 6dc68e451d..8860d79098 100644
--- a/hertzbeat-common-spring/src/test/resources/msg.properties
+++ b/hertzbeat-common-spring/src/test/resources/msg.properties
@@ -13,4 +13,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-hello=你好
+hello=Ola, Mundo!
diff --git 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/AppControllerTest.java
 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/AppControllerTest.java
index 77c361687d..40b607ad27 100644
--- 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/AppControllerTest.java
+++ 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/AppControllerTest.java
@@ -71,7 +71,7 @@ class AppControllerTest {
         paramDefine.setField("port");
         paramDefine.setType("number");
         paramDefine.setDefaultValue("12");
-        paramDefine.setPlaceholder("请输出密码");
+        paramDefine.setPlaceholder("Enter password");
         paramDefine.setCreator("tom");
         paramDefine.setModifier("tom");
         paramDefines.add(paramDefine);
@@ -227,7 +227,7 @@ class AppControllerTest {
     void queryAppsHierarchy() throws Exception {
         // Data to make
         Hierarchy hierarchy = new Hierarchy();
-        hierarchy.setLabel("Linux系统");
+        hierarchy.setLabel("Linux system");
         hierarchy.setValue("linux");
         hierarchy.setCategory("os");
         List<Hierarchy> list = new ArrayList<>();
diff --git 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/MonitorControllerTest.java
 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/MonitorControllerTest.java
index 196afb121e..503323ab89 100644
--- 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/MonitorControllerTest.java
+++ 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/controller/MonitorControllerTest.java
@@ -62,7 +62,7 @@ class MonitorControllerTest {
         monitor.setName("TanCloud");
         monitor.setInstance("192.167.25.11:8989");
         monitor.setIntervals(600);
-        monitor.setDescription("对SAAS网站TanCloud的可用性监控");
+        monitor.setDescription("Availability monitoring for the TanCloud SaaS 
site");
         monitor.setCreator("tom");
         monitor.setModifier("tom");
 
@@ -160,7 +160,7 @@ class MonitorControllerTest {
         monitor.setName("TanCloud");
         monitor.setInstance("192.167.25.11:8989");
         monitor.setIntervals(600);
-        monitor.setDescription("对SAAS网站TanCloud的可用性监控");
+        monitor.setDescription("Availability monitoring for the TanCloud SaaS 
site");
         monitor.setCreator("tom");
         monitor.setModifier("tom");
 
@@ -203,7 +203,7 @@ class MonitorControllerTest {
         monitor.setName("TanCloud");
         monitor.setInstance("192.167.25.11:8989");
         monitor.setIntervals(600);
-        monitor.setDescription("对SAAS网站TanCloud的可用性监控");
+        monitor.setDescription("Availability monitoring for the TanCloud SaaS 
site");
         monitor.setCreator("tom");
         monitor.setModifier("tom");
 
diff --git 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/ObserveEntityServiceTest.java
 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/ObserveEntityServiceTest.java
index 0290444188..78fe8d4879 100644
--- 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/ObserveEntityServiceTest.java
+++ 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/ObserveEntityServiceTest.java
@@ -37,6 +37,7 @@ import java.util.HashMap;
 import java.util.Collections;
 import java.util.List;
 import java.time.LocalDateTime;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -250,9 +251,12 @@ class ObserveEntityServiceTest {
     private EntityValidationService entityValidationService;
     private EntityWorkspaceAccessService entityWorkspaceAccessService;
     private EntityWorkspaceQueryService entityWorkspaceQueryService;
+    private Locale previousLocale;
 
     @BeforeEach
     void setUpNoiseControlDefaults() {
+        previousLocale = Locale.getDefault();
+        Locale.setDefault(Locale.US);
         telemetryIntakeService = org.mockito.Mockito.spy(new 
TelemetryIntakeServiceImpl(logQueryRepository));
         entityObservabilityGateway = org.mockito.Mockito.spy(
                 new EntityObservabilityGatewayImpl(telemetryIntakeService, 
entityTraceQueryService));
@@ -402,6 +406,7 @@ class ObserveEntityServiceTest {
     @AfterEach
     void tearDownRequestContext() {
         AuthTokenRequestContext.clear();
+        Locale.setDefault(previousLocale);
     }
 
     @Test
@@ -1330,7 +1335,7 @@ class ObserveEntityServiceTest {
         assertFalse(detail.getUnifiedEvidenceSummary().isTracesActive());
         assertNotNull(detail.getTriageRecommendation());
         assertEquals("metrics", 
detail.getTriageRecommendation().getRecommendedFocus());
-        assertEquals("查看监控", 
detail.getTriageRecommendation().getActionLabel());
+        assertEquals("View monitors", 
detail.getTriageRecommendation().getActionLabel());
         assertTrue(detail.getOpsSummary().isOwnerReady());
         assertTrue(detail.getOpsSummary().isRunbookReady());
         assertTrue(detail.getOpsSummary().isRelationReady());
diff --git 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/entity/EntityDetailObservabilityReadModelServiceTest.java
 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/entity/EntityDetailObservabilityReadModelServiceTest.java
index cb95e56700..ec4e2a36c0 100644
--- 
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/entity/EntityDetailObservabilityReadModelServiceTest.java
+++ 
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/entity/EntityDetailObservabilityReadModelServiceTest.java
@@ -153,7 +153,7 @@ class EntityDetailObservabilityReadModelServiceTest {
         EntityUnifiedEvidenceSummary unifiedSummary = new 
EntityUnifiedEvidenceSummary(
                 2, true, true, false, 1, 1, 0, 987L, List.of("metrics", 
"logs"));
         EntityTriageRecommendation triage = new EntityTriageRecommendation(
-                "evidence", "metrics", "Metrics first", "Down monitor", 
"active alert", "查看监控", 987L);
+                "evidence", "metrics", "Metrics first", "Down monitor", 
"active alert", "View monitors", 987L);
         
List<org.apache.hertzbeat.common.observability.dto.evidence.MetricEvidence> 
metricEvidence =
                 Collections.emptyList();
         
List<org.apache.hertzbeat.common.observability.dto.evidence.LogEvidence> 
logEvidence =
diff --git 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpGrpcIngestionServiceImpl.java
 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpGrpcIngestionServiceImpl.java
index ae4b98867a..d5d3fbbe2a 100644
--- 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpGrpcIngestionServiceImpl.java
+++ 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpGrpcIngestionServiceImpl.java
@@ -1633,7 +1633,7 @@ public class OtlpGrpcIngestionServiceImpl implements 
OtlpGrpcIngestionService {
             return observations;
         }
         return List.of(new MetricObservation("metric", null, null, 
baseMetricMetadata("unsupported", "unsupported",
-                "unsupported", "未知 OTLP metric 类型,当前未被 HertzBeat facade 
识别。")));
+                "unsupported", 
OtlpIngestionMessages.get("observability.otlp.metric.compatibility.unknown-type"))));
     }
 
     private MetricObservation fromNumberDataPoint(String metricType,
@@ -1664,7 +1664,7 @@ public class OtlpGrpcIngestionServiceImpl implements 
OtlpGrpcIngestionService {
                 "partial",
                 "supported",
                 "partial",
-                "Histogram 指标已写入 Greptime,但 HertzBeat 当前 facade 仅保留代表值与 
bucket/bounds 元信息,未提供完整 histogram 查询语义。"
+                
OtlpIngestionMessages.get("observability.otlp.metric.compatibility.histogram.reason")
         );
         appendMetricTimeRange(metadata, point.getStartTimeUnixNano(), 
point.getTimeUnixNano());
         metadata.put(OTLP_METRIC_DATA_POINT_COUNT, 
String.valueOf(dataPointCount));
@@ -1692,7 +1692,7 @@ public class OtlpGrpcIngestionServiceImpl implements 
OtlpGrpcIngestionService {
                 "unsupported",
                 "unsupported",
                 "partial",
-                "Greptime 当前 OTLP metrics 数据模型不支持 
ExponentialHistogram;HertzBeat 仅保留代表值与兼容性元信息。"
+                
OtlpIngestionMessages.get("observability.otlp.metric.compatibility.exponential-histogram.reason")
         );
         appendMetricTimeRange(metadata, point.getStartTimeUnixNano(), 
point.getTimeUnixNano());
         metadata.put(OTLP_METRIC_DATA_POINT_COUNT, 
String.valueOf(dataPointCount));
@@ -1724,7 +1724,7 @@ public class OtlpGrpcIngestionServiceImpl implements 
OtlpGrpcIngestionService {
                 "partial",
                 "partial",
                 "partial",
-                "Summary quantiles 语义受 Greptime 数据模型与当前 HertzBeat facade 
限制,当前仅保留 summary/quantiles 元信息。"
+                
OtlpIngestionMessages.get("observability.otlp.metric.compatibility.summary.reason")
         );
         appendMetricTimeRange(metadata, point.getStartTimeUnixNano(), 
point.getTimeUnixNano());
         metadata.put(OTLP_METRIC_DATA_POINT_COUNT, 
String.valueOf(dataPointCount));
@@ -1802,18 +1802,24 @@ public class OtlpGrpcIngestionServiceImpl implements 
OtlpGrpcIngestionService {
             metadata.put(OTLP_METRIC_COMPATIBILITY_REASON, overallReason);
         }
         if ("supported".equals(greptimeCompatibility)) {
-            metadata.put(OTLP_METRIC_GREPTIME_REASON, "Greptime 当前 OTLP 
metrics 数据模型支持该类型。");
+            metadata.put(OTLP_METRIC_GREPTIME_REASON,
+                    
OtlpIngestionMessages.get("observability.otlp.metric.greptime.reason.supported"));
         } else if ("partial".equals(greptimeCompatibility)) {
-            metadata.put(OTLP_METRIC_GREPTIME_REASON, "Greptime 当前 OTLP 
metrics 数据模型仅部分保留该类型语义。");
+            metadata.put(OTLP_METRIC_GREPTIME_REASON,
+                    
OtlpIngestionMessages.get("observability.otlp.metric.greptime.reason.partial"));
         } else {
-            metadata.put(OTLP_METRIC_GREPTIME_REASON, "Greptime 当前 OTLP 
metrics 数据模型不支持该类型。");
+            metadata.put(OTLP_METRIC_GREPTIME_REASON,
+                    
OtlpIngestionMessages.get("observability.otlp.metric.greptime.reason.unsupported"));
         }
         if ("supported".equals(facadeCompatibility)) {
-            metadata.put(OTLP_METRIC_FACADE_REASON, "HertzBeat 当前 facade 
可直接消费该类型。");
+            metadata.put(OTLP_METRIC_FACADE_REASON,
+                    
OtlpIngestionMessages.get("observability.otlp.metric.facade.reason.supported"));
         } else if ("partial".equals(facadeCompatibility)) {
-            metadata.put(OTLP_METRIC_FACADE_REASON, "HertzBeat 当前 facade 
仅保留代表值与兼容元信息。");
+            metadata.put(OTLP_METRIC_FACADE_REASON,
+                    
OtlpIngestionMessages.get("observability.otlp.metric.facade.reason.partial"));
         } else {
-            metadata.put(OTLP_METRIC_FACADE_REASON, "HertzBeat 当前 facade 
不支持该类型。");
+            metadata.put(OTLP_METRIC_FACADE_REASON,
+                    
OtlpIngestionMessages.get("observability.otlp.metric.facade.reason.unsupported"));
         }
         return metadata;
     }
diff --git 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionMessages.java
 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionMessages.java
new file mode 100644
index 0000000000..8b83bad27c
--- /dev/null
+++ 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionMessages.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package org.apache.hertzbeat.observability.ingestion.service.impl;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import org.apache.hertzbeat.common.util.ResourceBundleUtil;
+
+/**
+ * Localized backend copy for OTLP ingestion DTOs.
+ */
+final class OtlpIngestionMessages {
+
+    private static final String BUNDLE_NAME = "observability";
+
+    private OtlpIngestionMessages() {
+    }
+
+    static String get(String key) {
+        try {
+            return ResourceBundleUtil.getBundle(BUNDLE_NAME).getString(key);
+        } catch (MissingResourceException exception) {
+            return key;
+        }
+    }
+
+    static String format(String key, Object... args) {
+        return MessageFormat.format(get(key), args);
+    }
+}
diff --git 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
index 7efc672823..371509bcb2 100644
--- 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
+++ 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
@@ -219,8 +219,10 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                 || latestMetricSnapshot.getObservedAt() >= 
latestMonitorObservedAt)) {
             recentEvents.add(new OtlpIngestionOverviewDto.RecentSignalEvent(
                     "metrics",
-                    defaultText(latestMetricSnapshot.getServiceName(), 
"指标已接入"),
-                    defaultText(latestMetricSnapshot.getServiceNamespace(), 
"最近已收到指标数据"),
+                    defaultText(latestMetricSnapshot.getServiceName(),
+                            
message("observability.otlp.overview.event.metrics.title")),
+                    defaultText(latestMetricSnapshot.getServiceNamespace(),
+                            
message("observability.otlp.overview.event.metrics.copy")),
                     latestMetricSnapshot.getObservedAt()
             ));
         } else if (latestMonitor.isPresent()) {
@@ -254,7 +256,9 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                         monitorTotalCount + otlpMetricCount,
                         metricsLatestObservedAt,
                         metricsIntakeMode(monitorTotalCount, otlpMetricCount),
-                        metricsActive ? "最近已收到可查看的指标数据" : "最近 24 小时还没有指标数据"
+                        metricsActive
+                                ? 
message("observability.otlp.overview.metrics.active")
+                                : 
message("observability.otlp.overview.metrics.inactive")
                 ),
                 new OtlpIngestionOverviewDto.SignalOverview(
                         "logs",
@@ -262,7 +266,9 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                         logTotalCount,
                         logsLatestObservedAt,
                         "OTLP",
-                        logTotalCount > 0 ? "最近 24 小时已收到日志数据" : "最近 24 
小时还没有日志数据"
+                        logTotalCount > 0
+                                ? 
message("observability.otlp.overview.logs.active")
+                                : 
message("observability.otlp.overview.logs.inactive")
                 ),
                 new OtlpIngestionOverviewDto.SignalOverview(
                         "traces",
@@ -270,7 +276,9 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                         traceOverview.getTotalTraceCount(),
                         traceOverview.getLatestObservedAt(),
                         "OTLP",
-                        traceOverview.getTotalTraceCount() > 0 ? "最近 24 
小时已收到链路数据" : "最近 24 小时还没有链路数据"
+                        traceOverview.getTotalTraceCount() > 0
+                                ? 
message("observability.otlp.overview.traces.active")
+                                : 
message("observability.otlp.overview.traces.inactive")
                 ),
                 activeSignalCount,
                 latestObservedAt,
@@ -294,72 +302,90 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
         long total = workspaceQueryGateway.countCollectors();
         long online = 
workspaceQueryGateway.countCollectorsByStatus(CommonConstants.COLLECTOR_STATUS_ONLINE);
         if (total <= 0) {
-            return readinessCheck("collector", "Collector 集群", "warning", "未注册 
Collector",
-                    "先部署 Collector 或使用内置采集入口", checkedAt);
+            return readinessCheck("collector", 
message("observability.otlp.readiness.collector.title"), "warning",
+                    
message("observability.otlp.readiness.collector.unregistered"),
+                    message("observability.otlp.readiness.collector.deploy"), 
checkedAt);
         }
         if (online >= total) {
-            return readinessCheck("collector", "Collector 集群", "success", 
online + " / " + total + " 在线",
-                    "采集节点可接收任务", checkedAt);
+            return readinessCheck("collector", 
message("observability.otlp.readiness.collector.title"), "success",
+                    message("observability.otlp.readiness.collector.online", 
online, total),
+                    
message("observability.otlp.readiness.collector.accepting"), checkedAt);
         }
         if (online > 0) {
-            return readinessCheck("collector", "Collector 集群", "warning", 
online + " / " + total + " 在线",
-                    (total - online) + " 个采集节点离线", checkedAt);
+            return readinessCheck("collector", 
message("observability.otlp.readiness.collector.title"), "warning",
+                    message("observability.otlp.readiness.collector.online", 
online, total),
+                    message("observability.otlp.readiness.collector.offline", 
total - online), checkedAt);
         }
-        return readinessCheck("collector", "Collector 集群", "danger", "0 / " + 
total + " 在线",
-                "所有采集节点离线", checkedAt);
+        return readinessCheck("collector", 
message("observability.otlp.readiness.collector.title"), "danger",
+                message("observability.otlp.readiness.collector.online", 0, 
total),
+                message("observability.otlp.readiness.collector.all-offline"), 
checkedAt);
     }
 
     private OtlpIngestionOverviewDto.ReadinessCheck buildStorageReadiness(long 
checkedAt) {
         int total = historyDataReaders.size();
         long available = countAvailableHistoryReaders();
         if (total <= 0) {
-            return readinessCheck("storage", "历史存储", "warning", "未启用历史存储",
-                    "检查历史存储配置", checkedAt);
+            return readinessCheck("storage", 
message("observability.otlp.readiness.storage.title"), "warning",
+                    message("observability.otlp.readiness.storage.disabled"),
+                    
message("observability.otlp.readiness.storage.check-config"), checkedAt);
         }
         if (available >= total) {
-            return readinessCheck("storage", "历史存储", "success", available + " 
/ " + total + " 可用",
-                    "HistoryDataReader 可用", checkedAt);
+            return readinessCheck("storage", 
message("observability.otlp.readiness.storage.title"), "success",
+                    message("observability.otlp.readiness.storage.available", 
available, total),
+                    
message("observability.otlp.readiness.storage.reader-available"), checkedAt);
         }
         if (available > 0) {
-            return readinessCheck("storage", "历史存储", "warning", available + " 
/ " + total + " 可用",
-                    "部分历史存储不可用", checkedAt);
+            return readinessCheck("storage", 
message("observability.otlp.readiness.storage.title"), "warning",
+                    message("observability.otlp.readiness.storage.available", 
available, total),
+                    message("observability.otlp.readiness.storage.partial"), 
checkedAt);
         }
-        return readinessCheck("storage", "历史存储", "danger", "0 / " + total + " 
可用",
-                "检查历史存储配置", checkedAt);
+        return readinessCheck("storage", 
message("observability.otlp.readiness.storage.title"), "danger",
+                message("observability.otlp.readiness.storage.available", 0, 
total),
+                message("observability.otlp.readiness.storage.check-config"), 
checkedAt);
     }
 
     private OtlpIngestionOverviewDto.ReadinessCheck buildQueryReadiness(long 
checkedAt) {
         boolean promqlAvailable = hasPromqlExecutor();
         boolean historyAvailable = countAvailableHistoryReaders() > 0;
         if (promqlAvailable && historyAvailable) {
-            return readinessCheck("query", "查询服务", "success", "指标、日志和链路查询可用",
-                    "PromQL 与历史查询可用", checkedAt);
+            return readinessCheck("query", 
message("observability.otlp.readiness.query.title"), "success",
+                    message("observability.otlp.readiness.query.available"),
+                    
message("observability.otlp.readiness.query.promql-history"), checkedAt);
         }
         if (promqlAvailable || historyAvailable) {
-            return readinessCheck("query", "查询服务", "warning", "部分查询能力可用",
-                    promqlAvailable ? "PromQL 可用,历史查询待检查" : "历史查询可用,PromQL 
待检查", checkedAt);
+            return readinessCheck("query", 
message("observability.otlp.readiness.query.title"), "warning",
+                    message("observability.otlp.readiness.query.partial"),
+                    promqlAvailable
+                            ? 
message("observability.otlp.readiness.query.promql-only")
+                            : 
message("observability.otlp.readiness.query.history-only"), checkedAt);
         }
-        return readinessCheck("query", "查询服务", "danger", "查询服务不可用",
-                "检查 PromQL 与历史查询配置", checkedAt);
+        return readinessCheck("query", 
message("observability.otlp.readiness.query.title"), "danger",
+                message("observability.otlp.readiness.query.unavailable"),
+                message("observability.otlp.readiness.query.check-config"), 
checkedAt);
     }
 
     private OtlpIngestionOverviewDto.ReadinessCheck 
buildGreptimeReadiness(long checkedAt) {
         boolean greptimeEnabled = 
greptimeProperties.stream().anyMatch(GreptimeProperties::enabled);
         if (!greptimeEnabled) {
-            return readinessCheck("greptime", "GreptimeDB", "neutral", "未启用 
GreptimeDB",
-                    "当前使用其他历史存储或尚未配置", checkedAt);
+            return readinessCheck("greptime", "GreptimeDB", "neutral",
+                    message("observability.otlp.readiness.greptime.disabled"),
+                    
message("observability.otlp.readiness.greptime.other-storage"), checkedAt);
         }
         if (greptimeSqlQueryExecutors.isEmpty()) {
-            return readinessCheck("greptime", "GreptimeDB", "warning", 
"GreptimeDB 已启用,SQL 执行器未就绪",
-                    "检查 GreptimeDB HTTP 配置", checkedAt);
+            return readinessCheck("greptime", "GreptimeDB", "warning",
+                    
message("observability.otlp.readiness.greptime.sql-not-ready"),
+                    
message("observability.otlp.readiness.greptime.check-http"), checkedAt);
         }
         try {
             greptimeSqlQueryExecutors.getFirst().execute("SELECT 1");
-            return readinessCheck("greptime", "GreptimeDB", "success", "SQL 
自检通过",
-                    "SELECT 1 成功", checkedAt);
+            return readinessCheck("greptime", "GreptimeDB", "success",
+                    message("observability.otlp.readiness.greptime.sql-ok"),
+                    
message("observability.otlp.readiness.greptime.select-ok"), checkedAt);
         } catch (RuntimeException exception) {
-            return readinessCheck("greptime", "GreptimeDB", "danger", "SQL 
自检失败",
-                    defaultText(exception.getMessage(), "检查 GreptimeDB 连接"), 
checkedAt);
+            return readinessCheck("greptime", "GreptimeDB", "danger",
+                    
message("observability.otlp.readiness.greptime.sql-failed"),
+                    defaultText(exception.getMessage(),
+                            
message("observability.otlp.readiness.greptime.check-connection")), checkedAt);
         }
     }
 
@@ -483,9 +509,9 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                     "deployment.environment.name": "prod",
                 })
 
-                # 所有 signals 统一发送到 HertzBeat OTLP HTTP 入口:
                 # %s
-                """.formatted(unifiedBaseEndpoint);
+                # %s
+                
""".formatted(message("observability.otlp.guide.snippet.python.http.comment"), 
unifiedBaseEndpoint);
         String pythonGrpcSnippet = """
                 from opentelemetry.sdk.resources import Resource
                 resource = Resource.create({
@@ -494,9 +520,9 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                     "deployment.environment.name": "prod",
                 })
 
-                # 所有 signals 统一发送到 HertzBeat OTLP gRPC 入口:
                 # %s
-                """.formatted(grpcEndpoint);
+                # %s
+                
""".formatted(message("observability.otlp.guide.snippet.python.grpc.comment"), 
grpcEndpoint);
 
         return new OtlpIngestionGuideDto(
                 "OTLP HTTP",
@@ -510,57 +536,61 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                                 "http",
                                 "OTLP HTTP",
                                 metricsEndpoint,
-                                "把指标数据发送到这个 OTLP HTTP 
地址。收到数据后,可直接在监控和实体详情中继续查看。",
-                                "如果已经配置了监控任务,数据会继续显示在对应的监控项中。"
+                                
message("observability.otlp.guide.metrics.http.description"),
+                                
message("observability.otlp.guide.metrics.http.note")
                         ),
                         new OtlpIngestionGuideDto.SignalGuide(
                                 "logs",
                                 "http",
                                 "OTLP HTTP",
                                 logsEndpoint,
-                                "把日志数据发送到这个 OTLP HTTP 地址。收到数据后,可直接前往日志管理查看。",
-                                "使用接入令牌即可完成认证。"
+                                
message("observability.otlp.guide.logs.http.description"),
+                                
message("observability.otlp.guide.logs.http.note")
                         ),
                         new OtlpIngestionGuideDto.SignalGuide(
                                 "traces",
                                 "http",
                                 "OTLP HTTP",
                                 traceEndpoint,
-                                "把链路数据发送到这个 OTLP HTTP 地址。收到数据后,可直接前往链路管理查看。",
-                                "系统会根据服务信息自动尝试关联到实体。"
+                                
message("observability.otlp.guide.traces.http.description"),
+                                
message("observability.otlp.guide.traces.http.note")
                         ),
                         new OtlpIngestionGuideDto.SignalGuide(
                                 "metrics",
                                 "grpc",
                                 "OTLP gRPC",
                                 grpcEndpoint,
-                                "把指标数据发送到这个 OTLP gRPC 地址,适合已经使用 OpenTelemetry 
Collector 或语言 SDK 的服务。",
-                                "收到数据后,可直接在监控和实体详情中继续查看。"
+                                
message("observability.otlp.guide.metrics.grpc.description"),
+                                
message("observability.otlp.guide.metrics.grpc.note")
                         ),
                         new OtlpIngestionGuideDto.SignalGuide(
                                 "logs",
                                 "grpc",
                                 "OTLP gRPC",
                                 grpcEndpoint,
-                                "把日志数据发送到这个 OTLP gRPC 地址。收到数据后,可直接前往日志管理查看。",
-                                "使用 Authorization Bearer 接入令牌即可完成认证。"
+                                
message("observability.otlp.guide.logs.grpc.description"),
+                                
message("observability.otlp.guide.logs.grpc.note")
                         ),
                         new OtlpIngestionGuideDto.SignalGuide(
                                 "traces",
                                 "grpc",
                                 "OTLP gRPC",
                                 grpcEndpoint,
-                                "把链路数据发送到这个 OTLP gRPC 地址。收到数据后,可直接前往链路管理查看。",
-                                "系统会根据服务信息自动尝试关联到实体。"
+                                
message("observability.otlp.guide.traces.grpc.description"),
+                                
message("observability.otlp.guide.traces.grpc.note")
                         )
                 ),
                 List.of(
                         new OtlpIngestionGuideDto.Snippet("collector-http", 
"http", "OpenTelemetry Collector", "yaml", collectorSnippet),
-                        new OtlpIngestionGuideDto.Snippet("java-http", "http", 
"Java 环境变量", "bash", javaSnippet),
-                        new OtlpIngestionGuideDto.Snippet("python-http", 
"http", "Python 资源属性", "python", pythonSnippet),
+                        new OtlpIngestionGuideDto.Snippet("java-http", "http",
+                                
message("observability.otlp.guide.snippet.java-env"), "bash", javaSnippet),
+                        new OtlpIngestionGuideDto.Snippet("python-http", 
"http",
+                                
message("observability.otlp.guide.snippet.python-resource"), "python", 
pythonSnippet),
                         new OtlpIngestionGuideDto.Snippet("collector-grpc", 
"grpc", "OpenTelemetry Collector", "yaml", collectorGrpcSnippet),
-                        new OtlpIngestionGuideDto.Snippet("java-grpc", "grpc", 
"Java 环境变量", "bash", javaGrpcSnippet),
-                        new OtlpIngestionGuideDto.Snippet("python-grpc", 
"grpc", "Python 资源属性", "python", pythonGrpcSnippet)
+                        new OtlpIngestionGuideDto.Snippet("java-grpc", "grpc",
+                                
message("observability.otlp.guide.snippet.java-env"), "bash", javaGrpcSnippet),
+                        new OtlpIngestionGuideDto.Snippet("python-grpc", 
"grpc",
+                                
message("observability.otlp.guide.snippet.python-resource"), "python", 
pythonGrpcSnippet)
                 )
         );
     }
@@ -666,7 +696,7 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                     null,
                     new OtlpMetricsConsoleDto.Stats(0, 0, null),
                     "no_context",
-                    "缺少可用于构建 OTLP 指标查询的服务上下文。"
+                    message("observability.otlp.metrics-console.no-context")
             );
         }
         if (!metricQueryRepository.hasPromqlExecutor()) {
@@ -678,7 +708,7 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                     null,
                     new OtlpMetricsConsoleDto.Stats(0, 0, null),
                     "load_failed",
-                    "当前环境未配置可用的 PromQL 指标查询执行器。"
+                    
message("observability.otlp.metrics-console.promql-unavailable")
             );
         }
         MetricsQueryExecution execution = 
executeMetricsConsoleQuery(resolvedQuery, resolvedStart, resolvedEnd);
@@ -1166,12 +1196,20 @@ public class OtlpIngestionWorkspaceServiceImpl 
implements OtlpIngestionWorkspace
 
     private String metricsIntakeMode(long monitorTotalCount, long 
otlpMetricCount) {
         if (monitorTotalCount > 0 && otlpMetricCount > 0) {
-            return "OTLP + 实体监控";
+            return message("observability.otlp.overview.metrics.mode.mixed");
         }
         if (otlpMetricCount > 0) {
             return "OTLP";
         }
-        return "实体监控";
+        return message("observability.otlp.overview.metrics.mode.monitor");
+    }
+
+    private static String message(String key) {
+        return OtlpIngestionMessages.get(key);
+    }
+
+    private static String message(String key, Object... args) {
+        return OtlpIngestionMessages.format(key, args);
     }
 
     private String defaultText(String primary, String fallback) {
diff --git 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/EntityObservabilityGatewayImpl.java
 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/EntityObservabilityGatewayImpl.java
index ac65e7546a..b21b6d109e 100644
--- 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/EntityObservabilityGatewayImpl.java
+++ 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/EntityObservabilityGatewayImpl.java
@@ -265,80 +265,80 @@ public class EntityObservabilityGatewayImpl implements 
EntityObservabilityGatewa
         if (activeAlertCount > 0) {
             actions.add(new EntityNextActionInfo(
                     "review_alerts",
-                    "处理活跃告警",
-                    activeAlertCount + " 条活跃告警已经关联到当前实体,先确认影响范围和当前状态。",
-                    "查看活跃告警",
+                    
ObservabilityMessages.get("observability.entity.next.review-alerts.title"),
+                    
ObservabilityMessages.format("observability.entity.next.review-alerts.description",
 activeAlertCount),
+                    
ObservabilityMessages.get("observability.entity.next.review-alerts.action"),
                     100
             ));
         }
         if (opsSummary != null && !opsSummary.isOwnerReady()) {
             actions.add(new EntityNextActionInfo(
                     "complete_owner",
-                    "补负责人",
-                    "当前实体还没有明确负责人,排障和治理动作缺少责任归属。",
-                    "补负责人",
+                    
ObservabilityMessages.get("observability.entity.next.complete-owner.title"),
+                    
ObservabilityMessages.get("observability.entity.next.complete-owner.description"),
+                    
ObservabilityMessages.get("observability.entity.next.complete-owner.action"),
                     90
             ));
         }
         if (opsSummary != null && !opsSummary.isRunbookReady()) {
             actions.add(new EntityNextActionInfo(
                     "complete_runbook",
-                    "补处置手册",
-                    "先补一条 runbook 或处置入口,告警后才能直接推动恢复。",
-                    "补处置手册",
+                    
ObservabilityMessages.get("observability.entity.next.complete-runbook.title"),
+                    
ObservabilityMessages.get("observability.entity.next.complete-runbook.description"),
+                    
ObservabilityMessages.get("observability.entity.next.complete-runbook.action"),
                     80
             ));
         }
         if (evidenceSummary != null && healthyMonitorCount + downMonitorCount 
== 0) {
             actions.add(new EntityNextActionInfo(
                     "bind_monitor",
-                    "补绑监控",
-                    "实体已经存在,但还没有绑定任何监控任务,当前状态只能停留在目录元数据层。",
-                    "关联监控",
+                    
ObservabilityMessages.get("observability.entity.next.bind-monitor.title"),
+                    
ObservabilityMessages.get("observability.entity.next.bind-monitor.description"),
+                    
ObservabilityMessages.get("observability.entity.next.bind-monitor.action"),
                     75
             ));
         } else if (downMonitorCount > 0 && activeAlertCount == 0) {
             actions.add(new EntityNextActionInfo(
                     "bind_monitor",
-                    "查看异常监控",
-                    downMonitorCount + " 个监控当前异常,建议先确认哪个证据最值得继续排查。",
-                    "查看核心监控",
+                    
ObservabilityMessages.get("observability.entity.next.abnormal-monitors.title"),
+                    
ObservabilityMessages.format("observability.entity.next.abnormal-monitors.description",
 downMonitorCount),
+                    
ObservabilityMessages.get("observability.entity.next.abnormal-monitors.action"),
                     74
             ));
         }
         if (opsSummary != null && !opsSummary.isTelemetryReady()) {
             actions.add(new EntityNextActionInfo(
                     "open_discovery",
-                    "归并证据到实体",
-                    "当前还没有形成足够的监控、身份或日志线索,建议先回到发现工作台归并证据。",
-                    "打开发现工作台",
+                    
ObservabilityMessages.get("observability.entity.next.open-discovery.title"),
+                    
ObservabilityMessages.get("observability.entity.next.open-discovery.description"),
+                    
ObservabilityMessages.get("observability.entity.next.open-discovery.action"),
                     70
             ));
         }
         if (logSummary != null && logSummary.getHintCount() > 0 && 
activeAlertCount == 0) {
             actions.add(new EntityNextActionInfo(
                     "inspect_logs",
-                    "查看日志线索",
-                    "日志入口已经具备,先确认 OTel resource 或 fallback 查询是否能快速定位异常。",
-                    "查看日志线索",
+                    
ObservabilityMessages.get("observability.entity.next.inspect-logs.title"),
+                    
ObservabilityMessages.get("observability.entity.next.inspect-logs.description"),
+                    
ObservabilityMessages.get("observability.entity.next.inspect-logs.action"),
                     60
             ));
         }
         if (opsSummary != null && !opsSummary.isRelationReady()) {
             actions.add(new EntityNextActionInfo(
                     "review_relations",
-                    "补关键关系",
-                    "关键上下游关系还不完整,建议先补齐最关键的依赖边界。",
-                    "查看关系",
+                    
ObservabilityMessages.get("observability.entity.next.review-relations.title"),
+                    
ObservabilityMessages.get("observability.entity.next.review-relations.description"),
+                    
ObservabilityMessages.get("observability.entity.next.review-relations.action"),
                     50
             ));
         }
         if (actions.isEmpty()) {
             actions.add(new EntityNextActionInfo(
                     "inspect_logs",
-                    "继续排查当前实体",
-                    "目录归属和证据都已经具备,可以直接从日志或监控继续深入分析。",
-                    "查看日志线索",
+                    
ObservabilityMessages.get("observability.entity.next.fallback.title"),
+                    
ObservabilityMessages.get("observability.entity.next.fallback.description"),
+                    
ObservabilityMessages.get("observability.entity.next.fallback.action"),
                     10
             ));
         }
@@ -366,11 +366,12 @@ public class EntityObservabilityGatewayImpl implements 
EntityObservabilityGatewa
     @Override
     public String buildEntityReturnLabel(ObservedEntityContext entityContext) {
         if (entityContext == null || entityContext.getEntity() == null) {
-            return "实体详情";
+            return 
ObservabilityMessages.get("observability.entity.return-label.fallback");
         }
         return defaultText(
                 trimToNull(entityContext.getEntity().getDisplayName()),
-                defaultText(trimToNull(entityContext.getEntity().getName()), 
"实体详情")
+                defaultText(trimToNull(entityContext.getEntity().getName()),
+                        
ObservabilityMessages.get("observability.entity.return-label.fallback"))
         );
     }
 
diff --git 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/ObservabilityMessages.java
 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/ObservabilityMessages.java
new file mode 100644
index 0000000000..b1d4c93b77
--- /dev/null
+++ 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/ObservabilityMessages.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package org.apache.hertzbeat.observability.shared.service.impl;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import org.apache.hertzbeat.common.util.ResourceBundleUtil;
+
+/**
+ * Localized backend copy for observability DTOs.
+ */
+final class ObservabilityMessages {
+
+    private static final String BUNDLE_NAME = "observability";
+
+    private ObservabilityMessages() {
+    }
+
+    static String get(String key) {
+        try {
+            return ResourceBundleUtil.getBundle(BUNDLE_NAME).getString(key);
+        } catch (MissingResourceException exception) {
+            return key;
+        }
+    }
+
+    static String format(String key, Object... args) {
+        return MessageFormat.format(get(key), args);
+    }
+}
diff --git 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/TelemetryIntakeServiceImpl.java
 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/TelemetryIntakeServiceImpl.java
index cb79db53b0..7b53fa7675 100644
--- 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/TelemetryIntakeServiceImpl.java
+++ 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/shared/service/impl/TelemetryIntakeServiceImpl.java
@@ -591,7 +591,7 @@ public class TelemetryIntakeServiceImpl implements 
TelemetryEvidenceGateway {
                             preferredMonitor == null ? Collections.emptyList() 
: List.of(defaultText(preferredMonitor.getName(), 
preferredMonitor.getInstance())),
                             defaultText(preferredMonitor == null ? null : 
preferredMonitor.getName(), "monitor")),
                     "entity.monitor.availability",
-                    "实体监控状态",
+                    
ObservabilityMessages.get("observability.telemetry.metric.entity-monitor-status"),
                     "summary",
                     "count",
                     (double) healthyCount,
@@ -965,21 +965,28 @@ public class TelemetryIntakeServiceImpl implements 
TelemetryEvidenceGateway {
                 && !"normal".equalsIgnoreCase(item.getSeverityOrHealth()));
         if (metricsRequireAttention) {
             String summary = activeAlerts > 0
-                    ? "当前已有活跃告警,建议先确认监控面是否正在持续异常。"
-                    : "当前监控面已经出现异常或波动,建议先从指标和监控状态入手。";
+                    ? 
ObservabilityMessages.get("observability.telemetry.triage.metrics.alert.summary")
+                    : 
ObservabilityMessages.get("observability.telemetry.triage.metrics.monitor.summary");
             String whyNow = activeAlerts > 0
-                    ? "活跃告警通常意味着影响已经明确,可先查看监控和告警上下文。"
-                    : "异常监控最能快速反映健康度变化,适合作为第一观察面。";
-            return new EntityTriageRecommendation("rule", FOCUS_METRICS, 
"优先查看监控", summary, whyNow, "查看监控", now);
+                    ? 
ObservabilityMessages.get("observability.telemetry.triage.metrics.alert.reason")
+                    : 
ObservabilityMessages.get("observability.telemetry.triage.metrics.monitor.reason");
+            return new EntityTriageRecommendation(
+                    "rule",
+                    FOCUS_METRICS,
+                    
ObservabilityMessages.get("observability.telemetry.triage.metrics.title"),
+                    summary,
+                    whyNow,
+                    
ObservabilityMessages.get("observability.telemetry.triage.metrics.action"),
+                    now);
         }
         if (traceSummary != null && traceSummary.getRecentErrorTraceCount() > 
0 || !CollectionUtils.isEmpty(traceEvidence)) {
             return new EntityTriageRecommendation(
                     "rule",
                     FOCUS_TRACES,
-                    "优先查看链路",
-                    "当前链路侧已经出现错误或活跃调用,适合先查看调用路径和错误跨度。",
-                    "链路最容易帮助定位请求经过了哪些组件,以及失败发生在什么位置。",
-                    "查看链路",
+                    
ObservabilityMessages.get("observability.telemetry.triage.traces.title"),
+                    
ObservabilityMessages.get("observability.telemetry.triage.traces.summary"),
+                    
ObservabilityMessages.get("observability.telemetry.triage.traces.reason"),
+                    
ObservabilityMessages.get("observability.telemetry.triage.traces.action"),
                     now
             );
         }
@@ -987,20 +994,20 @@ public class TelemetryIntakeServiceImpl implements 
TelemetryEvidenceGateway {
             return new EntityTriageRecommendation(
                     "rule",
                     FOCUS_LOGS,
-                    "优先查看日志",
-                    "当前已有可直接使用的日志检索线索,适合先查看最近错误文本和上下文。",
-                    "日志通常能最快补全异常细节,帮助确认具体报错和影响范围。",
-                    "查看日志",
+                    
ObservabilityMessages.get("observability.telemetry.triage.logs.title"),
+                    
ObservabilityMessages.get("observability.telemetry.triage.logs.summary"),
+                    
ObservabilityMessages.get("observability.telemetry.triage.logs.reason"),
+                    
ObservabilityMessages.get("observability.telemetry.triage.logs.action"),
                     now
             );
         }
         return new EntityTriageRecommendation(
                 "rule",
                 FOCUS_EVIDENCE,
-                "继续补充证据",
-                "当前还没有足够强的三信号线索,建议先确认接入状态或补充更多运行数据。",
-                "先确认最近是否已经收到指标、日志或链路数据,再继续定位。",
-                "查看证据",
+                
ObservabilityMessages.get("observability.telemetry.triage.evidence.title"),
+                
ObservabilityMessages.get("observability.telemetry.triage.evidence.summary"),
+                
ObservabilityMessages.get("observability.telemetry.triage.evidence.reason"),
+                
ObservabilityMessages.get("observability.telemetry.triage.evidence.action"),
                 now
         );
     }
@@ -1269,12 +1276,12 @@ public class TelemetryIntakeServiceImpl implements 
TelemetryEvidenceGateway {
 
         List<String> segments = new ArrayList<>();
         if (StringUtils.hasText(compatibility)) {
-            segments.add("OTLP metrics 兼容性:" + switch (compatibility) {
-                case "supported" -> "支持";
-                case "partial" -> "部分支持";
-                case "unsupported" -> "不支持";
+            
segments.add(ObservabilityMessages.format("observability.telemetry.metric.context.compatibility",
 switch (compatibility) {
+                case "supported" -> 
ObservabilityMessages.get("observability.telemetry.metric.context.supported");
+                case "partial" -> 
ObservabilityMessages.get("observability.telemetry.metric.context.partial");
+                case "unsupported" -> 
ObservabilityMessages.get("observability.telemetry.metric.context.unsupported");
                 default -> compatibility;
-            });
+            }));
         }
         if (StringUtils.hasText(greptimeCompatibility) || 
StringUtils.hasText(facadeCompatibility)) {
             segments.add("Greptime=" + defaultText(greptimeCompatibility, 
"unknown")
@@ -1287,10 +1294,10 @@ public class TelemetryIntakeServiceImpl implements 
TelemetryEvidenceGateway {
             segments.add("monotonic=" + monotonic);
         }
         if ("summary".equals(metricType) && StringUtils.hasText(quantiles)) {
-            segments.add("Summary quantiles 已保留兼容元信息");
+            
segments.add(ObservabilityMessages.get("observability.telemetry.metric.context.summary-quantiles"));
         }
         if ("histogram".equals(metricType) && 
StringUtils.hasText(histogramBuckets) && StringUtils.hasText(histogramBounds)) {
-            segments.add("Histogram buckets/bounds 已保留兼容元信息");
+            
segments.add(ObservabilityMessages.get("observability.telemetry.metric.context.histogram-buckets"));
         }
         if (StringUtils.hasText(reason)) {
             segments.add(reason);
diff --git 
a/hertzbeat-observability/src/main/resources/observability_en_US.properties 
b/hertzbeat-observability/src/main/resources/observability_en_US.properties
new file mode 100644
index 0000000000..32d90f7f43
--- /dev/null
+++ b/hertzbeat-observability/src/main/resources/observability_en_US.properties
@@ -0,0 +1,120 @@
+observability.entity.next.review-alerts.title=Review active alerts
+observability.entity.next.review-alerts.description={0} active alerts are 
linked to this entity. Confirm the impact scope and current status first.
+observability.entity.next.review-alerts.action=View active alerts
+observability.entity.next.complete-owner.title=Assign owner
+observability.entity.next.complete-owner.description=This entity has no clear 
owner yet, so troubleshooting and governance actions lack accountability.
+observability.entity.next.complete-owner.action=Assign owner
+observability.entity.next.complete-runbook.title=Add runbook
+observability.entity.next.complete-runbook.description=Add a runbook or 
response entry first so alerts can lead directly to recovery.
+observability.entity.next.complete-runbook.action=Add runbook
+observability.entity.next.bind-monitor.title=Bind monitors
+observability.entity.next.bind-monitor.description=The entity exists but has 
no bound monitor tasks yet, so the current status can only stay at catalog 
metadata level.
+observability.entity.next.bind-monitor.action=Link monitors
+observability.entity.next.abnormal-monitors.title=Review abnormal monitors
+observability.entity.next.abnormal-monitors.description={0} monitors are 
abnormal. Confirm which evidence is most useful for deeper investigation.
+observability.entity.next.abnormal-monitors.action=View core monitors
+observability.entity.next.open-discovery.title=Merge evidence into entity
+observability.entity.next.open-discovery.description=There is not enough 
monitor, identity, or log evidence yet. Return to the discovery workbench to 
merge evidence first.
+observability.entity.next.open-discovery.action=Open discovery workbench
+observability.entity.next.inspect-logs.title=Review log clues
+observability.entity.next.inspect-logs.description=Log entry points are ready. 
Check whether OTel resource filters or fallback queries can locate the issue 
quickly.
+observability.entity.next.inspect-logs.action=Review log clues
+observability.entity.next.review-relations.title=Add key relations
+observability.entity.next.review-relations.description=Critical upstream and 
downstream relations are incomplete. Add the most important dependency 
boundaries first.
+observability.entity.next.review-relations.action=View relations
+observability.entity.next.fallback.title=Continue investigating this entity
+observability.entity.next.fallback.description=Catalog ownership and evidence 
are ready. Continue deeper analysis from logs or monitor status.
+observability.entity.next.fallback.action=Review log clues
+observability.entity.return-label.fallback=Entity detail
+observability.telemetry.metric.entity-monitor-status=Entity monitor status
+observability.telemetry.triage.metrics.alert.summary=Active alerts already 
exist. Confirm whether the monitor plane is still abnormal.
+observability.telemetry.triage.metrics.monitor.summary=The monitor plane has 
abnormal or fluctuating signals. Start with metrics and monitor status.
+observability.telemetry.triage.metrics.alert.reason=Active alerts usually mean 
the impact is clear. Review monitor and alert context first.
+observability.telemetry.triage.metrics.monitor.reason=Abnormal monitors often 
show health changes fastest, making them a good first observation surface.
+observability.telemetry.triage.metrics.title=Review monitors first
+observability.telemetry.triage.metrics.action=View monitors
+observability.telemetry.triage.traces.title=Review traces first
+observability.telemetry.triage.traces.summary=Trace-side errors or active 
calls are present. Inspect call paths and error spans first.
+observability.telemetry.triage.traces.reason=Traces help identify which 
components a request passed through and where the failure happened.
+observability.telemetry.triage.traces.action=View traces
+observability.telemetry.triage.logs.title=Review logs first
+observability.telemetry.triage.logs.summary=Direct log retrieval clues are 
available. Inspect recent error text and surrounding context first.
+observability.telemetry.triage.logs.reason=Logs usually fill in exception 
details fastest and help confirm concrete errors and impact scope.
+observability.telemetry.triage.logs.action=View logs
+observability.telemetry.triage.evidence.title=Collect more evidence
+observability.telemetry.triage.evidence.summary=There are not enough strong 
three-signal clues yet. Check intake status or add more runtime data first.
+observability.telemetry.triage.evidence.reason=Confirm whether recent metrics, 
logs, or traces have arrived before continuing the investigation.
+observability.telemetry.triage.evidence.action=View evidence
+observability.telemetry.metric.context.compatibility=OTLP metrics 
compatibility: {0}
+observability.telemetry.metric.context.supported=supported
+observability.telemetry.metric.context.partial=partial support
+observability.telemetry.metric.context.unsupported=unsupported
+observability.telemetry.metric.context.summary-quantiles=Summary quantiles 
compatibility metadata is retained.
+observability.telemetry.metric.context.histogram-buckets=Histogram buckets and 
bounds compatibility metadata is retained.
+observability.otlp.metric.compatibility.unknown-type=Unknown OTLP metric type. 
The current HertzBeat facade does not recognize it yet.
+observability.otlp.metric.compatibility.histogram.reason=Histogram metrics are 
written to Greptime, while the current HertzBeat facade keeps representative 
values and bucket/bounds metadata without full histogram query semantics.
+observability.otlp.metric.compatibility.exponential-histogram.reason=The 
current Greptime OTLP metrics data model does not support ExponentialHistogram. 
HertzBeat keeps representative values and compatibility metadata.
+observability.otlp.metric.compatibility.summary.reason=Summary quantile 
semantics are limited by the Greptime data model and the current HertzBeat 
facade. Summary and quantile metadata is retained.
+observability.otlp.metric.greptime.reason.supported=The current Greptime OTLP 
metrics data model supports this type.
+observability.otlp.metric.greptime.reason.partial=The current Greptime OTLP 
metrics data model preserves this type only partially.
+observability.otlp.metric.greptime.reason.unsupported=The current Greptime 
OTLP metrics data model does not support this type.
+observability.otlp.metric.facade.reason.supported=The current HertzBeat facade 
can consume this type directly.
+observability.otlp.metric.facade.reason.partial=The current HertzBeat facade 
keeps only representative values and compatibility metadata.
+observability.otlp.metric.facade.reason.unsupported=The current HertzBeat 
facade does not support this type.
+observability.otlp.overview.event.metrics.title=Metrics received
+observability.otlp.overview.event.metrics.copy=Recent metrics data received
+observability.otlp.overview.metrics.active=Recent queryable metrics data has 
been received.
+observability.otlp.overview.metrics.inactive=No metrics data has arrived in 
the last 24 hours.
+observability.otlp.overview.logs.active=Logs data has arrived in the last 24 
hours.
+observability.otlp.overview.logs.inactive=No logs data has arrived in the last 
24 hours.
+observability.otlp.overview.traces.active=Trace data has arrived in the last 
24 hours.
+observability.otlp.overview.traces.inactive=No trace data has arrived in the 
last 24 hours.
+observability.otlp.overview.metrics.mode.mixed=OTLP + entity monitors
+observability.otlp.overview.metrics.mode.monitor=Entity monitors
+observability.otlp.readiness.collector.title=Collector cluster
+observability.otlp.readiness.collector.unregistered=No Collector is registered.
+observability.otlp.readiness.collector.deploy=Deploy a Collector or use the 
built-in intake endpoint first.
+observability.otlp.readiness.collector.online={0} / {1} online
+observability.otlp.readiness.collector.accepting=Collector nodes can receive 
tasks.
+observability.otlp.readiness.collector.offline={0} collector nodes are offline.
+observability.otlp.readiness.collector.all-offline=All collector nodes are 
offline.
+observability.otlp.readiness.storage.title=History storage
+observability.otlp.readiness.storage.disabled=History storage is not enabled.
+observability.otlp.readiness.storage.check-config=Check the history storage 
configuration.
+observability.otlp.readiness.storage.available={0} / {1} available
+observability.otlp.readiness.storage.reader-available=HistoryDataReader is 
available.
+observability.otlp.readiness.storage.partial=Some history storage backends are 
unavailable.
+observability.otlp.readiness.query.title=Query service
+observability.otlp.readiness.query.available=Metrics, logs, and traces queries 
are available.
+observability.otlp.readiness.query.promql-history=PromQL and history queries 
are available.
+observability.otlp.readiness.query.partial=Some query capabilities are 
available.
+observability.otlp.readiness.query.promql-only=PromQL is available. History 
queries need checking.
+observability.otlp.readiness.query.history-only=History queries are available. 
PromQL needs checking.
+observability.otlp.readiness.query.unavailable=Query service is unavailable.
+observability.otlp.readiness.query.check-config=Check the PromQL and history 
query configuration.
+observability.otlp.readiness.greptime.disabled=GreptimeDB is not enabled.
+observability.otlp.readiness.greptime.other-storage=Another history storage 
backend is in use, or storage is not configured yet.
+observability.otlp.readiness.greptime.sql-not-ready=GreptimeDB is enabled, but 
the SQL executor is not ready.
+observability.otlp.readiness.greptime.check-http=Check the GreptimeDB HTTP 
configuration.
+observability.otlp.readiness.greptime.sql-ok=SQL self-check passed.
+observability.otlp.readiness.greptime.select-ok=SELECT 1 succeeded.
+observability.otlp.readiness.greptime.sql-failed=SQL self-check failed.
+observability.otlp.readiness.greptime.check-connection=Check the GreptimeDB 
connection.
+observability.otlp.guide.snippet.python.http.comment=All signals are sent to 
the HertzBeat OTLP HTTP endpoint:
+observability.otlp.guide.snippet.python.grpc.comment=All signals are sent to 
the HertzBeat OTLP gRPC endpoint:
+observability.otlp.guide.metrics.http.description=Send metrics data to this 
OTLP HTTP endpoint. After data arrives, continue from monitors and entity 
details.
+observability.otlp.guide.metrics.http.note=If monitor tasks are already 
configured, the data continues to appear in the corresponding monitors.
+observability.otlp.guide.logs.http.description=Send logs data to this OTLP 
HTTP endpoint. After data arrives, review it from log management.
+observability.otlp.guide.logs.http.note=Use an intake token for authentication.
+observability.otlp.guide.traces.http.description=Send trace data to this OTLP 
HTTP endpoint. After data arrives, review it from trace management.
+observability.otlp.guide.traces.http.note=The system tries to associate data 
with entities from service information automatically.
+observability.otlp.guide.metrics.grpc.description=Send metrics data to this 
OTLP gRPC endpoint. This fits services already using an OpenTelemetry Collector 
or language SDK.
+observability.otlp.guide.metrics.grpc.note=After data arrives, continue from 
monitors and entity details.
+observability.otlp.guide.logs.grpc.description=Send logs data to this OTLP 
gRPC endpoint. After data arrives, review it from log management.
+observability.otlp.guide.logs.grpc.note=Use the Authorization Bearer intake 
token for authentication.
+observability.otlp.guide.traces.grpc.description=Send trace data to this OTLP 
gRPC endpoint. After data arrives, review it from trace management.
+observability.otlp.guide.traces.grpc.note=The system tries to associate data 
with entities from service information automatically.
+observability.otlp.guide.snippet.java-env=Java environment variables
+observability.otlp.guide.snippet.python-resource=Python resource attributes
+observability.otlp.metrics-console.no-context=Missing service context for 
building the OTLP metrics query.
+observability.otlp.metrics-console.promql-unavailable=No available PromQL 
metrics query executor is configured in the current environment.
diff --git 
a/hertzbeat-observability/src/main/resources/observability_zh_CN.properties 
b/hertzbeat-observability/src/main/resources/observability_zh_CN.properties
new file mode 100644
index 0000000000..a07bf338e6
--- /dev/null
+++ b/hertzbeat-observability/src/main/resources/observability_zh_CN.properties
@@ -0,0 +1,120 @@
+observability.entity.next.review-alerts.title=处理活跃告警
+observability.entity.next.review-alerts.description={0} 
条活跃告警已经关联到当前实体,先确认影响范围和当前状态。
+observability.entity.next.review-alerts.action=查看活跃告警
+observability.entity.next.complete-owner.title=补负责人
+observability.entity.next.complete-owner.description=当前实体还没有明确负责人,排障和治理动作缺少责任归属。
+observability.entity.next.complete-owner.action=补负责人
+observability.entity.next.complete-runbook.title=补处置手册
+observability.entity.next.complete-runbook.description=先补一条 runbook 
或处置入口,告警后才能直接推动恢复。
+observability.entity.next.complete-runbook.action=补处置手册
+observability.entity.next.bind-monitor.title=补绑监控
+observability.entity.next.bind-monitor.description=实体已经存在,但还没有绑定任何监控任务,当前状态只能停留在目录元数据层。
+observability.entity.next.bind-monitor.action=关联监控
+observability.entity.next.abnormal-monitors.title=查看异常监控
+observability.entity.next.abnormal-monitors.description={0} 
个监控当前异常,建议先确认哪个证据最值得继续排查。
+observability.entity.next.abnormal-monitors.action=查看核心监控
+observability.entity.next.open-discovery.title=归并证据到实体
+observability.entity.next.open-discovery.description=当前还没有形成足够的监控、身份或日志线索,建议先回到发现工作台归并证据。
+observability.entity.next.open-discovery.action=打开发现工作台
+observability.entity.next.inspect-logs.title=查看日志线索
+observability.entity.next.inspect-logs.description=日志入口已经具备,先确认 OTel resource 
或 fallback 查询是否能快速定位异常。
+observability.entity.next.inspect-logs.action=查看日志线索
+observability.entity.next.review-relations.title=补关键关系
+observability.entity.next.review-relations.description=关键上下游关系还不完整,建议先补齐最关键的依赖边界。
+observability.entity.next.review-relations.action=查看关系
+observability.entity.next.fallback.title=继续排查当前实体
+observability.entity.next.fallback.description=目录归属和证据都已经具备,可以直接从日志或监控继续深入分析。
+observability.entity.next.fallback.action=查看日志线索
+observability.entity.return-label.fallback=实体详情
+observability.telemetry.metric.entity-monitor-status=实体监控状态
+observability.telemetry.triage.metrics.alert.summary=当前已有活跃告警,建议先确认监控面是否正在持续异常。
+observability.telemetry.triage.metrics.monitor.summary=当前监控面已经出现异常或波动,建议先从指标和监控状态入手。
+observability.telemetry.triage.metrics.alert.reason=活跃告警通常意味着影响已经明确,可先查看监控和告警上下文。
+observability.telemetry.triage.metrics.monitor.reason=异常监控最能快速反映健康度变化,适合作为第一观察面。
+observability.telemetry.triage.metrics.title=优先查看监控
+observability.telemetry.triage.metrics.action=查看监控
+observability.telemetry.triage.traces.title=优先查看链路
+observability.telemetry.triage.traces.summary=当前链路侧已经出现错误或活跃调用,适合先查看调用路径和错误跨度。
+observability.telemetry.triage.traces.reason=链路最容易帮助定位请求经过了哪些组件,以及失败发生在什么位置。
+observability.telemetry.triage.traces.action=查看链路
+observability.telemetry.triage.logs.title=优先查看日志
+observability.telemetry.triage.logs.summary=当前已有可直接使用的日志检索线索,适合先查看最近错误文本和上下文。
+observability.telemetry.triage.logs.reason=日志通常能最快补全异常细节,帮助确认具体报错和影响范围。
+observability.telemetry.triage.logs.action=查看日志
+observability.telemetry.triage.evidence.title=继续补充证据
+observability.telemetry.triage.evidence.summary=当前还没有足够强的三信号线索,建议先确认接入状态或补充更多运行数据。
+observability.telemetry.triage.evidence.reason=先确认最近是否已经收到指标、日志或链路数据,再继续定位。
+observability.telemetry.triage.evidence.action=查看证据
+observability.telemetry.metric.context.compatibility=OTLP metrics 兼容性:{0}
+observability.telemetry.metric.context.supported=支持
+observability.telemetry.metric.context.partial=部分支持
+observability.telemetry.metric.context.unsupported=不支持
+observability.telemetry.metric.context.summary-quantiles=Summary quantiles 
已保留兼容元信息
+observability.telemetry.metric.context.histogram-buckets=Histogram 
buckets/bounds 已保留兼容元信息
+observability.otlp.metric.compatibility.unknown-type=未知 OTLP metric 类型,当前未被 
HertzBeat facade 识别。
+observability.otlp.metric.compatibility.histogram.reason=Histogram 指标已写入 
Greptime,但 HertzBeat 当前 facade 仅保留代表值与 bucket/bounds 元信息,未提供完整 histogram 查询语义。
+observability.otlp.metric.compatibility.exponential-histogram.reason=Greptime 
当前 OTLP metrics 数据模型不支持 ExponentialHistogram;HertzBeat 仅保留代表值与兼容性元信息。
+observability.otlp.metric.compatibility.summary.reason=Summary quantiles 语义受 
Greptime 数据模型与当前 HertzBeat facade 限制,当前仅保留 summary/quantiles 元信息。
+observability.otlp.metric.greptime.reason.supported=Greptime 当前 OTLP metrics 
数据模型支持该类型。
+observability.otlp.metric.greptime.reason.partial=Greptime 当前 OTLP metrics 
数据模型仅部分保留该类型语义。
+observability.otlp.metric.greptime.reason.unsupported=Greptime 当前 OTLP metrics 
数据模型不支持该类型。
+observability.otlp.metric.facade.reason.supported=HertzBeat 当前 facade 可直接消费该类型。
+observability.otlp.metric.facade.reason.partial=HertzBeat 当前 facade 
仅保留代表值与兼容元信息。
+observability.otlp.metric.facade.reason.unsupported=HertzBeat 当前 facade 不支持该类型。
+observability.otlp.overview.event.metrics.title=指标已接入
+observability.otlp.overview.event.metrics.copy=最近已收到指标数据
+observability.otlp.overview.metrics.active=最近已收到可查看的指标数据
+observability.otlp.overview.metrics.inactive=最近 24 小时还没有指标数据
+observability.otlp.overview.logs.active=最近 24 小时已收到日志数据
+observability.otlp.overview.logs.inactive=最近 24 小时还没有日志数据
+observability.otlp.overview.traces.active=最近 24 小时已收到链路数据
+observability.otlp.overview.traces.inactive=最近 24 小时还没有链路数据
+observability.otlp.overview.metrics.mode.mixed=OTLP + 实体监控
+observability.otlp.overview.metrics.mode.monitor=实体监控
+observability.otlp.readiness.collector.title=Collector 集群
+observability.otlp.readiness.collector.unregistered=未注册 Collector
+observability.otlp.readiness.collector.deploy=先部署 Collector 或使用内置采集入口
+observability.otlp.readiness.collector.online={0} / {1} 在线
+observability.otlp.readiness.collector.accepting=采集节点可接收任务
+observability.otlp.readiness.collector.offline={0} 个采集节点离线
+observability.otlp.readiness.collector.all-offline=所有采集节点离线
+observability.otlp.readiness.storage.title=历史存储
+observability.otlp.readiness.storage.disabled=未启用历史存储
+observability.otlp.readiness.storage.check-config=检查历史存储配置
+observability.otlp.readiness.storage.available={0} / {1} 可用
+observability.otlp.readiness.storage.reader-available=HistoryDataReader 可用
+observability.otlp.readiness.storage.partial=部分历史存储不可用
+observability.otlp.readiness.query.title=查询服务
+observability.otlp.readiness.query.available=指标、日志和链路查询可用
+observability.otlp.readiness.query.promql-history=PromQL 与历史查询可用
+observability.otlp.readiness.query.partial=部分查询能力可用
+observability.otlp.readiness.query.promql-only=PromQL 可用,历史查询待检查
+observability.otlp.readiness.query.history-only=历史查询可用,PromQL 待检查
+observability.otlp.readiness.query.unavailable=查询服务不可用
+observability.otlp.readiness.query.check-config=检查 PromQL 与历史查询配置
+observability.otlp.readiness.greptime.disabled=未启用 GreptimeDB
+observability.otlp.readiness.greptime.other-storage=当前使用其他历史存储或尚未配置
+observability.otlp.readiness.greptime.sql-not-ready=GreptimeDB 已启用,SQL 执行器未就绪
+observability.otlp.readiness.greptime.check-http=检查 GreptimeDB HTTP 配置
+observability.otlp.readiness.greptime.sql-ok=SQL 自检通过
+observability.otlp.readiness.greptime.select-ok=SELECT 1 成功
+observability.otlp.readiness.greptime.sql-failed=SQL 自检失败
+observability.otlp.readiness.greptime.check-connection=检查 GreptimeDB 连接
+observability.otlp.guide.snippet.python.http.comment=所有 signals 统一发送到 
HertzBeat OTLP HTTP 入口:
+observability.otlp.guide.snippet.python.grpc.comment=所有 signals 统一发送到 
HertzBeat OTLP gRPC 入口:
+observability.otlp.guide.metrics.http.description=把指标数据发送到这个 OTLP HTTP 
地址。收到数据后,可直接在监控和实体详情中继续查看。
+observability.otlp.guide.metrics.http.note=如果已经配置了监控任务,数据会继续显示在对应的监控项中。
+observability.otlp.guide.logs.http.description=把日志数据发送到这个 OTLP HTTP 
地址。收到数据后,可直接前往日志管理查看。
+observability.otlp.guide.logs.http.note=使用接入令牌即可完成认证。
+observability.otlp.guide.traces.http.description=把链路数据发送到这个 OTLP HTTP 
地址。收到数据后,可直接前往链路管理查看。
+observability.otlp.guide.traces.http.note=系统会根据服务信息自动尝试关联到实体。
+observability.otlp.guide.metrics.grpc.description=把指标数据发送到这个 OTLP gRPC 
地址,适合已经使用 OpenTelemetry Collector 或语言 SDK 的服务。
+observability.otlp.guide.metrics.grpc.note=收到数据后,可直接在监控和实体详情中继续查看。
+observability.otlp.guide.logs.grpc.description=把日志数据发送到这个 OTLP gRPC 
地址。收到数据后,可直接前往日志管理查看。
+observability.otlp.guide.logs.grpc.note=使用 Authorization Bearer 接入令牌即可完成认证。
+observability.otlp.guide.traces.grpc.description=把链路数据发送到这个 OTLP gRPC 
地址。收到数据后,可直接前往链路管理查看。
+observability.otlp.guide.traces.grpc.note=系统会根据服务信息自动尝试关联到实体。
+observability.otlp.guide.snippet.java-env=Java 环境变量
+observability.otlp.guide.snippet.python-resource=Python 资源属性
+observability.otlp.metrics-console.no-context=缺少可用于构建 OTLP 指标查询的服务上下文。
+observability.otlp.metrics-console.promql-unavailable=当前环境未配置可用的 PromQL 
指标查询执行器。
diff --git 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionControllerTest.java
 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionControllerTest.java
index f7b11a189f..88fa4e03b0 100644
--- 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionControllerTest.java
+++ 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionControllerTest.java
@@ -74,7 +74,8 @@ class OtlpIngestionControllerTest {
         );
         overview.setReadinessChecks(List.of(
                 new OtlpIngestionOverviewDto.ReadinessCheck(
-                        "collector", "Collector 集群", "success", "1 / 1 在线", 
"采集节点可接收任务", 1_710_000_000_100L)
+                        "collector", "Collector cluster", "success", "1 / 1 
online",
+                        "Collector nodes can receive tasks.", 
1_710_000_000_100L)
         ));
         when(otlpIngestionWorkspaceService.getOverview()).thenReturn(overview);
 
@@ -85,7 +86,7 @@ class OtlpIngestionControllerTest {
                 .andExpect(jsonPath("$.data.boundEntityCount").value(4))
                 .andExpect(jsonPath("$.data.logs.totalCount").value(5))
                 
.andExpect(jsonPath("$.data.readinessChecks[0].key").value("collector"))
-                
.andExpect(jsonPath("$.data.readinessChecks[0].summary").value("1 / 1 在线"));
+                
.andExpect(jsonPath("$.data.readinessChecks[0].summary").value("1 / 1 online"));
 
         verify(otlpIngestionWorkspaceService).getOverview();
     }
diff --git 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionServiceSourceLocalizationTest.java
 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionServiceSourceLocalizationTest.java
new file mode 100644
index 0000000000..ae7576a549
--- /dev/null
+++ 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionServiceSourceLocalizationTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package org.apache.hertzbeat.observability.ingestion.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
+
+class OtlpIngestionServiceSourceLocalizationTest {
+
+    private static final List<Path> PRODUCTION_SOURCES = List.of(
+            
Path.of("src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/"
+                    + "OtlpGrpcIngestionServiceImpl.java"),
+            
Path.of("src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/"
+                    + "OtlpIngestionWorkspaceServiceImpl.java")
+    );
+    private static final Pattern HAN_SCRIPT = 
Pattern.compile("\\p{Script=Han}");
+
+    @Test
+    void productionSourcesShouldNotContainHanScriptLiterals() throws 
IOException {
+        for (Path productionSource : PRODUCTION_SOURCES) {
+            String source = Files.readString(productionSource);
+            assertFalse(HAN_SCRIPT.matcher(source).find(),
+                    () -> "source file must not contain Han-script literals: " 
+ productionSource);
+        }
+    }
+}
diff --git 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
index 4f3309d874..ab48464a6f 100644
--- 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
+++ 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
@@ -36,6 +36,7 @@ import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import org.apache.hertzbeat.common.entity.dto.query.DatasourceQueryData;
@@ -58,6 +59,7 @@ import 
org.apache.hertzbeat.warehouse.repository.MetricQueryRepository;
 import org.apache.hertzbeat.warehouse.db.GreptimeSqlQueryExecutor;
 import org.apache.hertzbeat.warehouse.store.history.tsdb.HistoryDataReader;
 import 
org.apache.hertzbeat.warehouse.store.history.tsdb.greptime.GreptimeProperties;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -89,8 +91,12 @@ class OtlpIngestionWorkspaceServiceImplTest {
 
     private ObservabilitySignalIntakeGateway observabilitySignalIntakeGateway;
 
+    private Locale previousLocale;
+
     @BeforeEach
     void setUp() {
+        previousLocale = Locale.getDefault();
+        Locale.setDefault(Locale.US);
         observabilitySignalIntakeGateway = new 
InMemoryObservabilitySignalIntakeGateway();
         otlpIngestionWorkspaceService = new OtlpIngestionWorkspaceServiceImpl(
                 entityTraceQueryService,
@@ -104,6 +110,11 @@ class OtlpIngestionWorkspaceServiceImplTest {
         );
     }
 
+    @AfterEach
+    void restoreLocale() {
+        Locale.setDefault(previousLocale);
+    }
+
     private void stubRecentLogs(LogEntry... logs) {
         when(logQueryRepository.queryRecentLogs(anyLong(), anyLong(), 
eq(20))).thenReturn(List.of(logs));
     }
@@ -208,16 +219,16 @@ class OtlpIngestionWorkspaceServiceImplTest {
                 
overview.getReadinessChecks().stream().map(OtlpIngestionOverviewDto.ReadinessCheck::getKey).toList());
         assertTrue(overview.getReadinessChecks().stream().anyMatch(check -> 
"collector".equals(check.getKey())
                 && "warning".equals(check.getStatus())
-                && check.getSummary().contains("2 / 3 在线")));
+                && check.getSummary().contains("2 / 3 online")));
         assertTrue(overview.getReadinessChecks().stream().anyMatch(check -> 
"storage".equals(check.getKey())
                 && "success".equals(check.getStatus())
-                && check.getSummary().contains("1 / 1 可用")));
+                && check.getSummary().contains("1 / 1 available")));
         assertTrue(overview.getReadinessChecks().stream().anyMatch(check -> 
"query".equals(check.getKey())
                 && "success".equals(check.getStatus())
-                && check.getSummary().contains("指标、日志和链路查询可用")));
+                && check.getSummary().contains("Metrics, logs, and traces 
queries are available.")));
         assertTrue(overview.getReadinessChecks().stream().anyMatch(check -> 
"greptime".equals(check.getKey())
                 && "success".equals(check.getStatus())
-                && check.getSummary().contains("SQL 自检通过")));
+                && check.getSummary().contains("SQL self-check passed.")));
     }
 
     @Test
@@ -280,7 +291,7 @@ class OtlpIngestionWorkspaceServiceImplTest {
                 && snippet.getContent().contains("endpoint: 
demo.hertzbeat.apache.org:4317")
                 && snippet.getContent().contains("Authorization: \"Bearer 
<api-token>\"")));
         assertTrue(guide.getSignals().stream().filter(signal -> 
"grpc".equals(signal.getProtocol()))
-                .allMatch(signal -> signal.getNote() == null || 
!signal.getNote().contains("登录 token")));
+                .allMatch(signal -> signal.getNote() == null || 
!signal.getNote().contains("login token")));
         assertFalse(guide.getSnippets().isEmpty());
     }
 
diff --git 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/shared/service/impl/EntityObservabilityGatewayImplTest.java
 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/shared/service/impl/EntityObservabilityGatewayImplTest.java
index 9fbb86ddb3..e2fcb981ff 100644
--- 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/shared/service/impl/EntityObservabilityGatewayImplTest.java
+++ 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/shared/service/impl/EntityObservabilityGatewayImplTest.java
@@ -25,10 +25,15 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.time.LocalDateTime;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.regex.Pattern;
 import org.apache.hertzbeat.common.constants.CommonConstants;
 import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
 import org.apache.hertzbeat.common.entity.manager.EntityIdentity;
@@ -59,16 +64,45 @@ import 
org.apache.hertzbeat.common.observability.dto.trace.EntityTraceQueryHintD
 import 
org.apache.hertzbeat.common.observability.dto.trace.EntityTraceSummaryDto;
 import org.apache.hertzbeat.common.observability.model.ObservedEntityContext;
 import 
org.apache.hertzbeat.observability.traces.service.EntityTraceQueryService;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
 class EntityObservabilityGatewayImplTest {
 
+    private static final Pattern HAN_SCRIPT = 
Pattern.compile("\\p{Script=Han}");
+    private static final Path ENTITY_OBSERVABILITY_GATEWAY_IMPL = Path.of(
+            
"src/main/java/org/apache/hertzbeat/observability/shared/service/impl/EntityObservabilityGatewayImpl.java");
+
     private final TelemetryIntakeServiceImpl telemetryIntakeService = 
Mockito.mock(TelemetryIntakeServiceImpl.class);
     private final EntityTraceQueryService entityTraceQueryService = 
Mockito.mock(EntityTraceQueryService.class);
     private final EntityObservabilityGatewayImpl gateway =
             new EntityObservabilityGatewayImpl(telemetryIntakeService, 
entityTraceQueryService);
 
+    private Locale previousLocale;
+
+    @BeforeEach
+    void useEnglishCatalog() {
+        previousLocale = Locale.getDefault();
+        Locale.setDefault(Locale.US);
+        ResourceBundle.clearCache();
+    }
+
+    @AfterEach
+    void restoreLocale() {
+        Locale.setDefault(previousLocale);
+        ResourceBundle.clearCache();
+    }
+
+    @Test
+    void sourceShouldNotContainHanScriptUserFacingCopy() throws Exception {
+        String source = Files.readString(ENTITY_OBSERVABILITY_GATEWAY_IMPL);
+
+        assertFalse(HAN_SCRIPT.matcher(source).find(),
+                "User-facing entity observability copy belongs in locale 
resources, not Java source");
+    }
+
     @Test
     void resolveEntityTraceSummaryShouldFallbackToQueryServiceWhenInactive() {
         ObservedEntityContext entityContext = ObservedEntityContext.from(null, 
Collections.emptyList());
@@ -132,7 +166,7 @@ class EntityObservabilityGatewayImplTest {
         EntityUnifiedEvidenceSummary unifiedEvidenceSummary =
                 new EntityUnifiedEvidenceSummary(3, true, true, true, 1L, 1, 
1, 12L, List.of("metrics", "logs", "traces"));
         EntityTriageRecommendation triageRecommendation =
-                new EntityTriageRecommendation("focus_logs", "logs", "处理日志", 
"查看日志", "high", "进入日志", 13L);
+                new EntityTriageRecommendation("focus_logs", "logs", "Handle 
logs", "Review logs", "high", "Open logs", 13L);
 
         when(telemetryIntakeService.buildMetricEvidence(any(), any(), 
any())).thenReturn(metricEvidence);
         when(telemetryIntakeService.buildLogEvidence(any(), any(), 
any())).thenReturn(logEvidence);
@@ -161,7 +195,7 @@ class EntityObservabilityGatewayImplTest {
     @Test
     void enrichEntityLogQueryHintsShouldPreferEvidenceAndTraceFallbacks() {
         EntityLogQueryHint originalHint = new EntityLogQueryHint();
-        originalHint.setTitle("原始日志");
+        originalHint.setTitle("Original logs");
         originalHint.setResourceFilters(Map.of());
         originalHint.setSearchTerms(List.of());
 
@@ -195,7 +229,7 @@ class EntityObservabilityGatewayImplTest {
 
         assertEquals(1, enriched.size());
         EntityLogQueryHint first = enriched.getFirst();
-        assertEquals("原始日志", first.getTitle());
+        assertEquals("Original logs", first.getTitle());
         assertEquals(Map.of("service.name", "checkout"), 
first.getResourceFilters());
         assertEquals(List.of("checkout", "error"), first.getSearchTerms());
         assertEquals("trace-1", first.getTraceId());
@@ -211,7 +245,7 @@ class EntityObservabilityGatewayImplTest {
     @Test
     void buildEntityLogSummaryShouldUsePreferredHintAsSummarySource() {
         EntityLogQueryHint hint = new EntityLogQueryHint(
-                "日志入口",
+                "Log entry",
                 Map.of("service.name", "checkout"),
                 List.of("checkout", "error"),
                 "trace-1",
@@ -226,8 +260,8 @@ class EntityObservabilityGatewayImplTest {
         EntityLogSummaryInfo summary = 
gateway.buildEntityLogSummary(List.of(hint));
 
         assertEquals(1, summary.getHintCount());
-        assertEquals("日志入口", summary.getPreferredQueryType());
-        assertEquals("日志入口", summary.getPreferredQueryTitle());
+        assertEquals("Log entry", summary.getPreferredQueryType());
+        assertEquals("Log entry", summary.getPreferredQueryTitle());
         assertEquals(Map.of("service.name", "checkout"), 
summary.getPreferredResourceFilters());
         assertEquals(List.of("checkout", "error"), 
summary.getPreferredSearchTerms());
         assertEquals("checkout", summary.getFallbackSearchTerm());
@@ -494,7 +528,7 @@ class EntityObservabilityGatewayImplTest {
 
         entity.setDisplayName(null);
         assertEquals("checkout-service", 
gateway.buildEntityReturnLabel(entityContext));
-        assertEquals("实体详情", 
gateway.buildEntityReturnLabel(ObservedEntityContext.from(null, 
Collections.emptyList())));
+        assertEquals("Entity detail", 
gateway.buildEntityReturnLabel(ObservedEntityContext.from(null, 
Collections.emptyList())));
     }
 
     @Test
@@ -654,7 +688,7 @@ class EntityObservabilityGatewayImplTest {
     void 
buildEntityDiscoveryHandoffShouldCarryEntityContextAndPreferAlertQuery() {
         EntityResponseHandoffInfo handoff = 
gateway.buildEntityDiscoveryHandoff(
                 "/entities/1",
-                "结账服务",
+                "Checkout service",
                 "team-a",
                 "checkout",
                 "prod",
@@ -665,7 +699,7 @@ class EntityObservabilityGatewayImplTest {
         );
 
         assertEquals("/entities/1", handoff.getReturnTo());
-        assertEquals("结账服务", handoff.getReturnLabel());
+        assertEquals("Checkout service", handoff.getReturnLabel());
         assertEquals("alert-token", handoff.getQuery());
         assertEquals("team-a", handoff.getOwner());
         assertEquals("checkout", handoff.getSystem());
@@ -676,13 +710,13 @@ class EntityObservabilityGatewayImplTest {
     @Test
     void buildEntityEditorHandoffShouldSelectFocusByReadinessPriority() {
         EntityResponseHandoffInfo ownershipHandoff =
-                gateway.buildEntityEditorHandoff("/entities/1", "结账服务", false, 
true, true, true);
+                gateway.buildEntityEditorHandoff("/entities/1", "Checkout 
service", false, true, true, true);
         EntityResponseHandoffInfo relationHandoff =
-                gateway.buildEntityEditorHandoff("/entities/1", "结账服务", true, 
true, false, true);
+                gateway.buildEntityEditorHandoff("/entities/1", "Checkout 
service", true, true, false, true);
         EntityResponseHandoffInfo monitorHandoff =
-                gateway.buildEntityEditorHandoff("/entities/1", "结账服务", true, 
true, true, false);
+                gateway.buildEntityEditorHandoff("/entities/1", "Checkout 
service", true, true, true, false);
         EntityResponseHandoffInfo defaultHandoff =
-                gateway.buildEntityEditorHandoff("/entities/1", "结账服务", true, 
true, true, true);
+                gateway.buildEntityEditorHandoff("/entities/1", "Checkout 
service", true, true, true, true);
 
         assertEquals("ownership", ownershipHandoff.getFocus());
         assertEquals("relations", relationHandoff.getFocus());
@@ -694,7 +728,7 @@ class EntityObservabilityGatewayImplTest {
     void buildEntityResponseHandoffsShouldAssembleAllTargetsFromRequest() {
         ObserveEntity entity = new ObserveEntity();
         entity.setName("checkout");
-        entity.setDisplayName("结账服务");
+        entity.setDisplayName("Checkout service");
         EntityIdentity identity = EntityIdentity.builder()
                 .identityKey("service.name")
                 .identityValue("checkout")
@@ -704,7 +738,7 @@ class EntityObservabilityGatewayImplTest {
         EntityTraceSummaryDto traceSummary = new EntityTraceSummaryDto(1, 1, 
20L, true, "trace-1");
         EntityResponseHandoffsRequest request = new 
EntityResponseHandoffsRequest(
                 "/entities/1",
-                "结账服务",
+                "Checkout service",
                 "team-a",
                 "checkout-system",
                 "prod",
@@ -758,7 +792,7 @@ class EntityObservabilityGatewayImplTest {
         );
         EntityResponseHandoffsRequest request = new 
EntityResponseHandoffsRequest(
                 "/entities/1",
-                "结账服务",
+                "Checkout service",
                 "team-a",
                 "checkout-system",
                 "prod",
diff --git 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/shared/service/impl/TelemetryIntakeServiceImplTest.java
 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/shared/service/impl/TelemetryIntakeServiceImplTest.java
index e60f26de0a..0ba7c3bec9 100644
--- 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/shared/service/impl/TelemetryIntakeServiceImplTest.java
+++ 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/shared/service/impl/TelemetryIntakeServiceImplTest.java
@@ -24,9 +24,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.regex.Pattern;
 import org.apache.hertzbeat.common.entity.log.LogEntry;
 import org.apache.hertzbeat.common.entity.manager.EntityIdentity;
 import org.apache.hertzbeat.common.entity.manager.ObserveEntity;
@@ -44,6 +49,8 @@ import 
org.apache.hertzbeat.common.observability.model.CodeNavigationHint;
 import org.apache.hertzbeat.common.observability.model.ObservedEntityContext;
 import org.apache.hertzbeat.common.util.JsonUtil;
 import org.apache.hertzbeat.warehouse.repository.LogQueryRepository;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
@@ -51,10 +58,37 @@ import org.mockito.Mockito;
 
 class TelemetryIntakeServiceImplTest {
 
+    private static final Pattern HAN_SCRIPT = 
Pattern.compile("\\p{Script=Han}");
+    private static final Path TELEMETRY_INTAKE_SERVICE_IMPL = Path.of(
+            
"src/main/java/org/apache/hertzbeat/observability/shared/service/impl/TelemetryIntakeServiceImpl.java");
+
     private final LogQueryRepository logQueryRepository = 
Mockito.mock(LogQueryRepository.class);
     private final TelemetryIntakeServiceImpl telemetryIntakeService =
             new TelemetryIntakeServiceImpl(logQueryRepository);
 
+    private Locale previousLocale;
+
+    @BeforeEach
+    void useEnglishCatalog() {
+        previousLocale = Locale.getDefault();
+        Locale.setDefault(Locale.US);
+        ResourceBundle.clearCache();
+    }
+
+    @AfterEach
+    void restoreLocale() {
+        Locale.setDefault(previousLocale);
+        ResourceBundle.clearCache();
+    }
+
+    @Test
+    void sourceShouldNotContainHanScriptUserFacingCopy() throws Exception {
+        String source = Files.readString(TELEMETRY_INTAKE_SERVICE_IMPL);
+
+        assertFalse(HAN_SCRIPT.matcher(source).find(),
+                "User-facing telemetry intake copy belongs in locale 
resources, not Java source");
+    }
+
     @Test
     void buildCodeNavigationHintUsesEntityCodeLocations() {
         ObservedEntityContext entityContext = 
buildObservedEntityContextWithCodeLocations("https://github.com/apache/hertzbeat.git";);
@@ -68,7 +102,7 @@ class TelemetryIntakeServiceImplTest {
                         "code.filepath", 
"hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/ObserveEntityServiceImpl.java"
                 ),
                 List.of("localOtlpTraceIngest"),
-                "查看代码"
+                "View code"
         );
 
         assertNotNull(hint);
@@ -657,7 +691,7 @@ class TelemetryIntakeServiceImplTest {
                         "instance", "e2e",
                         "otlp.metric.compatibility", "partial",
                         "otlp.metric.compatibility.reason",
-                        "Summary quantiles 当前仅作为兼容元信息保留,尚未作为一等查询语义暴露。",
+                        "Summary quantiles are retained as compatibility 
metadata only.",
                         "otlp.metric.greptime.compatibility", "partial",
                         "otlp.metric.facade.compatibility", "partial",
                         "otlp.metric.summary.quantiles",
@@ -708,7 +742,7 @@ class TelemetryIntakeServiceImplTest {
         assertEquals("partial", 
metricEvidence.getAttributes().get("otlp.metric.compatibility"));
         
assertTrue(metricEvidence.getAttributes().get("otlp.metric.summary.quantiles").contains("\"quantile\":0.95"));
         assertNotNull(metricEvidence.getOtelContext());
-        assertTrue(metricEvidence.getOtelContext().contains("部分支持"));
+        assertTrue(metricEvidence.getOtelContext().contains("partial 
support"));
         assertTrue(metricEvidence.getOtelContext().contains("Summary 
quantiles"));
     }
 


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

Reply via email to