feat(ui): i18n for CloudZero cost tracking

This commit is contained in:
吹雪 2026-06-11 04:31:59 +09:00
parent cd547a117b
commit 663681bc4e
7 changed files with 219 additions and 65 deletions

View file

@ -7,8 +7,10 @@ import CloudZeroCreationModal from "./CloudZeroCreateModal";
import { useQueryClient } from "@tanstack/react-query";
import { createQueryKeys } from "@/app/(dashboard)/hooks/common/queryKeysFactory";
import { CloudZeroIntegrationSettings } from "./CloudZeroIntegrationSettings";
import { useTranslation } from "react-i18next";
export default function CloudZeroCostTracking() {
const { t } = useTranslation();
const { accessToken } = useAuthorized();
const { data: settings, isLoading, error } = useCloudZeroSettings(accessToken);
const queryClient = useQueryClient();
@ -28,7 +30,7 @@ export default function CloudZeroCostTracking() {
if (isLoading) {
return (
<Card>
<Typography.Text>Loading CloudZero settings...</Typography.Text>
<Typography.Text>{t("cloudZero.cloudZeroCostTracking.loadingSettings")}</Typography.Text>
</Card>
);
}
@ -37,7 +39,9 @@ export default function CloudZeroCostTracking() {
return (
<Card>
<Typography.Text className="text-red-600">
Error loading CloudZero settings: {error instanceof Error ? error.message : String(error)}
{t("cloudZero.cloudZeroCostTracking.errorLoadingSettings", {
error: error instanceof Error ? error.message : String(error),
})}
</Typography.Text>
</Card>
);

View file

@ -3,6 +3,7 @@ import MessageManager from "@/components/molecules/message_manager";
import { useEffect } from "react";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { useCloudZeroCreate } from "@/app/(dashboard)/hooks/cloudzero/useCloudZeroCreate";
import { useTranslation } from "react-i18next";
interface CloudZeroCreationModalProps {
open: boolean;
@ -11,6 +12,7 @@ interface CloudZeroCreationModalProps {
}
export default function CloudZeroCreationModal({ open, onOk, onCancel }: CloudZeroCreationModalProps) {
const { t } = useTranslation();
const { accessToken } = useAuthorized();
const [form] = Form.useForm();
const createMutation = useCloudZeroCreate(accessToken || "");
@ -32,7 +34,7 @@ export default function CloudZeroCreationModal({ open, onOk, onCancel }: CloudZe
},
{
onSuccess: () => {
MessageManager.success("CloudZero integration created successfully");
MessageManager.success(t("cloudZero.cloudZeroCreateModal.createSuccess"));
form.resetFields();
onOk();
},
@ -40,7 +42,7 @@ export default function CloudZeroCreationModal({ open, onOk, onCancel }: CloudZe
if (error?.errorFields) {
return;
}
MessageManager.error(error?.message || "Failed to create CloudZero integration");
MessageManager.error(error?.message || t("cloudZero.cloudZeroCreateModal.createFailed"));
},
},
);
@ -48,7 +50,7 @@ export default function CloudZeroCreationModal({ open, onOk, onCancel }: CloudZe
if (error?.errorFields) {
return;
}
MessageManager.error(error?.message || "Failed to create CloudZero integration");
MessageManager.error(error?.message || t("cloudZero.cloudZeroCreateModal.createFailed"));
}
};
@ -59,13 +61,13 @@ export default function CloudZeroCreationModal({ open, onOk, onCancel }: CloudZe
return (
<Modal
title="Create CloudZero Integration"
title={t("cloudZero.cloudZeroCreateModal.title")}
open={open}
onOk={handleSubmit}
onCancel={handleCancel}
confirmLoading={createMutation.isPending}
okText={createMutation.isPending ? "Creating..." : "Create"}
cancelText="Cancel"
okText={createMutation.isPending ? t("cloudZero.cloudZeroCreateModal.creating") : t("common.create")}
cancelText={t("common.cancel")}
okButtonProps={{
disabled: createMutation.isPending,
}}
@ -75,23 +77,23 @@ export default function CloudZeroCreationModal({ open, onOk, onCancel }: CloudZe
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Form.Item
label="CloudZero API Key"
label={t("cloudZero.cloudZeroCreateModal.apiKeyLabel")}
name="api_key"
rules={[{ required: true, message: "Please enter your CloudZero API key" }]}
rules={[{ required: true, message: t("cloudZero.cloudZeroCreateModal.apiKeyRequired") }]}
>
<Input.Password placeholder="Enter your CloudZero API key" />
<Input.Password placeholder={t("cloudZero.cloudZeroCreateModal.apiKeyPlaceholder")} />
</Form.Item>
<Form.Item
label="Connection ID"
label={t("cloudZero.cloudZeroCreateModal.connectionIdLabel")}
name="connection_id"
rules={[{ required: true, message: "Please enter your CloudZero connection ID" }]}
rules={[{ required: true, message: t("cloudZero.cloudZeroCreateModal.connectionIdRequired") }]}
>
<Input placeholder="Enter your CloudZero connection ID" />
<Input placeholder={t("cloudZero.cloudZeroCreateModal.connectionIdPlaceholder")} />
</Form.Item>
<Form.Item
label="Timezone"
label={t("cloudZero.cloudZeroCreateModal.timezoneLabel")}
name="timezone"
tooltip="Timezone for date handling (defaults to UTC if not provided)"
tooltip={t("cloudZero.cloudZeroCreateModal.timezoneTooltip")}
>
<Input placeholder="UTC" />
</Form.Item>

View file

@ -1,4 +1,5 @@
import { Empty, Typography, Button } from "antd";
import { useTranslation } from "react-i18next";
const { Title, Paragraph } = Typography;
@ -7,21 +8,22 @@ interface CloudZeroEmptyPlaceholderProps {
}
export default function CloudZeroEmptyPlaceholder({ startCreation }: CloudZeroEmptyPlaceholderProps) {
const { t } = useTranslation();
return (
<div className="bg-white p-12 rounded-lg border border-dashed border-gray-300 text-center max-w-2xl mx-auto mt-8">
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description={
<div className="space-y-2">
<Title level={4}>No CloudZero Integration Found</Title>
<Title level={4}>{t("cloudZero.cloudZeroEmptyPlaceholder.title")}</Title>
<Paragraph type="secondary" className="max-w-md mx-auto">
Connect your CloudZero account to start tracking and analyzing your cloud costs directly from LiteLLM.
{t("cloudZero.cloudZeroEmptyPlaceholder.description")}
</Paragraph>
</div>
}
>
<Button type="primary" size="large" onClick={startCreation} className="flex items-center gap-2 mx-auto mt-4">
Add CloudZero Integration
{t("cloudZero.cloudZeroEmptyPlaceholder.addButton")}
</Button>
</Empty>
</div>

View file

@ -9,6 +9,7 @@ import { CheckCircle, Edit, Play, Trash2, Upload } from "lucide-react";
import { useState } from "react";
import CloudZeroUpdateModal from "./CloudZeroUpdateModal";
import { CloudZeroSettings } from "./types";
import { useTranslation } from "react-i18next";
interface CloudZeroIntegrationSettingsProps {
settings: CloudZeroSettings;
@ -16,6 +17,7 @@ interface CloudZeroIntegrationSettingsProps {
}
export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: CloudZeroIntegrationSettingsProps) {
const { t } = useTranslation();
const { accessToken } = useAuthorized();
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
@ -31,10 +33,10 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
{ limit: 10 },
{
onSuccess: (data) => {
MessageManager.success("Dry run completed successfully");
MessageManager.success(t("cloudZero.cloudZeroIntegrationSettings.dryRunSuccess"));
},
onError: (error) => {
MessageManager.error(error?.message || "Failed to perform dry run");
MessageManager.error(error?.message || t("cloudZero.cloudZeroIntegrationSettings.dryRunFailed"));
},
},
);
@ -49,10 +51,10 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
{ operation: "replace_hourly" },
{
onSuccess: () => {
MessageManager.success("Data successfully exported to CloudZero");
MessageManager.success(t("cloudZero.cloudZeroIntegrationSettings.exportSuccess"));
},
onError: (error) => {
MessageManager.error(error?.message || "Failed to export data");
MessageManager.error(error?.message || t("cloudZero.cloudZeroIntegrationSettings.exportFailed"));
},
},
);
@ -80,12 +82,12 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
deleteMutation.mutate(undefined, {
onSuccess: () => {
MessageManager.success("CloudZero integration deleted successfully");
MessageManager.success(t("cloudZero.cloudZeroIntegrationSettings.deleteSuccess"));
setIsDeleteModalOpen(false);
onSettingsUpdated();
},
onError: (error) => {
MessageManager.error(error?.message || "Failed to delete CloudZero integration");
MessageManager.error(error?.message || t("cloudZero.cloudZeroIntegrationSettings.deleteFailed"));
},
});
};
@ -100,16 +102,16 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
<Card
title={
<div className="flex items-center gap-2">
<span className="text-lg font-semibold">CloudZero Configuration</span>
<span className="text-lg font-semibold">{t("cloudZero.cloudZeroIntegrationSettings.cardTitle")}</span>
<Tag color="success" className="ml-2 capitalize">
{settings.status || "Active"}
{settings.status || t("common.active")}
</Tag>
</div>
}
extra={
<div className="flex gap-2">
<Button icon={<Edit size={16} />} onClick={handleEdit} className="flex items-center gap-2">
Edit
{t("common.edit")}
</Button>
<Button
danger
@ -117,7 +119,7 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
onClick={handleDeleteClick}
className="flex items-center gap-2"
>
Delete
{t("common.delete")}
</Button>
</div>
}
@ -134,23 +136,33 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
xs: 1,
}}
>
<Descriptions.Item label="API Key (Redacted)">
<Descriptions.Item label={t("cloudZero.cloudZeroIntegrationSettings.apiKeyRedacted")}>
<span className="font-mono text-gray-600">
{settings.api_key_masked || <span className="text-gray-400 italic">Not configured</span>}
{settings.api_key_masked || (
<span className="text-gray-400 italic">
{t("cloudZero.cloudZeroIntegrationSettings.notConfigured")}
</span>
)}
</span>
</Descriptions.Item>
<Descriptions.Item label="Connection ID">
<Descriptions.Item label={t("cloudZero.cloudZeroIntegrationSettings.connectionId")}>
<span className="font-mono text-gray-600">
{settings.connection_id || <span className="text-gray-400 italic">Not configured</span>}
{settings.connection_id || (
<span className="text-gray-400 italic">
{t("cloudZero.cloudZeroIntegrationSettings.notConfigured")}
</span>
)}
</span>
</Descriptions.Item>
<Descriptions.Item label="Timezone">
{settings.timezone || <span className="text-gray-400 italic">Default (UTC)</span>}
<Descriptions.Item label={t("cloudZero.cloudZeroIntegrationSettings.timezone")}>
{settings.timezone || (
<span className="text-gray-400 italic">{t("cloudZero.cloudZeroIntegrationSettings.defaultUtc")}</span>
)}
</Descriptions.Item>
</Descriptions>
<Divider orientation="left" className="text-gray-500">
Actions
{t("common.actions")}
</Divider>
<div className="flex flex-wrap gap-4 mb-6">
@ -160,15 +172,15 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
icon={<Play size={16} />}
className="flex items-center gap-2"
>
Run Dry Run Simulation
{t("cloudZero.cloudZeroIntegrationSettings.runDryRun")}
</Button>
<Popconfirm
title="Export Data to CloudZero"
description="This will push the current accumulated cost data to CloudZero. Continue?"
title={t("cloudZero.cloudZeroIntegrationSettings.exportPopconfirmTitle")}
description={t("cloudZero.cloudZeroIntegrationSettings.exportPopconfirmDescription")}
onConfirm={handleExport}
okText="Export"
cancelText="Cancel"
okText={t("cloudZero.cloudZeroIntegrationSettings.exportOkText")}
cancelText={t("common.cancel")}
>
<Button
type="primary"
@ -176,7 +188,7 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
icon={<Upload size={16} />}
className="flex items-center gap-2"
>
Export Data Now
{t("cloudZero.cloudZeroIntegrationSettings.exportDataNow")}
</Button>
</Popconfirm>
</div>
@ -184,10 +196,14 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
{dryRunResult && (
<div className="mt-6 animate-in fade-in slide-in-from-top-4 duration-300">
<Alert
message="Dry Run Results"
message={t("cloudZero.cloudZeroIntegrationSettings.dryRunResultsTitle")}
description={
<div className="mt-2">
<p className="mb-2 text-gray-600">Simulation output for connection: {settings.connection_id}</p>
<p className="mb-2 text-gray-600">
{t("cloudZero.cloudZeroIntegrationSettings.dryRunSimulationOutput", {
connectionId: settings.connection_id,
})}
</p>
<pre className="bg-gray-50 p-4 rounded-md border border-gray-200 overflow-x-auto text-xs font-mono text-gray-800">
{dryRunResult}
</pre>
@ -211,18 +227,18 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
<DeleteResourceModal
isOpen={isDeleteModalOpen}
title="Delete CloudZero Integration?"
message="Are you sure you want to delete this CloudZero integration? All associated settings and configurations will be permanently removed."
resourceInformationTitle="Integration Details"
title={t("cloudZero.cloudZeroIntegrationSettings.deleteModalTitle")}
message={t("cloudZero.cloudZeroIntegrationSettings.deleteModalMessage")}
resourceInformationTitle={t("cloudZero.cloudZeroIntegrationSettings.integrationDetails")}
resourceInformation={[
{
label: "Connection ID",
label: t("cloudZero.cloudZeroIntegrationSettings.connectionId"),
value: settings.connection_id,
code: true,
},
{
label: "Timezone",
value: settings.timezone || "Default (UTC)",
label: t("cloudZero.cloudZeroIntegrationSettings.timezone"),
value: settings.timezone || t("cloudZero.cloudZeroIntegrationSettings.defaultUtc"),
},
]}
onCancel={handleDeleteCancel}

View file

@ -4,6 +4,7 @@ import { Form, Input, Modal } from "antd";
import MessageManager from "@/components/molecules/message_manager";
import { useEffect } from "react";
import { CloudZeroSettings } from "./types";
import { useTranslation } from "react-i18next";
interface CloudZeroUpdateModalProps {
open: boolean;
@ -13,6 +14,7 @@ interface CloudZeroUpdateModalProps {
}
export default function CloudZeroUpdateModal({ open, onOk, onCancel, settings }: CloudZeroUpdateModalProps) {
const { t } = useTranslation();
const { accessToken } = useAuthorized();
const [form] = Form.useForm();
const updateMutation = useCloudZeroUpdateSettings(accessToken || "");
@ -40,7 +42,7 @@ export default function CloudZeroUpdateModal({ open, onOk, onCancel, settings }:
},
{
onSuccess: () => {
MessageManager.success("CloudZero integration updated successfully");
MessageManager.success(t("cloudZero.cloudZeroUpdateModal.updateSuccess"));
form.resetFields();
onOk();
},
@ -48,7 +50,7 @@ export default function CloudZeroUpdateModal({ open, onOk, onCancel, settings }:
if (error?.errorFields) {
return;
}
MessageManager.error(error?.message || "Failed to update CloudZero integration");
MessageManager.error(error?.message || t("cloudZero.cloudZeroUpdateModal.updateFailed"));
},
},
);
@ -56,7 +58,7 @@ export default function CloudZeroUpdateModal({ open, onOk, onCancel, settings }:
if (error?.errorFields) {
return;
}
MessageManager.error(error?.message || "Failed to update CloudZero integration");
MessageManager.error(error?.message || t("cloudZero.cloudZeroUpdateModal.updateFailed"));
}
};
@ -67,13 +69,13 @@ export default function CloudZeroUpdateModal({ open, onOk, onCancel, settings }:
return (
<Modal
title="Edit CloudZero Integration"
title={t("cloudZero.cloudZeroUpdateModal.title")}
open={open}
onOk={handleSubmit}
onCancel={handleCancel}
confirmLoading={updateMutation.isPending}
okText={updateMutation.isPending ? "Updating..." : "Update"}
cancelText="Cancel"
okText={updateMutation.isPending ? t("cloudZero.cloudZeroUpdateModal.updating") : t("common.update")}
cancelText={t("common.cancel")}
okButtonProps={{
disabled: updateMutation.isPending,
}}
@ -83,24 +85,24 @@ export default function CloudZeroUpdateModal({ open, onOk, onCancel, settings }:
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Form.Item
label="CloudZero API Key"
label={t("cloudZero.cloudZeroUpdateModal.apiKeyLabel")}
name="api_key"
rules={[{ required: false, message: "Please enter your CloudZero API key" }]}
tooltip="Leave empty to keep the existing API key"
rules={[{ required: false, message: t("cloudZero.cloudZeroUpdateModal.apiKeyRuleMessage") }]}
tooltip={t("cloudZero.cloudZeroUpdateModal.apiKeyTooltip")}
>
<Input.Password placeholder="Leave empty to keep existing" />
<Input.Password placeholder={t("cloudZero.cloudZeroUpdateModal.apiKeyPlaceholder")} />
</Form.Item>
<Form.Item
label="Connection ID"
label={t("cloudZero.cloudZeroUpdateModal.connectionIdLabel")}
name="connection_id"
rules={[{ required: true, message: "Please enter your CloudZero connection ID" }]}
rules={[{ required: true, message: t("cloudZero.cloudZeroUpdateModal.connectionIdRequired") }]}
>
<Input placeholder="Enter your CloudZero connection ID" />
<Input placeholder={t("cloudZero.cloudZeroUpdateModal.connectionIdPlaceholder")} />
</Form.Item>
<Form.Item
label="Timezone"
label={t("cloudZero.cloudZeroUpdateModal.timezoneLabel")}
name="timezone"
tooltip="Timezone for date handling (defaults to UTC if not provided)"
tooltip={t("cloudZero.cloudZeroUpdateModal.timezoneTooltip")}
>
<Input placeholder="UTC" />
</Form.Item>

View file

@ -7059,5 +7059,69 @@
"deleteMessage": "Are you sure you want to delete this access group? This action cannot be undone.",
"resourceInfoTitle": "Access Group Information"
}
},
"cloudZero": {
"cloudZeroCostTracking": {
"loadingSettings": "Loading CloudZero settings...",
"errorLoadingSettings": "Error loading CloudZero settings: {{error}}"
},
"cloudZeroCreateModal": {
"title": "Create CloudZero Integration",
"creating": "Creating...",
"createSuccess": "CloudZero integration created successfully",
"createFailed": "Failed to create CloudZero integration",
"apiKeyLabel": "CloudZero API Key",
"apiKeyRequired": "Please enter your CloudZero API key",
"apiKeyPlaceholder": "Enter your CloudZero API key",
"connectionIdLabel": "Connection ID",
"connectionIdRequired": "Please enter your CloudZero connection ID",
"connectionIdPlaceholder": "Enter your CloudZero connection ID",
"timezoneLabel": "Timezone",
"timezoneTooltip": "Timezone for date handling (defaults to UTC if not provided)"
},
"cloudZeroEmptyPlaceholder": {
"title": "No CloudZero Integration Found",
"description": "Connect your CloudZero account to start tracking and analyzing your cloud costs directly from LiteLLM.",
"addButton": "Add CloudZero Integration"
},
"cloudZeroIntegrationSettings": {
"cardTitle": "CloudZero Configuration",
"apiKeyRedacted": "API Key (Redacted)",
"connectionId": "Connection ID",
"timezone": "Timezone",
"notConfigured": "Not configured",
"defaultUtc": "Default (UTC)",
"runDryRun": "Run Dry Run Simulation",
"exportPopconfirmTitle": "Export Data to CloudZero",
"exportPopconfirmDescription": "This will push the current accumulated cost data to CloudZero. Continue?",
"exportOkText": "Export",
"exportDataNow": "Export Data Now",
"dryRunSuccess": "Dry run completed successfully",
"dryRunFailed": "Failed to perform dry run",
"exportSuccess": "Data successfully exported to CloudZero",
"exportFailed": "Failed to export data",
"deleteSuccess": "CloudZero integration deleted successfully",
"deleteFailed": "Failed to delete CloudZero integration",
"dryRunResultsTitle": "Dry Run Results",
"dryRunSimulationOutput": "Simulation output for connection: {{connectionId}}",
"deleteModalTitle": "Delete CloudZero Integration?",
"deleteModalMessage": "Are you sure you want to delete this CloudZero integration? All associated settings and configurations will be permanently removed.",
"integrationDetails": "Integration Details"
},
"cloudZeroUpdateModal": {
"title": "Edit CloudZero Integration",
"updating": "Updating...",
"updateSuccess": "CloudZero integration updated successfully",
"updateFailed": "Failed to update CloudZero integration",
"apiKeyLabel": "CloudZero API Key",
"apiKeyRuleMessage": "Please enter your CloudZero API key",
"apiKeyTooltip": "Leave empty to keep the existing API key",
"apiKeyPlaceholder": "Leave empty to keep existing",
"connectionIdLabel": "Connection ID",
"connectionIdRequired": "Please enter your CloudZero connection ID",
"connectionIdPlaceholder": "Enter your CloudZero connection ID",
"timezoneLabel": "Timezone",
"timezoneTooltip": "Timezone for date handling (defaults to UTC if not provided)"
}
}
}

View file

@ -7059,5 +7059,69 @@
"deleteMessage": "确定要删除此访问组吗?此操作无法撤销。",
"resourceInfoTitle": "访问组信息"
}
},
"cloudZero": {
"cloudZeroCostTracking": {
"loadingSettings": "正在加载 CloudZero 设置...",
"errorLoadingSettings": "加载 CloudZero 设置时出错:{{error}}"
},
"cloudZeroCreateModal": {
"title": "创建 CloudZero 集成",
"creating": "创建中...",
"createSuccess": "CloudZero 集成已成功创建",
"createFailed": "创建 CloudZero 集成失败",
"apiKeyLabel": "CloudZero API 密钥",
"apiKeyRequired": "请输入您的 CloudZero API 密钥",
"apiKeyPlaceholder": "输入您的 CloudZero API 密钥",
"connectionIdLabel": "Connection ID",
"connectionIdRequired": "请输入您的 CloudZero 连接 ID",
"connectionIdPlaceholder": "输入您的 CloudZero 连接 ID",
"timezoneLabel": "时区",
"timezoneTooltip": "日期处理时区(未提供时默认为 UTC)"
},
"cloudZeroEmptyPlaceholder": {
"title": "未找到 CloudZero 集成",
"description": "连接您的 CloudZero 账户,即可直接在 LiteLLM 中追踪和分析云端成本。",
"addButton": "添加 CloudZero 集成"
},
"cloudZeroIntegrationSettings": {
"cardTitle": "CloudZero 配置",
"apiKeyRedacted": "API 密钥(已脱敏)",
"connectionId": "Connection ID",
"timezone": "时区",
"notConfigured": "未配置",
"defaultUtc": "默认 (UTC)",
"runDryRun": "运行试运行模拟",
"exportPopconfirmTitle": "将数据导出至 CloudZero",
"exportPopconfirmDescription": "此操作将把当前累计的成本数据推送至 CloudZero。是否继续?",
"exportOkText": "导出",
"exportDataNow": "立即导出数据",
"dryRunSuccess": "试运行已成功完成",
"dryRunFailed": "执行试运行失败",
"exportSuccess": "数据已成功导出至 CloudZero",
"exportFailed": "导出数据失败",
"deleteSuccess": "CloudZero 集成已成功删除",
"deleteFailed": "删除 CloudZero 集成失败",
"dryRunResultsTitle": "试运行结果",
"dryRunSimulationOutput": "连接 {{connectionId}} 的模拟输出",
"deleteModalTitle": "删除 CloudZero 集成?",
"deleteModalMessage": "确定要删除此 CloudZero 集成吗?所有相关设置和配置将被永久移除。",
"integrationDetails": "集成详情"
},
"cloudZeroUpdateModal": {
"title": "编辑 CloudZero 集成",
"updating": "更新中...",
"updateSuccess": "CloudZero 集成已成功更新",
"updateFailed": "更新 CloudZero 集成失败",
"apiKeyLabel": "CloudZero API 密钥",
"apiKeyRuleMessage": "请输入您的 CloudZero API 密钥",
"apiKeyTooltip": "留空则保留现有 API 密钥",
"apiKeyPlaceholder": "留空则保留现有密钥",
"connectionIdLabel": "Connection ID",
"connectionIdRequired": "请输入您的 CloudZero 连接 ID",
"connectionIdPlaceholder": "输入您的 CloudZero 连接 ID",
"timezoneLabel": "时区",
"timezoneTooltip": "日期处理时区(未提供时默认为 UTC)"
}
}
}