mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: use i18next
This commit is contained in:
@ -13,11 +13,13 @@
|
|||||||
"copy-to-clipboard": "^3.3.2",
|
"copy-to-clipboard": "^3.3.2",
|
||||||
"dayjs": "^1.11.3",
|
"dayjs": "^1.11.3",
|
||||||
"emoji-picker-react": "^3.6.2",
|
"emoji-picker-react": "^3.6.2",
|
||||||
|
"i18next": "^21.9.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-feather": "^2.0.10",
|
"react-feather": "^2.0.10",
|
||||||
|
"react-i18next": "^11.18.6",
|
||||||
"react-redux": "^8.0.1",
|
"react-redux": "^8.0.1",
|
||||||
"react-router-dom": "^6.4.0",
|
"react-router-dom": "^6.4.0",
|
||||||
"vite-plugin-pwa": "^0.12.8"
|
"vite-plugin-pwa": "^0.12.8"
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { RouterProvider } from "react-router-dom";
|
import { RouterProvider } from "react-router-dom";
|
||||||
import useI18n from "./hooks/useI18n";
|
|
||||||
import { globalService, locationService } from "./services";
|
import { globalService, locationService } from "./services";
|
||||||
import { useAppSelector } from "./store";
|
import { useAppSelector } from "./store";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import * as storage from "./helpers/storage";
|
import * as storage from "./helpers/storage";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { setLocale } = useI18n();
|
const { i18n } = useTranslation();
|
||||||
const global = useAppSelector((state) => state.global);
|
const global = useAppSelector((state) => state.global);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -20,7 +20,7 @@ function App() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocale(global.locale);
|
i18n.changeLanguage(global.locale);
|
||||||
storage.set({
|
storage.set({
|
||||||
locale: global.locale,
|
locale: global.locale,
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
@ -10,7 +10,7 @@ import "../less/about-site-dialog.less";
|
|||||||
type Props = DialogProps;
|
type Props = DialogProps;
|
||||||
|
|
||||||
const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
|
const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const [profile, setProfile] = useState<Profile>();
|
const [profile, setProfile] = useState<Profile>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import { memoService } from "../services";
|
import { memoService } from "../services";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
@ -18,7 +18,7 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
|||||||
createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
|
createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
|
||||||
archivedAtStr: utils.getDateTimeString(propsMemo.updatedTs ?? Date.now()),
|
archivedAtStr: utils.getDateTimeString(propsMemo.updatedTs ?? Date.now()),
|
||||||
};
|
};
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import { memoService } from "../services";
|
import { memoService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -12,7 +12,7 @@ import "../less/archived-memo-dialog.less";
|
|||||||
type Props = DialogProps;
|
type Props = DialogProps;
|
||||||
|
|
||||||
const ArchivedMemoDialog: React.FC<Props> = (props: Props) => {
|
const ArchivedMemoDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const { destroy } = props;
|
const { destroy } = props;
|
||||||
const memos = useAppSelector((state) => state.memo.memos);
|
const memos = useAppSelector((state) => state.memo.memos);
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import useI18n from "../hooks/useI18n";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { memoService } from "../services";
|
import { memoService } from "../services";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
@ -12,7 +12,7 @@ interface Props extends DialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ChangeMemoCreatedTsDialog: React.FC<Props> = (props: Props) => {
|
const ChangeMemoCreatedTsDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const { destroy, memoId } = props;
|
const { destroy, memoId } = props;
|
||||||
const [createdAt, setCreatedAt] = useState("");
|
const [createdAt, setCreatedAt] = useState("");
|
||||||
const maxDatetimeValue = dayjs().format("YYYY-MM-DDTHH:mm");
|
const maxDatetimeValue = dayjs().format("YYYY-MM-DDTHH:mm");
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import { userService } from "../services";
|
import { userService } from "../services";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
@ -17,7 +17,7 @@ const validateConfig: ValidatorConfig = {
|
|||||||
type Props = DialogProps;
|
type Props = DialogProps;
|
||||||
|
|
||||||
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const [newPassword, setNewPassword] = useState("");
|
const [newPassword, setNewPassword] = useState("");
|
||||||
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
const [newPasswordAgain, setNewPasswordAgain] = useState("");
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { memoService, shortcutService } from "../services";
|
import { memoService, shortcutService } from "../services";
|
||||||
import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter";
|
import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
@ -6,7 +7,6 @@ import Icon from "./Icon";
|
|||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import Selector from "./common/Selector";
|
import Selector from "./common/Selector";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import "../less/create-shortcut-dialog.less";
|
import "../less/create-shortcut-dialog.less";
|
||||||
|
|
||||||
interface Props extends DialogProps {
|
interface Props extends DialogProps {
|
||||||
@ -19,7 +19,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
|||||||
const [title, setTitle] = useState<string>("");
|
const [title, setTitle] = useState<string>("");
|
||||||
const [filters, setFilters] = useState<Filter[]>([]);
|
const [filters, setFilters] = useState<Filter[]>([]);
|
||||||
const requestState = useLoading(false);
|
const requestState = useLoading(false);
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const shownMemoLength = memoService.getState().memos.filter((memo) => {
|
const shownMemoLength = memoService.getState().memos.filter((memo) => {
|
||||||
return checkShouldShowMemoWithFilters(memo, filters);
|
return checkShouldShowMemoWithFilters(memo, filters);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import toImage from "../labs/html2image";
|
import toImage from "../labs/html2image";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
||||||
import * as utils from "../helpers/utils";
|
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 weekdayChineseStrArray = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
|
|
||||||
const DailyReviewDialog: React.FC<Props> = (props: Props) => {
|
const DailyReviewDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const memos = useAppSelector((state) => state.memo.memos);
|
const memos = useAppSelector((state) => state.memo.memos);
|
||||||
const [currentDateStamp, setCurrentDateStamp] = useState(utils.getDateStampByDate(utils.getDateString(props.currentDateStamp)));
|
const [currentDateStamp, setCurrentDateStamp] = useState(utils.getDateStampByDate(utils.getDateString(props.currentDateStamp)));
|
||||||
const [showDatePicker, toggleShowDatePicker] = useToggle(false);
|
const [showDatePicker, toggleShowDatePicker] = useToggle(false);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react";
|
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 useRefresh from "../../hooks/useRefresh";
|
||||||
import Only from "../common/OnlyWhen";
|
import Only from "../common/OnlyWhen";
|
||||||
import "../../less/editor.less";
|
import "../../less/editor.less";
|
||||||
@ -35,7 +35,7 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
|
|||||||
onConfirmBtnClick: handleConfirmBtnClickCallback,
|
onConfirmBtnClick: handleConfirmBtnClickCallback,
|
||||||
onContentChange: handleContentChangeCallback,
|
onContentChange: handleContentChangeCallback,
|
||||||
} = props;
|
} = props;
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const editorRef = useRef<HTMLTextAreaElement>(null);
|
const editorRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ import dayjs from "dayjs";
|
|||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
import { indexOf } from "lodash-es";
|
import { indexOf } from "lodash-es";
|
||||||
import { memo, useEffect, useRef, useState } from "react";
|
import { memo, useEffect, useRef, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import "dayjs/locale/zh";
|
import "dayjs/locale/zh";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import { UNKNOWN_ID } from "../helpers/consts";
|
import { UNKNOWN_ID } from "../helpers/consts";
|
||||||
import { DONE_BLOCK_REG, TODO_BLOCK_REG } from "../helpers/marked";
|
import { DONE_BLOCK_REG, TODO_BLOCK_REG } from "../helpers/marked";
|
||||||
import { editorStateService, locationService, memoService, userService } from "../services";
|
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: React.FC<Props> = (props: Props) => {
|
||||||
const memo = props.memo;
|
const memo = props.memo;
|
||||||
const { t, locale } = useI18n();
|
const { t, i18n } = useTranslation();
|
||||||
const [createdAtStr, setCreatedAtStr] = useState<string>(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
|
const [createdAtStr, setCreatedAtStr] = useState<string>(getFormatedMemoCreatedAtStr(memo.createdTs, i18n.language));
|
||||||
const memoContainerRef = useRef<HTMLDivElement>(null);
|
const memoContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const isVisitorMode = userService.isVisitorMode();
|
const isVisitorMode = userService.isVisitorMode();
|
||||||
@ -42,14 +42,14 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
let intervalFlag = -1;
|
let intervalFlag = -1;
|
||||||
if (Date.now() - memo.createdTs < 1000 * 60 * 60 * 24) {
|
if (Date.now() - memo.createdTs < 1000 * 60 * 60 * 24) {
|
||||||
intervalFlag = setInterval(() => {
|
intervalFlag = setInterval(() => {
|
||||||
setCreatedAtStr(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
|
setCreatedAtStr(getFormatedMemoCreatedAtStr(memo.createdTs, i18n.language));
|
||||||
}, 1000 * 1);
|
}, 1000 * 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(intervalFlag);
|
clearInterval(intervalFlag);
|
||||||
};
|
};
|
||||||
}, [locale]);
|
}, [i18n.language]);
|
||||||
|
|
||||||
const handleShowMemoStoryDialog = () => {
|
const handleShowMemoStoryDialog = () => {
|
||||||
showMemoCardDialog(memo);
|
showMemoCardDialog(memo);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { editorStateService, memoService, userService } from "../services";
|
import { editorStateService, memoService, userService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
import { UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts";
|
||||||
@ -13,7 +14,6 @@ import Selector from "./common/Selector";
|
|||||||
import MemoContent from "./MemoContent";
|
import MemoContent from "./MemoContent";
|
||||||
import MemoResources from "./MemoResources";
|
import MemoResources from "./MemoResources";
|
||||||
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
|
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import "../less/memo-card-dialog.less";
|
import "../less/memo-card-dialog.less";
|
||||||
|
|
||||||
interface LinkedMemo extends Memo {
|
interface LinkedMemo extends Memo {
|
||||||
@ -26,13 +26,13 @@ interface Props extends DialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MemoCardDialog: React.FC<Props> = (props: Props) => {
|
const MemoCardDialog: React.FC<Props> = (props: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const memos = useAppSelector((state) => state.memo.memos);
|
const memos = useAppSelector((state) => state.memo.memos);
|
||||||
const [memo, setMemo] = useState<Memo>({
|
const [memo, setMemo] = useState<Memo>({
|
||||||
...props.memo,
|
...props.memo,
|
||||||
});
|
});
|
||||||
const [linkMemos, setLinkMemos] = useState<LinkedMemo[]>([]);
|
const [linkMemos, setLinkMemos] = useState<LinkedMemo[]>([]);
|
||||||
const [linkedMemos, setLinkedMemos] = useState<LinkedMemo[]>([]);
|
const [linkedMemos, setLinkedMemos] = useState<LinkedMemo[]>([]);
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchLinkedMemos = async () => {
|
const fetchLinkedMemos = async () => {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
||||||
import { IEmojiData } from "emoji-picker-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 { UNKNOWN_ID } from "../helpers/consts";
|
||||||
import { editorStateService, locationService, memoService, resourceService } from "../services";
|
import { editorStateService, locationService, memoService, resourceService } from "../services";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import * as storage from "../helpers/storage";
|
import * as storage from "../helpers/storage";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -18,7 +18,7 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MemoEditor = () => {
|
const MemoEditor = () => {
|
||||||
const { t, locale } = useI18n();
|
const { t, i18n } = useTranslation();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const user = useAppSelector((state) => state.user.user);
|
||||||
const editorState = useAppSelector((state) => state.editor);
|
const editorState = useAppSelector((state) => state.editor);
|
||||||
const tags = useAppSelector((state) => state.memo.tags);
|
const tags = useAppSelector((state) => state.memo.tags);
|
||||||
@ -276,7 +276,7 @@ const MemoEditor = () => {
|
|||||||
onConfirmBtnClick: handleSaveBtnClick,
|
onConfirmBtnClick: handleSaveBtnClick,
|
||||||
onContentChange: handleContentChange,
|
onContentChange: handleContentChange,
|
||||||
}),
|
}),
|
||||||
[isEditing, state.fullscreen, locale, editorFontStyle]
|
[isEditing, state.fullscreen, i18n.language, editorFontStyle]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { locationService, shortcutService } from "../services";
|
import { locationService, shortcutService } from "../services";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { getTextWithMemoType } from "../helpers/filter";
|
import { getTextWithMemoType } from "../helpers/filter";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import "../less/memo-filter.less";
|
import "../less/memo-filter.less";
|
||||||
|
|
||||||
const MemoFilter = () => {
|
const MemoFilter = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const query = useAppSelector((state) => state.location.query);
|
const query = useAppSelector((state) => state.location.query);
|
||||||
useAppSelector((state) => state.shortcut.shortcuts);
|
|
||||||
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
|
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
|
||||||
const shortcut = shortcutId ? shortcutService.getShortcutById(shortcutId) : null;
|
const shortcut = shortcutId ? shortcutService.getShortcutById(shortcutId) : null;
|
||||||
const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut);
|
const showFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || shortcut);
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`filter-query-container ${showFilter ? "" : "!hidden"}`}>
|
<div className={`filter-query-container ${showFilter ? "" : "!hidden"}`}>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { memoService, shortcutService } from "../services";
|
import { memoService, shortcutService } from "../services";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { IMAGE_URL_REG, LINK_URL_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/marked";
|
import { IMAGE_URL_REG, LINK_URL_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/marked";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
@ -11,7 +11,7 @@ import Memo from "./Memo";
|
|||||||
import "../less/memo-list.less";
|
import "../less/memo-list.less";
|
||||||
|
|
||||||
const MemoList = () => {
|
const MemoList = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const query = useAppSelector((state) => state.location.query);
|
const query = useAppSelector((state) => state.location.query);
|
||||||
const { memos, isFetching } = useAppSelector((state) => state.memo);
|
const { memos, isFetching } = useAppSelector((state) => state.memo);
|
||||||
const wrapperElement = useRef<HTMLDivElement>(null);
|
const wrapperElement = useRef<HTMLDivElement>(null);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { userService } from "../services";
|
import { userService } from "../services";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
import showAboutSiteDialog from "./AboutSiteDialog";
|
import showAboutSiteDialog from "./AboutSiteDialog";
|
||||||
import showArchivedMemoDialog from "./ArchivedMemoDialog";
|
import showArchivedMemoDialog from "./ArchivedMemoDialog";
|
||||||
@ -15,7 +15,7 @@ interface Props {
|
|||||||
|
|
||||||
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
||||||
const { shownStatus, setShownStatus } = props;
|
const { shownStatus, setShownStatus } = props;
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const popupElRef = useRef<HTMLDivElement>(null);
|
const popupElRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import copy from "copy-to-clipboard";
|
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 useLoading from "../hooks/useLoading";
|
||||||
import { resourceService } from "../services";
|
import { resourceService } from "../services";
|
||||||
import Dropdown from "./common/Dropdown";
|
import Dropdown from "./common/Dropdown";
|
||||||
@ -20,7 +20,7 @@ interface State {
|
|||||||
|
|
||||||
const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { destroy } = props;
|
const { destroy } = props;
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
resources: [],
|
resources: [],
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { locationService } from "../services";
|
import { locationService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { memoSpecialTypes } from "../helpers/filter";
|
import { memoSpecialTypes } from "../helpers/filter";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import "../less/search-bar.less";
|
import "../less/search-bar.less";
|
||||||
|
|
||||||
const SearchBar = () => {
|
const SearchBar = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const memoType = useAppSelector((state) => state.location.query?.type);
|
const memoType = useAppSelector((state) => state.location.query?.type);
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const handleMemoTypeItemClick = (type: MemoSpecType | undefined) => {
|
const handleMemoTypeItemClick = (type: MemoSpecType | undefined) => {
|
||||||
const { type: prevType } = locationService.getState().query ?? {};
|
const { type: prevType } = locationService.getState().query ?? {};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import MyAccountSection from "./Settings/MyAccountSection";
|
import MyAccountSection from "./Settings/MyAccountSection";
|
||||||
@ -18,7 +18,7 @@ interface State {
|
|||||||
|
|
||||||
const SettingDialog: React.FC<Props> = (props: Props) => {
|
const SettingDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { destroy } = props;
|
const { destroy } = props;
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const user = useAppSelector((state) => state.user.user);
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
selectedSection: "my-account",
|
selectedSection: "my-account",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { isEmpty } from "lodash-es";
|
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 { userService } from "../../services";
|
||||||
import { useAppSelector } from "../../store";
|
import { useAppSelector } from "../../store";
|
||||||
import * as api from "../../helpers/api";
|
import * as api from "../../helpers/api";
|
||||||
@ -15,7 +15,7 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PreferencesSection = () => {
|
const PreferencesSection = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const currentUser = useAppSelector((state) => state.user.user);
|
const currentUser = useAppSelector((state) => state.user.user);
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
createUserEmail: "",
|
createUserEmail: "",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import useI18n from "../../hooks/useI18n";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../../store";
|
import { useAppSelector } from "../../store";
|
||||||
import { userService } from "../../services";
|
import { userService } from "../../services";
|
||||||
import { validate, ValidatorConfig } from "../../helpers/validator";
|
import { validate, ValidatorConfig } from "../../helpers/validator";
|
||||||
@ -16,7 +16,7 @@ const validateConfig: ValidatorConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MyAccountSection = () => {
|
const MyAccountSection = () => {
|
||||||
const { t, locale } = useI18n();
|
const { t, i18n } = useTranslation();
|
||||||
const user = useAppSelector((state) => state.user.user as User);
|
const user = useAppSelector((state) => state.user.user as User);
|
||||||
const [username, setUsername] = useState<string>(user.name);
|
const [username, setUsername] = useState<string>(user.name);
|
||||||
const openAPIRoute = `${window.location.origin}/api/memo?openId=${user.openId}`;
|
const openAPIRoute = `${window.location.origin}/api/memo?openId=${user.openId}`;
|
||||||
@ -33,7 +33,7 @@ const MyAccountSection = () => {
|
|||||||
|
|
||||||
const usernameValidResult = validate(username, validateConfig);
|
const usernameValidResult = validate(username, validateConfig);
|
||||||
if (!usernameValidResult.result) {
|
if (!usernameValidResult.result) {
|
||||||
toastHelper.error(t("common.username") + locale === "zh" ? "" : " " + usernameValidResult.reason);
|
toastHelper.error(t("common.username") + i18n.language === "zh" ? "" : " " + usernameValidResult.reason);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ const MyAccountSection = () => {
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
name: username,
|
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) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toastHelper.error(error.response.data.message);
|
toastHelper.error(error.response.data.message);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { globalService, userService } from "../../services";
|
import { globalService, userService } from "../../services";
|
||||||
import { useAppSelector } from "../../store";
|
import { useAppSelector } from "../../store";
|
||||||
import { VISIBILITY_SELECTOR_ITEMS } from "../../helpers/consts";
|
import { VISIBILITY_SELECTOR_ITEMS } from "../../helpers/consts";
|
||||||
import useI18n from "../../hooks/useI18n";
|
|
||||||
import Selector from "../common/Selector";
|
import Selector from "../common/Selector";
|
||||||
import "../../less/settings/preferences-section.less";
|
import "../../less/settings/preferences-section.less";
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ const editorFontStyleSelectorItems = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const PreferencesSection = () => {
|
const PreferencesSection = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const { setting } = useAppSelector((state) => state.user.user as User);
|
const { setting } = useAppSelector((state) => state.user.user as User);
|
||||||
|
|
||||||
const handleLocaleChanged = async (value: string) => {
|
const handleLocaleChanged = async (value: string) => {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { userService } from "../services";
|
import { userService } from "../services";
|
||||||
import toImage from "../labs/html2image";
|
import toImage from "../labs/html2image";
|
||||||
import { ANIMATION_DURATION } from "../helpers/consts";
|
import { ANIMATION_DURATION } from "../helpers/consts";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { IMAGE_URL_REG } from "../helpers/marked";
|
import { IMAGE_URL_REG } from "../helpers/marked";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
@ -18,7 +18,7 @@ interface Props extends DialogProps {
|
|||||||
|
|
||||||
const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { memo: propsMemo, destroy } = props;
|
const { memo: propsMemo, destroy } = props;
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const { user: userinfo } = userService.getState();
|
const { user: userinfo } = userService.getState();
|
||||||
const memo = {
|
const memo = {
|
||||||
...propsMemo,
|
...propsMemo,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { locationService, shortcutService } from "../services";
|
import { locationService, shortcutService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
@ -14,7 +14,7 @@ const ShortcutList = () => {
|
|||||||
const query = useAppSelector((state) => state.location.query);
|
const query = useAppSelector((state) => state.location.query);
|
||||||
const shortcuts = useAppSelector((state) => state.shortcut.shortcuts);
|
const shortcuts = useAppSelector((state) => state.shortcut.shortcuts);
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const pinnedShortcuts = shortcuts
|
const pinnedShortcuts = shortcuts
|
||||||
.filter((s) => s.rowStatus === "ARCHIVED")
|
.filter((s) => s.rowStatus === "ARCHIVED")
|
||||||
@ -59,7 +59,7 @@ interface ShortcutContainerProps {
|
|||||||
|
|
||||||
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
|
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
|
||||||
const { shortcut, isActive } = props;
|
const { shortcut, isActive } = props;
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||||
|
|
||||||
const handleShortcutClick = () => {
|
const handleShortcutClick = () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { userService } from "../services";
|
import { userService } from "../services";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
import showDailyReviewDialog from "./DailyReviewDialog";
|
import showDailyReviewDialog from "./DailyReviewDialog";
|
||||||
@ -12,7 +12,7 @@ import TagList from "./TagList";
|
|||||||
import "../less/siderbar.less";
|
import "../less/siderbar.less";
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleSettingBtnClick = () => {
|
const handleSettingBtnClick = () => {
|
||||||
showSettingDialog();
|
showSettingDialog();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { locationService, memoService, userService } from "../services";
|
import { locationService, memoService, userService } from "../services";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import Only from "./common/OnlyWhen";
|
import Only from "./common/OnlyWhen";
|
||||||
@ -14,7 +14,7 @@ interface Tag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TagList = () => {
|
const TagList = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const { memos, tags: tagsText } = useAppSelector((state) => state.memo);
|
const { memos, tags: tagsText } = useAppSelector((state) => state.memo);
|
||||||
const query = useAppSelector((state) => state.location.query);
|
const query = useAppSelector((state) => state.location.query);
|
||||||
const [tags, setTags] = useState<Tag[]>([]);
|
const [tags, setTags] = useState<Tag[]>([]);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import userService from "../services/userService";
|
import userService from "../services/userService";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import { locationService } from "../services";
|
import { locationService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -9,7 +9,7 @@ import MenuBtnsPopup from "./MenuBtnsPopup";
|
|||||||
import "../less/user-banner.less";
|
import "../less/user-banner.less";
|
||||||
|
|
||||||
const UserBanner = () => {
|
const UserBanner = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const { user, owner } = useAppSelector((state) => state.user);
|
const { user, owner } = useAppSelector((state) => state.user);
|
||||||
const { memos, tags } = useAppSelector((state) => state.memo);
|
const { memos, tags } = useAppSelector((state) => state.memo);
|
||||||
const [shouldShowPopupBtns, setShouldShowPopupBtns] = useState(false);
|
const [shouldShowPopupBtns, setShouldShowPopupBtns] = useState(false);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { memo, useEffect, useRef } from "react";
|
import { memo, useEffect, useRef } from "react";
|
||||||
import useI18n from "../../hooks/useI18n";
|
import { useTranslation } from "react-i18next";
|
||||||
import useToggle from "../../hooks/useToggle";
|
import useToggle from "../../hooks/useToggle";
|
||||||
import Icon from "../Icon";
|
import Icon from "../Icon";
|
||||||
import "../../less/common/selector.less";
|
import "../../less/common/selector.less";
|
||||||
@ -23,7 +23,7 @@ const nullItem = {
|
|||||||
|
|
||||||
const Selector: React.FC<Props> = (props: Props) => {
|
const Selector: React.FC<Props> = (props: Props) => {
|
||||||
const { className, dataSource, handleValueChanged, value } = props;
|
const { className, dataSource, handleValueChanged, value } = props;
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const [showSelector, toggleSelectorStatus] = useToggle(false);
|
const [showSelector, toggleSelectorStatus] = useToggle(false);
|
||||||
|
|
||||||
const seletorElRef = useRef<HTMLDivElement>(null);
|
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 { createRoot } from "react-dom/client";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import I18nProvider from "./labs/i18n/I18nProvider";
|
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
import "./i18n";
|
||||||
import "./helpers/polyfill";
|
import "./helpers/polyfill";
|
||||||
import "./less/global.less";
|
import "./less/global.less";
|
||||||
import "./css/index.css";
|
import "./css/index.css";
|
||||||
@ -10,9 +10,7 @@ import "./css/index.css";
|
|||||||
const container = document.getElementById("root");
|
const container = document.getElementById("root");
|
||||||
const root = createRoot(container as HTMLElement);
|
const root = createRoot(container as HTMLElement);
|
||||||
root.render(
|
root.render(
|
||||||
<I18nProvider>
|
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App />
|
<App />
|
||||||
</Provider>
|
</Provider>
|
||||||
</I18nProvider>
|
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import { globalService, userService } from "../services";
|
import { globalService, userService } from "../services";
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
@ -18,7 +18,7 @@ const validateConfig: ValidatorConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Auth = () => {
|
const Auth = () => {
|
||||||
const { t, locale } = useI18n();
|
const { t, i18n } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const pageLoadingState = useLoading(true);
|
const pageLoadingState = useLoading(true);
|
||||||
const [siteHost, setSiteHost] = useState<User>();
|
const [siteHost, setSiteHost] = useState<User>();
|
||||||
@ -157,15 +157,15 @@ const Auth = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="footer-container">
|
<div className="footer-container">
|
||||||
<div className="language-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
|
English
|
||||||
</span>
|
</span>
|
||||||
<span className="split-line">/</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>
|
||||||
<span className="split-line">/</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
|
Tiếng Việt
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { memoService, userService } from "../services";
|
import { memoService, userService } from "../services";
|
||||||
import { isNullorUndefined } from "../helpers/utils";
|
import { isNullorUndefined } from "../helpers/utils";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import useQuery from "../hooks/useQuery";
|
import useQuery from "../hooks/useQuery";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import Only from "../components/common/OnlyWhen";
|
import Only from "../components/common/OnlyWhen";
|
||||||
@ -17,7 +17,7 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Explore = () => {
|
const Explore = () => {
|
||||||
const { t, locale } = useI18n();
|
const { t, i18n } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const user = useAppSelector((state) => state.user.user);
|
||||||
@ -78,7 +78,7 @@ const Explore = () => {
|
|||||||
<main className="memos-wrapper">
|
<main className="memos-wrapper">
|
||||||
{state.memos.length > 0 ? (
|
{state.memos.length > 0 ? (
|
||||||
state.memos.map((memo) => {
|
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 (
|
return (
|
||||||
<div className="memo-container" key={memo.id}>
|
<div className="memo-container" key={memo.id}>
|
||||||
<div className="memo-header">
|
<div className="memo-header">
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { globalService, userService } from "../services";
|
import { globalService, userService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import useI18n from "../hooks/useI18n";
|
|
||||||
import { isNullorUndefined } from "../helpers/utils";
|
import { isNullorUndefined } from "../helpers/utils";
|
||||||
import Only from "../components/common/OnlyWhen";
|
import Only from "../components/common/OnlyWhen";
|
||||||
import toastHelper from "../components/Toast";
|
import toastHelper from "../components/Toast";
|
||||||
@ -14,7 +14,7 @@ import MemoList from "../components/MemoList";
|
|||||||
import "../less/home.less";
|
import "../less/home.less";
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const { t } = useI18n();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const user = useAppSelector((state) => state.user.user);
|
const user = useAppSelector((state) => state.user.user);
|
||||||
|
@ -1001,7 +1001,7 @@
|
|||||||
"@babel/types" "^7.4.4"
|
"@babel/types" "^7.4.4"
|
||||||
esutils "^2.0.2"
|
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"
|
version "7.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259"
|
||||||
integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
|
integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
|
||||||
@ -2543,6 +2543,20 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react-is "^16.7.0"
|
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:
|
iconv-lite@^0.6.3:
|
||||||
version "0.6.3"
|
version "0.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||||
@ -3307,6 +3321,14 @@ react-feather@^2.0.10:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prop-types "^15.7.2"
|
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:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
@ -3936,6 +3958,11 @@ vite@^3.0.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
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:
|
webidl-conversions@^4.0.2:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||||
|
Reference in New Issue
Block a user