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",