diff --git a/packages/console/src/components/AppContent/components/Sidebar/hook.tsx b/packages/console/src/components/AppContent/components/Sidebar/hook.tsx index 2ef34c350..dc9b570c6 100644 --- a/packages/console/src/components/AppContent/components/Sidebar/hook.tsx +++ b/packages/console/src/components/AppContent/components/Sidebar/hook.tsx @@ -1,9 +1,8 @@ -import { Language } from '@logto/phrases'; -import { conditionalString, Optional } from '@silverhand/essentials'; +import { Optional } from '@silverhand/essentials'; import { FC, ReactNode } from 'react'; import { TFuncKey } from 'react-i18next'; -import useLanguage from '@/hooks/use-language'; +import useDocumentationUrl from '@/hooks/use-documentation-url'; import useUserPreferences from '@/hooks/use-user-preferences'; import Contact from './components/Contact'; @@ -48,7 +47,7 @@ export const useSidebarMenuItems = (): { const { data: { getStartedHidden }, } = useUserPreferences(); - const language = useLanguage(); + const documentationUrl = useDocumentationUrl(); const sections: SidebarSection[] = [ { @@ -110,9 +109,7 @@ export const useSidebarMenuItems = (): { { Icon: Document, title: 'docs', - externalLink: `https://docs.logto.io/${conditionalString( - language !== Language.English && language.toLowerCase() - )}`, + externalLink: documentationUrl, }, ], }, diff --git a/packages/console/src/hooks/use-documentation-url.ts b/packages/console/src/hooks/use-documentation-url.ts new file mode 100644 index 000000000..d2352d1c8 --- /dev/null +++ b/packages/console/src/hooks/use-documentation-url.ts @@ -0,0 +1,27 @@ +import { useTranslation } from 'react-i18next'; + +/** + * Supported languages on https://docs.logto.io + */ +enum DocumentationLanguage { + English = 'en', + Chinese = 'zh-CN', +} + +const documentationSiteUrl = 'https://docs.logto.io'; + +const useDocumentationUrl = () => { + const { + i18n: { language }, + } = useTranslation(); + + const documentationUrl = Object.values(DocumentationLanguage) + .filter((language) => language !== DocumentationLanguage.English) + .includes(language) + ? `${documentationSiteUrl}/${language.toLocaleLowerCase()}` + : documentationSiteUrl; + + return documentationUrl; +}; + +export default useDocumentationUrl; diff --git a/packages/console/src/hooks/use-language.ts b/packages/console/src/hooks/use-language.ts deleted file mode 100644 index 79c898d25..000000000 --- a/packages/console/src/hooks/use-language.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Language } from '@logto/phrases'; -import { useTranslation } from 'react-i18next'; - -const useLanguage = () => { - const { - i18n: { language }, - } = useTranslation(); - - return Object.values(Language).includes(language) ? language : Language.English; -}; - -export default useLanguage; diff --git a/packages/console/src/pages/GetStarted/hook.ts b/packages/console/src/pages/GetStarted/hook.ts index 88619d14b..ce236bfbe 100644 --- a/packages/console/src/pages/GetStarted/hook.ts +++ b/packages/console/src/pages/GetStarted/hook.ts @@ -1,7 +1,6 @@ -import { AdminConsoleKey, Language } from '@logto/phrases'; +import { AdminConsoleKey } from '@logto/phrases'; import { AppearanceMode, Application } from '@logto/schemas'; import { demoAppApplicationId } from '@logto/schemas/lib/seeds'; -import { conditionalString } from '@silverhand/essentials'; import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import useSWR from 'swr'; @@ -19,7 +18,7 @@ import OneClick from '@/assets/images/one-click.svg'; import PasswordlessDark from '@/assets/images/passwordless-dark.svg'; import Passwordless from '@/assets/images/passwordless.svg'; import { RequestError } from '@/hooks/use-api'; -import useLanguage from '@/hooks/use-language'; +import useDocumentationUrl from '@/hooks/use-documentation-url'; import useSettings from '@/hooks/use-settings'; import { useTheme } from '@/hooks/use-theme'; @@ -35,7 +34,7 @@ type GetStartedMetadata = { }; const useGetStartedMetadata = () => { - const language = useLanguage(); + const documentationUrl = useDocumentationUrl(); const { settings, updateSettings } = useSettings(); const theme = useTheme(); const isLightMode = theme === AppearanceMode.LightMode; @@ -123,21 +122,16 @@ const useGetStartedMetadata = () => { isComplete: settings?.furtherReadingsChecked, onClick: () => { void updateSettings({ furtherReadingsChecked: true }); - window.open( - `https://docs.logto.io/${conditionalString( - language !== Language.English && language.toLowerCase() - )}/docs/tutorials/get-started/further-readings`, - '_blank' - ); + window.open(`${documentationUrl}/docs/tutorials/get-started/further-readings/`, '_blank'); }, }, ]; return metadataItems.filter(({ isHidden }) => !isHidden); }, [ + documentationUrl, hideDemo, isLightMode, - language, navigate, settings?.applicationCreated, settings?.demoChecked, diff --git a/packages/console/src/pages/Settings/index.tsx b/packages/console/src/pages/Settings/index.tsx index 0fd31faa8..bf484617e 100644 --- a/packages/console/src/pages/Settings/index.tsx +++ b/packages/console/src/pages/Settings/index.tsx @@ -1,4 +1,4 @@ -import { languageOptions } from '@logto/phrases'; +import { Language, languageOptions } from '@logto/phrases'; import { AppearanceMode } from '@logto/schemas'; import classNames from 'classnames'; import { Controller, useForm } from 'react-hook-form'; @@ -12,7 +12,6 @@ import FormField from '@/components/FormField'; import Select from '@/components/Select'; import TabNav, { TabNavItem } from '@/components/TabNav'; import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal'; -import useLanguage from '@/hooks/use-language'; import useUserPreferences, { UserPreferences } from '@/hooks/use-user-preferences'; import * as detailsStyles from '@/scss/details.module.scss'; @@ -20,8 +19,15 @@ import ChangePassword from './components/ChangePassword'; import * as styles from './index.module.scss'; const Settings = () => { - const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const defaultLanguage = useLanguage(); + const { + t, + i18n: { language }, + } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + + const defaultLanguage = Object.values(Language).includes(language) + ? language + : Language.English; + const { data, error, update, isLoading, isLoaded } = useUserPreferences(); const { handleSubmit, diff --git a/packages/phrases-ui/src/types.ts b/packages/phrases-ui/src/types.ts index 88dd08aa8..c4269c864 100644 --- a/packages/phrases-ui/src/types.ts +++ b/packages/phrases-ui/src/types.ts @@ -12,9 +12,15 @@ export enum Language { Korean = 'ko-KR', } -export const languageOptions = [ - { value: Language.English, title: 'English' }, - { value: Language.Chinese, title: '简体中文' }, - { value: Language.Turkish, title: 'Türkçe' }, - { value: Language.Korean, title: '한국어' }, -]; +const languageCodeAndDisplayNameMappings: Record = { + [Language.English]: 'English', + [Language.Chinese]: '简体中文', + [Language.Turkish]: 'Türkçe', + [Language.Korean]: '한국어', +}; + +export const languageOptions = Object.entries(languageCodeAndDisplayNameMappings) + .filter((entry): entry is [Language, string] => + Object.values(Language).includes(entry[0]) + ) + .map(([key, value]) => ({ value: key, title: value })); diff --git a/packages/phrases/src/types.ts b/packages/phrases/src/types.ts index d77236169..b0a5cb4e0 100644 --- a/packages/phrases/src/types.ts +++ b/packages/phrases/src/types.ts @@ -16,9 +16,15 @@ export enum Language { export const languageEnumGuard = z.nativeEnum(Language); -export const languageOptions = [ - { value: Language.English, title: 'English' }, - { value: Language.Chinese, title: '简体中文' }, - { value: Language.Turkish, title: 'Türkçe' }, - { value: Language.Korean, title: '한국어' }, -]; +const languageCodeAndDisplayNameMappings: Record = { + [Language.English]: 'English', + [Language.Chinese]: '简体中文', + [Language.Turkish]: 'Türkçe', + [Language.Korean]: '한국어', +}; + +export const languageOptions = Object.entries(languageCodeAndDisplayNameMappings) + .filter((entry): entry is [Language, string] => + Object.values(Language).includes(entry[0]) + ) + .map(([key, value]) => ({ value: key, title: value }));