chore: update i18n

pull/161/head
Steven 3 years ago
parent e3fac742c5
commit a7a01df79a

@ -45,15 +45,13 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
</button>
</div>
<div className="dialog-content-container">
<p>
Memos is an <i>open source</i>, <i>self-hosted</i> knowledge base that works with a SQLite db file.
</p>
<p>{t("slogan")}</p>
<br />
<div className="addtion-info-container">
<GitHubBadge />
<Only when={profile !== undefined}>
<>
version:
{t("common.version")}:
<span className="pre-text">
{profile?.version}-{profile?.mode}
</span>

@ -1,5 +1,6 @@
import { IMAGE_URL_REG } from "../helpers/consts";
import * as utils from "../helpers/utils";
import useI18n from "../hooks/useI18n";
import useToggle from "../hooks/useToggle";
import { memoService } from "../services";
import { formatMemoContent } from "../helpers/marked";
@ -19,6 +20,7 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
createdAtStr: utils.getDateTimeString(propsMemo.createdTs),
archivedAtStr: utils.getDateTimeString(propsMemo.updatedTs ?? Date.now()),
};
const { t } = useI18n();
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
@ -60,10 +62,11 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
<span className="time-text">Archived at {memo.archivedAtStr}</span>
<div className="btns-container">
<span className="btn restore-btn" onClick={handleRestoreMemoClick}>
Restore
{t("common.restore")}
</span>
<span className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`} onClick={handleDeleteMemoClick}>
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
{t("common.delete")}
{showConfirmDeleteBtn ? "!" : ""}
</span>
</div>
</div>

@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { validate, ValidatorConfig } from "../helpers/validator";
import useI18n from "../hooks/useI18n";
import { userService } from "../services";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
@ -16,6 +17,7 @@ const validateConfig: ValidatorConfig = {
interface Props extends DialogProps {}
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
const { t } = useI18n();
const [newPassword, setNewPassword] = useState("");
const [newPasswordAgain, setNewPasswordAgain] = useState("");
@ -85,10 +87,10 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
</label>
<div className="btns-container">
<span className="btn cancel-btn" onClick={handleCloseBtnClick}>
Cancel
{t("common.cancel")}
</span>
<span className="btn confirm-btn" onClick={handleSaveBtnClick}>
Save
{t("common.save")}
</span>
</div>
</div>

@ -1,4 +1,5 @@
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react";
import useI18n from "../../hooks/useI18n";
import useRefresh from "../../hooks/useRefresh";
import Only from "../common/OnlyWhen";
import "../../less/editor.less";
@ -37,6 +38,7 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
onCancelBtnClick: handleCancelBtnClickCallback,
onContentChange: handleContentChangeCallback,
} = props;
const { t } = useI18n();
const editorRef = useRef<HTMLTextAreaElement>(null);
const refresh = useRefresh();
@ -130,12 +132,12 @@ const Editor = forwardRef((props: EditorProps, ref: React.ForwardedRef<EditorRef
<div className="btns-container">
<Only when={showCancelBtn}>
<button className="action-btn cancel-btn" onClick={handleCommonCancelBtnClick}>
Cancel editting
{t("editor.cancel-edit")}
</button>
</Only>
<Only when={showConfirmBtn}>
<button className="action-btn confirm-btn" disabled={editorRef.current?.value === ""} onClick={handleCommonConfirmBtnClick}>
Save <span className="icon-text"></span>
{t("editor.save")} <span className="icon-text"></span>
</button>
</Only>
</div>

@ -2,6 +2,8 @@ import { memo, useEffect, useRef, useState } from "react";
import { indexOf } from "lodash-es";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import "dayjs/locale/zh";
import useI18n from "../hooks/useI18n";
import { IMAGE_URL_REG, UNKNOWN_ID } from "../helpers/consts";
import { DONE_BLOCK_REG, formatMemoContent, TODO_BLOCK_REG } from "../helpers/marked";
import { editorStateService, locationService, memoService, userService } from "../services";
@ -27,20 +29,21 @@ interface State {
expandButtonStatus: ExpandButtonStatus;
}
export const getFormatedMemoCreatedAtStr = (createdTs: number): string => {
export const getFormatedMemoCreatedAtStr = (createdTs: number, locale = "en"): string => {
if (Date.now() - createdTs < 1000 * 60 * 60 * 24) {
return dayjs(createdTs).fromNow();
return dayjs(createdTs).locale(locale).fromNow();
} else {
return dayjs(createdTs).format("YYYY/MM/DD HH:mm:ss");
return dayjs(createdTs).locale(locale).format("YYYY/MM/DD HH:mm:ss");
}
};
const Memo: React.FC<Props> = (props: Props) => {
const memo = props.memo;
const { t, locale } = useI18n();
const [state, setState] = useState<State>({
expandButtonStatus: -1,
});
const [createdAtStr, setCreatedAtStr] = useState<string>(getFormatedMemoCreatedAtStr(memo.createdTs));
const [createdAtStr, setCreatedAtStr] = useState<string>(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
const memoContainerRef = useRef<HTMLDivElement>(null);
const imageUrls = Array.from(memo.content.match(IMAGE_URL_REG) ?? []).map((s) => s.replace(IMAGE_URL_REG, "$1"));
const isVisitorMode = userService.isVisitorMode();
@ -59,10 +62,10 @@ const Memo: React.FC<Props> = (props: Props) => {
if (Date.now() - memo.createdTs < 1000 * 60 * 60 * 24) {
setInterval(() => {
setCreatedAtStr(dayjs(memo.createdTs).fromNow());
setCreatedAtStr(getFormatedMemoCreatedAtStr(memo.createdTs, locale));
}, 1000 * 1);
}
}, []);
}, [locale]);
const handleShowMemoStoryDialog = () => {
showMemoCardDialog(memo);
@ -186,25 +189,25 @@ const Memo: React.FC<Props> = (props: Props) => {
<div className="btns-container">
<div className="btn" onClick={handleTogglePinMemoBtnClick}>
<Icon.MapPin className={`icon-img ${memo.pinned ? "" : "opacity-20"}`} />
<span className="tip-text">{memo.pinned ? "Unpin" : "Pin"}</span>
<span className="tip-text">{memo.pinned ? t("common.unpin") : t("common.pin")}</span>
</div>
<div className="btn" onClick={handleEditMemoClick}>
<Icon.Edit3 className="icon-img" />
<span className="tip-text">Edit</span>
<span className="tip-text">{t("common.edit")}</span>
</div>
<div className="btn" onClick={handleGenMemoImageBtnClick}>
<Icon.Share className="icon-img" />
<span className="tip-text">Share</span>
<span className="tip-text">{t("common.share")}</span>
</div>
</div>
<span className="btn" onClick={handleMarkMemoClick}>
Mark
{t("common.mark")}
</span>
<span className="btn" onClick={handleShowMemoStoryDialog}>
View Story
</span>
<span className="btn archive-btn" onClick={handleArchiveMemoClick}>
Archive
{t("common.archive")}
</span>
</div>
</div>

@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
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";
@ -16,6 +17,7 @@ interface State {
}
const MemoEditor: React.FC<Props> = () => {
const { t, locale } = useI18n();
const editorState = useAppSelector((state) => state.editor);
const tags = useAppSelector((state) => state.memo.tags);
const [state, setState] = useState<State>({
@ -212,7 +214,7 @@ const MemoEditor: React.FC<Props> = () => {
() => ({
className: "memo-editor",
initialContent: getEditorContentCache(),
placeholder: "Any thoughts...",
placeholder: t("editor.placeholder"),
fullscreen: state.fullscreen,
showConfirmBtn: true,
showCancelBtn: isEditing,
@ -220,7 +222,7 @@ const MemoEditor: React.FC<Props> = () => {
onCancelBtnClick: handleCancelBtnClick,
onContentChange: handleContentChange,
}),
[isEditing, state.fullscreen]
[isEditing, state.fullscreen, locale]
);
return (

@ -1,6 +1,7 @@
import { useEffect, useRef } from "react";
import * as api from "../helpers/api";
import { locationService, userService } from "../services";
import useI18n from "../hooks/useI18n";
import toastHelper from "./Toast";
import Only from "./common/OnlyWhen";
import showAboutSiteDialog from "./AboutSiteDialog";
@ -13,6 +14,7 @@ interface Props {
const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
const { shownStatus, setShownStatus } = props;
const { t } = useI18n();
const popupElRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@ -63,14 +65,14 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
return (
<div className={`menu-btns-popup ${shownStatus ? "" : "hidden"}`} ref={popupElRef}>
<button className="btn action-btn" onClick={handleAboutBtnClick}>
<span className="icon">🤠</span> About
<span className="icon">🤠</span> {t("common.about")}
</button>
<button className="btn action-btn" onClick={handlePingBtnClick}>
<span className="icon">🎯</span> Ping
</button>
<Only when={!userService.isVisitorMode()}>
<button className="btn action-btn" onClick={handleSignOutBtnClick}>
<span className="icon">👋</span> Sign out
<span className="icon">👋</span> {t("common.sign-out")}
</button>
</Only>
</div>

@ -1,11 +1,12 @@
import { useState } from "react";
import { useAppSelector } from "../store";
import useI18n from "../hooks/useI18n";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
import MyAccountSection from "./Settings/MyAccountSection";
import PreferencesSection from "./Settings/PreferencesSection";
import MemberSection from "./Settings/MemberSection";
import "../less/setting-dialog.less";
import Icon from "./Icon";
interface Props extends DialogProps {}
@ -17,6 +18,7 @@ interface State {
const SettingDialog: React.FC<Props> = (props: Props) => {
const { destroy } = props;
const { t } = useI18n();
const user = useAppSelector((state) => state.user.user);
const [state, setState] = useState<State>({
selectedSection: "my-account",
@ -34,30 +36,30 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
<Icon.X className="icon-img" />
</button>
<div className="section-selector-container">
<span className="section-title">Basic</span>
<span className="section-title">{t("common.basic")}</span>
<div className="section-items-container">
<span
onClick={() => handleSectionSelectorItemClick("my-account")}
className={`section-item ${state.selectedSection === "my-account" ? "selected" : ""}`}
>
<span className="icon-text">🤠</span> My account
<span className="icon-text">🤠</span> {t("setting.my-account")}
</span>
<span
onClick={() => handleSectionSelectorItemClick("preferences")}
className={`section-item ${state.selectedSection === "preferences" ? "selected" : ""}`}
>
<span className="icon-text">🏟</span> Preferences
<span className="icon-text">🏟</span> {t("setting.preference")}
</span>
</div>
{user?.role === "HOST" ? (
<>
<span className="section-title">Admin</span>
<span className="section-title">{t("common.admin")}</span>
<div className="section-items-container">
<span
onClick={() => handleSectionSelectorItemClick("member")}
className={`section-item ${state.selectedSection === "member" ? "selected" : ""}`}
>
<span className="icon-text">👤</span> Member
<span className="icon-text">👤</span> {t("setting.member")}
</span>
</div>
</>

@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react";
import { isEmpty } from "lodash-es";
import useI18n from "../../hooks/useI18n";
import { userService } from "../../services";
import { useAppSelector } from "../../store";
import * as api from "../../helpers/api";
@ -16,6 +17,7 @@ interface State {
}
const PreferencesSection: React.FC<Props> = () => {
const { t } = useI18n();
const currentUser = useAppSelector((state) => state.user.user);
const [state, setState] = useState<State>({
createUserEmail: "",
@ -110,18 +112,18 @@ const PreferencesSection: React.FC<Props> = () => {
return (
<div className="section-container member-section-container">
<p className="title-text">Create a member</p>
<p className="title-text">{t("setting.member-section.create-a-member")}</p>
<div className="create-member-container">
<div className="input-form-container">
<span className="field-text">Email</span>
<input type="email" placeholder="Email" value={state.createUserEmail} onChange={handleEmailInputChange} />
<span className="field-text">{t("common.email")}</span>
<input type="email" placeholder={t("common.email")} value={state.createUserEmail} onChange={handleEmailInputChange} />
</div>
<div className="input-form-container">
<span className="field-text">Password</span>
<input type="text" placeholder="Password" value={state.createUserPassword} onChange={handlePasswordInputChange} />
<span className="field-text">{t("common.password")}</span>
<input type="text" placeholder={t("common.password")} value={state.createUserPassword} onChange={handlePasswordInputChange} />
</div>
<div className="btns-container">
<button onClick={handleCreateUserBtnClick}>Create</button>
<button onClick={handleCreateUserBtnClick}>{t("common.create")}</button>
</div>
</div>
<p className="title-text">Member list</p>
@ -140,12 +142,12 @@ const PreferencesSection: React.FC<Props> = () => {
) : (
<Dropdown className="actions-dropdown">
{user.rowStatus === "NORMAL" ? (
<button onClick={() => handleArchiveUserClick(user)}>Archive</button>
<button onClick={() => handleArchiveUserClick(user)}>{t("common.archive")}</button>
) : (
<>
<button onClick={() => handleRestoreUserClick(user)}>Restore</button>
<button onClick={() => handleRestoreUserClick(user)}>{t("common.restore")}</button>
<button className="delete" onClick={() => handleDeleteUserClick(user)}>
Delete
{t("common.delete")}
</button>
</>
)}

@ -1,4 +1,5 @@
import { useState } from "react";
import useI18n from "../../hooks/useI18n";
import { useAppSelector } from "../../store";
import { userService } from "../../services";
import { validate, ValidatorConfig } from "../../helpers/validator";
@ -17,6 +18,7 @@ const validateConfig: ValidatorConfig = {
interface Props {}
const MyAccountSection: React.FC<Props> = () => {
const { t } = useI18n();
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}`;
@ -68,17 +70,17 @@ const MyAccountSection: React.FC<Props> = () => {
return (
<>
<div className="section-container account-section-container">
<p className="title-text">Account Information</p>
<p className="title-text">{t("setting.account-section.title")}</p>
<label className="form-label">
<span className="normal-text">Email:</span>
<span className="normal-text">{t("common.email")}:</span>
<span className="normal-text">{user.email}</span>
</label>
<label className="form-label input-form-label username-label">
<span className="normal-text">Username:</span>
<span className="normal-text">{t("common.username")}:</span>
<input type="text" value={username} onChange={handleUsernameChanged} />
<div className={`btns-container ${username === user.name ? "!hidden" : ""}`} onClick={handlePreventDefault}>
<span className="btn confirm-btn" onClick={handleConfirmEditUsernameBtnClick}>
Save
{t("common.save")}
</span>
<span
className="btn cancel-btn"
@ -86,14 +88,14 @@ const MyAccountSection: React.FC<Props> = () => {
setUsername(user.name);
}}
>
Cancel
{t("common.cancel")}
</span>
</div>
</label>
<label className="form-label password-label">
<span className="normal-text">Password:</span>
<span className="normal-text">{t("common.password")}:</span>
<span className="btn" onClick={handleChangePasswordBtnClick}>
Change it
{t("common.change")}
</span>
</label>
</div>
@ -101,10 +103,9 @@ const MyAccountSection: React.FC<Props> = () => {
<p className="title-text">Open API</p>
<p className="value-text">{openAPIRoute}</p>
<span className="reset-btn" onClick={handleResetOpenIdBtnClick}>
Reset API
{t("common.reset")} API
</span>
<div className="usage-guide-container">
<p className="title-text">Usage guide:</p>
<pre>{`POST ${openAPIRoute}\nContent-type: application/json\n{\n "content": "Hello #memos from ${window.location.origin}"\n}`}</pre>
</div>
</div>

@ -1,8 +1,6 @@
import { globalService, memoService, userService } from "../../services";
import * as utils from "../../helpers/utils";
import { globalService, userService } from "../../services";
import { useAppSelector } from "../../store";
import Only from "../common/OnlyWhen";
import toastHelper from "../Toast";
import useI18n from "../../hooks/useI18n";
import Selector from "../common/Selector";
import "../../less/settings/preferences-section.less";
@ -20,66 +18,9 @@ const localeSelectorItems = [
];
const PreferencesSection: React.FC<Props> = () => {
const { t } = useI18n();
const { setting } = useAppSelector((state) => state.user.user as User);
const handleExportBtnClick = async () => {
const formatedMemos = memoService.getState().memos.map((m) => {
return {
content: m.content,
createdTs: m.createdTs,
};
});
const jsonStr = JSON.stringify(formatedMemos);
const element = document.createElement("a");
element.setAttribute("href", "data:text/json;charset=utf-8," + encodeURIComponent(jsonStr));
element.setAttribute("download", `memos-${utils.getDateTimeString(Date.now())}.json`);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
};
const handleImportBtnClick = async () => {
const fileInputEl = document.createElement("input");
fileInputEl.type = "file";
fileInputEl.accept = "application/JSON";
fileInputEl.onchange = () => {
if (fileInputEl.files?.length && fileInputEl.files.length > 0) {
const reader = new FileReader();
reader.readAsText(fileInputEl.files[0]);
reader.onload = async (event) => {
const memoList = JSON.parse(event.target?.result as string) as Memo[];
if (!Array.isArray(memoList)) {
toastHelper.error("Unexpected data type.");
}
let succeedAmount = 0;
for (const memo of memoList) {
const content = memo.content || "";
const createdTs = (memo as any).createdAt || memo.createdTs || Date.now();
try {
const memoCreate = {
content,
createdTs: Math.floor(utils.getTimeStampByDate(createdTs) / 1000),
};
await memoService.createMemo(memoCreate);
succeedAmount++;
} catch (error) {
// do nth
}
}
await memoService.fetchAllMemos();
toastHelper.success(`${succeedAmount} memos successfully imported.`);
};
}
};
fileInputEl.click();
};
const handleLocaleChanged = async (value: string) => {
globalService.setLocale(value as Locale);
await userService.upsertUserSetting("locale", value);
@ -87,22 +28,10 @@ const PreferencesSection: React.FC<Props> = () => {
return (
<div className="section-container preferences-section-container">
{/* Hide export/import buttons */}
<label className="form-label">
<span className="normal-text">Language:</span>
<span className="normal-text">{t("common.language")}:</span>
<Selector className="ml-2 w-28" value={setting.locale} dataSource={localeSelectorItems} handleValueChanged={handleLocaleChanged} />
</label>
<Only when={false}>
<p className="title-text">Others</p>
<div className="btns-container">
<button className="btn" onClick={handleExportBtnClick}>
Export data as JSON
</button>
<button className="btn" onClick={handleImportBtnClick}>
Import from JSON
</button>
</div>
</Only>
</div>
);
};

@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { userService } from "../services";
import toImage from "../labs/html2image";
import { ANIMATION_DURATION, IMAGE_URL_REG } from "../helpers/consts";
import useI18n from "../hooks/useI18n";
import * as utils from "../helpers/utils";
import { formatMemoContent } from "../helpers/marked";
import Only from "./common/OnlyWhen";
@ -16,6 +17,7 @@ interface Props extends DialogProps {
const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
const { memo: propsMemo, destroy } = props;
const { t } = useI18n();
const { user: userinfo } = userService.getState();
const memo = {
...propsMemo,
@ -73,7 +75,8 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
<>
<div className="dialog-header-container">
<p className="title-text">
<span className="icon-text">🌄</span>Share Memo
<span className="icon-text">🌄</span>
{t("common.share")} Memo
</p>
<button className="btn close-btn" onClick={handleCloseBtnClick}>
<Icon.X className="icon-img" />

@ -1,6 +1,7 @@
import { useEffect } from "react";
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";
@ -59,6 +60,7 @@ interface ShortcutContainerProps {
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
const { shortcut, isActive } = props;
const { t } = useI18n();
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
const handleShortcutClick = () => {
@ -119,17 +121,18 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
<div className="action-btns-wrapper">
<div className="action-btns-container">
<span className="btn" onClick={handlePinShortcutBtnClick}>
{shortcut.rowStatus === "ARCHIVED" ? "Unpin" : "Pin"}
{shortcut.rowStatus === "ARCHIVED" ? t("common.unpin") : t("common.pin")}
</span>
<span className="btn" onClick={handleEditShortcutBtnClick}>
Edit
{t("common.edit")}
</span>
<span
className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`}
onClick={handleDeleteMemoClick}
onMouseLeave={handleDeleteBtnMouseLeave}
>
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
{t("common.delete")}
{showConfirmDeleteBtn ? "!" : ""}
</span>
</div>
</div>

@ -1,4 +1,5 @@
import { userService } from "../services";
import useI18n from "../hooks/useI18n";
import Icon from "./Icon";
import Only from "./common/OnlyWhen";
import showDailyReviewDialog from "./DailyReviewDialog";
@ -14,14 +15,14 @@ import "../less/siderbar.less";
interface Props {}
const Sidebar: React.FC<Props> = () => {
const { t } = useI18n();
const handleMyAccountBtnClick = () => {
showSettingDialog();
};
const handleResourcesBtnClick = () => {
showResourcesDialog();
};
const handleArchivedBtnClick = () => {
showArchivedMemoDialog();
};
@ -37,17 +38,17 @@ const Sidebar: React.FC<Props> = () => {
<UsageHeatMap />
<div className="action-btns-container">
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
<span className="icon">📅</span> Daily Review
<span className="icon">📅</span> {t("sidebar.daily-review")}
</button>
<Only when={!userService.isVisitorMode()}>
<button className="btn action-btn" onClick={handleResourcesBtnClick}>
<span className="icon">🌄</span> Resources
<span className="icon">🌄</span> {t("sidebar.resources")}
</button>
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
<span className="icon"></span> Setting
<span className="icon"></span> {t("sidebar.setting")}
</button>
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
<span className="icon">🗂</span> Archived
<span className="icon">🗂</span> {t("sidebar.archived")}
</button>
</Only>
</div>

@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import * as utils from "../helpers/utils";
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";
@ -16,6 +17,7 @@ interface Tag {
interface Props {}
const TagList: React.FC<Props> = () => {
const { t } = useI18n();
const { memos, tags: tagsText } = useAppSelector((state) => state.memo);
const query = useAppSelector((state) => state.location.query);
const [tags, setTags] = useState<Tag[]>([]);
@ -75,9 +77,7 @@ const TagList: React.FC<Props> = () => {
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
))}
<Only when={!userService.isVisitorMode() && tags.length < 5}>
<p className="tag-tip-container">
Enter <span className="code-text">#tag </span> to create a tag
</p>
<p className="tip-text">{t("tag-list.tip-text")}</p>
</Only>
</div>
</div>

@ -18,10 +18,10 @@
}
> .addtion-info-container {
@apply flex flex-row justify-start items-center;
@apply flex flex-row text-sm justify-start items-center;
> .github-badge-container {
@apply mr-2;
@apply mr-4;
}
}
}

@ -61,12 +61,8 @@
}
}
> .tag-tip-container {
@apply w-full mt-2 pl-4 text-sm text-gray-400;
> .code-text {
@apply p-1 mx-1 text-blue-600 font-mono whitespace-pre-line bg-blue-100 rounded;
}
> .tip-text {
@apply w-full mt-2 pl-4 text-sm text-gray-400 font-mono;
}
}
}

@ -3,12 +3,56 @@
"about": "About",
"email": "Email",
"password": "Password",
"sign-in": "Sign in"
"username": "Username",
"save": "Save",
"cancel": "Cancel",
"create": "Create",
"change": "Change",
"reset": "Reset",
"language": "Language",
"version": "Version",
"pin": "Pin",
"unpin": "Unpin",
"edit": "Edit",
"delete": "Delete",
"share": "Share",
"mark": "Mark",
"archive": "Archive",
"basic": "Basic",
"admin": "Admin",
"sign-in": "Sign in",
"sign-out": "Sign out",
"back-to-home": "Back to Home"
},
"slogan": "An open source, self-hosted knowledge base that works with a SQLite db file.",
"auth": {
"signup-as-host": "Sign up as Host",
"host-tip": "You are registering as the Site Host.",
"not-host-tip": "If you don't have an account, please contact the site host."
},
"sidebar": {
"daily-review": "Daily Review",
"resources": "Resources",
"setting": "Setting",
"archived": "Archived"
},
"editor": {
"save": "Save",
"cancel-edit": "Cancel edit",
"placeholder": "Any thoughts..."
},
"tag-list": {
"tip-text": "Enter `#tag ` to create a tag"
},
"setting": {
"my-account": "My Account",
"preference": "Preference",
"member": "Member",
"account-section": {
"title": "Account Information"
},
"member-section": {
"create-a-member": "Create a member"
}
}
}

@ -3,12 +3,57 @@
"about": "关于",
"email": "邮箱",
"password": "密码",
"sign-in": "登录"
"username": "用户名",
"save": "保存",
"cancel": "退出",
"create": "创建",
"change": "修改",
"reset": "重置",
"restore": "恢复",
"language": "语言",
"version": "版本",
"pin": "置顶",
"unpin": "取消置顶",
"edit": "编辑",
"delete": "删除",
"share": "分享",
"mark": "Mark",
"archive": "归档",
"basic": "基础",
"admin": "管理员",
"sign-in": "登录",
"sign-out": "退出登录",
"back-to-home": "回到主页"
},
"slogan": "一个开源的、支持私有化部署的碎片化知识卡片管理工具。",
"auth": {
"signup-as-host": "注册为 Host",
"host-tip": "你正在注册为 Host 用户账号。",
"not-host-tip": "如果你没有账号,请联系站点 Host"
},
"sidebar": {
"daily-review": "每日回顾",
"resources": "资源",
"setting": "设置",
"archived": "已归档"
},
"editor": {
"save": "记下",
"cancel-edit": "退出编辑",
"placeholder": "现在的想法是..."
},
"tag-list": {
"tip-text": "输入`#tag `来创建标签"
},
"setting": {
"my-account": "我的账号",
"preference": "偏好设置",
"member": "成员",
"account-section": {
"title": "账号信息"
},
"member-section": {
"create-a-member": "创建成员"
}
}
}

@ -11,7 +11,7 @@ import "../less/auth.less";
interface Props {}
const validateConfig: ValidatorConfig = {
minLength: 4,
minLength: 6,
maxLength: 24,
noSpace: true,
noChinese: true,

@ -1,6 +1,7 @@
import { useEffect } from "react";
import { locationService, userService } from "../services";
import { useAppSelector } from "../store";
import useI18n from "../hooks/useI18n";
import useLoading from "../hooks/useLoading";
import Only from "../components/common/OnlyWhen";
import Sidebar from "../components/Sidebar";
@ -12,6 +13,7 @@ import toastHelper from "../components/Toast";
import "../less/home.less";
function Home() {
const { t } = useI18n();
const user = useAppSelector((state) => state.user.user);
const location = useAppSelector((state) => state.location);
const loadingState = useLoading();
@ -58,11 +60,11 @@ function Home() {
<div className="addtion-btn-container">
{user ? (
<button className="btn" onClick={() => (window.location.href = "/")}>
<span className="icon">🏠</span> Back to Home
<span className="icon">🏠</span> {t("common.back-to-home")}
</button>
) : (
<button className="btn" onClick={() => (window.location.href = "/auth")}>
<span className="icon">👉</span> Sign in
<span className="icon">👉</span> {t("common.sign-in")}
</button>
)}
</div>

Loading…
Cancel
Save