mirror of
https://github.com/artegoser/AnoPaper.git
synced 2024-11-06 04:53:56 +03:00
feat: localisation
This commit is contained in:
parent
e2708f1985
commit
8fb91d7ecb
15 changed files with 208 additions and 64 deletions
18
src/App.jsx
18
src/App.jsx
|
@ -12,6 +12,8 @@ import PubNoteSafe from "./pages/pubNoteSafe";
|
|||
import RenderMarkdown from "./components/markdown";
|
||||
import socket from "./components/socket";
|
||||
import Settings from "./pages/settings";
|
||||
import Locales from "./localisation/main";
|
||||
import { useState } from "react";
|
||||
|
||||
function App() {
|
||||
Storage.prototype.setObj = function (key, obj) {
|
||||
|
@ -21,10 +23,24 @@ function App() {
|
|||
return JSON.parse(this.getItem(key)) || {};
|
||||
};
|
||||
|
||||
const [key, setKey] = useState(Math.random());
|
||||
|
||||
window.settings = localStorage.getObj("settings") || {};
|
||||
window.locals =
|
||||
Locales[window.settings.language] ||
|
||||
Locales[navigator.language] ||
|
||||
Locales[navigator.userLanguage] ||
|
||||
Locales.en;
|
||||
|
||||
window.addEventListener("reRenderPage", () => {
|
||||
setKey(Math.random());
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 lg:grid-cols-5 gap-10 text-black dark:text-white">
|
||||
<div
|
||||
className="grid grid-cols-4 lg:grid-cols-5 gap-10 text-black dark:text-white"
|
||||
key={key}
|
||||
>
|
||||
<Menu />
|
||||
<div className="col-span-4 p-5 m-4 rounded-2xl">
|
||||
<Routes>
|
||||
|
|
|
@ -11,14 +11,18 @@ function Menu() {
|
|||
<div className="grid grid-cols-1 col-span-4 lg:col-span-1 gap-2 m-4 place-content-start justify-self-center justify-center">
|
||||
<ButtonWithIcon
|
||||
icon={MagnifyingGlassCircleIcon}
|
||||
text="Заметки"
|
||||
text={locals.Notes}
|
||||
href="/notes"
|
||||
/>
|
||||
<ButtonWithIcon icon={PencilIcon} text="Написать" href="/" />
|
||||
<ButtonWithIcon icon={Cog6ToothIcon} text="Настройки" href="/settings" />
|
||||
<ButtonWithIcon icon={PencilIcon} text={locals.Write} href="/" />
|
||||
<ButtonWithIcon
|
||||
icon={Cog6ToothIcon}
|
||||
text={locals.Settings}
|
||||
href="/settings"
|
||||
/>
|
||||
<ButtonWithIcon
|
||||
icon={ExclamationCircleIcon}
|
||||
text="Подробнее"
|
||||
text={locals.About}
|
||||
href="/about"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import RenderMarkdown from "../components/markdown";
|
||||
import printDate from "./utils";
|
||||
import { printDate } from "./utils";
|
||||
|
||||
function Note({ note }) {
|
||||
return (
|
||||
|
|
|
@ -13,4 +13,8 @@ function printDate(time) {
|
|||
return dateStr;
|
||||
}
|
||||
|
||||
export default printDate;
|
||||
function reRenderPage() {
|
||||
window.dispatchEvent(new Event("reRenderPage"));
|
||||
}
|
||||
|
||||
export { printDate, reRenderPage };
|
||||
|
|
46
src/localisation/en.js
Normal file
46
src/localisation/en.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
let en = {
|
||||
Notes: "Notes",
|
||||
Write: "Write",
|
||||
Chat: "Chat",
|
||||
Settings: "Settings",
|
||||
About: "About",
|
||||
Name: "Name",
|
||||
UserName: "Username",
|
||||
User: "User",
|
||||
PhotoUrl: "Photo URL",
|
||||
Url: "URL",
|
||||
Status: "Status",
|
||||
UserStatus: "User status",
|
||||
EditPreview: "Editing in preview",
|
||||
EditPreviewWarn:
|
||||
"Can cause irreversible text changes, such as breaking code tags",
|
||||
PublicNote: "Public note",
|
||||
PublicNoteTitle: "If enabled, note will be visible to all users",
|
||||
Interface: "Interface",
|
||||
Language: "Language",
|
||||
ThirdPartyApi: "Third party API",
|
||||
OpenAiKey: "OpenAI API key",
|
||||
Key: "Key",
|
||||
Preview: "Preview",
|
||||
NotePlaceholder:
|
||||
"Your note starts here. You can use markdown, MathJax and GFM.",
|
||||
NoteName: "Note name",
|
||||
Publish: "Publish",
|
||||
Save: "Save",
|
||||
WriteNote: "Write note",
|
||||
PubError: "Error in publishing note",
|
||||
PubErrorMsg: "Note was not published due to an unknown error",
|
||||
PubErrorMsgNoName: "Note was not published, because it does not have a name.",
|
||||
PubErrorMsgNoText: "Note was not published, because it does not have a text.",
|
||||
Back: "Back",
|
||||
PubNoteNotExist: "This note does not exist",
|
||||
NoteNotExist: "This note does not exist",
|
||||
Idontexists: "I don't exist",
|
||||
PubUrlPlaceholder:
|
||||
"The link to send a public note. When you click this link, the note will disappear from the server and be saved locally.",
|
||||
Delete: "Delete",
|
||||
Open: "Open",
|
||||
NoNotesYet: "No notes yet",
|
||||
};
|
||||
|
||||
export default en;
|
22
src/localisation/main.js
Normal file
22
src/localisation/main.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import ru from "./ru";
|
||||
import en from "./en";
|
||||
|
||||
let Locales = {
|
||||
ru,
|
||||
en,
|
||||
"en-US": en,
|
||||
"en-AU": en,
|
||||
"en-BZ": en,
|
||||
"en-CA": en,
|
||||
"en-IE": en,
|
||||
"en-JM": en,
|
||||
"en-NZ": en,
|
||||
"en-ZA": en,
|
||||
"en-TT": en,
|
||||
"en-GB": en,
|
||||
"en-US": en,
|
||||
|
||||
"ru-RU": ru,
|
||||
};
|
||||
|
||||
export default Locales;
|
47
src/localisation/ru.js
Normal file
47
src/localisation/ru.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
let ru = {
|
||||
Notes: "Заметки",
|
||||
Write: "Написать",
|
||||
Chat: "Чат",
|
||||
Settings: "Настройки",
|
||||
About: "Подробнее",
|
||||
Name: "Имя",
|
||||
UserName: "Имя пользователя",
|
||||
User: "Пользователь",
|
||||
PhotoUrl: "Ссылка на фото",
|
||||
Url: "Ссылка",
|
||||
Status: "Статус",
|
||||
UserStatus: "Статус пользователя",
|
||||
EditPreview: "Редактирование в предпросмотре",
|
||||
EditPreviewWarn:
|
||||
"Может вызывать необратимые изменения текста, например ломает теги кода",
|
||||
PublicNote: "Публичная заметка",
|
||||
PublicNoteTitle: "Если включено, то заметка будет видна всем пользователям",
|
||||
Interface: "Интерфейс",
|
||||
Language: "Язык",
|
||||
ThirdPartyApi: "Сторонний API",
|
||||
OpenAiKey: "OpenAI API ключ",
|
||||
Key: "Ключ",
|
||||
Preview: "Предпросмотр",
|
||||
NotePlaceholder:
|
||||
"Ваша заметка начинается здесь. Вы можете использовать markdown, MathJax и GFM.",
|
||||
NoteName: "Название заметки",
|
||||
Publish: "Опубликовать",
|
||||
Save: "Сохранить",
|
||||
WriteNote: "Написать заметку",
|
||||
PubError: "Ошибка в публикации заметки",
|
||||
PubErrorMsg: "Заметка не была опубликована из-за неизвестной ошибки",
|
||||
PubErrorMsgNoName:
|
||||
"Заметка не была опубликована, так как отсутствует название.",
|
||||
PubErrorMsgNoText: "Заметка не была опубликована, так как отсутствует текст.",
|
||||
Back: "Назад",
|
||||
PubNoteNotExist: "Такой публичной заметки не существует",
|
||||
NoteNotExist: "Заметки не существует",
|
||||
Idontexists: "Меня не существует",
|
||||
PubUrlPlaceholder:
|
||||
"Ссылка для отправки публичной заметки. При переходе на эту ссылку, заметка исчезнет с сервера и будет сохранена локально.",
|
||||
Delete: "Удалить",
|
||||
Open: "Открыть",
|
||||
NoNotesYet: "Заметок пока нет",
|
||||
};
|
||||
|
||||
export default ru;
|
|
@ -3,7 +3,7 @@ import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
|
|||
import { CheckBox } from "../components/checkbox";
|
||||
import { useState } from "react";
|
||||
import RenderMarkdown from "../components/markdown";
|
||||
import printDate from "../components/utils";
|
||||
import { printDate } from "../components/utils";
|
||||
import rehypeRemark from "rehype-remark/lib";
|
||||
import ContentEditable from "react-contenteditable";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
|
@ -41,11 +41,11 @@ function CreateNote() {
|
|||
<div>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2">
|
||||
<h2 className="text-center lg:text-left leading-tight text-2xl font-bold">
|
||||
{`${preview ? "" : "Написать заметку"}`}
|
||||
{`${preview ? "" : locals.WriteNote}`}
|
||||
</h2>
|
||||
<CheckBox
|
||||
className="justify-self-center lg:justify-self-end"
|
||||
label="Предпросмотр"
|
||||
label={locals.Preview}
|
||||
id="preview"
|
||||
onClick={(val) => {
|
||||
setText(localStorage.getItem("NoteText"));
|
||||
|
@ -60,7 +60,7 @@ function CreateNote() {
|
|||
className={`mb-2 md:w-1/6 w-full ${inputStyle} ${
|
||||
preview ? "hidden" : ""
|
||||
}`}
|
||||
placeholder="Название заметки..."
|
||||
placeholder={locals.NoteName}
|
||||
maxLength={64}
|
||||
value={localStorage.getItem("NoteName") || ""}
|
||||
onChange={(e) => {
|
||||
|
@ -75,7 +75,7 @@ function CreateNote() {
|
|||
${preview ? "hidden" : ""}
|
||||
`}
|
||||
rows="10"
|
||||
placeholder="Ваша заметка начинается здесь. Можно использовать markdown..."
|
||||
placeholder={locals.NotePlaceholder}
|
||||
maxLength={5000}
|
||||
onChange={(e) => {
|
||||
setText(e.target.value);
|
||||
|
@ -108,8 +108,8 @@ function CreateNote() {
|
|||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 justify-items-center w-full">
|
||||
<SettingsCheckBox
|
||||
label="Публичная заметка"
|
||||
title="Если включено, то заметка будет видна всем пользователям"
|
||||
label={locals.PublicNote}
|
||||
title={locals.PublicNoteTitle}
|
||||
checked={settings.publicNote}
|
||||
settingName="publicNote"
|
||||
className="justify-self-center lg:justify-self-start"
|
||||
|
@ -120,7 +120,7 @@ function CreateNote() {
|
|||
<div className="justify-self-center lg:justify-self-end">
|
||||
<ButtonWithIcon
|
||||
icon={ChevronDoubleRightIcon}
|
||||
text={publicState ? "Опубликовать" : "Сохранить"}
|
||||
text={publicState ? locals.Publish : locals.Save}
|
||||
reverse={true}
|
||||
href={publicState ? "/notes/publish" : "/notes/save-local"}
|
||||
className="m-1"
|
||||
|
|
|
@ -14,17 +14,17 @@ function NotePage() {
|
|||
icon={ChevronDoubleLeftIcon}
|
||||
className="mb-4"
|
||||
href="/notes"
|
||||
text="Заметки"
|
||||
text={locals.Notes}
|
||||
/>
|
||||
|
||||
{note ? <Note note={note} /> : <div>Заметки не существует.</div>}
|
||||
{note ? <Note note={note} /> : <div>{locals.NoteNotExists}</div>}
|
||||
{note && (
|
||||
<div className="grid grid-cols-1">
|
||||
<div className="justify-self-center lg:justify-self-end">
|
||||
<ButtonWithIcon
|
||||
className="mt-4"
|
||||
href="/notes"
|
||||
text="Удалить"
|
||||
text={locals.Delete}
|
||||
icon={TrashIcon}
|
||||
onClick={() => {
|
||||
let notesObj = localStorage.getObj("Notes");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ButtonWithIcon } from "../components/button";
|
||||
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
|
||||
import printDate from "../components/utils";
|
||||
import { printDate } from "../components/utils";
|
||||
|
||||
function Notes() {
|
||||
let notes = Object.entries(localStorage.getObj("Notes"))
|
||||
|
@ -23,7 +23,7 @@ function Notes() {
|
|||
href={`/notes/${val[0]}`}
|
||||
reverse={true}
|
||||
icon={ChevronDoubleRightIcon}
|
||||
text="Перейти"
|
||||
text={locals.Open}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -34,7 +34,7 @@ function Notes() {
|
|||
if (notes.length === 0)
|
||||
return (
|
||||
<div className="md">
|
||||
<h3>Заметок пока нет</h3>
|
||||
<h3>{locals.NoNotesYet}</h3>
|
||||
</div>
|
||||
);
|
||||
return notes;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import printDate from "../components/utils";
|
||||
import { printDate } from "../components/utils";
|
||||
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline";
|
||||
import { ButtonWithIcon } from "../components/button";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
@ -12,22 +12,20 @@ function PubError() {
|
|||
<ButtonWithIcon
|
||||
className="mb-4"
|
||||
href="/"
|
||||
text="Вернуться"
|
||||
text={locals.Back}
|
||||
icon={ChevronDoubleLeftIcon}
|
||||
/>
|
||||
|
||||
<div className="border border-blue-300 rounded-lg p-4">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2">
|
||||
<h2 className="font-medium text-center lg:text-left leading-tight text-4xl mt-0 mb-2">
|
||||
Ошибка в публикации заметки
|
||||
{locals.PubError}
|
||||
</h2>
|
||||
<div className="justify-self-center lg:justify-self-end">
|
||||
{printDate(Date.now())}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full md">
|
||||
{err ? err : "Заметка не была опубликована из-за неизвестной ошибки"}
|
||||
</div>
|
||||
<div className="w-full md">{err ? err : locals.PubErrorMsg}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import RenderMarkdown from "../components/markdown";
|
||||
import { useState } from "react";
|
||||
import { Navigate, useParams } from "react-router-dom";
|
||||
import printDate from "../components/utils";
|
||||
import { printDate } from "../components/utils";
|
||||
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline";
|
||||
import { ButtonWithIcon } from "../components/button";
|
||||
|
||||
|
|
|
@ -9,6 +9,12 @@ function PubNoteSafe() {
|
|||
let params = useParams();
|
||||
|
||||
let [note, setNote] = useState(false);
|
||||
let nullNote = {
|
||||
text: locals.PubNoteNotExist,
|
||||
name: locals.Idontexists,
|
||||
time: Date.now(),
|
||||
code: 0,
|
||||
};
|
||||
|
||||
if (note === false)
|
||||
fetch(`/get-note/safe/${params.id}`)
|
||||
|
@ -20,21 +26,11 @@ function PubNoteSafe() {
|
|||
setNote(data);
|
||||
})
|
||||
.catch(() => {
|
||||
setNote({
|
||||
text: "Такой публичной заметки не сущуествует",
|
||||
name: "Меня не существует",
|
||||
time: Date.now(),
|
||||
code: 0,
|
||||
});
|
||||
setNote(nullNote);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setNote({
|
||||
text: "Такой публичной заметки не сущуествует",
|
||||
name: "Меня не существует",
|
||||
time: Date.now(),
|
||||
code: 0,
|
||||
});
|
||||
setNote(nullNote);
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -42,7 +38,7 @@ function PubNoteSafe() {
|
|||
<ButtonWithIcon
|
||||
className="mb-4"
|
||||
href="/"
|
||||
text="Писать"
|
||||
text={locals.Write}
|
||||
icon={ChevronDoubleLeftIcon}
|
||||
/>
|
||||
|
||||
|
@ -50,8 +46,7 @@ function PubNoteSafe() {
|
|||
<div className="p-4 mb-2">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2">
|
||||
<h2 className="font-medium text-center lg:text-left p-2">
|
||||
Ссылка для отправки публичной заметки. При переходе на эту ссылку,
|
||||
заметка исчезнет с сервера и будет сохранена локально.
|
||||
{locals.PubUrlPlaceholder}
|
||||
</h2>
|
||||
<CopyToClipboard
|
||||
text={`${window.location.origin}/pubNotes/${params.id}`}
|
||||
|
|
|
@ -16,10 +16,10 @@ function Publish() {
|
|||
};
|
||||
|
||||
if (!note.name) {
|
||||
err = "Заметка не была опубликована, так как отсутствует название.";
|
||||
err = locals.PubErrorMsgNoName;
|
||||
}
|
||||
if (!note.text) {
|
||||
err = "Заметка не была опубликована, так как отсутствует текст.";
|
||||
err = locals.PubErrorMsgNoText;
|
||||
}
|
||||
|
||||
fetch(`/publish`, {
|
||||
|
|
|
@ -3,53 +3,56 @@ import {
|
|||
SettingsTextInput,
|
||||
SettingsSelectInput,
|
||||
} from "../components/settingsInputs";
|
||||
import { reRenderPage } from "../components/utils";
|
||||
import Locales from "../localisation/main";
|
||||
|
||||
function Settings() {
|
||||
return (
|
||||
<div className="">
|
||||
<h1 className="text-center lg:text-left leading-tight text-2xl font-bold">
|
||||
Настройки
|
||||
{locals.Settings}
|
||||
</h1>
|
||||
|
||||
<SettingsPlaceholder text="Пользователь" />
|
||||
<SettingsPlaceholder text={locals.User} />
|
||||
|
||||
<SettingsTextInput
|
||||
placeholder="Имя"
|
||||
label="Имя пользователя"
|
||||
placeholder={locals.Name}
|
||||
label={locals.UserName}
|
||||
settingName="userName"
|
||||
/>
|
||||
|
||||
<SettingsTextInput
|
||||
placeholder="Ссылка"
|
||||
label="Ссылка на фото"
|
||||
placeholder={locals.Url}
|
||||
label={locals.PhotoUrl}
|
||||
settingName="userPhoto"
|
||||
/>
|
||||
|
||||
<SettingsTextInput
|
||||
placeholder="Статус"
|
||||
label="Статус пользователя"
|
||||
placeholder={locals.Status}
|
||||
label={locals.UserStatus}
|
||||
settingName="userStatus"
|
||||
/>
|
||||
|
||||
<SettingsPlaceholder text="Заметки" />
|
||||
<SettingsPlaceholder text={locals.Notes} />
|
||||
|
||||
<SettingsCheckBox
|
||||
label="Редактирование в предпросмотре"
|
||||
title="Может вызывать необратимые изменения текста, например ломает теги кода"
|
||||
label={locals.EditPreview}
|
||||
title={locals.EditPreviewWarn}
|
||||
checked={settings.editPreview}
|
||||
settingName="editPreview"
|
||||
/>
|
||||
|
||||
<SettingsCheckBox
|
||||
label="Публичная заметка"
|
||||
title="Если включено, то заметка будет видна всем пользователям"
|
||||
label={locals.PublicNote}
|
||||
title={locals.PublicNoteTitle}
|
||||
checked={settings.publicNote}
|
||||
settingName="publicNote"
|
||||
/>
|
||||
|
||||
<SettingsPlaceholder text="Интерфейс" />
|
||||
<SettingsPlaceholder text={locals.Interface} />
|
||||
|
||||
<SettingsSelectInput
|
||||
label="Язык"
|
||||
label={locals.Language}
|
||||
settingName="language"
|
||||
options={[
|
||||
{
|
||||
|
@ -58,16 +61,25 @@ function Settings() {
|
|||
},
|
||||
{
|
||||
value: "en",
|
||||
label: "English",
|
||||
label: "English (US)",
|
||||
},
|
||||
]}
|
||||
onChange={(e) => {
|
||||
window.locals =
|
||||
Locales[window.settings.language] ||
|
||||
Locales[navigator.language] ||
|
||||
Locales[navigator.userLanguage] ||
|
||||
Locales.en;
|
||||
|
||||
reRenderPage();
|
||||
}}
|
||||
/>
|
||||
|
||||
<SettingsPlaceholder text="Стороннее API" />
|
||||
<SettingsPlaceholder text={locals.ThirdPartyApi} />
|
||||
|
||||
<SettingsTextInput
|
||||
placeholder="Ключ"
|
||||
label="OpenAi API ключ"
|
||||
placeholder={locals.Key}
|
||||
label={locals.OpenAiKey}
|
||||
settingName="openAiKey"
|
||||
secret
|
||||
/>
|
||||
|
|
Loading…
Reference in a new issue