diff --git a/airflow-core/src/airflow/ui/.prettierignore b/airflow-core/src/airflow/ui/.prettierignore
index 49a8631b874a0..c18e858d9b9d7 100644
--- a/airflow-core/src/airflow/ui/.prettierignore
+++ b/airflow-core/src/airflow/ui/.prettierignore
@@ -5,3 +5,4 @@ dist/
*.yaml
coverage/*
.pnpm-store
+src/i18n/locales/*
diff --git a/airflow-core/src/airflow/ui/src/components/Assets/AssetEvents.tsx b/airflow-core/src/airflow/ui/src/components/Assets/AssetEvents.tsx
index 20f3de83ab6d5..ffbcff9636534 100644
--- a/airflow-core/src/airflow/ui/src/components/Assets/AssetEvents.tsx
+++ b/airflow-core/src/airflow/ui/src/components/Assets/AssetEvents.tsx
@@ -57,7 +57,7 @@ export const AssetEvents = ({
title,
...rest
}: AssetEventProps & BoxProps) => {
- const { t: translate } = useTranslation("dashboard");
+ const { t: translate } = useTranslation(["dashboard", "common"]);
const assetSortOptions = createListCollection({
items: [
{ label: translate("sortBy.newestFirst"), value: "-timestamp" },
@@ -74,7 +74,7 @@ export const AssetEvents = ({
{data?.total_entries ?? " "}
- {translate("assetEvent", { count: data?.total_entries ?? 0 })}
+ {translate("common:assetEvent", { count: data?.total_entries ?? 0 })}
{setOrderBy === undefined ? undefined : (
diff --git a/airflow-core/src/airflow/ui/src/components/Assets/TriggeredRuns.tsx b/airflow-core/src/airflow/ui/src/components/Assets/TriggeredRuns.tsx
index 3e052c306af2a..c6d48c54fbfb8 100644
--- a/airflow-core/src/airflow/ui/src/components/Assets/TriggeredRuns.tsx
+++ b/airflow-core/src/airflow/ui/src/components/Assets/TriggeredRuns.tsx
@@ -17,11 +17,11 @@
* under the License.
*/
import { Flex, Link, Text } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import type { DagRunAssetReference, DagRunState } from "openapi/requests/types.gen";
import { Button, Popover } from "src/components/ui";
-import { pluralize } from "src/utils";
import { StateBadge } from "../StateBadge";
@@ -30,13 +30,15 @@ type Props = {
};
export const TriggeredRuns = ({ dagRuns }: Props) => {
+ const { t: translate } = useTranslation("common");
+
if (dagRuns === undefined || dagRuns.length === 0) {
return undefined;
}
return dagRuns.length === 1 ? (
- Triggered Dag Run:
+ {`${translate("triggered")} ${translate("dagRun_one")}`}:
@@ -49,7 +51,7 @@ export const TriggeredRuns = ({ dagRuns }: Props) => {
diff --git a/airflow-core/src/airflow/ui/src/components/PoolBar.tsx b/airflow-core/src/airflow/ui/src/components/PoolBar.tsx
index 2780a614ce973..560f2bcf4cc8d 100644
--- a/airflow-core/src/airflow/ui/src/components/PoolBar.tsx
+++ b/airflow-core/src/airflow/ui/src/components/PoolBar.tsx
@@ -17,9 +17,9 @@
* under the License.
*/
import { Flex } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { Tooltip } from "src/components/ui";
-import { capitalize } from "src/utils";
import { type Slots, slotConfigs } from "src/utils/slots";
export const PoolBar = ({
@@ -30,39 +30,43 @@ export const PoolBar = ({
readonly pool: Slots;
readonly poolsWithSlotType?: Slots;
readonly totalSlots: number;
-}) => (
- <>
- {slotConfigs.map(({ color, icon, key }) => {
- const slotValue = pool[key];
- const flexValue = slotValue / totalSlots || 0;
+}) => {
+ const { t: translate } = useTranslation("common");
- if (flexValue === 0) {
- return undefined;
- }
+ return (
+ <>
+ {slotConfigs.map(({ color, icon, key }) => {
+ const slotValue = pool[key];
+ const flexValue = slotValue / totalSlots || 0;
- const tooltipContent = `${capitalize(key.replace("_", " "))}: ${slotValue}${
- poolsWithSlotType ? ` (${poolsWithSlotType[key]} pools)` : ""
- }`;
+ if (flexValue === 0) {
+ return undefined;
+ }
- return (
-
-
- {icon}
- {slotValue}
-
-
- );
- })}
- >
-);
+ const slotType = key.replace("_slots", "");
+ const poolCount = poolsWithSlotType ? poolsWithSlotType[key] : 0;
+ const tooltipContent = `${translate(`pools.${slotType}`)}: ${slotValue} (${poolCount} ${translate("pools.pools", { count: poolCount })})`;
+
+ return (
+
+
+ {icon}
+ {slotValue}
+
+
+ );
+ })}
+ >
+ );
+};
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/en/common.json b/airflow-core/src/airflow/ui/src/i18n/locales/en/common.json
index 1bbffb0644802..0b5395ebef9e5 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/en/common.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/common.json
@@ -42,5 +42,36 @@
"documentation": "Documentation",
"githubRepo": "GitHub Repo",
"restApiReference": "REST API Reference"
+ },
+ "states": {
+ "queued": "Queued",
+ "running": "Running",
+ "success": "Success",
+ "failed": "Failed",
+ "skipped": "Skipped",
+ "removed": "Removed",
+ "scheduled": "Scheduled",
+ "restarting": "Restarting",
+ "up_for_retry": "Up For Retry",
+ "up_for_reschedule": "Up For Reschedule",
+ "upstream_failed": "Upstream Failed",
+ "deferred": "Deferred",
+ "no_status": "No Status"
+ },
+ "dagRun_one": "Dag Run",
+ "dagRun_other": "Dag Runs",
+ "taskInstance_one": "Task Instance",
+ "taskInstance_other": "Task Instances",
+ "assetEvent_one": "Asset Event",
+ "assetEvent_other": "Asset Events",
+ "triggered": "Triggered",
+ "pools": {
+ "open": "Open",
+ "running": "Running",
+ "queued": "Queued",
+ "scheduled": "Scheduled",
+ "deferred": "Deferred",
+ "pools_one": "pool",
+ "pools_other": "pools"
}
}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/en/dashboard.json b/airflow-core/src/airflow/ui/src/i18n/locales/en/dashboard.json
index 7259f912f640a..2c24501203be0 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/en/dashboard.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/dashboard.json
@@ -12,38 +12,27 @@
"metaDatabase": "MetaDatabase",
"scheduler": "Scheduler",
"triggerer": "Triggerer",
- "dagProcessor": "Dag Processor"
+ "dagProcessor": "Dag Processor",
+ "status": "Status",
+ "lastHeartbeat": "Last Heartbeat",
+ "healthy": "Healthy",
+ "unhealthy": "Unhealthy"
+ },
+ "importErrors": {
+ "searchByFile": "Search by file",
+ "dagImportError_one": "Dag Import Error",
+ "dagImportError_other": "Dag Import Errors",
+ "timestamp": "Timestamp"
},
"poolSlots": "Pool Slots",
"managePools": "Manage Pools",
"history": "History",
- "dagRun_one": "Dag Run",
- "dagRun_other": "Dag Runs",
- "taskInstance_one": "Task Instance",
- "taskInstance_other": "Task Instances",
- "assetEvent_one": "Asset Event",
- "assetEvent_other": "Asset Events",
- "states": {
- "queued": "Queued",
- "running": "Running",
- "success": "Success",
- "failed": "Failed",
- "skipped": "Skipped",
- "removed": "Removed",
- "scheduled": "Scheduled",
- "restarting": "Restarting",
- "up_for_retry": "Up For Retry",
- "up_for_reschedule": "Up For Reschedule",
- "upstream_failed": "Upstream Failed",
- "deferred": "Deferred",
- "no_status": "No Status"
- },
- "noAssetEvents": "No Asset Events found.",
"sortBy": {
"newestFirst": "Newest First",
"oldestFirst": "Oldest First"
},
"source": "Source",
"group": "Group",
- "uri": "Uri"
+ "uri": "Uri",
+ "noAssetEvents": "No Asset Events found."
}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/zh_TW/common.json b/airflow-core/src/airflow/ui/src/i18n/locales/zh_TW/common.json
index b0eb5b17fdaa0..c9f25ec852193 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/zh_TW/common.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/zh_TW/common.json
@@ -21,7 +21,7 @@
},
"browse": {
"auditLog": "審計日誌",
- "xcoms": "Xcoms"
+ "xcoms": "XComs"
},
"admin": {
"Variables": "變數",
@@ -42,5 +42,26 @@
"documentation": "文件",
"githubRepo": "GitHub 倉庫",
"restApiReference": "REST API 參考"
- }
+ },
+ "states": {
+ "queued": "排隊中",
+ "running": "執行中",
+ "success": "成功",
+ "failed": "失敗",
+ "skipped": "已跳過",
+ "removed": "已移除",
+ "scheduled": "已排程",
+ "restarting": "重啟中",
+ "up_for_retry": "等待重試",
+ "up_for_reschedule": "等待重新排程",
+ "upstream_failed": "上游任務失敗",
+ "deferred": "已延後",
+ "no_status": "無狀態"
+ },
+ "dagRun_one": "Dag 執行",
+ "dagRun_other": "Dag 執行",
+ "taskInstance_one": "任務實例",
+ "taskInstance_other": "任務實例",
+ "assetEvent_one": "資源事件",
+ "assetEvent_other": "資源事件"
}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/zh_TW/dashboard.json b/airflow-core/src/airflow/ui/src/i18n/locales/zh_TW/dashboard.json
index f669905ea3ce3..fd5aa0c97b62e 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/zh_TW/dashboard.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/zh_TW/dashboard.json
@@ -17,33 +17,12 @@
"poolSlots": "資源池配額",
"managePools": "管理資源池",
"history": "歷史記錄",
- "dagRun_one": "Dag 執行",
- "dagRun_other": "Dag 執行",
- "taskInstance_one": "任務實例",
- "taskInstance_other": "任務實例",
- "assetEvent_one": "資源事件",
- "assetEvent_other": "資源事件",
- "states": {
- "queued": "排隊中",
- "running": "執行中",
- "success": "成功",
- "failed": "失敗",
- "skipped": "已跳過",
- "removed": "已移除",
- "scheduled": "已排程",
- "restarting": "重啟中",
- "up_for_retry": "等待重試",
- "up_for_reschedule": "等待重新排程",
- "upstream_failed": "上游任務失敗",
- "deferred": "已延後",
- "no_status": "無狀態"
- },
- "noAssetEvents": "未找到資源事件",
"sortBy": {
"newestFirst": "由新到舊",
"oldestFirst": "由舊到新"
},
"source": "來源",
"group": "群組",
- "uri": "Uri"
+ "uri": "Uri",
+ "noAssetEvents": "未找到資源事件"
}
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/HealthBadge.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/HealthBadge.tsx
index 861630ee78f50..458bdd7aead62 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/HealthBadge.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/HealthBadge.tsx
@@ -17,11 +17,11 @@
* under the License.
*/
import { Skeleton, Text } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { StateBadge } from "src/components/StateBadge";
import Time from "src/components/Time";
import { Tooltip } from "src/components/ui";
-import { capitalize } from "src/utils";
export const HealthBadge = ({
isLoading,
@@ -34,6 +34,8 @@ export const HealthBadge = ({
readonly status?: string | null;
readonly title: string;
}) => {
+ const { t: translate } = useTranslation("dashboard");
+
if (isLoading) {
return ;
}
@@ -44,9 +46,15 @@ export const HealthBadge = ({
- Status: {capitalize(status)}
- Last Heartbeat:
+ {translate("health.status")}
+ {": "}
+ {translate(`health.${status}`)}
+
+
+ {translate("health.lastHeartbeat")}
+ {": "}
+
}
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/HealthSection.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/HealthSection.tsx
deleted file mode 100644
index 455f4482935c1..0000000000000
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Health/HealthSection.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-/*!
- * 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.
- */
-import { Badge, Skeleton, Text } from "@chakra-ui/react";
-
-import { Tooltip } from "src/components/ui";
-
-export const HealthSection = ({
- isLoading,
- latestHeartbeat,
- status,
- title,
-}: {
- readonly isLoading: boolean;
- readonly latestHeartbeat?: string | null;
- readonly status?: string | null;
- readonly title: string;
-}) => {
- if (isLoading) {
- return ;
- }
-
- return (
-
- Status: {status}
- Last Heartbeat: {latestHeartbeat}
-
- }
- disabled={!Boolean(latestHeartbeat)}
- >
-
- {title}
-
-
- );
-};
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
index fe32a63371b1f..d3a8e6c5bba48 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx
@@ -36,7 +36,7 @@ type DagRunMetricsProps = {
const DAGRUN_STATES: Array = ["queued", "running", "success", "failed"];
export const DagRunMetrics = ({ dagRunStates, endDate, startDate, total }: DagRunMetricsProps) => {
- const { t: translate } = useTranslation("dashboard");
+ const { t: translate } = useTranslation();
return (
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
index 66ce62781481b..0057b2a152455 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx
@@ -43,7 +43,7 @@ export const MetricSection = ({ endDate, kind, runs, startDate, state, total }:
const remainingWidth = BAR_WIDTH - stateWidth;
const searchParams = new URLSearchParams(`?state=${state}&start_date=${startDate}`);
- const { t: translate } = useTranslation("dashboard");
+ const { t: translate } = useTranslation();
if (endDate !== undefined) {
searchParams.append("end_date", endDate);
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
index 698a21eaeddeb..172a0b488797f 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx
@@ -55,7 +55,7 @@ export const TaskInstanceMetrics = ({
taskInstanceStates,
total,
}: TaskInstanceMetricsProps) => {
- const { t: translate } = useTranslation("dashboard");
+ const { t: translate } = useTranslation();
return (
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx
index 6a4a8350953f6..c3662fb4c0704 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrors.tsx
@@ -17,18 +17,19 @@
* under the License.
*/
import { Box, Button, Skeleton, useDisclosure } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { LuFileWarning } from "react-icons/lu";
import { useImportErrorServiceGetImportErrors } from "openapi/queries/queries";
import { ErrorAlert } from "src/components/ErrorAlert";
import { StateBadge } from "src/components/StateBadge";
-import { pluralize } from "src/utils";
import { DAGImportErrorsModal } from "./DAGImportErrorsModal";
import { StatsCard } from "./StatsCard";
export const DAGImportErrors = ({ iconOnly = false }: { readonly iconOnly?: boolean }) => {
const { onClose, onOpen, open } = useDisclosure();
+ const { t: translate } = useTranslation("dashboard");
const { data, error, isLoading } = useImportErrorServiceGetImportErrors();
const importErrorsCount = data?.total_entries ?? 0;
@@ -51,7 +52,7 @@ export const DAGImportErrors = ({ iconOnly = false }: { readonly iconOnly?: bool
colorPalette="failed"
height={7}
onClick={onOpen}
- title={pluralize("Dag Import Error", importErrorsCount)}
+ title={translate("importErrors.dagImportError", { count: importErrorsCount })}
>
{importErrorsCount}
@@ -62,7 +63,7 @@ export const DAGImportErrors = ({ iconOnly = false }: { readonly iconOnly?: bool
count={importErrorsCount}
icon={}
isLoading={isLoading}
- label="Dag Import Errors"
+ label={translate("importErrors.dagImportError", { count: importErrorsCount })}
onClick={onOpen}
/>
)}
diff --git a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrorsModal.tsx b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrorsModal.tsx
index ef1a63aa2b27f..a738ba3d3feb2 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrorsModal.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/DAGImportErrorsModal.tsx
@@ -18,6 +18,7 @@
*/
import { Heading, Text, HStack } from "@chakra-ui/react";
import { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
import { LuFileWarning } from "react-icons/lu";
import { PiFilePy } from "react-icons/pi";
@@ -39,6 +40,7 @@ export const DAGImportErrorsModal: React.FC = ({ impor
const [page, setPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
const [filteredErrors, setFilteredErrors] = useState(importErrors);
+ const { t: translate } = useTranslation("dashboard");
const startRange = (page - 1) * PAGE_LIMIT;
const endRange = startRange + PAGE_LIMIT;
@@ -64,14 +66,14 @@ export const DAGImportErrorsModal: React.FC = ({ impor
- Dag Import Errors
+ {translate("importErrors.dagImportError", { count: importErrors.length })}
@@ -87,7 +89,9 @@ export const DAGImportErrorsModal: React.FC = ({ impor
- Timestamp:
+ {translate("importErrors.timestamp")}
+ {": "}
+
{importError.stack_trace}