Skip to content

Commit cfb81fb

Browse files
authored
Add LanguageModal for lang selection (#51369)
* feat: add LanguageModal * feat: add 'direction' translation to en and zh-TW
1 parent d8db236 commit cfb81fb

File tree

5 files changed

+124
-23
lines changed

5 files changed

+124
-23
lines changed

airflow-core/src/airflow/ui/src/i18n/locales/en/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"dagRun_other": "Dag Runs",
2020
"defaultToGraphView": "Default to graph view",
2121
"defaultToGridView": "Default to grid view",
22+
"direction": "Direction",
2223
"docs": {
2324
"documentation": "Documentation",
2425
"githubRepo": "GitHub Repo",

airflow-core/src/airflow/ui/src/i18n/locales/zh-TW/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"dagRun_other": "Dag 執行",
2020
"defaultToGraphView": "預設使用圖形視圖",
2121
"defaultToGridView": "預設使用網格視圖",
22+
"direction": "書寫方向",
2223
"docs": {
2324
"documentation": "文件",
2425
"githubRepo": "GitHub 倉庫",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*!
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import React from "react";
20+
import { useTranslation } from "react-i18next";
21+
22+
import { Dialog } from "src/components/ui";
23+
24+
import LanguageSelector from "./LanguageSelector";
25+
26+
type LanguageModalProps = {
27+
readonly isOpen: boolean;
28+
readonly onClose: () => void;
29+
};
30+
31+
const LanguageModal: React.FC<LanguageModalProps> = ({ isOpen, onClose }) => {
32+
const { t: translate } = useTranslation();
33+
34+
return (
35+
<Dialog.Root lazyMount onOpenChange={onClose} open={isOpen} size="xl">
36+
<Dialog.Content backdrop>
37+
<Dialog.Header>{translate("selectLanguage")}</Dialog.Header>
38+
<Dialog.CloseTrigger />
39+
<Dialog.Body>
40+
<LanguageSelector />
41+
</Dialog.Body>
42+
</Dialog.Content>
43+
</Dialog.Root>
44+
);
45+
};
46+
47+
export default LanguageModal;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*!
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import { Field, VStack, Box, Text } from "@chakra-ui/react";
20+
import { Select, type SingleValue } from "chakra-react-select";
21+
import React, { useMemo } from "react";
22+
import { useTranslation } from "react-i18next";
23+
24+
import { supportedLanguages } from "src/i18n/config";
25+
26+
const LanguageSelector: React.FC = () => {
27+
const { i18n, t: translate } = useTranslation();
28+
const options = useMemo(
29+
() =>
30+
supportedLanguages.map((lang) => ({
31+
label: lang.name,
32+
value: lang.code,
33+
})),
34+
[],
35+
);
36+
37+
const handleLanguageChange = (selectedOption: SingleValue<{ label: string; value: string }>) => {
38+
if (selectedOption) {
39+
void i18n.changeLanguage(selectedOption.value);
40+
}
41+
};
42+
43+
const currentLang = options.find((option) => option.value === i18n.language);
44+
const langDir = i18n.dir(i18n.language);
45+
46+
return (
47+
<VStack align="stretch" gap={6}>
48+
<Field.Root>
49+
<Select<{ label: string; value: string }>
50+
onChange={handleLanguageChange}
51+
options={options}
52+
placeholder={translate("selectLanguage")}
53+
value={currentLang}
54+
/>
55+
</Field.Root>
56+
<Box borderRadius="md" boxShadow="sm" display="flex" flexDirection="column" gap={2} p={6}>
57+
<Text fontSize="lg" fontWeight="bold">
58+
{currentLang?.label} ({i18n.language})
59+
</Text>
60+
<Text>{`${translate("direction")}: ${langDir.toUpperCase()}`}</Text>
61+
</Box>
62+
</VStack>
63+
);
64+
};
65+
66+
export default LanguageSelector;

airflow-core/src/airflow/ui/src/layouts/Nav/UserSettingsButton.tsx

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ import timezone from "dayjs/plugin/timezone";
2222
import utc from "dayjs/plugin/utc";
2323
import { useState } from "react";
2424
import { useTranslation } from "react-i18next";
25-
import { FiClock, FiGrid, FiLogOut, FiMoon, FiSun, FiUser, FiGlobe, FiChevronRight } from "react-icons/fi";
25+
import { FiClock, FiGrid, FiLogOut, FiMoon, FiSun, FiUser, FiGlobe } from "react-icons/fi";
2626
import { MdOutlineAccountTree } from "react-icons/md";
2727
import { useLocalStorage } from "usehooks-ts";
2828

2929
import { Menu } from "src/components/ui";
3030
import { useColorMode } from "src/context/colorMode/useColorMode";
3131
import { useTimezone } from "src/context/timezone";
32-
import { supportedLanguages } from "src/i18n/config";
3332

33+
import LanguageModal from "./LanguageModal";
3434
import LogoutModal from "./LogoutModal";
3535
import { NavButton } from "./NavButton";
3636
import TimezoneModal from "./TimezoneModal";
@@ -39,10 +39,11 @@ dayjs.extend(utc);
3939
dayjs.extend(timezone);
4040

4141
export const UserSettingsButton = () => {
42-
const { i18n, t: translate } = useTranslation();
42+
const { t: translate } = useTranslation();
4343
const { colorMode, toggleColorMode } = useColorMode();
4444
const { onClose: onCloseTimezone, onOpen: onOpenTimezone, open: isOpenTimezone } = useDisclosure();
4545
const { onClose: onCloseLogout, onOpen: onOpenLogout, open: isOpenLogout } = useDisclosure();
46+
const { onClose: onCloseLanguage, onOpen: onOpenLanguage, open: isOpenLanguage } = useDisclosure();
4647
const { selectedTimezone } = useTimezone();
4748
const [dagView, setDagView] = useLocalStorage<"graph" | "grid">("default_dag_view", "grid");
4849

@@ -54,26 +55,10 @@ export const UserSettingsButton = () => {
5455
<NavButton icon={<FiUser size="1.75rem" />} title={translate("user")} />
5556
</Menu.Trigger>
5657
<Menu.Content>
57-
<Menu.Root>
58-
<Menu.TriggerItem>
59-
<FiGlobe size="1.25rem" style={{ marginRight: "8px" }} />
60-
{translate("selectLanguage")}
61-
<FiChevronRight size="1.25rem" style={{ marginLeft: "auto" }} />
62-
</Menu.TriggerItem>
63-
<Menu.Content>
64-
<Menu.RadioItemGroup
65-
onValueChange={(element) => void i18n.changeLanguage(element.value)}
66-
value={i18n.language}
67-
>
68-
{supportedLanguages.map((lang) => (
69-
<Menu.RadioItem key={lang.code} value={lang.code}>
70-
{lang.name}
71-
<Menu.ItemIndicator />
72-
</Menu.RadioItem>
73-
))}
74-
</Menu.RadioItemGroup>
75-
</Menu.Content>
76-
</Menu.Root>
58+
<Menu.Item onClick={onOpenLanguage} value="language">
59+
<FiGlobe size="1.25rem" style={{ marginRight: "8px" }} />
60+
{translate("selectLanguage")}
61+
</Menu.Item>
7762
<Menu.Item onClick={toggleColorMode} value="color-mode">
7863
{colorMode === "light" ? (
7964
<>
@@ -112,6 +97,7 @@ export const UserSettingsButton = () => {
11297
{translate("logout")}
11398
</Menu.Item>
11499
</Menu.Content>
100+
<LanguageModal isOpen={isOpenLanguage} onClose={onCloseLanguage} />
115101
<TimezoneModal isOpen={isOpenTimezone} onClose={onCloseTimezone} />
116102
<LogoutModal isOpen={isOpenLogout} onClose={onCloseLogout} />
117103
</Menu.Root>

0 commit comments

Comments
 (0)