diff --git a/apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf b/apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf
deleted file mode 100644
index 2a588b752..000000000
--- a/apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf
+++ /dev/null
@@ -1,12 +0,0 @@
-emqx_dashboard_schema {
- protocol {
- desc {
- en: "Protocol Name"
- zh: "协议名"
- }
- label: {
- en: "Protocol"
- zh: "协议"
- }
- }
-}
diff --git a/apps/emqx_dashboard/i18n/emqx_dashboard_schema.conf b/apps/emqx_dashboard/i18n/emqx_dashboard_schema.conf
new file mode 100644
index 000000000..d7b2055bb
--- /dev/null
+++ b/apps/emqx_dashboard/i18n/emqx_dashboard_schema.conf
@@ -0,0 +1,190 @@
+emqx_dashboard_schema {
+ listeners {
+ desc {
+ en: """HTTP(s) listeners are identified by their protocol type and are
+used to serve dashboard UI and restful HTTP API.
+Listeners must have a unique combination of port number and IP address.
+For example, an HTTP listener can listen on all configured IP addresses
+on a given port for a machine by specifying the IP address 0.0.0.0.
+Alternatively, the HTTP listener can specify a unique IP address for each listener,
+but use the same port."""
+ zh: """仪表盘监听器设置。"""
+ }
+ label {
+ en: "Listeners"
+ zh: "监听器"
+ }
+ }
+ sample_interval {
+ desc {
+ en: """How often to update metrics displayed in the dashboard.
"
+Note: `sample_interval` should be a divisor of 60."""
+ zh: """更新仪表板中显示的指标的时间间隔。"""
+ }
+ }
+ token_expired_time {
+ desc {
+ en: "JWT token expiration time."
+ zh: "JWT token 过期时间"
+ }
+ label {
+ en: "Token expired time"
+ zh: "JWT 过期时间"
+ }
+ }
+ num_acceptors {
+ desc {
+ en: "Socket acceptor pool size for TCP protocols."
+ zh: "TCP协议的Socket acceptor池大小"
+ }
+ label {
+ en: "Number of acceptors"
+ zh: "Acceptor 数量"
+ }
+ }
+ max_connections {
+ desc {
+ en: "Maximum number of simultaneous connections."
+ zh: "同时处理的最大连接数"
+ }
+ label {
+ en: "Maximum connections"
+ zh: "最大连接数"
+ }
+ }
+ backlog {
+ desc {
+ en: "Defines the maximum length that the queue of pending connections can grow to."
+ zh: "排队等待连接的队列的最大长度"
+ }
+ label {
+ en: "Backlog"
+ zh: "排队长度"
+ }
+ }
+ send_timeout {
+ desc {
+ en: "Send timeout for the socket."
+ zh: "Socket发送超时时间"
+ }
+ label {
+ en: "Send timeout"
+ zh: "发送超时时间"
+ }
+ }
+ inet6 {
+ desc {
+ en: "Enable IPv6 support."
+ zh: "启用IPv6"
+ }
+ label {
+ en: "IPv6"
+ zh: "IPv6"
+ }
+ }
+ ipv6_v6only {
+ desc {
+ en: "Disable IPv4-to-IPv6 mapping for the listener."
+ zh: "禁用IPv4-to-IPv6映射"
+ }
+ label {
+ en: "IPv6 only"
+ zh: "IPv6 only"
+ }
+ }
+ desc_dashboard {
+ desc {
+ en: "Configuration for EMQX dashboard."
+ zh: "EMQX仪表板配置"
+ }
+ label {
+ en: "Dashboard"
+ zh: "仪表板"
+ }
+ }
+ desc_listeners {
+ desc {
+ en: "Configuration for the dashboard listener."
+ zh: "仪表板监听器配置"
+ }
+ label {
+ en: "Listeners"
+ zh: "监听器"
+ }
+ }
+ desc_http {
+ desc {
+ en: "Configuration for the dashboard listener (plaintext)."
+ zh: "仪表板监听器(HTTP)配置"
+ }
+ label {
+ en: "HTTP"
+ zh: "HTTP"
+ }
+ }
+ desc_https {
+ desc {
+ en: "Configuration for the dashboard listener (TLS)."
+ zh: "仪表板监听器(HTTPS)配置"
+ }
+ label {
+ en: "HTTPS"
+ zh: "HTTPS"
+ }
+ }
+ bind {
+ desc {
+ en: "Port without IP(18083) or port with specified IP(127.0.0.1:18083)."
+ zh: "监听的地址与端口"
+ }
+ label {
+ en: "Bind"
+ zh: "绑定端口"
+ }
+ }
+ default_username {
+ desc {
+ en: "The default username of the automatically created dashboard user."
+ zh: "默认的仪表板用户名"
+ }
+ label {
+ en: "Default username"
+ zh: "默认用户名"
+ }
+ }
+ default_password {
+ desc {
+ en: """The initial default password for dashboard 'admin' user.
"
+For safety, it should be changed as soon as possible."""
+ zh: """默认的仪表板用户密码
+为了安全,应该尽快修改密码。"""
+ }
+ label {
+ en: "Default password"
+ zh: "默认密码"
+ }
+ }
+ cors {
+ desc {
+ en: """Support Cross-Origin Resource Sharing (CORS).
+Allows a server to indicate any origins (domain, scheme, or port) other than
+允许服务器指示任何来源(域名、协议或端口),除了本服务器之外的任何浏览器应允许加载资源。"""
+ }
+ label {
+ en: "CORS"
+ zh: "跨域资源共享"
+ }
+ }
+ i18n_lang {
+ desc {
+ en: "Internationalization language support."
+ zh: "多语言支持"
+ }
+ label {
+ en: "I18n language"
+ zh: "多语言支持"
+ }
+ }
+}
diff --git a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl
index 6f187a694..4fb7b2abc 100644
--- a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl
+++ b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl
@@ -32,16 +32,7 @@ fields("dashboard") ->
{listeners,
sc(
ref("listeners"),
- #{
- desc =>
- "HTTP(s) listeners are identified by their protocol type and are\n"
- "used to serve dashboard UI and restful HTTP API.
\n"
- "Listeners must have a unique combination of port number and IP address.
\n"
- "For example, an HTTP listener can listen on all configured IP addresses\n"
- "on a given port for a machine by specifying the IP address 0.0.0.0.
\n"
- "Alternatively, the HTTP listener can specify a unique IP address for each listener,\n"
- "but use the same port."
- }
+ #{ desc => ?DESC(listeners)}
)},
{default_username, fun default_username/1},
{default_password, fun default_password/1},
@@ -50,9 +41,8 @@ fields("dashboard") ->
emqx_schema:duration_s(),
#{
default => "10s",
- desc =>
- "How often to update metrics displayed in the dashboard.
"
- "Note: `sample_interval` should be a divisor of 60."
+ desc => ?DESC(sample_interval),
+ validator => fun validate_sample_interval/1
}
)},
{token_expired_time,
@@ -60,7 +50,7 @@ fields("dashboard") ->
emqx_schema:duration(),
#{
default => "30m",
- desc => "JWT token expiration time."
+ desc => ?DESC(token_expired_time)
}
)},
{cors, fun cors/1},
@@ -93,7 +83,7 @@ fields("http") ->
integer(),
#{
default => 4,
- desc => "Socket acceptor pool size for TCP protocols."
+ desc => ?DESC(num_acceptors)
}
)},
{"max_connections",
@@ -101,7 +91,7 @@ fields("http") ->
integer(),
#{
default => 512,
- desc => "Maximum number of simultaneous connections."
+ desc => ?DESC(max_connections)
}
)},
{"backlog",
@@ -109,8 +99,7 @@ fields("http") ->
integer(),
#{
default => 1024,
- desc =>
- "Defines the maximum length that the queue of pending connections can grow to."
+ desc => ?DESC(backlog)
}
)},
{"send_timeout",
@@ -118,7 +107,7 @@ fields("http") ->
emqx_schema:duration(),
#{
default => "5s",
- desc => "Send timeout for the socket."
+ desc => ?DESC(send_timeout)
}
)},
{"inet6",
@@ -126,7 +115,7 @@ fields("http") ->
boolean(),
#{
default => false,
- desc => "Sets up the listener for IPv6."
+ desc => ?DESC(inet6)
}
)},
{"ipv6_v6only",
@@ -134,7 +123,7 @@ fields("http") ->
boolean(),
#{
default => false,
- desc => "Disable IPv4-to-IPv6 mapping for the listener."
+ desc => ?DESC(ipv6_v6only)
}
)}
];
@@ -145,27 +134,22 @@ fields("https") ->
emqx_schema:server_ssl_opts_schema(#{}, true)
).
-desc("dashboard") ->
- "Configuration for EMQX dashboard.";
-desc("listeners") ->
- "Configuration for the dashboard listener.";
-desc("http") ->
- "Configuration for the dashboard listener (plaintext).";
-desc("https") ->
- "Configuration for the dashboard listener (TLS).";
-desc(_) ->
- undefined.
+desc("dashboard") -> ?DESC(desc_dashboard);
+desc("listeners") -> ?DESC(desc_listeners);
+desc("http") -> ?DESC(desc_http);
+desc("https") -> ?DESC(desc_https);
+desc(_) -> undefined.
bind(type) -> hoconsc:union([non_neg_integer(), emqx_schema:ip_port()]);
bind(default) -> 18083;
bind(required) -> true;
-bind(desc) -> "Port without IP(18083) or port with specified IP(127.0.0.1:18083).";
+bind(desc) -> ?DESC(bind);
bind(_) -> undefined.
default_username(type) -> string();
default_username(default) -> "admin";
default_username(required) -> true;
-default_username(desc) -> "The default username of the automatically created dashboard user.";
+default_username(desc) -> ?DESC(default_username);
default_username('readOnly') -> true;
default_username(_) -> undefined.
@@ -180,11 +164,7 @@ default_password('readOnly') ->
default_password(sensitive) ->
true;
default_password(desc) ->
- ""
- "\n"
- "The initial default password for dashboard 'admin' user.\n"
- "For safety, it should be changed as soon as possible."
- "";
+ ?DESC(default_password);
default_password(_) ->
undefined.
@@ -195,18 +175,22 @@ cors(default) ->
cors(required) ->
false;
cors(desc) ->
- "Support Cross-Origin Resource Sharing (CORS).\n"
- "Allows a server to indicate any origins (domain, scheme, or port) other than\n"
- "its own from which a browser should permit loading resources.";
+ ?DESC(cors);
cors(_) ->
undefined.
i18n_lang(type) -> ?ENUM([en, zh]);
i18n_lang(default) -> en;
i18n_lang('readOnly') -> true;
-i18n_lang(desc) -> "Internationalization language support.";
+i18n_lang(desc) -> ?DESC(i18n_lang);
i18n_lang(_) -> undefined.
+validate_sample_interval(Second) ->
+ case Second >= 1 andalso Second =< 60 andalso (60 rem Second =:= 0) of
+ true -> ok;
+ false -> error({"Sample interval must be between 1 and 60 and be a divisor of 60.", Second})
+ end.
+
sc(Type, Meta) -> hoconsc:mk(Type, Meta).
ref(Field) -> hoconsc:ref(?MODULE, Field).
diff --git a/scripts/merge-i18n.escript b/scripts/merge-i18n.escript
index 13a7df27b..8a42cffe4 100755
--- a/scripts/merge-i18n.escript
+++ b/scripts/merge-i18n.escript
@@ -3,7 +3,7 @@
-mode(compile).
main(_) ->
- {ok, BaseConf} = file:read_file("apps/emqx_dashboard/i18n/emqx_dashboard_i18n.conf"),
+ {ok, BaseConf} = file:read_file("apps/emqx_dashboard/i18n/emqx_dashboard_schema.conf"),
Cfgs = get_all_cfgs("apps/"),
Conf = [merge(BaseConf, Cfgs),