This is an automated email from the ASF dual-hosted git repository.
bbovenzi 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 ab3f0eca2a8 Migrate all plaintext to i18n (#51635)
ab3f0eca2a8 is described below
commit ab3f0eca2a809b7d6f8a7d499538d30ce6d69d09
Author: Brent Bovenzi <[email protected]>
AuthorDate: Thu Jun 12 12:41:47 2025 -0400
Migrate all plaintext to i18n (#51635)
Fix tryNumber key
Fix run details merge issues
Replace all plaintext and change eslint to ERROR
Fix zh-tw assets file
---
airflow-core/src/airflow/ui/rules/i18next.js | 8 +-
.../src/airflow/ui/src/components/ConfigForm.tsx | 4 +-
.../src/airflow/ui/src/components/DagVersion.tsx | 5 +-
.../airflow/ui/src/components/DagVersionSelect.tsx | 4 +-
.../airflow/ui/src/components/DurationChart.tsx | 4 +-
.../airflow/ui/src/i18n/locales/de/components.json | 4 +-
.../src/airflow/ui/src/i18n/locales/en/assets.json | 31 ++-
.../src/airflow/ui/src/i18n/locales/en/browse.json | 5 +-
.../src/airflow/ui/src/i18n/locales/en/common.json | 17 ++
.../airflow/ui/src/i18n/locales/en/components.json | 12 +-
.../src/airflow/ui/src/i18n/locales/en/dag.json | 26 ++-
.../airflow/ui/src/i18n/locales/pl/components.json | 4 +-
.../airflow/ui/src/i18n/locales/zh-TW/assets.json | 12 +-
.../airflow/ui/src/layouts/Details/Gantt/Gantt.tsx | 21 --
.../airflow/ui/src/layouts/Details/Gantt/index.ts | 20 --
.../src/airflow/ui/src/pages/Asset/AssetLayout.tsx | 4 +-
.../ui/src/pages/Asset/CreateAssetEvent.tsx | 6 +-
.../ui/src/pages/Asset/CreateAssetEventModal.tsx | 36 ++--
.../src/airflow/ui/src/pages/Asset/Header.tsx | 9 +-
.../airflow/ui/src/pages/AssetsList/AssetsList.tsx | 10 +-
.../ui/src/pages/Connections/ConnectionForm.tsx | 4 +-
airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx | 26 +--
airflow-core/src/airflow/ui/src/pages/Error.tsx | 12 +-
.../src/airflow/ui/src/pages/Events/Events.tsx | 4 +-
.../src/airflow/ui/src/pages/Run/Details.tsx | 213 ++++++++++-----------
.../src/airflow/ui/src/pages/Run/Header.tsx | 20 +-
airflow-core/src/airflow/ui/src/pages/Run/Run.tsx | 18 +-
.../src/airflow/ui/src/pages/Task/Header.tsx | 45 +++--
.../ui/src/pages/Task/Overview/Overview.tsx | 6 +-
.../src/airflow/ui/src/pages/Task/Task.tsx | 16 +-
.../ui/src/pages/TaskInstance/BlockingDeps.tsx | 8 +-
.../airflow/ui/src/pages/TaskInstance/Details.tsx | 61 +++---
.../ui/src/pages/TaskInstance/ExtraLinks.tsx | 4 +-
.../airflow/ui/src/pages/TaskInstance/Header.tsx | 26 ++-
.../pages/TaskInstance/Logs/ExternalLogLink.tsx | 4 +-
.../ui/src/pages/TaskInstance/Logs/Logs.tsx | 4 +-
.../ui/src/pages/TaskInstance/TaskInstance.tsx | 30 +--
.../ui/src/pages/TaskInstance/TriggererInfo.tsx | 75 ++++----
.../src/airflow/ui/src/pages/XCom/XCom.tsx | 7 +-
airflow-core/src/airflow/ui/src/utils/TrimText.tsx | 8 +-
airflow-core/src/airflow/ui/src/utils/index.ts | 1 -
.../src/airflow/ui/src/utils/pluralize.test.ts | 77 --------
airflow-core/src/airflow/ui/src/utils/pluralize.ts | 28 ---
43 files changed, 457 insertions(+), 482 deletions(-)
diff --git a/airflow-core/src/airflow/ui/rules/i18next.js
b/airflow-core/src/airflow/ui/rules/i18next.js
index f255bc1cc03..1ba9fc3ded7 100644
--- a/airflow-core/src/airflow/ui/rules/i18next.js
+++ b/airflow-core/src/airflow/ui/rules/i18next.js
@@ -22,7 +22,7 @@
*/
import i18nextPlugin from "eslint-plugin-i18next";
-import { WARN } from "./levels.js";
+import { ERROR } from "./levels.js";
const allExtensions = "*.{j,t}s{x,}";
@@ -37,6 +37,10 @@ export const i18nextRules = /** @type {const} @satisfies
{FlatConfig.Config} */
// Check files in the ui/src directory
`src/**/${allExtensions}`,
],
+ ignores: [
+ // Ignore test files
+ "src/**/*.test.tsx",
+ ],
plugins: {
i18next: i18nextPlugin,
},
@@ -56,7 +60,7 @@ export const i18nextRules = /** @type {const} @satisfies
{FlatConfig.Config} */
* @see
[i18next/no-literal-string](https://github.com/edvardchen/eslint-plugin-i18next#no-literal-string)
*/
"i18next/no-literal-string": [
- WARN,
+ ERROR,
{
markupOnly: true,
},
diff --git a/airflow-core/src/airflow/ui/src/components/ConfigForm.tsx
b/airflow-core/src/airflow/ui/src/components/ConfigForm.tsx
index 2f9f408e244..1977d25236d 100644
--- a/airflow-core/src/airflow/ui/src/components/ConfigForm.tsx
+++ b/airflow-core/src/airflow/ui/src/components/ConfigForm.tsx
@@ -51,7 +51,7 @@ const ConfigForm = <T extends FieldValues = FieldValues>({
setErrors,
setFormError,
}: ConfigFormProps<T>) => {
- const { t: translate } = useTranslation("components");
+ const { t: translate } = useTranslation(["components", "common"]);
const { conf, setConf } = useParamStore();
const validateAndPrettifyJson = (value: string) => {
@@ -68,7 +68,7 @@ const ConfigForm = <T extends FieldValues = FieldValues>({
return formattedJson;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message :
translate("configForm.unkownError");
+ const errorMessage = error instanceof Error ? error.message :
translate("common:error.unknown");
setErrors((prev) => ({
...prev,
diff --git a/airflow-core/src/airflow/ui/src/components/DagVersion.tsx
b/airflow-core/src/airflow/ui/src/components/DagVersion.tsx
index 68174bfceae..2c65c3c09eb 100644
--- a/airflow-core/src/airflow/ui/src/components/DagVersion.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DagVersion.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Text } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import type { DagVersionResponse } from "openapi/requests/types.gen";
@@ -24,13 +25,15 @@ import Time from "./Time";
import { Tooltip } from "./ui";
export const DagVersion = ({ version }: { readonly version: DagVersionResponse
| null | undefined }) => {
+ const { t: translate } = useTranslation("components");
+
if (version === null || version === undefined) {
return undefined;
}
return (
<Tooltip content={<Time datetime={version.created_at} />}>
- <Text as="span">v{version.version_number}</Text>
+ <Text as="span">{translate("versionSelect.versionCode", { versionCode:
version.version_number })}</Text>
</Tooltip>
);
};
diff --git a/airflow-core/src/airflow/ui/src/components/DagVersionSelect.tsx
b/airflow-core/src/airflow/ui/src/components/DagVersionSelect.tsx
index b0f88ed9a46..9ea751d2ed2 100644
--- a/airflow-core/src/airflow/ui/src/components/DagVersionSelect.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DagVersionSelect.tsx
@@ -91,7 +91,9 @@ export const DagVersionSelect = ({ showLabel = true }: {
readonly showLabel?: bo
<Select.Content>
{versionOptions.items.map((option) => (
<Select.Item item={option} key={option.version.version_number}>
- <Text>v{option.version.version_number}</Text>
+ <Text>
+ {translate("versionSelect.versionCode", { versionCode:
option.version.version_number })}
+ </Text>
<Time datetime={option.version.created_at} />
</Select.Item>
))}
diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
index bf8fb803db7..5d09441284f 100644
--- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
@@ -102,10 +102,10 @@ export const DurationChart = ({
{entries.length > 1
? kind === "Dag Run"
? translate("durationChart.lastDagRun_other", { count:
entries.length })
- : translate("durationChart.lasttaskInstance_other", { count:
entries.length })
+ : translate("durationChart.lastTaskInstance_other", { count:
entries.length })
: kind === "Dag Run"
? translate("durationChart.lastDagRun_one")
- : translate("durationChart.lasttaskInstance_one")}
+ : translate("durationChart.lastTaskInstance_one")}
</Heading>
<Bar
data={{
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/de/components.json
b/airflow-core/src/airflow/ui/src/i18n/locales/de/components.json
index 57f2717f4b6..845a7a62d27 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/de/components.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/de/components.json
@@ -41,8 +41,8 @@
"duration": "Laufzeit (Sekunden)",
"lastDagRun_one": "Letzter Dag Lauf",
"lastDagRun_other": "Letzte {{count}} Dag Läufe",
- "lasttaskInstance_one": "Letzte Task Instanz",
- "lasttaskInstance_other": "Letzte {{count}} Task Instanzen",
+ "lastTaskInstance_one": "Letzte Task Instanz",
+ "lastTaskInstance_other": "Letzte {{count}} Task Instanzen",
"queuedDuration": "Zeit in der Warteschlange",
"runAfter": "Lauf ab",
"runDuration": "Laufzeit"
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/en/assets.json
b/airflow-core/src/airflow/ui/src/i18n/locales/en/assets.json
index b927d6891ea..e82019096c8 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/en/assets.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/assets.json
@@ -1,10 +1,29 @@
{
- "columns": {
- "consumingDags": "Consuming Dags",
- "group": "Group",
- "lastAssetEvent": "Last Asset Event",
- "name": "Name",
- "producingTasks": "Producing Tasks"
+ "consumingDags": "Consuming Dags",
+ "createEvent": {
+ "button": "Create Event",
+ "manual": {
+ "description": "Directly create an Asset Event",
+ "extra": "Asset Event Extra",
+ "label": "Manual"
+ },
+ "materialize": {
+ "description": "Trigger the Dag upstream of this asset",
+ "descriptionWithDag": "Trigger the Dag upstream of this asset:
{{dagName}}",
+ "label": "Materialize",
+ "unpauseDag": "Unpause {{dagName}} on trigger"
+ },
+ "success": {
+ "manualDescription": "Manual asset event creation was successful.",
+ "manualTitle": "Asset Event Created",
+ "materializeDescription": "Upstream Dag {{dagId}} was triggered
successfully.",
+ "materializeTitle": "Materializing Asset"
+ },
+ "title": "Create Asset Event for {{name}}"
},
+ "group": "Group",
+ "lastAssetEvent": "Last Asset Event",
+ "name": "Name",
+ "producingTasks": "Producing Tasks",
"searchPlaceholder": "Search Assets"
}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/en/browse.json
b/airflow-core/src/airflow/ui/src/i18n/locales/en/browse.json
index 56d89a8dc5c..8bdbec5aa9c 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/en/browse.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/browse.json
@@ -10,13 +10,14 @@
"user": "User",
"when": "When"
},
- "title": "Audit Log Events"
+ "title": "Audit Log"
},
"xcom":{
"columns":{
"dag": "Dag",
"key": "Key",
"value": "Value"
- }
+ },
+ "title": "XCom"
}
}
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 8908acf24e5..92ed06fc380 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
@@ -53,6 +53,7 @@
"queuedAt": "Queued At",
"runAfter": "Run After",
"runType": "Run Type",
+ "sourceAssetEvent": "Source Asset Event",
"triggeredBy": "Triggered By"
},
"dagRun_one": "Dag Run",
@@ -68,6 +69,13 @@
},
"duration": "Duration",
"endDate": "End Date",
+ "error": {
+ "back": "Back",
+ "defaultMessage": "An unexpected error occurred",
+ "home": "Home",
+ "notFound": "Page Not Found",
+ "title": "Error"
+ },
"expression": {
"all": "All",
"and": "AND",
@@ -169,6 +177,7 @@
"to": "To"
},
"task": {
+ "documentation": "Task Documentation",
"lastInstance": "Last Instance",
"operator": "Operator",
"triggerRule": "Trigger Rule"
@@ -189,6 +198,14 @@
"queue": "Queue",
"queuedWhen": "Queued At",
"scheduledWhen": "Scheduled At",
+ "triggerer": {
+ "assigned": "Assigned triggerer",
+ "class": "Trigger class",
+ "createdAt": "Trigger creation time",
+ "id": "Trigger ID",
+ "latestHeartbeat": "Latest triggerer heartbeat",
+ "title": "Triggerer Info"
+ },
"unixname": "Unix Name"
},
"taskInstance_one": "Task Instance",
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/en/components.json
b/airflow-core/src/airflow/ui/src/i18n/locales/en/components.json
index 5e1b1abd9d7..d4526010cd1 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/en/components.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/components.json
@@ -28,8 +28,7 @@
"configForm": {
"advancedOptions": "Advanced Options",
"configJson": "Configuration JSON",
- "invalidJson": "Invalid JSON format: {{errorMessage}}",
- "unkownError": "Unknown error occurred."
+ "invalidJson": "Invalid JSON format: {{errorMessage}}"
},
"dagWarnings": {
"error_one": "1 Error",
@@ -41,8 +40,8 @@
"duration": "Duration (seconds)",
"lastDagRun_one": "Last Dag Run",
"lastDagRun_other": "Last {{count}} Dag Runs",
- "lasttaskInstance_one": "Last Task Instance",
- "lasttaskInstance_other": "Last {{count}} Task Instances",
+ "lastTaskInstance_one": "Last Task Instance",
+ "lastTaskInstance_other": "Last {{count}} Task Instances",
"queuedDuration": "Queued Duration",
"runAfter": "Run After",
"runDuration": "Run Duration"
@@ -96,6 +95,11 @@
"title": "Trigger Dag",
"unpause": "Unpause {{dagDisplayName}} on trigger"
},
+ "trimText": {
+ "details": "Details",
+ "empty": "Empty",
+ "noContent": "No content available."
+ },
"versionDetails": {
"bundleLink": "Bundle Link",
"bundleName": "Bundle Name",
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
index 4e94582a22d..4a1a3c6f9f6 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/en/dag.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/dag.json
@@ -1,10 +1,16 @@
{
"allRuns": "All Runs",
+ "blockingDeps": {
+ "dependency": "Dependency",
+ "reason": "Reason",
+ "title": "Dependencies Blocking Task From Getting Scheduled"
+ },
"code": {
"bundleUrl": "Bundle Url",
"noCode": "No Code Found",
"parsedAt": "Parsed at:"
},
+ "extraLinks": "Extra Links",
"grid": {
"buttons": {
"resetToLatest": "Reset to latest",
@@ -16,12 +22,18 @@
"dagDocs": "Dag Docs"
}
},
+ "logs": {
+ "noTryNumber": "No try number",
+ "viewInExternal": "View logs in {{name}} (attempt {{attempt}})"
+ },
"overview": {
"buttons": {
"failedRun_one": "Failed Run",
"failedRun_other": "Failed Runs",
"failedTask_one": "Failed Task",
- "failedTask_other": "Failed Tasks"
+ "failedTask_other": "Failed Tasks",
+ "failedTaskInstance_one": "Failed Task Instance",
+ "failedTaskInstance_other": "Failed Task Instances"
},
"charts": {
"assetEvent_one": "Created Asset Event",
@@ -55,14 +67,20 @@
}
},
"tabs": {
- "asset_events": "Asset Events",
+ "assetEvents": "Asset Events",
+ "auditLog": "Audit Log",
"backfills": "Backfills",
"code": "Code",
"details": "Details",
- "events": "Audit Logs",
+ "logs": "Logs",
+ "mappedTaskInstances_one": "Task Instance [{{count}}]",
+ "mappedTaskInstances_other": "Task Instances [{{count}}]",
"overview": "Overview",
+ "renderedTemplates": "Rendered Templates",
"runs": "Runs",
- "tasks": "Tasks"
+ "taskInstances": "Task Instances",
+ "tasks": "Tasks",
+ "xcom": "XCom"
},
"taskGroups": {
"collapseAll": "Collapse all task groups",
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/pl/components.json
b/airflow-core/src/airflow/ui/src/i18n/locales/pl/components.json
index 25526c053ae..363fd119d84 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/pl/components.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/pl/components.json
@@ -41,8 +41,8 @@
"duration": "Czas trwania (sekundy)",
"lastDagRun_one": "Ostatnie wykonanie Daga",
"lastDagRun_other": "Ostatnie {{count}} wykonania Daga",
- "lasttaskInstance_one": "Ostatnia instancja zadania",
- "lasttaskInstance_other": "Ostatnie {{count}} instancje zadania",
+ "lastTaskInstance_one": "Ostatnia instancja zadania",
+ "lastTaskInstance_other": "Ostatnie {{count}} instancje zadania",
"queuedDuration": "Czas oczekiwania",
"runAfter": "Uruchom po",
"runDuration": "Czas trwania wykonania"
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/assets.json
b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/assets.json
index 04e46d86abf..133c2177884 100644
--- a/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/assets.json
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/assets.json
@@ -1,10 +1,8 @@
{
- "columns": {
- "consumingDags": "消費者 Dags",
- "group": "群組",
- "lastAssetEvent": "最後資源事件",
- "name": "名稱",
- "producingTasks": "生產任務"
- },
+ "consumingDags": "消費者 Dags",
+ "group": "群組",
+ "lastAssetEvent": "最後資源事件",
+ "name": "名稱",
+ "producingTasks": "生產任務",
"searchPlaceholder": "搜尋資源"
}
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
deleted file mode 100644
index 90530c4b7b0..00000000000
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-/*!
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { Box } from "@chakra-ui/react";
-
-export const Gantt = () => <Box>gantt</Box>;
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/index.ts
b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/index.ts
deleted file mode 100644
index 24f6dabe4cf..00000000000
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/index.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*!
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export * from "./Gantt";
diff --git a/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx
b/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx
index c41be956098..e136bf18ce5 100644
--- a/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx
@@ -19,6 +19,7 @@
import { HStack, Box } from "@chakra-ui/react";
import { useReactFlow } from "@xyflow/react";
import { useCallback } from "react";
+import { useTranslation } from "react-i18next";
import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
import { useParams } from "react-router-dom";
@@ -33,6 +34,7 @@ import { CreateAssetEvent } from "./CreateAssetEvent";
import { Header } from "./Header";
export const AssetLayout = () => {
+ const { t: translate } = useTranslation(["assets", "common"]);
const { assetId } = useParams();
const { setTableURLState, tableURLState } = useTableURLState();
@@ -51,7 +53,7 @@ export const AssetLayout = () => {
const links = [
{
label: asset?.name,
- title: "Asset",
+ title: translate("common:asset_one"),
value: `/assets/${assetId}`,
},
];
diff --git a/airflow-core/src/airflow/ui/src/pages/Asset/CreateAssetEvent.tsx
b/airflow-core/src/airflow/ui/src/pages/Asset/CreateAssetEvent.tsx
index 6310e6de50d..10b00c9310a 100644
--- a/airflow-core/src/airflow/ui/src/pages/Asset/CreateAssetEvent.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Asset/CreateAssetEvent.tsx
@@ -18,6 +18,7 @@
*/
import { Box } from "@chakra-ui/react";
import { useDisclosure } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiPlay } from "react-icons/fi";
import type { AssetResponse } from "openapi/requests/types.gen";
@@ -32,16 +33,17 @@ type Props = {
export const CreateAssetEvent = ({ asset, withText = true }: Props) => {
const { onClose, onOpen, open } = useDisclosure();
+ const { t: translate } = useTranslation("assets");
return (
<Box>
<ActionButton
- actionName="Create Asset Event"
+ actionName={translate("createEvent.button")}
colorPalette="blue"
disabled={asset === undefined}
icon={<FiPlay />}
onClick={onOpen}
- text="Create Asset Event"
+ text={translate("createEvent.button")}
variant="solid"
withText={withText}
/>
diff --git
a/airflow-core/src/airflow/ui/src/pages/Asset/CreateAssetEventModal.tsx
b/airflow-core/src/airflow/ui/src/pages/Asset/CreateAssetEventModal.tsx
index f54e87182d6..212e9d6a187 100644
--- a/airflow-core/src/airflow/ui/src/pages/Asset/CreateAssetEventModal.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Asset/CreateAssetEventModal.tsx
@@ -19,6 +19,7 @@
import { Button, Field, Heading, HStack, VStack, Text } from
"@chakra-ui/react";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
+import { useTranslation } from "react-i18next";
import { FiPlay } from "react-icons/fi";
import {
@@ -52,6 +53,7 @@ type Props = {
};
export const CreateAssetEventModal = ({ asset, onClose, open }: Props) => {
+ const { t: translate } = useTranslation(["assets", "components"]);
const [eventType, setEventType] = useState("manual");
const [extraError, setExtraError] = useState<string | undefined>();
const [unpause, setUnpause] = useState(true);
@@ -82,7 +84,7 @@ export const CreateAssetEventModal = ({ asset, onClose, open
}: Props) => {
setExtra(formattedJson); // Update only if the value is different
}
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : "Unknown
error occurred.";
+ const errorMessage = error instanceof Error ? error.message :
translate("common:error.unknown");
setExtraError(errorMessage);
}
@@ -107,14 +109,14 @@ export const CreateAssetEventModal = ({ asset, onClose,
open }: Props) => {
];
toaster.create({
- description: `Upstream Dag ${response.dag_id} was triggered
successfully.`,
- title: "Materializing Asset",
+ description: translate("createEvent.success.materializeDescription", {
dagId: response.dag_id }),
+ title: translate("createEvent.success.materializeTitle"),
type: "success",
});
} else {
toaster.create({
- description: "Manual asset event creation was successful.",
- title: "Asset Event Created",
+ description: translate("createEvent.success.manualDescription"),
+ title: translate("createEvent.success.manualTitle"),
type: "success",
});
}
@@ -164,7 +166,7 @@ export const CreateAssetEventModal = ({ asset, onClose,
open }: Props) => {
<Dialog.Content backdrop>
<Dialog.Header paddingBottom={0}>
<VStack align="start" gap={4}>
- <Heading size="xl">Create Asset Event for {asset.name}</Heading>
+ <Heading size="xl">{translate("createEvent.title", { name:
asset.name })}</Heading>
</VStack>
</Dialog.Header>
@@ -180,24 +182,34 @@ export const CreateAssetEventModal = ({ asset, onClose,
open }: Props) => {
>
<HStack align="stretch">
<RadioCardItem
- description={`Trigger the Dag upstream of this
asset${upstreamDagId === undefined ? "" : `: ${dag?.dag_display_name ??
upstreamDagId}`}`}
+ description={
+ upstreamDagId === undefined
+ ? translate("createEvent.materialize.description")
+ : translate("createEvent.materialize.descriptionWithDag", {
+ dagName: dag?.dag_display_name ?? upstreamDagId,
+ })
+ }
disabled={!hasUpstreamDag}
- label="Materialize"
+ label={translate("createEvent.materialize.label")}
value="materialize"
/>
- <RadioCardItem description="Directly create an Asset Event"
label="Manual" value="manual" />
+ <RadioCardItem
+ description={translate("createEvent.manual.description")}
+ label={translate("createEvent.manual.label")}
+ value="manual"
+ />
</HStack>
</RadioCardRoot>
{eventType === "manual" ? (
<Field.Root mt={6}>
- <Field.Label fontSize="md">Asset Event Extra</Field.Label>
+ <Field.Label
fontSize="md">{translate("createEvent.manual.extra")}</Field.Label>
<JsonEditor onChange={validateAndPrettifyJson} value={extra} />
<Text color="fg.error">{extraError}</Text>
</Field.Root>
) : undefined}
{eventType === "materialize" && dag?.is_paused ? (
<Checkbox checked={unpause} colorPalette="blue" onChange={() =>
setUnpause(!unpause)}>
- Unpause {dag.dag_display_name} on trigger
+ {translate("createEvent.materialize.unpauseDag", { dagName:
dag.dag_display_name })}
</Checkbox>
) : undefined}
<ErrorAlert error={eventType === "manual" ? manualError :
materializeError} />
@@ -209,7 +221,7 @@ export const CreateAssetEventModal = ({ asset, onClose,
open }: Props) => {
loading={isPending || isMaterializePending}
onClick={handleSubmit}
>
- <FiPlay /> Create Event
+ <FiPlay /> {translate("createEvent.button")}
</Button>
</Dialog.Footer>
</Dialog.Content>
diff --git a/airflow-core/src/airflow/ui/src/pages/Asset/Header.tsx
b/airflow-core/src/airflow/ui/src/pages/Asset/Header.tsx
index b7fdfa46043..0ab20fa3e73 100644
--- a/airflow-core/src/airflow/ui/src/pages/Asset/Header.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Asset/Header.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { useTranslation } from "react-i18next";
import { FiDatabase } from "react-icons/fi";
import type { AssetResponse } from "openapi/requests/types.gen";
@@ -30,14 +31,16 @@ export const Header = ({
readonly asset?: AssetResponse;
readonly isRefreshing?: boolean;
}) => {
+ const { t: translate } = useTranslation("assets");
+
const stats = [
- { label: "Group", value: asset?.group },
+ { label: translate("group"), value: asset?.group },
{
- label: "Producing Tasks",
+ label: translate("producingTasks"),
value: <DependencyPopover dependencies={asset?.producing_tasks ?? []}
type="Task" />,
},
{
- label: "Consuming Dags",
+ label: translate("consumingDags"),
value: <DependencyPopover dependencies={asset?.consuming_dags ?? []}
type="Dag" />,
},
];
diff --git a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx
b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx
index d05404f7ff5..51767172c67 100644
--- a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx
@@ -44,7 +44,7 @@ const createColumns = (translate: (key: string) => string):
Array<ColumnDef<Asse
<RouterLink to={`/assets/${original.id}`}>{original.name}</RouterLink>
</Link>
),
- header: () => translate("columns.name"),
+ header: () => translate("name"),
},
{
accessorKey: "last_asset_event",
@@ -59,12 +59,12 @@ const createColumns = (translate: (key: string) => string):
Array<ColumnDef<Asse
return <Time datetime={timestamp} />;
},
enableSorting: false,
- header: () => translate("columns.lastAssetEvent"),
+ header: () => translate("lastAssetEvent"),
},
{
accessorKey: "group",
enableSorting: false,
- header: () => translate("columns.group"),
+ header: () => translate("group"),
},
{
accessorKey: "consuming_dags",
@@ -73,7 +73,7 @@ const createColumns = (translate: (key: string) => string):
Array<ColumnDef<Asse
<DependencyPopover dependencies={original.consuming_dags} type="Dag" />
) : undefined,
enableSorting: false,
- header: () => translate("columns.consumingDags"),
+ header: () => translate("consumingDags"),
},
{
accessorKey: "producing_tasks",
@@ -82,7 +82,7 @@ const createColumns = (translate: (key: string) => string):
Array<ColumnDef<Asse
<DependencyPopover dependencies={original.producing_tasks} type="Task"
/>
) : undefined,
enableSorting: false,
- header: () => translate("columns.producingTasks"),
+ header: () => translate("producingTasks"),
},
{
accessorKey: "trigger",
diff --git
a/airflow-core/src/airflow/ui/src/pages/Connections/ConnectionForm.tsx
b/airflow-core/src/airflow/ui/src/pages/Connections/ConnectionForm.tsx
index 2badcd37966..bf921a1de3c 100644
--- a/airflow-core/src/airflow/ui/src/pages/Connections/ConnectionForm.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Connections/ConnectionForm.tsx
@@ -66,7 +66,7 @@ const ConnectionForm = ({
mode: "onBlur",
});
- const { t: translate } = useTranslation("admin");
+ const { t: translate } = useTranslation(["admin", "common"]);
const selectedConnType = watch("conn_type"); // Get the selected connection
type
const standardFields = connectionTypeMeta[selectedConnType]?.standard_fields
?? {};
const paramsDic = { paramsDict:
connectionTypeMeta[selectedConnType]?.extra_fields ?? ({} as ParamsSpec) };
@@ -107,7 +107,7 @@ const ConnectionForm = ({
return formattedJson;
} catch (error_) {
- const errorMessage = error_ instanceof Error ? error_.message : "Unknown
error occurred.";
+ const errorMessage = error_ instanceof Error ? error_.message :
translate("common:error.unknown");
setErrors((prev) => ({
...prev,
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 5190a357853..cca182140b8 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
@@ -34,20 +34,20 @@ import { isStatePending, useAutoRefresh } from "src/utils";
import { Header } from "./Header";
-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 tabs = [
+ { 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.auditLog"), value:
"events" },
+ { icon: <FiCode />, label: translate("tabs.code"), value: "code" },
+ { icon: <MdDetails />, label: translate("tabs.details"), value: "details"
},
+ ];
+
const {
data: dag,
error,
@@ -89,15 +89,15 @@ export const Dag = () => {
} satisfies DAGWithLatestDagRunsResponse;
}
+ const displayTabs = tabs.filter((tab) => !(dag?.timetable_summary === null
&& tab.value === "backfills"));
+
return (
<ReactFlowProvider>
<DetailsLayout
dag={dag}
error={error ?? runsError}
isLoading={isLoading || isLoadingRuns}
- tabs={getTabs(translate).filter(
- (tab) => !(dag?.timetable_summary === null && tab.value ===
"backfills"),
- )}
+ tabs={displayTabs}
>
<Header
dag={dag}
diff --git a/airflow-core/src/airflow/ui/src/pages/Error.tsx
b/airflow-core/src/airflow/ui/src/pages/Error.tsx
index 525cbb9e764..f486c9e7306 100644
--- a/airflow-core/src/airflow/ui/src/pages/Error.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Error.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Box, VStack, Heading, Text, Button, Container, HStack, Code } from
"@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { useNavigate, useRouteError, isRouteErrorResponse } from
"react-router-dom";
import { AirflowPin } from "src/assets/AirflowPin";
@@ -24,15 +25,16 @@ import { AirflowPin } from "src/assets/AirflowPin";
export const ErrorPage = () => {
const navigate = useNavigate();
const error = useRouteError();
+ const { t: translate } = useTranslation();
- let errorMessage = "An unexpected error occurred";
+ let errorMessage = translate("error.defaultMessage");
let statusCode = "";
if (isRouteErrorResponse(error)) {
statusCode = String(error.status);
errorMessage =
((error as unknown as Error).message || (error as { statusText?: string
}).statusText) ??
- "Page Not Found";
+ translate("error.notFound");
} else if (error instanceof Error) {
errorMessage = error.message;
} else if (typeof error === "string") {
@@ -49,7 +51,7 @@ export const ErrorPage = () => {
<AirflowPin height="50px" width="50px" />
<VStack gap={4}>
- <Heading>{statusCode || "Error"}</Heading>
+ <Heading>{statusCode || translate("error.title")}</Heading>
<Text fontSize="lg">{errorMessage}</Text>
{error instanceof Error && isDev ? (
<Code borderRadius="md" fontSize="sm" p={3} width="full">
@@ -60,10 +62,10 @@ export const ErrorPage = () => {
<HStack gap={4}>
<Button colorPalette="blue" onClick={() => navigate(-1)} size="lg">
- Back
+ {translate("error.back")}
</Button>
<Button colorPalette="blue" onClick={() => navigate("/")}
size="lg" variant="outline">
- Home
+ {translate("error.home")}
</Button>
</HStack>
</VStack>
diff --git a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
index a3b306a4af3..f71e57b6419 100644
--- a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
@@ -167,7 +167,9 @@ export const Events = () => {
return (
<Box>
<Flex alignItems="center" justifyContent="space-between">
- <Heading>{translate("auditLog.title")}</Heading>
+ {dagId === undefined && runId === undefined && taskId === undefined ? (
+ <Heading size="md">{translate("auditLog.title")}</Heading>
+ ) : undefined}
<ButtonGroup attached mt="1" size="sm" variant="surface">
<IconButton
aria-label={translate("auditLog.actions.expandAllExtra")}
diff --git a/airflow-core/src/airflow/ui/src/pages/Run/Details.tsx
b/airflow-core/src/airflow/ui/src/pages/Run/Details.tsx
index 75860092487..23d525b0edf 100644
--- a/airflow-core/src/airflow/ui/src/pages/Run/Details.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Run/Details.tsx
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Box, Flex, HStack, StackSeparator, Table, Text, VStack } from
"@chakra-ui/react";
+import { Flex, HStack, StackSeparator, Table, Text, VStack } from
"@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { useDagRunServiceGetDagRun } from "openapi/queries";
@@ -29,6 +30,7 @@ import { ClipboardRoot, ClipboardIconButton } from
"src/components/ui";
import { getDuration, isStatePending, useAutoRefresh } from "src/utils";
export const Details = () => {
+ const { t: translate } = useTranslation(["common", "components"]);
const { dagId = "", runId = "" } = useParams();
const refetchInterval = useAutoRefresh({ dagId });
@@ -42,112 +44,109 @@ export const Details = () => {
{ refetchInterval: (query) => (isStatePending(query.state.data?.state) ?
refetchInterval : false) },
);
- // TODO : Render DagRun configuration object
+ if (!dagRun) {
+ return undefined;
+ }
+
return (
- <Box p={2}>
- {dagRun === undefined ? (
- <div />
- ) : (
- <Table.Root striped>
- <Table.Body>
- <Table.Row>
- <Table.Cell>State</Table.Cell>
- <Table.Cell>
- <Flex gap={1}>
- <StateBadge state={dagRun.state} />
- {dagRun.state}
- </Flex>
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Run ID</Table.Cell>
- <Table.Cell>
- <HStack>
- {dagRun.dag_run_id}
- <ClipboardRoot value={dagRun.dag_run_id}>
- <ClipboardIconButton />
- </ClipboardRoot>
- </HStack>
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Run Type</Table.Cell>
- <Table.Cell>
- <HStack>
- <RunTypeIcon runType={dagRun.run_type} />
- <Text>{dagRun.run_type}</Text>
- </HStack>
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Run Duration</Table.Cell>
- <Table.Cell>{getDuration(dagRun.start_date,
dagRun.end_date)}</Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Last Scheduling Decision</Table.Cell>
- <Table.Cell>
- <Time datetime={dagRun.last_scheduling_decision} />
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Queued at</Table.Cell>
- <Table.Cell>
- <Time datetime={dagRun.queued_at} />
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Start Date</Table.Cell>
- <Table.Cell>
- <Time datetime={dagRun.start_date} />
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>End Date</Table.Cell>
- <Table.Cell>
- <Time datetime={dagRun.end_date} />
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Data Interval Start</Table.Cell>
- <Table.Cell>
- <Time datetime={dagRun.data_interval_start} />
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Data Interval End</Table.Cell>
- <Table.Cell>
- <Time datetime={dagRun.data_interval_end} />
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Trigger Source</Table.Cell>
- <Table.Cell>{dagRun.triggered_by}</Table.Cell>
- </Table.Row>
- {dagRun.bundle_version !== null && (
- <Table.Row>
- <Table.Cell>Bundle Version</Table.Cell>
- <Table.Cell>{dagRun.bundle_version}</Table.Cell>
- </Table.Row>
- )}
- <Table.Row>
- <Table.Cell>Dag Version(s)</Table.Cell>
- <Table.Cell>
- <VStack separator={<StackSeparator />}>
- {dagRun.dag_versions.map((dagVersion) => (
- <DagVersionDetails dagVersion={dagVersion}
key={dagVersion.id} />
- ))}
- </VStack>
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Run Config</Table.Cell>
- <Table.Cell>
- <RenderedJsonField content={dagRun.conf ?? {}} />
- </Table.Cell>
- </Table.Row>
- </Table.Body>
- </Table.Root>
- )}
- </Box>
+ <Table.Root striped>
+ <Table.Body>
+ <Table.Row>
+ <Table.Cell>{translate("state")}</Table.Cell>
+ <Table.Cell>
+ <Flex gap={1}>
+ <StateBadge state={dagRun.state} />
+ {dagRun.state}
+ </Flex>
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("runId")}</Table.Cell>
+ <Table.Cell>
+ <HStack>
+ {dagRun.dag_run_id}
+ <ClipboardRoot value={dagRun.dag_run_id}>
+ <ClipboardIconButton />
+ </ClipboardRoot>
+ </HStack>
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("dagRun.runType")}</Table.Cell>
+ <Table.Cell>
+ <HStack>
+ <RunTypeIcon runType={dagRun.run_type} />
+ <Text>{dagRun.run_type}</Text>
+ </HStack>
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("duration")}</Table.Cell>
+ <Table.Cell>{getDuration(dagRun.start_date,
dagRun.end_date)}</Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("dagRun.lastSchedulingDecision")}</Table.Cell>
+ <Table.Cell>
+ <Time datetime={dagRun.last_scheduling_decision} />
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("dagRun.queuedAt")}</Table.Cell>
+ <Table.Cell>
+ <Time datetime={dagRun.queued_at} />
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("startDate")}</Table.Cell>
+ <Table.Cell>
+ <Time datetime={dagRun.start_date} />
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("endDate")}</Table.Cell>
+ <Table.Cell>
+ <Time datetime={dagRun.end_date} />
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("dagRun.dataIntervalStart")}</Table.Cell>
+ <Table.Cell>
+ <Time datetime={dagRun.data_interval_start} />
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("dagRun.dataIntervalEnd")}</Table.Cell>
+ <Table.Cell>
+ <Time datetime={dagRun.data_interval_end} />
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("dagRun.triggeredBy")}</Table.Cell>
+ <Table.Cell>{dagRun.triggered_by}</Table.Cell>
+ </Table.Row>
+ {dagRun.bundle_version !== null && (
+ <Table.Row>
+
<Table.Cell>{translate("components:versionDetails.bundleVersion")}</Table.Cell>
+ <Table.Cell>{dagRun.bundle_version}</Table.Cell>
+ </Table.Row>
+ )}
+ <Table.Row>
+ <Table.Cell>{translate("dagRun.dagVersions")}</Table.Cell>
+ <Table.Cell>
+ <VStack separator={<StackSeparator />}>
+ {dagRun.dag_versions.map((dagVersion) => (
+ <DagVersionDetails dagVersion={dagVersion} key={dagVersion.id}
/>
+ ))}
+ </VStack>
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("dagRun.conf")}</Table.Cell>
+ <Table.Cell>
+ <RenderedJsonField content={dagRun.conf ?? {}} />
+ </Table.Cell>
+ </Table.Row>
+ </Table.Body>
+ </Table.Root>
);
};
diff --git a/airflow-core/src/airflow/ui/src/pages/Run/Header.tsx
b/airflow-core/src/airflow/ui/src/pages/Run/Header.tsx
index c412da36b9d..2a338baf3a1 100644
--- a/airflow-core/src/airflow/ui/src/pages/Run/Header.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Run/Header.tsx
@@ -18,6 +18,7 @@
*/
import { HStack, Text, Box } from "@chakra-ui/react";
import { useCallback, useState, useRef } from "react";
+import { useTranslation } from "react-i18next";
import { FiBarChart, FiMessageSquare } from "react-icons/fi";
import type { DAGRunResponse } from "openapi/requests/types.gen";
@@ -39,6 +40,7 @@ export const Header = ({
readonly dagRun: DAGRunResponse;
readonly isRefreshing?: boolean;
}) => {
+ const { t: translate } = useTranslation();
const [note, setNote] = useState<string | null>(dagRun.note);
const dagId = dagRun.dag_id;
@@ -67,14 +69,14 @@ export const Header = ({
actions={
<>
<EditableMarkdownButton
- header="Dag Run Note"
+ header={translate("note.dagRun")}
icon={<FiMessageSquare />}
isPending={isPending}
mdContent={note}
onConfirm={onConfirm}
- placeholder="Add a note..."
+ placeholder={translate("note.placeholder")}
setMdContent={setNote}
- text={Boolean(dagRun.note) ? "Note" : "Add a note"}
+ text={Boolean(dagRun.note) ? translate("note.label") :
translate("note.add")}
withText={containerWidth > 700}
/>
<ClearRunButton dagRun={dagRun} isHotkeyEnabled
withText={containerWidth > 700} />
@@ -89,12 +91,12 @@ export const Header = ({
? []
: [
{
- label: "Logical Date",
+ label: translate("logicalDate"),
value: <Time datetime={dagRun.logical_date} />,
},
]),
{
- label: "Run Type",
+ label: translate("dagRun.runType"),
value: (
<HStack>
<RunTypeIcon runType={dagRun.run_type} />
@@ -102,11 +104,11 @@ export const Header = ({
</HStack>
),
},
- { label: "Start", value: <Time datetime={dagRun.start_date} /> },
- { label: "End", value: <Time datetime={dagRun.end_date} /> },
- { label: "Duration", value: getDuration(dagRun.start_date,
dagRun.end_date) },
+ { label: translate("startDate"), value: <Time
datetime={dagRun.start_date} /> },
+ { label: translate("endDate"), value: <Time
datetime={dagRun.end_date} /> },
+ { label: translate("duration"), value:
getDuration(dagRun.start_date, dagRun.end_date) },
{
- label: "Dag Version(s)",
+ label: translate("dagRun.dagVersions"),
value: (
<LimitedItemsList
items={dagRun.dag_versions.map((version) => (
diff --git a/airflow-core/src/airflow/ui/src/pages/Run/Run.tsx
b/airflow-core/src/airflow/ui/src/pages/Run/Run.tsx
index 5b4dfffd3e5..0b9d8c9db94 100644
--- a/airflow-core/src/airflow/ui/src/pages/Run/Run.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Run/Run.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { ReactFlowProvider } from "@xyflow/react";
+import { useTranslation } from "react-i18next";
import { FiCode, FiDatabase } from "react-icons/fi";
import { MdDetails, MdOutlineEventNote, MdOutlineTask } from "react-icons/md";
import { useParams } from "react-router-dom";
@@ -27,17 +28,18 @@ import { isStatePending, useAutoRefresh } from "src/utils";
import { Header } from "./Header";
-const tabs = [
- { icon: <MdOutlineTask />, label: "Task Instances", value: "" },
- { icon: <MdOutlineEventNote />, label: "Audit Logs", value: "events" },
- { icon: <FiCode />, label: "Code", value: "code" },
- { icon: <MdDetails />, label: "Details", value: "details" },
- { icon: <FiDatabase />, label: "Asset Events", value: "asset_events" },
-];
-
export const Run = () => {
+ const { t: translate } = useTranslation("dag");
const { dagId = "", runId = "" } = useParams();
+ const tabs = [
+ { icon: <MdOutlineTask />, label: translate("tabs.taskInstances"), value:
"" },
+ { icon: <FiDatabase />, label: translate("tabs.assetEvents"), value:
"asset_events" },
+ { icon: <MdOutlineEventNote />, label: translate("tabs.auditLog"), value:
"events" },
+ { icon: <FiCode />, label: translate("tabs.code"), value: "code" },
+ { icon: <MdDetails />, label: translate("tabs.details"), value: "details"
},
+ ];
+
const refetchInterval = useAutoRefresh({ dagId });
const {
diff --git a/airflow-core/src/airflow/ui/src/pages/Task/Header.tsx
b/airflow-core/src/airflow/ui/src/pages/Task/Header.tsx
index e90793c2f12..2681dd52e39 100644
--- a/airflow-core/src/airflow/ui/src/pages/Task/Header.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Task/Header.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { useTranslation } from "react-i18next";
import { FiBookOpen } from "react-icons/fi";
import type { TaskResponse } from "openapi/requests/types.gen";
@@ -23,23 +24,27 @@ import { TaskIcon } from "src/assets/TaskIcon";
import DisplayMarkdownButton from "src/components/DisplayMarkdownButton";
import { HeaderCard } from "src/components/HeaderCard";
-export const Header = ({ task }: { readonly task: TaskResponse }) => (
- <HeaderCard
- actions={
- task.doc_md === null ? undefined : (
- <DisplayMarkdownButton
- header="Task Documentation"
- icon={<FiBookOpen />}
- mdContent={task.doc_md}
- text="Task Docs"
- />
- )
- }
- icon={<TaskIcon />}
- stats={[
- { label: "Operator", value: task.operator_name },
- { label: "Trigger Rule", value: task.trigger_rule },
- ]}
- title={`${task.task_display_name}${task.is_mapped ? " [ ]" : ""}`}
- />
-);
+export const Header = ({ task }: { readonly task: TaskResponse }) => {
+ const { t: translate } = useTranslation();
+
+ return (
+ <HeaderCard
+ actions={
+ task.doc_md === null ? undefined : (
+ <DisplayMarkdownButton
+ header={translate("task.documentation")}
+ icon={<FiBookOpen />}
+ mdContent={task.doc_md}
+ text={translate("docs.documentation")}
+ />
+ )
+ }
+ icon={<TaskIcon />}
+ stats={[
+ { label: translate("task.operator"), value: task.operator_name },
+ { label: translate("task.triggerRule"), value: task.trigger_rule },
+ ]}
+ title={`${task.task_display_name}${task.is_mapped ? " [ ]" : ""}`}
+ />
+ );
+};
diff --git a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
index e36a613eb61..79b39d21b9f 100644
--- a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
@@ -19,6 +19,7 @@
import { Box, HStack, Skeleton, SimpleGrid } from "@chakra-ui/react";
import dayjs from "dayjs";
import { useState } from "react";
+import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { useTaskInstanceServiceGetTaskInstances } from "openapi/queries";
@@ -31,6 +32,7 @@ const defaultHour = "24";
export const Overview = () => {
const { dagId = "", groupId, taskId } = useParams();
+ const { t: translate } = useTranslation("dag");
const now = dayjs();
const [startDate, setStartDate] = useState(now.subtract(Number(defaultHour),
"hour").toISOString());
@@ -86,7 +88,9 @@ export const Overview = () => {
timestamp: ti.start_date ?? ti.logical_date,
}))}
isLoading={isFailedTaskInstancesLoading}
- label="Failed Task Instance"
+ label={translate("overview.buttons.failedTaskInstance", {
+ count: failedTaskInstances?.total_entries ?? 0,
+ })}
route={{
pathname: "task_instances",
search: "state=failed",
diff --git a/airflow-core/src/airflow/ui/src/pages/Task/Task.tsx
b/airflow-core/src/airflow/ui/src/pages/Task/Task.tsx
index 0dc3dce538c..1095642b3d2 100644
--- a/airflow-core/src/airflow/ui/src/pages/Task/Task.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Task/Task.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { ReactFlowProvider } from "@xyflow/react";
+import { useTranslation } from "react-i18next";
import { LuChartColumn } from "react-icons/lu";
import { MdOutlineEventNote, MdOutlineTask } from "react-icons/md";
import { useParams } from "react-router-dom";
@@ -28,16 +29,17 @@ import { getGroupTask } from "src/utils/groupTask";
import { GroupTaskHeader } from "./GroupTaskHeader";
import { Header } from "./Header";
-const tabs = [
- { icon: <LuChartColumn />, label: "Overview", value: "" },
- { icon: <MdOutlineTask />, label: "Task Instances", value: "task_instances"
},
- { icon: <MdOutlineEventNote />, label: "Audit Logs", value: "events" },
-];
-
export const Task = () => {
+ const { t: translate } = useTranslation("dag");
const { dagId = "", groupId, taskId } = useParams();
- const displayTabs = groupId === undefined ? tabs : tabs.filter((tab) =>
tab.label !== "Events");
+ const tabs = [
+ { icon: <LuChartColumn />, label: translate("tabs.overview"), value: "" },
+ { icon: <MdOutlineTask />, label: translate("tabs.taskInstances"), value:
"task_instances" },
+ { icon: <MdOutlineEventNote />, label: translate("tabs.auditLog"), value:
"events" },
+ ];
+
+ const displayTabs = groupId === undefined ? tabs : tabs.filter((tab) =>
tab.value !== "events");
const {
data: task,
diff --git
a/airflow-core/src/airflow/ui/src/pages/TaskInstance/BlockingDeps.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/BlockingDeps.tsx
index e14502b7475..2ed76613df7 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/BlockingDeps.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/BlockingDeps.tsx
@@ -17,11 +17,13 @@
* under the License.
*/
import { Box, Table, Heading } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { useTaskInstanceServiceGetTaskInstanceDependencies } from
"openapi/queries";
import type { TaskInstanceResponse } from "openapi/requests/types.gen";
export const BlockingDeps = ({ taskInstance }: { readonly taskInstance:
TaskInstanceResponse }) => {
+ const { t: translate } = useTranslation();
const { data } = useTaskInstanceServiceGetTaskInstanceDependencies({
dagId: taskInstance.dag_id,
dagRunId: taskInstance.dag_run_id,
@@ -36,13 +38,13 @@ export const BlockingDeps = ({ taskInstance }: { readonly
taskInstance: TaskInst
return (
<Box flexGrow={1} mt={3}>
<Heading py={2} size="sm">
- Dependencies Blocking Task From Getting Scheduled
+ {translate("dag.blockingDeps.title")}
</Heading>
<Table.Root striped>
<Table.Body>
<Table.Row>
- <Table.ColumnHeader>Dependency</Table.ColumnHeader>
- <Table.ColumnHeader>Reason</Table.ColumnHeader>
+
<Table.ColumnHeader>{translate("dag.blockingDeps.dependency")}</Table.ColumnHeader>
+
<Table.ColumnHeader>{translate("dag.blockingDeps.reason")}</Table.ColumnHeader>
</Table.Row>
{data.dependencies.map((dep) => (
<Table.Row key={dep.name}>
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Details.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Details.tsx
index 9fed186008f..62ef8472e0d 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Details.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Details.tsx
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Box, Flex, HStack, Table, Heading } from "@chakra-ui/react";
+import { Box, Flex, HStack, Table } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { useParams, useSearchParams } from "react-router-dom";
import {
@@ -35,6 +36,7 @@ import { ExtraLinks } from "./ExtraLinks";
import { TriggererInfo } from "./TriggererInfo";
export const Details = () => {
+ const { t: translate } = useTranslation();
const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
@@ -94,22 +96,19 @@ export const Details = () => {
{taskInstance !== undefined && (taskInstance.trigger ??
taskInstance.triggerer_job) ? (
<TriggererInfo taskInstance={taskInstance} />
) : undefined}
- <Heading py={2} size="sm">
- Task Instance Info
- </Heading>
<Table.Root striped>
<Table.Body>
<Table.Row>
- <Table.Cell>State</Table.Cell>
+ <Table.Cell>{translate("state")}</Table.Cell>
<Table.Cell>
<Flex gap={1}>
<StateBadge state={tryInstance?.state} />
- {tryInstance?.state ?? "no status"}
+ {tryInstance?.state ?? translate("states.no_status")}
</Flex>
</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Task ID</Table.Cell>
+ <Table.Cell>{translate("taskId")}</Table.Cell>
<Table.Cell>
<HStack>
{tryInstance?.task_id}
@@ -120,7 +119,7 @@ export const Details = () => {
</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Run ID</Table.Cell>
+ <Table.Cell>{translate("runId")}</Table.Cell>
<Table.Cell>
<HStack>
{tryInstance?.dag_run_id}
@@ -131,15 +130,15 @@ export const Details = () => {
</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Map Index</Table.Cell>
+ <Table.Cell>{translate("mapIndex")}</Table.Cell>
<Table.Cell>{tryInstance?.map_index}</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Operator</Table.Cell>
+ <Table.Cell>{translate("task.operator")}</Table.Cell>
<Table.Cell>{tryInstance?.operator}</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Duration</Table.Cell>
+ <Table.Cell>{translate("duration")}</Table.Cell>
<Table.Cell>
{Boolean(tryInstance?.start_date) // eslint-disable-next-line
unicorn/no-null
? getDuration(tryInstance?.start_date ?? null,
tryInstance?.end_date ?? null)
@@ -147,25 +146,25 @@ export const Details = () => {
</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Started</Table.Cell>
+ <Table.Cell>{translate("startDate")}</Table.Cell>
<Table.Cell>
<Time datetime={tryInstance?.start_date} />
</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Ended</Table.Cell>
+ <Table.Cell>{translate("endDate")}</Table.Cell>
<Table.Cell>
<Time datetime={tryInstance?.end_date} />
</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Dag Version</Table.Cell>
+ <Table.Cell>{translate("taskInstance.dagVersion")}</Table.Cell>
<Table.Cell>
<DagVersionDetails dagVersion={taskInstance?.dag_version} />
</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Process ID (PID)</Table.Cell>
+ <Table.Cell>{translate("taskInstance.pid")}</Table.Cell>
<Table.Cell>
<HStack>
{tryInstance?.pid}
@@ -176,7 +175,7 @@ export const Details = () => {
</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Hostname</Table.Cell>
+ <Table.Cell>{translate("taskInstance.hostname")}</Table.Cell>
<Table.Cell>
<HStack>
{tryInstance?.hostname}
@@ -187,37 +186,25 @@ export const Details = () => {
</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Pool</Table.Cell>
- <Table.Cell>{tryInstance?.pool}</Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Pool Slots</Table.Cell>
- <Table.Cell>{tryInstance?.pool_slots}</Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Executor</Table.Cell>
- <Table.Cell>{tryInstance?.executor}</Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Executor Config</Table.Cell>
- <Table.Cell>{tryInstance?.executor_config}</Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Unix Name</Table.Cell>
+ <Table.Cell>{translate("taskInstance.unixname")}</Table.Cell>
<Table.Cell>{tryInstance?.unixname}</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Max Tries</Table.Cell>
- <Table.Cell>{tryInstance?.max_tries}</Table.Cell>
+ <Table.Cell>{translate("taskInstance.pool")}</Table.Cell>
+ <Table.Cell>{tryInstance?.pool}</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Queue</Table.Cell>
+ <Table.Cell>{translate("taskInstance.queue")}</Table.Cell>
<Table.Cell>{tryInstance?.queue}</Table.Cell>
</Table.Row>
<Table.Row>
- <Table.Cell>Priority Weight</Table.Cell>
+ <Table.Cell>{translate("taskInstance.priorityWeight")}</Table.Cell>
<Table.Cell>{tryInstance?.priority_weight}</Table.Cell>
</Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("taskInstance.executor")}</Table.Cell>
+ <Table.Cell>{tryInstance?.executor_config}</Table.Cell>
+ </Table.Row>
</Table.Body>
</Table.Root>
</Box>
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstance/ExtraLinks.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/ExtraLinks.tsx
index efad2007b4e..167346175eb 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/ExtraLinks.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/ExtraLinks.tsx
@@ -17,11 +17,13 @@
* under the License.
*/
import { Box, Button, Heading, HStack } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { useTaskInstanceServiceGetExtraLinks } from "openapi/queries";
export const ExtraLinks = () => {
+ const { t: translate } = useTranslation();
const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
const { data } = useTaskInstanceServiceGetExtraLinks({
@@ -33,7 +35,7 @@ export const ExtraLinks = () => {
return data && Object.keys(data.extra_links).length > 0 ? (
<Box py={1}>
- <Heading size="sm">Extra Links</Heading>
+ <Heading size="sm">{translate("dag.extraLinks")}</Heading>
<HStack gap={2} py={2}>
{Object.entries(data.extra_links).map(([key, value], _) =>
value === null ? undefined : (
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Header.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Header.tsx
index 702060ae285..34e80d3716a 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Header.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Header.tsx
@@ -18,6 +18,7 @@
*/
import { Box } from "@chakra-ui/react";
import { useCallback, useRef, useState } from "react";
+import { useTranslation } from "react-i18next";
import { FiMessageSquare } from "react-icons/fi";
import { MdOutlineTask } from "react-icons/md";
@@ -38,20 +39,25 @@ export const Header = ({
readonly isRefreshing?: boolean;
readonly taskInstance: TaskInstanceResponse;
}) => {
+ const { t: translate } = useTranslation();
const containerRef = useRef<HTMLDivElement>();
const containerWidth = useContainerWidth(containerRef);
const stats = [
- { label: "Operator", value: taskInstance.operator },
- ...(taskInstance.map_index > -1 ? [{ label: "Map Index", value:
taskInstance.rendered_map_index }] : []),
- ...(taskInstance.try_number > 1 ? [{ label: "Try Number", value:
taskInstance.try_number }] : []),
- { label: "Start", value: <Time datetime={taskInstance.start_date} /> },
- { label: "End", value: <Time datetime={taskInstance.end_date} /> },
+ { label: translate("task.operator"), value: taskInstance.operator },
+ ...(taskInstance.map_index > -1
+ ? [{ label: translate("mapIndex"), value:
taskInstance.rendered_map_index }]
+ : []),
+ ...(taskInstance.try_number > 1
+ ? [{ label: translate("tryNumber"), value: taskInstance.try_number }]
+ : []),
+ { label: translate("startDate"), value: <Time
datetime={taskInstance.start_date} /> },
+ { label: translate("endDate"), value: <Time
datetime={taskInstance.end_date} /> },
...(Boolean(taskInstance.start_date)
- ? [{ label: "Duration", value: getDuration(taskInstance.start_date,
taskInstance.end_date) }]
+ ? [{ label: translate("duration"), value:
getDuration(taskInstance.start_date, taskInstance.end_date) }]
: []),
{
- label: "DAG Version",
+ label: translate("taskInstance.dagVersion"),
value: <DagVersion version={taskInstance.dag_version} />,
},
];
@@ -88,14 +94,14 @@ export const Header = ({
actions={
<>
<EditableMarkdownButton
- header="Task Instance Note"
+ header={translate("note.taskInstance")}
icon={<FiMessageSquare />}
isPending={isPending}
mdContent={note}
onConfirm={onConfirm}
- placeholder="Add a note..."
+ placeholder={translate("note.placeholder")}
setMdContent={setNote}
- text={Boolean(taskInstance.note) ? "Note" : "Add a note"}
+ text={Boolean(taskInstance.note) ? translate("note.label") :
translate("note.add")}
withText={containerWidth > 700}
/>
<ClearTaskInstanceButton
diff --git
a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/ExternalLogLink.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/ExternalLogLink.tsx
index e54d2bbd873..50aafc5d773 100644
---
a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/ExternalLogLink.tsx
+++
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/ExternalLogLink.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Button } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { useTaskInstanceServiceGetExternalLogUrl } from "openapi/queries";
@@ -29,6 +30,7 @@ type Props = {
};
export const ExternalLogLink = ({ externalLogName, taskInstance, tryNumber }:
Props) => {
+ const { t: translate } = useTranslation("dag");
const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
const {
@@ -57,7 +59,7 @@ export const ExternalLogLink = ({ externalLogName,
taskInstance, tryNumber }: Pr
return (
<Button asChild colorScheme="blue" variant="outline">
<a href={externalLogData.url} rel="noopener noreferrer" target="_blank">
- View logs in {externalLogName} (attempt {tryNumber})
+ {translate("logs.viewInExternal", { attempt: tryNumber, name:
externalLogName })}
</a>
</Button>
);
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
index 25fce170dd0..96e9d06fccc 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Logs/Logs.tsx
@@ -19,6 +19,7 @@
import { Box, Heading, VStack } from "@chakra-ui/react";
import { useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
+import { useTranslation } from "react-i18next";
import { useParams, useSearchParams } from "react-router-dom";
import { useTaskInstanceServiceGetMappedTaskInstance } from "openapi/queries";
@@ -34,6 +35,7 @@ import { TaskLogHeader } from "./TaskLogHeader";
export const Logs = () => {
const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
+ const { t: translate } = useTranslation("dag");
const tryNumberParam = searchParams.get(SearchParamsKeys.TRY_NUMBER);
const logLevelFilters = searchParams.getAll(SearchParamsKeys.LOG_LEVEL);
@@ -104,7 +106,7 @@ export const Logs = () => {
/>
{showExternalLogRedirect && externalLogName && taskInstance ? (
tryNumber === undefined ? (
- <p>No try number</p>
+ <p>{translate("logs.noTryNumber")}</p>
) : (
<ExternalLogLink
externalLogName={externalLogName}
diff --git
a/airflow-core/src/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
index c11fce76741..c7fa69d6811 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { ReactFlowProvider } from "@xyflow/react";
+import { useTranslation } from "react-i18next";
import { FiCode, FiDatabase } from "react-icons/fi";
import { MdDetails, MdOutlineEventNote, MdOutlineTask, MdReorder, MdSyncAlt }
from "react-icons/md";
import { PiBracketsCurlyBold } from "react-icons/pi";
@@ -32,19 +33,24 @@ import { isStatePending, useAutoRefresh } from "src/utils";
import { Header } from "./Header";
-const tabs = [
- { icon: <MdReorder />, label: "Logs", value: "" },
- { icon: <PiBracketsCurlyBold />, label: "Rendered Templates", value:
"rendered_templates" },
- { icon: <MdSyncAlt />, label: "XCom", value: "xcom" },
- { icon: <MdOutlineEventNote />, label: "Audit Logs", value: "events" },
- { icon: <FiCode />, label: "Code", value: "code" },
- { icon: <MdDetails />, label: "Details", value: "details" },
- { icon: <FiDatabase />, label: "Asset Events", value: "asset_events" },
-];
-
export const TaskInstance = () => {
+ const { t: translate } = useTranslation("dag");
const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
+ const tabs = [
+ { icon: <MdReorder />, label: translate("tabs.logs"), value: "" },
+ {
+ icon: <PiBracketsCurlyBold />,
+ label: translate("tabs.renderedTemplates"),
+ value: "rendered_templates",
+ },
+ { icon: <MdSyncAlt />, label: translate("tabs.xcom"), value: "xcom" },
+ { icon: <FiDatabase />, label: translate("tabs.assetEvents"), value:
"asset_events" },
+ { icon: <MdOutlineEventNote />, label: translate("tabs.auditLog"), value:
"events" },
+ { icon: <FiCode />, label: translate("tabs.code"), value: "code" },
+ { icon: <MdDetails />, label: translate("tabs.details"), value: "details"
},
+ ];
+
const refetchInterval = useAutoRefresh({ dagId });
const {
@@ -98,7 +104,9 @@ export const TaskInstance = () => {
...tabs.slice(0, 1),
{
icon: <MdOutlineTask />,
- label: `Task Instances [${mappedTaskInstance?.task_count ?? ""}]`,
+ label: translate("tabs.mappedTaskInstances_other", {
+ count: Number(mappedTaskInstance?.task_count ?? 0),
+ }),
value: "task_instances",
},
...tabs.slice(1),
diff --git
a/airflow-core/src/airflow/ui/src/pages/TaskInstance/TriggererInfo.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/TriggererInfo.tsx
index cc718810d58..9787635b38a 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/TriggererInfo.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/TriggererInfo.tsx
@@ -17,42 +17,47 @@
* under the License.
*/
import { Box, Table, Heading } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import type { TaskInstanceResponse } from "openapi/requests/types.gen";
import Time from "src/components/Time";
-export const TriggererInfo = ({ taskInstance }: { readonly taskInstance:
TaskInstanceResponse }) => (
- <Box py={1}>
- <Heading py={1} size="sm">
- Triggerer Info
- </Heading>
- <Table.Root striped>
- <Table.Body>
- <Table.Row>
- <Table.Cell>Trigger class</Table.Cell>
- <Table.Cell>{taskInstance.trigger?.classpath}</Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Trigger ID</Table.Cell>
- <Table.Cell>{taskInstance.trigger?.id}</Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Trigger creation time</Table.Cell>
- <Table.Cell>
- <Time datetime={taskInstance.trigger?.created_date} />
- </Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Assigned triggerer</Table.Cell>
- <Table.Cell>{taskInstance.triggerer_job?.hostname}</Table.Cell>
- </Table.Row>
- <Table.Row>
- <Table.Cell>Latest triggerer heartbeat</Table.Cell>
- <Table.Cell>
- <Time datetime={taskInstance.triggerer_job?.latest_heartbeat} />
- </Table.Cell>
- </Table.Row>
- </Table.Body>
- </Table.Root>
- </Box>
-);
+export const TriggererInfo = ({ taskInstance }: { readonly taskInstance:
TaskInstanceResponse }) => {
+ const { t: translate } = useTranslation();
+
+ return (
+ <Box py={1}>
+ <Heading py={1} size="sm">
+ {translate("taskInstance.triggerer.title")}
+ </Heading>
+ <Table.Root striped>
+ <Table.Body>
+ <Table.Row>
+
<Table.Cell>{translate("taskInstance.triggerer.class")}</Table.Cell>
+ <Table.Cell>{taskInstance.trigger?.classpath}</Table.Cell>
+ </Table.Row>
+ <Table.Row>
+ <Table.Cell>{translate("taskInstance.triggerer.id")}</Table.Cell>
+ <Table.Cell>{taskInstance.trigger?.id}</Table.Cell>
+ </Table.Row>
+ <Table.Row>
+
<Table.Cell>{translate("taskInstance.triggerer.createdAt")}</Table.Cell>
+ <Table.Cell>
+ <Time datetime={taskInstance.trigger?.created_date} />
+ </Table.Cell>
+ </Table.Row>
+ <Table.Row>
+
<Table.Cell>{translate("taskInstance.triggerer.assigned")}</Table.Cell>
+ <Table.Cell>{taskInstance.triggerer_job?.hostname}</Table.Cell>
+ </Table.Row>
+ <Table.Row>
+
<Table.Cell>{translate("taskInstance.triggerer.latestHeartbeat")}</Table.Cell>
+ <Table.Cell>
+ <Time datetime={taskInstance.triggerer_job?.latest_heartbeat} />
+ </Table.Cell>
+ </Table.Row>
+ </Table.Body>
+ </Table.Root>
+ </Box>
+ );
+};
diff --git a/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx
b/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx
index 1fe894d3edf..842bd7ac7d4 100644
--- a/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Box, Link } from "@chakra-ui/react";
+import { Box, Heading, Link } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
import { useTranslation } from "react-i18next";
import { Link as RouterLink, useParams } from "react-router-dom";
@@ -119,6 +119,9 @@ export const XCom = () => {
return (
<Box>
+ {dagId === "~" && runId === "~" && taskId === "~" ? (
+ <Heading size="md">{translate("xcom.title")}</Heading>
+ ) : undefined}
<ErrorAlert error={error} />
<DataTable
columns={columns(translate)}
@@ -127,7 +130,7 @@ export const XCom = () => {
initialState={tableURLState}
isFetching={isFetching}
isLoading={isLoading}
- modelName="XCom"
+ modelName={translate("xcom.title")}
onStateChange={setTableURLState}
skeletonCount={undefined}
total={data ? data.total_entries : 0}
diff --git a/airflow-core/src/airflow/ui/src/utils/TrimText.tsx
b/airflow-core/src/airflow/ui/src/utils/TrimText.tsx
index 39d8993bad8..3ae671d529a 100644
--- a/airflow-core/src/airflow/ui/src/utils/TrimText.tsx
+++ b/airflow-core/src/airflow/ui/src/utils/TrimText.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Text, Box, useDisclosure, Heading, Stack } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { Dialog, Tooltip } from "src/components/ui";
@@ -44,6 +45,7 @@ export const TrimText = ({
showTooltip = false,
text,
}: TrimTextProps) => {
+ const { t: translate } = useTranslation(["components"]);
const safeText = text ?? "";
const { isTrimmed, trimmedText } = trimText(safeText, charLimit);
@@ -70,7 +72,7 @@ export const TrimText = ({
<Dialog.Root onOpenChange={onClose} open={isClickable ? open :
undefined} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
- <Heading size="xl">Details</Heading>
+ <Heading size="xl">{translate("trimText.details")}</Heading>
</Dialog.Header>
<Dialog.CloseTrigger />
@@ -100,14 +102,14 @@ export const TrimText = ({
color={isEmpty ? "gray.emphasized" : undefined}
fontWeight={isEmpty ? "bold" : "normal"}
>
- {isEmpty ? "Empty" : String(value)}
+ {isEmpty ? translate("trimText.empty") :
String(value)}
</Text>
</Box>
</Box>
);
})
) : (
- <Text>No content available.</Text>
+ <Text>{translate("trimText.noContent")}</Text>
)}
</Stack>
</Dialog.Body>
diff --git a/airflow-core/src/airflow/ui/src/utils/index.ts
b/airflow-core/src/airflow/ui/src/utils/index.ts
index 49924fabcad..d1badfb8f00 100644
--- a/airflow-core/src/airflow/ui/src/utils/index.ts
+++ b/airflow-core/src/airflow/ui/src/utils/index.ts
@@ -18,7 +18,6 @@
*/
export { capitalize } from "./capitalize";
-export { pluralize } from "./pluralize";
export { getDuration, renderDuration } from "./datetimeUtils";
export { getMetaKey } from "./getMetaKey";
export { useContainerWidth } from "./useContainerWidth";
diff --git a/airflow-core/src/airflow/ui/src/utils/pluralize.test.ts
b/airflow-core/src/airflow/ui/src/utils/pluralize.test.ts
deleted file mode 100644
index 541caf62318..00000000000
--- a/airflow-core/src/airflow/ui/src/utils/pluralize.test.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-/*!
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { describe, expect, it } from "vitest";
-
-import { pluralize } from "./pluralize";
-
-type PluralizeTestCase = {
- in: [string, number, string?, boolean?];
- out: string;
-};
-
-const pluralizeTestCases = [
- { in: ["DAG", 0, undefined, undefined], out: "0 DAGs" },
- { in: ["DAG", 1, undefined, undefined], out: "1 DAG" },
- { in: ["DAG", 12_000, undefined, undefined], out: "12,000 DAGs" },
- { in: ["DAG", 12_000_000, undefined, undefined], out: "12,000,000 DAGs" },
- { in: ["DAG", 0, undefined, undefined], out: "0 DAGs" },
- { in: ["DAG", 1, undefined, undefined], out: "1 DAG" },
- { in: ["DAG", 12_000, undefined, undefined], out: "12,000 DAGs" },
- { in: ["DAG", 12_000_000, undefined, undefined], out: "12,000,000 DAGs" },
- // Omit the count.
- { in: ["DAG", 0, undefined, true], out: "DAGs" },
- { in: ["DAG", 1, undefined, true], out: "DAG" },
- { in: ["DAG", 12_000, undefined, true], out: "DAGs" },
- { in: ["DAG", 12_000_000, undefined, true], out: "DAGs" },
- { in: ["DAG", 0, undefined, true], out: "DAGs" },
- { in: ["DAG", 1, undefined, true], out: "DAG" },
- { in: ["DAG", 12_000, undefined, true], out: "DAGs" },
- { in: ["DAG", 12_000_000, undefined, true], out: "DAGs" },
- // The casing of the string is preserved.
- { in: ["goose", 0, "geese", undefined], out: "0 geese" },
- { in: ["goose", 1, "geese", undefined], out: "1 goose" },
- // The plural form is different from the singular form.
- { in: ["Goose", 0, "Geese", undefined], out: "0 Geese" },
- { in: ["Goose", 1, "Geese", undefined], out: "1 Goose" },
- { in: ["Goose", 12_000, "Geese", undefined], out: "12,000 Geese" },
- { in: ["Goose", 12_000_000, "Geese", undefined], out: "12,000,000 Geese" },
- { in: ["Goose", 0, "Geese", undefined], out: "0 Geese" },
- { in: ["Goose", 1, "Geese", undefined], out: "1 Goose" },
- { in: ["Goose", 12_000, "Geese", undefined], out: "12,000 Geese" },
- { in: ["Goose", 12_000_000, "Geese", undefined], out: "12,000,000 Geese" },
- // In the case of "Moose", the plural is the same as the singular and you
- // probably wouldn't elect to use this function at all, but there could be
- // cases where dynamic data makes it unavoidable.
- { in: ["Moose", 0, "Moose", undefined], out: "0 Moose" },
- { in: ["Moose", 1, "Moose", undefined], out: "1 Moose" },
- { in: ["Moose", 12_000, "Moose", undefined], out: "12,000 Moose" },
- { in: ["Moose", 12_000_000, "Moose", undefined], out: "12,000,000 Moose" },
- { in: ["Moose", 0, "Moose", undefined], out: "0 Moose" },
- { in: ["Moose", 1, "Moose", undefined], out: "1 Moose" },
- { in: ["Moose", 12_000, "Moose", undefined], out: "12,000 Moose" },
- { in: ["Moose", 12_000_000, "Moose", undefined], out: "12,000,000 Moose" },
-] as const satisfies Array<PluralizeTestCase>;
-
-describe("pluralize", () => {
- it("case", () => {
- pluralizeTestCases.forEach((testCase) =>
- expect(pluralize(testCase.in[0], testCase.in[1], testCase.in[2],
testCase.in[3])).toEqual(testCase.out),
- );
- });
-});
diff --git a/airflow-core/src/airflow/ui/src/utils/pluralize.ts
b/airflow-core/src/airflow/ui/src/utils/pluralize.ts
deleted file mode 100644
index b8e13dc84a8..00000000000
--- a/airflow-core/src/airflow/ui/src/utils/pluralize.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*!
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export const pluralize = (
- singularLabel: string,
- count = 0,
- pluralLabel = `${singularLabel}s`,
- omitCount = false,
- // eslint-disable-next-line @typescript-eslint/max-params
-) =>
- // toLocaleString() will add commas for thousands, millions, etc.
- `${omitCount ? "" : `${count.toLocaleString()} `}${count === 1 ?
singularLabel : pluralLabel}`;