mirror of
https://github.com/usememos/memos.git
synced 2024-12-25 04:13:07 +03:00
feat: use i18next
This commit is contained in:
parent
307483e499
commit
366afdd1e4
@ -13,11 +13,13 @@
|
||||
"copy-to-clipboard": "^3.3.2",
|
||||
"dayjs": "^1.11.3",
|
||||
"emoji-picker-react": "^3.6.2",
|
||||
"i18next": "^21.9.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"qs": "^6.11.0",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-feather": "^2.0.10",
|
||||
"react-i18next": "^11.18.6",
|
||||
"react-redux": "^8.0.1",
|
||||
"react-router-dom": "^6.4.0",
|
||||
"vite-plugin-pwa": "^0.12.8"
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import useI18n from "./hooks/useI18n";
|
||||
import { globalService, locationService } from "./services";
|
||||
import { useAppSelector } from "./store";
|
||||
import router from "./router";
|
||||
import * as storage from "./helpers/storage";
|
||||
|
||||
function App() {
|
||||
const { setLocale } = useI18n();
|
||||
const { i18n } = useTranslation();
|
||||
const global = useAppSelector((state) => state.global);
|
||||
|
||||
useEffect(() => {
|
||||
@ -20,7 +20,7 @@ function App() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLocale(global.locale);
|
||||
i18n.changeLanguage(global.locale);
|
||||
storage.set({
|
||||
locale: global.locale,
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as api from "../helpers/api";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
@ -10,7 +10,7 @@ import "../less/about-site-dialog.less";
|
||||
type Props = DialogProps;
|
||||
|
||||
const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const [profile, setProfile] = useState<Profile>();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as utils from "../helpers/utils";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import { memoService } from "../services";
|
||||
import toastHelper from "./Toast";
|
||||
@ -18,7 +18,7 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
||||
createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
|
||||
archivedAtStr: utils.getDateTimeString(propsMemo.updatedTs ?? Date.now()),
|
||||
};
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { memoService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import Icon from "./Icon";
|
||||
@ -12,7 +12,7 @@ import "../less/archived-memo-dialog.less";
|
||||
type Props = DialogProps;
|
||||
|
||||
const ArchivedMemoDialog: React.FC<Props> = (props: Props) => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const { destroy } = props;
|
||||
const memos = useAppSelector((state) => state.memo.memos);
|
||||
const loadingState = useLoading();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { memoService } from "../services";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
@ -12,7 +12,7 @@ interface Props extends DialogProps {
|
||||
}
|
||||
|
||||
const ChangeMemoCreatedTsDialog: React.FC<Props> = (props: Props) => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const { destroy, memoId } = props;
|
||||
const [createdAt, setCreatedAt] = useState("");
|
||||
const maxDatetimeValue = dayjs().format("YYYY-MM-DDTHH:mm");
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { userService } from "../services";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
@ -17,7 +17,7 @@ const validateConfig: ValidatorConfig = {
|
||||
type Props = DialogProps;
|
||||
|
||||
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { memoService, shortcutService } from "../services";
|
||||
import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
@ -6,7 +7,6 @@ import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
import Selector from "./common/Selector";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import "../less/create-shortcut-dialog.less";
|
||||
|
||||
interface Props extends DialogProps {
|
||||
@ -19,7 +19,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [filters, setFilters] = useState<Filter[]>([]);
|
||||
const requestState = useLoading(false);
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const shownMemoLength = memoService.getState().memos.filter((memo) => {
|
||||
return checkShouldShowMemoWithFilters(memo, filters);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../store";
|
||||
import toImage from "../labs/html2image";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
||||
import * as utils from "../helpers/utils";
|
||||
@ -20,7 +20,7 @@ const monthChineseStrArray = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "
|
||||
const weekdayChineseStrArray = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
|
||||
const DailyReviewDialog: React.FC<Props> = (props: Props) => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const memos = useAppSelector((state) => state.memo.memos);
|
||||
const [currentDateStamp, setCurrentDateStamp] = useState(utils.getDateStampByDate(utils.getDateString(props.currentDateStamp)));
|
||||
const [showDatePicker, toggleShowDatePicker] = useToggle(false);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react";
|
||||
import useI18n from "../../hooks/useI18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useRefresh from "../../hooks/useRefresh";
|
||||
import Only from "../common/OnlyWhen";
|
||||
import "../../less/editor.less";
|
||||
@ -35,7 +35,7 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
|
||||
onConfirmBtnClick: handleConfirmBtnClickCallback,
|
||||
onContentChange: handleContentChangeCallback,
|
||||
} = props;
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const editorRef = useRef<HTMLTextAreaElement>(null);
|
||||
const refresh = useRefresh();
|
||||
|
||||
|
@ -2,8 +2,8 @@ import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import { indexOf } from "lodash-es";
|
||||
import { memo, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "dayjs/locale/zh";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { UNKNOWN_ID } from "../helpers/consts";
|
||||
import { DONE_BLOCK_REG, TODO_BLOCK_REG } from "../helpers/marked";
|
||||
import { editorStateService, locationService, memoService, userService } from "../services";
|
||||
@ -32,8 +32,8 @@ export const getFormatedMemoCreatedAtStr = (createdTs: number, locale = "en"): s
|
||||
|
||||
const Memo: React.FC<Props> = (props: Props) => {
|
||||
const memo = props.memo;
|
||||
const { t, locale } = useI18n();
|
||||
const [createdAtStr, setCreatedAtStr] = useState<string>(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
|
||||
const { t, i18n } = useTranslation();
|
||||
const [createdAtStr, setCreatedAtStr] = useState<string>(getFormatedMemoCreatedAtStr(memo.createdTs, i18n.language));
|
||||
const memoContainerRef = useRef<HTMLDivElement>(null);
|
||||
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
||||
const isVisitorMode = userService.isVisitorMode();
|
||||
@ -42,14 +42,14 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
let intervalFlag = -1;
|
||||
if (Date.now() - memo.createdTs < 1000 * 60 * 60 * 24) {
|
||||
intervalFlag = setInterval(() => {
|
||||
setCreatedAtStr(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
|
||||
setCreatedAtStr(getFormatedMemoCreatedAtStr(memo.createdTs, i18n.language));
|
||||
}, 1000 * 1);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalFlag);
|
||||
};
|
||||
}, [locale]);
|
||||
}, [i18n.language]);
|
||||
|
||||
const handleShowMemoStoryDialog = () => {
|
||||
showMemoCardDialog(memo);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import copy from "copy-to-clipboard";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { editorStateService, memoService, userService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
||||
@ -13,7 +14,6 @@ import Selector from "./common/Selector";
|
||||
import MemoContent from "./MemoContent";
|
||||
import MemoResources from "./MemoResources";
|
||||
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import "../less/memo-card-dialog.less";
|
||||
|
||||
interface LinkedMemo extends Memo {
|
||||
@ -26,13 +26,13 @@ interface Props extends DialogProps {
|
||||
}
|
||||
|
||||
const MemoCardDialog: React.FC<Props> = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const memos = useAppSelector((state) => state.memo.memos);
|
||||
const [memo, setMemo] = useState<Memo>({
|
||||
...props.memo,
|
||||
});
|
||||
const [linkMemos, setLinkMemos] = useState<LinkedMemo[]>([]);
|
||||
const [linkedMemos, setLinkedMemos] = useState<LinkedMemo[]>([]);
|
||||
const { t } = useI18n();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLinkedMemos = async () => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { IEmojiData } from "emoji-picker-react";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UNKNOWN_ID } from "../helpers/consts";
|
||||
import { editorStateService, locationService, memoService, resourceService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { useAppSelector } from "../store";
|
||||
import * as storage from "../helpers/storage";
|
||||
import Icon from "./Icon";
|
||||
@ -18,7 +18,7 @@ interface State {
|
||||
}
|
||||
|
||||
const MemoEditor = () => {
|
||||
const { t, locale } = useI18n();
|
||||
const { t, i18n } = useTranslation();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const editorState = useAppSelector((state) => state.editor);
|
||||
const tags = useAppSelector((state) => state.memo.tags);
|
||||
@ -276,7 +276,7 @@ const MemoEditor = () => {
|
||||
onConfirmBtnClick: handleSaveBtnClick,
|
||||
onContentChange: handleContentChange,
|
||||
}),
|
||||
[isEditing, state.fullscreen, locale, editorFontStyle]
|
||||
[isEditing, state.fullscreen, i18n.language, editorFontStyle]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -1,17 +1,16 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService, shortcutService } from "../services";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { getTextWithMemoType } from "../helpers/filter";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import "../less/memo-filter.less";
|
||||
|
||||
const MemoFilter = () => {
|
||||
const { t } = useTranslation();
|
||||
const query = useAppSelector((state) => state.location.query);
|
||||
useAppSelector((state) => state.shortcut.shortcuts);
|
||||
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
|
||||
const shortcut = shortcutId ? shortcutService.getShortcutById(shortcutId) : null;
|
||||
const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut);
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<div className={`filter-query-container ${showFilter ? "" : "!hidden"}`}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { memoService, shortcutService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { useAppSelector } from "../store";
|
||||
import { IMAGE_URL_REG, LINK_URL_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/marked";
|
||||
import * as utils from "../helpers/utils";
|
||||
@ -11,7 +11,7 @@ import Memo from "./Memo";
|
||||
import "../less/memo-list.less";
|
||||
|
||||
const MemoList = () => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const query = useAppSelector((state) => state.location.query);
|
||||
const { memos, isFetching } = useAppSelector((state) => state.memo);
|
||||
const wrapperElement = useRef<HTMLDivElement>(null);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { userService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import showAboutSiteDialog from "./AboutSiteDialog";
|
||||
import showArchivedMemoDialog from "./ArchivedMemoDialog";
|
||||
@ -15,7 +15,7 @@ interface Props {
|
||||
|
||||
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
||||
const { shownStatus, setShownStatus } = props;
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const popupElRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import copy from "copy-to-clipboard";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { resourceService } from "../services";
|
||||
import Dropdown from "./common/Dropdown";
|
||||
@ -20,7 +20,7 @@ interface State {
|
||||
|
||||
const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||
const { destroy } = props;
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const loadingState = useLoading();
|
||||
const [state, setState] = useState<State>({
|
||||
resources: [],
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { locationService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { memoSpecialTypes } from "../helpers/filter";
|
||||
import Icon from "./Icon";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import "../less/search-bar.less";
|
||||
|
||||
const SearchBar = () => {
|
||||
const { t } = useTranslation();
|
||||
const memoType = useAppSelector((state) => state.location.query?.type);
|
||||
const { t } = useI18n();
|
||||
|
||||
const handleMemoTypeItemClick = (type: MemoSpecType | undefined) => {
|
||||
const { type: prevType } = locationService.getState().query ?? {};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../store";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import MyAccountSection from "./Settings/MyAccountSection";
|
||||
@ -18,7 +18,7 @@ interface State {
|
||||
|
||||
const SettingDialog: React.FC<Props> = (props: Props) => {
|
||||
const { destroy } = props;
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const [state, setState] = useState<State>({
|
||||
selectedSection: "my-account",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import useI18n from "../../hooks/useI18n";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { userService } from "../../services";
|
||||
import { useAppSelector } from "../../store";
|
||||
import * as api from "../../helpers/api";
|
||||
@ -15,7 +15,7 @@ interface State {
|
||||
}
|
||||
|
||||
const PreferencesSection = () => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const currentUser = useAppSelector((state) => state.user.user);
|
||||
const [state, setState] = useState<State>({
|
||||
createUserEmail: "",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import useI18n from "../../hooks/useI18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../../store";
|
||||
import { userService } from "../../services";
|
||||
import { validate, ValidatorConfig } from "../../helpers/validator";
|
||||
@ -16,7 +16,7 @@ const validateConfig: ValidatorConfig = {
|
||||
};
|
||||
|
||||
const MyAccountSection = () => {
|
||||
const { t, locale } = useI18n();
|
||||
const { t, i18n } = useTranslation();
|
||||
const user = useAppSelector((state) => state.user.user as User);
|
||||
const [username, setUsername] = useState<string>(user.name);
|
||||
const openAPIRoute = `${window.location.origin}/api/memo?openId=${user.openId}`;
|
||||
@ -33,7 +33,7 @@ const MyAccountSection = () => {
|
||||
|
||||
const usernameValidResult = validate(username, validateConfig);
|
||||
if (!usernameValidResult.result) {
|
||||
toastHelper.error(t("common.username") + locale === "zh" ? "" : " " + usernameValidResult.reason);
|
||||
toastHelper.error(t("common.username") + i18n.language === "zh" ? "" : " " + usernameValidResult.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ const MyAccountSection = () => {
|
||||
id: user.id,
|
||||
name: username,
|
||||
});
|
||||
toastHelper.info(t("common.username") + locale === "zh" ? "" : " " + t("common.changed"));
|
||||
toastHelper.info(t("common.username") + i18n.language === "zh" ? "" : " " + t("common.changed"));
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toastHelper.error(error.response.data.message);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { globalService, userService } from "../../services";
|
||||
import { useAppSelector } from "../../store";
|
||||
import { VISIBILITY_SELECTOR_ITEMS } from "../../helpers/consts";
|
||||
import useI18n from "../../hooks/useI18n";
|
||||
import Selector from "../common/Selector";
|
||||
import "../../less/settings/preferences-section.less";
|
||||
|
||||
@ -32,7 +32,7 @@ const editorFontStyleSelectorItems = [
|
||||
];
|
||||
|
||||
const PreferencesSection = () => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const { setting } = useAppSelector((state) => state.user.user as User);
|
||||
|
||||
const handleLocaleChanged = async (value: string) => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { userService } from "../services";
|
||||
import toImage from "../labs/html2image";
|
||||
import { ANIMATION_DURATION } from "../helpers/consts";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { IMAGE_URL_REG } from "../helpers/marked";
|
||||
import Only from "./common/OnlyWhen";
|
||||
@ -18,7 +18,7 @@ interface Props extends DialogProps {
|
||||
|
||||
const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
||||
const { memo: propsMemo, destroy } = props;
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const { user: userinfo } = userService.getState();
|
||||
const memo = {
|
||||
...propsMemo,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { locationService, shortcutService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import * as utils from "../helpers/utils";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
@ -14,7 +14,7 @@ const ShortcutList = () => {
|
||||
const query = useAppSelector((state) => state.location.query);
|
||||
const shortcuts = useAppSelector((state) => state.shortcut.shortcuts);
|
||||
const loadingState = useLoading();
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const pinnedShortcuts = shortcuts
|
||||
.filter((s) => s.rowStatus === "ARCHIVED")
|
||||
@ -59,7 +59,7 @@ interface ShortcutContainerProps {
|
||||
|
||||
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
|
||||
const { shortcut, isActive } = props;
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||
|
||||
const handleShortcutClick = () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { userService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import Icon from "./Icon";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import showDailyReviewDialog from "./DailyReviewDialog";
|
||||
@ -12,7 +12,7 @@ import TagList from "./TagList";
|
||||
import "../less/siderbar.less";
|
||||
|
||||
const Sidebar = () => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSettingBtnClick = () => {
|
||||
showSettingDialog();
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService, memoService, userService } from "../services";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import Icon from "./Icon";
|
||||
import Only from "./common/OnlyWhen";
|
||||
@ -14,7 +14,7 @@ interface Tag {
|
||||
}
|
||||
|
||||
const TagList = () => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const { memos, tags: tagsText } = useAppSelector((state) => state.memo);
|
||||
const query = useAppSelector((state) => state.location.query);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as utils from "../helpers/utils";
|
||||
import userService from "../services/userService";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { locationService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import Icon from "./Icon";
|
||||
@ -9,7 +9,7 @@ import MenuBtnsPopup from "./MenuBtnsPopup";
|
||||
import "../less/user-banner.less";
|
||||
|
||||
const UserBanner = () => {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const { user, owner } = useAppSelector((state) => state.user);
|
||||
const { memos, tags } = useAppSelector((state) => state.memo);
|
||||
const [shouldShowPopupBtns, setShouldShowPopupBtns] = useState(false);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { memo, useEffect, useRef } from "react";
|
||||
import useI18n from "../../hooks/useI18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useToggle from "../../hooks/useToggle";
|
||||
import Icon from "../Icon";
|
||||
import "../../less/common/selector.less";
|
||||
@ -23,7 +23,7 @@ const nullItem = {
|
||||
|
||||
const Selector: React.FC<Props> = (props: Props) => {
|
||||
const { className, dataSource, handleValueChanged, value } = props;
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const [showSelector, toggleSelectorStatus] = useToggle(false);
|
||||
|
||||
const seletorElRef = useRef<HTMLDivElement>(null);
|
||||
|
@ -1,3 +0,0 @@
|
||||
import useI18n from "../labs/i18n/useI18n";
|
||||
|
||||
export default useI18n;
|
23
web/src/i18n.ts
Normal file
23
web/src/i18n.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import enLocale from "./locales/en.json";
|
||||
import zhLocale from "./locales/zh.json";
|
||||
import viLocale from "./locales/vi.json";
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
en: {
|
||||
translation: enLocale,
|
||||
},
|
||||
zh: {
|
||||
translation: zhLocale,
|
||||
},
|
||||
vi: {
|
||||
translation: viLocale,
|
||||
},
|
||||
},
|
||||
lng: "en",
|
||||
fallbackLng: "en",
|
||||
});
|
||||
|
||||
export default i18n;
|
@ -1,27 +0,0 @@
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
import i18nStore from "./i18nStore";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
const i18nContext = createContext(i18nStore.getState());
|
||||
|
||||
const I18nProvider: React.FC<Props> = (props: Props) => {
|
||||
const { children } = props;
|
||||
const [i18nState, setI18nState] = useState(i18nStore.getState());
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = i18nStore.subscribe((ns) => {
|
||||
setI18nState(ns);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <i18nContext.Provider value={i18nState}>{children}</i18nContext.Provider>;
|
||||
};
|
||||
|
||||
export default I18nProvider;
|
@ -1,52 +0,0 @@
|
||||
type I18nState = Readonly<{
|
||||
locale: string;
|
||||
}>;
|
||||
|
||||
type Listener = (ns: I18nState, ps?: I18nState) => void;
|
||||
|
||||
const createI18nStore = (preloadedState: I18nState) => {
|
||||
const listeners: Listener[] = [];
|
||||
let currentState = preloadedState;
|
||||
|
||||
const getState = () => {
|
||||
return currentState;
|
||||
};
|
||||
|
||||
const setState = (state: Partial<I18nState>) => {
|
||||
const nextState = {
|
||||
...currentState,
|
||||
...state,
|
||||
};
|
||||
const prevState = currentState;
|
||||
currentState = nextState;
|
||||
|
||||
for (const cb of listeners) {
|
||||
cb(currentState, prevState);
|
||||
}
|
||||
};
|
||||
|
||||
const subscribe = (listener: Listener) => {
|
||||
let isSubscribed = true;
|
||||
listeners.push(listener);
|
||||
|
||||
const unsubscribe = () => {
|
||||
if (!isSubscribed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = listeners.indexOf(listener);
|
||||
listeners.splice(index, 1);
|
||||
isSubscribed = false;
|
||||
};
|
||||
|
||||
return unsubscribe;
|
||||
};
|
||||
|
||||
return {
|
||||
getState,
|
||||
setState,
|
||||
subscribe,
|
||||
};
|
||||
};
|
||||
|
||||
export default createI18nStore;
|
@ -1,9 +0,0 @@
|
||||
import createI18nStore from "./createI18nStore";
|
||||
|
||||
const defaultI18nState = {
|
||||
locale: "en",
|
||||
};
|
||||
|
||||
const i18nStore = createI18nStore(defaultI18nState);
|
||||
|
||||
export default i18nStore;
|
@ -1,57 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import i18nStore from "./i18nStore";
|
||||
import enLocale from "../../locales/en.json";
|
||||
import zhLocale from "../../locales/zh.json";
|
||||
import viLocale from "../../locales/vi.json";
|
||||
|
||||
const resources: Record<string, any> = {
|
||||
en: enLocale,
|
||||
zh: zhLocale,
|
||||
vi: viLocale,
|
||||
};
|
||||
|
||||
const useI18n = () => {
|
||||
const [{ locale }, setState] = useState(i18nStore.getState());
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = i18nStore.subscribe((ns) => {
|
||||
setState(ns);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const translate = (key: string): string => {
|
||||
const keys = key.split(".");
|
||||
let value = resources[locale];
|
||||
for (const k of keys) {
|
||||
if (value) {
|
||||
value = value[k];
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
if (value) {
|
||||
return value;
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
};
|
||||
|
||||
const setLocale = (locale: Locale) => {
|
||||
i18nStore.setState({
|
||||
locale,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
t: translate,
|
||||
locale,
|
||||
setLocale,
|
||||
};
|
||||
};
|
||||
|
||||
export default useI18n;
|
@ -1,8 +1,8 @@
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Provider } from "react-redux";
|
||||
import I18nProvider from "./labs/i18n/I18nProvider";
|
||||
import store from "./store";
|
||||
import App from "./App";
|
||||
import "./i18n";
|
||||
import "./helpers/polyfill";
|
||||
import "./less/global.less";
|
||||
import "./css/index.css";
|
||||
@ -10,9 +10,7 @@ import "./css/index.css";
|
||||
const container = document.getElementById("root");
|
||||
const root = createRoot(container as HTMLElement);
|
||||
root.render(
|
||||
<I18nProvider>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</I18nProvider>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import * as api from "../helpers/api";
|
||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { globalService, userService } from "../services";
|
||||
import Icon from "../components/Icon";
|
||||
@ -18,7 +18,7 @@ const validateConfig: ValidatorConfig = {
|
||||
};
|
||||
|
||||
const Auth = () => {
|
||||
const { t, locale } = useI18n();
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const pageLoadingState = useLoading(true);
|
||||
const [siteHost, setSiteHost] = useState<User>();
|
||||
@ -157,15 +157,15 @@ const Auth = () => {
|
||||
</div>
|
||||
<div className="footer-container">
|
||||
<div className="language-container">
|
||||
<span className={`locale-item ${locale === "en" ? "active" : ""}`} onClick={() => handleLocaleItemClick("en")}>
|
||||
<span className={`locale-item ${i18n.language === "en" ? "active" : ""}`} onClick={() => handleLocaleItemClick("en")}>
|
||||
English
|
||||
</span>
|
||||
<span className="split-line">/</span>
|
||||
<span className={`locale-item ${locale === "zh" ? "active" : ""}`} onClick={() => handleLocaleItemClick("zh")}>
|
||||
<span className={`locale-item ${i18n.language === "zh" ? "active" : ""}`} onClick={() => handleLocaleItemClick("zh")}>
|
||||
中文
|
||||
</span>
|
||||
<span className="split-line">/</span>
|
||||
<span className={`locale-item ${locale === "vi" ? "active" : ""}`} onClick={() => handleLocaleItemClick("vi")}>
|
||||
<span className={`locale-item ${i18n.language === "vi" ? "active" : ""}`} onClick={() => handleLocaleItemClick("vi")}>
|
||||
Tiếng Việt
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1,10 +1,10 @@
|
||||
import dayjs from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { memoService, userService } from "../services";
|
||||
import { isNullorUndefined } from "../helpers/utils";
|
||||
import { useAppSelector } from "../store";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import useQuery from "../hooks/useQuery";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import Only from "../components/common/OnlyWhen";
|
||||
@ -17,7 +17,7 @@ interface State {
|
||||
}
|
||||
|
||||
const Explore = () => {
|
||||
const { t, locale } = useI18n();
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const query = useQuery();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
@ -78,7 +78,7 @@ const Explore = () => {
|
||||
<main className="memos-wrapper">
|
||||
{state.memos.length > 0 ? (
|
||||
state.memos.map((memo) => {
|
||||
const createdAtStr = dayjs(memo.createdTs).locale(locale).format("YYYY/MM/DD HH:mm:ss");
|
||||
const createdAtStr = dayjs(memo.createdTs).locale(i18n.language).format("YYYY/MM/DD HH:mm:ss");
|
||||
return (
|
||||
<div className="memo-container" key={memo.id}>
|
||||
<div className="memo-header">
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { globalService, userService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import useI18n from "../hooks/useI18n";
|
||||
import { isNullorUndefined } from "../helpers/utils";
|
||||
import Only from "../components/common/OnlyWhen";
|
||||
import toastHelper from "../components/Toast";
|
||||
@ -14,7 +14,7 @@ import MemoList from "../components/MemoList";
|
||||
import "../less/home.less";
|
||||
|
||||
function Home() {
|
||||
const { t } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
|
@ -1001,7 +1001,7 @@
|
||||
"@babel/types" "^7.4.4"
|
||||
esutils "^2.0.2"
|
||||
|
||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.8.4":
|
||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.14.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.8.4":
|
||||
version "7.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259"
|
||||
integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
|
||||
@ -2543,6 +2543,20 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
html-parse-stringify@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
|
||||
dependencies:
|
||||
void-elements "3.1.0"
|
||||
|
||||
i18next@^21.9.2:
|
||||
version "21.9.2"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.9.2.tgz#3f7c5594393eb27117c1db4c38f5ec766e68de0e"
|
||||
integrity sha512-00fVrLQOwy45nm3OtC9l1WiLK3nJlIYSljgCt0qzTaAy65aciMdRy9GsuW+a2AtKtdg9/njUGfRH30LRupV7ZQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.2"
|
||||
|
||||
iconv-lite@^0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||
@ -3307,6 +3321,14 @@ react-feather@^2.0.10:
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-i18next@^11.18.6:
|
||||
version "11.18.6"
|
||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.6.tgz#e159c2960c718c1314f1e8fcaa282d1c8b167887"
|
||||
integrity sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.5"
|
||||
html-parse-stringify "^3.0.1"
|
||||
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
@ -3936,6 +3958,11 @@ vite@^3.0.0:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
void-elements@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
|
||||
|
||||
webidl-conversions@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||
|
Loading…
Reference in New Issue
Block a user