feat: localisation

This commit is contained in:
Artemy 2023-04-04 15:15:15 +03:00
parent e2708f1985
commit 8fb91d7ecb
15 changed files with 208 additions and 64 deletions

View file

@ -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>

View file

@ -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>

View file

@ -1,5 +1,5 @@
import RenderMarkdown from "../components/markdown";
import printDate from "./utils";
import { printDate } from "./utils";
function Note({ note }) {
return (

View file

@ -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
View 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
View 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
View 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;

View file

@ -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"

View file

@ -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");

View file

@ -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;

View file

@ -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>
);

View file

@ -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";

View file

@ -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}`}

View file

@ -16,10 +16,10 @@ function Publish() {
};
if (!note.name) {
err = "Заметка не была опубликована, так как отсутствует название.";
err = locals.PubErrorMsgNoName;
}
if (!note.text) {
err = "Заметка не была опубликована, так как отсутствует текст.";
err = locals.PubErrorMsgNoText;
}
fetch(`/publish`, {

View file

@ -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
/>