This is an automated email from the ASF dual-hosted git repository.

jscheffl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 55a1ef0dab8 Translate Dag Details (#51493)
55a1ef0dab8 is described below

commit 55a1ef0dab81a51d4b6c0c5dd90238f6bd1960fb
Author: Brent Bovenzi <[email protected]>
AuthorDate: Sat Jun 7 05:25:43 2025 -0400

    Translate Dag Details (#51493)
    
    * Translate Dag Details
    
    * Update dagbreadcrumb and other layout components
---
 .../ui/src/components/ActionAccordion/columns.tsx  |   2 +-
 .../Clear/TaskInstance/ClearTaskInstanceButton.tsx |   8 +-
 .../airflow/ui/src/components/TrendCountButton.tsx |   4 +-
 airflow-core/src/airflow/ui/src/i18n/config.ts     |   2 +
 .../src/airflow/ui/src/i18n/locales/de/common.json |   1 +
 .../src/airflow/ui/src/i18n/locales/de/dags.json   |   2 -
 .../src/airflow/ui/src/i18n/locales/en/common.json |  29 +++++-
 .../src/airflow/ui/src/i18n/locales/en/dag.json    | 109 +++++++++++++++++++++
 .../src/airflow/ui/src/i18n/locales/en/dags.json   |   2 -
 .../src/airflow/ui/src/i18n/locales/pl/common.json |   1 +
 .../src/airflow/ui/src/i18n/locales/pl/dags.json   |   2 -
 .../airflow/ui/src/i18n/locales/zh-TW/common.json  |   1 +
 .../ui/src/i18n/locales/zh-TW/connections.json     |   4 +-
 .../airflow/ui/src/i18n/locales/zh-TW/dags.json    |   2 -
 .../ui/src/layouts/Details/DagBreadcrumb.tsx       |  30 ++++--
 .../ui/src/layouts/Details/DagRunSelect.tsx        |   7 +-
 .../ui/src/layouts/Details/DetailsLayout.tsx       |   4 +-
 .../ui/src/layouts/Details/Grid/DurationAxis.tsx   |   2 +-
 .../ui/src/layouts/Details/Grid/DurationTick.tsx   |  19 +++-
 .../airflow/ui/src/layouts/Details/Grid/Grid.tsx   |  12 ++-
 .../ui/src/layouts/Details/Grid/TaskNames.tsx      |   4 +-
 .../ui/src/layouts/Details/PanelButtons.tsx        |  41 ++++----
 .../ui/src/layouts/Details/ToggleGroups.tsx        |  13 ++-
 .../ui/src/pages/Dag/Backfills/Backfills.tsx       |  26 ++---
 .../src/airflow/ui/src/pages/Dag/Code/Code.tsx     |  18 ++--
 airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx  |  22 +++--
 .../src/airflow/ui/src/pages/Dag/Details.tsx       |  40 ++++----
 .../src/airflow/ui/src/pages/Dag/Header.tsx        |  18 ++--
 .../ui/src/pages/Dag/Overview/FailedLogs.tsx       |  10 +-
 .../airflow/ui/src/pages/Dag/Overview/Overview.tsx |   8 +-
 .../ui/src/pages/Dag/Overview/TaskLogPreview.tsx   |   6 +-
 .../airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx    |   8 +-
 .../src/airflow/ui/src/pages/Dag/Tasks/Tasks.tsx   |   5 +-
 .../ui/src/pages/TaskInstances/TaskInstances.tsx   |   2 +-
 34 files changed, 326 insertions(+), 138 deletions(-)

diff --git 
a/airflow-core/src/airflow/ui/src/components/ActionAccordion/columns.tsx 
b/airflow-core/src/airflow/ui/src/components/ActionAccordion/columns.tsx
index f432bff2f68..f4faf32aff4 100644
--- a/airflow-core/src/airflow/ui/src/components/ActionAccordion/columns.tsx
+++ b/airflow-core/src/airflow/ui/src/components/ActionAccordion/columns.tsx
@@ -39,7 +39,7 @@ export const getColumns = (translate: TFunction): 
Array<MetaColumn<TaskInstanceR
   },
   {
     accessorKey: "map_index",
-    header: 
translate("dags:runAndTaskActions.clear.dialog.affectedTasks.columns.mapIndex"),
+    header: translate("common:mapIndex"),
   },
   {
     accessorKey: "run_id",
diff --git 
a/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx
 
b/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx
index 369c3565583..e9a4bdc9885 100644
--- 
a/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx
+++ 
b/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx
@@ -35,7 +35,7 @@ type Props = {
 
 const ClearTaskInstanceButton = ({ isHotkeyEnabled = false, taskInstance, 
withText = true }: Props) => {
   const { onClose, onOpen, open } = useDisclosure();
-  const { t: translate } = useTranslation();
+  const { t: translate } = useTranslation("dags");
 
   useHotkeys(
     "shift+c",
@@ -48,16 +48,16 @@ const ClearTaskInstanceButton = ({ isHotkeyEnabled = false, 
taskInstance, withTe
   return (
     <Tooltip
       closeDelay={100}
-      content={translate("dags:runAndTaskActions.clear.buttonTooltip")}
+      content={translate("runAndTaskActions.clear.buttonTooltip")}
       disabled={!isHotkeyEnabled}
       openDelay={100}
     >
       <Box>
         <ActionButton
-          actionName={translate("dags:runAndTaskActions.clear.button", { type: 
"Task Instance" })}
+          actionName={translate("runAndTaskActions.clear.button", { type: 
"Task Instance" })}
           icon={<CgRedo />}
           onClick={onOpen}
-          text={translate("dags:runAndTaskActions.clear.button", { type: "Task 
Instance" })}
+          text={translate("runAndTaskActions.clear.button", { type: "Task 
Instance" })}
           withText={withText}
         />
 
diff --git a/airflow-core/src/airflow/ui/src/components/TrendCountButton.tsx 
b/airflow-core/src/airflow/ui/src/components/TrendCountButton.tsx
index 9880ab55f61..ecdfbf33e2b 100644
--- a/airflow-core/src/airflow/ui/src/components/TrendCountButton.tsx
+++ b/airflow-core/src/airflow/ui/src/components/TrendCountButton.tsx
@@ -19,8 +19,6 @@
 import { HStack, Badge, Text, Skeleton } from "@chakra-ui/react";
 import { Link, type To } from "react-router-dom";
 
-import { pluralize } from "src/utils";
-
 import { TrendCountChart, type ChartEvent } from "./TrendCountChart";
 
 type Props = {
@@ -53,7 +51,7 @@ export const TrendCountButton = ({
           {count}
         </Badge>
         <Text fontSize="sm" fontWeight="bold">
-          {pluralize(label, count, undefined, true)}
+          {label}
         </Text>
         <TrendCountChart endDate={endDate} events={events} 
startDate={startDate} />
       </HStack>
diff --git a/airflow-core/src/airflow/ui/src/i18n/config.ts 
b/airflow-core/src/airflow/ui/src/i18n/config.ts
index 810dc2110eb..9efa606af54 100644
--- a/airflow-core/src/airflow/ui/src/i18n/config.ts
+++ b/airflow-core/src/airflow/ui/src/i18n/config.ts
@@ -28,6 +28,7 @@ import deDashboard from "./locales/de/dashboard.json";
 import enCommon from "./locales/en/common.json";
 import enComponents from "./locales/en/components.json";
 import enConnections from "./locales/en/connections.json";
+import enDag from "./locales/en/dag.json";
 import enDags from "./locales/en/dags.json";
 import enDashboard from "./locales/en/dashboard.json";
 import koCommon from "./locales/ko/common.json";
@@ -71,6 +72,7 @@ const resources = {
     common: enCommon,
     components: enComponents,
     connections: enConnections,
+    dag: enDag,
     dags: enDags,
     dashboard: enDashboard,
   },
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/de/common.json 
b/airflow-core/src/airflow/ui/src/i18n/locales/de/common.json
index 864ee3d436d..c285cf927ba 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/de/common.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/de/common.json
@@ -35,6 +35,7 @@
   "logicalDate": "Logisches Datum",
   "logout": "Abmelden",
   "logoutConfirmation": "Sie sind dabei sich von dem System abzumelden.",
+  "mapIndex": "Planungs-Index",
   "modal": {
     "cancel": "Abbrechen",
     "confirm": "Bestätigen",
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/de/dags.json 
b/airflow-core/src/airflow/ui/src/i18n/locales/de/dags.json
index e8b9fc51aac..f66ec55dc99 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/de/dags.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/de/dags.json
@@ -37,7 +37,6 @@
       "dialog": {
         "affectedTasks": {
           "columns": {
-            "mapIndex": "Planungs-Index",
             "runId": "Lauf ID",
             "state": "Status",
             "taskId": "Task ID"
@@ -135,7 +134,6 @@
       "dagVersion": "Dag Version",
       "duration": "Laufzeit",
       "endDate": "Enddatum",
-      "mapIndex": "Planungs-Index",
       "operator": "Operator-Klasse",
       "pool": "Pool",
       "startDate": "Startdatum",
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 80272396994..de6d102b326 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
@@ -9,6 +9,8 @@
   },
   "assetEvent_one": "Asset Event",
   "assetEvent_other": "Asset Events",
+  "backfill_one": "Backfill",
+  "backfill_other": "Backfills",
   "browse": {
     "auditLog": "Audit Log",
     "xcoms": "XComs"
@@ -17,6 +19,7 @@
   "dag_other": "Dags",
   "dagRun_one": "Dag Run",
   "dagRun_other": "Dag Runs",
+  "dagWarnings": "Dag warnings/errors",
   "defaultToGraphView": "Default to graph view",
   "defaultToGridView": "Default to grid view",
   "direction": "Direction",
@@ -25,7 +28,10 @@
     "githubRepo": "GitHub Repo",
     "restApiReference": "REST API Reference"
   },
-  "duration": "Duration",
+  "duration": {
+    "label": "Duration",
+    "seconds": "{{count}}s"
+  },
   "endDate": "End Date",
   "expression": {
     "all": "All",
@@ -36,6 +42,7 @@
   "logicalDate": "Logical Date",
   "logout": "Logout",
   "logoutConfirmation": "You are about to logout from the application.",
+  "mapIndex": "Map Index",
   "modal": {
     "cancel": "Cancel",
     "confirm": "Confirm",
@@ -55,6 +62,7 @@
     "security": "Security"
   },
   "noItemsFound": "No {{modelName}} found",
+  "operator": "Operator",
   "pools": {
     "deferred": "Deferred",
     "open": "Open",
@@ -100,17 +108,26 @@
   "switchToDarkMode": "Switch to Dark Mode",
   "switchToLightMode": "Switch to Light Mode",
   "table": {
+    "completedAt": "Completed at",
+    "createdAt": "Created at",
+    "duration": "Duration",
     "filterByTag": "Filter Dags by tag",
     "filterColumns": "Filter table columns",
     "filterReset_one": "Reset filter",
     "filterReset_other": "Reset filters",
+    "from": "From",
+    "maxActiveRuns": "Max Active Runs",
     "noTagsFound": "No tags found",
+    "reprocessBehavior": "Reprocess Behavior",
     "tagMode": {
       "all": "All",
       "any": "Any"
     },
-    "tagPlaceholder": "Filter by tag"
+    "tagPlaceholder": "Filter by tag",
+    "to": "To"
   },
+  "task_one": "Task",
+  "task_other": "Tasks",
   "taskInstance_one": "Task Instance",
   "taskInstance_other": "Task Instances",
   "timeRange": {
@@ -128,6 +145,12 @@
     "utc": "UTC (Coordinated Universal Time)"
   },
   "triggered": "Triggered",
+  "triggerRule": "Trigger Rule",
   "tryNumber": "Try Number",
-  "user": "User"
+  "user": "User",
+  "wrap": {
+    "tooltip": "Press w to toggle wrap",
+    "unwrap": "Unwrap",
+    "wrap": "Wrap"
+  }
 }
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/en/dag.json 
b/airflow-core/src/airflow/ui/src/i18n/locales/en/dag.json
new file mode 100644
index 00000000000..f6e5dd3eaa6
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/dag.json
@@ -0,0 +1,109 @@
+{
+  "allRuns": "All Runs",
+  "code": {
+    "bundleUrl": "Bundle Url",
+    "bundleVersion": "Bundle Version:",
+    "noCode": "No Code Found",
+    "parsedAt": "Parsed at:"
+  },
+  "details": {
+    "fields": {
+      "bundleVersion": "Bundle Version",
+      "catchup": "Catchup",
+      "concurrency": "Concurrency",
+      "dagId": "Dag ID",
+      "dagRunTimeout": "Dag Run Timeout",
+      "defaultArgs": "Default Args",
+      "description": "Description",
+      "endDate": "End Date",
+      "fileLocation": "File Location",
+      "hasTaskConcurrencyLimits": "Has Task Concurrency Limits",
+      "lastExpired": "Last Expired",
+      "lastParsed": "Last Parsed",
+      "latestDagVersion": "Latest Dag Version",
+      "maxActiveRuns": "Max Active Runs",
+      "maxActiveTasks": "Max Active Tasks",
+      "maxConsecutiveFailedDagRuns": "Max Consecutive Failed Dag Runs",
+      "params": "Params",
+      "startDate": "Start Date",
+      "timezone": "Timezone"
+    }
+  },
+  "grid": {
+    "buttons": {
+      "resetToLatest": "Reset to latest",
+      "toggleGroup": "Toggle group"
+    }
+  },
+  "header": {
+    "buttons": {
+      "dagDocs": "Dag Docs"
+    },
+    "modals": {
+      "docTitle": "Dag Documentation"
+    },
+    "stats": {
+      "latestDagVersion": "Latest Dag Version",
+      "latestRun": "Latest Run",
+      "nextRun": "Next Run",
+      "owner": "Owner",
+      "schedule": "Schedule",
+      "tags": "Tags"
+    }
+  },
+  "overview": {
+    "buttons": {
+      "failedRun_one": "Failed Run",
+      "failedRun_other": "Failed Runs",
+      "failedTask_one": "Failed Task",
+      "failedTask_other": "Failed Tasks"
+    },
+    "charts": {
+      "assetEvent": "Created Asset Event"
+    },
+    "failedLogs": {
+      "title": "Recent Failed Task Logs",
+      "viewFullLogs": "View full logs"
+    }
+  },
+  "panel": {
+    "buttons": {
+      "options": "Options",
+      "showGraph": "Show Graph",
+      "showGrid": "Show Grid"
+    },
+    "dagRuns": {
+      "label": "Number of Dag Runs"
+    },
+    "dependencies": {
+      "label": "Dependencies",
+      "options": {
+        "allDagDependencies": "All Dag Dependencies",
+        "externalConditions": "External conditions",
+        "onlyTasks": "Only tasks"
+      },
+      "placeholder": "Dependencies"
+    },
+    "graphDirection": {
+      "label": "Graph Direction"
+    }
+  },
+  "tabs": {
+    "backfills": "Backfills",
+    "code": "Code",
+    "details": "Details",
+    "events": "Events",
+    "overview": "Overview",
+    "runs": "Runs",
+    "tasks": "Tasks"
+  },
+  "task": {
+    "card": {
+      "lastInstance": "Last Instance"
+    }
+  },
+  "taskGroups": {
+    "collapseAll": "Collapse all task groups",
+    "expandAll": "Expand all task groups"
+  }
+}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/en/dags.json 
b/airflow-core/src/airflow/ui/src/i18n/locales/en/dags.json
index 628cddbc56d..f2ae3f7812c 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/en/dags.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/dags.json
@@ -37,7 +37,6 @@
       "dialog": {
         "affectedTasks": {
           "columns": {
-            "mapIndex": "Map Index",
             "runId": "Run ID",
             "state": "State",
             "taskId": "Task ID"
@@ -135,7 +134,6 @@
       "dagVersion": "Dag Version",
       "duration": "Duration",
       "endDate": "End Date",
-      "mapIndex": "Map Index",
       "operator": "Operator",
       "pool": "Pool",
       "startDate": "Start Date",
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/pl/common.json 
b/airflow-core/src/airflow/ui/src/i18n/locales/pl/common.json
index dfa748ac28d..66a4eab3ef5 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/pl/common.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/pl/common.json
@@ -35,6 +35,7 @@
   "logicalDate": "Data logiczna",
   "logout": "Wyloguj",
   "logoutConfirmation": "Zamierzasz wylogować się z aplikacji.",
+  "mapIndex": "Indeks mapowania",
   "modal": {
     "cancel": "Anuluj",
     "confirm": "Potwierdź",
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/pl/dags.json 
b/airflow-core/src/airflow/ui/src/i18n/locales/pl/dags.json
index 6e728d8b355..60386fc0ae4 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/pl/dags.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/pl/dags.json
@@ -37,7 +37,6 @@
       "dialog": {
         "affectedTasks": {
           "columns": {
-            "mapIndex": "Index zmapowania",
             "runId": "Identifikator Wykonania",
             "state": "Stan",
             "taskId": "Identifikator Zadania"
@@ -135,7 +134,6 @@
       "dagVersion": "Wersja Daga",
       "duration": "Czas trwania",
       "endDate": "Data zakończenia",
-      "mapIndex": "Indeks mapowania",
       "operator": "Operator",
       "pool": "Pula",
       "startDate": "Data rozpoczęcia",
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 9ab309d94fe..af2be17a809 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
@@ -27,6 +27,7 @@
   },
   "logout": "登出",
   "logoutConfirmation": "確定要登出嗎?",
+  "mapIndex": "映射索引",
   "modal": {
     "cancel": "取消",
     "confirm": "確認",
diff --git 
a/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/connections.json 
b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/connections.json
index 1a1c49b9ba5..a8a6257cb43 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/connections.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/connections.json
@@ -1,4 +1,4 @@
 {
-    "test": "測試連線",
-    "testDisabled": "測試連線功能已停用。請聯繫管理員以啟用。"
+  "test": "測試連線",
+  "testDisabled": "測試連線功能已停用。請聯繫管理員以啟用。"
 }
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/dags.json 
b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/dags.json
index f16d44472ec..a11dc98fee8 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/dags.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/dags.json
@@ -37,7 +37,6 @@
       "dialog": {
         "affectedTasks": {
           "columns": {
-            "mapIndex": "映射索引",
             "runId": "執行 ID",
             "state": "狀態",
             "taskId": "任務 ID"
@@ -134,7 +133,6 @@
       "dagVersion": "Dag 版本",
       "duration": "持續時間",
       "endDate": "結束日期",
-      "mapIndex": "映射索引",
       "operator": "運算子",
       "pool": "資源池",
       "startDate": "開始日期",
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx
index 6e175b9b28e..b9d144944b6 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DagBreadcrumb.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 import type { ReactNode } from "react";
+import { useTranslation } from "react-i18next";
 import { useParams } from "react-router-dom";
 
 import {
@@ -32,6 +33,7 @@ import { TogglePause } from "src/components/TogglePause";
 import { isStatePending, useAutoRefresh } from "src/utils";
 
 export const DagBreadcrumb = () => {
+  const { t: translate } = useTranslation();
   const { dagId = "", groupId, mapIndex = "-1", runId, taskId } = useParams();
   const refetchInterval = useAutoRefresh({ dagId });
 
@@ -71,7 +73,7 @@ export const DagBreadcrumb = () => {
             skipConfirm
           />
         ),
-        title: "Dag",
+        title: translate("dag_one"),
         value: `/dags/${dagId}`,
       },
     ];
@@ -81,7 +83,7 @@ export const DagBreadcrumb = () => {
     links.push({
       label: dagRun === undefined ? runId : <Time datetime={dagRun.run_after} 
/>,
       labelExtra: dagRun === undefined ? undefined : <StateBadge fontSize="xs" 
state={dagRun.state} />,
-      title: "Dag Run",
+      title: translate("dagRun_one"),
       value: `/dags/${dagId}/runs/${runId}`,
     });
   }
@@ -90,8 +92,8 @@ export const DagBreadcrumb = () => {
   if (groupId !== undefined) {
     if (runId === undefined) {
       links.push({
-        label: "All Runs",
-        title: "Dag Run",
+        label: translate("allRuns", { ns: "dag" }),
+        title: translate("dagRun_one"),
         value: `/dags/${dagId}/runs`,
       });
     }
@@ -108,24 +110,34 @@ export const DagBreadcrumb = () => {
     if (task?.is_mapped) {
       links.push({
         label: `${task.task_display_name ?? taskId} [ ]`,
-        title: "Task",
+        title: translate("task_one"),
         value: `/dags/${dagId}/runs/${runId}/tasks/${taskId}/mapped`,
       });
     } else {
       links.push({
         label: task?.task_display_name ?? taskId,
-        title: "Task",
+        title: translate("task_one"),
       });
     }
   }
 
   if (runId === undefined && taskId !== undefined) {
-    links.push({ label: "All Runs", title: "Dag Run", value: 
`/dags/${dagId}/runs` });
-    links.push({ label: task?.task_display_name ?? taskId, title: "Task" });
+    links.push({
+      label: translate("allRuns", { ns: "dag" }),
+      title: translate("dagRun_one"),
+      value: `/dags/${dagId}/runs`,
+    });
+    links.push({
+      label: task?.task_display_name ?? taskId,
+      title: translate("task_one"),
+    });
   }
 
   if (mapIndex !== "-1") {
-    links.push({ label: mappedTaskInstance?.rendered_map_index ?? mapIndex, 
title: "Map Index" });
+    links.push({
+      label: mappedTaskInstance?.rendered_map_index ?? mapIndex,
+      title: translate("mapIndex"),
+    });
   }
 
   return <BreadcrumbStats links={links} />;
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/DagRunSelect.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/DagRunSelect.tsx
index 431c94b108b..f73d9c3d49a 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DagRunSelect.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DagRunSelect.tsx
@@ -18,6 +18,7 @@
  */
 import { createListCollection, type SelectValueChangeDetails, Select } from 
"@chakra-ui/react";
 import { forwardRef, useMemo } from "react";
+import { useTranslation } from "react-i18next";
 import { useNavigate, useParams } from "react-router-dom";
 
 import type { GridDAGRunwithTIs } from "openapi/requests/types.gen";
@@ -36,7 +37,7 @@ type DagRunSelectProps = {
 
 export const DagRunSelect = forwardRef<HTMLDivElement, DagRunSelectProps>(({ 
limit }, ref) => {
   const { dagId = "", runId, taskId } = useParams();
-
+  const { t: translate } = useTranslation(["dag", "common"]);
   const navigate = useNavigate();
 
   const { data, isLoading } = useGrid(limit);
@@ -73,10 +74,10 @@ export const DagRunSelect = forwardRef<HTMLDivElement, 
DagRunSelectProps>(({ lim
       value={runId === undefined ? [] : [runId]}
       width="250px"
     >
-      <Select.Label fontSize="xs">Dag Run</Select.Label>
+      <Select.Label 
fontSize="xs">{translate("common:dagRun_one")}</Select.Label>
       <Select.Control>
         <Select.Trigger>
-          <Select.ValueText placeholder="All Runs">
+          <Select.ValueText placeholder={translate("dag:allRuns")}>
             {selectedRun ? (
               <>
                 <Time datetime={selectedRun.run_after} />
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
index bae001db6f3..5ff7ed51ff6 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -20,6 +20,7 @@ import { Box, HStack, Flex, useDisclosure } from 
"@chakra-ui/react";
 import { useReactFlow } from "@xyflow/react";
 import { useRef } from "react";
 import type { PropsWithChildren, ReactNode } from "react";
+import { useTranslation } from "react-i18next";
 import { LuFileWarning } from "react-icons/lu";
 import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
 import { Outlet, useParams } from "react-router-dom";
@@ -50,6 +51,7 @@ type Props = {
 } & PropsWithChildren;
 
 export const DetailsLayout = ({ children, error, isLoading, tabs }: Props) => {
+  const { t: translate } = useTranslation();
   const { dagId = "" } = useParams();
 
   const { data: dag } = useDagServiceGetDag({ dagId });
@@ -108,7 +110,7 @@ export const DetailsLayout = ({ children, error, isLoading, 
tabs }: Props) => {
               {Boolean(error) || (warningData?.dag_warnings.length ?? 0) > 0 ? 
(
                 <>
                   <ActionButton
-                    actionName="Dag warnings/errors"
+                    actionName={translate("common:dagWarnings")}
                     colorPalette={Boolean(error) ? "red" : "orange"}
                     icon={<LuFileWarning />}
                     margin="2"
diff --git 
a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/DurationAxis.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/DurationAxis.tsx
index 8c027eec05e..0c798ab843a 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/DurationAxis.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/DurationAxis.tsx
@@ -19,5 +19,5 @@
 import { Box, type BoxProps } from "@chakra-ui/react";
 
 export const DurationAxis = (props: BoxProps) => (
-  <Box borderBottomWidth={1} left="30px" position="absolute" right="25px" 
zIndex={0} {...props} />
+  <Box borderBottomWidth={1} left={0} position="absolute" right={0} zIndex={0} 
{...props} />
 );
diff --git 
a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/DurationTick.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/DurationTick.tsx
index fcb785c91f8..24958b706aa 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/DurationTick.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/DurationTick.tsx
@@ -17,9 +17,18 @@
  * under the License.
  */
 import { Text, type TextProps } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
 
-export const DurationTick = ({ children, ...rest }: TextProps) => (
-  <Text color="border.emphasized" fontSize="xs" position="absolute" right={1} 
whiteSpace="nowrap" {...rest}>
-    {children}
-  </Text>
-);
+type Props = {
+  readonly duration: number;
+} & TextProps;
+
+export const DurationTick = ({ duration, ...rest }: Props) => {
+  const { t: translate } = useTranslation();
+
+  return (
+    <Text color="border.emphasized" fontSize="xs" position="absolute" 
right={1} whiteSpace="nowrap" {...rest}>
+      {translate("duration.seconds", { count: Math.floor(duration) })}
+    </Text>
+  );
+};
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
index 75fe7209dc8..5a407c355e4 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/Grid.tsx
@@ -20,6 +20,7 @@ import { Box, Flex, IconButton } from "@chakra-ui/react";
 import dayjs from "dayjs";
 import dayjsDuration from "dayjs/plugin/duration";
 import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
 import { FiChevronsRight } from "react-icons/fi";
 import { Link, useParams } from "react-router-dom";
 
@@ -39,6 +40,7 @@ type Props = {
 };
 
 export const Grid = ({ limit }: Props) => {
+  const { t: translate } = useTranslation("dag");
   const { openGroupIds } = useOpenGroups();
   const { dagId = "" } = useParams();
 
@@ -83,9 +85,9 @@ export const Grid = ({ limit }: Props) => {
           <Flex flexDirection="column-reverse" height="100px" 
position="relative" width="100%">
             {Boolean(runs.length) && (
               <>
-                <DurationTick bottom="92px">{Math.floor(max)}s</DurationTick>
-                <DurationTick bottom="46px">{Math.floor(max / 
2)}s</DurationTick>
-                <DurationTick bottom="-4px">0s</DurationTick>
+                <DurationTick bottom="92px" duration={max} />
+                <DurationTick bottom="46px" duration={max / 2} />
+                <DurationTick bottom="-4px" duration={0} />
               </>
             )}
           </Flex>
@@ -97,12 +99,12 @@ export const Grid = ({ limit }: Props) => {
           {runAfter === undefined ? undefined : (
             <Link to={`/dags/${dagId}`}>
               <IconButton
-                aria-label="Reset to latest"
+                aria-label={translate("grid.buttons.resetToLatest")}
                 height="98px"
                 loading={isLoading}
                 minW={0}
                 ml={1}
-                title="Reset to latest"
+                title={translate("grid.buttons.resetToLatest")}
                 variant="surface"
                 zIndex={1}
               >
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/TaskNames.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/TaskNames.tsx
index 9792b1f4c54..014ac8f5fd4 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/TaskNames.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/TaskNames.tsx
@@ -18,6 +18,7 @@
  */
 import { Box, chakra, Flex, Link } from "@chakra-ui/react";
 import type { MouseEvent } from "react";
+import { useTranslation } from "react-i18next";
 import { FiChevronUp } from "react-icons/fi";
 import { Link as RouterLink, useParams, useSearchParams } from 
"react-router-dom";
 
@@ -48,6 +49,7 @@ const onMouseLeave = (event: MouseEvent<HTMLDivElement>) => {
 };
 
 export const TaskNames = ({ nodes }: Props) => {
+  const { t: translate } = useTranslation("dag");
   const { toggleGroupId } = useOpenGroups();
   const { dagId = "", groupId, taskId } = useParams();
   const [searchParams] = useSearchParams();
@@ -86,7 +88,7 @@ export const TaskNames = ({ nodes }: Props) => {
             </RouterLink>
           </Link>
           <chakra.button
-            aria-label="Toggle group"
+            aria-label={translate("grid.buttons.toggleGroup")}
             display="inline"
             height="20px"
             ml={1}
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
index db5cb9dbf60..52dbd974dab 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
@@ -48,13 +48,14 @@ type Props = {
   readonly setLimit: React.Dispatch<React.SetStateAction<number>>;
 };
 
-const options = createListCollection({
-  items: [
-    { label: "Only tasks", value: "tasks" },
-    { label: "External conditions", value: "immediate" },
-    { label: "All Dag Dependencies", value: "all" },
-  ],
-});
+const getOptions = (translate: (key: string) => string) =>
+  createListCollection({
+    items: [
+      { label: translate("dag:panel.dependencies.options.onlyTasks"), value: 
"tasks" },
+      { label: translate("dag:panel.dependencies.options.externalConditions"), 
value: "immediate" },
+      { label: translate("dag:panel.dependencies.options.allDagDependencies"), 
value: "all" },
+    ],
+  });
 
 const displayRunOptions = createListCollection({
   items: [
@@ -72,7 +73,7 @@ const deps = ["all", "immediate", "tasks"];
 type Dependency = (typeof deps)[number];
 
 export const PanelButtons = ({ dagView, limit, panelGroupRef, setDagView, 
setLimit }: Props) => {
-  const { t: translate } = useTranslation("components");
+  const { t: translate } = useTranslation(["components", "dag"]);
   const { dagId = "" } = useParams();
   const { fitView } = useReactFlow();
   const [dependencies, setDependencies, removeDependencies] = 
useLocalStorage<Dependency>(
@@ -122,7 +123,7 @@ export const PanelButtons = ({ dagView, limit, 
panelGroupRef, setDagView, setLim
     <Flex justifyContent="space-between" position="absolute" top={1} 
width="100%" zIndex={1}>
       <ButtonGroup attached size="sm" variant="outline">
         <IconButton
-          aria-label="Show Grid"
+          aria-label={translate("dag:panel.buttons.showGrid")}
           colorPalette="blue"
           onClick={() => {
             setDagView("grid");
@@ -130,13 +131,13 @@ export const PanelButtons = ({ dagView, limit, 
panelGroupRef, setDagView, setLim
               handleFocus("grid");
             }
           }}
-          title="Show Grid"
+          title={translate("dag:panel.buttons.showGrid")}
           variant={dagView === "grid" ? "solid" : "outline"}
         >
           <FiGrid />
         </IconButton>
         <IconButton
-          aria-label="Show Graph"
+          aria-label={translate("dag:panel.buttons.showGraph")}
           colorPalette="blue"
           onClick={() => {
             setDagView("graph");
@@ -144,7 +145,7 @@ export const PanelButtons = ({ dagView, limit, 
panelGroupRef, setDagView, setLim
               handleFocus("graph");
             }
           }}
-          title="Show Graph"
+          title={translate("dag:panel.buttons.showGraph")}
           variant={dagView === "graph" ? "solid" : "outline"}
         >
           <MdOutlineAccountTree />
@@ -156,7 +157,7 @@ export const PanelButtons = ({ dagView, limit, 
panelGroupRef, setDagView, setLim
         <Popover.Root autoFocus={false} positioning={{ placement: "bottom-end" 
}}>
           <Popover.Trigger asChild>
             <Button size="sm" variant="outline">
-              Options
+              {translate("dag:panel.buttons.options")}
               <FiChevronDown size="0.5rem" />
             </Button>
           </Popover.Trigger>
@@ -171,16 +172,16 @@ export const PanelButtons = ({ dagView, limit, 
panelGroupRef, setDagView, setLim
                       <DagRunSelect limit={limit} />
                       <Select.Root
                         // @ts-expect-error The expected option type is 
incorrect
-                        collection={options}
+                        collection={getOptions(translate)}
                         data-testid="dependencies"
                         onValueChange={handleDepsChange}
                         size="sm"
                         value={[dependencies]}
                       >
-                        <Select.Label fontSize="xs">Dependencies</Select.Label>
+                        <Select.Label 
fontSize="xs">{translate("dag:panel.dependencies.label")}</Select.Label>
                         <Select.Control>
                           <Select.Trigger>
-                            <Select.ValueText placeholder="Dependencies" />
+                            <Select.ValueText 
placeholder={translate("dag:panel.dependencies.label")} />
                           </Select.Trigger>
                           <Select.IndicatorGroup>
                             <Select.Indicator />
@@ -188,7 +189,7 @@ export const PanelButtons = ({ dagView, limit, 
panelGroupRef, setDagView, setLim
                         </Select.Control>
                         <Select.Positioner>
                           <Select.Content>
-                            {options.items.map((option) => (
+                            {getOptions(translate).items.map((option) => (
                               <Select.Item item={option} key={option.value}>
                                 {option.label}
                               </Select.Item>
@@ -203,7 +204,9 @@ export const PanelButtons = ({ dagView, limit, 
panelGroupRef, setDagView, setLim
                         size="sm"
                         value={[direction]}
                       >
-                        <Select.Label fontSize="xs">Graph 
Direction</Select.Label>
+                        <Select.Label fontSize="xs">
+                          {translate("dag:panel.graphDirection.label")}
+                        </Select.Label>
                         <Select.Control>
                           <Select.Trigger>
                             <Select.ValueText />
@@ -232,7 +235,7 @@ export const PanelButtons = ({ dagView, limit, 
panelGroupRef, setDagView, setLim
                       size="sm"
                       value={[limit.toString()]}
                     >
-                      <Select.Label>Number of Dag Runs</Select.Label>
+                      
<Select.Label>{translate("dag:panel.dagRuns.label")}</Select.Label>
                       <Select.Control>
                         <Select.Trigger>
                           <Select.ValueText />
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/ToggleGroups.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/ToggleGroups.tsx
index 8b2ebcefe2d..a412a99af2e 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/ToggleGroups.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/ToggleGroups.tsx
@@ -18,6 +18,7 @@
  */
 import { type ButtonGroupProps, IconButton, ButtonGroup } from 
"@chakra-ui/react";
 import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
 import { MdExpand, MdCompress } from "react-icons/md";
 import { useParams } from "react-router-dom";
 
@@ -27,6 +28,7 @@ import { useOpenGroups } from "src/context/openGroups";
 import { flattenNodes } from "./Grid/utils";
 
 export const ToggleGroups = (props: ButtonGroupProps) => {
+  const { t: translate } = useTranslation();
   const { dagId = "" } = useParams();
   const { data: structure } = useStructureServiceStructureData({
     dagId,
@@ -54,24 +56,27 @@ export const ToggleGroups = (props: ButtonGroupProps) => {
     setOpenGroupIds([]);
   };
 
+  const expandLabel = translate("dag:taskGroups.expandAll");
+  const collapseLabel = translate("dag:taskGroups.collapseAll");
+
   return (
     <ButtonGroup attached size="sm" variant="surface" {...props}>
       <IconButton
-        aria-label="Expand all task groups"
+        aria-label={expandLabel}
         disabled={isExpandDisabled}
         onClick={onExpand}
         size="sm"
-        title="Expand all task groups"
+        title={expandLabel}
         variant="surface"
       >
         <MdExpand />
       </IconButton>
       <IconButton
-        aria-label="Collapse all task groups"
+        aria-label={collapseLabel}
         disabled={isCollapseDisabled}
         onClick={onCollapse}
         size="sm"
-        title="Collapse all task groups"
+        title={collapseLabel}
         variant="surface"
       >
         <MdCompress />
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx
index f85e05676ab..9b9ec34ab0d 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx
@@ -18,6 +18,7 @@
  */
 import { Box, Heading, Text } from "@chakra-ui/react";
 import type { ColumnDef } from "@tanstack/react-table";
+import { useTranslation } from "react-i18next";
 import { useParams } from "react-router-dom";
 
 import { useBackfillServiceListBackfillsUi } from "openapi/queries";
@@ -27,9 +28,9 @@ import { useTableURLState } from 
"src/components/DataTable/useTableUrlState";
 import { ErrorAlert } from "src/components/ErrorAlert";
 import Time from "src/components/Time";
 import { reprocessBehaviors } from "src/constants/reprocessBehaviourParams";
-import { getDuration, pluralize } from "src/utils";
+import { getDuration } from "src/utils";
 
-const columns: Array<ColumnDef<BackfillResponse>> = [
+const getColumns = (translate: (key: string) => string): 
Array<ColumnDef<BackfillResponse>> => [
   {
     accessorKey: "date_from",
     cell: ({ row }) => (
@@ -38,7 +39,7 @@ const columns: Array<ColumnDef<BackfillResponse>> = [
       </Text>
     ),
     enableSorting: false,
-    header: "From",
+    header: translate("table.from"),
   },
   {
     accessorKey: "date_to",
@@ -48,7 +49,7 @@ const columns: Array<ColumnDef<BackfillResponse>> = [
       </Text>
     ),
     enableSorting: false,
-    header: "To",
+    header: translate("table.to"),
   },
   {
     accessorKey: "reprocess_behavior",
@@ -61,7 +62,7 @@ const columns: Array<ColumnDef<BackfillResponse>> = [
       </Text>
     ),
     enableSorting: false,
-    header: "Reprocess Behavior",
+    header: translate("table.reprocessBehavior"),
   },
   {
     accessorKey: "created_at",
@@ -71,7 +72,7 @@ const columns: Array<ColumnDef<BackfillResponse>> = [
       </Text>
     ),
     enableSorting: false,
-    header: "Created at",
+    header: translate("table.createdAt"),
   },
   {
     accessorKey: "completed_at",
@@ -81,7 +82,7 @@ const columns: Array<ColumnDef<BackfillResponse>> = [
       </Text>
     ),
     enableSorting: false,
-    header: "Completed at",
+    header: translate("table.completedAt"),
   },
   {
     accessorKey: "duration",
@@ -93,16 +94,17 @@ const columns: Array<ColumnDef<BackfillResponse>> = [
       </Text>
     ),
     enableSorting: false,
-    header: "Duration",
+    header: translate("table.duration"),
   },
   {
     accessorKey: "max_active_runs",
     enableSorting: false,
-    header: "Max Active Runs",
+    header: translate("table.maxActiveRuns"),
   },
 ];
 
 export const Backfills = () => {
+  const { t: translate } = useTranslation();
   const { setTableURLState, tableURLState } = useTableURLState();
 
   const { pagination } = tableURLState;
@@ -119,14 +121,14 @@ export const Backfills = () => {
     <Box>
       <ErrorAlert error={error} />
       <Heading my={1} size="md">
-        {pluralize("Backfill", data ? data.total_entries : 0)}
+        {translate("backfill", { count: data ? data.total_entries : 0 })}
       </Heading>
       <DataTable
-        columns={columns}
+        columns={getColumns(translate)}
         data={data ? data.backfills : []}
         isFetching={isFetching}
         isLoading={isLoading}
-        modelName="Backfill"
+        modelName={translate("backfill_one")}
         onStateChange={setTableURLState}
         total={data ? data.total_entries : 0}
       />
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx
index fef86924492..287609d416b 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx
@@ -19,6 +19,7 @@
 import { Box, Button, Heading, HStack, Link } from "@chakra-ui/react";
 import { useState } from "react";
 import { useHotkeys } from "react-hotkeys-hook";
+import { useTranslation } from "react-i18next";
 import { useParams } from "react-router-dom";
 import { createElement, PrismLight as SyntaxHighlighter } from 
"react-syntax-highlighter";
 import python from "react-syntax-highlighter/dist/esm/languages/prism/python";
@@ -43,6 +44,7 @@ import { useConfig } from "src/queries/useConfig";
 SyntaxHighlighter.registerLanguage("python", python);
 
 export const Code = () => {
+  const { t: translate } = useTranslation(["dag", "common"]);
   const { dagId } = useParams();
 
   const selectedVersion = useSelectedVersion();
@@ -95,7 +97,7 @@ export const Code = () => {
         <HStack gap={5}>
           {dag?.last_parsed_time !== undefined && (
             <Heading as="h4" fontSize="14px" size="md">
-              Parsed at: <Time datetime={dag.last_parsed_time} />
+              {translate("code.parsedAt")} <Time 
datetime={dag.last_parsed_time} />
             </Heading>
           )}
 
@@ -103,12 +105,12 @@ export const Code = () => {
             // eslint-disable-next-line 
@typescript-eslint/prefer-optional-chain
             dagVersion !== undefined && dagVersion.bundle_version !== null ? (
               <Heading as="h4" fontSize="14px" size="md" 
wordBreak="break-word">
-                Bundle Version:{" "}
+                {translate("code.bundleVersion")}{" "}
                 {dagVersion.bundle_url === null ? (
                   dagVersion.bundle_version
                 ) : (
                   <Link
-                    aria-label="Bundle Url"
+                    aria-label={translate("code.bundleUrl")}
                     color="fg.info"
                     href={dagVersion.bundle_url}
                     rel="noopener noreferrer"
@@ -126,14 +128,14 @@ export const Code = () => {
           <ClipboardRoot value={code?.content ?? ""}>
             <ClipboardButton />
           </ClipboardRoot>
-          <Tooltip closeDelay={100} content="Press w to toggle wrap" 
openDelay={100}>
+          <Tooltip closeDelay={100} content={translate("common:wrap.tooltip")} 
openDelay={100}>
             <Button
-              aria-label={wrap ? "Unwrap" : "Wrap"}
+              aria-label={translate(`common:wrap.${wrap ? "un" : ""}wrap`)}
               bg="bg.panel"
               onClick={toggleWrap}
               variant="outline"
             >
-              {wrap ? "Unwrap" : "Wrap"}
+              {translate(`common:wrap.${wrap ? "un" : ""}wrap`)}
             </Button>
           </Tooltip>
         </HStack>
@@ -190,7 +192,9 @@ export const Code = () => {
           style={style}
           wrapLongLines={wrap}
         >
-          {codeError?.status === 404 && !Boolean(code?.content) ? "No Code 
Found" : (code?.content ?? "")}
+          {codeError?.status === 404 && !Boolean(code?.content)
+            ? translate("code.noCode")
+            : (code?.content ?? "")}
         </SyntaxHighlighter>
       </Box>
     </Box>
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
index 573ec06c121..5190a357853 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
@@ -18,6 +18,7 @@
  */
 import { ReactFlowProvider } from "@xyflow/react";
 import { useState } from "react";
+import { useTranslation } from "react-i18next";
 import { FiBarChart, FiCode } from "react-icons/fi";
 import { LuChartColumn } from "react-icons/lu";
 import { MdDetails, MdOutlineEventNote } from "react-icons/md";
@@ -33,17 +34,18 @@ import { isStatePending, useAutoRefresh } from "src/utils";
 
 import { Header } from "./Header";
 
-const tabs = [
-  { icon: <LuChartColumn />, label: "Overview", value: "" },
-  { icon: <FiBarChart />, label: "Runs", value: "runs" },
-  { icon: <TaskIcon />, label: "Tasks", value: "tasks" },
-  { icon: <RiArrowGoBackFill />, label: "Backfills", value: "backfills" },
-  { icon: <MdOutlineEventNote />, label: "Events", value: "events" },
-  { icon: <FiCode />, label: "Code", value: "code" },
-  { icon: <MdDetails />, label: "Details", value: "details" },
+const getTabs = (translate: (key: string) => string) => [
+  { icon: <LuChartColumn />, label: translate("tabs.overview"), value: "" },
+  { icon: <FiBarChart />, label: translate("tabs.runs"), value: "runs" },
+  { icon: <TaskIcon />, label: translate("tabs.tasks"), value: "tasks" },
+  { icon: <RiArrowGoBackFill />, label: translate("tabs.backfills"), value: 
"backfills" },
+  { icon: <MdOutlineEventNote />, label: translate("tabs.events"), value: 
"events" },
+  { icon: <FiCode />, label: translate("tabs.code"), value: "code" },
+  { icon: <MdDetails />, label: translate("tabs.details"), value: "details" },
 ];
 
 export const Dag = () => {
+  const { t: translate } = useTranslation("dag");
   const { dagId = "" } = useParams();
 
   const {
@@ -93,7 +95,9 @@ export const Dag = () => {
         dag={dag}
         error={error ?? runsError}
         isLoading={isLoading || isLoadingRuns}
-        tabs={tabs.filter((tab) => !(dag?.timetable_summary === null && 
tab.value === "backfills"))}
+        tabs={getTabs(translate).filter(
+          (tab) => !(dag?.timetable_summary === null && tab.value === 
"backfills"),
+        )}
       >
         <Header
           dag={dag}
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Details.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Details.tsx
index ff392408d65..bc89371a675 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Details.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Details.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 import { Box, Code, HStack, Table } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
 import { useParams } from "react-router-dom";
 
 import { useDagServiceGetDagDetails } from "openapi/queries";
@@ -26,6 +27,7 @@ import Time from "src/components/Time";
 import { ClipboardRoot, ClipboardIconButton } from "src/components/ui";
 
 export const Details = () => {
+  const { t: translate } = useTranslation("dag");
   const { dagId = "" } = useParams();
 
   const { data: dag } = useDagServiceGetDagDetails({
@@ -40,7 +42,7 @@ export const Details = () => {
         <Table.Root striped>
           <Table.Body>
             <Table.Row>
-              <Table.Cell>Dag ID</Table.Cell>
+              <Table.Cell>{translate("details.fields.dagId")}</Table.Cell>
               <Table.Cell>
                 <HStack>
                   {dag.dag_id}
@@ -51,86 +53,86 @@ export const Details = () => {
               </Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Description</Table.Cell>
+              
<Table.Cell>{translate("details.fields.description")}</Table.Cell>
               <Table.Cell>{dag.description}</Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Timezone</Table.Cell>
+              <Table.Cell>{translate("details.fields.timezone")}</Table.Cell>
               <Table.Cell>{dag.timezone}</Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>File Location</Table.Cell>
+              
<Table.Cell>{translate("details.fields.fileLocation")}</Table.Cell>
               <Table.Cell>
                 <Code>{dag.fileloc}</Code>
               </Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Last Parsed</Table.Cell>
+              <Table.Cell>{translate("details.fields.lastParsed")}</Table.Cell>
               <Table.Cell>
                 <Time datetime={dag.last_parsed} />
               </Table.Cell>
             </Table.Row>
             {dag.bundle_version !== null && (
               <Table.Row>
-                <Table.Cell>Bundle Version</Table.Cell>
+                
<Table.Cell>{translate("details.fields.bundleVersion")}</Table.Cell>
                 <Table.Cell>{dag.bundle_version}</Table.Cell>
               </Table.Row>
             )}
             <Table.Row>
-              <Table.Cell>Latest Dag Version</Table.Cell>
+              
<Table.Cell>{translate("details.fields.latestDagVersion")}</Table.Cell>
               <Table.Cell>
                 <DagVersionDetails dagVersion={dag.latest_dag_version} />
               </Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Start Date</Table.Cell>
+              <Table.Cell>{translate("details.fields.startDate")}</Table.Cell>
               <Table.Cell>
                 <Time datetime={dag.start_date} />
               </Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>End Date</Table.Cell>
+              <Table.Cell>{translate("details.fields.endDate")}</Table.Cell>
               <Table.Cell>
                 <Time datetime={dag.end_date} />
               </Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Last Expired</Table.Cell>
+              
<Table.Cell>{translate("details.fields.lastExpired")}</Table.Cell>
               <Table.Cell>
                 <Time datetime={dag.last_expired} />
               </Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Concurrency</Table.Cell>
+              
<Table.Cell>{translate("details.fields.concurrency")}</Table.Cell>
               <Table.Cell>{dag.concurrency}</Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Has Task Concurrency Limits</Table.Cell>
+              
<Table.Cell>{translate("details.fields.hasTaskConcurrencyLimits")}</Table.Cell>
               
<Table.Cell>{dag.has_task_concurrency_limits.toString()}</Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Dag Run Timeout</Table.Cell>
+              
<Table.Cell>{translate("details.fields.dagRunTimeout")}</Table.Cell>
               <Table.Cell>{dag.dag_run_timeout}</Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Max Active Runs</Table.Cell>
+              
<Table.Cell>{translate("details.fields.maxActiveRuns")}</Table.Cell>
               <Table.Cell>{dag.max_active_runs}</Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Max Active Tasks</Table.Cell>
+              
<Table.Cell>{translate("details.fields.maxActiveTasks")}</Table.Cell>
               <Table.Cell>{dag.max_active_tasks}</Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Max Consecutive Failed Dag Runs</Table.Cell>
+              
<Table.Cell>{translate("details.fields.maxConsecutiveFailedDagRuns")}</Table.Cell>
               <Table.Cell>{dag.max_consecutive_failed_dag_runs}</Table.Cell>
             </Table.Row>
             <Table.Row>
-              <Table.Cell>Catchup</Table.Cell>
+              <Table.Cell>{translate("details.fields.catchup")}</Table.Cell>
               <Table.Cell>{dag.catchup.toString()}</Table.Cell>
             </Table.Row>
             {dag.default_args === null ? undefined : (
               <Table.Row>
-                <Table.Cell>Default Args</Table.Cell>
+                
<Table.Cell>{translate("details.fields.defaultArgs")}</Table.Cell>
                 <Table.Cell>
                   <RenderedJsonField content={dag.default_args} />
                 </Table.Cell>
@@ -138,7 +140,7 @@ export const Details = () => {
             )}
             {dag.params === null ? undefined : (
               <Table.Row>
-                <Table.Cell>Params</Table.Cell>
+                <Table.Cell>{translate("details.fields.params")}</Table.Cell>
                 <Table.Cell>
                   <RenderedJsonField content={dag.params} />
                 </Table.Cell>
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx
index 18a1b49f18a..74f5b06ac07 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 import { Link } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
 import { FiBookOpen } from "react-icons/fi";
 import { useParams, Link as RouterLink } from "react-router-dom";
 
@@ -42,17 +43,18 @@ export const Header = ({
   readonly dagWithRuns?: DAGWithLatestDagRunsResponse;
   readonly isRefreshing?: boolean;
 }) => {
+  const { t: translate } = useTranslation("dag");
   // We would still like to show the dagId even if the dag object hasn't 
loaded yet
   const { dagId } = useParams();
   const latestRun = dagWithRuns?.latest_dag_runs ? 
dagWithRuns.latest_dag_runs[0] : undefined;
 
   const stats = [
     {
-      label: "Schedule",
+      label: translate("header.stats.schedule"),
       value: dagWithRuns === undefined ? undefined : <Schedule 
dag={dagWithRuns} />,
     },
     {
-      label: "Latest Run",
+      label: translate("header.stats.latestRun"),
       value:
         Boolean(latestRun) && latestRun !== undefined ? (
           <Link asChild color="fg.info">
@@ -69,7 +71,7 @@ export const Header = ({
         ) : undefined,
     },
     {
-      label: "Next Run",
+      label: translate("header.stats.nextRun"),
       value: Boolean(dagWithRuns?.next_dagrun_run_after) ? (
         <DagRunInfo
           logicalDate={dagWithRuns?.next_dagrun_logical_date}
@@ -78,15 +80,15 @@ export const Header = ({
       ) : undefined,
     },
     {
-      label: "Owner",
+      label: translate("header.stats.owner"),
       value: <DagOwners ownerLinks={dag?.owner_links ?? undefined} 
owners={dag?.owners} />,
     },
     {
-      label: "Tags",
+      label: translate("header.stats.tags"),
       value: <DagTags tags={dag?.tags ?? []} />,
     },
     {
-      label: "Latest Dag Version",
+      label: translate("header.stats.latestDagVersion"),
       value: <DagVersion version={dag?.latest_dag_version} />,
     },
   ];
@@ -98,10 +100,10 @@ export const Header = ({
           <>
             {dag.doc_md === null ? undefined : (
               <DisplayMarkdownButton
-                header="Dag Documentation"
+                header={translate("header.modals.docTitle")}
                 icon={<FiBookOpen />}
                 mdContent={dag.doc_md}
-                text="Dag Docs"
+                text={translate("header.buttons.dagDocs")}
               />
             )}
             <ParseDag dagId={dag.dag_id} fileToken={dag.file_token} />
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/FailedLogs.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/FailedLogs.tsx
index 7702e308db2..d3b4be87560 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/FailedLogs.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/FailedLogs.tsx
@@ -19,6 +19,7 @@
 import { Flex, Heading, Button } from "@chakra-ui/react";
 import { useState } from "react";
 import { useHotkeys } from "react-hotkeys-hook";
+import { useTranslation } from "react-i18next";
 
 import type { TaskInstanceCollectionResponse } from 
"openapi/requests/types.gen";
 import { Tooltip } from "src/components/ui";
@@ -31,6 +32,7 @@ const FailedLogs = ({
 }: {
   readonly failedTasks: TaskInstanceCollectionResponse | undefined;
 }) => {
+  const { t: translate } = useTranslation(["dag", "common"]);
   const defaultWrap = Boolean(useConfig("default_wrap"));
   const [wrap, setWrap] = useState(defaultWrap);
 
@@ -47,17 +49,17 @@ const FailedLogs = ({
   return (
     <Flex flexDirection="column" gap={3}>
       <Flex alignItems="center" justifyContent="space-between">
-        <Heading size="md">Recent Failed Task Logs</Heading>
-        <Tooltip closeDelay={100} content="Press w to toggle wrap" 
openDelay={100}>
+        <Heading size="md">{translate("overview.failedLogs.title")}</Heading>
+        <Tooltip closeDelay={100} content={translate("common:wrap.tooltip")} 
openDelay={100}>
           <Button
-            aria-label={wrap ? "Unwrap" : "Wrap"}
+            aria-label={translate(`common:wrap.${wrap ? "un" : ""}wrap`)}
             bg="bg.panel"
             fontSize="sm"
             onClick={toggleWrap}
             size="sm"
             variant="outline"
           >
-            {wrap ? "Unwrap" : "Wrap"}
+            {translate(`common:wrap.${wrap ? "un" : ""}wrap`)}
           </Button>
         </Tooltip>
       </Flex>
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
index b28ca34e109..faeec065e0f 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
@@ -19,6 +19,7 @@
 import { Box, HStack, Skeleton } from "@chakra-ui/react";
 import dayjs from "dayjs";
 import { lazy, useState, Suspense } from "react";
+import { useTranslation } from "react-i18next";
 import { useParams } from "react-router-dom";
 
 import {
@@ -37,6 +38,7 @@ const FailedLogs = lazy(() => import("./FailedLogs"));
 const defaultHour = "24";
 
 export const Overview = () => {
+  const { t: translate } = useTranslation("dag");
   const { dagId } = useParams();
 
   const now = dayjs();
@@ -103,7 +105,7 @@ export const Overview = () => {
             timestamp: ti.start_date ?? ti.logical_date,
           }))}
           isLoading={isLoading}
-          label="Failed Task"
+          label={translate("overview.buttons.failedTask", { count: 
failedTasks?.total_entries ?? 0 })}
           route={{
             pathname: "tasks",
             search: "state=failed",
@@ -118,7 +120,7 @@ export const Overview = () => {
             timestamp: dr.run_after,
           }))}
           isLoading={isLoadingFailedRuns}
-          label="Failed Run"
+          label={translate("overview.buttons.failedRun", { count: 
failedRuns?.total_entries ?? 0 })}
           route={{
             pathname: "runs",
             search: "state=failed",
@@ -140,7 +142,7 @@ export const Overview = () => {
             isLoading={isLoadingAssetEvents}
             ml={0}
             setOrderBy={setAssetSortBy}
-            title="Created Asset Event"
+            title={translate("overview.charts.assetEvent")}
           />
         ) : undefined}
       </HStack>
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/TaskLogPreview.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/TaskLogPreview.tsx
index a4b82e16803..fe4ecd42858 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/TaskLogPreview.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/TaskLogPreview.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 import { Box, Flex, Link } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
 import { Link as RouterLink } from "react-router-dom";
 
 import type { TaskInstanceResponse } from "openapi/requests/types.gen";
@@ -34,6 +35,7 @@ export const TaskLogPreview = ({
   readonly taskInstance: TaskInstanceResponse;
   readonly wrap: boolean;
 }) => {
+  const { t: translate } = useTranslation("dag");
   const { data, error, isLoading } = useLogs(
     {
       dagId: taskInstance.dag_id,
@@ -58,7 +60,9 @@ export const TaskLogPreview = ({
         <Flex gap={1}>
           <ClearTaskInstanceButton taskInstance={taskInstance} 
withText={false} />
           <Link asChild color="fg.info" fontSize="sm">
-            <RouterLink to={getTaskInstanceLink(taskInstance)}>View full 
logs</RouterLink>
+            <RouterLink to={getTaskInstanceLink(taskInstance)}>
+              {translate("overview.failedLogs.viewFullLogs")}
+            </RouterLink>
           </Link>
         </Flex>
       </Flex>
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx
index 1efb1513193..3877105ea72 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 import { Heading, VStack, Box, SimpleGrid, Text, Link } from 
"@chakra-ui/react";
+import { useTranslation } from "react-i18next";
 import { Link as RouterLink } from "react-router-dom";
 
 import { useTaskInstanceServiceGetTaskInstances } from 
"openapi/queries/queries.ts";
@@ -35,6 +36,7 @@ type Props = {
 };
 
 export const TaskCard = ({ dagId, task }: Props) => {
+  const { t: translate } = useTranslation(["dag", "common"]);
   const refetchInterval = useAutoRefresh({ dagId });
 
   const { data } = useTaskInstanceServiceGetTaskInstances(
@@ -64,19 +66,19 @@ export const TaskCard = ({ dagId, task }: Props) => {
       <SimpleGrid columns={4} gap={4} height={20}>
         <VStack align="flex-start" gap={1}>
           <Heading color="fg.muted" fontSize="xs">
-            Operator
+            {translate("common:operator")}
           </Heading>
           <Text fontSize="sm">{task.operator_name}</Text>
         </VStack>
         <VStack align="flex-start" gap={1}>
           <Heading color="fg.muted" fontSize="xs">
-            Trigger Rule
+            {translate("common:triggerRule")}
           </Heading>
           <Text fontSize="sm">{task.trigger_rule}</Text>
         </VStack>
         <VStack align="flex-start" gap={1}>
           <Heading color="fg.muted" fontSize="xs">
-            Last Instance
+            {translate("task.card.lastInstance")}
           </Heading>
           {data?.task_instances[0] ? (
             <TaskInstanceTooltip taskInstance={data.task_instances[0]}>
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/Tasks.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/Tasks.tsx
index ae33c69f66f..75b4cc81ff0 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/Tasks.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/Tasks.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 import { Heading, Skeleton, Box } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
 import { useParams } from "react-router-dom";
 
 import { useTaskServiceGetTasks } from "openapi/queries";
@@ -24,7 +25,6 @@ import type { TaskResponse } from 
"openapi/requests/types.gen";
 import { DataTable } from "src/components/DataTable";
 import type { CardDef } from "src/components/DataTable/types";
 import { ErrorAlert } from "src/components/ErrorAlert";
-import { pluralize } from "src/utils";
 
 import { TaskCard } from "./TaskCard";
 
@@ -36,6 +36,7 @@ const cardDef = (dagId: string): CardDef<TaskResponse> => ({
 });
 
 export const Tasks = () => {
+  const { t: translate } = useTranslation();
   const { dagId = "" } = useParams();
   const {
     data,
@@ -50,7 +51,7 @@ export const Tasks = () => {
     <Box>
       <ErrorAlert error={tasksError} />
       <Heading my={1} size="md">
-        {pluralize("Task", data ? data.total_entries : 0)}
+        {translate("task", { count: data?.total_entries ?? 0 })}
       </Heading>
       <DataTable
         cardDef={cardDef(dagId)}
diff --git 
a/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx 
b/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx
index b2f481fac14..e0a427cac64 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx
@@ -108,7 +108,7 @@ const taskInstanceColumns = ({
       ]),
   {
     accessorKey: "rendered_map_index",
-    header: translate("dags:taskInstances.columns.mapIndex"),
+    header: translate("common:mapIndex"),
   },
   {
     accessorKey: "state",

Reply via email to