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 { useEffect, Suspense } from "react";
import { Toaster } from "react-hot-toast";
@ -58,6 +59,7 @@ const App = () => {
useEffect(() => {
document.documentElement.setAttribute("lang", locale);
i18n.changeLanguage(locale);
dayjs.locale(locale);
storage.set({
locale: locale,
});

View File

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

View File

@ -4,6 +4,7 @@ import { toast } from "react-hot-toast";
import { useResourceStore } from "../store/module";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
import { useTranslation } from "react-i18next";
const fileTypeAutocompleteOptions = ["image/*", "text/*", "audio/*", "video/*", "application/*"];
@ -20,6 +21,7 @@ interface State {
}
const CreateResourceDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const { destroy, onCancel, onConfirm } = props;
const resourceStore = useResourceStore();
const [state, setState] = useState<State>({
@ -144,14 +146,14 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
return (
<>
<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}>
<Icon.X />
</button>
</div>
<div className="dialog-content-container !w-80">
<Typography className="!mb-1" level="body2">
Upload method
{t("resources.create-dialog.upload-method")}
</Typography>
<Select
className="w-full mb-2"
@ -159,15 +161,15 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
value={state.selectedMode}
startDecorator={<Icon.File className="w-4 h-auto" />}
>
<Option value="local-file">Local file</Option>
<Option value="external-link">External link</Option>
<Option value="local-file">{t("resources.create-dialog.local-file.option")}</Option>
<Option value="external-link">{t("resources.create-dialog.external-link.option")}</Option>
</Select>
{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">
<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>
<input
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" && (
<>
<Typography className="!mb-1" level="body2">
Link
{t("resources.create-dialog.external-link.link")}
</Typography>
<Input
className="mb-2"
@ -204,16 +206,22 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
fullWidth
/>
<Typography className="!mb-1" level="body2">
File name
{t("resources.create-dialog.external-link.file-name")}
</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">
Type
{t("resources.create-dialog.external-link.type")}
</Typography>
<Autocomplete
className="w-full"
size="sm"
placeholder="File type"
placeholder={t("resources.create-dialog.external-link.type-placeholder")}
freeSolo={true}
options={fileTypeAutocompleteOptions}
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">
<Button variant="plain" color="neutral" onClick={handleCloseDialog}>
Cancel
{t("common.cancel")}
</Button>
<Button onClick={handleConfirmBtnClick} loading={state.uploadingFlag} disabled={!allowConfirmAction()}>
Create
{t("common.create")}
</Button>
</div>
</div>

View File

@ -3,12 +3,14 @@ import { toast } from "react-hot-toast";
import copy from "copy-to-clipboard";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
import { useTranslation } from "react-i18next";
interface Props extends DialogProps {
memoId: MemoId;
}
const EmbedMemoDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const { memoId, destroy } = props;
const memoEmbeddedCode = () => {
@ -23,20 +25,20 @@ const EmbedMemoDialog: React.FC<Props> = (props: Props) => {
return (
<>
<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()}>
<Icon.X />
</button>
</div>
<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">
<code className="w-full break-all whitespace-pre-wrap">{memoEmbeddedCode()}</code>
</pre>
<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}>
Copy
{t("embed-memo.copy")}
</span>
</p>
</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"
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
id="header-archived-memo"

View File

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

View File

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

View File

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

View File

@ -79,7 +79,7 @@ const PreferencesSection = () => {
</Select>
</div>
<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
className="!min-w-[10rem] w-auto text-sm"
value={setting.resourceVisibility}
@ -98,7 +98,7 @@ const PreferencesSection = () => {
</div>
<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">
<Select
placeholder="hh"

View File

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

View File

@ -5,6 +5,7 @@
"resources": "Resources",
"settings": "Settings",
"daily-review": "Daily Review",
"ask-ai": "Ask AI",
"archived": "Archived",
"email": "Email",
"password": "Password",
@ -82,7 +83,24 @@
"no-files-selected": "No files selected❗",
"upload-successfully": "Upload successfully",
"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-memos": "Archived Memos",
@ -102,6 +120,7 @@
"memo": {
"view-detail": "View Detail",
"copy": "Copy",
"embed": "Embed memo",
"visibility": {
"private": "Only visible to you",
"protected": "Visible to members",
@ -155,6 +174,9 @@
"search": {
"quickly-filter": "Quickly filter"
},
"search-bar": {
"input-placeholder": "Search memos"
},
"setting": {
"my-account": "My Account",
"preference": "Preferences",
@ -171,13 +193,15 @@
"preference-section": {
"theme": "Theme",
"default-memo-visibility": "Default memo visibility",
"default-resource-visibility": "Default resource visibility",
"enable-folding-memo": "Enable folding memo",
"enable-double-click": "Enable double-click to edit",
"editor-font-style": "Editor font style",
"mobile-editor-style": "Mobile editor style",
"default-memo-sort-option": "Memo display time",
"created_ts": "Created Time",
"updated_ts": "Updated Time"
"updated_ts": "Updated Time",
"daily-review-time-offset": "Daily Review Time Offset"
},
"storage-section": {
"storage-services-list": "Storage service list",
@ -273,5 +297,22 @@
"sat": "Sat",
"sunday": "Sunday",
"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": {
"about": "Про Memos",
"home": "Главная",
"daily-review": "По дням",
"resources": "Ресурсы",
"ask-ai": "Спросить ИИ",
"archived": "В архиве",
"email": "Эл. почта",
"password": "Пароль",
"repeat-password-short": "Повторить",
@ -48,8 +53,9 @@
"link": "Ссылка",
"vacuum": "Сжать",
"select": "Выбрать",
"avatar": "Avatar",
"database": "Database"
"avatar": "Аватар",
"database": "База-Данных",
"settings": "Настройки"
},
"slogan": "Self-hosted платформа с открытым исходным кодом для заметок и управления записями с поддержкой социальных функций.",
"auth": {
@ -80,7 +86,25 @@
"warning-text-unused": "Вы уверены, что хотите удалить неиспользуемые ресурсы? ЭТО ДЕЙСТВИЕ НЕВОЗМОЖНО ОТМЕНИТЬ❗",
"no-unused-resources": "Нет неиспользуемых ресурсов",
"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-memos": "Заархивированные записи",
@ -100,11 +124,12 @@
"memo": {
"view-detail": "Подробно",
"copy": "Копировать",
"embed": "Встроить запись",
"visibility": {
"private": "Видно только вам",
"protected": "Видно только пользователям",
"public": "Видно всем",
"disabled": "Public memos are disabled"
"disabled": "Публичные записи отключены"
}
},
"memo-list": {
@ -149,6 +174,9 @@
"search": {
"quickly-filter": "Быстрый фильтр"
},
"search-bar": {
"input-placeholder": "Поиск заметок"
},
"setting": {
"my-account": "Мой аккаунт",
"preference": "Настройки",
@ -163,13 +191,15 @@
"preference-section": {
"theme": "Тема",
"default-memo-visibility": "Видимость записей по умолчанию",
"default-resource-visibility": "Видимость ресурсов по умолчанию",
"enable-folding-memo": "Включить сворачивание записей",
"editor-font-style": "Стиль шрифта",
"mobile-editor-style": "Стиль мобильного редактора",
"default-memo-sort-option": "Отображаемое время записи",
"created_ts": "Время создания",
"updated_ts": "Время обновления",
"enable-double-click": "Enable double-click to edit"
"enable-double-click": "Разрешить двойной клик для редактирования",
"daily-review-time-offset": "Смещение времени ежедневного просмотра"
},
"member-section": {
"create-a-member": "Создать пользователя"
@ -187,30 +217,30 @@
"additional-script": "Настраиваемый скрипт",
"additional-style-placeholder": "Настраиваемый код CSS",
"additional-script-placeholder": "Настраиваемый код JavaScript",
"disable-public-memos": "Disable public memos"
"disable-public-memos": "Отключить публичные записи"
},
"appearance-option": {
"system": "Системная",
"light": "Светлая",
"dark": "Тёмная"
},
"storage": "Storage",
"storage": "Хранилище",
"sso": "SSO",
"storage-section": {
"storage-services-list": "Storage service list",
"create-a-service": "Create a service",
"update-a-service": "Update a service",
"warning-text": "Are you sure to delete this storage service? THIS ACTION IS IRREVERSIBLE❗",
"delete-storage": "Delete Storage"
"storage-services-list": "Список хранилищ",
"create-a-service": "Создать сервис",
"update-a-service": "Обновить сервис",
"warning-text": "Вы уверены, что хотите удалить это хранилище? ЭТО ДЕЙСТВИЕ НЕВОЗМОЖНО ОТМЕНИТЬ❗",
"delete-storage": "Удалить Хранилище"
}
},
"amount-text": {
"memo_one": "ЗАПИСЬ",
"tag_one": "ТЕГ",
"day_one": "ДЕНЬ",
"memo_other": "MEMOS",
"tag_other": "TAGS",
"day_other": "DAYS"
"memo_other": "ЗАПИСИ",
"tag_other": "ТЕГИ",
"day_other": "ДНИ"
},
"message": {
"no-memos": "нет записей 🌃",
@ -226,7 +256,7 @@
"image-load-failed": "Ошибка загрузки изображения",
"fill-form": "Пожалуйста, заполните форму",
"login-failed": "Ошибка входа",
"signup-failed": "Помилка реєстрації",
"signup-failed": "Ошибка регистрации",
"user-not-found": "Пользователь не найден",
"password-changed": "Пароль изменён",
"private-only": "Это частная заметка.",
@ -249,8 +279,8 @@
"succeed-update-customized-profile": "Собственный профиль успешно обновлён",
"succeed-update-additional-script": "Настраиваемый скрипт успешно обновлён",
"update-succeed": "Успешно обновлено",
"succeed-copy-code": "Succeed to copy code to clipboard.",
"page-not-found": "404 - Page Not Found 😥"
"succeed-copy-code": "Код успешно скопирован.",
"page-not-found": "404 - Страница не найдена 😥"
},
"days": {
"monday": "Понедельник",
@ -267,5 +297,22 @@
"sat": "Сб.",
"sunday": "Воскресенье",
"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/fr";
import "dayjs/locale/vi";
import "dayjs/locale/ru";
import "./less/code-highlight.less";
import "./css/global.css";
import "./css/tailwind.css";

View File

@ -12,9 +12,7 @@ import showPreviewImageDialog from "../components/PreviewImageDialog";
import Icon from "../components/Icon";
import DatePicker from "../components/base/DatePicker";
import DailyMemo from "../components/DailyMemo";
const monthChineseStrArray = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"];
const weekdayChineseStrArray = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
import dayjs from "dayjs";
const DailyReview = () => {
const { t } = useTranslation();
@ -80,6 +78,9 @@ const DailyReview = () => {
toggleShowDatePicker(false);
};
const currentMonth = dayjs().format("MMM");
const currentDayOfWeek = dayjs().format("ddd");
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">
<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="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">
{monthChineseStrArray[currentDate.getMonth()]}
{currentMonth[0].toUpperCase() + currentMonth.substring(1)}
</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">
{weekdayChineseStrArray[currentDate.getDay()]}
{currentDayOfWeek[0].toUpperCase() + currentDayOfWeek.substring(1)}
</div>
</div>
</div>