feat: update RU i18n locale (#1422)

* feat: Fix i18n and RU locale

* fix: eslint issues

* change the position of deps

---------

Co-authored-by: CorrectRoadH <a778917369@gmail.com>
This commit is contained in:
Dmitry Shemin 2023-04-01 14:35:25 +07:00 committed by GitHub
parent d21abfc60c
commit b03778fa73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 169 additions and 59 deletions

View File

@ -1,3 +1,4 @@
import dayjs from "dayjs";
import { useColorScheme } from "@mui/joy"; import { useColorScheme } from "@mui/joy";
import { useEffect, Suspense } from "react"; import { useEffect, Suspense } from "react";
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
@ -58,6 +59,7 @@ const App = () => {
useEffect(() => { useEffect(() => {
document.documentElement.setAttribute("lang", locale); document.documentElement.setAttribute("lang", locale);
i18n.changeLanguage(locale); i18n.changeLanguage(locale);
dayjs.locale(locale);
storage.set({ storage.set({
locale: locale, locale: locale,
}); });

View File

@ -8,10 +8,12 @@ import { useMessageStore } from "../store/zustand/message";
import Icon from "./Icon"; import Icon from "./Icon";
import { generateDialog } from "./Dialog"; import { generateDialog } from "./Dialog";
import showSettingDialog from "./SettingDialog"; import showSettingDialog from "./SettingDialog";
import { useTranslation } from "react-i18next";
type Props = DialogProps; type Props = DialogProps;
const AskAIDialog: React.FC<Props> = (props: Props) => { const AskAIDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const { destroy, hide } = props; const { destroy, hide } = props;
const fetchingState = useLoading(false); const fetchingState = useLoading(false);
const messageStore = useMessageStore(); const messageStore = useMessageStore();
@ -79,7 +81,7 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
<div className="dialog-header-container"> <div className="dialog-header-container">
<p className="title-text flex flex-row items-center"> <p className="title-text flex flex-row items-center">
<Icon.Bot className="mr-1 w-5 h-auto opacity-80" /> <Icon.Bot className="mr-1 w-5 h-auto opacity-80" />
Ask AI {t("ask-ai.title")}
</p> </p>
<button className="btn close-btn" onClick={() => hide()}> <button className="btn close-btn" onClick={() => hide()}>
<Icon.X /> <Icon.X />
@ -111,14 +113,14 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
)} )}
{!isEnabled && ( {!isEnabled && (
<div className="w-full flex flex-col justify-center items-center mt-4 space-y-2"> <div className="w-full flex flex-col justify-center items-center mt-4 space-y-2">
<p>You have not set up your OpenAI API key.</p> <p>{t("ask-ai.not_enabled")}</p>
<Button onClick={() => handleGotoSystemSetting()}>Go to settings</Button> <Button onClick={() => handleGotoSystemSetting()}>{t("ask-ai.go-to-settings")}</Button>
</div> </div>
)} )}
<div className="w-full relative mt-4"> <div className="w-full relative mt-4">
<Textarea <Textarea
className="w-full" className="w-full"
placeholder="Ask anything…" placeholder={t("ask-ai.placeholder")}
value={question} value={question}
minRows={1} minRows={1}
maxRows={5} maxRows={5}

View File

@ -4,6 +4,7 @@ import { toast } from "react-hot-toast";
import { useResourceStore } from "../store/module"; import { useResourceStore } from "../store/module";
import Icon from "./Icon"; import Icon from "./Icon";
import { generateDialog } from "./Dialog"; import { generateDialog } from "./Dialog";
import { useTranslation } from "react-i18next";
const fileTypeAutocompleteOptions = ["image/*", "text/*", "audio/*", "video/*", "application/*"]; const fileTypeAutocompleteOptions = ["image/*", "text/*", "audio/*", "video/*", "application/*"];
@ -20,6 +21,7 @@ interface State {
} }
const CreateResourceDialog: React.FC<Props> = (props: Props) => { const CreateResourceDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const { destroy, onCancel, onConfirm } = props; const { destroy, onCancel, onConfirm } = props;
const resourceStore = useResourceStore(); const resourceStore = useResourceStore();
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
@ -144,14 +146,14 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
return ( return (
<> <>
<div className="dialog-header-container"> <div className="dialog-header-container">
<p className="title-text">Create Resource</p> <p className="title-text">{t("resources.create-dialog.title")}</p>
<button className="btn close-btn" onClick={handleCloseDialog}> <button className="btn close-btn" onClick={handleCloseDialog}>
<Icon.X /> <Icon.X />
</button> </button>
</div> </div>
<div className="dialog-content-container !w-80"> <div className="dialog-content-container !w-80">
<Typography className="!mb-1" level="body2"> <Typography className="!mb-1" level="body2">
Upload method {t("resources.create-dialog.upload-method")}
</Typography> </Typography>
<Select <Select
className="w-full mb-2" className="w-full mb-2"
@ -159,15 +161,15 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
value={state.selectedMode} value={state.selectedMode}
startDecorator={<Icon.File className="w-4 h-auto" />} startDecorator={<Icon.File className="w-4 h-auto" />}
> >
<Option value="local-file">Local file</Option> <Option value="local-file">{t("resources.create-dialog.local-file.option")}</Option>
<Option value="external-link">External link</Option> <Option value="external-link">{t("resources.create-dialog.external-link.option")}</Option>
</Select> </Select>
{state.selectedMode === "local-file" && ( {state.selectedMode === "local-file" && (
<> <>
<div className="w-full relative bg-blue-50 dark:bg-zinc-900 rounded-md flex flex-row justify-center items-center py-8"> <div className="w-full relative bg-blue-50 dark:bg-zinc-900 rounded-md flex flex-row justify-center items-center py-8">
<label htmlFor="files" className="p-2 px-4 text-sm text-white cursor-pointer bg-blue-500 block rounded hover:opacity-80"> <label htmlFor="files" className="p-2 px-4 text-sm text-white cursor-pointer bg-blue-500 block rounded hover:opacity-80">
Choose a file... {t("resources.create-dialog.local-file.choose")}
</label> </label>
<input <input
className="absolute inset-0 w-full h-full opacity-0" className="absolute inset-0 w-full h-full opacity-0"
@ -194,7 +196,7 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
{state.selectedMode === "external-link" && ( {state.selectedMode === "external-link" && (
<> <>
<Typography className="!mb-1" level="body2"> <Typography className="!mb-1" level="body2">
Link {t("resources.create-dialog.external-link.link")}
</Typography> </Typography>
<Input <Input
className="mb-2" className="mb-2"
@ -204,16 +206,22 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
fullWidth fullWidth
/> />
<Typography className="!mb-1" level="body2"> <Typography className="!mb-1" level="body2">
File name {t("resources.create-dialog.external-link.file-name")}
</Typography> </Typography>
<Input className="mb-2" placeholder="File name" value={resourceCreate.filename} onChange={handleFileNameChanged} fullWidth /> <Input
className="mb-2"
placeholder={t("resources.create-dialog.external-link.file-name-placeholder")}
value={resourceCreate.filename}
onChange={handleFileNameChanged}
fullWidth
/>
<Typography className="!mb-1" level="body2"> <Typography className="!mb-1" level="body2">
Type {t("resources.create-dialog.external-link.type")}
</Typography> </Typography>
<Autocomplete <Autocomplete
className="w-full" className="w-full"
size="sm" size="sm"
placeholder="File type" placeholder={t("resources.create-dialog.external-link.type-placeholder")}
freeSolo={true} freeSolo={true}
options={fileTypeAutocompleteOptions} options={fileTypeAutocompleteOptions}
onChange={(_, value) => handleFileTypeChanged(value || "")} onChange={(_, value) => handleFileTypeChanged(value || "")}
@ -223,10 +231,10 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
<div className="mt-2 w-full flex flex-row justify-end items-center space-x-1"> <div className="mt-2 w-full flex flex-row justify-end items-center space-x-1">
<Button variant="plain" color="neutral" onClick={handleCloseDialog}> <Button variant="plain" color="neutral" onClick={handleCloseDialog}>
Cancel {t("common.cancel")}
</Button> </Button>
<Button onClick={handleConfirmBtnClick} loading={state.uploadingFlag} disabled={!allowConfirmAction()}> <Button onClick={handleConfirmBtnClick} loading={state.uploadingFlag} disabled={!allowConfirmAction()}>
Create {t("common.create")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -3,12 +3,14 @@ import { toast } from "react-hot-toast";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import Icon from "./Icon"; import Icon from "./Icon";
import { generateDialog } from "./Dialog"; import { generateDialog } from "./Dialog";
import { useTranslation } from "react-i18next";
interface Props extends DialogProps { interface Props extends DialogProps {
memoId: MemoId; memoId: MemoId;
} }
const EmbedMemoDialog: React.FC<Props> = (props: Props) => { const EmbedMemoDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const { memoId, destroy } = props; const { memoId, destroy } = props;
const memoEmbeddedCode = () => { const memoEmbeddedCode = () => {
@ -23,20 +25,20 @@ const EmbedMemoDialog: React.FC<Props> = (props: Props) => {
return ( return (
<> <>
<div className="dialog-header-container"> <div className="dialog-header-container">
<p className="title-text">Embed Memo</p> <p className="title-text">{t("embed-memo.title")}</p>
<button className="btn close-btn" onClick={() => destroy()}> <button className="btn close-btn" onClick={() => destroy()}>
<Icon.X /> <Icon.X />
</button> </button>
</div> </div>
<div className="dialog-content-container !w-80"> <div className="dialog-content-container !w-80">
<p className="text-base leading-6 mb-2">Copy and paste the below codes into your blog or website.</p> <p className="text-base leading-6 mb-2">{t("embed-memo.text")}</p>
<pre className="w-full font-mono text-sm p-3 border rounded-lg"> <pre className="w-full font-mono text-sm p-3 border rounded-lg">
<code className="w-full break-all whitespace-pre-wrap">{memoEmbeddedCode()}</code> <code className="w-full break-all whitespace-pre-wrap">{memoEmbeddedCode()}</code>
</pre> </pre>
<p className="w-full text-sm leading-6 flex flex-row justify-between items-center mt-2"> <p className="w-full text-sm leading-6 flex flex-row justify-between items-center mt-2">
<span className="italic opacity-80">* Only the public memo supports.</span> <span className="italic opacity-80">{t("embed-memo.only-public-supported")}</span>
<span className="btn-primary" onClick={handleCopyCode}> <span className="btn-primary" onClick={handleCopyCode}>
Copy {t("embed-memo.copy")}
</span> </span>
</p> </p>
</div> </div>

View File

@ -112,7 +112,7 @@ const Header = () => {
className="px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg dark:text-gray-200 hover:bg-white hover:shadow dark:hover:bg-zinc-700" className="px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg dark:text-gray-200 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
onClick={() => showAskAIDialog()} onClick={() => showAskAIDialog()}
> >
<Icon.Bot className="mr-4 w-6 h-auto opacity-80" /> Ask AI <Icon.Bot className="mr-4 w-6 h-auto opacity-80" /> {t("common.ask-ai")}
</button> </button>
<button <button
id="header-archived-memo" id="header-archived-memo"

View File

@ -205,7 +205,7 @@ const Memo: React.FC<Props> = (props: Props) => {
className={`status-text ${memo.visibility.toLocaleLowerCase()}`} className={`status-text ${memo.visibility.toLocaleLowerCase()}`}
onClick={() => handleMemoVisibilityClick(memo.visibility)} onClick={() => handleMemoVisibilityClick(memo.visibility)}
> >
{memo.visibility} {t(`visibility.${memo.visibility}`)}
</span> </span>
)} )}
</div> </div>
@ -237,7 +237,7 @@ const Memo: React.FC<Props> = (props: Props) => {
{t("memo.view-detail")} {t("memo.view-detail")}
</span> </span>
<span className="btn" onClick={handleShowEmbedMemoDialog}> <span className="btn" onClick={handleShowEmbedMemoDialog}>
Embed memo {t("memo.embed")}
</span> </span>
<span className="btn archive-btn" onClick={handleArchiveMemoClick}> <span className="btn archive-btn" onClick={handleArchiveMemoClick}>
{t("common.archive")} {t("common.archive")}

View File

@ -1,11 +1,13 @@
import { useState, useRef } from "react"; import { useState, useRef } from "react";
import Icon from "./Icon"; import Icon from "./Icon";
import useDebounce from "../hooks/useDebounce"; import useDebounce from "../hooks/useDebounce";
import { useTranslation } from "react-i18next";
interface ResourceSearchBarProps { interface ResourceSearchBarProps {
setQuery: (queryText: string) => void; setQuery: (queryText: string) => void;
} }
const ResourceSearchBar = ({ setQuery }: ResourceSearchBarProps) => { const ResourceSearchBar = ({ setQuery }: ResourceSearchBarProps) => {
const { t } = useTranslation();
const [queryText, setQueryText] = useState(""); const [queryText, setQueryText] = useState("");
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
@ -29,7 +31,7 @@ const ResourceSearchBar = ({ setQuery }: ResourceSearchBarProps) => {
<input <input
className="flex ml-2 w-24 grow text-sm outline-none bg-transparent dark:text-gray-200" className="flex ml-2 w-24 grow text-sm outline-none bg-transparent dark:text-gray-200"
type="text" type="text"
placeholder="Search resource " placeholder={t("resources.search-bar-placeholder")}
ref={inputRef} ref={inputRef}
value={queryText} value={queryText}
onChange={handleTextQueryInput} onChange={handleTextQueryInput}

View File

@ -3,8 +3,10 @@ import useDebounce from "../hooks/useDebounce";
import { useFilterStore, useDialogStore, useLayoutStore } from "../store/module"; import { useFilterStore, useDialogStore, useLayoutStore } from "../store/module";
import { resolution } from "../utils/layout"; import { resolution } from "../utils/layout";
import Icon from "./Icon"; import Icon from "./Icon";
import { useTranslation } from "react-i18next";
const SearchBar = () => { const SearchBar = () => {
const { t } = useTranslation();
const filterStore = useFilterStore(); const filterStore = useFilterStore();
const dialogStore = useDialogStore(); const dialogStore = useDialogStore();
const layoutStore = useLayoutStore(); const layoutStore = useLayoutStore();
@ -64,7 +66,7 @@ const SearchBar = () => {
<input <input
className="flex ml-2 w-24 grow text-sm outline-none bg-transparent dark:text-gray-200" className="flex ml-2 w-24 grow text-sm outline-none bg-transparent dark:text-gray-200"
type="text" type="text"
placeholder="Search memos" placeholder={t("search-bar.input-placeholder")}
ref={inputRef} ref={inputRef}
value={queryText} value={queryText}
onChange={handleTextQueryInput} onChange={handleTextQueryInput}

View File

@ -79,7 +79,7 @@ const PreferencesSection = () => {
</Select> </Select>
</div> </div>
<div className="form-label selector"> <div className="form-label selector">
<span className="normal-text">Default resource visibility</span> <span className="normal-text">{t("setting.preference-section.default-resource-visibility")}</span>
<Select <Select
className="!min-w-[10rem] w-auto text-sm" className="!min-w-[10rem] w-auto text-sm"
value={setting.resourceVisibility} value={setting.resourceVisibility}
@ -98,7 +98,7 @@ const PreferencesSection = () => {
</div> </div>
<div className="form-label selector"> <div className="form-label selector">
<span className="normal-text">Daily Review Time Offset</span> <span className="normal-text">{t("setting.preference-section.daily-review-time-offset")}</span>
<span className="w-auto inline-flex"> <span className="w-auto inline-flex">
<Select <Select
placeholder="hh" placeholder="hh"

View File

@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import { DAILY_TIMESTAMP } from "../../helpers/consts"; import { DAILY_TIMESTAMP } from "../../helpers/consts";
import Icon from "../Icon"; import Icon from "../Icon";
import "../../less/common/date-picker.less"; import "../../less/common/date-picker.less";
import { useTranslation } from "react-i18next";
interface DatePickerProps { interface DatePickerProps {
className?: string; className?: string;
@ -10,6 +11,7 @@ interface DatePickerProps {
} }
const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => { const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => {
const { t } = useTranslation();
const { className, datestamp, handleDateStampChange } = props; const { className, datestamp, handleDateStampChange } = props;
const [currentDateStamp, setCurrentDateStamp] = useState<DateStamp>(getMonthFirstDayDateStamp(datestamp)); const [currentDateStamp, setCurrentDateStamp] = useState<DateStamp>(getMonthFirstDayDateStamp(datestamp));
@ -67,13 +69,13 @@ const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => {
</div> </div>
<div className="date-picker-day-container"> <div className="date-picker-day-container">
<div className="date-picker-day-header"> <div className="date-picker-day-header">
<span className="day-item">Mon</span> <span className="day-item">{t("days.mon")}</span>
<span className="day-item">Tue</span> <span className="day-item">{t("days.tue")}</span>
<span className="day-item">Wed</span> <span className="day-item">{t("days.wed")}</span>
<span className="day-item">Thu</span> <span className="day-item">{t("days.thu")}</span>
<span className="day-item">Fri</span> <span className="day-item">{t("days.fri")}</span>
<span className="day-item">Sat</span> <span className="day-item">{t("days.sat")}</span>
<span className="day-item">Sun</span> <span className="day-item">{t("days.sun")}</span>
</div> </div>
{dayList.map((d) => { {dayList.map((d) => {

View File

@ -5,6 +5,7 @@
"resources": "Resources", "resources": "Resources",
"settings": "Settings", "settings": "Settings",
"daily-review": "Daily Review", "daily-review": "Daily Review",
"ask-ai": "Ask AI",
"archived": "Archived", "archived": "Archived",
"email": "Email", "email": "Email",
"password": "Password", "password": "Password",
@ -82,7 +83,24 @@
"no-files-selected": "No files selected❗", "no-files-selected": "No files selected❗",
"upload-successfully": "Upload successfully", "upload-successfully": "Upload successfully",
"file-drag-drop-prompt": "Drag and drop your file here to upload file", "file-drag-drop-prompt": "Drag and drop your file here to upload file",
"select": "Select" "select": "Select",
"search-bar-placeholder": "Search resource",
"create-dialog": {
"title": "Create Resource",
"upload-method": "Upload method",
"local-file": {
"option": "Local file",
"choose": "Choose a file..."
},
"external-link": {
"option": "External link",
"link": "Link",
"file-name": "File name",
"file-name-placeholder": "File name",
"type": "Type",
"type-placeholder": "File type"
}
}
}, },
"archived": { "archived": {
"archived-memos": "Archived Memos", "archived-memos": "Archived Memos",
@ -102,6 +120,7 @@
"memo": { "memo": {
"view-detail": "View Detail", "view-detail": "View Detail",
"copy": "Copy", "copy": "Copy",
"embed": "Embed memo",
"visibility": { "visibility": {
"private": "Only visible to you", "private": "Only visible to you",
"protected": "Visible to members", "protected": "Visible to members",
@ -155,6 +174,9 @@
"search": { "search": {
"quickly-filter": "Quickly filter" "quickly-filter": "Quickly filter"
}, },
"search-bar": {
"input-placeholder": "Search memos"
},
"setting": { "setting": {
"my-account": "My Account", "my-account": "My Account",
"preference": "Preferences", "preference": "Preferences",
@ -171,13 +193,15 @@
"preference-section": { "preference-section": {
"theme": "Theme", "theme": "Theme",
"default-memo-visibility": "Default memo visibility", "default-memo-visibility": "Default memo visibility",
"default-resource-visibility": "Default resource visibility",
"enable-folding-memo": "Enable folding memo", "enable-folding-memo": "Enable folding memo",
"enable-double-click": "Enable double-click to edit", "enable-double-click": "Enable double-click to edit",
"editor-font-style": "Editor font style", "editor-font-style": "Editor font style",
"mobile-editor-style": "Mobile editor style", "mobile-editor-style": "Mobile editor style",
"default-memo-sort-option": "Memo display time", "default-memo-sort-option": "Memo display time",
"created_ts": "Created Time", "created_ts": "Created Time",
"updated_ts": "Updated Time" "updated_ts": "Updated Time",
"daily-review-time-offset": "Daily Review Time Offset"
}, },
"storage-section": { "storage-section": {
"storage-services-list": "Storage service list", "storage-services-list": "Storage service list",
@ -273,5 +297,22 @@
"sat": "Sat", "sat": "Sat",
"sunday": "Sunday", "sunday": "Sunday",
"sun": "Sun" "sun": "Sun"
},
"ask-ai": {
"title": "Ask AI",
"not-enabled": "You have not set up your OpenAI API key.",
"go-to-settings": "Go to settings",
"placeholder": "Ask anything…"
},
"embed-memo": {
"title": "Embed Memo",
"text": "Copy and paste the below codes into your blog or website.",
"only-public-supported": "* Only the public memo supports.",
"copy": "Copy"
},
"visibility": {
"PUBLIC": "PUBLIC",
"PROTECTED": "PROTECTED",
"PRIVATE": "PRIVATE"
} }
} }

View File

@ -1,6 +1,11 @@
{ {
"common": { "common": {
"about": "Про Memos", "about": "Про Memos",
"home": "Главная",
"daily-review": "По дням",
"resources": "Ресурсы",
"ask-ai": "Спросить ИИ",
"archived": "В архиве",
"email": "Эл. почта", "email": "Эл. почта",
"password": "Пароль", "password": "Пароль",
"repeat-password-short": "Повторить", "repeat-password-short": "Повторить",
@ -48,8 +53,9 @@
"link": "Ссылка", "link": "Ссылка",
"vacuum": "Сжать", "vacuum": "Сжать",
"select": "Выбрать", "select": "Выбрать",
"avatar": "Avatar", "avatar": "Аватар",
"database": "Database" "database": "База-Данных",
"settings": "Настройки"
}, },
"slogan": "Self-hosted платформа с открытым исходным кодом для заметок и управления записями с поддержкой социальных функций.", "slogan": "Self-hosted платформа с открытым исходным кодом для заметок и управления записями с поддержкой социальных функций.",
"auth": { "auth": {
@ -80,7 +86,25 @@
"warning-text-unused": "Вы уверены, что хотите удалить неиспользуемые ресурсы? ЭТО ДЕЙСТВИЕ НЕВОЗМОЖНО ОТМЕНИТЬ❗", "warning-text-unused": "Вы уверены, что хотите удалить неиспользуемые ресурсы? ЭТО ДЕЙСТВИЕ НЕВОЗМОЖНО ОТМЕНИТЬ❗",
"no-unused-resources": "Нет неиспользуемых ресурсов", "no-unused-resources": "Нет неиспользуемых ресурсов",
"name": "Название", "name": "Название",
"clear": "Clear" "clear": "Отчистить",
"search-bar-placeholder": "Поиск ресурсов",
"select": "Выбрать",
"create-dialog": {
"title": "Создать ресурс",
"upload-method": "Метод загрузки",
"local-file": {
"option": "Локальный файл",
"choose": "Выберите файл..."
},
"external-link": {
"option": "Внешняя ссылка",
"link": "Ссылка",
"file-name": "Название файла",
"file-name-placeholder": "Название файла",
"type": "Тип",
"type-placeholder": "Тип файла"
}
}
}, },
"archived": { "archived": {
"archived-memos": "Заархивированные записи", "archived-memos": "Заархивированные записи",
@ -100,11 +124,12 @@
"memo": { "memo": {
"view-detail": "Подробно", "view-detail": "Подробно",
"copy": "Копировать", "copy": "Копировать",
"embed": "Встроить запись",
"visibility": { "visibility": {
"private": "Видно только вам", "private": "Видно только вам",
"protected": "Видно только пользователям", "protected": "Видно только пользователям",
"public": "Видно всем", "public": "Видно всем",
"disabled": "Public memos are disabled" "disabled": "Публичные записи отключены"
} }
}, },
"memo-list": { "memo-list": {
@ -149,6 +174,9 @@
"search": { "search": {
"quickly-filter": "Быстрый фильтр" "quickly-filter": "Быстрый фильтр"
}, },
"search-bar": {
"input-placeholder": "Поиск заметок"
},
"setting": { "setting": {
"my-account": "Мой аккаунт", "my-account": "Мой аккаунт",
"preference": "Настройки", "preference": "Настройки",
@ -163,13 +191,15 @@
"preference-section": { "preference-section": {
"theme": "Тема", "theme": "Тема",
"default-memo-visibility": "Видимость записей по умолчанию", "default-memo-visibility": "Видимость записей по умолчанию",
"default-resource-visibility": "Видимость ресурсов по умолчанию",
"enable-folding-memo": "Включить сворачивание записей", "enable-folding-memo": "Включить сворачивание записей",
"editor-font-style": "Стиль шрифта", "editor-font-style": "Стиль шрифта",
"mobile-editor-style": "Стиль мобильного редактора", "mobile-editor-style": "Стиль мобильного редактора",
"default-memo-sort-option": "Отображаемое время записи", "default-memo-sort-option": "Отображаемое время записи",
"created_ts": "Время создания", "created_ts": "Время создания",
"updated_ts": "Время обновления", "updated_ts": "Время обновления",
"enable-double-click": "Enable double-click to edit" "enable-double-click": "Разрешить двойной клик для редактирования",
"daily-review-time-offset": "Смещение времени ежедневного просмотра"
}, },
"member-section": { "member-section": {
"create-a-member": "Создать пользователя" "create-a-member": "Создать пользователя"
@ -187,30 +217,30 @@
"additional-script": "Настраиваемый скрипт", "additional-script": "Настраиваемый скрипт",
"additional-style-placeholder": "Настраиваемый код CSS", "additional-style-placeholder": "Настраиваемый код CSS",
"additional-script-placeholder": "Настраиваемый код JavaScript", "additional-script-placeholder": "Настраиваемый код JavaScript",
"disable-public-memos": "Disable public memos" "disable-public-memos": "Отключить публичные записи"
}, },
"appearance-option": { "appearance-option": {
"system": "Системная", "system": "Системная",
"light": "Светлая", "light": "Светлая",
"dark": "Тёмная" "dark": "Тёмная"
}, },
"storage": "Storage", "storage": "Хранилище",
"sso": "SSO", "sso": "SSO",
"storage-section": { "storage-section": {
"storage-services-list": "Storage service list", "storage-services-list": "Список хранилищ",
"create-a-service": "Create a service", "create-a-service": "Создать сервис",
"update-a-service": "Update a service", "update-a-service": "Обновить сервис",
"warning-text": "Are you sure to delete this storage service? THIS ACTION IS IRREVERSIBLE❗", "warning-text": "Вы уверены, что хотите удалить это хранилище? ЭТО ДЕЙСТВИЕ НЕВОЗМОЖНО ОТМЕНИТЬ❗",
"delete-storage": "Delete Storage" "delete-storage": "Удалить Хранилище"
} }
}, },
"amount-text": { "amount-text": {
"memo_one": "ЗАПИСЬ", "memo_one": "ЗАПИСЬ",
"tag_one": "ТЕГ", "tag_one": "ТЕГ",
"day_one": "ДЕНЬ", "day_one": "ДЕНЬ",
"memo_other": "MEMOS", "memo_other": "ЗАПИСИ",
"tag_other": "TAGS", "tag_other": "ТЕГИ",
"day_other": "DAYS" "day_other": "ДНИ"
}, },
"message": { "message": {
"no-memos": "нет записей 🌃", "no-memos": "нет записей 🌃",
@ -226,7 +256,7 @@
"image-load-failed": "Ошибка загрузки изображения", "image-load-failed": "Ошибка загрузки изображения",
"fill-form": "Пожалуйста, заполните форму", "fill-form": "Пожалуйста, заполните форму",
"login-failed": "Ошибка входа", "login-failed": "Ошибка входа",
"signup-failed": "Помилка реєстрації", "signup-failed": "Ошибка регистрации",
"user-not-found": "Пользователь не найден", "user-not-found": "Пользователь не найден",
"password-changed": "Пароль изменён", "password-changed": "Пароль изменён",
"private-only": "Это частная заметка.", "private-only": "Это частная заметка.",
@ -249,8 +279,8 @@
"succeed-update-customized-profile": "Собственный профиль успешно обновлён", "succeed-update-customized-profile": "Собственный профиль успешно обновлён",
"succeed-update-additional-script": "Настраиваемый скрипт успешно обновлён", "succeed-update-additional-script": "Настраиваемый скрипт успешно обновлён",
"update-succeed": "Успешно обновлено", "update-succeed": "Успешно обновлено",
"succeed-copy-code": "Succeed to copy code to clipboard.", "succeed-copy-code": "Код успешно скопирован.",
"page-not-found": "404 - Page Not Found 😥" "page-not-found": "404 - Страница не найдена 😥"
}, },
"days": { "days": {
"monday": "Понедельник", "monday": "Понедельник",
@ -267,5 +297,22 @@
"sat": "Сб.", "sat": "Сб.",
"sunday": "Воскресенье", "sunday": "Воскресенье",
"sun": "Вс." "sun": "Вс."
},
"ask-ai": {
"title": "Спросить ИИ",
"not-enabled": "Вам нужно установить ключ OpenAI API.",
"go-to-settings": "Перейти в настройки",
"placeholder": "Спросите что угодно…"
},
"embed-memo": {
"title": "Встраивание записи",
"text": "Скопируйте и вставьте код в Ваш блог или сайт",
"only-public-supported": "* Поддерживаются только публичные записи",
"copy": "Скопировать"
},
"visibility": {
"PUBLIC": "ПУБЛИЧНОЕ",
"PROTECTED": "ЗАЩИЩЁННОЕ",
"PRIVATE": "ЛИЧНОЕ"
} }
} }

View File

@ -11,6 +11,7 @@ import "./i18n";
import "dayjs/locale/zh"; import "dayjs/locale/zh";
import "dayjs/locale/fr"; import "dayjs/locale/fr";
import "dayjs/locale/vi"; import "dayjs/locale/vi";
import "dayjs/locale/ru";
import "./less/code-highlight.less"; import "./less/code-highlight.less";
import "./css/global.css"; import "./css/global.css";
import "./css/tailwind.css"; import "./css/tailwind.css";

View File

@ -12,9 +12,7 @@ import showPreviewImageDialog from "../components/PreviewImageDialog";
import Icon from "../components/Icon"; import Icon from "../components/Icon";
import DatePicker from "../components/base/DatePicker"; import DatePicker from "../components/base/DatePicker";
import DailyMemo from "../components/DailyMemo"; import DailyMemo from "../components/DailyMemo";
import dayjs from "dayjs";
const monthChineseStrArray = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"];
const weekdayChineseStrArray = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const DailyReview = () => { const DailyReview = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -80,6 +78,9 @@ const DailyReview = () => {
toggleShowDatePicker(false); toggleShowDatePicker(false);
}; };
const currentMonth = dayjs().format("MMM");
const currentDayOfWeek = dayjs().format("ddd");
return ( return (
<section className="w-full max-w-2xl min-h-full flex flex-col justify-start items-center px-4 sm:px-2 sm:pt-4 pb-8 bg-zinc-100 dark:bg-zinc-800"> <section className="w-full max-w-2xl min-h-full flex flex-col justify-start items-center px-4 sm:px-2 sm:pt-4 pb-8 bg-zinc-100 dark:bg-zinc-800">
<MobileHeader showSearch={false} /> <MobileHeader showSearch={false} />
@ -127,11 +128,11 @@ const DailyReview = () => {
<div className="mx-auto font-bold text-gray-600 dark:text-gray-300 text-center leading-6 mb-2">{currentDate.getFullYear()}</div> <div className="mx-auto font-bold text-gray-600 dark:text-gray-300 text-center leading-6 mb-2">{currentDate.getFullYear()}</div>
<div className="flex flex-col justify-center items-center m-auto w-24 h-24 shadow rounded-3xl dark:bg-zinc-800"> <div className="flex flex-col justify-center items-center m-auto w-24 h-24 shadow rounded-3xl dark:bg-zinc-800">
<div className="text-center w-full leading-6 text-sm text-white bg-blue-700 rounded-t-3xl"> <div className="text-center w-full leading-6 text-sm text-white bg-blue-700 rounded-t-3xl">
{monthChineseStrArray[currentDate.getMonth()]} {currentMonth[0].toUpperCase() + currentMonth.substring(1)}
</div> </div>
<div className="text-black dark:text-white text-4xl font-medium leading-12">{currentDate.getDate()}</div> <div className="text-black dark:text-white text-4xl font-medium leading-12">{currentDate.getDate()}</div>
<div className="dark:text-gray-300 text-center w-full leading-6 -mt-2 text-xs"> <div className="dark:text-gray-300 text-center w-full leading-6 -mt-2 text-xs">
{weekdayChineseStrArray[currentDate.getDay()]} {currentDayOfWeek[0].toUpperCase() + currentDayOfWeek.substring(1)}
</div> </div>
</div> </div>
</div> </div>