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 474d062d7f5 i18n: Add translations for Assets, Browse, and Admin pages
(#51558)
474d062d7f5 is described below
commit 474d062d7f58a61df272f996cc732c18ca2173bc
Author: LI,JHE-CHEN <[email protected]>
AuthorDate: Thu Jun 12 04:43:26 2025 +0800
i18n: Add translations for Assets, Browse, and Admin pages (#51558)
* i18n: translate variables page
* i18n: translate pools page
* i18n: translate Providers & Plugins page
* i18n: translate Connections and Config page
* i18n: translate Assets & Browse page
* i18n: Address feedback on PR#51558
* fix: Modify translations
* fix: Modify translations based on PR feedback
---
.../ui/src/components/FlexibleForm/index.tsx | 1 -
airflow-core/src/airflow/ui/src/i18n/config.ts | 26 ++--
.../src/airflow/ui/src/i18n/locales/de/admin.json | 6 +
.../ui/src/i18n/locales/de/connections.json | 4 -
.../src/airflow/ui/src/i18n/locales/en/admin.json | 141 +++++++++++++++++++++
.../src/airflow/ui/src/i18n/locales/en/assets.json | 10 ++
.../src/airflow/ui/src/i18n/locales/en/browse.json | 22 ++++
.../src/airflow/ui/src/i18n/locales/en/common.json | 4 +
.../ui/src/i18n/locales/en/connections.json | 4 -
.../src/airflow/ui/src/i18n/locales/pl/admin.json | 6 +
.../ui/src/i18n/locales/pl/connections.json | 4 -
.../airflow/ui/src/i18n/locales/zh-TW/admin.json | 141 +++++++++++++++++++++
.../airflow/ui/src/i18n/locales/zh-TW/assets.json | 10 ++
.../airflow/ui/src/i18n/locales/zh-TW/browse.json | 22 ++++
.../airflow/ui/src/i18n/locales/zh-TW/common.json | 49 ++++++-
.../ui/src/i18n/locales/zh-TW/connections.json | 4 -
.../airflow/ui/src/pages/AssetsList/AssetsList.tsx | 23 ++--
.../ui/src/pages/AssetsList/DependencyPopover.tsx | 69 +++++-----
.../src/airflow/ui/src/pages/Configs/Configs.tsx | 18 ++-
.../src/pages/Connections/AddConnectionButton.tsx | 8 +-
.../ui/src/pages/Connections/ConnectionForm.tsx | 32 ++---
.../ui/src/pages/Connections/Connections.tsx | 27 ++--
.../pages/Connections/DeleteConnectionButton.tsx | 22 ++--
.../pages/Connections/DeleteConnectionsButton.tsx | 19 +--
.../src/pages/Connections/EditConnectionButton.tsx | 8 +-
.../src/pages/Connections/TestConnectionButton.tsx | 8 +-
.../pages/Dashboard/Stats/PluginImportErrors.tsx | 8 +-
.../Dashboard/Stats/PluginImportErrorsModal.tsx | 6 +-
.../src/airflow/ui/src/pages/Events/Events.tsx | 39 +++---
airflow-core/src/airflow/ui/src/pages/Plugins.tsx | 16 ++-
.../airflow/ui/src/pages/Pools/AddPoolButton.tsx | 6 +-
.../ui/src/pages/Pools/DeletePoolButton.tsx | 10 +-
.../airflow/ui/src/pages/Pools/EditPoolButton.tsx | 8 +-
.../src/airflow/ui/src/pages/Pools/PoolBarCard.tsx | 69 +++++-----
.../src/airflow/ui/src/pages/Pools/PoolForm.tsx | 20 +--
.../src/airflow/ui/src/pages/Pools/Pools.tsx | 23 ++--
.../src/airflow/ui/src/pages/Providers.tsx | 18 ++-
.../src/pages/Variables/DeleteVariablesButton.tsx | 22 ++--
.../src/pages/Variables/ImportVariablesButton.tsx | 6 +-
.../ui/src/pages/Variables/ImportVariablesForm.tsx | 30 +++--
.../Variables/ManageVariable/AddVariableButton.tsx | 6 +-
.../ManageVariable/DeleteVariableButton.tsx | 22 ++--
.../ManageVariable/EditVariableButton.tsx | 8 +-
.../Variables/ManageVariable/VariableForm.tsx | 20 +--
.../airflow/ui/src/pages/Variables/Variables.tsx | 28 ++--
.../src/airflow/ui/src/pages/XCom/XCom.tsx | 19 +--
46 files changed, 780 insertions(+), 292 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/components/FlexibleForm/index.tsx
b/airflow-core/src/airflow/ui/src/components/FlexibleForm/index.tsx
index 704b5d956ae..93460cadb18 100644
--- a/airflow-core/src/airflow/ui/src/components/FlexibleForm/index.tsx
+++ b/airflow-core/src/airflow/ui/src/components/FlexibleForm/index.tsx
@@ -23,6 +23,5 @@ export type FlexibleFormElementProps = {
};
export const flexibleFormDefaultSection = "Run Parameters";
-export const flexibleFormExtraFieldSection = "Extra Fields";
export { FlexibleForm } from "./FlexibleForm";
diff --git a/airflow-core/src/airflow/ui/src/i18n/config.ts
b/airflow-core/src/airflow/ui/src/i18n/config.ts
index 11100bb7998..a8c93554d8a 100644
--- a/airflow-core/src/airflow/ui/src/i18n/config.ts
+++ b/airflow-core/src/airflow/ui/src/i18n/config.ts
@@ -20,15 +20,17 @@ import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import { initReactI18next } from "react-i18next";
+import deAdmin from "./locales/de/admin.json";
import deCommon from "./locales/de/common.json";
import deComponents from "./locales/de/components.json";
-import deConnections from "./locales/de/connections.json";
import deDag from "./locales/de/dag.json";
import deDags from "./locales/de/dags.json";
import deDashboard from "./locales/de/dashboard.json";
+import enAdmin from "./locales/en/admin.json";
+import enAssets from "./locales/en/assets.json";
+import enBrowse from "./locales/en/browse.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";
@@ -38,14 +40,16 @@ import koCommon from "./locales/ko/common.json";
import koDashboard from "./locales/ko/dashboard.json";
import nlCommon from "./locales/nl/common.json";
import nlDashboard from "./locales/nl/dashboard.json";
+import plAdmin from "./locales/pl/admin.json";
import plCommon from "./locales/pl/common.json";
import plComponents from "./locales/pl/components.json";
-import plConnections from "./locales/pl/connections.json";
import plDag from "./locales/pl/dag.json";
import plDags from "./locales/pl/dags.json";
import plDashboard from "./locales/pl/dashboard.json";
+import zhTWAdmin from "./locales/zh-TW/admin.json";
+import zhTWAssets from "./locales/zh-TW/assets.json";
+import zhTWBrowse from "./locales/zh-TW/browse.json";
import zhTWCommon from "./locales/zh-TW/common.json";
-import zhTWConnections from "./locales/zh-TW/connections.json";
import zhTWDags from "./locales/zh-TW/dags.json";
import zhTWDashboard from "./locales/zh-TW/dashboard.json";
@@ -63,21 +67,23 @@ export const supportedLanguages = [
] as const;
export const defaultLanguage = "en";
-export const namespaces = ["common", "dashboard", "dags", "connections"] as
const;
+export const namespaces = ["common", "dashboard", "dags", "admin", "browse",
"assets"] as const;
const resources = {
de: {
+ admin: deAdmin,
common: deCommon,
components: deComponents,
- connections: deConnections,
dag: deDag,
dags: deDags,
dashboard: deDashboard,
},
en: {
+ admin: enAdmin,
+ assets: enAssets,
+ browse: enBrowse,
common: enCommon,
components: enComponents,
- connections: enConnections,
dag: enDag,
dags: enDags,
dashboard: enDashboard,
@@ -95,16 +101,18 @@ const resources = {
dashboard: nlDashboard,
},
pl: {
+ admin: plAdmin,
common: plCommon,
components: plComponents,
- connections: plConnections,
dag: plDag,
dags: plDags,
dashboard: plDashboard,
},
"zh-TW": {
+ admin: zhTWAdmin,
+ assets: zhTWAssets,
+ browse: zhTWBrowse,
common: zhTWCommon,
- connections: zhTWConnections,
dags: zhTWDags,
dashboard: zhTWDashboard,
},
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/de/admin.json
b/airflow-core/src/airflow/ui/src/i18n/locales/de/admin.json
new file mode 100644
index 00000000000..ad8d52ac527
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/de/admin.json
@@ -0,0 +1,6 @@
+{
+ "connections":{
+ "test": "Verbindung testen",
+ "testDisabled": "Das Testen von Verbindungen ist deaktiviert. Der
Administrator kann via Konfiguration das Testen freischalten."
+ }
+}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/de/connections.json
b/airflow-core/src/airflow/ui/src/i18n/locales/de/connections.json
deleted file mode 100644
index e9896e4f47d..00000000000
--- a/airflow-core/src/airflow/ui/src/i18n/locales/de/connections.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "test": "Verbindung testen",
- "testDisabled": "Das Testen von Verbindungen ist deaktiviert. Der
Administrator kann via Konfiguration das Testen freischalten."
-}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/en/admin.json
b/airflow-core/src/airflow/ui/src/i18n/locales/en/admin.json
new file mode 100644
index 00000000000..a0de0cc1490
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/admin.json
@@ -0,0 +1,141 @@
+{
+ "columns":{
+ "description": "Description",
+ "key": "Key",
+ "name": "Name",
+ "value": "Value"
+ },
+ "config":{
+ "columns":{
+ "section": "Section"
+ },
+ "title": "Airflow Configuration"
+ },
+ "connections":{
+ "add":"Add Connection",
+ "columns":{
+ "connectionId": "Connection ID",
+ "connectionType": "Connection Type",
+ "host": "Host",
+ "port": "Port"
+ },
+ "delete":{
+ "deleteConnection_one": "Delete 1 connection",
+ "deleteConnection_other": "Delete {{count}} connections",
+ "firstConfirmMessage_one": "You are about to delete the following
connection:",
+ "firstConfirmMessage_other": "You are about to delete the following
connections:",
+ "title": "Delete Connection"
+ },
+ "edit": "Edit Connection",
+ "form":{
+ "connectionIdRequired": "Connection ID is required",
+ "connectionIdRequirement": "Connection ID cannot contain only spaces",
+ "connectionTypeRequired": "Connection Type is required",
+ "extraFields": "Extra Fields",
+ "extraFieldsJson": "Extra Fields JSON",
+ "helperText": "Connection type missing? Make sure you have installed the
corresponding Airflow Providers Package.",
+ "selectConnectionType": "Select Connection Type",
+ "standardFields": "Standard Fields"
+ },
+ "noRowMessage": "No connections found",
+ "searchPlaceholder": "Search Connections",
+ "test": "Test Connection",
+ "testDisabled": "Test connection feature is disabled. Please contact an
administrator to enable it."
+ },
+ "deleteActions":{
+ "button": "Delete",
+ "modal":{
+ "confirmButton": "Yes, Delete",
+ "secondConfirmMessage": "This action is permanent and cannot be undone.",
+ "thirdConfirmMessage": " Are you sure you want to proceed?"
+ },
+ "selected": "Selected"
+ },
+ "formActions":{
+ "reset": "Reset",
+ "save": "Save"
+ },
+ "plugins": {
+ "columns": {
+ "source": "Source"
+ },
+ "importError_one": "Plugin Import Error",
+ "importError_other": "Plugin Import Errors",
+ "searchPlaceholder": "Search by file"
+ },
+ "pools": {
+ "add": "Add Pool",
+ "deferredSlotsIncluded": "Deferred Slots Included",
+ "delete":{
+ "title": "Delete Pool",
+ "warning": "This will remove all metadata related to the pool and may
affect tasks using this pool."
+ },
+ "edit": "Edit Pool",
+ "form": {
+ "checkbox": "Check to include deferred tasks when calculating open pool
slots",
+ "description": "Description",
+ "includeDeferred": "Include Deferred",
+ "nameMaxLength": "Name can contain a maximum of 256 characters",
+ "nameRequired": "Name is required",
+ "slots": "Slots"
+ },
+ "noPoolsFound": "No pools found",
+ "searchPlaceholder": "Search Pools",
+ "sort": {
+ "asc": "Name (A-Z)",
+ "desc": "Name (Z-A)",
+ "placeholder": "Sort by"
+ }
+ },
+ "providers": {
+ "columns": {
+ "packageName": "Package Name",
+ "version": "Version"
+ }
+ },
+ "variables": {
+ "add": "Add Variable",
+ "columns": {
+ "isEncrypted": "Is Encrypted"
+ },
+ "delete": {
+ "deleteVariable_one": "Delete 1 Variable",
+ "deleteVariable_other": "Delete {{count}} Variables",
+ "firstConfirmMessage_one": "You are about to delete the following
variable:",
+ "firstConfirmMessage_other": "You are about to delete the following
variables:",
+ "title": "Delete Variable"
+ },
+ "edit": "Edit Variable",
+ "export": "Export",
+ "form": {
+ "invalidJson": "Invalid JSON",
+ "keyMaxLength": "Key can contain a maximum of 250 characters",
+ "keyRequired": "Key is required",
+ "valueRequired": "Value is required"
+ },
+ "import": {
+ "button": "Import",
+ "conflictResolution": "Select Variable Conflict Resolution",
+ "errorParsingJsonFile": "Error Parsing JSON File: Upload a JSON file
containing variables (e.g., {\"key\": \"value\", ...}).",
+ "options": {
+ "fail": {
+ "description": "Fails the import if any existing variables are
detected.",
+ "title": "Fail"
+ },
+ "overwrite": {
+ "description": "Overwrites the variable in case of a conflict.",
+ "title": "Overwrite"
+ },
+ "skip": {
+ "description": "Skips importing variables that already exist.",
+ "title": "Skip"
+ }
+ },
+ "title": "Import Variables",
+ "upload": "Upload a JSON File",
+ "uploadPlaceholder": "Upload a JSON file containing variables (e.g.,
{\"key\": \"value\", ...})"
+ },
+ "noRowsMessage": "No variables found",
+ "searchPlaceholder": "Search Keys"
+ }
+}
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
new file mode 100644
index 00000000000..b927d6891ea
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/assets.json
@@ -0,0 +1,10 @@
+{
+ "columns": {
+ "consumingDags": "Consuming Dags",
+ "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
new file mode 100644
index 00000000000..56d89a8dc5c
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/en/browse.json
@@ -0,0 +1,22 @@
+{
+ "auditLog":{
+ "actions": {
+ "collapseAllExtra": "Collapse all extra json",
+ "expandAllExtra": "Expand all extra json"
+ },
+ "columns":{
+ "event": "Event",
+ "extra": "Extra",
+ "user": "User",
+ "when": "When"
+ },
+ "title": "Audit Log Events"
+ },
+ "xcom":{
+ "columns":{
+ "dag": "Dag",
+ "key": "Key",
+ "value": "Value"
+ }
+ }
+}
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 de6d102b326..31ca56d587e 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
@@ -7,6 +7,8 @@
"Providers": "Providers",
"Variables": "Variables"
},
+ "asset_one": "Asset",
+ "asset_other": "Assets",
"assetEvent_one": "Asset Event",
"assetEvent_other": "Asset Events",
"backfill_one": "Backfill",
@@ -17,6 +19,7 @@
},
"dag_one": "Dag",
"dag_other": "Dags",
+ "dagId": "Dag ID",
"dagRun_one": "Dag Run",
"dagRun_other": "Dag Runs",
"dagWarnings": "Dag warnings/errors",
@@ -128,6 +131,7 @@
},
"task_one": "Task",
"task_other": "Tasks",
+ "taskId": "Task ID",
"taskInstance_one": "Task Instance",
"taskInstance_other": "Task Instances",
"timeRange": {
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/en/connections.json
b/airflow-core/src/airflow/ui/src/i18n/locales/en/connections.json
deleted file mode 100644
index c50cd75a2c7..00000000000
--- a/airflow-core/src/airflow/ui/src/i18n/locales/en/connections.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "test": "Test Connection",
- "testDisabled": "Testing connections disabled. Contact your admin to
enable it."
-}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/pl/admin.json
b/airflow-core/src/airflow/ui/src/i18n/locales/pl/admin.json
new file mode 100644
index 00000000000..bbc6a9d53e5
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/pl/admin.json
@@ -0,0 +1,6 @@
+{
+ "connections":{
+ "test": "Test połączenia",
+ "testDisabled": "Testowanie połączeń wyłączone. Skontaktuj się z
administratorem, aby je włączyć."
+ }
+}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/pl/connections.json
b/airflow-core/src/airflow/ui/src/i18n/locales/pl/connections.json
deleted file mode 100644
index a2f993ddb45..00000000000
--- a/airflow-core/src/airflow/ui/src/i18n/locales/pl/connections.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "test": "Test połączenia",
- "testDisabled": "Testowanie połączeń wyłączone. Skontaktuj się z
administratorem, aby je włączyć."
-}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/admin.json
b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/admin.json
new file mode 100644
index 00000000000..53010df9c88
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/admin.json
@@ -0,0 +1,141 @@
+{
+ "columns":{
+ "description": "描述",
+ "key": "鍵",
+ "name": "名稱",
+ "value": "值"
+ },
+ "config":{
+ "columns":{
+ "section": "區段"
+ },
+ "title": "Airflow 設定"
+ },
+ "connections":{
+ "add":"新增連線",
+ "columns":{
+ "connectionId": "連線 ID",
+ "connectionType": "連線類型",
+ "host": "主機",
+ "port": "埠"
+ },
+ "delete":{
+ "deleteConnection_one": "刪除 1 個連線",
+ "deleteConnection_other": "刪除 {{count}} 個連線",
+ "firstConfirmMessage_one": "您即將刪除以下連線:",
+ "firstConfirmMessage_other": "您即將刪除以下連線:",
+ "title": "刪除連線"
+ },
+ "edit": "編輯連線",
+ "form":{
+ "connectionIdRequired": "連線 ID 是必填的",
+ "connectionIdRequirement": "連線 ID 不能只包含空格",
+ "connectionTypeRequired": "連線類型是必填的",
+ "extraFields": "額外欄位",
+ "extraFieldsJson": "額外欄位 JSON",
+ "helperText": "找不到連線類型?請確保您已安裝對應的 Airflow Providers 套件。",
+ "selectConnectionType": "選擇連線類型",
+ "standardFields": "標準欄位"
+ },
+ "noRowMessage": "找不到連線",
+ "searchPlaceholder": "搜尋連線",
+ "test": "測試連線",
+ "testDisabled": "測試連線功能已停用。請聯繫管理員以啟用。"
+ },
+ "deleteActions":{
+ "button": "刪除",
+ "modal":{
+ "confirmButton": "確定刪除",
+ "secondConfirmMessage": "此動作無法復原。",
+ "thirdConfirmMessage": "您確定要繼續嗎?"
+ },
+ "selected": "已選取"
+ },
+ "formActions":{
+ "reset": "重置",
+ "save": "儲存"
+ },
+ "plugins": {
+ "columns": {
+ "source": "來源"
+ },
+ "importError_one": "外掛匯入錯誤",
+ "importError_other": "外掛匯入錯誤",
+ "searchPlaceholder": "搜尋檔案"
+ },
+ "pools": {
+ "add": "新增資源池",
+ "deferredSlotsIncluded": "包含延後任務",
+ "delete":{
+ "title": "刪除資源池",
+ "warning": "這將刪除所有與此資源池相關的系統資料,可能會影響使用此資源池的任務。"
+ },
+ "edit": "編輯資源池",
+ "form": {
+ "checkbox": "計算可用資源池配額時,將包含延後的任務",
+ "description": "描述",
+ "includeDeferred": "包含延後任務",
+ "nameMaxLength": "名稱最多只能包含 256 個字元",
+ "nameRequired": "名稱是必填的",
+ "slots": "配額"
+ },
+ "noPoolsFound": "找不到資源池",
+ "searchPlaceholder": "搜尋資源池",
+ "sort": {
+ "asc": "名稱 (A-Z)",
+ "desc": "名稱 (Z-A)",
+ "placeholder": "排序方式"
+ }
+ },
+ "providers": {
+ "columns": {
+ "packageName": "套件名稱",
+ "version": "版本"
+ }
+ },
+ "variables": {
+ "add": "新增變數",
+ "columns": {
+ "isEncrypted": "是否加密"
+ },
+ "delete": {
+ "deleteVariable_one": "刪除 1 個變數",
+ "deleteVariable_other": "刪除 {{count}} 個變數",
+ "firstConfirmMessage_one": "您即將刪除以下變數:",
+ "firstConfirmMessage_other": "您即將刪除以下變數:",
+ "title": "刪除變數"
+ },
+ "edit": "編輯變數",
+ "export": "匯出",
+ "form": {
+ "invalidJson": "無效的 JSON",
+ "keyMaxLength": "鍵最多只能包含 250 個字元",
+ "keyRequired": "鍵是必填的",
+ "valueRequired": "值是必填的"
+ },
+ "import": {
+ "button": "匯入",
+ "conflictResolution": "選擇變數衝突解決方式",
+ "errorParsingJsonFile": "解析 JSON 檔案時發生錯誤:請上傳包含變數的 JSON 檔案 (例如:{\"key\":
\"value\", ...})。",
+ "options": {
+ "fail": {
+ "description": "如果偵測到任何已存在的變數,則匯入失敗。",
+ "title": "失敗"
+ },
+ "overwrite": {
+ "description": "發生衝突時覆蓋變數。",
+ "title": "覆蓋"
+ },
+ "skip": {
+ "description": "略過匯入已存在的變數。",
+ "title": "跳過"
+ }
+ },
+ "title": "匯入變數",
+ "upload": "上傳 JSON 檔案",
+ "uploadPlaceholder": "上傳包含變數的 JSON 檔案 (例如:{\"key\": \"value\", ...})"
+ },
+ "noRowsMessage": "找不到變數",
+ "searchPlaceholder": "搜尋鍵"
+ }
+}
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
new file mode 100644
index 00000000000..04e46d86abf
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/assets.json
@@ -0,0 +1,10 @@
+{
+ "columns": {
+ "consumingDags": "消費者 Dags",
+ "group": "群組",
+ "lastAssetEvent": "最後資源事件",
+ "name": "名稱",
+ "producingTasks": "生產任務"
+ },
+ "searchPlaceholder": "搜尋資源"
+}
diff --git a/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/browse.json
b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/browse.json
new file mode 100644
index 00000000000..8787b39e735
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/browse.json
@@ -0,0 +1,22 @@
+{
+ "auditLog":{
+ "actions": {
+ "collapseAllExtra": "收合所有額外 JSON",
+ "expandAllExtra": "展開所有額外 JSON"
+ },
+ "columns":{
+ "event": "事件",
+ "extra": "額外資訊",
+ "user": "使用者",
+ "when": "時間"
+ },
+ "title": "審計日誌事件"
+ },
+ "xcom":{
+ "columns":{
+ "dag": "Dag",
+ "key": "鍵",
+ "value": "值"
+ }
+ }
+}
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 af2be17a809..18c11dfbc02 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
@@ -1,22 +1,28 @@
{
"admin": {
"Config": "設定",
- "Connections": "連結",
+ "Connections": "連線",
"Plugins": "外掛",
"Pools": "資源池",
"Providers": "Providers",
"Variables": "變數"
},
+ "asset_one": "資源",
+ "asset_other": "資源",
"assetEvent_one": "資源事件",
"assetEvent_other": "資源事件",
+ "backfill_one": "回填",
+ "backfill_other": "回填",
"browse": {
"auditLog": "審計日誌",
"xcoms": "XComs"
},
"dag_one": "Dag",
"dag_other": "Dags",
+ "dagId": "Dag ID",
"dagRun_one": "Dag 執行",
"dagRun_other": "Dag 執行",
+ "dagWarnings": "Dag 警告 / 錯誤",
"defaultToGraphView": "預設使用圖形視圖",
"defaultToGridView": "預設使用網格視圖",
"direction": "書寫方向",
@@ -25,6 +31,18 @@
"githubRepo": "GitHub 倉庫",
"restApiReference": "REST API 參考"
},
+ "duration": {
+ "label": "持續時間",
+ "seconds": "{{count}} 秒"
+ },
+ "endDate": "結束日期",
+ "expression": {
+ "all": "全部",
+ "and": "且",
+ "any": "任何",
+ "or": "或"
+ },
+ "logicalDate": "邏輯日期",
"logout": "登出",
"logoutConfirmation": "確定要登出嗎?",
"mapIndex": "映射索引",
@@ -46,16 +64,18 @@
"plugins": "插件",
"security": "安全"
},
- "noItemsFound": "找不到{{modelName}}",
+ "noItemsFound": "找不到 {{modelName}}",
+ "operator": "運算子",
"pools": {
"deferred": "已延後",
- "open": "開啟",
+ "open": "開放",
"pools_one": "資源池",
"pools_other": "資源池",
"queued": "排隊中",
"running": "執行中",
"scheduled": "已排程"
},
+ "runId": "執行 ID",
"runTypes": {
"asset_triggered": "資源觸發",
"backfill": "回填",
@@ -70,6 +90,8 @@
"users": "使用者"
},
"selectLanguage": "選擇語言",
+ "startDate": "開始日期",
+ "state": "狀態",
"states": {
"deferred": "已延後",
"failed": "失敗",
@@ -89,17 +111,27 @@
"switchToDarkMode": "切換到深色模式",
"switchToLightMode": "切換到淺色模式",
"table": {
+ "completedAt": "完成時間",
+ "createdAt": "建立時間",
+ "duration": "持續時間",
"filterByTag": "依標籤篩選 Dags",
"filterColumns": "篩選表格欄位",
"filterReset_one": "重置篩選",
"filterReset_other": "重置篩選",
+ "from": "從",
+ "maxActiveRuns": "最大活躍執行數",
"noTagsFound": "找不到標籤",
+ "reprocessBehavior": "重新處理行為",
"tagMode": {
"all": "全部",
"any": "任何"
},
- "tagPlaceholder": "依標籤篩選"
+ "tagPlaceholder": "依標籤篩選",
+ "to": "到"
},
+ "task_one": "任務",
+ "task_other": "任務",
+ "taskId": "任務 ID",
"taskInstance_one": "任務實例",
"taskInstance_other": "任務實例",
"timeRange": {
@@ -117,5 +149,12 @@
"utc": "UTC"
},
"triggered": "已觸發",
- "user": "使用者"
+ "triggerRule": "觸發規則",
+ "tryNumber": "嘗試次數",
+ "user": "使用者",
+ "wrap": {
+ "tooltip": "按 w 切換換行",
+ "unwrap": "不換行",
+ "wrap": "換行"
+ }
}
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
deleted file mode 100644
index a8a6257cb43..00000000000
--- a/airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/connections.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "test": "測試連線",
- "testDisabled": "測試連線功能已停用。請聯繫管理員以啟用。"
-}
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 8014b61d623..d05404f7ff5 100644
--- a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx
@@ -19,6 +19,7 @@
import { Box, Heading, Link, VStack } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
import { useState } from "react";
+import { useTranslation } from "react-i18next";
import { useSearchParams, Link as RouterLink } from "react-router-dom";
import { useAssetServiceGetAssets } from "openapi/queries";
@@ -30,13 +31,12 @@ import { SearchBar } from "src/components/SearchBar";
import Time from "src/components/Time";
import { SearchParamsKeys } from "src/constants/searchParams";
import { CreateAssetEvent } from "src/pages/Asset/CreateAssetEvent";
-import { pluralize } from "src/utils";
import { DependencyPopover } from "./DependencyPopover";
type AssetRow = { row: { original: AssetResponse } };
-const columns: Array<ColumnDef<AssetResponse>> = [
+const createColumns = (translate: (key: string) => string):
Array<ColumnDef<AssetResponse>> => [
{
accessorKey: "name",
cell: ({ row: { original } }: AssetRow) => (
@@ -44,7 +44,7 @@ const columns: Array<ColumnDef<AssetResponse>> = [
<RouterLink to={`/assets/${original.id}`}>{original.name}</RouterLink>
</Link>
),
- header: () => "Name",
+ header: () => translate("columns.name"),
},
{
accessorKey: "last_asset_event",
@@ -59,12 +59,12 @@ const columns: Array<ColumnDef<AssetResponse>> = [
return <Time datetime={timestamp} />;
},
enableSorting: false,
- header: () => "Last Asset Event",
+ header: () => translate("columns.lastAssetEvent"),
},
{
accessorKey: "group",
enableSorting: false,
- header: () => "Group",
+ header: () => translate("columns.group"),
},
{
accessorKey: "consuming_dags",
@@ -73,7 +73,7 @@ const columns: Array<ColumnDef<AssetResponse>> = [
<DependencyPopover dependencies={original.consuming_dags} type="Dag" />
) : undefined,
enableSorting: false,
- header: () => "Consuming Dags",
+ header: () => translate("columns.consumingDags"),
},
{
accessorKey: "producing_tasks",
@@ -82,7 +82,7 @@ const columns: Array<ColumnDef<AssetResponse>> = [
<DependencyPopover dependencies={original.producing_tasks} type="Task"
/>
) : undefined,
enableSorting: false,
- header: () => "Producing Tasks",
+ header: () => translate("columns.producingTasks"),
},
{
accessorKey: "trigger",
@@ -95,6 +95,7 @@ const columns: Array<ColumnDef<AssetResponse>> = [
const NAME_PATTERN_PARAM = SearchParamsKeys.NAME_PATTERN;
export const AssetsList = () => {
+ const { t: translate } = useTranslation(["assets", "common"]);
const [searchParams, setSearchParams] = useSearchParams();
const [namePattern, setNamePattern] =
useState(searchParams.get(NAME_PATTERN_PARAM) ?? undefined);
@@ -132,21 +133,21 @@ export const AssetsList = () => {
buttonProps={{ disabled: true }}
defaultValue={namePattern ?? ""}
onChange={handleSearchChange}
- placeHolder="Search Assets"
+ placeHolder={translate("searchPlaceholder")}
/>
<Heading py={3} size="md">
- {pluralize("Asset", data?.total_entries)}
+ {data?.total_entries} {translate("common:asset", { count:
data?.total_entries })}
</Heading>
</VStack>
<Box overflow="auto">
<DataTable
- columns={columns}
+ columns={createColumns(translate)}
data={data?.assets ?? []}
errorMessage={<ErrorAlert error={error} />}
initialState={tableURLState}
isLoading={isLoading}
- modelName="Asset"
+ modelName={translate("common:asset_one")}
onStateChange={setTableURLState}
total={data?.total_entries}
/>
diff --git
a/airflow-core/src/airflow/ui/src/pages/AssetsList/DependencyPopover.tsx
b/airflow-core/src/airflow/ui/src/pages/AssetsList/DependencyPopover.tsx
index 0e3d17707ab..99575c8f32a 100644
--- a/airflow-core/src/airflow/ui/src/pages/AssetsList/DependencyPopover.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/AssetsList/DependencyPopover.tsx
@@ -17,48 +17,53 @@
* under the License.
*/
import { Link } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import type { DagScheduleAssetReference, TaskOutletAssetReference } from
"openapi/requests/types.gen";
import { Button, Popover } from "src/components/ui";
-import { pluralize } from "src/utils";
type Props = {
readonly dependencies: Array<DagScheduleAssetReference |
TaskOutletAssetReference>;
readonly type: "Dag" | "Task";
};
-export const DependencyPopover = ({ dependencies, type }: Props) => (
- // eslint-disable-next-line jsx-a11y/no-autofocus
- <Popover.Root autoFocus={false} lazyMount unmountOnExit>
- <Popover.Trigger asChild>
- <Button size="sm" variant="outline">
- {pluralize(type, dependencies.length)}
- </Button>
- </Popover.Trigger>
- <Popover.Content css={{ "--popover-bg": "colors.bg.emphasized" }}
width="fit-content">
- <Popover.Arrow />
- <Popover.Body>
- {dependencies.map((dependency) => {
- let key = dependency.dag_id;
- let link = `/dags/${dependency.dag_id}`;
- let label = dependency.dag_id;
+export const DependencyPopover = ({ dependencies, type }: Props) => {
+ const { t: translate } = useTranslation("common");
+ const dependencyKey = type.toLowerCase() as "dag" | "task";
- if (type === "Task") {
- const dep = dependency as TaskOutletAssetReference;
+ return (
+ // eslint-disable-next-line jsx-a11y/no-autofocus
+ <Popover.Root autoFocus={false} lazyMount unmountOnExit>
+ <Popover.Trigger asChild>
+ <Button size="sm" variant="outline">
+ {dependencies.length} {translate(dependencyKey, { count:
dependencies.length })}
+ </Button>
+ </Popover.Trigger>
+ <Popover.Content css={{ "--popover-bg": "colors.bg.emphasized" }}
width="fit-content">
+ <Popover.Arrow />
+ <Popover.Body>
+ {dependencies.map((dependency) => {
+ let key = dependency.dag_id;
+ let link = `/dags/${dependency.dag_id}`;
+ let label = dependency.dag_id;
- key = `${dep.dag_id}-${dep.task_id}`;
- link = `/dags/${dep.dag_id}/tasks/${dep.task_id}`;
- label = `${dep.dag_id}.${dep.task_id}`;
- }
+ if (type === "Task") {
+ const dep = dependency as TaskOutletAssetReference;
- return (
- <Link asChild color="fg.info" display="block" key={key} py={2}>
- <RouterLink to={link}>{label}</RouterLink>
- </Link>
- );
- })}
- </Popover.Body>
- </Popover.Content>
- </Popover.Root>
-);
+ key = `${dep.dag_id}-${dep.task_id}`;
+ link = `/dags/${dep.dag_id}/tasks/${dep.task_id}`;
+ label = `${dep.dag_id}.${dep.task_id}`;
+ }
+
+ return (
+ <Link asChild color="fg.info" display="block" key={key} py={2}>
+ <RouterLink to={link}>{label}</RouterLink>
+ </Link>
+ );
+ })}
+ </Popover.Body>
+ </Popover.Content>
+ </Popover.Root>
+ );
+};
diff --git a/airflow-core/src/airflow/ui/src/pages/Configs/Configs.tsx
b/airflow-core/src/airflow/ui/src/pages/Configs/Configs.tsx
index 8d542e71c14..f194da70113 100644
--- a/airflow-core/src/airflow/ui/src/pages/Configs/Configs.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Configs/Configs.tsx
@@ -18,6 +18,9 @@
*/
import { Heading, Separator } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
+import type { TFunction } from "i18next";
+import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
import { useConfigServiceGetConfig } from "openapi/queries";
import type { ConfigOption } from "openapi/requests/types.gen";
@@ -28,27 +31,30 @@ type ConfigColums = {
section: string;
} & ConfigOption;
-const columns: Array<ColumnDef<ConfigColums>> = [
+const createColumns = (translate: TFunction): Array<ColumnDef<ConfigColums>>
=> [
{
accessorKey: "section",
enableSorting: false,
- header: "Section",
+ header: translate("config.columns.section"),
},
{
accessorKey: "key",
enableSorting: false,
- header: "Key",
+ header: translate("columns.key"),
},
{
accessorKey: "value",
enableSorting: false,
- header: "Value",
+ header: translate("columns.value"),
},
];
export const Configs = () => {
+ const { t: translate } = useTranslation(["admin", "common"]);
const { data, error } = useConfigServiceGetConfig();
+ const columns = useMemo(() => createColumns(translate), [translate]);
+
const render =
data?.sections.flatMap((section) =>
section.options.map((option) => ({
@@ -59,10 +65,10 @@ export const Configs = () => {
return (
<>
- <Heading mb={4}>Airflow Configuration</Heading>
+ <Heading mb={4}>{translate("config.title")}</Heading>
<Separator />
{error === null ? (
- <DataTable columns={columns} data={render} modelName="Config" />
+ <DataTable columns={columns} data={render}
modelName={translate("common:admin.Config")} />
) : (
<ErrorAlert error={error} />
)}
diff --git
a/airflow-core/src/airflow/ui/src/pages/Connections/AddConnectionButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Connections/AddConnectionButton.tsx
index be9de6ce449..2b437260e72 100644
--- a/airflow-core/src/airflow/ui/src/pages/Connections/AddConnectionButton.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Connections/AddConnectionButton.tsx
@@ -18,6 +18,7 @@
*/
import { Box, Heading, VStack } from "@chakra-ui/react";
import { useDisclosure } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiPlusCircle } from "react-icons/fi";
import { Dialog } from "src/components/ui";
@@ -28,6 +29,7 @@ import ConnectionForm from "./ConnectionForm";
import type { ConnectionBody } from "./Connections";
const AddConnectionButton = () => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const { addConnection, error, isPending } = useAddConnection({
onSuccessConfirm: onClose });
const initialConnection: ConnectionBody = {
@@ -45,11 +47,11 @@ const AddConnectionButton = () => {
return (
<Box>
<ActionButton
- actionName="Add Connection"
+ actionName={translate("connections.add")}
colorPalette="blue"
icon={<FiPlusCircle />}
onClick={onOpen}
- text="Add Connection"
+ text={translate("connections.add")}
variant="solid"
/>
@@ -57,7 +59,7 @@ const AddConnectionButton = () => {
<Dialog.Content backdrop>
<Dialog.Header paddingBottom={0}>
<VStack align="start" gap={4}>
- <Heading size="xl">Add Connection</Heading>
+ <Heading size="xl">{translate("connections.add")}</Heading>
</VStack>
</Dialog.Header>
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 866d555c068..2badcd37966 100644
--- a/airflow-core/src/airflow/ui/src/pages/Connections/ConnectionForm.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Connections/ConnectionForm.tsx
@@ -20,10 +20,11 @@ import { Input, Button, Box, Spacer, HStack, Field, Stack,
VStack, Spinner } fro
import { Select } from "chakra-react-select";
import { useEffect, useState } from "react";
import { useForm, Controller } from "react-hook-form";
+import { useTranslation } from "react-i18next";
import { FiSave } from "react-icons/fi";
import { ErrorAlert } from "src/components/ErrorAlert";
-import { FlexibleForm, flexibleFormExtraFieldSection } from
"src/components/FlexibleForm";
+import { FlexibleForm } from "src/components/FlexibleForm";
import { JsonEditor } from "src/components/JsonEditor";
import { Accordion } from "src/components/ui";
import { useConnectionTypeMeta } from "src/queries/useConnectionTypeMeta";
@@ -65,6 +66,7 @@ const ConnectionForm = ({
mode: "onBlur",
});
+ const { t: translate } = useTranslation("admin");
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) };
@@ -131,7 +133,7 @@ const ConnectionForm = ({
<Field.Root invalid={Boolean(fieldState.error)}
orientation="horizontal" required>
<Stack>
<Field.Label fontSize="md" style={{ flexBasis: "30%" }}>
- Connection ID <Field.RequiredIndicator />
+ {translate("connections.columns.connectionId")}
<Field.RequiredIndicator />
</Field.Label>
</Stack>
<Stack css={{ flexBasis: "70%" }}>
@@ -141,8 +143,9 @@ const ConnectionForm = ({
</Field.Root>
)}
rules={{
- required: "Connection ID is required",
- validate: (value) => (value.trim() === "" ? "Connection ID cannot
contain only spaces" : true),
+ required: translate("connections.form.connectionIdRequired"),
+ validate: (value) =>
+ value.trim() === "" ?
translate("connections.form.connectionIdRequirement") : true,
}}
/>
@@ -153,7 +156,7 @@ const ConnectionForm = ({
<Field.Root invalid={Boolean(fieldState.error)}
orientation="horizontal" required>
<Stack>
<Field.Label fontSize="md" style={{ flexBasis: "30%" }}>
- Connection Type <Field.RequiredIndicator />
+ {translate("connections.columns.connectionType")}
<Field.RequiredIndicator />
</Field.Label>
</Stack>
<Stack css={{ flexBasis: "70%" }}>
@@ -166,19 +169,16 @@ const ConnectionForm = ({
isDisabled={isMetaPending}
onChange={(val) => onChange(val?.value)}
options={connTypesOptions}
- placeholder="Select Connection Type"
+
placeholder={translate("connections.form.selectConnectionType")}
value={connTypesOptions.find((type) => type.value ===
value)}
/>
</Stack>
- <Field.HelperText>
- Connection type missing? Make sure you have installed the
corresponding Airflow Providers
- Package.
- </Field.HelperText>
+
<Field.HelperText>{translate("connections.form.helperText")}</Field.HelperText>
</Stack>
</Field.Root>
)}
rules={{
- required: "Connection Type is required",
+ required: translate("connections.form.connectionTypeRequired"),
}}
/>
@@ -192,19 +192,21 @@ const ConnectionForm = ({
variant="enclosed"
>
<Accordion.Item key="standardFields" value="standardFields">
- <Accordion.ItemTrigger>Standard Fields</Accordion.ItemTrigger>
+
<Accordion.ItemTrigger>{translate("connections.form.standardFields")}</Accordion.ItemTrigger>
<Accordion.ItemContent>
<StandardFields control={control}
standardFields={standardFields} />
</Accordion.ItemContent>
</Accordion.Item>
<FlexibleForm
- flexibleFormDefaultSection={flexibleFormExtraFieldSection}
+
flexibleFormDefaultSection={translate("connections.form.extraFields")}
initialParamsDict={paramsDic}
key={selectedConnType}
setError={setFormErrors}
/>
<Accordion.Item key="extraJson" value="extraJson">
- <Accordion.ItemTrigger cursor="button">Extra Fields
JSON</Accordion.ItemTrigger>
+ <Accordion.ItemTrigger cursor="button">
+ {translate("connections.form.extraFieldsJson")}
+ </Accordion.ItemTrigger>
<Accordion.ItemContent>
<Controller
control={control}
@@ -235,7 +237,7 @@ const ConnectionForm = ({
disabled={Boolean(errors.conf) || formErrors || isPending ||
!isValid}
onClick={() => void handleSubmit(onSubmit)()}
>
- <FiSave /> Save
+ <FiSave /> {translate("formActions.save")}
</Button>
</HStack>
</Box>
diff --git a/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx
b/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx
index fe1553aca7b..3ce3a020cb2 100644
--- a/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx
@@ -18,7 +18,9 @@
*/
import { Box, Flex, HStack, Spacer, VStack } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
+import type { TFunction } from "i18next";
import { useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { useConnectionServiceGetConnections } from "openapi/queries";
@@ -57,7 +59,8 @@ const getColumns = ({
onRowSelect,
onSelectAll,
selectedRows,
-}: GetColumnsParams): Array<ColumnDef<ConnectionResponse>> => [
+ translate,
+}: { translate: TFunction } & GetColumnsParams):
Array<ColumnDef<ConnectionResponse>> => [
{
accessorKey: "select",
cell: ({ row }) => (
@@ -84,23 +87,23 @@ const getColumns = ({
},
{
accessorKey: "connection_id",
- header: "Connection Id",
+ header: translate("connections.columns.connectionId"),
},
{
accessorKey: "conn_type",
- header: "Connection Type",
+ header: translate("connections.columns.connectionType"),
},
{
accessorKey: "description",
- header: "Description",
+ header: translate("columns.description"),
},
{
accessorKey: "host",
- header: "Host",
+ header: translate("connections.columns.host"),
},
{
accessorKey: "port",
- header: "Port",
+ header: translate("connections.columns.port"),
},
{
accessorKey: "actions",
@@ -120,6 +123,7 @@ const getColumns = ({
];
export const Connections = () => {
+ const { t: translate } = useTranslation("admin");
const { setTableURLState, tableURLState } = useTableURLState();
const [searchParams, setSearchParams] = useSearchParams();
const { NAME_PATTERN: NAME_PATTERN_PARAM }: SearchParamsKeysType =
SearchParamsKeys;
@@ -151,8 +155,9 @@ export const Connections = () => {
onRowSelect: handleRowSelect,
onSelectAll: handleSelectAll,
selectedRows,
+ translate,
}),
- [allRowsSelected, handleRowSelect, handleSelectAll, selectedRows],
+ [allRowsSelected, handleRowSelect, handleSelectAll, selectedRows,
translate],
);
const handleSearchChange = (value: string) => {
@@ -176,7 +181,7 @@ export const Connections = () => {
buttonProps={{ disabled: true }}
defaultValue={connectionIdPattern ?? ""}
onChange={handleSearchChange}
- placeHolder="Search Connections"
+ placeHolder={translate("connections.searchPlaceholder")}
/>
<HStack gap={4} mt={2}>
<Spacer />
@@ -192,14 +197,16 @@ export const Connections = () => {
initialState={tableURLState}
isFetching={isFetching}
isLoading={isLoading}
- modelName="Connection"
+ modelName={translate("common:admin.Connections")}
onStateChange={setTableURLState}
total={data?.total_entries ?? 0}
/>
</Box>
<ActionBar.Root closeOnInteractOutside={false}
open={Boolean(selectedRows.size)}>
<ActionBar.Content>
- <ActionBar.SelectionTrigger>{selectedRows.size}
selected</ActionBar.SelectionTrigger>
+ <ActionBar.SelectionTrigger>
+ {selectedRows.size} {translate("deleteActions.selected")}
+ </ActionBar.SelectionTrigger>
<ActionBar.Separator />
<Tooltip content="Delete selected connections">
<DeleteConnectionsButton
diff --git
a/airflow-core/src/airflow/ui/src/pages/Connections/DeleteConnectionButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Connections/DeleteConnectionButton.tsx
index 8cb239dc3f8..a1af0745f12 100644
---
a/airflow-core/src/airflow/ui/src/pages/Connections/DeleteConnectionButton.tsx
+++
b/airflow-core/src/airflow/ui/src/pages/Connections/DeleteConnectionButton.tsx
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Flex, useDisclosure, Text, VStack, Heading } from "@chakra-ui/react";
+import { Flex, useDisclosure, Text, VStack, Heading, Code } from
"@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiTrash } from "react-icons/fi";
import { Button, Dialog } from "src/components/ui";
@@ -29,6 +30,7 @@ type Props = {
};
const DeleteConnectionButton = ({ connectionId, disabled }: Props) => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const { isPending, mutate } = useDeleteConnection({
onSuccessConfirm: onClose,
@@ -37,13 +39,13 @@ const DeleteConnectionButton = ({ connectionId, disabled }:
Props) => {
return (
<>
<ActionButton
- actionName="Delete Connection"
+ actionName={translate("connections.delete.title")}
disabled={disabled}
icon={<FiTrash />}
onClick={() => {
onOpen();
}}
- text="Delete Connection"
+ text={translate("connections.delete.title")}
withText={false}
/>
@@ -51,7 +53,7 @@ const DeleteConnectionButton = ({ connectionId, disabled }:
Props) => {
<Dialog.Content backdrop>
<Dialog.Header>
<VStack align="start" gap={4}>
- <Heading size="xl">Delete Connection</Heading>
+ <Heading
size="xl">{translate("connections.delete.deleteConnection", { count: 1
})}</Heading>
</VStack>
</Dialog.Header>
@@ -59,10 +61,14 @@ const DeleteConnectionButton = ({ connectionId, disabled }:
Props) => {
<Dialog.Body width="full">
<Text color="gray.solid" fontSize="md" fontWeight="semibold"
mb={4}>
- You are about to delete connection having ID
<strong>{connectionId}</strong>.
+ {translate("connections.delete.firstConfirmMessage_one")}
<br />
- This action is permanent and cannot be undone.{" "}
- <strong>Are you sure you want to proceed?</strong>
+ <Code mb={2} mt={2} p={4}>
+ {connectionId}
+ </Code>
+ <br />
+ {translate("deleteActions.modal.secondConfirmMessage")}
+
<strong>{translate("deleteActions.modal.thirdConfirmMessage")}</strong>
</Text>
<Flex justifyContent="end" mt={3}>
<Button
@@ -74,7 +80,7 @@ const DeleteConnectionButton = ({ connectionId, disabled }:
Props) => {
});
}}
>
- <FiTrash /> <Text fontWeight="bold">Yes, Delete</Text>
+ <FiTrash /> <Text
fontWeight="bold">{translate("deleteActions.modal.confirmButton")}</Text>
</Button>
</Flex>
</Dialog.Body>
diff --git
a/airflow-core/src/airflow/ui/src/pages/Connections/DeleteConnectionsButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Connections/DeleteConnectionsButton.tsx
index 66e248cdfe3..fcaf2178351 100644
---
a/airflow-core/src/airflow/ui/src/pages/Connections/DeleteConnectionsButton.tsx
+++
b/airflow-core/src/airflow/ui/src/pages/Connections/DeleteConnectionsButton.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Code, Flex, Heading, Text, VStack, useDisclosure } from
"@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiTrash, FiTrash2 } from "react-icons/fi";
import { ErrorAlert } from "src/components/ErrorAlert";
@@ -29,6 +30,7 @@ type Props = {
};
const DeleteConnectionsButton = ({ clearSelections, deleteKeys: connectionIds
}: Props) => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const { error, isPending, mutate } = useBulkDeleteConnections({
clearSelections,
@@ -42,14 +44,16 @@ const DeleteConnectionsButton = ({ clearSelections,
deleteKeys: connectionIds }:
return (
<>
<Button aria-label="Delete selected connections" onClick={onOpen}
size="sm" variant="outline">
- <FiTrash2 /> Delete
+ <FiTrash2 /> {translate("deleteActions.button")}
</Button>
<Dialog.Root onOpenChange={onClose} open={open} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
<VStack align="start" gap={4}>
- <Heading size="xl">Delete Connection{connectionIds.length > 1 ?
"s" : ""}</Heading>
+ <Heading size="xl">
+ {translate("connections.delete.deleteConnection", { count:
connectionIds.length })}
+ </Heading>
</VStack>
</Dialog.Header>
@@ -57,17 +61,14 @@ const DeleteConnectionsButton = ({ clearSelections,
deleteKeys: connectionIds }:
<Dialog.Body width="full">
<Text color="gray.solid" fontSize="md" fontWeight="semibold"
mb={4}>
- You are about to delete{" "}
- <strong>
- {connectionIds.length} connection{connectionIds.length > 1 ?
"s" : ""}.
- </strong>
+ {translate("connections.delete.firstConfirmMessage", { count:
connectionIds.length })}
<br />
<Code mb={2} mt={2} p={4}>
{connectionIds.join(", ")}
</Code>
<br />
- This action is permanent and cannot be undone.{" "}
- <strong>Are you sure you want to proceed?</strong>
+ {translate("deleteActions.modal.secondConfirmMessage")}
+
<strong>{translate("deleteActions.modal.thirdConfirmMessage")}</strong>
</Text>
<ErrorAlert error={error} />
<Flex justifyContent="end" mt={3}>
@@ -78,7 +79,7 @@ const DeleteConnectionsButton = ({ clearSelections,
deleteKeys: connectionIds }:
mutate({ requestBody: { actions: [{ action: "delete",
entities: connectionIds }] } });
}}
>
- <FiTrash /> <Text as="span">Yes, Delete</Text>
+ <FiTrash /> <Text
as="span">{translate("deleteActions.modal.confirmButton")}</Text>
</Button>
</Flex>
</Dialog.Body>
diff --git
a/airflow-core/src/airflow/ui/src/pages/Connections/EditConnectionButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Connections/EditConnectionButton.tsx
index 33ce9832b9a..1e3b7eccfab 100644
--- a/airflow-core/src/airflow/ui/src/pages/Connections/EditConnectionButton.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Connections/EditConnectionButton.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Heading, useDisclosure } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiEdit } from "react-icons/fi";
import type { ConnectionResponse } from "openapi/requests/types.gen";
@@ -33,6 +34,7 @@ type Props = {
};
const EditConnectionButton = ({ connection, disabled }: Props) => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const initialConnectionValue: ConnectionBody = {
conn_type: connection.conn_type,
@@ -57,20 +59,20 @@ const EditConnectionButton = ({ connection, disabled }:
Props) => {
return (
<>
<ActionButton
- actionName="Edit Connection"
+ actionName={translate("connections.edit")}
disabled={disabled}
icon={<FiEdit />}
onClick={() => {
onOpen();
}}
- text="Edit Connection"
+ text={translate("connections.edit")}
withText={false}
/>
<Dialog.Root onOpenChange={handleClose} open={open} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
- <Heading size="xl">Edit Connection</Heading>
+ <Heading size="xl">{translate("connections.edit")}</Heading>
</Dialog.Header>
<Dialog.CloseTrigger />
diff --git
a/airflow-core/src/airflow/ui/src/pages/Connections/TestConnectionButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Connections/TestConnectionButton.tsx
index 3fddfb62e8b..7b27877d870 100644
--- a/airflow-core/src/airflow/ui/src/pages/Connections/TestConnectionButton.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Connections/TestConnectionButton.tsx
@@ -35,7 +35,7 @@ const connectedIcon = <FiWifi color="green" />;
const disconnectedIcon = <FiWifiOff color="red" />;
const TestConnectionButton = ({ connection }: Props) => {
- const { t: translate } = useTranslation("connections");
+ const { t: translate } = useTranslation("admin");
const [icon, setIcon] = useState(defaultIcon);
const testConnection = useConfig("test_connection");
let option: TestConnectionOption;
@@ -72,7 +72,9 @@ const TestConnectionButton = ({ connection }: Props) => {
return (
<ActionButton
- actionName={option === "Enabled" ? translate("test") :
translate("testDisabled")}
+ actionName={
+ option === "Enabled" ? translate("connections.test") :
translate("connections.testDisabled")
+ }
disabled={option === "Disabled"}
display={option === "Hidden" ? "none" : "flex"}
icon={icon}
@@ -80,7 +82,7 @@ const TestConnectionButton = ({ connection }: Props) => {
onClick={() => {
mutate({ requestBody: connectionBody });
}}
- text={translate("test")}
+ text={translate("connections.test")}
withText={false}
/>
);
diff --git
a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrors.tsx
b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrors.tsx
index 5c0a89680c1..cea92bed6d7 100644
---
a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrors.tsx
+++
b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrors.tsx
@@ -17,19 +17,19 @@
* under the License.
*/
import { Box, Text, Button, useDisclosure, Skeleton } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiChevronRight } from "react-icons/fi";
import { LuPlug } from "react-icons/lu";
import { usePluginServiceImportErrors } from "openapi/queries";
import { ErrorAlert, type ExpandedApiError } from "src/components/ErrorAlert";
import { StateBadge } from "src/components/StateBadge";
-import { pluralize } from "src/utils";
import { PluginImportErrorsModal } from "./PluginImportErrorsModal";
export const PluginImportErrors = ({ iconOnly = false }: { readonly iconOnly?:
boolean }) => {
const { onClose, onOpen, open } = useDisclosure();
-
+ const { t: translate } = useTranslation("admin");
const { data, error, isLoading } = usePluginServiceImportErrors();
const importErrorsCount = data?.total_entries ?? 0;
@@ -56,7 +56,7 @@ export const PluginImportErrors = ({ iconOnly = false }: {
readonly iconOnly?: b
colorPalette="failed"
height={7}
onClick={onOpen}
- title={pluralize("Plugin Import Error", importErrorsCount)}
+ title={translate("plugins.importError", { count: importErrorsCount
})}
>
<LuPlug size="0.5rem" />
{importErrorsCount}
@@ -75,7 +75,7 @@ export const PluginImportErrors = ({ iconOnly = false }: {
readonly iconOnly?: b
{importErrorsCount}
</StateBadge>
<Box alignItems="center" display="flex" gap={1}>
- <Text fontWeight="bold">Plugin Import Errors</Text>
+ <Text fontWeight="bold">{translate("plugins.importError", { count:
importErrorsCount })}</Text>
<FiChevronRight />
</Box>
</Button>
diff --git
a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrorsModal.tsx
b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrorsModal.tsx
index e4a76482f06..905a023a22c 100644
---
a/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrorsModal.tsx
+++
b/airflow-core/src/airflow/ui/src/pages/Dashboard/Stats/PluginImportErrorsModal.tsx
@@ -18,6 +18,7 @@
*/
import { Heading, Text, HStack } from "@chakra-ui/react";
import { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
import { LuFileWarning } from "react-icons/lu";
import { PiFilePy } from "react-icons/pi";
@@ -39,6 +40,7 @@ export const PluginImportErrorsModal:
React.FC<PluginImportErrorsModalProps> = (
onClose,
open,
}) => {
+ const { t: translate } = useTranslation("admin");
const [page, setPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
const [filteredErrors, setFilteredErrors] = useState(importErrors);
@@ -67,14 +69,14 @@ export const PluginImportErrorsModal:
React.FC<PluginImportErrorsModalProps> = (
<Dialog.Header display="flex" justifyContent="space-between">
<HStack fontSize="xl">
<LuFileWarning />
- <Heading>Plugin Import Errors</Heading>
+ <Heading>{translate("plugins.importError_one")}</Heading>
</HStack>
<SearchBar
buttonProps={{ disabled: true }}
defaultValue={searchQuery}
hideAdvanced
onChange={setSearchQuery}
- placeHolder="Search by file"
+ placeHolder={translate("plugins.searchPlaceholder")}
/>
</Dialog.Header>
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 5a732fcba44..a3b306a4af3 100644
--- a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
@@ -18,6 +18,7 @@
*/
import { Box, ButtonGroup, Code, Flex, Heading, IconButton, useDisclosure }
from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
+import { useTranslation } from "react-i18next";
import { MdCompress, MdExpand } from "react-icons/md";
import { useParams } from "react-router-dom";
@@ -36,12 +37,15 @@ type EventsColumn = {
taskId?: string;
};
-const eventsColumn = ({ dagId, open, runId, taskId }: EventsColumn):
Array<ColumnDef<EventLogResponse>> => [
+const eventsColumn = (
+ { dagId, open, runId, taskId }: EventsColumn,
+ translate: (key: string) => string,
+): Array<ColumnDef<EventLogResponse>> => [
{
accessorKey: "when",
cell: ({ row: { original } }) => <Time datetime={original.when} />,
enableSorting: true,
- header: "When",
+ header: translate("auditLog.columns.when"),
meta: {
skeletonWidth: 10,
},
@@ -49,7 +53,7 @@ const eventsColumn = ({ dagId, open, runId, taskId }:
EventsColumn): Array<Colum
{
accessorKey: "event",
enableSorting: true,
- header: "Event",
+ header: translate("auditLog.columns.event"),
meta: {
skeletonWidth: 10,
},
@@ -57,7 +61,7 @@ const eventsColumn = ({ dagId, open, runId, taskId }:
EventsColumn): Array<Colum
{
accessorKey: "owner",
enableSorting: true,
- header: "User",
+ header: translate("auditLog.columns.user"),
meta: {
skeletonWidth: 10,
},
@@ -78,7 +82,7 @@ const eventsColumn = ({ dagId, open, runId, taskId }:
EventsColumn): Array<Colum
return undefined;
},
enableSorting: false,
- header: "Extra",
+ header: translate("auditLog.columns.extra"),
meta: {
skeletonWidth: 200,
},
@@ -89,7 +93,7 @@ const eventsColumn = ({ dagId, open, runId, taskId }:
EventsColumn): Array<Colum
{
accessorKey: "dag_id",
enableSorting: true,
- header: "Dag ID",
+ header: translate("common:dagId"),
meta: {
skeletonWidth: 10,
},
@@ -101,7 +105,7 @@ const eventsColumn = ({ dagId, open, runId, taskId }:
EventsColumn): Array<Colum
{
accessorKey: "run_id",
enableSorting: true,
- header: "Run ID",
+ header: translate("common:runId"),
meta: {
skeletonWidth: 10,
},
@@ -113,7 +117,7 @@ const eventsColumn = ({ dagId, open, runId, taskId }:
EventsColumn): Array<Colum
{
accessorKey: "task_id",
enableSorting: true,
- header: "Task ID",
+ header: translate("common:taskId"),
meta: {
skeletonWidth: 10,
},
@@ -122,7 +126,7 @@ const eventsColumn = ({ dagId, open, runId, taskId }:
EventsColumn): Array<Colum
{
accessorKey: "map_index",
enableSorting: false,
- header: "Map Index",
+ header: translate("common:mapIndex"),
meta: {
skeletonWidth: 10,
},
@@ -130,7 +134,7 @@ const eventsColumn = ({ dagId, open, runId, taskId }:
EventsColumn): Array<Colum
{
accessorKey: "try_number",
enableSorting: false,
- header: "Try Number",
+ header: translate("common:tryNumber"),
meta: {
skeletonWidth: 10,
},
@@ -138,6 +142,7 @@ const eventsColumn = ({ dagId, open, runId, taskId }:
EventsColumn): Array<Colum
];
export const Events = () => {
+ const { t: translate } = useTranslation("browse");
const { dagId, runId, taskId } = useParams();
const { setTableURLState, tableURLState } = useTableURLState();
const { pagination, sorting } = tableURLState;
@@ -162,22 +167,22 @@ export const Events = () => {
return (
<Box>
<Flex alignItems="center" justifyContent="space-between">
- <Heading>Audit Log Events</Heading>
+ <Heading>{translate("auditLog.title")}</Heading>
<ButtonGroup attached mt="1" size="sm" variant="surface">
<IconButton
- aria-label="Expand all extra json"
+ aria-label={translate("auditLog.actions.expandAllExtra")}
onClick={onOpen}
size="sm"
- title="Expand all extra json"
+ title={translate("auditLog.actions.expandAllExtra")}
variant="surface"
>
<MdExpand />
</IconButton>
<IconButton
- aria-label="Collapse all extra json"
+ aria-label={translate("auditLog.actions.collapseAllExtra")}
onClick={onClose}
size="sm"
- title="Collapse all extra json"
+ title={translate("auditLog.actions.collapseAllExtra")}
variant="surface"
>
<MdCompress />
@@ -186,13 +191,13 @@ export const Events = () => {
</Flex>
<ErrorAlert error={error} />
<DataTable
- columns={eventsColumn({ dagId, open, runId, taskId })}
+ columns={eventsColumn({ dagId, open, runId, taskId }, translate)}
data={data ? data.event_logs : []}
displayMode="table"
initialState={tableURLState}
isFetching={isFetching}
isLoading={isLoading}
- modelName="Event"
+ modelName={translate("auditLog.columns.event")}
onStateChange={setTableURLState}
skeletonCount={undefined}
total={data ? data.total_entries : 0}
diff --git a/airflow-core/src/airflow/ui/src/pages/Plugins.tsx
b/airflow-core/src/airflow/ui/src/pages/Plugins.tsx
index a97c1e9fb82..2829c05c311 100644
--- a/airflow-core/src/airflow/ui/src/pages/Plugins.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Plugins.tsx
@@ -18,6 +18,9 @@
*/
import { Box, Heading, HStack } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
+import type { TFunction } from "i18next";
+import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
import { usePluginServiceGetPlugins } from "openapi/queries";
import type { PluginResponse } from "openapi/requests/types.gen";
@@ -26,33 +29,36 @@ import { ErrorAlert } from "src/components/ErrorAlert";
import { PluginImportErrors } from "./Dashboard/Stats/PluginImportErrors";
-const columns: Array<ColumnDef<PluginResponse>> = [
+const createColumns = (translate: TFunction): Array<ColumnDef<PluginResponse>>
=> [
{
accessorKey: "name",
enableSorting: false,
- header: "Name",
+ header: translate("columns.name"),
},
{
accessorKey: "source",
enableSorting: false,
- header: "Source",
+ header: translate("plugins.columns.source"),
},
];
export const Plugins = () => {
+ const { t: translate } = useTranslation(["admin", "common"]);
const { data, error } = usePluginServiceGetPlugins();
+ const columns = useMemo(() => createColumns(translate), [translate]);
+
return (
<Box p={2}>
<HStack>
- <Heading>Plugins</Heading>
+ <Heading>{translate("common:admin.Plugins")}</Heading>
<PluginImportErrors iconOnly />
</HStack>
<DataTable
columns={columns}
data={data?.plugins ?? []}
errorMessage={<ErrorAlert error={error} />}
- modelName="Plugin"
+ modelName={translate("common:admin.Plugins")}
total={data?.total_entries}
/>
</Box>
diff --git a/airflow-core/src/airflow/ui/src/pages/Pools/AddPoolButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Pools/AddPoolButton.tsx
index e3de3529829..48167580872 100644
--- a/airflow-core/src/airflow/ui/src/pages/Pools/AddPoolButton.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Pools/AddPoolButton.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Heading, useDisclosure } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiPlusCircle } from "react-icons/fi";
import { Button, Dialog, Toaster } from "src/components/ui";
@@ -25,6 +26,7 @@ import { useAddPool } from "src/queries/useAddPool";
import PoolForm, { type PoolBody } from "./PoolForm";
const AddPoolButton = () => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const { addPool, error, isPending, setError } = useAddPool({
onSuccessConfirm: onClose,
@@ -46,13 +48,13 @@ const AddPoolButton = () => {
<>
<Toaster />
<Button colorPalette="blue" onClick={onOpen}>
- <FiPlusCircle /> Add Pool
+ <FiPlusCircle /> {translate("pools.add")}
</Button>
<Dialog.Root onOpenChange={handleClose} open={open} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
- <Heading size="xl">Add Pool</Heading>
+ <Heading size="xl">{translate("pools.add")}</Heading>
</Dialog.Header>
<Dialog.CloseTrigger />
diff --git a/airflow-core/src/airflow/ui/src/pages/Pools/DeletePoolButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Pools/DeletePoolButton.tsx
index 1e76102ba83..bc2c47e8e38 100644
--- a/airflow-core/src/airflow/ui/src/pages/Pools/DeletePoolButton.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Pools/DeletePoolButton.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { useDisclosure } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiTrash2 } from "react-icons/fi";
import DeleteDialog from "src/components/DeleteDialog";
@@ -29,6 +30,7 @@ type Props = {
};
const DeletePoolButton = ({ poolName, withText = false }: Props) => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const { isPending, mutate } = useDeletePool({
onSuccessConfirm: onClose,
@@ -37,11 +39,11 @@ const DeletePoolButton = ({ poolName, withText = false }:
Props) => {
return (
<>
<ActionButton
- actionName="Delete Pool"
+ actionName={translate("pools.delete.title")}
colorPalette="red"
icon={<FiTrash2 />}
onClick={onOpen}
- text="Delete Pool"
+ text={translate("pools.delete.warning")}
variant="solid"
withText={withText}
/>
@@ -52,8 +54,8 @@ const DeletePoolButton = ({ poolName, withText = false }:
Props) => {
onDelete={() => mutate({ poolName })}
open={open}
resourceName={poolName}
- title="Delete Pool"
- warningText="This will remove all metadata related to the pool and may
affect tasks using this pool."
+ title={translate("pools.delete.title")}
+ warningText={translate("pools.delete.warning")}
/>
</>
);
diff --git a/airflow-core/src/airflow/ui/src/pages/Pools/EditPoolButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Pools/EditPoolButton.tsx
index 3f1a9898cf8..914b36f7462 100644
--- a/airflow-core/src/airflow/ui/src/pages/Pools/EditPoolButton.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Pools/EditPoolButton.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Heading, useDisclosure } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiEdit } from "react-icons/fi";
import type { PoolResponse } from "openapi/requests/types.gen";
@@ -31,6 +32,7 @@ type Props = {
};
const EditPoolButton = ({ pool }: Props) => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const initialPoolValue: PoolBody = {
description: pool.description ?? "",
@@ -50,19 +52,19 @@ const EditPoolButton = ({ pool }: Props) => {
return (
<>
<ActionButton
- actionName="Edit Pool"
+ actionName={translate("pools.edit")}
icon={<FiEdit />}
onClick={() => {
onOpen();
}}
- text="Edit Pool"
+ text={translate("pools.edit")}
withText={false}
/>
<Dialog.Root onOpenChange={handleClose} open={open} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
- <Heading size="xl">Edit Pool</Heading>
+ <Heading size="xl">{translate("pools.edit")}</Heading>
</Dialog.Header>
<Dialog.CloseTrigger />
diff --git a/airflow-core/src/airflow/ui/src/pages/Pools/PoolBarCard.tsx
b/airflow-core/src/airflow/ui/src/pages/Pools/PoolBarCard.tsx
index b4b31c4fcde..2e0403592c3 100644
--- a/airflow-core/src/airflow/ui/src/pages/Pools/PoolBarCard.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Pools/PoolBarCard.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Box, Flex, HStack, Text, VStack } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import type { PoolResponse } from "openapi/requests/types.gen";
import { PoolBar } from "src/components/PoolBar";
@@ -30,40 +31,44 @@ type PoolBarCardProps = {
readonly pool: PoolResponse;
};
-const PoolBarCard = ({ pool }: PoolBarCardProps) => (
- <Box borderColor="border.emphasized" borderRadius={8} borderWidth={1} mb={2}
overflow="hidden">
- <Flex alignItems="center" bg="bg.muted" justifyContent="space-between"
p={4}>
- <VStack align="start" flex="1">
- <HStack justifyContent="space-between" width="100%">
- <Text fontSize="lg" fontWeight="bold" whiteSpace="normal"
wordBreak="break-word">
- {pool.name} ({pool.slots} slots)
- {pool.include_deferred ? (
- <Tooltip content="Deferred Slots Included">
- <StateIcon size={18} state="deferred" style={{ display:
"inline", marginLeft: 6 }} />
- </Tooltip>
- ) : undefined}
- </Text>
- <HStack gap={0}>
- <EditPoolButton pool={pool} />
- {pool.name === "default_pool" ? undefined : (
- <DeletePoolButton poolName={pool.name} withText={false} />
- )}
- </HStack>
- </HStack>
- {pool.description ?? (
- <Text color="gray.fg" fontSize="sm">
- {pool.description}
- </Text>
- )}
- </VStack>
- </Flex>
+const PoolBarCard = ({ pool }: PoolBarCardProps) => {
+ const { t: translate } = useTranslation("admin");
- <Box margin={4}>
- <Flex bg="gray.muted" borderRadius="md" h="20px" overflow="hidden"
w="100%">
- <PoolBar pool={pool} totalSlots={pool.slots} />
+ return (
+ <Box borderColor="border.emphasized" borderRadius={8} borderWidth={1}
mb={2} overflow="hidden">
+ <Flex alignItems="center" bg="bg.muted" justifyContent="space-between"
p={4}>
+ <VStack align="start" flex="1">
+ <HStack justifyContent="space-between" width="100%">
+ <Text fontSize="lg" fontWeight="bold" whiteSpace="normal"
wordBreak="break-word">
+ {pool.name} ({pool.slots} {translate("pools.form.slots")})
+ {pool.include_deferred ? (
+ <Tooltip content={translate("pools.deferredSlotsIncluded")}>
+ <StateIcon size={18} state="deferred" style={{ display:
"inline", marginLeft: 6 }} />
+ </Tooltip>
+ ) : undefined}
+ </Text>
+ <HStack gap={0}>
+ <EditPoolButton pool={pool} />
+ {pool.name === "default_pool" ? undefined : (
+ <DeletePoolButton poolName={pool.name} withText={false} />
+ )}
+ </HStack>
+ </HStack>
+ {pool.description ?? (
+ <Text color="gray.fg" fontSize="sm">
+ {pool.description}
+ </Text>
+ )}
+ </VStack>
</Flex>
+
+ <Box margin={4}>
+ <Flex bg="gray.muted" borderRadius="md" h="20px" overflow="hidden"
w="100%">
+ <PoolBar pool={pool} totalSlots={pool.slots} />
+ </Flex>
+ </Box>
</Box>
- </Box>
-);
+ );
+};
export default PoolBarCard;
diff --git a/airflow-core/src/airflow/ui/src/pages/Pools/PoolForm.tsx
b/airflow-core/src/airflow/ui/src/pages/Pools/PoolForm.tsx
index f93ab81442f..a95252c97f7 100644
--- a/airflow-core/src/airflow/ui/src/pages/Pools/PoolForm.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Pools/PoolForm.tsx
@@ -18,6 +18,7 @@
*/
import { Box, Field, HStack, Input, Spacer, Textarea } from "@chakra-ui/react";
import { Controller, useForm } from "react-hook-form";
+import { useTranslation } from "react-i18next";
import { FiSave } from "react-icons/fi";
import { ErrorAlert } from "src/components/ErrorAlert";
@@ -40,6 +41,7 @@ type PoolFormProps = {
};
const PoolForm = ({ error, initialPool, isPending, manageMutate, setError }:
PoolFormProps) => {
+ const { t: translate } = useTranslation("admin");
const {
control,
formState: { isDirty, isValid },
@@ -67,15 +69,15 @@ const PoolForm = ({ error, initialPool, isPending,
manageMutate, setError }: Poo
render={({ field, fieldState }) => (
<Field.Root invalid={Boolean(fieldState.error)} required>
<Field.Label fontSize="md">
- Name <Field.RequiredIndicator />
+ {translate("columns.name")} <Field.RequiredIndicator />
</Field.Label>
<Input {...field} disabled={Boolean(initialPool.name)} required
size="sm" />
{fieldState.error ?
<Field.ErrorText>{fieldState.error.message}</Field.ErrorText> : undefined}
</Field.Root>
)}
rules={{
- required: "Name is required",
- validate: (_value) => _value.length <= 256 || "Name can contain a
maximum of 256 characters",
+ required: translate("pools.form.nameRequired"),
+ validate: (_value) => _value.length <= 256 ||
translate("pools.form.rules.nameMaxLength"),
}}
/>
@@ -84,7 +86,7 @@ const PoolForm = ({ error, initialPool, isPending,
manageMutate, setError }: Poo
name="slots"
render={({ field }) => (
<Field.Root mt={4}>
- <Field.Label fontSize="md">Slots</Field.Label>
+ <Field.Label
fontSize="md">{translate("pools.form.slots")}</Field.Label>
<Input {...field} min={initialPool.slots} size="sm" type="number"
/>
</Field.Root>
)}
@@ -95,7 +97,7 @@ const PoolForm = ({ error, initialPool, isPending,
manageMutate, setError }: Poo
name="description"
render={({ field }) => (
<Field.Root mb={4} mt={4}>
- <Field.Label fontSize="md">Description</Field.Label>
+ <Field.Label
fontSize="md">{translate("columns.description")}</Field.Label>
<Textarea {...field} disabled={initialPool.name ===
"default_pool"} size="sm" />
</Field.Root>
)}
@@ -106,9 +108,9 @@ const PoolForm = ({ error, initialPool, isPending,
manageMutate, setError }: Poo
name="include_deferred"
render={({ field }) => (
<Field.Root mb={4} mt={4}>
- <Field.Label fontSize="md">Include Deferred</Field.Label>
+ <Field.Label
fontSize="md">{translate("pools.form.includeDeferred")}</Field.Label>
<Checkbox checked={field.value} colorPalette="blue"
onChange={field.onChange} size="sm">
- Check to include deferred tasks when calculating open pool slots
+ {translate("pools.form.checkbox")}
</Checkbox>
</Field.Root>
)}
@@ -120,7 +122,7 @@ const PoolForm = ({ error, initialPool, isPending,
manageMutate, setError }: Poo
<HStack w="full">
{isDirty ? (
<Button onClick={handleReset} variant="outline">
- Reset
+ {translate("formActions.reset")}
</Button>
) : undefined}
<Spacer />
@@ -129,7 +131,7 @@ const PoolForm = ({ error, initialPool, isPending,
manageMutate, setError }: Poo
disabled={!isValid || isPending}
onClick={() => void handleSubmit(onSubmit)()}
>
- <FiSave /> Save
+ <FiSave /> {translate("formActions.save")}
</Button>
</HStack>
</Box>
diff --git a/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx
b/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx
index 48b0b54c4b0..ff1ca88c9f4 100644
--- a/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx
@@ -19,6 +19,7 @@
import { Box, HStack, Skeleton } from "@chakra-ui/react";
import { createListCollection } from "@chakra-ui/react/collection";
import { useState } from "react";
+import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { usePoolServiceGetPools } from "openapi/queries";
@@ -42,14 +43,15 @@ const cardDef = (): CardDef<PoolResponse> => ({
},
});
-const poolSortOptions = createListCollection({
- items: [
- { label: "Name (A-Z)", value: "name" },
- { label: "Name (Z-A)", value: "-name" },
- ],
-});
-
export const Pools = () => {
+ const { t: translate } = useTranslation(["admin", "common"]);
+
+ const poolSortOptions = createListCollection({
+ items: [
+ { label: translate("pools.sort.asc"), value: "name" },
+ { label: translate("pools.sort.desc"), value: "-name" },
+ ],
+ });
const [searchParams, setSearchParams] = useSearchParams();
const { NAME_PATTERN: NAME_PATTERN_PARAM }: SearchParamsKeysType =
SearchParamsKeys;
const [poolNamePattern, setPoolNamePattern] =
useState(searchParams.get(NAME_PATTERN_PARAM) ?? undefined);
@@ -94,7 +96,7 @@ export const Pools = () => {
buttonProps={{ disabled: true }}
defaultValue={poolNamePattern ?? ""}
onChange={handleSearchChange}
- placeHolder="Search Pools"
+ placeHolder={translate("pools.searchPlaceholder")}
/>
<HStack gap={4} mt={4}>
<Select.Root
@@ -105,7 +107,7 @@ export const Pools = () => {
width={130}
>
<Select.Trigger>
- <Select.ValueText placeholder="Sort by" />
+ <Select.ValueText
placeholder={translate("pools.sort.placeholder")} />
</Select.Trigger>
<Select.Content>
@@ -126,7 +128,8 @@ export const Pools = () => {
displayMode="card"
initialState={tableURLState}
isLoading={isLoading}
- modelName="Pool"
+ modelName={translate("common:admin.Pools")}
+ noRowsMessage={translate("pools.noPoolsFound")}
onStateChange={setTableURLState}
total={data ? data.total_entries : 0}
/>
diff --git a/airflow-core/src/airflow/ui/src/pages/Providers.tsx
b/airflow-core/src/airflow/ui/src/pages/Providers.tsx
index 85f11d3d12c..8602f3534e9 100644
--- a/airflow-core/src/airflow/ui/src/pages/Providers.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Providers.tsx
@@ -18,6 +18,9 @@
*/
import { Box, Heading, Link } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
+import type { TFunction } from "i18next";
+import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
import { useProviderServiceGetProviders } from "openapi/queries";
import type { ProviderResponse } from "openapi/requests/types.gen";
@@ -26,7 +29,7 @@ import { useTableURLState } from
"src/components/DataTable/useTableUrlState";
import { ErrorAlert } from "src/components/ErrorAlert";
import { urlRegex } from "src/constants/urlRegex";
-const columns: Array<ColumnDef<ProviderResponse>> = [
+const createColumns = (translate: TFunction):
Array<ColumnDef<ProviderResponse>> => [
{
accessorKey: "package_name",
cell: ({ row: { original } }) => (
@@ -41,13 +44,13 @@ const columns: Array<ColumnDef<ProviderResponse>> = [
</Link>
),
enableSorting: false,
- header: "Package Name",
+ header: translate("providers.columns.packageName"),
},
{
accessorKey: "version",
cell: ({ row: { original } }) => original.version,
enableSorting: false,
- header: () => "Version",
+ header: translate("providers.columns.version"),
},
{
accessorKey: "description",
@@ -66,13 +69,16 @@ const columns: Array<ColumnDef<ProviderResponse>> = [
);
},
enableSorting: false,
- header: "Description",
+ header: translate("columns.description"),
},
];
export const Providers = () => {
+ const { t: translate } = useTranslation(["admin", "common"]);
const { setTableURLState, tableURLState } = useTableURLState();
+ const columns = useMemo(() => createColumns(translate), [translate]);
+
const { pagination } = tableURLState;
const { data, error } = useProviderServiceGetProviders({
@@ -82,13 +88,13 @@ export const Providers = () => {
return (
<Box p={2}>
- <Heading>Providers</Heading>
+ <Heading>{translate("common:admin.Providers")}</Heading>
<DataTable
columns={columns}
data={data?.providers ?? []}
errorMessage={<ErrorAlert error={error} />}
initialState={tableURLState}
- modelName="Provider"
+ modelName={translate("common:admin.Providers")}
onStateChange={setTableURLState}
total={data?.total_entries}
/>
diff --git
a/airflow-core/src/airflow/ui/src/pages/Variables/DeleteVariablesButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Variables/DeleteVariablesButton.tsx
index dfb1f7d20cb..628806a4745 100644
--- a/airflow-core/src/airflow/ui/src/pages/Variables/DeleteVariablesButton.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Variables/DeleteVariablesButton.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Flex, useDisclosure, Text, VStack, Heading, Code } from
"@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiTrash, FiTrash2 } from "react-icons/fi";
import { ErrorAlert } from "src/components/ErrorAlert";
@@ -29,6 +30,7 @@ type Props = {
};
const DeleteVariablesButton = ({ clearSelections, deleteKeys: variableKeys }:
Props) => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const { error, isPending, mutate } = useBulkDeleteVariables({
clearSelections, onSuccessConfirm: onClose });
@@ -42,32 +44,32 @@ const DeleteVariablesButton = ({ clearSelections,
deleteKeys: variableKeys }: Pr
variant="outline"
>
<FiTrash2 />
- Delete
+ {translate("deleteActions.button")}
</Button>
<Dialog.Root onOpenChange={onClose} open={open} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
<VStack align="start" gap={4}>
- <Heading size="xl">Delete Variable{variableKeys.length > 1 ? "s"
: ""}</Heading>
+ <Heading size="xl">
+ {translate("variables.delete.deleteVariable", {
+ count: variableKeys.length,
+ })}
+ </Heading>
</VStack>
</Dialog.Header>
<Dialog.CloseTrigger />
-
<Dialog.Body width="full">
<Text color="gray.solid" fontSize="md" fontWeight="semibold"
mb={4}>
- You are about to delete{" "}
- <strong>
- {variableKeys.length} variable{variableKeys.length > 1 ? "s" :
""}.
- </strong>
+ {translate("variables.delete.firstConfirmMessage", { count:
variableKeys.length })}
<br />
<Code mb={2} mt={2} p={4}>
{variableKeys.join(", ")}
</Code>
<br />
- This action is permanent and cannot be undone.{" "}
- <strong>Are you sure you want to proceed?</strong>
+ {translate("deleteActions.modal.secondConfirmMessage")}
+
<strong>{translate("deleteActions.modal.thirdConfirmMessage")}</strong>
</Text>
<ErrorAlert error={error} />
<Flex justifyContent="end" mt={3}>
@@ -88,7 +90,7 @@ const DeleteVariablesButton = ({ clearSelections, deleteKeys:
variableKeys }: Pr
});
}}
>
- <FiTrash /> <Text fontWeight="bold">Yes, Delete</Text>
+ <FiTrash /> <Text
fontWeight="bold">{translate("deleteActions.modal.confirmButton")}</Text>
</Button>
</Flex>
</Dialog.Body>
diff --git
a/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesButton.tsx
index 4f23d32d772..8c9275602a8 100644
--- a/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesButton.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesButton.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Heading, useDisclosure, VStack } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiUploadCloud } from "react-icons/fi";
import { Button, Dialog } from "src/components/ui";
@@ -28,19 +29,20 @@ type Props = {
};
const ImportVariablesButton = ({ disabled }: Props) => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
return (
<>
<Button colorPalette="blue" disabled={disabled} onClick={onOpen}>
- <FiUploadCloud /> Import Variables
+ <FiUploadCloud /> {translate("variables.import.title")}
</Button>
<Dialog.Root onOpenChange={onClose} open={open} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
<VStack align="start" gap={4}>
- <Heading size="xl"> Import Variables </Heading>
+ <Heading size="xl"> {translate("variables.import.title")}
</Heading>
</VStack>
</Dialog.Header>
diff --git
a/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.tsx
b/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.tsx
index 37111f88a6d..87713f4c5eb 100644
--- a/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.tsx
@@ -17,7 +17,9 @@
* under the License.
*/
import { Box, Center, HStack, Spinner } from "@chakra-ui/react";
+import type { TFunction } from "i18next";
import { useState } from "react";
+import { useTranslation } from "react-i18next";
import { FiUploadCloud } from "react-icons/fi";
import { LuFileUp } from "react-icons/lu";
@@ -33,25 +35,26 @@ type ImportVariablesFormProps = {
readonly onClose: () => void;
};
-const actionIfExistsOptions = [
+const actionIfExistsOptions = (translate: TFunction) => [
{
- description: "Fails the import if any existing variables are detected.",
- title: "Fail",
+ description: translate("variables.import.options.fail.description"),
+ title: translate("variables.import.options.fail.title"),
value: "fail",
},
{
- description: "Overwrites the variable in case of a conflict.",
- title: "Overwrite",
+ description: translate("variables.import.options.overwrite.description"),
+ title: translate("variables.import.options.overwrite.title"),
value: "overwrite",
},
{
- description: "Skips importing variables that already exist.",
- title: "Skip",
+ description: translate("variables.import.options.skip.description"),
+ title: translate("variables.import.options.skip.title"),
value: "skip",
},
];
const ImportVariablesForm = ({ onClose }: ImportVariablesFormProps) => {
+ const { t: translate } = useTranslation("admin");
const { error, isPending, mutate, setError } = useImportVariables({
onSuccessConfirm: onClose,
});
@@ -73,8 +76,7 @@ const ImportVariablesForm = ({ onClose }:
ImportVariablesFormProps) => {
} catch {
setError({
body: {
- detail:
- 'Error Parsing JSON File: Upload a JSON file containing
variables (e.g., {"key": "value", ...}).',
+ detail: translate("variables.import.errorParsingJsonFile"),
},
});
setFileContent(undefined);
@@ -125,7 +127,7 @@ const ImportVariablesForm = ({ onClose }:
ImportVariablesFormProps) => {
required
>
<FileUpload.Label fontSize="md" mb={3}>
- Upload a JSON File{" "}
+ {translate("variables.import.upload")}
</FileUpload.Label>
<InputGroup
endElement={
@@ -148,7 +150,7 @@ const ImportVariablesForm = ({ onClose }:
ImportVariablesFormProps) => {
startElement={<LuFileUp />}
w="full"
>
- <FileInput placeholder='Upload a JSON file containing variables
(e.g., {"key": "value", ...})' />
+ <FileInput
placeholder={translate("variables.import.uploadPlaceholder")} />
</InputGroup>
{isParsing ? (
<Center mt={2}>
@@ -166,10 +168,10 @@ const ImportVariablesForm = ({ onClose }:
ImportVariablesFormProps) => {
}}
>
<RadioCardLabel fontSize="md" mb={3}>
- Select Variable Conflict Resolution
+ {translate("variables.import.conflictResolution")}
</RadioCardLabel>
<HStack align="stretch">
- {actionIfExistsOptions.map((item) => (
+ {actionIfExistsOptions(translate).map((item) => (
<RadioCardItem
description={item.description}
key={item.value}
@@ -189,7 +191,7 @@ const ImportVariablesForm = ({ onClose }:
ImportVariablesFormProps) => {
</Box>
) : undefined}
<Button colorPalette="blue" disabled={!Boolean(fileContent) ||
isPending} onClick={onSubmit}>
- <FiUploadCloud /> Import
+ <FiUploadCloud /> {translate("variables.import.button")}
</Button>
</Box>
</>
diff --git
a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/AddVariableButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/AddVariableButton.tsx
index c3b3c3a5519..8ffb8a45f80 100644
---
a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/AddVariableButton.tsx
+++
b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/AddVariableButton.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Heading, useDisclosure } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiPlusCircle } from "react-icons/fi";
import { Button, Dialog, Toaster } from "src/components/ui";
@@ -29,6 +30,7 @@ type Props = {
};
const AddVariableButton = ({ disabled }: Props) => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const { addVariable, error, isPending, setError } = useAddVariable({
onSuccessConfirm: onClose,
@@ -49,13 +51,13 @@ const AddVariableButton = ({ disabled }: Props) => {
<>
<Toaster />
<Button colorPalette="blue" disabled={disabled} onClick={onOpen}>
- <FiPlusCircle /> Add Variable
+ <FiPlusCircle /> {translate("variables.add")}
</Button>
<Dialog.Root onOpenChange={handleClose} open={open} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
- <Heading size="xl">Add Variable</Heading>
+ <Heading size="xl">{translate("variables.add")}</Heading>
</Dialog.Header>
<Dialog.CloseTrigger />
diff --git
a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx
index fab939d73cc..9445bca6a5a 100644
---
a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx
+++
b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/DeleteVariableButton.tsx
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Flex, useDisclosure, Text, VStack, Heading } from "@chakra-ui/react";
+import { Flex, useDisclosure, Text, VStack, Heading, Code } from
"@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiTrash } from "react-icons/fi";
import { Button, Dialog } from "src/components/ui";
@@ -29,6 +30,7 @@ type Props = {
};
const DeleteVariableButton = ({ deleteKey: variableKey, disabled }: Props) => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const { isPending, mutate } = useDeleteVariable({
onSuccessConfirm: onClose,
@@ -37,13 +39,13 @@ const DeleteVariableButton = ({ deleteKey: variableKey,
disabled }: Props) => {
return (
<>
<ActionButton
- actionName="Delete Variable"
+ actionName={translate("variables.delete.title")}
disabled={disabled}
icon={<FiTrash />}
onClick={() => {
onOpen();
}}
- text="Delete Variable"
+ text={translate("variables.delete.title")}
withText={false}
/>
@@ -51,7 +53,7 @@ const DeleteVariableButton = ({ deleteKey: variableKey,
disabled }: Props) => {
<Dialog.Content backdrop>
<Dialog.Header>
<VStack align="start" gap={4}>
- <Heading size="xl">Delete Variable</Heading>
+ <Heading size="xl">{translate("variables.delete.deleteVariable",
{ count: 1 })}</Heading>
</VStack>
</Dialog.Header>
@@ -59,10 +61,14 @@ const DeleteVariableButton = ({ deleteKey: variableKey,
disabled }: Props) => {
<Dialog.Body width="full">
<Text color="gray.solid" fontSize="md" fontWeight="semibold"
mb={4}>
- You are about to delete variable with key
<strong>{variableKey}</strong>.
+ {translate("variables.delete.firstConfirmMessage_one")}
<br />
- This action is permanent and cannot be undone.{" "}
- <strong>Are you sure you want to proceed?</strong>
+ <Code mb={2} mt={2} p={4}>
+ {variableKey}
+ </Code>
+ <br />
+ {translate("deleteActions.modal.secondConfirmMessage")}
+
<strong>{translate("deleteActions.modal.thirdConfirmMessage")}</strong>
</Text>
<Flex justifyContent="end" mt={3}>
<Button
@@ -74,7 +80,7 @@ const DeleteVariableButton = ({ deleteKey: variableKey,
disabled }: Props) => {
});
}}
>
- <FiTrash /> <Text fontWeight="bold">Yes, Delete</Text>
+ <FiTrash /> <Text
fontWeight="bold">{translate("deleteActions.modal.confirmButton")}</Text>
</Button>
</Flex>
</Dialog.Body>
diff --git
a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/EditVariableButton.tsx
b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/EditVariableButton.tsx
index 5138cefb6fe..4310a2f92ff 100644
---
a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/EditVariableButton.tsx
+++
b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/EditVariableButton.tsx
@@ -17,6 +17,7 @@
* under the License.
*/
import { Heading, useDisclosure } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
import { FiEdit } from "react-icons/fi";
import type { VariableResponse } from "openapi/requests/types.gen";
@@ -33,6 +34,7 @@ type Props = {
};
const EditVariableButton = ({ disabled, variable }: Props) => {
+ const { t: translate } = useTranslation("admin");
const { onClose, onOpen, open } = useDisclosure();
const initialVariableValue: VariableBody = {
description: variable.description ?? "",
@@ -51,20 +53,20 @@ const EditVariableButton = ({ disabled, variable }: Props)
=> {
return (
<>
<ActionButton
- actionName="Edit Variable"
+ actionName={translate("variables.edit")}
disabled={disabled}
icon={<FiEdit />}
onClick={() => {
onOpen();
}}
- text="Edit Variable"
+ text={translate("variables.edit")}
withText={false}
/>
<Dialog.Root onOpenChange={handleClose} open={open} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
- <Heading size="xl">Edit Variable</Heading>
+ <Heading size="xl">{translate("variables.edit")}</Heading>
</Dialog.Header>
<Dialog.CloseTrigger />
diff --git
a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/VariableForm.tsx
b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/VariableForm.tsx
index 2b7c65e760e..63ab6bb4c24 100644
---
a/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/VariableForm.tsx
+++
b/airflow-core/src/airflow/ui/src/pages/Variables/ManageVariable/VariableForm.tsx
@@ -18,6 +18,7 @@
*/
import { Box, Field, HStack, Input, Spacer, Textarea, Text } from
"@chakra-ui/react";
import { Controller, useForm } from "react-hook-form";
+import { useTranslation } from "react-i18next";
import { FiSave } from "react-icons/fi";
import { ErrorAlert } from "src/components/ErrorAlert";
@@ -48,6 +49,7 @@ type VariableFormProps = {
};
const VariableForm = ({ error, initialVariable, isPending, manageMutate,
setError }: VariableFormProps) => {
+ const { t: translate } = useTranslation("admin");
const {
control,
formState: { isDirty, isValid },
@@ -75,15 +77,15 @@ const VariableForm = ({ error, initialVariable, isPending,
manageMutate, setErro
render={({ field, fieldState }) => (
<Field.Root invalid={Boolean(fieldState.error)} required>
<Field.Label fontSize="md">
- Key <Field.RequiredIndicator />
+ {translate("columns.key")} <Field.RequiredIndicator />
</Field.Label>
<Input {...field} disabled={Boolean(initialVariable.key)} required
size="sm" />
{fieldState.error ?
<Field.ErrorText>{fieldState.error.message}</Field.ErrorText> : undefined}
</Field.Root>
)}
rules={{
- required: "Key is required",
- validate: (_value) => _value.length <= 250 || "Key can contain a
maximum of 250 characters",
+ required: translate("variables.form.keyRequired"),
+ validate: (_value) => _value.length <= 250 ||
translate("variables.form.keyMaxLength"),
}}
/>
@@ -97,12 +99,12 @@ const VariableForm = ({ error, initialVariable, isPending,
manageMutate, setErro
return (
<Field.Root invalid={Boolean(fieldState.error)} mt={4} required>
<Field.Label fontSize="md">
- Value <Field.RequiredIndicator />
+ {translate("columns.value")} <Field.RequiredIndicator />
</Field.Label>
<Textarea {...field} size="sm" />
{showJsonWarning ? (
<Text color="fg.warning" fontSize="xs">
- Invalid JSON
+ {translate("variables.form.invalidJson")}
</Text>
) : undefined}
{fieldState.error ?
<Field.ErrorText>{fieldState.error.message}</Field.ErrorText> : undefined}
@@ -110,7 +112,7 @@ const VariableForm = ({ error, initialVariable, isPending,
manageMutate, setErro
);
}}
rules={{
- required: "Value is required",
+ required: translate("variables.form.valueRequired"),
}}
/>
@@ -119,7 +121,7 @@ const VariableForm = ({ error, initialVariable, isPending,
manageMutate, setErro
name="description"
render={({ field }) => (
<Field.Root mb={4} mt={4}>
- <Field.Label fontSize="md">Description</Field.Label>
+ <Field.Label
fontSize="md">{translate("columns.description")}</Field.Label>
<Textarea {...field} size="sm" />
</Field.Root>
)}
@@ -131,7 +133,7 @@ const VariableForm = ({ error, initialVariable, isPending,
manageMutate, setErro
<HStack w="full">
{isDirty ? (
<Button onClick={handleReset} variant="outline">
- Reset
+ {translate("formActions.reset")}
</Button>
) : undefined}
<Spacer />
@@ -140,7 +142,7 @@ const VariableForm = ({ error, initialVariable, isPending,
manageMutate, setErro
disabled={!isValid || isPending}
onClick={() => void handleSubmit(onSubmit)()}
>
- <FiSave /> Save
+ <FiSave /> {translate("formActions.save")}
</Button>
</HStack>
</Box>
diff --git a/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx
b/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx
index d7c4256f2c1..10cd89138bf 100644
--- a/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx
@@ -18,7 +18,9 @@
*/
import { Box, Flex, HStack, Spacer, VStack } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
+import type { TFunction } from "i18next";
import { useEffect, useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
import { FiShare } from "react-icons/fi";
import { useSearchParams } from "react-router-dom";
@@ -47,7 +49,8 @@ const getColumns = ({
onRowSelect,
onSelectAll,
selectedRows,
-}: GetColumnsParams): Array<ColumnDef<VariableResponse>> => [
+ translate,
+}: { translate: TFunction } & GetColumnsParams):
Array<ColumnDef<VariableResponse>> => [
{
accessorKey: "select",
cell: ({ row }) => (
@@ -75,21 +78,21 @@ const getColumns = ({
{
accessorKey: "key",
cell: ({ row }) => <TrimText isClickable onClickContent={row.original}
text={row.original.key} />,
- header: "Key",
+ header: translate("columns.key"),
},
{
accessorKey: "value",
cell: ({ row }) => <TrimText showTooltip text={row.original.value} />,
- header: "Value",
+ header: translate("columns.value"),
},
{
accessorKey: "description",
cell: ({ row }) => <TrimText showTooltip text={row.original.description}
/>,
- header: "Description",
+ header: translate("columns.description"),
},
{
accessorKey: "is_encrypted",
- header: "Is Encrypted",
+ header: translate("variables.columns.isEncrypted"),
},
{
accessorKey: "actions",
@@ -108,6 +111,7 @@ const getColumns = ({
];
export const Variables = () => {
+ const { t: translate } = useTranslation("admin");
const { setTableURLState, tableURLState } = useTableURLState({
pagination: { pageIndex: 0, pageSize: 30 },
sorting: [{ desc: false, id: "key" }],
@@ -142,8 +146,9 @@ export const Variables = () => {
onRowSelect: handleRowSelect,
onSelectAll: handleSelectAll,
selectedRows,
+ translate,
}),
- [allRowsSelected, handleRowSelect, handleSelectAll, selectedRows],
+ [allRowsSelected, handleRowSelect, handleSelectAll, selectedRows,
translate],
);
const handleSearchChange = (value: string) => {
@@ -190,7 +195,7 @@ export const Variables = () => {
buttonProps={{ disabled: true }}
defaultValue={variableKeyPattern ?? ""}
onChange={handleSearchChange}
- placeHolder="Search Keys"
+ placeHolder={translate("variables.searchPlaceholder")}
/>
<HStack gap={4} mt={2}>
<ImportVariablesButton disabled={selectedRows.size > 0} />
@@ -206,14 +211,17 @@ export const Variables = () => {
initialState={tableURLState}
isFetching={isFetching}
isLoading={isLoading}
- modelName="Variable"
+ modelName={translate("common:admin.Variables")}
+ noRowsMessage={translate("variables.noRowsMessage")}
onStateChange={setTableURLState}
total={data?.total_entries ?? 0}
/>
</Box>
<ActionBar.Root closeOnInteractOutside={false}
open={Boolean(selectedRows.size)}>
<ActionBar.Content>
- <ActionBar.SelectionTrigger>{selectedRows.size}
selected</ActionBar.SelectionTrigger>
+ <ActionBar.SelectionTrigger>
+ {selectedRows.size} {translate("deleteActions.selected")}
+ </ActionBar.SelectionTrigger>
<ActionBar.Separator />
<Tooltip content="Delete selected variables">
<DeleteVariablesButton clearSelections={clearSelections}
deleteKeys={[...selectedRows.keys()]} />
@@ -221,7 +229,7 @@ export const Variables = () => {
<Tooltip content="Export selected variables">
<Button onClick={() => downloadJson(selectedVariables,
"variables")} size="sm" variant="outline">
<FiShare />
- Export
+ {translate("variables.export")}
</Button>
</Tooltip>
<ActionBar.CloseTrigger onClick={clearSelections} />
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 43f1e3d17c3..1fe894d3edf 100644
--- a/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx
@@ -18,6 +18,7 @@
*/
import { Box, 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";
import { useXcomServiceGetXcomEntries } from "openapi/queries";
@@ -30,11 +31,11 @@ import { getTaskInstanceLinkFromObj } from
"src/utils/links";
import { XComEntry } from "./XComEntry";
-const columns: Array<ColumnDef<XComResponse>> = [
+const columns = (translate: (key: string) => string):
Array<ColumnDef<XComResponse>> => [
{
accessorKey: "key",
enableSorting: false,
- header: "Key",
+ header: translate("xcom.columns.key"),
},
{
accessorKey: "dag_id",
@@ -44,7 +45,7 @@ const columns: Array<ColumnDef<XComResponse>> = [
</Link>
),
enableSorting: false,
- header: "Dag",
+ header: translate("xcom.columns.dag"),
},
{
accessorKey: "run_id",
@@ -56,7 +57,7 @@ const columns: Array<ColumnDef<XComResponse>> = [
</Link>
),
enableSorting: false,
- header: "Run Id",
+ header: translate("common:runId"),
},
{
accessorKey: "task_id",
@@ -75,12 +76,12 @@ const columns: Array<ColumnDef<XComResponse>> = [
</Link>
),
enableSorting: false,
- header: "Task ID",
+ header: translate("common:taskId"),
},
{
accessorKey: "map_index",
enableSorting: false,
- header: "Map Index",
+ header: translate("common:mapIndex"),
},
{
cell: ({ row: { original } }) => (
@@ -93,13 +94,13 @@ const columns: Array<ColumnDef<XComResponse>> = [
/>
),
enableSorting: false,
- header: "Value",
+ header: translate("xcom.columns.value"),
},
];
export const XCom = () => {
const { dagId = "~", mapIndex = "-1", runId = "~", taskId = "~" } =
useParams();
-
+ const { t: translate } = useTranslation(["browse", "common"]);
const { setTableURLState, tableURLState } = useTableURLState();
const { pagination } = tableURLState;
@@ -120,7 +121,7 @@ export const XCom = () => {
<Box>
<ErrorAlert error={error} />
<DataTable
- columns={columns}
+ columns={columns(translate)}
data={data ? data.xcom_entries : []}
displayMode="table"
initialState={tableURLState}