feat: add typeScript support to enforce valid translation keys (#1954)

* #1952 Fix incorrect localization key for sign-up failure message

* feat: add typeScript support to enforce valid translation keys

* feat: add typeScript support to enforce valid translation keys

* fix lint errors

* fix lint error
This commit is contained in:
Ajay Kumbhare 2023-07-15 07:57:37 +05:30 committed by GitHub
parent 5e20094386
commit 1780225da5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 148 additions and 125 deletions

View File

@ -1,4 +1,4 @@
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useGlobalStore } from "@/store/module";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
@ -7,7 +7,7 @@ import GitHubBadge from "./GitHubBadge";
type Props = DialogProps;
const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const globalStore = useGlobalStore();
const profile = globalStore.state.systemStatus.profile;
const customizedProfile = globalStore.state.systemStatus.customizedProfile;

View File

@ -1,6 +1,6 @@
import { Option, Select } from "@mui/joy";
import { FC } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import Icon from "./Icon";
interface Props {
@ -9,11 +9,11 @@ interface Props {
className?: string;
}
const appearanceList = ["system", "light", "dark"];
const appearanceList = ["system", "light", "dark"] as const;
const AppearanceSelect: FC<Props> = (props: Props) => {
const { onChange, value, className } = props;
const { t } = useTranslation();
const t = useTranslate();
const getPrefixIcon = (appearance: Appearance) => {
const className = "w-4 h-auto";

View File

@ -1,7 +1,7 @@
import { Tooltip } from "@mui/joy";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useMemoStore } from "@/store/module";
import { useTranslate } from "@/utils/i18n";
import { getDateTimeString } from "@/helpers/datetime";
import Icon from "./Icon";
import MemoContent from "./MemoContent";
@ -15,7 +15,7 @@ interface Props {
const ArchivedMemo: React.FC<Props> = (props: Props) => {
const { memo } = props;
const { t } = useTranslation();
const t = useTranslate();
const memoStore = useMemoStore();
const handleDeleteMemoClick = async () => {

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useMemoStore } from "@/store/module";
import useLoading from "@/hooks/useLoading";
import Icon from "./Icon";
@ -11,7 +11,7 @@ import "@/less/archived-memo-dialog.less";
type Props = DialogProps;
const ArchivedMemoDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const { destroy } = props;
const memoStore = useMemoStore();
const memos = memoStore.state.memos;

View File

@ -1,4 +1,4 @@
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
interface Props {
className?: string;
@ -6,7 +6,7 @@ interface Props {
const BetaBadge: React.FC<Props> = (props: Props) => {
const { className } = props;
const { t } = useTranslation();
const t = useTranslate();
return (
<span

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useUserStore } from "@/store/module";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
@ -11,7 +11,7 @@ interface Props extends DialogProps {
const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
const { user: propsUser, destroy } = props;
const { t } = useTranslation();
const t = useTranslate();
const userStore = useUserStore();
const [newPassword, setNewPassword] = useState("");
const [newPasswordAgain, setNewPasswordAgain] = useState("");

View File

@ -1,7 +1,7 @@
import { getNormalizedTimeString, getUnixTime } from "@/helpers/datetime";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useMemoStore } from "@/store/module";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
@ -11,7 +11,7 @@ interface Props extends DialogProps {
}
const ChangeMemoCreatedTsDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const { destroy, memoId } = props;
const memoStore = useMemoStore();
const [createdAt, setCreatedAt] = useState("");

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useGlobalStore, useUserStore } from "@/store/module";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
@ -8,7 +8,7 @@ import { generateDialog } from "./Dialog";
type Props = DialogProps;
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const userStore = useUserStore();
const globalStore = useGlobalStore();
const profile = globalStore.state.systemStatus.profile;

View File

@ -1,6 +1,6 @@
import { useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useResourceStore } from "@/store/module";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
@ -25,7 +25,7 @@ const validateFilename = (filename: string): boolean => {
const ChangeResourceFilenameDialog: React.FC<Props> = (props: Props) => {
const { destroy, resourceId, resourceFilename } = props;
const { t } = useTranslation();
const t = useTranslate();
const resourceStore = useResourceStore();
const [filename, setFilename] = useState<string>(resourceFilename);

View File

@ -1,6 +1,6 @@
import { Button, Divider, Input, Option, Select, Typography } from "@mui/joy";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { toast } from "react-hot-toast";
import * as api from "@/helpers/api";
import { UNKNOWN_ID } from "@/helpers/consts";
@ -101,7 +101,7 @@ interface Props extends DialogProps {
}
const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const identityProviderTypes = [...new Set(templateList.map((t) => t.type))];
const { confirmCallback, destroy, identityProvider } = props;
const [basicInfo, setBasicInfo] = useState({
@ -236,7 +236,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
return (
<>
<div className="dialog-header-container">
<p className="title-text ml-auto">{t("setting.sso-section." + (isCreating ? "create" : "update") + "-sso")}</p>
<p className="title-text ml-auto">{t(isCreating ? "setting.sso-section.create-sso" : "setting.sso-section.update-sso")}</p>
<button className="btn close-btn ml-auto" onClick={handleCloseBtnClick}>
<Icon.X />
</button>
@ -411,7 +411,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
{t("common.cancel")}
</Button>
<Button onClick={handleConfirmBtnClick} disabled={!allowConfirmAction()}>
{t("common." + (isCreating ? "create" : "update"))}
{t(isCreating ? "common.create" : "common.update")}
</Button>
</div>
</div>

View File

@ -1,7 +1,7 @@
import { Button, Input, Select, Option, Typography, List, ListItem, Autocomplete } from "@mui/joy";
import React, { useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useResourceStore } from "../store/module";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
@ -21,7 +21,7 @@ interface State {
}
const CreateResourceDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const { destroy, onCancel, onConfirm } = props;
const resourceStore = useResourceStore();
const [state, setState] = useState<State>({

View File

@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useShortcutStore, useTagStore } from "@/store/module";
import { filterConsts, getDefaultFilter, relationConsts } from "@/helpers/filter";
import { getNormalizedTimeString } from "@/helpers/datetime";
@ -20,7 +20,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
const [title, setTitle] = useState<string>("");
const [filters, setFilters] = useState<Filter[]>([]);
const requestState = useLoading(false);
const { t } = useTranslation();
const t = useTranslate();
useEffect(() => {
if (shortcutId) {
@ -158,7 +158,7 @@ interface MemoFilterInputerProps {
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = (props: MemoFilterInputerProps) => {
const { index, filter, handleFilterChange, handleFilterRemove } = props;
const { t } = useTranslation();
const t = useTranslate();
const tagStore = useTagStore();
const [value, setValue] = useState<string>(filter.value.value);
const tags = Array.from(tagStore.getState().tags);

View File

@ -1,7 +1,7 @@
import { Button, Input, Typography } from "@mui/joy";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import * as api from "@/helpers/api";
import { generateDialog } from "./Dialog";
import Icon from "./Icon";
@ -14,7 +14,7 @@ interface Props extends DialogProps {
}
const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const { destroy, storage, confirmCallback } = props;
const [basicInfo, setBasicInfo] = useState({
name: "",
@ -106,7 +106,9 @@ const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
return (
<>
<div className="dialog-header-container">
<span className="title-text ml-auto">{t("setting.storage-section." + (isCreating ? "create" : "update") + "-storage")}</span>
<span className="title-text ml-auto">
{t(isCreating ? "setting.storage-section.create-storage" : "setting.storage-section.update-storage")}
</span>
<button className="btn close-btn ml-auto" onClick={handleCloseBtnClick}>
<Icon.X />
</button>
@ -219,7 +221,7 @@ const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
{t("common.cancel")}
</Button>
<Button onClick={handleConfirmBtnClick} disabled={!allowConfirmAction()}>
{t("common." + (isCreating ? "create" : "update"))}
{t(isCreating ? "common.create" : "common.update")}
</Button>
</div>
</div>

View File

@ -1,7 +1,7 @@
import { Input } from "@mui/joy";
import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useTagStore } from "@/store/module";
import { getTagSuggestionList } from "@/helpers/api";
import { matcher } from "@/labs/marked/matcher";
@ -22,7 +22,7 @@ const validateTagName = (tagName: string): boolean => {
const CreateTagDialog: React.FC<Props> = (props: Props) => {
const { destroy } = props;
const tagStore = useTagStore();
const { t } = useTranslation();
const t = useTranslate();
const [tagName, setTagName] = useState<string>("");
const [suggestTagNameList, setSuggestTagNameList] = useState<string[]>([]);
const [showTagSuggestions, setShowTagSuggestions] = useState<boolean>(false);

View File

@ -1,4 +1,4 @@
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import Icon from "../Icon";
import { generateDialog } from "./BaseDialog";
import "@/less/common-dialog.less";
@ -23,10 +23,10 @@ const defaultProps = {
confirmBtnText: "common.confirm",
onClose: () => null,
onConfirm: () => null,
};
} as const;
const CommonDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const { title, content, destroy, closeBtnText, confirmBtnText, onClose, onConfirm, style } = {
...defaultProps,
closeBtnText: t(defaultProps.closeBtnText),

View File

@ -1,7 +1,7 @@
import copy from "copy-to-clipboard";
import React from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
@ -10,7 +10,7 @@ interface Props extends DialogProps {
}
const EmbedMemoDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const { memoId, destroy } = props;
const memoEmbeddedCode = () => {

View File

@ -1,6 +1,6 @@
import { useEffect } from "react";
import { NavLink, useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useLayoutStore, useUserStore } from "@/store/module";
import { resolution } from "@/utils/layout";
import Icon from "./Icon";
@ -9,7 +9,7 @@ import showAboutSiteDialog from "./AboutSiteDialog";
import UpgradeVersionView from "./UpgradeVersionBanner";
const Header = () => {
const { t } = useTranslation();
const t = useTranslate();
const location = useLocation();
const userStore = useUserStore();
const layoutStore = useLayoutStore();

View File

@ -1,7 +1,6 @@
import { Tooltip } from "@mui/joy";
import Icon from "./Icon";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
interface Props {
className?: string;
url: string;
@ -10,7 +9,7 @@ interface Props {
const LearnMore: React.FC<Props> = (props: Props) => {
const { className, url, title } = props;
const { t } = useTranslation();
const t = useTranslate();
return (
<>

View File

@ -2,6 +2,7 @@ import { Divider, Tooltip } from "@mui/joy";
import { isEqual, uniqWith } from "lodash-es";
import { memo, useEffect, useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslate } from "@/utils/i18n";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { useFilterStore, useMemoStore, useUserStore } from "@/store/module";
@ -28,7 +29,8 @@ interface Props {
const Memo: React.FC<Props> = (props: Props) => {
const { memo, showCreator, showVisibility, showRelatedMemos } = props;
const { t, i18n } = useTranslation();
const { i18n } = useTranslation();
const t = useTranslate();
const filterStore = useFilterStore();
const userStore = useUserStore();
const memoStore = useMemoStore();
@ -225,7 +227,7 @@ const Memo: React.FC<Props> = (props: Props) => {
</div>
<div className="btns-container space-x-2">
{showVisibility && memo.visibility !== "PRIVATE" && (
<Tooltip title={t(`memo.visibility.${memo.visibility.toLowerCase()}`)} placement="top">
<Tooltip title={t(`memo.visibility.${memo.visibility.toLowerCase() as Lowercase<typeof memo.visibility>}`)} placement="top">
<div onClick={() => handleMemoVisibilityClick(memo.visibility)}>
{memo.visibility === "PUBLIC" ? (
<Icon.Globe2 className="w-4 h-auto cursor-pointer rounded text-green-600" />

View File

@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { marked } from "@/labs/marked";
import { useUserStore } from "@/store/module";
import Icon from "./Icon";
@ -23,7 +23,7 @@ interface State {
const MemoContent: React.FC<Props> = (props: Props) => {
const { className, content, showFull, onMemoContentClick, onMemoContentDoubleClick } = props;
const { t } = useTranslation();
const t = useTranslate();
const userStore = useUserStore();
const [state, setState] = useState<State>({
expandButtonStatus: -1,

View File

@ -1,5 +1,5 @@
import { toLower } from "lodash-es";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { VISIBILITY_SELECTOR_ITEMS } from "@/helpers/consts";
import { useGlobalStore } from "@/store/module";
import Selector from "@/components/kit/Selector";
@ -11,14 +11,14 @@ interface Props {
const MemoVisibilitySelector = (props: Props) => {
const { value, onChange } = props;
const { t } = useTranslation();
const t = useTranslate();
const {
state: { systemStatus },
} = useGlobalStore();
const memoVisibilityOptionSelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
return {
value: item.value,
text: t(`memo.visibility.${toLower(item.value)}`),
text: t(`memo.visibility.${toLower(item.value) as Lowercase<typeof item.value>}`),
};
});

View File

@ -1,6 +1,7 @@
import { isNumber, last, uniq } from "lodash-es";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslate } from "@/utils/i18n";
import { useTranslation } from "react-i18next";
import { getMatchedNodes } from "@/labs/marked";
import { upsertMemoResource } from "@/helpers/api";
@ -42,7 +43,8 @@ interface State {
const MemoEditor = (props: Props) => {
const { className, memoId, onConfirm } = props;
const { t, i18n } = useTranslation();
const { i18n } = useTranslation();
const t = useTranslate();
const {
state: { systemStatus },
} = useGlobalStore();

View File

@ -1,5 +1,5 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useLocation } from "react-router-dom";
import { useFilterStore, useShortcutStore } from "@/store/module";
import { getDateString } from "@/helpers/datetime";
@ -8,7 +8,7 @@ import Icon from "./Icon";
import "@/less/memo-filter.less";
const MemoFilter = () => {
const { t } = useTranslation();
const t = useTranslate();
const location = useLocation();
const filterStore = useFilterStore();
const shortcutStore = useShortcutStore();
@ -46,7 +46,8 @@ const MemoFilter = () => {
filterStore.setMemoTypeFilter(undefined);
}}
>
<Icon.Box className="icon-text" /> {t(getTextWithMemoType(memoType as MemoSpecType))}
<Icon.Box className="icon-text" />{" "}
{t(getTextWithMemoType(memoType as MemoSpecType) as Exclude<ReturnType<typeof getTextWithMemoType>, "">)}
</div>
<div
className={"filter-item-container " + (visibility ? "" : "!hidden")}

View File

@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useFilterStore, useMemoStore, useShortcutStore, useUserStore } from "@/store/module";
import { TAG_REG, LINK_REG, PLAIN_LINK_REG } from "@/labs/marked/parser";
import { getTimeStampByDate } from "@/helpers/datetime";
@ -11,7 +11,7 @@ import Memo from "./Memo";
import "@/less/memo-list.less";
const MemoList = () => {
const { t } = useTranslation();
const t = useTranslate();
const memoStore = useMemoStore();
const userStore = useUserStore();
const shortcutStore = useShortcutStore();

View File

@ -1,7 +1,7 @@
import copy from "copy-to-clipboard";
import React from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useResourceStore } from "@/store/module";
import { getResourceUrl } from "@/utils/resource";
import Dropdown from "./kit/Dropdown";
@ -15,7 +15,7 @@ interface Props {
}
const ResourceItemDropdown = ({ resource }: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const resourceStore = useResourceStore();
const resources = resourceStore.state.resources;

View File

@ -1,5 +1,5 @@
import { useState, useRef } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import Icon from "./Icon";
import useDebounce from "@/hooks/useDebounce";
@ -7,7 +7,7 @@ interface ResourceSearchBarProps {
setQuery: (queryText: string) => void;
}
const ResourceSearchBar = ({ setQuery }: ResourceSearchBarProps) => {
const { t } = useTranslation();
const t = useTranslate();
const [queryText, setQueryText] = useState("");
const inputRef = useRef<HTMLInputElement>(null);

View File

@ -1,11 +1,11 @@
import { useEffect, useState, useRef } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import useDebounce from "@/hooks/useDebounce";
import { useFilterStore } from "@/store/module";
import Icon from "./Icon";
const SearchBar = () => {
const { t } = useTranslation();
const t = useTranslate();
const filterStore = useFilterStore();
const [queryText, setQueryText] = useState("");
const inputRef = useRef<HTMLInputElement>(null);

View File

@ -1,7 +1,7 @@
import { Table } from "@mui/joy";
import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useUserStore } from "@/store/module";
import * as api from "@/helpers/api";
import Dropdown from "../kit/Dropdown";
@ -15,7 +15,7 @@ interface State {
}
const PreferencesSection = () => {
const { t } = useTranslation();
const t = useTranslate();
const userStore = useUserStore();
const currentUser = userStore.state.user;
const [state, setState] = useState<State>({

View File

@ -1,5 +1,5 @@
import { Button, Input, Textarea } from "@mui/joy";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useUserStore } from "@/store/module";
import { showCommonDialog } from "../Dialog/CommonDialog";
import showChangePasswordDialog from "../ChangePasswordDialog";
@ -8,7 +8,7 @@ import showUpdateAccountDialog from "../UpdateAccountDialog";
import UserAvatar from "../UserAvatar";
const MyAccountSection = () => {
const { t } = useTranslation();
const t = useTranslate();
const userStore = useUserStore();
const user = userStore.state.user as User;
const openAPIRoute = `${window.location.origin}/api/v1/memo?openId=${user.openId}`;

View File

@ -2,7 +2,7 @@ import { Input, Button, Divider, Switch, Option, Select } from "@mui/joy";
import { useState } from "react";
import { toast } from "react-hot-toast";
import React from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useGlobalStore, useUserStore } from "@/store/module";
import { VISIBILITY_SELECTOR_ITEMS } from "@/helpers/consts";
import AppearanceSelect from "../AppearanceSelect";
@ -11,7 +11,7 @@ import LearnMore from "../LearnMore";
import "@/less/settings/preferences-section.less";
const PreferencesSection = () => {
const { t } = useTranslation();
const t = useTranslate();
const globalStore = useGlobalStore();
const userStore = useUserStore();
const { appearance, locale } = globalStore.state;
@ -20,7 +20,7 @@ const PreferencesSection = () => {
const visibilitySelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
return {
value: item.value,
text: t(`memo.visibility.${item.text.toLowerCase()}`),
text: t(`memo.visibility.${item.text.toLowerCase() as Lowercase<typeof item.text>}`),
};
});

View File

@ -1,7 +1,7 @@
import { Divider } from "@mui/joy";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import * as api from "@/helpers/api";
import showCreateIdentityProviderDialog from "../CreateIdentityProviderDialog";
import Dropdown from "../kit/Dropdown";
@ -9,7 +9,7 @@ import { showCommonDialog } from "../Dialog/CommonDialog";
import LearnMore from "../LearnMore";
const SSOSection = () => {
const { t } = useTranslation();
const t = useTranslate();
const [identityProviderList, setIdentityProviderList] = useState<IdentityProvider[]>([]);
useEffect(() => {

View File

@ -1,7 +1,7 @@
import { Divider, Select, Option } from "@mui/joy";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useGlobalStore } from "@/store/module";
import * as api from "@/helpers/api";
import showCreateStorageServiceDialog from "../CreateStorageServiceDialog";
@ -11,7 +11,7 @@ import { showCommonDialog } from "../Dialog/CommonDialog";
import LearnMore from "../LearnMore";
const StorageSection = () => {
const { t } = useTranslation();
const t = useTranslate();
const globalStore = useGlobalStore();
const systemStatus = globalStore.state.systemStatus;
const [storageServiceId, setStorageServiceId] = useState(systemStatus.storageServiceId);

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { Button, Divider, Input, Switch, Textarea, Tooltip } from "@mui/joy";
import { formatBytes } from "@/helpers/utils";
import { useGlobalStore } from "@/store/module";
@ -21,7 +21,7 @@ interface State {
}
const SystemSection = () => {
const { t } = useTranslation();
const t = useTranslate();
const globalStore = useGlobalStore();
const systemStatus = globalStore.state.systemStatus;
const [state, setState] = useState<State>({

View File

@ -2,7 +2,7 @@ import { Select, Option } from "@mui/joy";
import { QRCodeSVG } from "qrcode.react";
import React, { useEffect, useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import copy from "copy-to-clipboard";
import { toLower } from "lodash-es";
import toImage from "@/labs/html2image";
@ -30,7 +30,7 @@ interface State {
const ShareMemoDialog: React.FC<Props> = (props: Props) => {
const { memo: propsMemo, destroy } = props;
const { t } = useTranslation();
const t = useTranslate();
const userStore = useUserStore();
const memoStore = useMemoStore();
const globalStore = useGlobalStore();
@ -109,7 +109,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
const memoVisibilityOptionSelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
return {
value: item.value,
text: t(`memo.visibility.${toLower(item.value)}`),
text: t(`memo.visibility.${toLower(item.value) as Lowercase<typeof item.value>}`),
};
});

View File

@ -1,6 +1,6 @@
import { useEffect } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useFilterStore, useShortcutStore } from "@/store/module";
import { getTimeStampByDate } from "@/helpers/datetime";
import useToggle from "@/hooks/useToggle";
@ -9,7 +9,7 @@ import Icon from "./Icon";
import showCreateShortcutDialog from "./CreateShortcutDialog";
const ShortcutList = () => {
const { t } = useTranslation();
const t = useTranslate();
const filterStore = useFilterStore();
const shortcutStore = useShortcutStore();
const filter = filterStore.state;
@ -62,7 +62,7 @@ interface ShortcutContainerProps {
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
const { shortcut, isActive } = props;
const { t } = useTranslation();
const t = useTranslate();
const filterStore = useFilterStore();
const shortcutStore = useShortcutStore();
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useFilterStore, useTagStore } from "@/store/module";
import useToggle from "@/hooks/useToggle";
import Icon from "./Icon";
@ -12,7 +12,7 @@ interface Tag {
}
const TagList = () => {
const { t } = useTranslation();
const t = useTranslate();
const filterStore = useFilterStore();
const tagStore = useTagStore();
const tagsText = tagStore.state.tags;

View File

@ -1,7 +1,7 @@
import { isEqual } from "lodash-es";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useUserStore } from "@/store/module";
import { convertFileToBase64 } from "@/helpers/utils";
import Icon from "./Icon";
@ -18,7 +18,7 @@ interface State {
}
const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const userStore = useUserStore();
const user = userStore.state.user as User;
const [state, setState] = useState<State>({

View File

@ -1,5 +1,5 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { toast } from "react-hot-toast";
import { useGlobalStore } from "@/store/module";
import * as api from "@/helpers/api";
@ -12,7 +12,7 @@ import AppearanceSelect from "./AppearanceSelect";
type Props = DialogProps;
const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const globalStore = useGlobalStore();
const [state, setState] = useState<CustomizedProfile>(globalStore.state.systemStatus.customizedProfile);

View File

@ -1,6 +1,6 @@
import { Button, Input } from "@mui/joy";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { toast } from "react-hot-toast";
import { useGlobalStore } from "@/store/module";
import * as api from "@/helpers/api";
@ -14,7 +14,7 @@ interface Props extends DialogProps {
}
const UpdateLocalStorageDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const t = useTranslate();
const { destroy, localStoragePath, confirmCallback } = props;
const globalStore = useGlobalStore();
const [path, setPath] = useState(localStoragePath || "");

View File

@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useFilterStore, useMemoStore, useUserStore } from "../store/module";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { getMemoStats } from "@/helpers/api";
import { DAILY_TIMESTAMP } from "@/helpers/consts";
import { getDateStampByDate, getDateString, getTimeStampByDate } from "@/helpers/datetime";
@ -29,7 +29,7 @@ interface DailyUsageStat {
}
const UsageHeatMap = () => {
const { t } = useTranslation();
const t = useTranslate();
const filterStore = useFilterStore();
const userStore = useUserStore();
const memoStore = useMemoStore();

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useNavigate } from "react-router-dom";
import { useGlobalStore, useUserStore } from "@/store/module";
import Dropdown from "./kit/Dropdown";
@ -8,7 +8,7 @@ import UserAvatar from "./UserAvatar";
import showAboutSiteDialog from "./AboutSiteDialog";
const UserBanner = () => {
const { t } = useTranslation();
const t = useTranslate();
const navigate = useNavigate();
const globalStore = useGlobalStore();
const userStore = useUserStore();

View File

@ -1,6 +1,6 @@
import { Badge, Button } from "@mui/joy";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { DAILY_TIMESTAMP } from "@/helpers/consts";
import { getMemoStats } from "@/helpers/api";
import { getDateStampByDate } from "@/helpers/datetime";
@ -15,7 +15,7 @@ interface DatePickerProps {
}
const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => {
const { t } = useTranslation();
const t = useTranslate();
const { className, datestamp, handleDateStampChange } = props;
const [currentDateStamp, setCurrentDateStamp] = useState<DateStamp>(getMonthFirstDayDateStamp(datestamp));
const [countByDate, setCountByDate] = useState(new Map());

View File

@ -1,6 +1,6 @@
import { Tooltip } from "@mui/joy";
import { memo, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import useToggle from "@/hooks/useToggle";
import Icon from "../Icon";
import "@/less/common/selector.less";
@ -20,13 +20,13 @@ interface Props {
}
const nullItem = {
text: "common.select",
text: "common.select" as const,
value: "",
};
const Selector: React.FC<Props> = (props: Props) => {
const { className, dataSource, handleValueChanged, value, disabled, tooltipTitle } = props;
const { t } = useTranslation();
const t = useTranslate();
const [showSelector, toggleSelectorStatus] = useToggle(false);
const selectorElRef = useRef<HTMLDivElement>(null);

View File

@ -11,7 +11,7 @@ export const VISIBILITY_SELECTOR_ITEMS = [
{ text: "PRIVATE", value: "PRIVATE" },
{ text: "PROTECTED", value: "PROTECTED" },
{ text: "PUBLIC", value: "PUBLIC" },
];
] as const;
// space width for tab action in editor
export const TAB_SPACE_WIDTH = 2;

View File

@ -4,7 +4,7 @@ import { TAG_REG, LINK_REG, PLAIN_LINK_REG } from "@/labs/marked/parser";
export const relationConsts = [
{ text: "filter.and", value: "AND" },
{ text: "filter.or", value: "OR" },
];
] as const;
export const filterConsts = {
TAG: {
@ -105,11 +105,11 @@ export const filterConsts = {
},
],
},
};
} as const;
export const memoSpecialTypes = filterConsts["TYPE"].values;
export const getTextWithMemoType = (type: string): string => {
export const getTextWithMemoType = (type: string) => {
for (const t of memoSpecialTypes) {
if (t.value === type) {
return t.text;

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import toast from "react-hot-toast";
import { useMemoStore } from "@/store/module";
import useLoading from "@/hooks/useLoading";
@ -9,7 +9,7 @@ import Empty from "@/components/Empty";
import "@/less/archived.less";
const Archived = () => {
const { t } = useTranslation();
const t = useTranslate();
const memoStore = useMemoStore();
const loadingState = useLoading();
const [archivedMemos, setArchivedMemos] = useState<Memo[]>([]);

View File

@ -1,7 +1,7 @@
import { Button, Divider } from "@mui/joy";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useGlobalStore, useUserStore } from "@/store/module";
import * as api from "@/helpers/api";
import { absolutifyLink } from "@/helpers/utils";
@ -11,7 +11,7 @@ import AppearanceSelect from "@/components/AppearanceSelect";
import LocaleSelect from "@/components/LocaleSelect";
const Auth = () => {
const { t } = useTranslation();
const t = useTranslate();
const globalStore = useGlobalStore();
const userStore = useUserStore();
const actionBtnLoadingState = useLoading(false);

View File

@ -1,7 +1,7 @@
import { last } from "lodash-es";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useSearchParams } from "react-router-dom";
import * as api from "@/helpers/api";
import { absolutifyLink } from "@/helpers/utils";
@ -14,7 +14,7 @@ interface State {
}
const AuthCallback = () => {
const { t } = useTranslation();
const t = useTranslate();
const [searchParams] = useSearchParams();
const userStore = useUserStore();
const [state, setState] = useState<State>({

View File

@ -1,7 +1,7 @@
import { last } from "lodash-es";
import { useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useMemoStore, useUserStore } from "@/store/module";
import { DAILY_TIMESTAMP, DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
import MobileHeader from "@/components/MobileHeader";
@ -17,7 +17,7 @@ import { convertToMillis, getDateStampByDate, getNormalizedDateString, getTimeSt
import Empty from "@/components/Empty";
const DailyReview = () => {
const { t } = useTranslation();
const t = useTranslate();
const memoStore = useMemoStore();
const memos = memoStore.state.memos;

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useLocation } from "react-router-dom";
import { useFilterStore, useMemoStore } from "@/store/module";
import { TAG_REG } from "@/labs/marked/parser";
@ -16,7 +16,7 @@ interface State {
}
const Explore = () => {
const { t } = useTranslation();
const t = useTranslate();
const location = useLocation();
const filterStore = useFilterStore();
const memoStore = useMemoStore();

View File

@ -1,6 +1,6 @@
import { useEffect } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useGlobalStore, useUserStore } from "@/store/module";
import MemoEditor from "@/components/MemoEditor";
import MemoFilter from "@/components/MemoFilter";
@ -9,7 +9,7 @@ import MobileHeader from "@/components/MobileHeader";
import HomeSidebar from "@/components/HomeSidebar";
function Home() {
const { t } = useTranslation();
const t = useTranslate();
const globalStore = useGlobalStore();
const userStore = useUserStore();
const user = userStore.state.user;

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { Link, useLocation, useParams } from "react-router-dom";
import { UNKNOWN_ID } from "@/helpers/consts";
import { useGlobalStore, useMemoStore } from "@/store/module";
@ -13,7 +13,7 @@ interface State {
}
const MemoDetail = () => {
const { t } = useTranslation();
const t = useTranslate();
const params = useParams();
const location = useLocation();
const globalStore = useGlobalStore();

View File

@ -1,9 +1,9 @@
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { Link } from "react-router-dom";
import "@/less/not-found.less";
const NotFound = () => {
const { t } = useTranslation();
const t = useTranslate();
return (
<div className="page-wrapper not-found">

View File

@ -1,7 +1,7 @@
import { Button } from "@mui/joy";
import { useEffect, useMemo, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
import useLoading from "@/hooks/useLoading";
import useEvent from "@/hooks/useEvent";
@ -17,7 +17,7 @@ import showCreateResourceDialog from "@/components/CreateResourceDialog";
import Empty from "@/components/Empty";
const ResourcesDashboard = () => {
const { t } = useTranslation();
const t = useTranslate();
const loadingState = useLoading();
const resourceStore = useResourceStore();
const resources = resourceStore.state.resources;

View File

@ -1,6 +1,6 @@
import { Option, Select } from "@mui/joy";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
import { useUserStore } from "@/store/module";
import Icon from "@/components/Icon";
import BetaBadge from "@/components/BetaBadge";
@ -20,7 +20,7 @@ interface State {
}
const Setting = () => {
const { t } = useTranslation();
const t = useTranslate();
const userStore = useUserStore();
const user = userStore.state.user;
const [state, setState] = useState<State>({

View File

@ -3,7 +3,7 @@ import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
import store, { useAppSelector } from "../";
import { patchResource, setResources, deleteResource, upsertResources } from "../reducer/resource";
import { useGlobalStore } from "./global";
import { useTranslation } from "react-i18next";
import { useTranslate } from "@/utils/i18n";
const convertResponseModelResource = (resource: Resource): Resource => {
return {
@ -15,7 +15,7 @@ const convertResponseModelResource = (resource: Resource): Resource => {
export const useResourceStore = () => {
const state = useAppSelector((state) => state.resource);
const { t } = useTranslation();
const t = useTranslate();
const globalStore = useGlobalStore();
const maxUploadSizeMiB = globalStore.state.systemStatus.maxUploadSizeMiB;

View File

@ -0,0 +1,3 @@
export type NestedKeyOf<T, K = keyof T> = K extends keyof T & (string | number)
? `${K}` | (T[K] extends object ? `${K}.${NestedKeyOf<T[K]>}` : never)
: never;

View File

@ -1,5 +1,8 @@
import i18n, { TLocale, availableLocales } from "@/i18n";
import { FallbackLngObjList } from "i18next";
import { useTranslation } from "react-i18next";
import locales from "@/locales/en.json";
import type { NestedKeyOf } from "@/types/utils/nestedKeyOf.types";
export const findNearestLanguageMatch = (codename: string): Locale => {
// Find existing translations for full codes (e.g. "en-US", "zh-Hant")
@ -33,3 +36,14 @@ export const findNearestLanguageMatch = (codename: string): Locale => {
// should be "en", so the selector is not empty if there isn't a translation for current user's language
return (i18n.store.options.fallbackLng as FallbackLngObjList).default[0] as Locale;
};
// Represents the keys of nested translation objects.
type Translations = NestedKeyOf<typeof locales>;
// Represents a typed translation function.
type TypedT = (key: Translations, params?: Record<string, any>) => string;
export const useTranslate = (): TypedT => {
const { t } = useTranslation<Translations>();
return t;
};