fix: ParseError 原因结构化本地化,消除英文/繁体界面的中文错误透传(Codex review + 双审计)
This commit is contained in:
parent
2e4e5ce449
commit
039f27da18
@ -115,9 +115,9 @@ class CodexRepository(
|
|||||||
parseUsageWindows(usageResult)
|
parseUsageWindows(usageResult)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
DebugLog.e(TAG, "解析 Codex 用量失败: ${e::class.simpleName}: ${e.message}")
|
DebugLog.e(TAG, "解析 Codex 用量失败: ${e::class.simpleName}: ${e.message}")
|
||||||
return@withContext Result.failure(RepositoryError.ParseError("Codex 用量响应格式异常: ${e.message ?: e::class.simpleName}"))
|
return@withContext Result.failure(RepositoryError.ParseError(RepositoryError.ParseErrorReason.MALFORMED_RESPONSE, "Codex 用量响应格式异常: ${e.message ?: e::class.simpleName}"))
|
||||||
}
|
}
|
||||||
if (windows.isEmpty()) return@withContext Result.failure(RepositoryError.ParseError("未找到 Codex 用量窗口数据"))
|
if (windows.isEmpty()) return@withContext Result.failure(RepositoryError.ParseError(RepositoryError.ParseErrorReason.NO_WINDOWS, "未找到 Codex 用量窗口数据"))
|
||||||
|
|
||||||
val config = ServiceConfigProvider.get(ServiceType.CODEX)
|
val config = ServiceConfigProvider.get(ServiceType.CODEX)
|
||||||
val primary = windows.firstOrNull { it.label.contains("h") } ?: windows.first()
|
val primary = windows.firstOrNull { it.label.contains("h") } ?: windows.first()
|
||||||
@ -155,16 +155,16 @@ class CodexRepository(
|
|||||||
DebugLog.e(TAG, "fetchModels: HTTP ${resp.code}")
|
DebugLog.e(TAG, "fetchModels: HTTP ${resp.code}")
|
||||||
return@withContext Result.failure(RepositoryError.ServerError(resp.code))
|
return@withContext Result.failure(RepositoryError.ServerError(resp.code))
|
||||||
}
|
}
|
||||||
val root = json.parseToJsonElement(resp.body?.string() ?: throw RepositoryError.ParseError("响应体为空")) as? JsonObject
|
val root = json.parseToJsonElement(resp.body?.string() ?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.EMPTY_BODY, "响应体为空")) as? JsonObject
|
||||||
?: throw RepositoryError.ParseError("响应根节点不是 JSON 对象")
|
?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.NOT_JSON_OBJECT, "响应根节点不是 JSON 对象")
|
||||||
// 结构: { "openai": { "models": { "gpt-5.6": {...}, ... } } }
|
// 结构: { "openai": { "models": { "gpt-5.6": {...}, ... } } }
|
||||||
val openaiProvider = root["openai"] as? JsonObject
|
val openaiProvider = root["openai"] as? JsonObject
|
||||||
val modelsObj = openaiProvider?.get("models") as? JsonObject
|
val modelsObj = openaiProvider?.get("models") as? JsonObject
|
||||||
modelsObj?.keys?.toList()?.sorted()
|
modelsObj?.keys?.toList()?.sorted()
|
||||||
?: throw RepositoryError.ParseError("未找到 OpenAI 模型列表")
|
?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.NO_MODELS, "未找到 OpenAI 模型列表")
|
||||||
}
|
}
|
||||||
if (models.isEmpty()) {
|
if (models.isEmpty()) {
|
||||||
return@withContext Result.failure(RepositoryError.ParseError("模型列表为空"))
|
return@withContext Result.failure(RepositoryError.ParseError(RepositoryError.ParseErrorReason.MODELS_EMPTY, "模型列表为空"))
|
||||||
}
|
}
|
||||||
DebugLog.i(TAG, "fetchModels: 获取到 ${models.size} 个模型")
|
DebugLog.i(TAG, "fetchModels: 获取到 ${models.size} 个模型")
|
||||||
Result.success(models)
|
Result.success(models)
|
||||||
@ -386,8 +386,8 @@ class CodexRepository(
|
|||||||
DebugLog.e(TAG, "fetchJson failed: HTTP ${it.code} ${it.message} url=$url")
|
DebugLog.e(TAG, "fetchJson failed: HTTP ${it.code} ${it.message} url=$url")
|
||||||
throw if (it.code in listOf(401, 403)) RepositoryError.InvalidCredential("HTTP ${it.code}") else RepositoryError.ServerError(it.code)
|
throw if (it.code in listOf(401, 403)) RepositoryError.InvalidCredential("HTTP ${it.code}") else RepositoryError.ServerError(it.code)
|
||||||
}
|
}
|
||||||
return (json.parseToJsonElement(it.body?.string() ?: throw RepositoryError.ParseError("响应体为空")) as? JsonObject)
|
return (json.parseToJsonElement(it.body?.string() ?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.EMPTY_BODY, "响应体为空")) as? JsonObject)
|
||||||
?: throw RepositoryError.ParseError("响应根节点不是 JSON 对象")
|
?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.NOT_JSON_OBJECT, "响应根节点不是 JSON 对象")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -106,7 +106,7 @@ class CommandCodeUsageRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (body == null) return@withContext Result.failure(
|
if (body == null) return@withContext Result.failure(
|
||||||
RepositoryError.ParseError("响应体为空")
|
RepositoryError.ParseError(RepositoryError.ParseErrorReason.EMPTY_BODY, "响应体为空")
|
||||||
)
|
)
|
||||||
|
|
||||||
val records = parseUsageResponse(body)
|
val records = parseUsageResponse(body)
|
||||||
|
|||||||
@ -178,13 +178,14 @@ class OllamaRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val html = resp.body?.string() ?: return@withContext Result.failure(
|
val html = resp.body?.string() ?: return@withContext Result.failure(
|
||||||
RepositoryError.ParseError("响应体为空")
|
RepositoryError.ParseError(RepositoryError.ParseErrorReason.EMPTY_BODY, "响应体为空")
|
||||||
)
|
)
|
||||||
|
|
||||||
val parsed = parseUsage(html)
|
val parsed = parseUsage(html)
|
||||||
if (parsed == null) {
|
if (parsed == null) {
|
||||||
return@withContext Result.failure(
|
return@withContext Result.failure(
|
||||||
RepositoryError.ParseError(
|
RepositoryError.ParseError(
|
||||||
|
RepositoryError.ParseErrorReason.NO_WINDOWS,
|
||||||
"解析失败:未找到 Ollama Cloud 用量数据。HTML=${html.length} 字节。Cookie 可能已过期。"
|
"解析失败:未找到 Ollama Cloud 用量数据。HTML=${html.length} 字节。Cookie 可能已过期。"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -241,15 +242,15 @@ class OllamaRepository(
|
|||||||
DebugLog.e(TAG, "fetchModels: HTTP ${resp.code}")
|
DebugLog.e(TAG, "fetchModels: HTTP ${resp.code}")
|
||||||
return@withContext Result.failure(RepositoryError.ServerError(resp.code))
|
return@withContext Result.failure(RepositoryError.ServerError(resp.code))
|
||||||
}
|
}
|
||||||
val root = json.parseToJsonElement(resp.body?.string() ?: throw RepositoryError.ParseError("响应体为空")) as? JsonObject
|
val root = json.parseToJsonElement(resp.body?.string() ?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.EMPTY_BODY, "响应体为空")) as? JsonObject
|
||||||
?: throw RepositoryError.ParseError("响应根节点不是 JSON 对象")
|
?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.NOT_JSON_OBJECT, "响应根节点不是 JSON 对象")
|
||||||
val provider = root["ollama-cloud"] as? JsonObject
|
val provider = root["ollama-cloud"] as? JsonObject
|
||||||
val modelsObj = provider?.get("models") as? JsonObject
|
val modelsObj = provider?.get("models") as? JsonObject
|
||||||
modelsObj?.keys?.toList()?.sorted()
|
modelsObj?.keys?.toList()?.sorted()
|
||||||
?: throw RepositoryError.ParseError("未找到 Ollama Cloud 模型列表")
|
?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.NO_MODELS, "未找到 Ollama Cloud 模型列表")
|
||||||
}
|
}
|
||||||
if (models.isEmpty()) {
|
if (models.isEmpty()) {
|
||||||
return@withContext Result.failure(RepositoryError.ParseError("模型列表为空"))
|
return@withContext Result.failure(RepositoryError.ParseError(RepositoryError.ParseErrorReason.MODELS_EMPTY, "模型列表为空"))
|
||||||
}
|
}
|
||||||
DebugLog.i(TAG, "fetchModels: 获取到 ${models.size} 个模型")
|
DebugLog.i(TAG, "fetchModels: 获取到 ${models.size} 个模型")
|
||||||
Result.success(models)
|
Result.success(models)
|
||||||
|
|||||||
@ -86,13 +86,14 @@ class OpenCodeGoRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val html = resp.body?.string() ?: return@withContext Result.failure(
|
val html = resp.body?.string() ?: return@withContext Result.failure(
|
||||||
RepositoryError.ParseError("响应体为空")
|
RepositoryError.ParseError(RepositoryError.ParseErrorReason.EMPTY_BODY, "响应体为空")
|
||||||
)
|
)
|
||||||
|
|
||||||
val windows = parseWindows(html)
|
val windows = parseWindows(html)
|
||||||
if (windows.isEmpty()) {
|
if (windows.isEmpty()) {
|
||||||
return@withContext Result.failure(
|
return@withContext Result.failure(
|
||||||
RepositoryError.ParseError(
|
RepositoryError.ParseError(
|
||||||
|
RepositoryError.ParseErrorReason.NO_WINDOWS,
|
||||||
"解析失败:未找到任何 OpenCode Go 配额窗口。HTML=${html.length} 字节。"
|
"解析失败:未找到任何 OpenCode Go 配额窗口。HTML=${html.length} 字节。"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -151,15 +152,15 @@ class OpenCodeGoRepository(
|
|||||||
DebugLog.e(TAG, "fetchModels: HTTP ${resp.code}")
|
DebugLog.e(TAG, "fetchModels: HTTP ${resp.code}")
|
||||||
return@withContext Result.failure(RepositoryError.ServerError(resp.code))
|
return@withContext Result.failure(RepositoryError.ServerError(resp.code))
|
||||||
}
|
}
|
||||||
val root = json.parseToJsonElement(resp.body?.string() ?: throw RepositoryError.ParseError("响应体为空")) as? JsonObject
|
val root = json.parseToJsonElement(resp.body?.string() ?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.EMPTY_BODY, "响应体为空")) as? JsonObject
|
||||||
?: throw RepositoryError.ParseError("响应根节点不是 JSON 对象")
|
?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.NOT_JSON_OBJECT, "响应根节点不是 JSON 对象")
|
||||||
val provider = root["opencode-go"] as? JsonObject
|
val provider = root["opencode-go"] as? JsonObject
|
||||||
val modelsObj = provider?.get("models") as? JsonObject
|
val modelsObj = provider?.get("models") as? JsonObject
|
||||||
modelsObj?.keys?.toList()?.sorted()
|
modelsObj?.keys?.toList()?.sorted()
|
||||||
?: throw RepositoryError.ParseError("未找到 OpenCode Go 模型列表")
|
?: throw RepositoryError.ParseError(RepositoryError.ParseErrorReason.NO_MODELS, "未找到 OpenCode Go 模型列表")
|
||||||
}
|
}
|
||||||
if (models.isEmpty()) {
|
if (models.isEmpty()) {
|
||||||
return@withContext Result.failure(RepositoryError.ParseError("模型列表为空"))
|
return@withContext Result.failure(RepositoryError.ParseError(RepositoryError.ParseErrorReason.MODELS_EMPTY, "模型列表为空"))
|
||||||
}
|
}
|
||||||
DebugLog.i(TAG, "fetchModels: 获取到 ${models.size} 个模型")
|
DebugLog.i(TAG, "fetchModels: 获取到 ${models.size} 个模型")
|
||||||
Result.success(models)
|
Result.success(models)
|
||||||
|
|||||||
@ -88,7 +88,7 @@ class OpenCodeUsageRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val body = resp.body?.string() ?: return@withContext Result.failure(
|
val body = resp.body?.string() ?: return@withContext Result.failure(
|
||||||
RepositoryError.ParseError("响应体为空")
|
RepositoryError.ParseError(RepositoryError.ParseErrorReason.EMPTY_BODY, "响应体为空")
|
||||||
)
|
)
|
||||||
|
|
||||||
val records = parseUsageRecords(body)
|
val records = parseUsageRecords(body)
|
||||||
|
|||||||
@ -23,8 +23,24 @@ sealed class RepositoryError(message: String, cause: Throwable? = null) : Except
|
|||||||
class ServerError(val code: Int, cause: Throwable? = null) :
|
class ServerError(val code: Int, cause: Throwable? = null) :
|
||||||
RepositoryError("服务端异常 (HTTP $code)", cause)
|
RepositoryError("服务端异常 (HTTP $code)", cause)
|
||||||
|
|
||||||
/** 解析失败(HTML 解析结果为空 / JSON 反序列化失败),detail 为具体原因 */
|
/** 解析失败的具体原因(结构化错误码,UI 层据此映射本地化文案,detail 仅作日志/调试) */
|
||||||
class ParseError(val detail: String, cause: Throwable? = null) :
|
enum class ParseErrorReason {
|
||||||
|
/** 响应体为空 */
|
||||||
|
EMPTY_BODY,
|
||||||
|
/** 响应根节点不是 JSON 对象 */
|
||||||
|
NOT_JSON_OBJECT,
|
||||||
|
/** 未找到用量窗口数据 */
|
||||||
|
NO_WINDOWS,
|
||||||
|
/** 未找到模型列表 */
|
||||||
|
NO_MODELS,
|
||||||
|
/** 模型列表为空 */
|
||||||
|
MODELS_EMPTY,
|
||||||
|
/** 响应格式异常(detail 含异常信息,仅供日志) */
|
||||||
|
MALFORMED_RESPONSE
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析失败(HTML 解析结果为空 / JSON 反序列化失败),detail 为具体原因(日志用,不直接透传 UI) */
|
||||||
|
class ParseError(val reason: ParseErrorReason, val detail: String, cause: Throwable? = null) :
|
||||||
RepositoryError("解析失败: $detail", cause)
|
RepositoryError("解析失败: $detail", cause)
|
||||||
|
|
||||||
/** 未知错误 */
|
/** 未知错误 */
|
||||||
|
|||||||
@ -189,7 +189,7 @@ data class DashboardCardUi(
|
|||||||
val displayBalance: ServiceBalance? get() = cachedBalance?.balance
|
val displayBalance: ServiceBalance? get() = cachedBalance?.balance
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 把 Repository 错误映射为本地化文案(错误详情 detail 为技术信息,随资源参数展示)。 */
|
/** 把 Repository 错误映射为本地化文案(ParseError 按 reason 映射资源,detail 仅作日志,不流向 UI)。 */
|
||||||
private fun errorToUiText(error: Throwable): UiText = when (error) {
|
private fun errorToUiText(error: Throwable): UiText = when (error) {
|
||||||
is RepositoryError.InvalidCredential ->
|
is RepositoryError.InvalidCredential ->
|
||||||
UiText.Resource(R.string.error_credential_invalid_reconfigure)
|
UiText.Resource(R.string.error_credential_invalid_reconfigure)
|
||||||
@ -204,8 +204,14 @@ private fun errorToUiText(error: Throwable): UiText = when (error) {
|
|||||||
is RepositoryError.Network -> UiText.Resource(R.string.error_network_check)
|
is RepositoryError.Network -> UiText.Resource(R.string.error_network_check)
|
||||||
is RepositoryError.ServerError ->
|
is RepositoryError.ServerError ->
|
||||||
UiText.Resource(R.string.error_server_http, listOf(error.code))
|
UiText.Resource(R.string.error_server_http, listOf(error.code))
|
||||||
is RepositoryError.ParseError ->
|
is RepositoryError.ParseError -> when (error.reason) {
|
||||||
UiText.Resource(R.string.error_parse_failed, listOf(error.detail))
|
RepositoryError.ParseErrorReason.EMPTY_BODY -> UiText.Resource(R.string.error_parse_empty_body)
|
||||||
|
RepositoryError.ParseErrorReason.NOT_JSON_OBJECT -> UiText.Resource(R.string.error_parse_not_json)
|
||||||
|
RepositoryError.ParseErrorReason.NO_WINDOWS -> UiText.Resource(R.string.error_parse_no_windows)
|
||||||
|
RepositoryError.ParseErrorReason.NO_MODELS -> UiText.Resource(R.string.error_parse_no_models)
|
||||||
|
RepositoryError.ParseErrorReason.MODELS_EMPTY -> UiText.Resource(R.string.error_parse_models_empty)
|
||||||
|
RepositoryError.ParseErrorReason.MALFORMED_RESPONSE -> UiText.Resource(R.string.error_parse_malformed)
|
||||||
|
}
|
||||||
else -> error.message?.let { UiText.Dynamic(it) }
|
else -> error.message?.let { UiText.Dynamic(it) }
|
||||||
?: UiText.Resource(R.string.common_unknown)
|
?: UiText.Resource(R.string.common_unknown)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -536,8 +536,14 @@ class ServiceDetailViewModel @Inject constructor(
|
|||||||
is RepositoryError.Network -> UiText.Resource(R.string.error_network_check)
|
is RepositoryError.Network -> UiText.Resource(R.string.error_network_check)
|
||||||
is RepositoryError.ServerError ->
|
is RepositoryError.ServerError ->
|
||||||
UiText.Resource(R.string.error_server_http, listOf(error.code))
|
UiText.Resource(R.string.error_server_http, listOf(error.code))
|
||||||
is RepositoryError.ParseError ->
|
is RepositoryError.ParseError -> when (error.reason) {
|
||||||
UiText.Resource(R.string.error_parse_failed, listOf(error.detail))
|
RepositoryError.ParseErrorReason.EMPTY_BODY -> UiText.Resource(R.string.error_parse_empty_body)
|
||||||
|
RepositoryError.ParseErrorReason.NOT_JSON_OBJECT -> UiText.Resource(R.string.error_parse_not_json)
|
||||||
|
RepositoryError.ParseErrorReason.NO_WINDOWS -> UiText.Resource(R.string.error_parse_no_windows)
|
||||||
|
RepositoryError.ParseErrorReason.NO_MODELS -> UiText.Resource(R.string.error_parse_no_models)
|
||||||
|
RepositoryError.ParseErrorReason.MODELS_EMPTY -> UiText.Resource(R.string.error_parse_models_empty)
|
||||||
|
RepositoryError.ParseErrorReason.MALFORMED_RESPONSE -> UiText.Resource(R.string.error_parse_malformed)
|
||||||
|
}
|
||||||
else -> error.message?.let { UiText.Dynamic(it) }
|
else -> error.message?.let { UiText.Dynamic(it) }
|
||||||
?: UiText.Resource(R.string.common_unknown)
|
?: UiText.Resource(R.string.common_unknown)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -177,8 +177,14 @@ class CredentialEditViewModel @Inject constructor(
|
|||||||
?: UiText.Resource(R.string.common_unknown)
|
?: UiText.Resource(R.string.common_unknown)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
is RepositoryError.ParseError ->
|
is RepositoryError.ParseError -> when (err.reason) {
|
||||||
UiText.Resource(R.string.error_parse_failed, listOf(err.detail))
|
RepositoryError.ParseErrorReason.EMPTY_BODY -> UiText.Resource(R.string.error_parse_empty_body)
|
||||||
|
RepositoryError.ParseErrorReason.NOT_JSON_OBJECT -> UiText.Resource(R.string.error_parse_not_json)
|
||||||
|
RepositoryError.ParseErrorReason.NO_WINDOWS -> UiText.Resource(R.string.error_parse_no_windows)
|
||||||
|
RepositoryError.ParseErrorReason.NO_MODELS -> UiText.Resource(R.string.error_parse_no_models)
|
||||||
|
RepositoryError.ParseErrorReason.MODELS_EMPTY -> UiText.Resource(R.string.error_parse_models_empty)
|
||||||
|
RepositoryError.ParseErrorReason.MALFORMED_RESPONSE -> UiText.Resource(R.string.error_parse_malformed)
|
||||||
|
}
|
||||||
else -> err?.message?.let { UiText.Dynamic(it) }
|
else -> err?.message?.let { UiText.Dynamic(it) }
|
||||||
?: UiText.Resource(R.string.common_unknown)
|
?: UiText.Resource(R.string.common_unknown)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# 默认(无 qualifier)资源对应的语言:英文(国际回退语言)
|
# 默认(无 qualifier)资源对应的语言:英文(国际回退语言)
|
||||||
# 非中文/英文系统语言会回退到默认英文资源;中文系统走 values-zh
|
# 非中文/英文系统语言会回退到默认英文资源;中文系统走 values-b+zh+Hans / values-b+zh+Hant
|
||||||
# AGP 据此 + values-zh 目录生成 locale config,Android 13+ 系统设置中
|
# AGP 据此 + 中文资源目录生成 locale config,Android 13+ 系统设置中
|
||||||
# 会显示 RainyToken 的独立语言选项(English / 简体中文)
|
# 会显示 RainyToken 的独立语言选项(English / 简体中文 / 繁體中文)
|
||||||
unqualifiedResLocale=en
|
unqualifiedResLocale=en
|
||||||
|
|||||||
@ -427,7 +427,12 @@
|
|||||||
<string name="action_save_cookie">保存 Cookie</string>
|
<string name="action_save_cookie">保存 Cookie</string>
|
||||||
|
|
||||||
<!-- ══════════ 服务详情/凭据校验(D 组迁移) ══════════ -->
|
<!-- ══════════ 服务详情/凭据校验(D 组迁移) ══════════ -->
|
||||||
<string name="error_parse_failed">数据解析失败: %1$s</string>
|
<string name="error_parse_empty_body">响应体为空</string>
|
||||||
|
<string name="error_parse_not_json">响应根节点不是 JSON 对象</string>
|
||||||
|
<string name="error_parse_no_windows">未找到用量窗口数据</string>
|
||||||
|
<string name="error_parse_no_models">未找到模型列表</string>
|
||||||
|
<string name="error_parse_models_empty">模型列表为空</string>
|
||||||
|
<string name="error_parse_malformed">响应格式异常</string>
|
||||||
<string name="window_every_week">每周</string>
|
<string name="window_every_week">每周</string>
|
||||||
<string name="msg_model_call_count">模型调用次数</string>
|
<string name="msg_model_call_count">模型调用次数</string>
|
||||||
<string name="msg_stale_cache">缓存数据 · %1$s</string>
|
<string name="msg_stale_cache">缓存数据 · %1$s</string>
|
||||||
|
|||||||
@ -427,7 +427,12 @@
|
|||||||
<string name="action_save_cookie">儲存 Cookie</string>
|
<string name="action_save_cookie">儲存 Cookie</string>
|
||||||
|
|
||||||
<!-- ══════════ 服务详情/凭据校验(D 组迁移) ══════════ -->
|
<!-- ══════════ 服务详情/凭据校验(D 组迁移) ══════════ -->
|
||||||
<string name="error_parse_failed">數據解析失敗: %1$s</string>
|
<string name="error_parse_empty_body">回應體為空</string>
|
||||||
|
<string name="error_parse_not_json">回應根節點不是 JSON 物件</string>
|
||||||
|
<string name="error_parse_no_windows">未找到用量視窗資料</string>
|
||||||
|
<string name="error_parse_no_models">未找到模型清單</string>
|
||||||
|
<string name="error_parse_models_empty">模型清單為空</string>
|
||||||
|
<string name="error_parse_malformed">回應格式異常</string>
|
||||||
<string name="window_every_week">每週</string>
|
<string name="window_every_week">每週</string>
|
||||||
<string name="msg_model_call_count">模型呼叫次數</string>
|
<string name="msg_model_call_count">模型呼叫次數</string>
|
||||||
<string name="msg_stale_cache">快取數據 · %1$s</string>
|
<string name="msg_stale_cache">快取數據 · %1$s</string>
|
||||||
|
|||||||
@ -430,7 +430,12 @@
|
|||||||
<string name="action_save_cookie">Save cookie</string>
|
<string name="action_save_cookie">Save cookie</string>
|
||||||
|
|
||||||
<!-- ══════════ 服务详情/凭据校验(D 组) ══════════ -->
|
<!-- ══════════ 服务详情/凭据校验(D 组) ══════════ -->
|
||||||
<string name="error_parse_failed">Failed to parse data: %1$s</string>
|
<string name="error_parse_empty_body">Empty response body</string>
|
||||||
|
<string name="error_parse_not_json">Response root is not a JSON object</string>
|
||||||
|
<string name="error_parse_no_windows">No usage window data found</string>
|
||||||
|
<string name="error_parse_no_models">Model list not found</string>
|
||||||
|
<string name="error_parse_models_empty">Model list is empty</string>
|
||||||
|
<string name="error_parse_malformed">Response format is abnormal</string>
|
||||||
<string name="window_every_week">Weekly</string>
|
<string name="window_every_week">Weekly</string>
|
||||||
<string name="msg_model_call_count">Model call count</string>
|
<string name="msg_model_call_count">Model call count</string>
|
||||||
<string name="msg_stale_cache">Cached data · %1$s</string>
|
<string name="msg_stale_cache">Cached data · %1$s</string>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user