1use std::collections::BTreeMap;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum DiagnosticTextKey {
11 ProtoJsonQotMarketNumeric,
12 QuotePermissionOpenMarket,
13 StaticCacheMissing,
14 OrderbookDetailPermission,
15 JpStockQuotePermission,
16 JpTradePermission,
17 AllDayRiskDisclosure,
18 UpdateCheckUnavailable,
19 Qot3401UnsupportedMarket,
20 Qot3401BackendError,
21 Qot3401EntityIdRequired,
22 Qot3401DiscoveryEmpty,
23 Qot3401SampleUnavailable,
24 QotInvalidCount,
25 QotUnsupportedMarket,
26 QotEventContractRequired,
27 QotMissingParameter,
28 QotInvalidParameter,
29 QotOptionInconsistentMarket,
30 QotOptionDuplicateLeg,
31 QotOptionLegRequired,
32 QotOptionLegLimit,
33 QotUnsupportedSecurityType,
34 QotCapitalUnsupportedSecurityType,
35 QotUsIndexUnsupported,
36 QotInsiderUnsupportedSecurityType,
37}
38
39impl DiagnosticTextKey {
40 pub const fn code(self) -> &'static str {
41 match self {
42 Self::ProtoJsonQotMarketNumeric => "proto_json.qot_market_numeric",
43 Self::QuotePermissionOpenMarket => "quote.permission.open_market",
44 Self::StaticCacheMissing => "static.cache_missing",
45 Self::OrderbookDetailPermission => "quote.permission.orderbook_detail",
46 Self::JpStockQuotePermission => "quote.permission.jp_stock",
47 Self::JpTradePermission => "trade.permission.jp_market",
48 Self::AllDayRiskDisclosure => "trade.risk_disclosure.all_day",
49 Self::UpdateCheckUnavailable => "update.check_unavailable",
50 Self::Qot3401UnsupportedMarket => "qot3401.unsupported_market",
51 Self::Qot3401BackendError => "qot3401.backend_error",
52 Self::Qot3401EntityIdRequired => "qot3401.entity_id_required",
53 Self::Qot3401DiscoveryEmpty => "qot3401.discovery_empty",
54 Self::Qot3401SampleUnavailable => "qot3401.sample_unavailable",
55 Self::QotInvalidCount => "qot.invalid_count",
56 Self::QotUnsupportedMarket => "qot.unsupported_market",
57 Self::QotEventContractRequired => "qot.event_contract_required",
58 Self::QotMissingParameter => "qot.missing_parameter",
59 Self::QotInvalidParameter => "qot.invalid_parameter",
60 Self::QotOptionInconsistentMarket => "qot.option_inconsistent_market",
61 Self::QotOptionDuplicateLeg => "qot.option_duplicate_leg",
62 Self::QotOptionLegRequired => "qot.option_leg_required",
63 Self::QotOptionLegLimit => "qot.option_leg_limit",
64 Self::QotUnsupportedSecurityType => "qot.unsupported_security_type",
65 Self::QotCapitalUnsupportedSecurityType => "qot.capital_unsupported_security_type",
66 Self::QotUsIndexUnsupported => "qot.us_index_unsupported",
67 Self::QotInsiderUnsupportedSecurityType => "qot.insider_unsupported_security_type",
68 }
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
73pub enum MessageLocale {
74 En,
75 ZhCn,
76 ZhHk,
77}
78
79impl MessageLocale {
80 pub fn from_normalized_lang(lang: &str) -> Self {
81 match lang {
82 "chs" | "zh_CN" | "zh-cn" | "zh-Hans" | "zh_Hans" => Self::ZhCn,
83 "cht" | "zh_HK" | "zh_TW" | "zh-hk" | "zh-tw" | "zh-Hant" | "zh_Hant" => Self::ZhHk,
84 _ => Self::En,
85 }
86 }
87
88 pub fn from_app_lang(app_lang: i32) -> Self {
89 match app_lang {
90 0 => Self::ZhCn,
91 1 => Self::ZhHk,
92 2 => Self::En,
93 _ => Self::En,
94 }
95 }
96}
97
98#[derive(Debug, Clone, Default, PartialEq, Eq)]
99pub struct MessageArgs {
100 values: BTreeMap<&'static str, String>,
101}
102
103impl MessageArgs {
104 pub fn new() -> Self {
105 Self::default()
106 }
107
108 pub fn with(mut self, key: &'static str, value: impl Into<String>) -> Self {
109 self.values.insert(key, value.into());
110 self
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub struct CatalogMessage {
116 pub key: DiagnosticTextKey,
117 pub locale: MessageLocale,
118 pub template: &'static str,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct DiagnosticText {
123 pub key: DiagnosticTextKey,
124 pub text: String,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct DiagnosticTextSpec {
129 key: DiagnosticTextKey,
130 args: MessageArgs,
131}
132
133impl DiagnosticTextSpec {
134 pub fn new(key: DiagnosticTextKey, args: MessageArgs) -> Self {
135 Self { key, args }
136 }
137
138 pub const fn key(&self) -> DiagnosticTextKey {
139 self.key
140 }
141
142 pub fn render_with_app_lang(&self, app_lang: i32) -> DiagnosticText {
143 render_with_app_lang(self.key, app_lang, self.args.clone())
144 }
145}
146
147impl DiagnosticText {
148 pub fn new(key: DiagnosticTextKey, text: impl Into<String>) -> Self {
149 Self {
150 key,
151 text: text.into(),
152 }
153 }
154
155 pub fn render(key: DiagnosticTextKey, locale: MessageLocale, args: MessageArgs) -> Self {
156 let row = catalog_message(key, locale);
157 Self::new(key, render_template(row.template, &args))
158 }
159
160 pub fn text_with_code(&self) -> String {
161 format!("{} [diag:{}]", self.text, self.key.code())
162 }
163}
164
165pub fn render_with_lang(
166 key: DiagnosticTextKey,
167 normalized_lang: &str,
168 args: MessageArgs,
169) -> DiagnosticText {
170 DiagnosticText::render(
171 key,
172 MessageLocale::from_normalized_lang(normalized_lang),
173 args,
174 )
175}
176
177pub fn render_with_app_lang(
178 key: DiagnosticTextKey,
179 app_lang: i32,
180 args: MessageArgs,
181) -> DiagnosticText {
182 DiagnosticText::render(key, MessageLocale::from_app_lang(app_lang), args)
183}
184
185pub fn proto_json_numeric_qot_market_hint() -> DiagnosticText {
186 DiagnosticText::render(
187 DiagnosticTextKey::ProtoJsonQotMarketNumeric,
188 MessageLocale::En,
189 MessageArgs::new(),
190 )
191}
192
193pub fn quote_permission_action_for_market(
194 market_suffix: Option<&str>,
195 symbol: &str,
196) -> DiagnosticText {
197 let market = market_suffix.unwrap_or("market");
198 let market_label = match market_suffix {
199 Some("JP") => "JP stock",
200 Some("US") => "US stock",
201 Some("HK") => "HK stock",
202 Some(market) => market,
203 None => "the market",
204 };
205 DiagnosticText::render(
206 DiagnosticTextKey::QuotePermissionOpenMarket,
207 MessageLocale::En,
208 MessageArgs::new()
209 .with("market", market)
210 .with("market_label", market_label)
211 .with("symbol", symbol),
212 )
213}
214
215pub fn static_cache_missing_message(normalized_symbol: &str, sec_key: &str) -> DiagnosticText {
216 DiagnosticText::render(
217 DiagnosticTextKey::StaticCacheMissing,
218 MessageLocale::En,
219 MessageArgs::new()
220 .with("symbol", normalized_symbol)
221 .with("sec_key", sec_key),
222 )
223}
224
225pub fn orderbook_detail_permission_action() -> DiagnosticText {
226 DiagnosticText::render(
227 DiagnosticTextKey::OrderbookDetailPermission,
228 MessageLocale::En,
229 MessageArgs::new(),
230 )
231}
232
233pub fn jp_stock_quote_permission_hint() -> DiagnosticText {
234 DiagnosticText::render(
235 DiagnosticTextKey::JpStockQuotePermission,
236 MessageLocale::ZhCn,
237 MessageArgs::new(),
238 )
239}
240
241pub fn jp_trade_permission_hint() -> DiagnosticText {
242 DiagnosticText::render(
243 DiagnosticTextKey::JpTradePermission,
244 MessageLocale::ZhCn,
245 MessageArgs::new(),
246 )
247}
248
249pub fn all_day_risk_disclosure_hint(help_url: &str) -> DiagnosticText {
250 DiagnosticText::render(
251 DiagnosticTextKey::AllDayRiskDisclosure,
252 MessageLocale::ZhCn,
253 MessageArgs::new().with("help_url", help_url),
254 )
255}
256
257pub fn update_check_unavailable_action() -> DiagnosticText {
258 DiagnosticText::render(
259 DiagnosticTextKey::UpdateCheckUnavailable,
260 MessageLocale::En,
261 MessageArgs::new(),
262 )
263}
264
265pub fn qot3401_unsupported_market_action() -> DiagnosticText {
266 DiagnosticText::render(
267 DiagnosticTextKey::Qot3401UnsupportedMarket,
268 MessageLocale::En,
269 MessageArgs::new(),
270 )
271}
272
273pub fn qot3401_backend_error_action(api_name: &str, ret_code: i32) -> DiagnosticText {
274 DiagnosticText::render(
275 DiagnosticTextKey::Qot3401BackendError,
276 MessageLocale::En,
277 MessageArgs::new()
278 .with("api_name", api_name)
279 .with("ret_code", ret_code.to_string()),
280 )
281}
282
283pub fn qot3401_entity_id_required_action() -> DiagnosticText {
284 DiagnosticText::render(
285 DiagnosticTextKey::Qot3401EntityIdRequired,
286 MessageLocale::En,
287 MessageArgs::new(),
288 )
289}
290
291pub fn qot3401_discovery_empty_action() -> DiagnosticText {
292 DiagnosticText::render(
293 DiagnosticTextKey::Qot3401DiscoveryEmpty,
294 MessageLocale::En,
295 MessageArgs::new(),
296 )
297}
298
299pub fn qot3401_sample_unavailable_action() -> DiagnosticText {
300 DiagnosticText::render(
301 DiagnosticTextKey::Qot3401SampleUnavailable,
302 MessageLocale::En,
303 MessageArgs::new(),
304 )
305}
306
307fn catalog_message(key: DiagnosticTextKey, locale: MessageLocale) -> CatalogMessage {
308 let template = match (key, locale) {
309 (DiagnosticTextKey::ProtoJsonQotMarketNumeric, MessageLocale::ZhCn) => {
310 "`market` 是生成的 proto 数字枚举,不是字符串。请使用 Qot_Common.QotMarket 数值,例如 HK=1, US=11, SH=21, SZ=22, SG=31, JP=41, AU=51, MY=61。示例:{\"market\":11}"
311 }
312 (DiagnosticTextKey::ProtoJsonQotMarketNumeric, MessageLocale::ZhHk) => {
313 "`market` 是生成的 proto 數字枚舉,不是字串。請使用 Qot_Common.QotMarket 數值,例如 HK=1, US=11, SH=21, SZ=22, SG=31, JP=41, AU=51, MY=61。示例:{\"market\":11}"
314 }
315 (DiagnosticTextKey::ProtoJsonQotMarketNumeric, MessageLocale::En) => {
316 "`market` is a numeric generated proto enum, not a string. Use Qot_Common.QotMarket values such as HK=1, US=11, SH=21, SZ=22, SG=31, JP=41, AU=51, MY=61. Example: {\"market\":11}"
317 }
318
319 (DiagnosticTextKey::QuotePermissionOpenMarket, MessageLocale::ZhCn) => {
320 "先运行 futucli quote-rights;请在官方 Futu/moomoo App 开通{market_label}行情权限,然后用 futucli quote-rights 和 futucli quote-capability {symbol} 复查。"
321 }
322 (DiagnosticTextKey::QuotePermissionOpenMarket, MessageLocale::ZhHk) => {
323 "先執行 futucli quote-rights;請在官方 Futu/moomoo App 開通{market_label}行情權限,然後用 futucli quote-rights 和 futucli quote-capability {symbol} 複查。"
324 }
325 (DiagnosticTextKey::QuotePermissionOpenMarket, MessageLocale::En) => {
326 "run futucli quote-rights; open {market_label} quote permission in the official Futu/moomoo App if it remains zero, then recheck with futucli quote-rights and futucli quote-capability {symbol}"
327 }
328
329 (DiagnosticTextKey::StaticCacheMissing, MessageLocale::ZhCn) => {
330 "静态缓存缺少 {symbol} ({sec_key});请等待 stock-list sync 收敛,或先调用 static/static-warmup。"
331 }
332 (DiagnosticTextKey::StaticCacheMissing, MessageLocale::ZhHk) => {
333 "靜態快取缺少 {symbol} ({sec_key});請等待 stock-list sync 收斂,或先呼叫 static/static-warmup。"
334 }
335 (DiagnosticTextKey::StaticCacheMissing, MessageLocale::En) => {
336 "static cache missing {symbol} ({sec_key}); wait stock-list sync or call static/static-warmup first"
337 }
338
339 (DiagnosticTextKey::OrderbookDetailPermission, MessageLocale::ZhCn) => {
340 "请检查 quote-rights,并确认订阅账户具备所需 orderbook detail/depth 行情权限。"
341 }
342 (DiagnosticTextKey::OrderbookDetailPermission, MessageLocale::ZhHk) => {
343 "請檢查 quote-rights,並確認訂閱帳戶具備所需 orderbook detail/depth 行情權限。"
344 }
345 (DiagnosticTextKey::OrderbookDetailPermission, MessageLocale::En) => {
346 "check quote-rights and subscribe with the required orderbook detail permission"
347 }
348
349 (DiagnosticTextKey::JpStockQuotePermission, MessageLocale::ZhCn) => {
350 "App 延时行情不等于 OpenD API 实时行情权限;请在官方 Futu/moomoo App 开通日股实时行情 / Premium 权限,然后用 futucli quote-rights 确认 JP stock 权限。"
351 }
352 (DiagnosticTextKey::JpStockQuotePermission, MessageLocale::ZhHk) => {
353 "App 延時行情不等於 OpenD API 即時行情權限;請在官方 Futu/moomoo App 開通日股即時行情 / Premium 權限,然後用 futucli quote-rights 確認 JP stock 權限。"
354 }
355 (DiagnosticTextKey::JpStockQuotePermission, MessageLocale::En) => {
356 "App delayed JP quotes do not imply OpenD API realtime quote permission; open JP realtime / Premium quote permission in the official Futu/moomoo App, then confirm JP stock permission with futucli quote-rights."
357 }
358
359 (DiagnosticTextKey::JpTradePermission, MessageLocale::ZhCn) => {
360 "当前账户未开通日股交易权限(JP / trd_market=15)。请在 Futu/moomoo App 中开通日股交易权限,并按提示完成相关协议、风险披露、税务/市场权限流程。开通后重新查询账户,确认 trdmarket_auth 包含 JP/15,再重试下单。App 路径建议:账户 > 更多/交易权限 > 日股交易权限(以当前 App 实际入口为准)"
361 }
362 (DiagnosticTextKey::JpTradePermission, MessageLocale::ZhHk) => {
363 "當前帳戶未開通日股交易權限(JP / trd_market=15)。請在 Futu/moomoo App 中開通日股交易權限,並按提示完成相關協議、風險披露、稅務/市場權限流程。開通後重新查詢帳戶,確認 trdmarket_auth 包含 JP/15,再重試下單。App 路徑建議:帳戶 > 更多/交易權限 > 日股交易權限(以當前 App 實際入口為準)"
364 }
365 (DiagnosticTextKey::JpTradePermission, MessageLocale::En) => {
366 "The account does not have JP trading permission (JP / trd_market=15). Open JP trading permission in the Futu/moomoo App, finish the required agreements, risk disclosure, tax and market permission steps, then query accounts again and confirm trdmarket_auth contains JP/15 before retrying the order."
367 }
368
369 (DiagnosticTextKey::AllDayRiskDisclosure, MessageLocale::ZhCn) => {
370 "后端要求先完成全时段交易风险披露。请在官方 Futu/moomoo App 内完成全时段交易相关风险披露后重试;daemon 只透传下单协议,无法代用户确认该披露。官方帮助入口:{help_url}"
371 }
372 (DiagnosticTextKey::AllDayRiskDisclosure, MessageLocale::ZhHk) => {
373 "後端要求先完成全時段交易風險披露。請在官方 Futu/moomoo App 內完成全時段交易相關風險披露後重試;daemon 只透傳下單協議,無法代用戶確認該披露。官方幫助入口:{help_url}"
374 }
375 (DiagnosticTextKey::AllDayRiskDisclosure, MessageLocale::En) => {
376 "The backend requires all-day trading risk disclosure before this order. Complete the all-day trading risk disclosure in the official Futu/moomoo App, then retry. The daemon only forwards order protocols and cannot confirm this disclosure for the user. Help: {help_url}"
377 }
378
379 (DiagnosticTextKey::UpdateCheckUnavailable, MessageLocale::ZhCn) => {
380 "更新检查不可用;稍后重跑 `futucli version --check`,或用 `--url` / FUTU_UPDATE_CHECK_URL 指定 manifest,也可以用 `futucli doctor --no-update-check` 跳过 doctor 的更新检查。"
381 }
382 (DiagnosticTextKey::UpdateCheckUnavailable, MessageLocale::ZhHk) => {
383 "更新檢查不可用;稍後重跑 `futucli version --check`,或用 `--url` / FUTU_UPDATE_CHECK_URL 指定 manifest,也可以用 `futucli doctor --no-update-check` 跳過 doctor 的更新檢查。"
384 }
385 (DiagnosticTextKey::UpdateCheckUnavailable, MessageLocale::En) => {
386 "update check unavailable; rerun `futucli version --check` later, override with `--url` / FUTU_UPDATE_CHECK_URL, or skip doctor with `futucli doctor --no-update-check`"
387 }
388
389 (DiagnosticTextKey::Qot3401UnsupportedMarket, MessageLocale::ZhCn) => {
390 "该 3401+ 接口当前不支持传入的 market。请使用数字 Qot_Common.QotMarket,并先用包内 examples/qot-3401-plus-proto-json-examples.json 查看该接口已登记的市场样例。常用值:HK=1, US=11, SH=21, SZ=22, SG=31, JP=41, AU=51, MY=61, CA=71。"
391 }
392 (DiagnosticTextKey::Qot3401UnsupportedMarket, MessageLocale::ZhHk) => {
393 "該 3401+ 介面目前不支援傳入的 market。請使用數字 Qot_Common.QotMarket,並先用包內 examples/qot-3401-plus-proto-json-examples.json 查看該介面已登記的市場示例。常用值:HK=1, US=11, SH=21, SZ=22, SG=31, JP=41, AU=51, MY=61, CA=71。"
394 }
395 (DiagnosticTextKey::Qot3401UnsupportedMarket, MessageLocale::En) => {
396 "this 3401+ endpoint does not support the supplied market; use numeric Qot_Common.QotMarket values and check examples/qot-3401-plus-proto-json-examples.json for the endpoint sample. Common values: HK=1, US=11, SH=21, SZ=22, SG=31, JP=41, AU=51, MY=61, CA=71"
397 }
398
399 (DiagnosticTextKey::Qot3401BackendError, MessageLocale::ZhCn) => {
400 "{api_name} 后端返回 ret={ret_code}。请先运行对应发现入口并使用返回 ID;如果发现入口也返回空或 ret=-1,请把 market、C2S JSON、daemon 日志和 qot_3401_plus_discovery_chain_smoke.py 输出一起回传。"
401 }
402 (DiagnosticTextKey::Qot3401BackendError, MessageLocale::ZhHk) => {
403 "{api_name} 後端返回 ret={ret_code}。請先執行對應發現入口並使用返回 ID;如果發現入口也返回空或 ret=-1,請把 market、C2S JSON、daemon 日誌和 qot_3401_plus_discovery_chain_smoke.py 輸出一起回傳。"
404 }
405 (DiagnosticTextKey::Qot3401BackendError, MessageLocale::En) => {
406 "{api_name} backend returned ret={ret_code}; run the discovery list endpoint first and use the returned id. If discovery is empty or also returns ret=-1, attach market, C2S JSON, daemon logs, and qot_3401_plus_discovery_chain_smoke.py output to the bug report"
407 }
408
409 (DiagnosticTextKey::Qot3401EntityIdRequired, MessageLocale::ZhCn) => {
410 "该详情接口需要有效 ID。机构详情请先跑 institution-list 取 institution_id;产业链详情请先跑 industrial-chain-list 取 chain_id;板块详情请从 heat-map-data / plate-list / chain-detail 返回的 plate_id 继续查询。"
411 }
412 (DiagnosticTextKey::Qot3401EntityIdRequired, MessageLocale::ZhHk) => {
413 "該詳情介面需要有效 ID。機構詳情請先跑 institution-list 取 institution_id;產業鏈詳情請先跑 industrial-chain-list 取 chain_id;板塊詳情請從 heat-map-data / plate-list / chain-detail 返回的 plate_id 繼續查詢。"
414 }
415 (DiagnosticTextKey::Qot3401EntityIdRequired, MessageLocale::En) => {
416 "this detail endpoint needs a valid id. Run institution-list for institution_id, industrial-chain-list for chain_id, and heat-map-data / plate-list / chain-detail for plate_id before querying detail endpoints"
417 }
418
419 (DiagnosticTextKey::Qot3401DiscoveryEmpty, MessageLocale::ZhCn) => {
420 "发现入口没有返回可用 ID。请换 HK/US 等已登记市场样例重试;若仍为空,把请求 JSON 和 daemon 日志回传,先不要把详情接口当作可用路径。"
421 }
422 (DiagnosticTextKey::Qot3401DiscoveryEmpty, MessageLocale::ZhHk) => {
423 "發現入口沒有返回可用 ID。請換 HK/US 等已登記市場示例重試;若仍為空,把請求 JSON 和 daemon 日誌回傳,先不要把詳情介面當作可用路徑。"
424 }
425 (DiagnosticTextKey::Qot3401DiscoveryEmpty, MessageLocale::En) => {
426 "the discovery endpoint returned no usable id; retry with a registered HK/US sample and attach request JSON plus daemon logs before treating detail endpoints as usable"
427 }
428
429 (DiagnosticTextKey::Qot3401SampleUnavailable, MessageLocale::ZhCn) => {
430 "当前请求缺少已验证生产样例。请参考包内 examples/qot-3401-plus-proto-json-examples.json,用真实后端成功样例补齐后再对外宣称可用。"
431 }
432 (DiagnosticTextKey::Qot3401SampleUnavailable, MessageLocale::ZhHk) => {
433 "當前請求缺少已驗證生產示例。請參考包內 examples/qot-3401-plus-proto-json-examples.json,用真實後端成功示例補齊後再對外宣稱可用。"
434 }
435 (DiagnosticTextKey::Qot3401SampleUnavailable, MessageLocale::En) => {
436 "this request has no verified production sample yet; use examples/qot-3401-plus-proto-json-examples.json and add a real backend success sample before claiming the path is usable"
437 }
438
439 (DiagnosticTextKey::QotInvalidCount, MessageLocale::ZhCn) => {
440 "{endpoint} 请求数量必须在 {min} 到 {max} 之间。"
441 }
442 (DiagnosticTextKey::QotInvalidCount, MessageLocale::ZhHk) => {
443 "{endpoint} 請求數量必須在 {min} 到 {max} 之間。"
444 }
445 (DiagnosticTextKey::QotInvalidCount, MessageLocale::En) => {
446 "{endpoint} request count must be between {min} and {max}"
447 }
448
449 (DiagnosticTextKey::QotUnsupportedMarket, MessageLocale::ZhCn) => {
450 "{endpoint} 当前市场 {market} 不支持该接口。"
451 }
452 (DiagnosticTextKey::QotUnsupportedMarket, MessageLocale::ZhHk) => {
453 "{endpoint} 當前市場 {market} 不支援該介面。"
454 }
455 (DiagnosticTextKey::QotUnsupportedMarket, MessageLocale::En) => {
456 "{endpoint} market {market} is not supported"
457 }
458
459 (DiagnosticTextKey::QotEventContractRequired, MessageLocale::ZhCn) => {
460 "{endpoint} 仅支持事件合约,且需要相应行情权限:{market}.{code}。"
461 }
462 (DiagnosticTextKey::QotEventContractRequired, MessageLocale::ZhHk) => {
463 "{endpoint} 僅支援事件合約,且需要相應行情權限:{market}.{code}。"
464 }
465 (DiagnosticTextKey::QotEventContractRequired, MessageLocale::En) => {
466 "{endpoint} requires an event contract with quote permission: {market}.{code}"
467 }
468
469 (DiagnosticTextKey::QotMissingParameter, MessageLocale::ZhCn) => {
470 "{endpoint} 缺少必要参数 {param}。"
471 }
472 (DiagnosticTextKey::QotMissingParameter, MessageLocale::ZhHk) => {
473 "{endpoint} 缺少必要參數 {param}。"
474 }
475 (DiagnosticTextKey::QotMissingParameter, MessageLocale::En) => {
476 "{endpoint} missing required parameter {param}"
477 }
478
479 (DiagnosticTextKey::QotInvalidParameter, MessageLocale::ZhCn) => {
480 "{endpoint} 参数 {param} 的值 {value} 无效(索引 {index})。"
481 }
482 (DiagnosticTextKey::QotInvalidParameter, MessageLocale::ZhHk) => {
483 "{endpoint} 參數 {param} 的值 {value} 無效(索引 {index})。"
484 }
485 (DiagnosticTextKey::QotInvalidParameter, MessageLocale::En) => {
486 "{endpoint} parameter {param} has invalid value {value} at index {index}"
487 }
488
489 (DiagnosticTextKey::QotOptionInconsistentMarket, MessageLocale::ZhCn) => {
490 "{endpoint} 组合腿市场必须一致。"
491 }
492 (DiagnosticTextKey::QotOptionInconsistentMarket, MessageLocale::ZhHk) => {
493 "{endpoint} 組合腿市場必須一致。"
494 }
495 (DiagnosticTextKey::QotOptionInconsistentMarket, MessageLocale::En) => {
496 "{endpoint} combo leg markets must match"
497 }
498
499 (DiagnosticTextKey::QotOptionDuplicateLeg, MessageLocale::ZhCn) => {
500 "{endpoint} 组合腿不能重复。"
501 }
502 (DiagnosticTextKey::QotOptionDuplicateLeg, MessageLocale::ZhHk) => {
503 "{endpoint} 組合腿不能重複。"
504 }
505 (DiagnosticTextKey::QotOptionDuplicateLeg, MessageLocale::En) => {
506 "{endpoint} combo legs must not contain duplicates"
507 }
508
509 (DiagnosticTextKey::QotOptionLegRequired, MessageLocale::ZhCn) => {
510 "{endpoint} 至少需要一个期权腿。"
511 }
512 (DiagnosticTextKey::QotOptionLegRequired, MessageLocale::ZhHk) => {
513 "{endpoint} 至少需要一個期權腿。"
514 }
515 (DiagnosticTextKey::QotOptionLegRequired, MessageLocale::En) => {
516 "{endpoint} at least one option leg is required"
517 }
518
519 (DiagnosticTextKey::QotOptionLegLimit, MessageLocale::ZhCn) => {
520 "{endpoint} 期权腿数量不能超过 {max}。"
521 }
522 (DiagnosticTextKey::QotOptionLegLimit, MessageLocale::ZhHk) => {
523 "{endpoint} 期權腿數量不能超過 {max}。"
524 }
525 (DiagnosticTextKey::QotOptionLegLimit, MessageLocale::En) => {
526 "{endpoint} option leg count must not exceed {max}"
527 }
528
529 (DiagnosticTextKey::QotUnsupportedSecurityType, MessageLocale::ZhCn) => {
530 "{endpoint} 不支持第 {index} 个组合腿的证券类型 {secType}。"
531 }
532 (DiagnosticTextKey::QotUnsupportedSecurityType, MessageLocale::ZhHk) => {
533 "{endpoint} 不支援第 {index} 個組合腿的證券類型 {secType}。"
534 }
535 (DiagnosticTextKey::QotUnsupportedSecurityType, MessageLocale::En) => {
536 "{endpoint} security type {secType} for combo leg {index} is not supported"
537 }
538
539 (DiagnosticTextKey::QotCapitalUnsupportedSecurityType, MessageLocale::ZhCn) => {
540 "{endpoint} 仅支持正股、信托、窝轮和数字货币。"
541 }
542 (DiagnosticTextKey::QotCapitalUnsupportedSecurityType, MessageLocale::ZhHk) => {
543 "{endpoint} 僅支援正股、信託、窩輪和數字貨幣。"
544 }
545 (DiagnosticTextKey::QotCapitalUnsupportedSecurityType, MessageLocale::En) => {
546 "{endpoint} supports only equity, trust, warrant, and crypto securities"
547 }
548
549 (DiagnosticTextKey::QotUsIndexUnsupported, MessageLocale::ZhCn) => {
550 "{endpoint} 暂不支持美股指数 {code}。"
551 }
552 (DiagnosticTextKey::QotUsIndexUnsupported, MessageLocale::ZhHk) => {
553 "{endpoint} 暫不支援美股指數 {code}。"
554 }
555 (DiagnosticTextKey::QotUsIndexUnsupported, MessageLocale::En) => {
556 "{endpoint} does not support US index {code}"
557 }
558
559 (DiagnosticTextKey::QotInsiderUnsupportedSecurityType, MessageLocale::ZhCn) => {
560 "{endpoint} 仅支持美股或新加坡市场的正股和信托;证券类型 {secType} 不受支持。"
561 }
562 (DiagnosticTextKey::QotInsiderUnsupportedSecurityType, MessageLocale::ZhHk) => {
563 "{endpoint} 僅支援美股或新加坡市場的正股和信託;證券類型 {secType} 不受支援。"
564 }
565 (DiagnosticTextKey::QotInsiderUnsupportedSecurityType, MessageLocale::En) => {
566 "{endpoint} supports only US or SG equities and trusts; security type {secType} is unsupported"
567 }
568 };
569 CatalogMessage {
570 key,
571 locale,
572 template,
573 }
574}
575
576fn render_template(template: &str, args: &MessageArgs) -> String {
577 let mut rendered = template.to_string();
578 for (key, value) in &args.values {
579 rendered = rendered.replace(&format!("{{{key}}}"), value);
580 rendered = rendered.replace(&format!("{{{{{key}}}}}"), value);
581 }
582 rendered
583}